geo-intersection-util
Version:
Point of intersection defined by its offset from the centerline and distance along the centerline
242 lines (241 loc) • 10.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Intersection = void 0;
class Intersection {
constructor() { }
getInterSection(pathString, shotLatLngStr, // changed from LatLng to string
greenCenter) {
try {
const shotLatLng = this.parseLatLng(shotLatLngStr); // <-- parse it
pathString = this.orientCenterLine(pathString, greenCenter);
const centerPath = this.getPoint(pathString, 500);
const intersectionLatLng = this.findIntersectionPoint(shotLatLng, centerPath);
if (intersectionLatLng) {
const cartesianPoint1 = this.latLngToCartesian(shotLatLng.lat, shotLatLng.lng);
const cartesianIntersection = this.latLngToCartesian(intersectionLatLng.lat, intersectionLatLng.lng);
const distanceInEuclidean = this.euclideanDistance(cartesianPoint1, cartesianIntersection);
const side = this.determineSideOnCurvedCenterline(centerPath, shotLatLng);
return {
intersectionLatLng,
distance: distanceInEuclidean,
side,
};
}
return {
intersectionLatLng: { lat: 0, lng: 0 },
distance: 0,
side: null,
};
}
catch (e) {
console.error(e, 'getInterSection Error');
}
}
calculateBearing(lat1, lng1, lat2, lng2) {
try {
const lat1Rad = (lat1 * Math.PI) / 180;
const lat2Rad = (lat2 * Math.PI) / 180;
const deltaLngRad = ((lng2 - lng1) * Math.PI) / 180;
const y = Math.sin(deltaLngRad) * Math.cos(lat2Rad);
const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) -
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(deltaLngRad);
const bearingRad = Math.atan2(y, x);
const bearingDeg = (bearingRad * 180) / Math.PI;
return (bearingDeg + 360) % 360;
}
catch (e) {
console.error(e, 'calculateBearing Error');
return 0;
}
}
extendLatLng(lat, lng, distance, bearing) {
try {
const R = 6378137;
const distanceRad = distance / R;
const bearingRad = (bearing * Math.PI) / 180;
const latRad = (lat * Math.PI) / 180;
const lngRad = (lng * Math.PI) / 180;
const newLatRad = Math.asin(Math.sin(latRad) * Math.cos(distanceRad) +
Math.cos(latRad) * Math.sin(distanceRad) * Math.cos(bearingRad));
const newLngRad = lngRad +
Math.atan2(Math.sin(bearingRad) * Math.sin(distanceRad) * Math.cos(latRad), Math.cos(distanceRad) - Math.sin(latRad) * Math.sin(newLatRad));
return {
lat: (newLatRad * 180) / Math.PI,
lng: (newLngRad * 180) / Math.PI,
};
}
catch (e) {
console.error(e, 'extendLatLng Error');
return { lat, lng };
}
}
getPoint(pointString, extensionDistance) {
try {
const points = this.getLatLngFromPointsStr(pointString);
if (points.length === 2) {
const [firstPoint, secondPoint] = points;
const forwardBearing = this.calculateBearing(firstPoint.lat, firstPoint.lng, secondPoint.lat, secondPoint.lng);
const reverseBearing = (forwardBearing + 180) % 360;
const extendedFirst = this.extendLatLng(firstPoint.lat, firstPoint.lng, extensionDistance, reverseBearing);
const extendedSecond = this.extendLatLng(secondPoint.lat, secondPoint.lng, extensionDistance, forwardBearing);
return [extendedFirst, firstPoint, secondPoint, extendedSecond];
}
const lastPoint = points[points.length - 1];
const secondLast = points[points.length - 2];
const bearing = this.calculateBearing(secondLast.lat, secondLast.lng, lastPoint.lat, lastPoint.lng);
const newPoint = this.extendLatLng(lastPoint.lat, lastPoint.lng, extensionDistance, bearing);
return [...points, newPoint];
}
catch (e) {
console.error(e, 'getPoint Error');
return [];
}
}
determineSideOnCurvedCenterline(centerlinePoints, point) {
try {
let closestSegmentIndex = -1;
let minDistance = Infinity;
for (let i = 0; i < centerlinePoints.length - 1; i++) {
const start = centerlinePoints[i];
const end = centerlinePoints[i + 1];
const t = Math.max(0, Math.min(1, ((point.lng - start.lng) * (end.lng - start.lng) +
(point.lat - start.lat) * (end.lat - start.lat)) /
(Math.pow((end.lng - start.lng), 2) + Math.pow((end.lat - start.lat), 2))));
const closest = {
lng: start.lng + t * (end.lng - start.lng),
lat: start.lat + t * (end.lat - start.lat),
};
const distance = this.getDistance(point, closest);
if (distance < minDistance) {
minDistance = distance;
closestSegmentIndex = i;
}
}
if (closestSegmentIndex >= 0) {
const start = centerlinePoints[closestSegmentIndex];
const end = centerlinePoints[closestSegmentIndex + 1];
const centerVec = { x: end.lng - start.lng, y: end.lat - start.lat };
const pointVec = { x: point.lng - start.lng, y: point.lat - start.lat };
const crossProduct = centerVec.x * pointVec.y - centerVec.y * pointVec.x;
if (crossProduct > 0)
return 'left';
else if (crossProduct < 0)
return 'right';
else
return 'center';
}
return 'error - could not determine side';
}
catch (e) {
console.error(e, 'determineSideOnCurvedCenterline Error');
return 'error';
}
}
euclideanDistance(p1, p2) {
return Math.sqrt(Math.pow((p1.x - p2.x), 2) + Math.pow((p1.y - p2.y), 2));
}
latLngToCartesian(lat, lng) {
const R = 6378137;
return {
x: R * ((lng * Math.PI) / 180),
y: R * Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360)),
};
}
getDistance(p1, p2) {
return Math.sqrt(Math.pow((p1.lat - p2.lat), 2) + Math.pow((p1.lng - p2.lng), 2));
}
distance(p1, p2) {
return Math.sqrt(Math.pow((p1.x - p2.x), 2) + Math.pow((p1.y - p2.y), 2));
}
findPerpendicularIntersection(p, p1, p2) {
try {
const A = p.x - p1.x;
const B = p.y - p1.y;
const C = p2.x - p1.x;
const D = p2.y - p1.y;
const dot = A * C + B * D;
const lenSq = Math.pow(C, 2) + Math.pow(D, 2);
const param = lenSq !== 0 ? dot / lenSq : -1;
if (param < 0 || param > 1)
return null;
return {
x: p1.x + param * C,
y: p1.y + param * D,
};
}
catch (e) {
console.error(e, 'findPerpendicularIntersection Error');
return null;
}
}
cartesianToLatLng(p) {
const R = 6378137;
return {
lng: (p.x / R) * (180 / Math.PI),
lat: (2 * Math.atan(Math.exp(p.y / R)) - Math.PI / 2) * (180 / Math.PI),
};
}
findIntersectionPoint(point, centerLine) {
try {
const cartPoint = this.latLngToCartesian(point.lat, point.lng);
let closest = null;
let minDistance = Infinity;
for (let i = 0; i < centerLine.length - 1; i++) {
const start = this.latLngToCartesian(centerLine[i].lat, centerLine[i].lng);
const end = this.latLngToCartesian(centerLine[i + 1].lat, centerLine[i + 1].lng);
const inter = this.findPerpendicularIntersection(cartPoint, start, end);
const nearest = inter !== null && inter !== void 0 ? inter : (this.distance(cartPoint, start) < this.distance(cartPoint, end) ? start : end);
const dist = this.distance(cartPoint, nearest);
if (dist < minDistance) {
minDistance = dist;
closest = nearest;
}
}
return closest ? this.cartesianToLatLng(closest) : null;
}
catch (e) {
console.error(e, 'findIntersectionPoint Error');
return null;
}
}
getLatLngFromPointsStr(pointsStr) {
try {
const segments = pointsStr.split(',');
return segments.map((seg) => {
const [lng, lat] = seg.trim().split(' ').map(Number);
return { lat, lng };
});
}
catch (e) {
console.error(e, 'getLatLngFromPointsStr Error');
return [];
}
}
parseCoordinate(coordinate) {
const [x, y] = coordinate.split(' ').map(Number);
return { x, y };
}
orientCenterLine(centerLine, greenCenter) {
const coords = centerLine.split(',');
const target = this.parseCoordinate(greenCenter);
let closestIndex = 0;
let minDistance = Infinity;
coords.forEach((coord, index) => {
const parsed = this.parseCoordinate(coord);
const distance = this.euclideanDistance(parsed, target);
if (distance < minDistance) {
minDistance = distance;
closestIndex = index;
}
});
if (closestIndex === 0) {
return coords.reverse().map((x) => x.trim()).join(',');
}
return coords.map((x) => x.trim()).join(',');
}
parseLatLng(coordStr) {
const [lng, lat] = coordStr.trim().split(' ').map(Number);
return { lat, lng };
}
}
exports.Intersection = Intersection;