126 lines
4.4 KiB
JavaScript

"use strict";
/**
* Spatial utility functions for geographic calculations.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.isPointInPolygon = isPointInPolygon;
exports.parseGeoJsonPolygon = parseGeoJsonPolygon;
exports.calculateBounds = calculateBounds;
exports.haversineDistance = haversineDistance;
exports.boundsFromCenterZoom = boundsFromCenterZoom;
exports.calculateCentroid = calculateCentroid;
/**
* Ray-casting algorithm to determine if a point is inside a polygon.
* @param lat Point latitude
* @param lng Point longitude
* @param polygonCoords Array of [lng, lat] coordinate pairs (GeoJSON order)
*/
function isPointInPolygon(lat, lng, polygonCoords) {
let inside = false;
for (let i = 0, j = polygonCoords.length - 1; i < polygonCoords.length; j = i++) {
const xi = polygonCoords[i][1]; // lat
const yi = polygonCoords[i][0]; // lng
const xj = polygonCoords[j][1];
const yj = polygonCoords[j][0];
const intersect = ((yi > lng) !== (yj > lng)) &&
(lat < (xj - xi) * (lng - yi) / (yj - yi) + xi);
if (intersect)
inside = !inside;
}
return inside;
}
/**
* Parse GeoJSON string and extract coordinate arrays for all polygons.
* Supports Polygon and MultiPolygon types.
* Returns array of coordinate rings (outer rings only).
*/
function parseGeoJsonPolygon(geojsonString) {
const geojson = JSON.parse(geojsonString);
if (geojson.type === 'Polygon') {
// Polygon coordinates: [ring][point][lng,lat]
return [geojson.coordinates[0]];
}
if (geojson.type === 'MultiPolygon') {
// MultiPolygon coordinates: [polygon][ring][point][lng,lat]
return geojson.coordinates.map((poly) => poly[0]);
}
throw new Error(`Unsupported GeoJSON type: ${geojson.type}`);
}
/**
* Calculate bounding box from an array of [lng, lat] coordinate pairs.
* Returns { minLat, maxLat, minLng, maxLng }.
*/
function calculateBounds(coordinates) {
let minLat = Infinity;
let maxLat = -Infinity;
let minLng = Infinity;
let maxLng = -Infinity;
for (const coord of coordinates) {
const lng = coord[0];
const lat = coord[1];
if (lat < minLat)
minLat = lat;
if (lat > maxLat)
maxLat = lat;
if (lng < minLng)
minLng = lng;
if (lng > maxLng)
maxLng = lng;
}
return { minLat, maxLat, minLng, maxLng };
}
/**
* Haversine distance between two lat/lng points in meters.
*/
function haversineDistance(lat1, lng1, lat2, lng2) {
const R = 6371000; // Earth radius in meters
const toRad = (deg) => (deg * Math.PI) / 180;
const dLat = toRad(lat2 - lat1);
const dLng = toRad(lng2 - lng1);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(dLng / 2) * Math.sin(dLng / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
/**
* Calculate the centroid of an array of [lng, lat] coordinate pairs.
* Returns { lat, lng }.
*/
/**
* Calculate a bounding box from a map center point and zoom level.
* Uses Web Mercator tile math to derive the geographic extent visible
* on a map of the given pixel dimensions at the given zoom.
* Default viewport: 1024x768 pixels.
*/
function boundsFromCenterZoom(lat, lng, zoom, widthPx = 1024, heightPx = 768) {
// Meters per pixel at a given zoom level at the equator
const metersPerPixelAtEquator = 156543.03392;
const metersPerPixel = metersPerPixelAtEquator * Math.cos((lat * Math.PI) / 180) / Math.pow(2, zoom);
const halfWidthMeters = (widthPx / 2) * metersPerPixel;
const halfHeightMeters = (heightPx / 2) * metersPerPixel;
// Convert meters offset to degrees
const latDegPerMeter = 1 / 111320;
const lngDegPerMeter = 1 / (111320 * Math.cos((lat * Math.PI) / 180));
const latOffset = halfHeightMeters * latDegPerMeter;
const lngOffset = halfWidthMeters * lngDegPerMeter;
return {
minLat: lat - latOffset,
maxLat: lat + latOffset,
minLng: lng - lngOffset,
maxLng: lng + lngOffset,
};
}
function calculateCentroid(coordinates) {
let sumLat = 0;
let sumLng = 0;
for (const coord of coordinates) {
sumLng += coord[0];
sumLat += coord[1];
}
return {
lat: sumLat / coordinates.length,
lng: sumLng / coordinates.length,
};
}
//# sourceMappingURL=spatial.js.map