mirror of
https://github.com/magicbug/Cloudlog
synced 2026-08-13 17:49:35 -04:00
Introduce a frontend QRA utilities module and switch QSO locator/bearing/distance logic to run client-side. Added assets/js/qra-utils.js (ported from the PHP Qra library), injected measurement_base and station_gridsquares into the QSO view, and updated application/controllers/Qso.php to provide the measurement_base for the frontend. Footer now loads qra-utils.js before qso.js. Modified assets/js/sections/qso.js to debounce locator input, place map markers from local grid→lat/lng math, and compute bearing/distance via QraUtils (removes several AJAX calls to server endpoints). This reduces server round-trips and keeps frontend behavior consistent with the PHP implementation.
151 lines
5.8 KiB
JavaScript
151 lines
5.8 KiB
JavaScript
/**
|
||
* QRA (Maidenhead grid locator) utilities
|
||
* Ported from application/libraries/Qra.php — output is byte-for-byte identical.
|
||
*/
|
||
(function (global) {
|
||
'use strict';
|
||
|
||
function deg2rad(deg) { return deg * Math.PI / 180; }
|
||
function rad2deg(rad) { return rad * 180 / Math.PI; }
|
||
|
||
/**
|
||
* Convert a Maidenhead locator to [lat, lng].
|
||
* Supports 4-, 6-, 8-, 10-char grids and comma-separated multi-grids.
|
||
* Returns [lat, lng] or false on invalid input.
|
||
*/
|
||
function qra2latlong(strQRA) {
|
||
if (!strQRA || typeof strQRA !== 'string') return false;
|
||
strQRA = strQRA.replace(/\s+/g, '');
|
||
|
||
if (strQRA.indexOf(',') !== -1) {
|
||
var parts = strQRA.split(',');
|
||
if (parts.length === 4) {
|
||
var lengths = parts.map(function (p) { return p.length; });
|
||
if (lengths.some(function (l) { return l !== lengths[0]; })) return false;
|
||
var coords = [0, 0];
|
||
for (var i = 0; i < 4; i++) {
|
||
var c = qra2latlong(parts[i]);
|
||
if (!c) return false;
|
||
coords[0] += c[0];
|
||
coords[1] += c[1];
|
||
}
|
||
return [Math.round(coords[0] / 4), Math.round(coords[1] / 4)];
|
||
} else if (parts.length === 2) {
|
||
if (parts[0].length !== parts[1].length) return false;
|
||
var c0 = qra2latlong(parts[0]);
|
||
var c1 = qra2latlong(parts[1]);
|
||
if (!c0 || !c1) return false;
|
||
var lat = c0[0] !== c1[0]
|
||
? Math.round(((c0[0] + c1[0]) / 2) * 10) / 10
|
||
: Math.round(c0[0] * 10) / 10;
|
||
var lng = c0[1] !== c1[1]
|
||
? Math.round((c0[1] + c1[1]) / 2)
|
||
: Math.round(c0[1]);
|
||
return [lat, lng];
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (strQRA.length % 2 !== 0 || strQRA.length > 10) return [0, 0];
|
||
strQRA = strQRA.toUpperCase();
|
||
if (strQRA.length === 4) strQRA += 'LL';
|
||
if (strQRA.length === 6) strQRA += '55';
|
||
if (strQRA.length === 8) strQRA += 'LL';
|
||
|
||
if (!/^[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}$/.test(strQRA)) return false;
|
||
|
||
var ch = strQRA.split('');
|
||
var A = 'A'.charCodeAt(0);
|
||
var Z = '0'.charCodeAt(0);
|
||
var a = ch[0].charCodeAt(0) - A;
|
||
var b = ch[1].charCodeAt(0) - A;
|
||
var c = ch[2].charCodeAt(0) - Z;
|
||
var d = ch[3].charCodeAt(0) - Z;
|
||
var e = ch[4].charCodeAt(0) - A;
|
||
var f = ch[5].charCodeAt(0) - A;
|
||
var g = ch[6].charCodeAt(0) - Z;
|
||
var h = ch[7].charCodeAt(0) - Z;
|
||
var ii = ch[8].charCodeAt(0) - A;
|
||
var j = ch[9].charCodeAt(0) - A;
|
||
|
||
var nLong = (a * 20) + (c * 2) + (e / 12) + (g / 120) + (ii / 2880) - 180;
|
||
var nLat = (b * 10) + d + (f / 24) + (h / 240) + (j / 5760) - 90;
|
||
|
||
return [nLat, nLong];
|
||
}
|
||
|
||
/**
|
||
* Distance between two lat/lng points.
|
||
* unit: 'M' miles | 'K' kilometers | 'N' nautical miles
|
||
* Returns numeric distance rounded to 1 decimal (matches PHP distance()).
|
||
*/
|
||
function calcDistance(lat1, lon1, lat2, lon2, unit) {
|
||
var theta = lon1 - lon2;
|
||
var dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) +
|
||
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
|
||
dist = Math.acos(Math.min(1, Math.max(-1, dist)));
|
||
dist = rad2deg(dist);
|
||
dist = dist * 60 * 1.1515;
|
||
if (unit === 'K') dist *= 1.609344;
|
||
else if (unit === 'N') dist *= 0.8684;
|
||
return Math.round(dist * 10) / 10;
|
||
}
|
||
|
||
/**
|
||
* Bearing in degrees (0–359) from point 1 to point 2 (matches PHP get_bearing()).
|
||
*/
|
||
function getBearing(lat1, lon1, lat2, lon2) {
|
||
return (Math.round(rad2deg(Math.atan2(
|
||
Math.sin(deg2rad(lon2) - deg2rad(lon1)) * Math.cos(deg2rad(lat2)),
|
||
Math.cos(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) -
|
||
Math.sin(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(lon2) - deg2rad(lon1))
|
||
))) + 360) % 360;
|
||
}
|
||
|
||
/**
|
||
* Full bearing string matching PHP Qra::bearing() output exactly.
|
||
* e.g. "135° SE 245 kilometers"
|
||
* myGrid / theirGrid: Maidenhead locator strings
|
||
* unit: 'M' | 'K' | 'N'
|
||
*/
|
||
function bearingString(myGrid, theirGrid, unit) {
|
||
var my = qra2latlong(myGrid);
|
||
var stn = qra2latlong(theirGrid);
|
||
if (!my || !stn) return '';
|
||
|
||
var bearingDeg = getBearing(my[0], my[1], stn[0], stn[1]);
|
||
var dist = Math.round(calcDistance(my[0], my[1], stn[0], stn[1], unit || 'M')); // rounded to 0 d.p. — matches PHP
|
||
|
||
var dirs = ['N', 'E', 'S', 'W'];
|
||
var rounded = Math.round(bearingDeg / 22.5) % 16;
|
||
var dir;
|
||
if ((rounded % 4) === 0) {
|
||
dir = dirs[rounded / 4];
|
||
} else {
|
||
dir = dirs[2 * Math.floor(((Math.floor(rounded / 4) + 1) % 4) / 2)];
|
||
dir += dirs[1 + 2 * Math.floor(rounded / 8)];
|
||
}
|
||
|
||
var unitLabel = unit === 'K' ? ' kilometers' : unit === 'N' ? ' nautic miles' : ' miles';
|
||
return Math.round(bearingDeg) + '\u00B0 ' + dir + ' ' + dist + unitLabel;
|
||
}
|
||
|
||
/**
|
||
* Distance between two gridsquares in km (matches PHP searchdistance() which hardcodes 'K').
|
||
* Returns numeric value or null on invalid input.
|
||
*/
|
||
function distanceKm(myGrid, theirGrid) {
|
||
var my = qra2latlong(myGrid);
|
||
var stn = qra2latlong(theirGrid);
|
||
if (!my || !stn) return null;
|
||
return calcDistance(my[0], my[1], stn[0], stn[1], 'K');
|
||
}
|
||
|
||
global.QraUtils = {
|
||
qra2latlong: qra2latlong,
|
||
bearingString: bearingString,
|
||
distanceKm: distanceKm,
|
||
};
|
||
|
||
}(window));
|