@math.gl/geospatial 5.0.0-beta.1 → 5.0.0-beta.2
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.
- package/dist/crs/crs-identifier.d.ts +23 -0
- package/dist/crs/crs-identifier.d.ts.map +1 -0
- package/dist/crs/crs-identifier.js +74 -0
- package/dist/crs/crs-identifier.js.map +1 -0
- package/dist/crs/index.cjs +48 -0
- package/dist/crs/index.cjs.map +2 -2
- package/dist/crs/index.d.ts +1 -0
- package/dist/crs/index.d.ts.map +1 -1
- package/dist/crs/index.js +1 -0
- package/dist/crs/index.js.map +1 -1
- package/dist/discontinuous/cap-map.d.ts.map +1 -1
- package/dist/discontinuous/cap-map.js +197 -13
- package/dist/discontinuous/cap-map.js.map +1 -1
- package/dist/discontinuous/elastic/geometry.d.ts +1 -1
- package/dist/discontinuous/elastic/geometry.d.ts.map +1 -1
- package/dist/discontinuous/elastic/geometry.js +7 -7
- package/dist/discontinuous/elastic/geometry.js.map +1 -1
- package/dist/discontinuous/elastic/map.d.ts.map +1 -1
- package/dist/discontinuous/elastic/map.js +263 -23
- package/dist/discontinuous/elastic/map.js.map +1 -1
- package/dist/discontinuous/elastic/source-arc.d.ts +10 -0
- package/dist/discontinuous/elastic/source-arc.d.ts.map +1 -0
- package/dist/discontinuous/elastic/source-arc.js +111 -0
- package/dist/discontinuous/elastic/source-arc.js.map +1 -0
- package/dist/discontinuous/global-polygons.d.ts +1 -1
- package/dist/discontinuous/global-polygons.d.ts.map +1 -1
- package/dist/discontinuous/global-polygons.js +49 -1
- package/dist/discontinuous/global-polygons.js.map +1 -1
- package/dist/discontinuous/map-engine.d.ts +9 -0
- package/dist/discontinuous/map-engine.d.ts.map +1 -1
- package/dist/discontinuous/map-layout.d.ts +21 -0
- package/dist/discontinuous/map-layout.d.ts.map +1 -1
- package/dist/discontinuous/map-layout.js +116 -1
- package/dist/discontinuous/map-layout.js.map +1 -1
- package/dist/discontinuous/map-transition.d.ts.map +1 -1
- package/dist/discontinuous/map-transition.js +66 -3
- package/dist/discontinuous/map-transition.js.map +1 -1
- package/dist/discontinuous/mercator-map.d.ts +2 -2
- package/dist/discontinuous/mercator-map.d.ts.map +1 -1
- package/dist/discontinuous/mercator-map.js +123 -11
- package/dist/discontinuous/mercator-map.js.map +1 -1
- package/dist/discontinuous/polygon-constraints.d.ts +16 -0
- package/dist/discontinuous/polygon-constraints.d.ts.map +1 -0
- package/dist/discontinuous/polygon-constraints.js +157 -0
- package/dist/discontinuous/polygon-constraints.js.map +1 -0
- package/dist/discontinuous/prepare-features.d.ts +27 -0
- package/dist/discontinuous/prepare-features.d.ts.map +1 -1
- package/dist/discontinuous/prepare-features.js +106 -5
- package/dist/discontinuous/prepare-features.js.map +1 -1
- package/dist/discontinuous/rotated-map.d.ts +9 -0
- package/dist/discontinuous/rotated-map.d.ts.map +1 -1
- package/dist/discontinuous/source-arc.d.ts +4 -0
- package/dist/discontinuous/source-arc.d.ts.map +1 -0
- package/dist/discontinuous/source-arc.js +23 -0
- package/dist/discontinuous/source-arc.js.map +1 -0
- package/dist/discontinuous/world-kernels.d.ts +1 -1
- package/dist/discontinuous.cjs +1047 -75
- package/dist/discontinuous.cjs.map +3 -3
- package/dist/index.cjs +48 -0
- package/dist/index.cjs.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/turf.cjs +69 -5
- package/dist/turf.cjs.map +2 -2
- package/dist/turf.d.ts +9 -0
- package/dist/turf.d.ts.map +1 -1
- package/dist/turf.js +67 -0
- package/dist/turf.js.map +1 -1
- package/dist/workers/index.cjs +1075 -79
- package/dist/workers/index.cjs.map +3 -3
- package/dist/workers/operations.cjs +1049 -77
- package/dist/workers/operations.cjs.map +3 -3
- package/package.json +5 -5
- package/src/crs/crs-identifier.ts +86 -0
- package/src/crs/index.ts +1 -0
- package/src/discontinuous/cap-map.ts +248 -19
- package/src/discontinuous/elastic/geometry.ts +18 -7
- package/src/discontinuous/elastic/map.ts +334 -30
- package/src/discontinuous/elastic/source-arc.ts +129 -0
- package/src/discontinuous/global-polygons.ts +56 -2
- package/src/discontinuous/map-layout.ts +165 -2
- package/src/discontinuous/map-transition.ts +67 -3
- package/src/discontinuous/mercator-map.ts +151 -12
- package/src/discontinuous/polygon-constraints.ts +184 -0
- package/src/discontinuous/prepare-features.ts +154 -6
- package/src/discontinuous/source-arc.ts +31 -0
- package/src/index.ts +1 -0
- package/src/turf.ts +86 -0
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
-
"version": "5.0.0-beta.
|
|
9
|
+
"version": "5.0.0-beta.2",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"webgl",
|
|
12
12
|
"javascript",
|
|
@@ -1046,9 +1046,9 @@
|
|
|
1046
1046
|
],
|
|
1047
1047
|
"dependencies": {
|
|
1048
1048
|
"@mapbox/martini": "0.2.0",
|
|
1049
|
-
"@math.gl/core": "5.0.0-beta.
|
|
1050
|
-
"@math.gl/geometry": "5.0.0-beta.
|
|
1051
|
-
"@math.gl/web-mercator": "5.0.0-beta.
|
|
1049
|
+
"@math.gl/core": "5.0.0-beta.2",
|
|
1050
|
+
"@math.gl/geometry": "5.0.0-beta.2",
|
|
1051
|
+
"@math.gl/web-mercator": "5.0.0-beta.2",
|
|
1052
1052
|
"polygon-clipping": "0.15.7"
|
|
1053
1053
|
},
|
|
1054
1054
|
"devDependencies": {
|
|
@@ -1060,5 +1060,5 @@
|
|
|
1060
1060
|
"proj4": "2.22.0"
|
|
1061
1061
|
},
|
|
1062
1062
|
"sideEffects": false,
|
|
1063
|
-
"gitHead": "
|
|
1063
|
+
"gitHead": "f5c7bfdb4d9232e793237f08a86b513aedccc0a0"
|
|
1064
1064
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {CRSIdentifier} from './crs';
|
|
6
|
+
|
|
7
|
+
/** WMS 1.3 `CRS:` codes and the OGC register entries they abbreviate. */
|
|
8
|
+
const WMS_CRS_CODES = new Map<string, string>([
|
|
9
|
+
['84', 'CRS84'],
|
|
10
|
+
['83', 'CRS83'],
|
|
11
|
+
['27', 'CRS27']
|
|
12
|
+
]);
|
|
13
|
+
/** Explicit input conveniences, not a case-insensitivity rule for all registry names. */
|
|
14
|
+
const KNOWN_AUTHORITIES = new Set(['EPSG', 'OGC', 'ESRI', 'IAU']);
|
|
15
|
+
const OGC_CRS_CODES = new Map<string, string>([
|
|
16
|
+
['CRS84', 'CRS84'],
|
|
17
|
+
['CRS83', 'CRS83'],
|
|
18
|
+
['CRS27', 'CRS27'],
|
|
19
|
+
['CRS84H', 'CRS84h']
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const AUTHORITY_CODE = /^([A-Z][A-Z0-9_-]*):([A-Z0-9_.-]+)$/i;
|
|
23
|
+
const OGC_URN = /^urn:ogc:def:crs:([A-Z][A-Z0-9_-]*):([^:]*):([A-Z0-9_.-]+)$/i;
|
|
24
|
+
const OGC_URI =
|
|
25
|
+
/^https?:\/\/www\.opengis\.net\/def\/crs\/([A-Z][A-Z0-9_-]*)\/([^/]+)\/([A-Z0-9_.-]+)\/?$/i;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Normalizes recognized spellings of an authority CRS identifier to `AUTHORITY:CODE`.
|
|
29
|
+
*
|
|
30
|
+
* Recognized forms are `AUTHORITY:CODE`, the WMS 1.3 codes `CRS:84`, `CRS:83` and `CRS:27`,
|
|
31
|
+
* OGC URNs (`urn:ogc:def:crs:EPSG::32619`) and OGC URIs
|
|
32
|
+
* (`http://www.opengis.net/def/crs/EPSG/0/32619`). Known authority prefixes (EPSG, OGC, ESRI and IAU) and explicitly known OGC CRS codes
|
|
33
|
+
* accept case-insensitive input aliases. Other authority and code spellings are preserved.
|
|
34
|
+
*
|
|
35
|
+
* A version is removed only when it is empty or `0`, or when it is `1.3` on an OGC `CRS84`,
|
|
36
|
+
* `CRS83` or `CRS27` code, which the OGC register publishes only at that version. Any other
|
|
37
|
+
* versioned identifier is returned unchanged, so it does not compare equal to an unversioned one.
|
|
38
|
+
* Unrecognized strings are also returned unchanged.
|
|
39
|
+
*
|
|
40
|
+
* The result identifies a registry entry only. Different codes are never equated: `EPSG:4326` and
|
|
41
|
+
* `OGC:CRS84` stay distinct, as do Web Mercator aliases. No datum equivalence, axis-order
|
|
42
|
+
* convention or registry lookup is applied.
|
|
43
|
+
*
|
|
44
|
+
* @param identifier - CRS identifier as written by a source or service.
|
|
45
|
+
* @returns The canonical `AUTHORITY:CODE` identifier, or `identifier` unchanged.
|
|
46
|
+
*/
|
|
47
|
+
export function normalizeCRSIdentifier(identifier: CRSIdentifier): CRSIdentifier {
|
|
48
|
+
const text = identifier.trim();
|
|
49
|
+
|
|
50
|
+
const authorityCode = AUTHORITY_CODE.exec(text);
|
|
51
|
+
if (authorityCode) {
|
|
52
|
+
const [, authority, code] = authorityCode;
|
|
53
|
+
if (authority.toUpperCase() === 'CRS') {
|
|
54
|
+
const ogcCode = WMS_CRS_CODES.get(code);
|
|
55
|
+
return ogcCode ? `OGC:${ogcCode}` : identifier;
|
|
56
|
+
}
|
|
57
|
+
return formatIdentifier(authority, code);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const versioned = OGC_URN.exec(text) || OGC_URI.exec(text);
|
|
61
|
+
if (versioned) {
|
|
62
|
+
const [, authority, version, code] = versioned;
|
|
63
|
+
return isUnversioned(authority, version, code) ? formatIdentifier(authority, code) : identifier;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return identifier;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Whether a URN or URI version names no particular revision of the registry entry. */
|
|
70
|
+
function isUnversioned(authority: string, version: string, code: string): boolean {
|
|
71
|
+
if (version === '' || version === '0') return true;
|
|
72
|
+
const ogcCode = code.toUpperCase();
|
|
73
|
+
return (
|
|
74
|
+
authority.toUpperCase() === 'OGC' &&
|
|
75
|
+
version === '1.3' &&
|
|
76
|
+
Array.from(WMS_CRS_CODES.values()).includes(ogcCode)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatIdentifier(authority: string, code: string): CRSIdentifier {
|
|
81
|
+
const upperAuthority = authority.toUpperCase();
|
|
82
|
+
const canonicalAuthority = KNOWN_AUTHORITIES.has(upperAuthority) ? upperAuthority : authority;
|
|
83
|
+
const canonicalCode =
|
|
84
|
+
canonicalAuthority === 'OGC' ? OGC_CRS_CODES.get(code.toUpperCase()) || code : code;
|
|
85
|
+
return `${canonicalAuthority}:${canonicalCode}`;
|
|
86
|
+
}
|
package/src/crs/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export type {
|
|
|
32
32
|
UnresolvedCRSReference
|
|
33
33
|
} from './spatial-reference';
|
|
34
34
|
export {createSpatialReference, inferCRSRepresentation} from './spatial-reference';
|
|
35
|
+
export {normalizeCRSIdentifier} from './crs-identifier';
|
|
35
36
|
export type {
|
|
36
37
|
EncodeWKTCRSOptions,
|
|
37
38
|
ParseWKTCRSOptions,
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
import clipCircle from './spherical-clip/clip-circle.js';
|
|
6
|
+
import {tracePolygonConstraints, composePolygonEdge} from './polygon-constraints';
|
|
7
|
+
import {sourceArc} from './source-arc';
|
|
6
8
|
import {circleStream} from './spherical-clip/circle.js';
|
|
7
9
|
import {
|
|
8
10
|
DiscontinuousMap,
|
|
@@ -91,11 +93,15 @@ export class CapMap extends DiscontinuousMap {
|
|
|
91
93
|
rings: readonly (readonly GeographicPosition[])[],
|
|
92
94
|
polygon: boolean,
|
|
93
95
|
options?: MapSamplingOptions,
|
|
94
|
-
regionId?: string
|
|
96
|
+
regionId?: string,
|
|
97
|
+
segmentIndices?: number[][]
|
|
95
98
|
): Position[][] {
|
|
96
99
|
const output: Position[][] = [];
|
|
97
100
|
const originals = new Map<string, Position>();
|
|
98
|
-
let ring: Position[] = []
|
|
101
|
+
let ring: Position[] = [],
|
|
102
|
+
indices: number[] = [],
|
|
103
|
+
activeSegment = 0;
|
|
104
|
+
let previousInput: GeographicPosition | undefined, currentInput: GeographicPosition | undefined;
|
|
99
105
|
const precision =
|
|
100
106
|
Math.min(
|
|
101
107
|
options?.maxAngularStep ?? 2,
|
|
@@ -184,13 +190,60 @@ export class CapMap extends DiscontinuousMap {
|
|
|
184
190
|
point: (x: number, y: number) => {
|
|
185
191
|
if (++emitted > 1_000_000)
|
|
186
192
|
throw new Error('Projection sampling limit exceeded; relax tolerance');
|
|
187
|
-
|
|
193
|
+
let source = originals.get(`${x},${y}`);
|
|
194
|
+
if (!source && !polygon && this.nonlinear && previousInput && currentInput) {
|
|
195
|
+
// The local nonlinear transform bends source arcs. A circle/chord
|
|
196
|
+
// intersection must be refined on its owning original source arc.
|
|
197
|
+
const a = geographic(previousInput),
|
|
198
|
+
b = geographic(currentInput),
|
|
199
|
+
length = angle(a, b);
|
|
200
|
+
const at = (t: number): Position =>
|
|
201
|
+
length < 1e-14
|
|
202
|
+
? ([...currentInput!] as Position)
|
|
203
|
+
: coordinates(
|
|
204
|
+
unit(
|
|
205
|
+
a.map(
|
|
206
|
+
(v, k) =>
|
|
207
|
+
(Math.sin((1 - t) * length) * v + Math.sin(t * length) * b[k]) /
|
|
208
|
+
Math.sin(length)
|
|
209
|
+
) as Vector
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
const value = (t: number) => geographic(local(at(t)))[0] - Math.cos(this.cap);
|
|
213
|
+
const approximate = this.source([x / RAD, y / RAD]);
|
|
214
|
+
const reference = length ? Math.min(1, angle(a, geographic(approximate)) / length) : 0;
|
|
215
|
+
const roots: number[] = [];
|
|
216
|
+
for (let j = 0; j < 32; j++) {
|
|
217
|
+
let lo = j / 32,
|
|
218
|
+
hi = (j + 1) / 32,
|
|
219
|
+
f = value(lo),
|
|
220
|
+
g = value(hi);
|
|
221
|
+
if (Math.abs(f) < 1e-14) roots.push(lo);
|
|
222
|
+
if (f * g >= 0) continue;
|
|
223
|
+
for (let k = 0; k < 52; k++) {
|
|
224
|
+
const mid = (lo + hi) / 2;
|
|
225
|
+
if (value(mid) >= 0 === f >= 0) lo = mid;
|
|
226
|
+
else hi = mid;
|
|
227
|
+
}
|
|
228
|
+
roots.push((lo + hi) / 2);
|
|
229
|
+
}
|
|
230
|
+
if (Math.abs(value(1)) < 1e-14) roots.push(1);
|
|
231
|
+
if (!roots.length) throw new Error('Unresolved nonlinear cap source intersection');
|
|
232
|
+
roots.sort((a, b) => Math.abs(a - reference) - Math.abs(b - reference));
|
|
233
|
+
source = at(roots[0]);
|
|
234
|
+
}
|
|
235
|
+
ring.push(source ?? this.source([x / RAD, y / RAD]));
|
|
236
|
+
if (segmentIndices) indices.push(activeSegment);
|
|
188
237
|
},
|
|
189
238
|
lineStart: () => {
|
|
190
239
|
ring = [];
|
|
240
|
+
indices = [];
|
|
191
241
|
},
|
|
192
242
|
lineEnd: () => {
|
|
193
|
-
if (ring.length > 1)
|
|
243
|
+
if (ring.length > 1) {
|
|
244
|
+
output.push(ring);
|
|
245
|
+
segmentIndices?.push(indices);
|
|
246
|
+
}
|
|
194
247
|
},
|
|
195
248
|
polygonStart: () => {},
|
|
196
249
|
polygonEnd: () => {}
|
|
@@ -207,7 +260,9 @@ export class CapMap extends DiscontinuousMap {
|
|
|
207
260
|
const x = q[0] * RAD,
|
|
208
261
|
y = q[1] * RAD;
|
|
209
262
|
originals.set(`${x},${y}`, [...p]);
|
|
263
|
+
currentInput = p;
|
|
210
264
|
stream.point(x, y);
|
|
265
|
+
previousInput = p;
|
|
211
266
|
};
|
|
212
267
|
const subdivide = (a: GeographicPosition, b: GeographicPosition, depth = 0) => {
|
|
213
268
|
if (this.nonlinear && depth < 20) {
|
|
@@ -232,9 +287,13 @@ export class CapMap extends DiscontinuousMap {
|
|
|
232
287
|
emit(b);
|
|
233
288
|
};
|
|
234
289
|
for (const points of rings) {
|
|
290
|
+
previousInput = undefined;
|
|
235
291
|
stream.lineStart();
|
|
236
292
|
if (points.length) emit(points[0]);
|
|
237
|
-
for (let i = 1; i < points.length; i++)
|
|
293
|
+
for (let i = 1; i < points.length; i++) {
|
|
294
|
+
activeSegment = i - 1;
|
|
295
|
+
subdivide(points[i - 1], points[i]);
|
|
296
|
+
}
|
|
238
297
|
if (polygon && points.length) subdivide(points[points.length - 1], points[0]);
|
|
239
298
|
stream.lineEnd();
|
|
240
299
|
}
|
|
@@ -246,15 +305,70 @@ export class CapMap extends DiscontinuousMap {
|
|
|
246
305
|
options?: MapSamplingOptions
|
|
247
306
|
): MapLine[] {
|
|
248
307
|
if (this.cap === Math.PI / 2 && !this.nonlinear) return super.projectLine(line, options);
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
308
|
+
// Attribute streaming intersections while the owning sampled edge is active.
|
|
309
|
+
// Rigid spherical rotations preserve the original great-circle semantics.
|
|
310
|
+
return super.projectLine(line, options).flatMap(part => {
|
|
311
|
+
const indices: number[][] | undefined = part.provenance ? [] : undefined;
|
|
312
|
+
const clipped = this.clip([part.sourceCoordinates], false, options, part.regionId, indices);
|
|
313
|
+
return clipped.flatMap((points, piece) =>
|
|
314
|
+
super
|
|
315
|
+
.projectLine(points, options)
|
|
316
|
+
.filter(q => q.regionId === part.regionId)
|
|
317
|
+
.map(q => {
|
|
318
|
+
const parent = part.provenance,
|
|
319
|
+
child = q.provenance;
|
|
320
|
+
if (!parent || !child) return q;
|
|
321
|
+
const provenance: NonNullable<MapLine['provenance']> = {
|
|
322
|
+
segments: [],
|
|
323
|
+
parameters: [],
|
|
324
|
+
vertices: [],
|
|
325
|
+
angularDistances: []
|
|
326
|
+
};
|
|
327
|
+
for (let j = 0; j < q.sourceCoordinates.length; j++) {
|
|
328
|
+
const k = child.segments[j];
|
|
329
|
+
// The incoming endpoint owns the clipped edge, including a newly
|
|
330
|
+
// generated entry point followed by its original end vertex.
|
|
331
|
+
const edge = indices![piece][k + 1];
|
|
332
|
+
const a = geographic(part.sourceCoordinates[edge]),
|
|
333
|
+
b = geographic(part.sourceCoordinates[edge + 1]),
|
|
334
|
+
p = geographic(q.sourceCoordinates[j]);
|
|
335
|
+
const length = angle(a, b),
|
|
336
|
+
t = length ? Math.min(1, angle(a, p) / length) : 0;
|
|
337
|
+
const endpoint = t < 1e-12 ? edge : t > 1 - 1e-12 ? edge + 1 : -1;
|
|
338
|
+
const segment = parent.segments[edge + 1];
|
|
339
|
+
const from = parent.segments[edge] === segment ? parent.parameters[edge] : 0;
|
|
340
|
+
provenance.segments.push(endpoint >= 0 ? parent.segments[endpoint] : segment);
|
|
341
|
+
provenance.parameters.push(
|
|
342
|
+
endpoint >= 0
|
|
343
|
+
? parent.parameters[endpoint]
|
|
344
|
+
: from + t * (parent.parameters[edge + 1] - from)
|
|
345
|
+
);
|
|
346
|
+
provenance.vertices.push(endpoint >= 0 ? parent.vertices[endpoint] : 0xffffffff);
|
|
347
|
+
if (options?.pathDistances)
|
|
348
|
+
provenance.angularDistances.push(
|
|
349
|
+
parent.angularDistances[edge] +
|
|
350
|
+
t * (parent.angularDistances[edge + 1] - parent.angularDistances[edge])
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
if (options?.pathBoundaries) {
|
|
354
|
+
const same = (a: GeographicPosition, b: GeographicPosition) =>
|
|
355
|
+
angle(geographic(a), geographic(b)) < 1e-12;
|
|
356
|
+
provenance.startReason = same(q.sourceCoordinates[0], part.sourceCoordinates[0])
|
|
357
|
+
? parent.startReason
|
|
358
|
+
: 'coverage-boundary';
|
|
359
|
+
provenance.endReason = same(
|
|
360
|
+
q.sourceCoordinates[q.sourceCoordinates.length - 1],
|
|
361
|
+
part.sourceCoordinates[part.sourceCoordinates.length - 1]
|
|
362
|
+
)
|
|
363
|
+
? parent.endReason
|
|
364
|
+
: 'coverage-boundary';
|
|
365
|
+
}
|
|
366
|
+
return {...q, provenance};
|
|
367
|
+
})
|
|
256
368
|
);
|
|
369
|
+
});
|
|
257
370
|
}
|
|
371
|
+
|
|
258
372
|
override projectPolygon(
|
|
259
373
|
rings: readonly (readonly GeographicPosition[])[],
|
|
260
374
|
options?: MapSamplingOptions
|
|
@@ -356,19 +470,134 @@ export class CapMap extends DiscontinuousMap {
|
|
|
356
470
|
for (const ring of outer) {
|
|
357
471
|
const plane = ring.map(planar),
|
|
358
472
|
sources = [ring, ...holes.filter(h => contains(plane, planar(h[0])))];
|
|
473
|
+
const parentPlane = part.sourceCoordinates.map(r => r.map(planar));
|
|
474
|
+
const fromPlane = (p: Position): Position =>
|
|
475
|
+
coordinates(unit(center.map((v, k) => v + x[k] * p[0] + y[k] * p[1]) as Vector));
|
|
476
|
+
const traced = tracePolygonConstraints(
|
|
477
|
+
parentPlane,
|
|
478
|
+
sources.map(r => r.map(planar)),
|
|
479
|
+
this.nonlinear ? 2e-6 : 1e-10,
|
|
480
|
+
part.edgeProvenance,
|
|
481
|
+
p => this.visible(fromPlane(p))
|
|
482
|
+
);
|
|
483
|
+
const resolved = traced.rings.map(r => r.map(fromPlane));
|
|
484
|
+
traced.owners.forEach((owners, r) =>
|
|
485
|
+
owners.forEach((owner, i) => {
|
|
486
|
+
if (!owner) return;
|
|
487
|
+
const [pr, pe] = owner,
|
|
488
|
+
a = part.sourceCoordinates[pr][pe],
|
|
489
|
+
b = part.sourceCoordinates[pr][pe + 1],
|
|
490
|
+
at = sourceArc(a, b);
|
|
491
|
+
for (const j of [i, i + 1]) {
|
|
492
|
+
const point = resolved[r][j],
|
|
493
|
+
distance = angle(geographic(a), geographic(b)),
|
|
494
|
+
fraction = distance ? angle(geographic(a), geographic(point)) / distance : 0;
|
|
495
|
+
if (fraction < 1e-10) resolved[r][j] = [...a];
|
|
496
|
+
else if (fraction > 1 - 1e-10) resolved[r][j] = [...b];
|
|
497
|
+
else if (
|
|
498
|
+
Math.hypot(...cross(geographic(a), geographic(b))) > 1e-14 &&
|
|
499
|
+
Math.abs(dot(unit(cross(geographic(a), geographic(b))), geographic(point))) < 1e-12
|
|
500
|
+
) {
|
|
501
|
+
resolved[r][j] = at(Math.min(1, Math.max(0, fraction)));
|
|
502
|
+
} else {
|
|
503
|
+
const value = (t: number) => geographic(this.local(at(t)))[0] - Math.cos(this.cap),
|
|
504
|
+
roots: number[] = [];
|
|
505
|
+
for (let k = 0; k < 32; k++) {
|
|
506
|
+
let lo = k / 32,
|
|
507
|
+
hi = (k + 1) / 32,
|
|
508
|
+
f = value(lo),
|
|
509
|
+
g = value(hi);
|
|
510
|
+
if (Math.abs(f) < 1e-13) roots.push(lo);
|
|
511
|
+
if (f * g >= 0) continue;
|
|
512
|
+
for (let n = 0; n < 52; n++) {
|
|
513
|
+
const mid = (lo + hi) / 2;
|
|
514
|
+
if (value(mid) >= 0 === f >= 0) lo = mid;
|
|
515
|
+
else hi = mid;
|
|
516
|
+
}
|
|
517
|
+
roots.push((lo + hi) / 2);
|
|
518
|
+
}
|
|
519
|
+
if (Math.abs(value(1)) < 1e-13) roots.push(1);
|
|
520
|
+
if (!roots.length) throw new Error('Unresolved cap polygon source intersection');
|
|
521
|
+
roots.sort((a, b) => Math.abs(a - fraction) - Math.abs(b - fraction));
|
|
522
|
+
resolved[r][j] = at(roots[0]);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
);
|
|
527
|
+
for (const ring of resolved) ring[ring.length - 1] = [...ring[0]];
|
|
359
528
|
result.push({
|
|
360
529
|
regionId: part.regionId,
|
|
361
|
-
sourceCoordinates:
|
|
362
|
-
coordinates:
|
|
530
|
+
sourceCoordinates: resolved,
|
|
531
|
+
coordinates: resolved.map(r =>
|
|
363
532
|
r.map(p => {
|
|
364
533
|
const q = super.projectPoint(p, part.regionId);
|
|
365
|
-
if (!q)
|
|
366
|
-
throw new Error(
|
|
367
|
-
`Cap point outside chart ${part.regionId}: ${p}, owner ${super.findRegions(p)}`
|
|
368
|
-
);
|
|
534
|
+
if (!q) throw new Error('Cap polygon source constraint left its owning chart');
|
|
369
535
|
return q.position;
|
|
370
536
|
})
|
|
371
|
-
)
|
|
537
|
+
),
|
|
538
|
+
...(options?.polygonEdgeProvenance
|
|
539
|
+
? {
|
|
540
|
+
edgeProvenance: traced.owners.map((owners, r) =>
|
|
541
|
+
owners.map((owner, i) => {
|
|
542
|
+
const a = resolved[r][i],
|
|
543
|
+
b = resolved[r][i + 1];
|
|
544
|
+
if (owner) {
|
|
545
|
+
const [pr, pe] = owner;
|
|
546
|
+
return composePolygonEdge(
|
|
547
|
+
part.edgeProvenance![pr][pe],
|
|
548
|
+
part.sourceCoordinates[pr][pe],
|
|
549
|
+
part.sourceCoordinates[pr][pe + 1],
|
|
550
|
+
a,
|
|
551
|
+
b
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
if (angle(geographic(a), geographic(b)) < 64 * Number.EPSILON)
|
|
555
|
+
return {
|
|
556
|
+
kind: 'continuous-chart-join' as const,
|
|
557
|
+
ring: 0xffffffff,
|
|
558
|
+
segment: 0xffffffff,
|
|
559
|
+
t0: 0,
|
|
560
|
+
t1: 0
|
|
561
|
+
};
|
|
562
|
+
const kinds: string[] = [];
|
|
563
|
+
region.boundary.forEach((p, j) => {
|
|
564
|
+
const n = cross(
|
|
565
|
+
geographic(p),
|
|
566
|
+
geographic(region.boundary[(j + 1) % region.boundary.length])
|
|
567
|
+
),
|
|
568
|
+
length = Math.hypot(...n);
|
|
569
|
+
if (
|
|
570
|
+
length &&
|
|
571
|
+
[a, b].every(p => Math.abs(dot(n, geographic(p))) / length < 1e-9)
|
|
572
|
+
) {
|
|
573
|
+
const seam = this.seams.find(s =>
|
|
574
|
+
s.regions.some((id, k) => id === region.id && s.edges[k] === j)
|
|
575
|
+
);
|
|
576
|
+
if (seam) kinds.push(seam.kind);
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
const horizon = [a, b].every(
|
|
580
|
+
p => Math.abs(geographic(this.local(p))[0] - Math.cos(this.cap)) < 1e-7
|
|
581
|
+
);
|
|
582
|
+
if (!horizon && !kinds.length)
|
|
583
|
+
throw new Error('Unknown cap polygon clipping constraint');
|
|
584
|
+
return {
|
|
585
|
+
kind: horizon
|
|
586
|
+
? ('coverage-boundary' as const)
|
|
587
|
+
: kinds.includes('boundary')
|
|
588
|
+
? ('coverage-boundary' as const)
|
|
589
|
+
: kinds.includes('cut')
|
|
590
|
+
? ('chart-cut' as const)
|
|
591
|
+
: ('continuous-chart-join' as const),
|
|
592
|
+
ring: 0xffffffff,
|
|
593
|
+
segment: 0xffffffff,
|
|
594
|
+
t0: 0,
|
|
595
|
+
t1: 0
|
|
596
|
+
};
|
|
597
|
+
})
|
|
598
|
+
)
|
|
599
|
+
}
|
|
600
|
+
: {})
|
|
372
601
|
});
|
|
373
602
|
}
|
|
374
603
|
}
|
|
@@ -89,7 +89,8 @@ export function sample(
|
|
|
89
89
|
line: Position[],
|
|
90
90
|
project: (p: Position) => Position | null,
|
|
91
91
|
options: MapSamplingOptions,
|
|
92
|
-
cellStep: number
|
|
92
|
+
cellStep: number,
|
|
93
|
+
interpolate?: (segment: number, t: number) => Position
|
|
93
94
|
) {
|
|
94
95
|
const {step, tolerance} = sampling(options),
|
|
95
96
|
source: Position[] = [],
|
|
@@ -101,11 +102,21 @@ export function sample(
|
|
|
101
102
|
};
|
|
102
103
|
const at = (p: Position) => {
|
|
103
104
|
const q = project(p);
|
|
104
|
-
if (!q) throw new Error(
|
|
105
|
+
if (!q) throw new Error(`Elastic grid is undefined inside its coverage at ${p}`);
|
|
105
106
|
return q;
|
|
106
107
|
};
|
|
107
|
-
const visit = (
|
|
108
|
-
|
|
108
|
+
const visit = (
|
|
109
|
+
a: Position,
|
|
110
|
+
b: Position,
|
|
111
|
+
qa: Position,
|
|
112
|
+
qb: Position,
|
|
113
|
+
depth: number,
|
|
114
|
+
segment: number,
|
|
115
|
+
t0: number,
|
|
116
|
+
t1: number
|
|
117
|
+
) => {
|
|
118
|
+
const tm = (t0 + t1) / 2,
|
|
119
|
+
mid = interpolate ? interpolate(segment, tm) : lerp(a, b, 0.5),
|
|
109
120
|
qm = at(mid),
|
|
110
121
|
chord = lerp(qa, qb, 0.5);
|
|
111
122
|
if (
|
|
@@ -113,14 +124,14 @@ export function sample(
|
|
|
113
124
|
Math.hypot(qm[0] - chord[0], qm[1] - chord[1]) > tolerance
|
|
114
125
|
) {
|
|
115
126
|
if (depth >= 24) throw new Error('Elastic sampling limit exceeded');
|
|
116
|
-
visit(a, mid, qa, qm, depth + 1);
|
|
117
|
-
visit(mid, b, qm, qb, depth + 1);
|
|
127
|
+
visit(a, mid, qa, qm, depth + 1, segment, t0, tm);
|
|
128
|
+
visit(mid, b, qm, qb, depth + 1, segment, tm, t1);
|
|
118
129
|
} else emit(b, qb);
|
|
119
130
|
};
|
|
120
131
|
if (line.length) {
|
|
121
132
|
emit(line[0], at(line[0]));
|
|
122
133
|
for (let i = 1; i < line.length; i++)
|
|
123
|
-
visit(line[i - 1], line[i], at(line[i - 1]), at(line[i]), 0);
|
|
134
|
+
visit(line[i - 1], line[i], at(line[i - 1]), at(line[i]), 0, i - 1, 0, 1);
|
|
124
135
|
}
|
|
125
136
|
return {source, xy};
|
|
126
137
|
}
|