marco/app/utils/geohash-coverage.js
2026-01-24 17:54:34 +07:00

79 lines
2.3 KiB
JavaScript

import Geohash from 'latlon-geohash';
/**
* Calculates 4-character geohash prefixes that cover the given bounding box.
*
* @param {Object} bbox
* @param {number} bbox.minLat
* @param {number} bbox.minLon
* @param {number} bbox.maxLat
* @param {number} bbox.maxLon
* @returns {string[]} Array of unique 4-character geohash prefixes
*/
export function getGeohashPrefixesInBbox(bbox) {
const { minLat, minLon, maxLat, maxLon } = bbox;
const prefixes = new Set();
// 4-char geohash precision is approx 20km x 39km at equator.
// We can step through the bbox in increments smaller than that to ensure coverage.
// Latitude: 1 deg ~= 111km. 20km ~= 0.18 deg.
// Longitude: 1 deg ~= 111km (at equator). 39km ~= 0.35 deg.
// Let's use conservative steps to hit every cell.
// 0.1 degree steps should be safe enough for 4-char hashes.
// Safety check to avoid infinite loops or massive arrays if bbox is weird
if (Math.abs(maxLat - minLat) > 20 || Math.abs(maxLon - minLon) > 20) {
console.warn(
'BBox too large for 4-char geohash scanning, aborting fine scan.'
);
return [];
}
const latStep = 0.1;
const lonStep = 0.1;
for (let lat = minLat; lat <= maxLat + latStep; lat += latStep) {
for (let lon = minLon; lon <= maxLon + lonStep; lon += lonStep) {
// Clamp to bbox for the edge cases
const cLat = Math.min(lat, maxLat);
const cLon = Math.min(lon, maxLon);
try {
const hash = Geohash.encode(cLat, cLon, 4);
prefixes.add(hash);
// eslint-disable-next-line no-unused-vars
} catch (e) {
// Ignore invalid coords if any
}
}
}
// Ensure corners are definitely included (floating point steps might miss slightly)
try {
prefixes.add(Geohash.encode(minLat, minLon, 4));
// eslint-disable-next-line no-unused-vars
} catch (e) {
/* ignore */
}
try {
prefixes.add(Geohash.encode(maxLat, maxLon, 4));
// eslint-disable-next-line no-unused-vars
} catch (e) {
/* ignore */
}
try {
prefixes.add(Geohash.encode(minLat, maxLon, 4));
// eslint-disable-next-line no-unused-vars
} catch (e) {
/* ignore */
}
try {
prefixes.add(Geohash.encode(maxLat, minLon, 4));
// eslint-disable-next-line no-unused-vars
} catch (e) {
/* ignore */
}
return Array.from(prefixes);
}