@geolibre/core 3.6.1 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,155 @@
1
+ import { f as getActiveEllipsoid } from "./ellipsoids-DlTLddDu.mjs";
2
+ import geographiclib from "geographiclib-geodesic";
3
+ // src/geodesic.ts
4
+ /**
5
+ * Distances and areas on the active body's ellipsoid, for the Field Calculator
6
+ * and the processing tools.
7
+ *
8
+ * The geodesic math is Charles Karney's GeographicLib (`geographiclib-geodesic`,
9
+ * MIT, no dependencies), the same algorithms PROJ's `geod` and pyproj use, with
10
+ * errors of a few nanometres. This is the only module that imports it, so
11
+ * replacing the package (for example with a vendored copy) touches this file
12
+ * alone.
13
+ *
14
+ * Each helper takes an optional ellipsoid and defaults to the active one
15
+ * (`ellipsoids.ts`), so the Moon and Mars are measured on their own figure
16
+ * rather than on Earth scaled by a radius ratio. Positions are GeoJSON
17
+ * `[lon, lat]` degrees; any Z is ignored.
18
+ *
19
+ * Workers import it as `@geolibre/core/geodesic` rather than through
20
+ * `worker-safe`, so the workers that need no measurements don't bundle
21
+ * GeographicLib.
22
+ */
23
+ const solvers = /* @__PURE__ */ new WeakMap();
24
+ function solverFor(ellipsoid) {
25
+ let solver = solvers.get(ellipsoid);
26
+ if (!solver) {
27
+ const f = ellipsoid.inverseFlattening ? 1 / ellipsoid.inverseFlattening : 0;
28
+ solver = new geographiclib.Geodesic.Geodesic(ellipsoid.semiMajorAxisMeters, f);
29
+ solvers.set(ellipsoid, solver);
30
+ }
31
+ return solver;
32
+ }
33
+ /** Geodesic (shortest-path) distance in metres between two positions. */
34
+ function geodesicDistance(from, to, ellipsoid = getActiveEllipsoid()) {
35
+ return solverFor(ellipsoid).Inverse(from[1], from[0], to[1], to[0], geographiclib.Geodesic.DISTANCE).s12;
36
+ }
37
+ const DEG_TO_RAD = Math.PI / 180;
38
+ /**
39
+ * Central angle in radians between two positions, taking their geodetic
40
+ * latitude and longitude as coordinates on a sphere. Much cheaper than
41
+ * {@link geodesicDistance}; with {@link geodesicRadiusBounds} it bounds a
42
+ * geodesic distance without computing it, which lets the O(n²) loops skip most
43
+ * pairs.
44
+ */
45
+ function centralAngle(from, to) {
46
+ const lat1 = from[1] * DEG_TO_RAD;
47
+ const lat2 = to[1] * DEG_TO_RAD;
48
+ const dLon = (to[0] - from[0]) * DEG_TO_RAD;
49
+ const h = Math.sin((lat2 - lat1) / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
50
+ if (h <= .5) return 2 * Math.asin(Math.sqrt(h));
51
+ const sinLat1 = Math.sin(lat1);
52
+ const cosLat1 = Math.cos(lat1);
53
+ const sinLat2 = Math.sin(lat2);
54
+ const cosLat2 = Math.cos(lat2);
55
+ const cosDLon = Math.cos(dLon);
56
+ const x = cosLat2 * Math.sin(dLon);
57
+ const y = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDLon;
58
+ const z = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDLon;
59
+ return Math.atan2(Math.hypot(x, y), z);
60
+ }
61
+ /**
62
+ * Radii in metres such that, for any two positions,
63
+ * `min * centralAngle(p, q) <= geodesicDistance(p, q) <= max * centralAngle(p, q)`.
64
+ *
65
+ * Why it holds: send each point of the ellipsoid to the point of the unit
66
+ * sphere with the same latitude and longitude. The line element is
67
+ * ds² = M² dφ² + N² cos²φ dλ² on the ellipsoid and dφ² + cos²φ dλ² on the
68
+ * sphere, and both radii of curvature M and N lie between b²/a (M at the
69
+ * equator) and a²/b (both at the poles). Every curve on one surface is
70
+ * therefore between b²/a and a²/b times as long as its image on the other, and
71
+ * so are the shortest paths. On Earth that is 6,335,439 m to 6,399,594 m, within
72
+ * 0.6 % of the mean radius; on a sphere both radii equal its radius. On a
73
+ * prolate figure (b > a, a negative inverse flattening) the two swap, hence the
74
+ * min and max. The 1e-9 margin covers rounding in the central angle and in
75
+ * GeographicLib.
76
+ */
77
+ function geodesicRadiusBounds(ellipsoid = getActiveEllipsoid()) {
78
+ const a = ellipsoid.semiMajorAxisMeters;
79
+ const b = ellipsoid.inverseFlattening ? a * (1 - 1 / ellipsoid.inverseFlattening) : a;
80
+ const meridianAtEquator = b * b / a;
81
+ const atPole = a * a / b;
82
+ return {
83
+ min: Math.min(meridianAtEquator, atPole) * (1 - 1e-9),
84
+ max: Math.max(meridianAtEquator, atPole) * (1 + 1e-9)
85
+ };
86
+ }
87
+ /**
88
+ * Whether two positions are at most `meters` apart along the geodesic. The
89
+ * answer is the same as comparing {@link geodesicDistance}, which is only
90
+ * computed when the {@link geodesicRadiusBounds} cannot decide. A loop can pass
91
+ * the bounds once, and the central angle when it already has it, so this stays
92
+ * the one place the rule is written.
93
+ */
94
+ function withinGeodesicDistance(from, to, meters, ellipsoid = getActiveEllipsoid(), bounds = geodesicRadiusBounds(ellipsoid), angle = centralAngle(from, to)) {
95
+ if (angle * bounds.max <= meters) return true;
96
+ if (angle * bounds.min > meters) return false;
97
+ return geodesicDistance(from, to, ellipsoid) <= meters;
98
+ }
99
+ /** Summed geodesic length in metres along a run of positions. */
100
+ function geodesicLength(positions, ellipsoid = getActiveEllipsoid()) {
101
+ let total = 0;
102
+ for (let i = 1; i < positions.length; i += 1) total += geodesicDistance(positions[i - 1], positions[i], ellipsoid);
103
+ return total;
104
+ }
105
+ /**
106
+ * Area in square metres enclosed by one ring, whatever its winding order. The
107
+ * ring may be open or closed. Edges are geodesics, so a ring that crosses the
108
+ * antimeridian or encircles a pole is measured correctly.
109
+ */
110
+ function geodesicRingArea(ring, ellipsoid = getActiveEllipsoid()) {
111
+ if (ring.length < 3) return 0;
112
+ const polygon = solverFor(ellipsoid).Polygon(false);
113
+ for (const position of ring) polygon.AddPoint(position[1], position[0]);
114
+ return Math.abs(polygon.Compute(false, true).area ?? 0);
115
+ }
116
+ /**
117
+ * Area in square metres of the box bounded by the meridians `west` and `east`
118
+ * and the parallels `south` and `north` (degrees). `east - west` may span up
119
+ * to 360°; a box with `east < west` crosses the antimeridian and spans
120
+ * `east - west + 360`. Latitudes are clamped to ±90°.
121
+ *
122
+ * Its edges along parallels are not geodesics, so this is the closed form for
123
+ * a zone of the ellipsoid, b² Δλ [F(φ₂) − F(φ₁)] with
124
+ * F(φ) = sin φ / (2 (1 − e² sin² φ)) + atanh(e sin φ) / (2e), which reduces to
125
+ * R² Δλ (sin φ₂ − sin φ₁) on a sphere and uses atan for a prolate figure
126
+ * (e² < 0). It holds up to the whole body, unlike a ring of geodesics, which
127
+ * would go the short way round a box wider than 180°.
128
+ */
129
+ function latLonBoxArea(west, south, east, north, ellipsoid = getActiveEllipsoid()) {
130
+ const a = ellipsoid.semiMajorAxisMeters;
131
+ const f = ellipsoid.inverseFlattening ? 1 / ellipsoid.inverseFlattening : 0;
132
+ const b = a * (1 - f);
133
+ const e2 = f * (2 - f);
134
+ const e = Math.sqrt(Math.abs(e2));
135
+ const zone = (lat) => {
136
+ const s = Math.sin(Math.max(-90, Math.min(90, lat)) * DEG_TO_RAD);
137
+ if (e === 0) return s;
138
+ const tail = e2 > 0 ? Math.atanh(e * s) : Math.atan(e * s);
139
+ return s / (2 * (1 - e2 * s * s)) + tail / (2 * e);
140
+ };
141
+ const span = east < west ? ((east - west) % 360 + 360) % 360 : east - west;
142
+ const dLon = Math.min(360, span) * DEG_TO_RAD;
143
+ return b * b * dLon * Math.abs(zone(north) - zone(south));
144
+ }
145
+ /**
146
+ * Whether a position is a usable lon/lat: both finite and |lat| <= 90°.
147
+ * GeographicLib returns NaN for latitudes beyond the poles.
148
+ */
149
+ function isLonLat(position) {
150
+ const [lon, lat] = position;
151
+ return Number.isFinite(lon) && Number.isFinite(lat) && Math.abs(lat) <= 90;
152
+ }
153
+ export { centralAngle, geodesicDistance, geodesicLength, geodesicRadiusBounds, geodesicRingArea, isLonLat, latLonBoxArea, withinGeodesicDistance };
154
+
155
+ //# sourceMappingURL=geodesic.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geodesic.mjs","names":[],"sources":["../src/geodesic.ts"],"sourcesContent":["/**\n * Distances and areas on the active body's ellipsoid, for the Field Calculator\n * and the processing tools.\n *\n * The geodesic math is Charles Karney's GeographicLib (`geographiclib-geodesic`,\n * MIT, no dependencies), the same algorithms PROJ's `geod` and pyproj use, with\n * errors of a few nanometres. This is the only module that imports it, so\n * replacing the package (for example with a vendored copy) touches this file\n * alone.\n *\n * Each helper takes an optional ellipsoid and defaults to the active one\n * (`ellipsoids.ts`), so the Moon and Mars are measured on their own figure\n * rather than on Earth scaled by a radius ratio. Positions are GeoJSON\n * `[lon, lat]` degrees; any Z is ignored.\n *\n * Workers import it as `@geolibre/core/geodesic` rather than through\n * `worker-safe`, so the workers that need no measurements don't bundle\n * GeographicLib.\n */\nimport geographiclib from \"geographiclib-geodesic\";\nimport type { Position } from \"geojson\";\nimport { getActiveEllipsoid, type Ellipsoid } from \"./ellipsoids\";\n\ntype GeodesicSolver = InstanceType<typeof geographiclib.Geodesic.Geodesic>;\n\n// One solver per ellipsoid: construction precomputes series coefficients that\n// every later call reuses.\nconst solvers = new WeakMap<Ellipsoid, GeodesicSolver>();\n\nfunction solverFor(ellipsoid: Ellipsoid): GeodesicSolver {\n let solver = solvers.get(ellipsoid);\n if (!solver) {\n // `inverseFlattening: 0` marks a sphere, which GeographicLib takes as f = 0.\n const f = ellipsoid.inverseFlattening ? 1 / ellipsoid.inverseFlattening : 0;\n solver = new geographiclib.Geodesic.Geodesic(ellipsoid.semiMajorAxisMeters, f);\n solvers.set(ellipsoid, solver);\n }\n return solver;\n}\n\n/** Geodesic (shortest-path) distance in metres between two positions. */\nexport function geodesicDistance(\n from: Position,\n to: Position,\n ellipsoid: Ellipsoid = getActiveEllipsoid(),\n): number {\n return solverFor(ellipsoid).Inverse(\n from[1],\n from[0],\n to[1],\n to[0],\n geographiclib.Geodesic.DISTANCE,\n ).s12!;\n}\n\nconst DEG_TO_RAD = Math.PI / 180;\n\n/**\n * Central angle in radians between two positions, taking their geodetic\n * latitude and longitude as coordinates on a sphere. Much cheaper than\n * {@link geodesicDistance}; with {@link geodesicRadiusBounds} it bounds a\n * geodesic distance without computing it, which lets the O(n²) loops skip most\n * pairs.\n */\nexport function centralAngle(from: Position, to: Position): number {\n const lat1 = from[1] * DEG_TO_RAD;\n const lat2 = to[1] * DEG_TO_RAD;\n const dLon = (to[0] - from[0]) * DEG_TO_RAD;\n const h =\n Math.sin((lat2 - lat1) / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;\n // The haversine is well conditioned up to 90° (h <= 0.5), the common case\n // in the neighbour searches. Beyond, asin loses precision towards the\n // antipode, so the atan2 form takes over.\n if (h <= 0.5) return 2 * Math.asin(Math.sqrt(h));\n const sinLat1 = Math.sin(lat1);\n const cosLat1 = Math.cos(lat1);\n const sinLat2 = Math.sin(lat2);\n const cosLat2 = Math.cos(lat2);\n const cosDLon = Math.cos(dLon);\n const x = cosLat2 * Math.sin(dLon);\n const y = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDLon;\n const z = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDLon;\n return Math.atan2(Math.hypot(x, y), z);\n}\n\n/**\n * Radii in metres such that, for any two positions,\n * `min * centralAngle(p, q) <= geodesicDistance(p, q) <= max * centralAngle(p, q)`.\n *\n * Why it holds: send each point of the ellipsoid to the point of the unit\n * sphere with the same latitude and longitude. The line element is\n * ds² = M² dφ² + N² cos²φ dλ² on the ellipsoid and dφ² + cos²φ dλ² on the\n * sphere, and both radii of curvature M and N lie between b²/a (M at the\n * equator) and a²/b (both at the poles). Every curve on one surface is\n * therefore between b²/a and a²/b times as long as its image on the other, and\n * so are the shortest paths. On Earth that is 6,335,439 m to 6,399,594 m, within\n * 0.6 % of the mean radius; on a sphere both radii equal its radius. On a\n * prolate figure (b > a, a negative inverse flattening) the two swap, hence the\n * min and max. The 1e-9 margin covers rounding in the central angle and in\n * GeographicLib.\n */\nexport function geodesicRadiusBounds(ellipsoid: Ellipsoid = getActiveEllipsoid()): {\n min: number;\n max: number;\n} {\n const a = ellipsoid.semiMajorAxisMeters;\n const b = ellipsoid.inverseFlattening ? a * (1 - 1 / ellipsoid.inverseFlattening) : a;\n const meridianAtEquator = (b * b) / a;\n const atPole = (a * a) / b;\n return {\n min: Math.min(meridianAtEquator, atPole) * (1 - 1e-9),\n max: Math.max(meridianAtEquator, atPole) * (1 + 1e-9),\n };\n}\n\n/**\n * Whether two positions are at most `meters` apart along the geodesic. The\n * answer is the same as comparing {@link geodesicDistance}, which is only\n * computed when the {@link geodesicRadiusBounds} cannot decide. A loop can pass\n * the bounds once, and the central angle when it already has it, so this stays\n * the one place the rule is written.\n */\nexport function withinGeodesicDistance(\n from: Position,\n to: Position,\n meters: number,\n ellipsoid: Ellipsoid = getActiveEllipsoid(),\n bounds: { min: number; max: number } = geodesicRadiusBounds(ellipsoid),\n angle: number = centralAngle(from, to),\n): boolean {\n if (angle * bounds.max <= meters) return true;\n if (angle * bounds.min > meters) return false;\n return geodesicDistance(from, to, ellipsoid) <= meters;\n}\n\n/** Summed geodesic length in metres along a run of positions. */\nexport function geodesicLength(\n positions: readonly Position[],\n ellipsoid: Ellipsoid = getActiveEllipsoid(),\n): number {\n let total = 0;\n for (let i = 1; i < positions.length; i += 1) {\n total += geodesicDistance(positions[i - 1], positions[i], ellipsoid);\n }\n return total;\n}\n\n/**\n * Area in square metres enclosed by one ring, whatever its winding order. The\n * ring may be open or closed. Edges are geodesics, so a ring that crosses the\n * antimeridian or encircles a pole is measured correctly.\n */\nexport function geodesicRingArea(\n ring: readonly Position[],\n ellipsoid: Ellipsoid = getActiveEllipsoid(),\n): number {\n if (ring.length < 3) return 0;\n const polygon = solverFor(ellipsoid).Polygon(false);\n for (const position of ring) polygon.AddPoint(position[1], position[0]);\n // `sign = true` reports a signed area, counter-clockwise positive, in\n // (-half, +half] of the body. Its absolute value is the area the ring\n // encloses for either winding, including a ring around a pole; a ring that\n // bounds more than half the body is measured as the smaller side.\n return Math.abs(polygon.Compute(false, true).area ?? 0);\n}\n\n/**\n * Area in square metres of the box bounded by the meridians `west` and `east`\n * and the parallels `south` and `north` (degrees). `east - west` may span up\n * to 360°; a box with `east < west` crosses the antimeridian and spans\n * `east - west + 360`. Latitudes are clamped to ±90°.\n *\n * Its edges along parallels are not geodesics, so this is the closed form for\n * a zone of the ellipsoid, b² Δλ [F(φ₂) − F(φ₁)] with\n * F(φ) = sin φ / (2 (1 − e² sin² φ)) + atanh(e sin φ) / (2e), which reduces to\n * R² Δλ (sin φ₂ − sin φ₁) on a sphere and uses atan for a prolate figure\n * (e² < 0). It holds up to the whole body, unlike a ring of geodesics, which\n * would go the short way round a box wider than 180°.\n */\nexport function latLonBoxArea(\n west: number,\n south: number,\n east: number,\n north: number,\n ellipsoid: Ellipsoid = getActiveEllipsoid(),\n): number {\n const a = ellipsoid.semiMajorAxisMeters;\n const f = ellipsoid.inverseFlattening ? 1 / ellipsoid.inverseFlattening : 0;\n const b = a * (1 - f);\n const e2 = f * (2 - f);\n const e = Math.sqrt(Math.abs(e2));\n const zone = (lat: number): number => {\n const s = Math.sin(Math.max(-90, Math.min(90, lat)) * DEG_TO_RAD);\n if (e === 0) return s;\n const tail = e2 > 0 ? Math.atanh(e * s) : Math.atan(e * s);\n return s / (2 * (1 - e2 * s * s)) + tail / (2 * e);\n };\n // A negative span crosses the antimeridian; wrapping it into [0°, 360°)\n // also keeps unwrapped longitudes such as 200 to -200 from giving a\n // negative area.\n const span = east < west ? (((east - west) % 360) + 360) % 360 : east - west;\n const dLon = Math.min(360, span) * DEG_TO_RAD;\n return b * b * dLon * Math.abs(zone(north) - zone(south));\n}\n\n/**\n * Whether a position is a usable lon/lat: both finite and |lat| <= 90°.\n * GeographicLib returns NaN for latitudes beyond the poles.\n */\nexport function isLonLat(position: Position): boolean {\n const [lon, lat] = position;\n return Number.isFinite(lon) && Number.isFinite(lat) && Math.abs(lat) <= 90;\n}\n"],"mappings":"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"}