mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-02-04 09:14:30 +00:00
104 lines
4.4 KiB
JavaScript
104 lines
4.4 KiB
JavaScript
//
|
|
// GEOGRAPHIC UTILITY FUNCTIONS
|
|
// Great Circle calculation, Maidenhead grid calcs, etc.
|
|
//
|
|
|
|
// Calculate great circle bearing between two lat/lon points.
|
|
function calcBearing(lat1, lon1, lat2, lon2) {
|
|
lat1 *= Math.PI / 180;
|
|
lon1 *= Math.PI / 180;
|
|
lat2 *= Math.PI / 180;
|
|
lon2 *= Math.PI / 180;
|
|
var lonDelta = lon2 - lon1;
|
|
var y = Math.sin(lonDelta) * Math.cos(lat2);
|
|
var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lonDelta);
|
|
var bearing = Math.atan2(y, x);
|
|
bearing = bearing * (180 / Math.PI);
|
|
if ( bearing < 0 ) { bearing += 360; }
|
|
return bearing;
|
|
}
|
|
|
|
// Convert a Maidenhead grid reference of arbitrary precision to the lat/long of the centre point of the square.
|
|
// Returns null if the grid format is invalid.
|
|
function latLonForGridCentre(grid) {
|
|
let [lat, lon, latCellSize, lonCellSize] = latLonForGridSWCornerPlusSize(grid);
|
|
if (lat != null && lon != null && latCellSize != null && lonCellSize != null) {
|
|
return [lat + latCellSize / 2.0, lon + lonCellSize / 2.0];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Convert a Maidenhead grid reference of arbitrary precision to lat/long, including in the result the size of the
|
|
// lowest grid square. This is a utility method used by the main methods that return the centre, southwest, and
|
|
// northeast coordinates of a grid square.
|
|
// The return type is always an array of size 4. The elements in it are null if the grid format is invalid.
|
|
function latLonForGridSWCornerPlusSize(grid) {
|
|
// Make sure we are in upper case so our maths works. Case is arbitrary for Maidenhead references
|
|
grid = grid.toUpperCase();
|
|
|
|
// Return null if our Maidenhead string is invalid or too short
|
|
let len = grid.length;
|
|
if (len <= 0 || (len % 2) !== 0) {
|
|
return [null, null, null, null];
|
|
}
|
|
|
|
let lat = 0.0; // aggregated latitude
|
|
let lon = 0.0; // aggregated longitude
|
|
let latCellSize = 10; // Size in degrees latitude of the current cell. Starts at 20 and gets smaller as the calculation progresses
|
|
let lonCellSize = 20; // Size in degrees longitude of the current cell. Starts at 20 and gets smaller as the calculation progresses
|
|
let latCellNo; // grid latitude cell number this time
|
|
let lonCellNo; // grid longitude cell number this time
|
|
|
|
// Iterate through blocks (two-character sections)
|
|
for (let block = 0; block * 2 < len; block += 1) {
|
|
if (block % 2 === 0) {
|
|
// Letters in this block
|
|
lonCellNo = grid.charCodeAt(block * 2) - 'A'.charCodeAt(0);
|
|
latCellNo = grid.charCodeAt(block * 2 + 1) - 'A'.charCodeAt(0);
|
|
// Bail if the values aren't in range. Allowed values are A-R (0-17) for the first letter block, or
|
|
// A-X (0-23) thereafter.
|
|
let maxCellNo = (block === 0) ? 17 : 23;
|
|
if (latCellNo < 0 || latCellNo > maxCellNo || lonCellNo < 0 || lonCellNo > maxCellNo) {
|
|
return [null, null, null, null];
|
|
}
|
|
} else {
|
|
// Numbers in this block
|
|
lonCellNo = parseInt(grid.charAt(block * 2));
|
|
latCellNo = parseInt(grid.charAt(block * 2 + 1));
|
|
// Bail if the values aren't in range 0-9..
|
|
if (latCellNo < 0 || latCellNo > 9 || lonCellNo < 0 || lonCellNo > 9) {
|
|
return [null, null, null, null];
|
|
}
|
|
}
|
|
|
|
// Aggregate the angles
|
|
lat += latCellNo * latCellSize;
|
|
lon += lonCellNo * lonCellSize;
|
|
|
|
// Reduce the cell size for the next block, unless we are on the last cell.
|
|
if (block * 2 < len - 2) {
|
|
// Still have more work to do, so reduce the cell size
|
|
if (block % 2 === 0) {
|
|
// Just dealt with letters, next block will be numbers so cells will be 1/10 the current size
|
|
latCellSize = latCellSize / 10.0;
|
|
lonCellSize = lonCellSize / 10.0;
|
|
} else {
|
|
// Just dealt with numbers, next block will be letters so cells will be 1/24 the current size
|
|
latCellSize = latCellSize / 24.0;
|
|
lonCellSize = lonCellSize / 24.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Offset back to (-180, -90) where the grid starts
|
|
lon -= 180.0;
|
|
lat -= 90.0;
|
|
|
|
// Return nulls on maths errors
|
|
if (isNaN(lat) || isNaN(lon) || isNaN(latCellSize) || isNaN(lonCellSize)) {
|
|
return [null, null, null, null];
|
|
}
|
|
|
|
return [lat, lon, latCellSize, lonCellSize];
|
|
} |