@math.gl/core 5.0.0-alpha.19 → 5.0.0-alpha.20

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.
Files changed (38) hide show
  1. package/APACHE-2.0-LICENSE.txt +55 -0
  2. package/ARROW-NOTICE.txt +18 -0
  3. package/LICENSE +146 -44
  4. package/README.md +5 -0
  5. package/THIRD-PARTY-NOTICES.md +16 -0
  6. package/dist/classes/euler.d.ts.map +1 -1
  7. package/dist/classes/euler.js +2 -0
  8. package/dist/classes/euler.js.map +1 -1
  9. package/dist/expressions/function-registry.d.ts.map +1 -1
  10. package/dist/expressions/function-registry.js +9 -1
  11. package/dist/expressions/function-registry.js.map +1 -1
  12. package/dist/expressions/get.js +1 -1
  13. package/dist/expressions/get.js.map +1 -1
  14. package/dist/expressions/index.cjs +12 -4
  15. package/dist/expressions/index.cjs.map +1 -1
  16. package/dist/expressions/parse-expression-string.js +3 -3
  17. package/dist/expressions/parse-expression-string.js.map +1 -1
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/spheroid.cjs +34 -57
  20. package/dist/spheroid.cjs.map +1 -1
  21. package/dist/spheroid.d.ts +1 -1
  22. package/dist/spheroid.d.ts.map +1 -1
  23. package/dist/spheroid.js +53 -64
  24. package/dist/spheroid.js.map +1 -1
  25. package/dist/types/bigint.d.ts +1 -1
  26. package/dist/types/bigint.d.ts.map +1 -1
  27. package/dist/types/bigint.js +21 -4
  28. package/dist/types/bigint.js.map +1 -1
  29. package/dist/types/bounds-types.js +1 -1
  30. package/dist/types/bounds-types.js.map +1 -1
  31. package/package.json +7 -3
  32. package/src/classes/euler.ts +2 -0
  33. package/src/expressions/function-registry.ts +9 -1
  34. package/src/expressions/get.ts +1 -1
  35. package/src/expressions/parse-expression-string.ts +3 -3
  36. package/src/spheroid.ts +62 -85
  37. package/src/types/bigint.ts +22 -4
  38. package/src/types/bounds-types.ts +1 -1
@@ -14,9 +14,9 @@ import {get} from './get';
14
14
  */
15
15
  export type AccessorFunction = (row: Record<string, unknown>) => unknown;
16
16
 
17
- const cachedExpressionMap: Record<string, AccessorFunction> = {
18
- '-': object => object
19
- };
17
+ const cachedExpressionMap = Object.assign(Object.create(null) as Record<string, AccessorFunction>, {
18
+ '-': (object: Record<string, unknown>) => object
19
+ });
20
20
 
21
21
  /**
22
22
  * Compiles a JSON-style expression string into an accessor function.
package/src/spheroid.ts CHANGED
@@ -2,13 +2,15 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // SPDX-FileCopyrightText: 2014 Mike Adair, Richard Greenwood, Didier Richard, Stephen Irons, Olivier Terral and Calvin Metcalf (proj4js)
4
4
  // SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
5
- // SPDX-FileComment: Forward geocentric equations and Hannover inverse moved/adapted from math.gl projection's proj4js 2.22.0 datumUtils.js port. See ../PROJ4-LICENSE.md. Safeguarded nearest-footpoint inverse and numeric output commit are original math.gl code.
5
+ // SPDX-FileComment: Forward geocentric equations and Hannover inverse moved/adapted from math.gl projection's proj4js 2.22.0 datumUtils.js port. See ../PROJ4-LICENSE.md. Safeguarded compensated nearest-footpoint inverse, thin-exterior/pole fixes and numeric output commit are original math.gl code, qualified with independent mathematics and offline GeographicLib 2.7 (Charles Karney, MIT).
6
6
 
7
7
  /** Numeric leaf: angles in radians, distances in axis units; no CRS, global config or scratch. */
8
8
  import type {SpheroidParameters} from './types';
9
9
  export type SpheroidGeometry = SpheroidParameters & {readonly eccentricitySquared: number};
10
10
  export type SpheroidPoint = {x: number; y: number; z: number};
11
11
 
12
+ const EPSILON = Number.EPSILON;
13
+
12
14
  /** Caller supplies validated positive finite axes and eccentricity. No latitude wrapping. */
13
15
  export function spheroidToCartesian(
14
16
  point: SpheroidPoint,
@@ -20,7 +22,7 @@ export function spheroidToCartesian(
20
22
  height = point.z;
21
23
  const {semiMajorAxis: a, semiMinorAxis: b, eccentricitySquared: es} = geometry;
22
24
  const sin = Math.sin(latitude),
23
- cos = Math.cos(latitude);
25
+ cos = Math.abs(latitude) === Math.PI / 2 ? 0 : Math.cos(latitude);
24
26
  // Use the axis ratio for very flat shapes: 1 - es*sin² loses polar precision.
25
27
  const ratio = b / a;
26
28
  const radius = es < 0.9 ? a / Math.sqrt(1 - es * sin * sin) : a / Math.hypot(cos, ratio * sin);
@@ -32,7 +34,7 @@ export function spheroidToCartesian(
32
34
 
33
35
  /**
34
36
  * Bounded inverse. Sphere is analytic; ordinary spheroids use the retained Hannover
35
- * iteration on exterior points. Interiors use a safeguarded nearest-footpoint solve.
37
+ * iteration on exterior points. Interiors and thin-shape exteriors share a safeguarded nearest-footpoint solve.
36
38
  * Equatorial interiors below the evolute cusp have two nearest normals: reject.
37
39
  * false leaves output untouched, including the center and unresolved binary64 cases.
38
40
  * Only exact poles canonicalize longitude; adapters may retain other conventions.
@@ -46,16 +48,17 @@ export function cartesianToSpheroid(
46
48
  y = point.y,
47
49
  z = point.z;
48
50
  const {semiMajorAxis: a, semiMinorAxis: b, eccentricitySquared: es} = geometry;
51
+ const longitude = Math.atan2(y, x);
49
52
  const p = Math.hypot(x, y),
50
53
  rr = Math.hypot(x, y, z);
51
54
  if (!(rr > 0) || !Number.isFinite(rr)) return false;
52
- if (es === 0) return commit(result, p === 0 ? 0 : Math.atan2(y, x), Math.atan2(z, p), rr - a);
55
+ if (es === 0) return commit(result, p === 0 ? 0 : longitude, Math.atan2(z, p), rr - a);
53
56
  if (p === 0) return commit(result, 0, (Math.sign(z) * Math.PI) / 2, Math.abs(z) - b);
54
57
  const horizontal = p / a,
55
58
  vertical = z / b;
56
- if (horizontal * horizontal + vertical * vertical < 1 - 16 * Number.EPSILON)
59
+ if (horizontal * horizontal + vertical * vertical < 1 - 16 * EPSILON)
57
60
  return interiorInverse(x, y, z, p, a, b, result);
58
- if (es >= 0.9) return exteriorInverse(x, y, z, p, a, b, result);
61
+ if (es >= 0.9) return interiorInverse(x, y, z, p, a, b, result);
59
62
  const ct = z / rr,
60
63
  st = p / rr;
61
64
  let rx = 1 / Math.sqrt(1 - es * (2 - es) * st * st);
@@ -69,16 +72,16 @@ export function cartesianToSpheroid(
69
72
  const nextCos = st * (1 - rk) * rx,
70
73
  nextSin = ct * rx;
71
74
  if (Math.abs(nextSin * cos - nextCos * sin) <= 1e-12) {
72
- if (!Number.isFinite(height) || !Number.isFinite(nextSin) || !Number.isFinite(nextCos)) break;
73
- return commit(result, Math.atan2(y, x), Math.atan2(nextSin, Math.abs(nextCos)), height);
75
+ if (!Number.isFinite(height)) break;
76
+ return commit(result, longitude, Math.atan2(nextSin, Math.abs(nextCos)), height);
74
77
  }
75
78
  cos = nextCos;
76
79
  sin = nextSin;
77
80
  }
78
- return exteriorInverse(x, y, z, p, a, b, result);
81
+ return interiorInverse(x, y, z, p, a, b, result);
79
82
  }
80
83
 
81
- /** Original nearest-normal solve in t = (lambda + b²)/a², avoiding interior cancellation.
84
+ /** Original nearest-normal solve in t = (lambda + b²)/a², avoiding interior cancellation and thin-exterior residual loss.
82
85
  * The positive interval selects the globally nearest branch, rather than a stationary normal.
83
86
  */
84
87
  function interiorInverse(
@@ -90,16 +93,13 @@ function interiorInverse(
90
93
  b: number,
91
94
  result: SpheroidPoint
92
95
  ): boolean {
96
+ const longitude = Math.atan2(y, x);
93
97
  const ratio = b / a,
94
98
  ratio2 = ratio * ratio;
95
99
  const horizontal = p / a,
96
100
  vertical = z / a,
97
101
  delta = 1 - ratio2;
98
102
  if (!(ratio2 > 0) || ratio > 1) return false;
99
- if (z === 0) {
100
- if (horizontal < delta) return false;
101
- return commit(result, Math.atan2(y, x), 0, p - a);
102
- }
103
103
  const aa = a * a,
104
104
  bb = b * b;
105
105
  const ratioError =
@@ -126,21 +126,44 @@ function interiorInverse(
126
126
  (2 * p)
127
127
  : 0;
128
128
  const shift = a * ratio2,
129
- base = p - a + shift;
129
+ distance = p - a,
130
+ base = distance + shift;
130
131
  const correction =
131
- radiusError + sumError(p - a, shift, base) + productError(a, ratio2, shift) + a * ratioError;
132
+ radiusError +
133
+ sumError(p, -a, distance) +
134
+ sumError(distance, shift, base) +
135
+ productError(a, ratio2, shift) +
136
+ a * ratioError;
137
+ if (z === 0) {
138
+ // A rounded hypot can equal the cusp for points just inside it. Include
139
+ // the radius/axis corrections before selecting a unique equatorial normal.
140
+ if (base + correction < 0) return false;
141
+ return commit(result, longitude, 0, distance);
142
+ }
132
143
  const weighted = Math.abs(vertical) * ratio;
133
- if (!(weighted > 0)) return false;
134
144
  let lower = 0,
135
- upper = ratio2;
145
+ upper = ratio2 + Math.hypot(horizontal, vertical * ratio);
146
+ if (!Number.isFinite(upper)) return false;
136
147
  // A lower bound near the short-axis singularity prevents thousands of bisections
137
148
  // for deep, nearly equatorial interiors. No subtraction of lambda and -b².
138
149
  let t = Math.max(weighted, horizontal - delta);
139
150
  if (!(t > lower && t < upper)) t = upper / 2;
140
- for (let i = 0; i < 128; i++) {
151
+ // Below the cusp, a vanishing polar input can make t subnormal. The
152
+ // limiting vertical footpoint remains well conditioned. Use it only when
153
+ // its bound t <= abs(z)*ratio/(a*v) is negligible relative to the cusp gap.
154
+ const gap = -(base + correction) / a;
155
+ const limiting = Math.sqrt(gap * (horizontal + delta)) / delta;
156
+ let latitude: number | undefined =
157
+ gap > 0 && (Math.abs(z) / limiting / a) * ratio <= EPSILON * gap
158
+ ? Math.atan2((Math.sign(z) * limiting) / ratio, horizontal / delta)
159
+ : undefined;
160
+ for (let i = 0; i < 1024 && latitude === undefined; i++) {
141
161
  const equatorial = delta + t;
142
162
  const u = horizontal / equatorial,
143
- v = weighted / t;
163
+ // Divide by t first: both z/a and a*t can lose precision in the
164
+ // subnormal range although the near-cusp latitude is representable.
165
+ verticalAt = z / t / a,
166
+ v = verticalAt * ratio;
144
167
  const at = a * t,
145
168
  difference = base - at;
146
169
  const offset =
@@ -148,40 +171,22 @@ function interiorInverse(
148
171
  (a * equatorial);
149
172
  const term = offset * (u + 1);
150
173
  const residual = term + v * v;
151
- if (
152
- Math.abs(residual) <= 4 * Number.EPSILON * (Math.abs(term) + v * v) ||
153
- upper - lower <= 4 * Number.EPSILON * t
154
- ) {
155
- const latitude = Math.atan2(vertical / t, horizontal / equatorial);
156
- const sin = Math.sin(latitude),
157
- cos = Math.cos(latitude);
158
- return commit(
159
- result,
160
- Math.atan2(y, x),
161
- latitude,
162
- p * cos + z * sin - a * Math.hypot(cos, ratio * sin)
163
- );
164
- }
174
+ if (!Number.isFinite(residual)) return false;
165
175
  if (residual > 0) lower = t;
166
176
  else upper = t;
167
177
  const derivative = -2 * ((u * u) / equatorial + (v * v) / t);
168
178
  const next = t - residual / derivative;
169
- const candidate =
170
- Number.isFinite(next) && next > lower && next < upper ? next : lower + (upper - lower) / 2;
171
- if (candidate === t) {
172
- const latitude = Math.atan2(vertical / t, horizontal / equatorial);
173
- return commit(
174
- result,
175
- Math.atan2(y, x),
176
- latitude,
177
- p * Math.cos(latitude) +
178
- z * Math.sin(latitude) -
179
- a * Math.hypot(Math.cos(latitude), ratio * Math.sin(latitude))
180
- );
179
+ const candidate = next > lower && next < upper ? next : lower + (upper - lower) / 2;
180
+ if (
181
+ Math.abs(residual) <= 4 * EPSILON * (Math.abs(term) + v * v) ||
182
+ upper - lower <= 4 * EPSILON * t
183
+ ) {
184
+ latitude = Math.atan2(verticalAt, u);
185
+ break;
181
186
  }
182
187
  t = candidate;
183
188
  }
184
- return false;
189
+ return latitude !== undefined && commitNormal(result, longitude, latitude, p, z, a, ratio);
185
190
  }
186
191
 
187
192
  /** Error-free transforms on the finite squared-axis domain used by geometry adapters. */
@@ -200,47 +205,19 @@ function productError(x: number, y: number, product: number): number {
200
205
  return hx * hy - product + hx * ly + lx * hy + lx * ly;
201
206
  }
202
207
 
203
- /** Original dimensionless Lagrange-multiplier solve, restricted to surface/exterior. */
204
- function exteriorInverse(
205
- x: number,
206
- y: number,
207
- z: number,
208
+ /** Shared signed distance along the solved surface normal. */
209
+ function commitNormal(
210
+ result: SpheroidPoint,
211
+ longitude: number,
212
+ latitude: number,
208
213
  p: number,
214
+ z: number,
209
215
  a: number,
210
- b: number,
211
- result: SpheroidPoint
216
+ ratio: number
212
217
  ): boolean {
213
- const ratio = b / a,
214
- ratio2 = ratio * ratio,
215
- horizontal = p / a,
216
- vertical = z / a;
217
- const norm = Math.hypot(horizontal, z / b);
218
- if (!Number.isFinite(norm) || norm < 1 - 8 * Number.EPSILON || !(ratio2 > 0)) return false;
219
- let lower = 0,
220
- upper = Math.hypot(horizontal, vertical * ratio);
221
- if (!Number.isFinite(upper)) return false;
222
- let multiplier = norm <= 1 + 8 * Number.EPSILON ? 0 : upper / 2;
223
- for (let i = 0; i < 96; i++) {
224
- const equatorial = 1 + multiplier,
225
- polar = ratio2 + multiplier;
226
- const u = horizontal / equatorial,
227
- v = (vertical * ratio) / polar;
228
- const length = Math.hypot(u, v);
229
- const residual = length - 1;
230
- if (Math.abs(residual) <= 4 * Number.EPSILON) {
231
- const latitude = Math.atan2(vertical / polar, horizontal / equatorial);
232
- const sin = Math.sin(latitude),
233
- cos = Math.cos(latitude);
234
- const height = p * cos + z * sin - a * Math.hypot(cos, ratio * sin);
235
- return commit(result, Math.atan2(y, x), latitude, height);
236
- }
237
- if (residual > 0) lower = multiplier;
238
- else upper = multiplier;
239
- const derivative = -((u * u) / equatorial + (v * v) / polar) / length;
240
- const next = multiplier - residual / derivative;
241
- multiplier = Number.isFinite(next) && next > lower && next < upper ? next : (lower + upper) / 2;
242
- }
243
- return false;
218
+ const sin = Math.sin(latitude),
219
+ cos = Math.cos(latitude);
220
+ return commit(result, longitude, latitude, p * cos + z * sin - a * Math.hypot(cos, ratio * sin));
244
221
  }
245
222
 
246
223
  /** Values are captured before invoking application setters; recursive writes are safe. */
@@ -1,10 +1,28 @@
1
1
  // math.gl
2
- // SPDX-License-Identifier: MIT
2
+ // SPDX-License-Identifier: MIT AND Apache-2.0
3
3
  // SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
4
+ // SPDX-FileCopyrightText: Copyright 2016-2019 The Apache Software Foundation
5
+ // SPDX-FileComment: BigInt availability/fallback structure adapted from Apache Arrow JS util/compat.ts; compared with apache-arrow-4.0.0. See ../../ARROW-NOTICE.txt and ../../APACHE-2.0-LICENSE.txt. Modified to separate availability flags, share errors and narrow exports.
6
+
7
+ // Licensed to the Apache Software Foundation (ASF) under one
8
+ // or more contributor license agreements. See the NOTICE file
9
+ // distributed with this work for additional information
10
+ // regarding copyright ownership. The ASF licenses this file
11
+ // to you under the Apache License, Version 2.0 (the
12
+ // "License"); you may not use this file except in compliance
13
+ // with the License. You may obtain a copy of the License at
14
+ //
15
+ // http://www.apache.org/licenses/LICENSE-2.0
16
+ //
17
+ // Unless required by applicable law or agreed to in writing,
18
+ // software distributed under the License is distributed on an
19
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
+ // KIND, either express or implied. See the License for the
21
+ // specific language governing permissions and limitations
22
+ // under the License.
4
23
 
5
24
  // BigInt compatibility layer
6
- // Inspired by ArrowJS (under Apache2 license)
7
- // https://github.com/apache/arrow/blob/master/js/src/util/compat.ts
25
+ // Source comparison: https://github.com/apache/arrow/blob/apache-arrow-4.0.0/js/src/util/compat.ts
8
26
  // Requires tsconfig.json: target=esnext or (lib: esnext.bigint)
9
27
  // Requires eslint: env: {es2020: true}
10
28
 
@@ -48,7 +66,7 @@ const BigInt64ArrayCtor = (() => {
48
66
  })();
49
67
 
50
68
  const BigUint64ArrayCtor = (() => {
51
- return BigUint64ArrayAvailable ? [BigUint64Array] : [BigInt64ArrayUnavailable];
69
+ return BigUint64ArrayAvailable ? BigUint64Array : BigInt64ArrayUnavailable;
52
70
  })();
53
71
 
54
72
  export {BigIntCtor as BigInt};
@@ -15,7 +15,7 @@ export type Bounds =
15
15
 
16
16
  /** Checks if a `Bounds` is a `Bounds2D` */
17
17
  export function isBounds2D(bounds: Bounds): bounds is Bounds2D {
18
- return bounds.length === 2;
18
+ return bounds.length === 2 && bounds[0].length === 2 && bounds[1].length === 2;
19
19
  }
20
20
 
21
21
  /** Accepts 2D and 3D bounds and returns a 2D bound, truncating 3D dimension if necessary */