Add src/client/types/geoms.ts
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
a69d334782
commit
d184068ae2
177
src/client/types/geoms.ts
Normal file
177
src/client/types/geoms.ts
Normal file
@ -0,0 +1,177 @@
|
||||
// import L from "leaflet"
|
||||
|
||||
const ellipsoid = {
|
||||
a: 6378137,
|
||||
b: 6356752.3142,
|
||||
f: 1 / 298.257223563
|
||||
};
|
||||
|
||||
function mod(n: number, p: number): number {
|
||||
const r = n % p;
|
||||
return r < 0 ? r + p : r;
|
||||
}
|
||||
|
||||
function wrap(degrees: number, max = 360) {
|
||||
if (-max <= degrees && degrees <= max) {
|
||||
return degrees;
|
||||
} else {
|
||||
return mod(degrees + max, 2 * max) - max;
|
||||
}
|
||||
}
|
||||
|
||||
function dist(src, dst, itr = 100, mit = true): number {
|
||||
const p1 = src,
|
||||
p2 = dst;
|
||||
const φ1 = toRadians(p1.lat),
|
||||
λ1 = toRadians(p1.lng);
|
||||
const φ2 = toRadians(p2.lat),
|
||||
λ2 = toRadians(p2.lng);
|
||||
const π = Math.PI;
|
||||
const ε = Number.EPSILON;
|
||||
|
||||
// allow alternative ellipsoid to be specified
|
||||
const { a, b, f } = ellipsoid;
|
||||
|
||||
const dL = λ2 - λ1; // L = difference in longitude, U = reduced latitude, defined by tan U = (1-f)·tanφ.
|
||||
const tanU1 = (1 - f) * Math.tan(φ1),
|
||||
cosU1 = 1 / Math.sqrt(1 + tanU1 * tanU1),
|
||||
sinU1 = tanU1 * cosU1;
|
||||
const tanU2 = (1 - f) * Math.tan(φ2),
|
||||
cosU2 = 1 / Math.sqrt(1 + tanU2 * tanU2),
|
||||
sinU2 = tanU2 * cosU2;
|
||||
|
||||
const antipodal = Math.abs(dL) > π / 2 || Math.abs(φ2 - φ1) > π / 2;
|
||||
|
||||
let λ = dL,
|
||||
sinλ: number | null = null,
|
||||
cosλ: number | null = null; // λ = difference in longitude on an auxiliary sphere
|
||||
let σ = antipodal ? π : 0,
|
||||
sinσ = 0,
|
||||
cosσ = antipodal ? -1 : 1,
|
||||
sinSqσ: number | null = null; // σ = angular distance P₁ P₂ on the sphere
|
||||
let cos2σₘ = 1; // σₘ = angular distance on the sphere from the equator to the midpoint of the line
|
||||
let sinα: number | null = null,
|
||||
cosSqα = 1; // α = azimuth of the geodesic at the equator
|
||||
let C: number | null = null;
|
||||
|
||||
let λʹ: number | null = null,
|
||||
iterations = 0;
|
||||
do {
|
||||
sinλ = Math.sin(λ);
|
||||
cosλ = Math.cos(λ);
|
||||
sinSqσ =
|
||||
cosU2 * sinλ * (cosU2 * sinλ) +
|
||||
(cosU1 * sinU2 - sinU1 * cosU2 * cosλ) * (cosU1 * sinU2 - sinU1 * cosU2 * cosλ);
|
||||
if (Math.abs(sinSqσ) < ε) {
|
||||
break; // co-incident/antipodal points (falls back on λ/σ = L)
|
||||
}
|
||||
sinσ = Math.sqrt(sinSqσ);
|
||||
cosσ = sinU1 * sinU2 + cosU1 * cosU2 * cosλ;
|
||||
σ = Math.atan2(sinσ, cosσ);
|
||||
sinα = (cosU1 * cosU2 * sinλ) / sinσ;
|
||||
cosSqα = 1 - sinα * sinα;
|
||||
cos2σₘ = cosSqα !== 0 ? cosσ - (2 * sinU1 * sinU2) / cosSqα : 0; // on equatorial line cos²α = 0 (§6)
|
||||
C = (f / 16) * cosSqα * (4 + f * (4 - 3 * cosSqα));
|
||||
λʹ = λ;
|
||||
λ = dL + (1 - C) * f * sinα * (σ + C * sinσ * (cos2σₘ + C * cosσ * (-1 + 2 * cos2σₘ * cos2σₘ)));
|
||||
const iterationCheck = antipodal ? Math.abs(λ) - π : Math.abs(λ);
|
||||
if (iterationCheck > π) {
|
||||
throw new EvalError("λ > π");
|
||||
}
|
||||
} while (Math.abs(λ - λʹ) > 1e-12 && ++iterations < itr);
|
||||
|
||||
if (iterations >= itr) {
|
||||
if (mit) {
|
||||
return dist(
|
||||
src,
|
||||
new L.LatLng(dst.lat, dst.lng - 0.01),
|
||||
itr,
|
||||
mit
|
||||
);
|
||||
} else {
|
||||
throw new EvalError(`Inverse vincenty formula failed to converge after ${itr} iterations
|
||||
(start=${src.lat}/${src.lng}; dest=${dst.lat}/${dst.lng})`);
|
||||
}
|
||||
}
|
||||
const uSq = (cosSqα * (a * a - b * b)) / (b * b);
|
||||
const A = 1 + (uSq / 16384) * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
|
||||
const B = (uSq / 1024) * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
|
||||
const Δσ =
|
||||
B *
|
||||
sinσ *
|
||||
(cos2σₘ +
|
||||
(B / 4) *
|
||||
(cosσ * (-1 + 2 * cos2σₘ * cos2σₘ) -
|
||||
(B / 6) * cos2σₘ * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σₘ * cos2σₘ)));
|
||||
|
||||
const s = b * A * (σ - Δσ); // s = length of the geodesic
|
||||
return s
|
||||
|
||||
}
|
||||
|
||||
function pointDistance(src: L.LatLng, dst: L.LatLng): number {
|
||||
return dist(
|
||||
new L.LatLng(src.lat, wrap(src.lng, 180)),
|
||||
new L.LatLng(dst.lat, wrap(dst.lng, 180))
|
||||
);
|
||||
}
|
||||
|
||||
function toRadians(degree: number): number {
|
||||
return (degree * Math.PI) / 180;
|
||||
}
|
||||
|
||||
function toDegrees(radians: number): number {
|
||||
return (radians * 180) / Math.PI;
|
||||
}
|
||||
|
||||
function midpoint(src: L.LatLng, dst: L.LatLng): L.LatLng {
|
||||
// φm = atan2( sinφ1 + sinφ2, √( (cosφ1 + cosφ2⋅cosΔλ)² + cos²φ2⋅sin²Δλ ) )
|
||||
// λm = λ1 + atan2(cosφ2⋅sinΔλ, cosφ1 + cosφ2⋅cosΔλ)
|
||||
// midpoint is sum of vectors to two points: mathforum.org/library/drmath/view/51822.html
|
||||
|
||||
const φ1 = toRadians(src.lat);
|
||||
const λ1 = toRadians(src.lng);
|
||||
const φ2 = toRadians(dst.lat);
|
||||
const Δλ = toRadians(dst.lng - src.lng);
|
||||
|
||||
// get cartesian coordinates for the two points
|
||||
const A = { x: Math.cos(φ1), y: 0, z: Math.sin(φ1) }; // place point A on prime meridian y=0
|
||||
const B = { x: Math.cos(φ2) * Math.cos(Δλ), y: Math.cos(φ2) * Math.sin(Δλ), z: Math.sin(φ2) };
|
||||
|
||||
// vector to midpoint is sum of vectors to two points (no need to normalise)
|
||||
const C = { x: A.x + B.x, y: A.y + B.y, z: A.z + B.z };
|
||||
|
||||
const φm = Math.atan2(C.z, Math.sqrt(C.x * C.x + C.y * C.y));
|
||||
const λm = λ1 + Math.atan2(C.y, C.x);
|
||||
|
||||
return new L.LatLng(toDegrees(φm), toDegrees(λm));
|
||||
}
|
||||
|
||||
function recursiveMidPoint(src: L.LatLng, dst: L.LatLng, opt: { step?: number, dist?: number }): L.LatLng[] {
|
||||
const geom: L.LatLng[] = [src, dst];
|
||||
const mp = midpoint(src, dst);
|
||||
|
||||
if (opt.step != undefined) {
|
||||
if (opt.step > 0) {
|
||||
geom.splice(0, 1, ...recursiveMidPoint(src, mp, { step: opt.step - 1 }));
|
||||
geom.splice(geom.length - 2, 2, ...recursiveMidPoint(mp, dst, { step: opt.step - 1 }));
|
||||
} else {
|
||||
geom.splice(1, 0, mp);
|
||||
}
|
||||
} else if (opt.dist != undefined) {
|
||||
// console.log(src, dst, pointDistance(src, dst))
|
||||
if (pointDistance(src, dst) > opt.dist) {
|
||||
geom.splice(0, 1, ...recursiveMidPoint(src, mp, { dist: opt.dist }));
|
||||
geom.splice(geom.length - 2, 2, ...recursiveMidPoint(mp, dst, { dist: opt.dist }));
|
||||
} else {
|
||||
geom.splice(1, 0, mp);
|
||||
}
|
||||
}
|
||||
console.log(geom)
|
||||
return geom;
|
||||
}
|
||||
|
||||
export function getGeoLine(src: L.LatLng, dst: L.LatLng, opt: { step: number, dist: number }) {
|
||||
|
||||
return recursiveMidPoint(src, dst, opt)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user