mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-07 10:47:50 +00:00
202 lines
6.0 KiB
TypeScript
202 lines
6.0 KiB
TypeScript
import { TrackPoint } from './gpx';
|
|
import { Coordinates } from './types';
|
|
|
|
export type SimplifiedTrackPoint = { point: TrackPoint; distance?: number };
|
|
|
|
const earthRadius = 6371008.8;
|
|
|
|
export function ramerDouglasPeucker(
|
|
points: TrackPoint[],
|
|
epsilon: number = 50,
|
|
measure: (a: TrackPoint, b: TrackPoint, c: TrackPoint) => number = crossarcDistance
|
|
): SimplifiedTrackPoint[] {
|
|
if (points.length == 0) {
|
|
return [];
|
|
} else if (points.length == 1) {
|
|
return [
|
|
{
|
|
point: points[0],
|
|
},
|
|
];
|
|
}
|
|
|
|
let simplified = [
|
|
{
|
|
point: points[0],
|
|
},
|
|
];
|
|
ramerDouglasPeuckerRecursive(points, epsilon, measure, 0, points.length - 1, simplified);
|
|
simplified.push({
|
|
point: points[points.length - 1],
|
|
});
|
|
return simplified;
|
|
}
|
|
|
|
function ramerDouglasPeuckerRecursive(
|
|
points: TrackPoint[],
|
|
epsilon: number,
|
|
measure: (a: TrackPoint, b: TrackPoint, c: TrackPoint) => number,
|
|
start: number,
|
|
end: number,
|
|
simplified: SimplifiedTrackPoint[]
|
|
) {
|
|
let largest = {
|
|
index: 0,
|
|
distance: 0,
|
|
};
|
|
|
|
for (let i = start + 1; i < end; i++) {
|
|
let distance = measure(points[start], points[end], points[i]);
|
|
if (distance > largest.distance) {
|
|
largest.index = i;
|
|
largest.distance = distance;
|
|
}
|
|
}
|
|
|
|
if (largest.distance > epsilon && largest.index != 0) {
|
|
ramerDouglasPeuckerRecursive(points, epsilon, measure, start, largest.index, simplified);
|
|
simplified.push({ point: points[largest.index], distance: largest.distance });
|
|
ramerDouglasPeuckerRecursive(points, epsilon, measure, largest.index, end, simplified);
|
|
}
|
|
}
|
|
|
|
export function crossarcDistance(
|
|
point1: TrackPoint,
|
|
point2: TrackPoint,
|
|
point3: TrackPoint | Coordinates
|
|
): number {
|
|
return crossarc(
|
|
point1.getCoordinates(),
|
|
point2.getCoordinates(),
|
|
point3 instanceof TrackPoint ? point3.getCoordinates() : point3
|
|
);
|
|
}
|
|
|
|
function crossarc(coord1: Coordinates, coord2: Coordinates, coord3: Coordinates): number {
|
|
// Calculates the shortest distance in meters
|
|
// between an arc (defined by p1 and p2) and a third point, p3.
|
|
// Input lat1,lon1,lat2,lon2,lat3,lon3 in degrees.
|
|
|
|
const rad = Math.PI / 180;
|
|
const lat1 = coord1.lat * rad;
|
|
const lat2 = coord2.lat * rad;
|
|
const lat3 = coord3.lat * rad;
|
|
|
|
const lon1 = coord1.lon * rad;
|
|
const lon2 = coord2.lon * rad;
|
|
const lon3 = coord3.lon * rad;
|
|
|
|
// Prerequisites for the formulas
|
|
const bear12 = bearing(lat1, lon1, lat2, lon2);
|
|
const bear13 = bearing(lat1, lon1, lat3, lon3);
|
|
let dis13 = distance(lat1, lon1, lat3, lon3);
|
|
|
|
let diff = Math.abs(bear13 - bear12);
|
|
if (diff > Math.PI) {
|
|
diff = 2 * Math.PI - diff;
|
|
}
|
|
|
|
// Is relative bearing obtuse?
|
|
if (diff > Math.PI / 2) {
|
|
return dis13;
|
|
}
|
|
|
|
// Find the cross-track distance.
|
|
let dxt = Math.asin(Math.sin(dis13 / earthRadius) * Math.sin(bear13 - bear12)) * earthRadius;
|
|
|
|
// Is p4 beyond the arc?
|
|
let dis12 = distance(lat1, lon1, lat2, lon2);
|
|
let dis14 =
|
|
Math.acos(Math.cos(dis13 / earthRadius) / Math.cos(dxt / earthRadius)) * earthRadius;
|
|
if (dis14 > dis12) {
|
|
return distance(lat2, lon2, lat3, lon3);
|
|
} else {
|
|
return Math.abs(dxt);
|
|
}
|
|
}
|
|
|
|
function distance(latA: number, lonA: number, latB: number, lonB: number): number {
|
|
// Finds the distance between two lat / lon points.
|
|
return (
|
|
Math.acos(
|
|
Math.sin(latA) * Math.sin(latB) +
|
|
Math.cos(latA) * Math.cos(latB) * Math.cos(lonB - lonA)
|
|
) * earthRadius
|
|
);
|
|
}
|
|
|
|
function bearing(latA: number, lonA: number, latB: number, lonB: number): number {
|
|
// Finds the bearing from one lat / lon point to another.
|
|
return Math.atan2(
|
|
Math.sin(lonB - lonA) * Math.cos(latB),
|
|
Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(lonB - lonA)
|
|
);
|
|
}
|
|
|
|
export function projectedPoint(
|
|
point1: TrackPoint,
|
|
point2: TrackPoint,
|
|
point3: TrackPoint | Coordinates
|
|
): Coordinates {
|
|
return projected(
|
|
point1.getCoordinates(),
|
|
point2.getCoordinates(),
|
|
point3 instanceof TrackPoint ? point3.getCoordinates() : point3
|
|
);
|
|
}
|
|
|
|
function projected(coord1: Coordinates, coord2: Coordinates, coord3: Coordinates): Coordinates {
|
|
// Calculates the point on the line defined by p1 and p2
|
|
// that is closest to the third point, p3.
|
|
// Input lat1,lon1,lat2,lon2,lat3,lon3 in degrees.
|
|
|
|
const rad = Math.PI / 180;
|
|
const lat1 = coord1.lat * rad;
|
|
const lat2 = coord2.lat * rad;
|
|
const lat3 = coord3.lat * rad;
|
|
|
|
const lon1 = coord1.lon * rad;
|
|
const lon2 = coord2.lon * rad;
|
|
const lon3 = coord3.lon * rad;
|
|
|
|
// Prerequisites for the formulas
|
|
const bear12 = bearing(lat1, lon1, lat2, lon2);
|
|
const bear13 = bearing(lat1, lon1, lat3, lon3);
|
|
let dis13 = distance(lat1, lon1, lat3, lon3);
|
|
|
|
let diff = Math.abs(bear13 - bear12);
|
|
if (diff > Math.PI) {
|
|
diff = 2 * Math.PI - diff;
|
|
}
|
|
|
|
// Is relative bearing obtuse?
|
|
if (diff > Math.PI / 2) {
|
|
return coord1;
|
|
}
|
|
|
|
// Find the cross-track distance.
|
|
let dxt = Math.asin(Math.sin(dis13 / earthRadius) * Math.sin(bear13 - bear12)) * earthRadius;
|
|
|
|
// Is p4 beyond the arc?
|
|
let dis12 = distance(lat1, lon1, lat2, lon2);
|
|
let dis14 =
|
|
Math.acos(Math.cos(dis13 / earthRadius) / Math.cos(dxt / earthRadius)) * earthRadius;
|
|
if (dis14 > dis12) {
|
|
return coord2;
|
|
} else {
|
|
// Determine the closest point (p4) on the great circle
|
|
const f = dis14 / earthRadius;
|
|
const lat4 = Math.asin(
|
|
Math.sin(lat1) * Math.cos(f) + Math.cos(lat1) * Math.sin(f) * Math.cos(bear12)
|
|
);
|
|
const lon4 =
|
|
lon1 +
|
|
Math.atan2(
|
|
Math.sin(bear12) * Math.sin(f) * Math.cos(lat1),
|
|
Math.cos(f) - Math.sin(lat1) * Math.sin(lat4)
|
|
);
|
|
|
|
return { lat: lat4 / rad, lon: lon4 / rad };
|
|
}
|
|
}
|