UNPKG

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
"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;