fix: 修正 Cesium 巡航车道中心对齐
This commit is contained in:
161
scripts/lib/lane-geometry.js
Normal file
161
scripts/lib/lane-geometry.js
Normal file
@@ -0,0 +1,161 @@
|
||||
"use strict";
|
||||
|
||||
const EARTH_RADIUS_METERS = 6371008.8;
|
||||
|
||||
function laneCenterline(lane) {
|
||||
const ring = lane?.geometry?.type === "Polygon" ? lane.geometry.coordinates?.[0] : null;
|
||||
if (!Array.isArray(ring) || ring.length < 5 || !sameCoordinate(ring[0], ring.at(-1))) return null;
|
||||
const vertices = ring.slice(0, -1);
|
||||
if (!vertices.every(validCoordinate)) return null;
|
||||
const half = vertices.length / 2;
|
||||
if (!Number.isInteger(half) || half < 2) return null;
|
||||
const centerline = vertices.slice(0, half).map((point, index) => [
|
||||
(point[0] + vertices[vertices.length - 1 - index][0]) / 2,
|
||||
(point[1] + vertices[vertices.length - 1 - index][1]) / 2,
|
||||
]);
|
||||
return polylineLength(centerline) > 0.01 ? centerline : null;
|
||||
}
|
||||
|
||||
function orientPolyline(polyline, reference) {
|
||||
if (!polyline?.length || !reference?.length) return null;
|
||||
const forward = projectedDistanceAlong(reference, polyline.at(-1)) - projectedDistanceAlong(reference, polyline[0]);
|
||||
if (Math.abs(forward) < 0.01) return null;
|
||||
return forward > 0 ? polyline.map(copyCoordinate) : [...polyline].reverse().map(copyCoordinate);
|
||||
}
|
||||
|
||||
function stitchPolylines(polylines, maxGapMeters) {
|
||||
if (!polylines.length) return null;
|
||||
const result = [];
|
||||
for (const polyline of polylines) {
|
||||
if (!polyline?.length) return null;
|
||||
if (result.length && haversineMeters(result.at(-1), polyline[0]) > maxGapMeters) return null;
|
||||
appendCoordinates(result, polyline);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function projectedDistanceAlong(polyline, point) {
|
||||
let traversed = 0;
|
||||
let best = { distance: Infinity, along: 0, lateral: 0 };
|
||||
for (let index = 1; index < polyline.length; index += 1) {
|
||||
const start = polyline[index - 1];
|
||||
const end = polyline[index];
|
||||
const meters = metersAt((start[1] + end[1]) / 2);
|
||||
const dx = (end[0] - start[0]) * meters.lon;
|
||||
const dy = (end[1] - start[1]) * meters.lat;
|
||||
const px = (point[0] - start[0]) * meters.lon;
|
||||
const py = (point[1] - start[1]) * meters.lat;
|
||||
const length = Math.hypot(dx, dy);
|
||||
if (length < 0.001) continue;
|
||||
const ratio = Math.max(0, Math.min(1, (px * dx + py * dy) / (length * length)));
|
||||
const offsetX = px - dx * ratio;
|
||||
const offsetY = py - dy * ratio;
|
||||
const distance = Math.hypot(offsetX, offsetY);
|
||||
if (distance < best.distance) {
|
||||
const rightX = dy / length;
|
||||
const rightY = -dx / length;
|
||||
best = {
|
||||
distance,
|
||||
along: traversed + length * ratio,
|
||||
lateral: offsetX * rightX + offsetY * rightY,
|
||||
};
|
||||
}
|
||||
traversed += length;
|
||||
}
|
||||
return best.along;
|
||||
}
|
||||
|
||||
function lateralOffsetFrom(polyline, point) {
|
||||
let best = null;
|
||||
for (let index = 1; index < polyline.length; index += 1) {
|
||||
const start = polyline[index - 1];
|
||||
const end = polyline[index];
|
||||
const meters = metersAt((start[1] + end[1]) / 2);
|
||||
const dx = (end[0] - start[0]) * meters.lon;
|
||||
const dy = (end[1] - start[1]) * meters.lat;
|
||||
const px = (point[0] - start[0]) * meters.lon;
|
||||
const py = (point[1] - start[1]) * meters.lat;
|
||||
const length = Math.hypot(dx, dy);
|
||||
if (length < 0.001) continue;
|
||||
const ratio = Math.max(0, Math.min(1, (px * dx + py * dy) / (length * length)));
|
||||
const offsetX = px - dx * ratio;
|
||||
const offsetY = py - dy * ratio;
|
||||
const distance = Math.hypot(offsetX, offsetY);
|
||||
if (!best || distance < best.distance) {
|
||||
best = { distance, lateral: offsetX * dy / length - offsetY * dx / length };
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function polylineMidpoint(polyline) {
|
||||
const target = polylineLength(polyline) / 2;
|
||||
let traversed = 0;
|
||||
for (let index = 1; index < polyline.length; index += 1) {
|
||||
const length = haversineMeters(polyline[index - 1], polyline[index]);
|
||||
if (traversed + length >= target) {
|
||||
const ratio = length ? (target - traversed) / length : 0;
|
||||
return [
|
||||
polyline[index - 1][0] + (polyline[index][0] - polyline[index - 1][0]) * ratio,
|
||||
polyline[index - 1][1] + (polyline[index][1] - polyline[index - 1][1]) * ratio,
|
||||
];
|
||||
}
|
||||
traversed += length;
|
||||
}
|
||||
return polyline.length ? copyCoordinate(polyline.at(-1)) : null;
|
||||
}
|
||||
|
||||
function polylineLength(polyline) {
|
||||
let total = 0;
|
||||
for (let index = 1; index < (polyline?.length || 0); index += 1) {
|
||||
total += haversineMeters(polyline[index - 1], polyline[index]);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
function haversineMeters(a, b) {
|
||||
const lat1 = degreesToRadians(a[1]);
|
||||
const lat2 = degreesToRadians(b[1]);
|
||||
const dLat = degreesToRadians(b[1] - a[1]);
|
||||
const dLon = degreesToRadians(b[0] - a[0]);
|
||||
const h = Math.sin(dLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
|
||||
return 2 * EARTH_RADIUS_METERS * Math.asin(Math.min(1, Math.sqrt(h)));
|
||||
}
|
||||
|
||||
function appendCoordinates(target, coordinates) {
|
||||
for (const coordinate of coordinates) {
|
||||
if (!sameCoordinate(target.at(-1), coordinate)) target.push(copyCoordinate(coordinate));
|
||||
}
|
||||
}
|
||||
|
||||
function validCoordinate(value) {
|
||||
return Array.isArray(value) && value.length >= 2 && Number.isFinite(value[0]) && Number.isFinite(value[1]);
|
||||
}
|
||||
|
||||
function sameCoordinate(a, b) {
|
||||
return Boolean(a && b && a[0] === b[0] && a[1] === b[1]);
|
||||
}
|
||||
|
||||
function copyCoordinate(coordinate) {
|
||||
return [coordinate[0], coordinate[1]];
|
||||
}
|
||||
|
||||
function metersAt(latitude) {
|
||||
return { lon: 111320 * Math.cos(degreesToRadians(latitude)), lat: 111320 };
|
||||
}
|
||||
|
||||
function degreesToRadians(value) {
|
||||
return value * Math.PI / 180;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
appendCoordinates,
|
||||
haversineMeters,
|
||||
laneCenterline,
|
||||
lateralOffsetFrom,
|
||||
orientPolyline,
|
||||
polylineLength,
|
||||
polylineMidpoint,
|
||||
projectedDistanceAlong,
|
||||
stitchPolylines,
|
||||
};
|
||||
Reference in New Issue
Block a user