@tangram.gl/basemap-layers 0.0.0-stage → 0.0.1

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 (105) hide show
  1. package/README.md +34 -2
  2. package/THIRD-PARTY-NOTICES.md +205 -0
  3. package/dist/geojson-worker.js +2160 -0
  4. package/dist/geojson-worker.js.map +6 -0
  5. package/dist/index.cjs +11388 -0
  6. package/dist/index.cjs.map +6 -0
  7. package/dist/index.js +11369 -0
  8. package/dist/index.js.map +6 -0
  9. package/dist/map-style.cjs +445 -0
  10. package/dist/map-style.cjs.map +6 -0
  11. package/dist/map-style.js +431 -0
  12. package/dist/map-style.js.map +6 -0
  13. package/dist/types/arabic-shaping.d.ts +7 -0
  14. package/dist/types/atmosphere-layer.d.ts +10 -0
  15. package/dist/types/basemap-layer.d.ts +150 -0
  16. package/dist/types/bidi.d.ts +8 -0
  17. package/dist/types/circle-layer.d.ts +49 -0
  18. package/dist/types/fill-extrusion-lighting-extension.d.ts +49 -0
  19. package/dist/types/fill-outline-extension.d.ts +22 -0
  20. package/dist/types/fill-pattern.d.ts +99 -0
  21. package/dist/types/fill-style-extensions.d.ts +100 -0
  22. package/dist/types/geojson-source.d.ts +29 -0
  23. package/dist/types/geojson-tiler.d.ts +48 -0
  24. package/dist/types/geojson-worker.d.ts +40 -0
  25. package/dist/types/globe-horizon-extension.d.ts +61 -0
  26. package/dist/types/globe-layers.d.ts +239 -0
  27. package/dist/types/globe-line-angle-extension.d.ts +37 -0
  28. package/dist/types/glyph-font-renderer.d.ts +37 -0
  29. package/dist/types/glyph-pbf.d.ts +37 -0
  30. package/dist/types/glyph-store.d.ts +103 -0
  31. package/dist/types/grid-index.d.ts +47 -0
  32. package/dist/types/hillshade-layer.d.ts +154 -0
  33. package/dist/types/index.d.ts +9 -0
  34. package/dist/types/is-globe-viewport.d.ts +2 -0
  35. package/dist/types/label-font-renderer.d.ts +18 -0
  36. package/dist/types/label-placement.d.ts +247 -0
  37. package/dist/types/label-text-layer.d.ts +231 -0
  38. package/dist/types/line-blur-extension.d.ts +73 -0
  39. package/dist/types/line-glyphs.d.ts +60 -0
  40. package/dist/types/line-placement.d.ts +93 -0
  41. package/dist/types/map-style-loader.d.ts +48 -0
  42. package/dist/types/map-style-schema.d.ts +72 -0
  43. package/dist/types/map-style.d.ts +6 -0
  44. package/dist/types/mapbox-style.d.ts +40 -0
  45. package/dist/types/mvt-label-layer.d.ts +923 -0
  46. package/dist/types/polylabel.d.ts +17 -0
  47. package/dist/types/raster-color-extension.d.ts +146 -0
  48. package/dist/types/shared-texture.d.ts +34 -0
  49. package/dist/types/sort-key.d.ts +10 -0
  50. package/dist/types/sprite.d.ts +82 -0
  51. package/dist/types/style-accessor.d.ts +45 -0
  52. package/dist/types/style-expression.d.ts +83 -0
  53. package/dist/types/style-resolver.d.ts +94 -0
  54. package/dist/types/text-font.d.ts +36 -0
  55. package/dist/types/text-halo-blur-extension.d.ts +55 -0
  56. package/dist/types/text-layout.d.ts +71 -0
  57. package/dist/types/util.d.ts +22 -0
  58. package/dist/types/zoom-paint-extension.d.ts +72 -0
  59. package/package.json +80 -3
  60. package/src/arabic-shaping.ts +366 -0
  61. package/src/atmosphere-layer.ts +19 -0
  62. package/src/basemap-layer.ts +533 -0
  63. package/src/bidi.ts +199 -0
  64. package/src/circle-layer.ts +191 -0
  65. package/src/fill-extrusion-lighting-extension.ts +206 -0
  66. package/src/fill-outline-extension.ts +70 -0
  67. package/src/fill-pattern.ts +400 -0
  68. package/src/fill-style-extensions.ts +177 -0
  69. package/src/geojson-source.ts +318 -0
  70. package/src/geojson-tiler.ts +185 -0
  71. package/src/geojson-worker.ts +98 -0
  72. package/src/globe-horizon-extension.ts +64 -0
  73. package/src/globe-layers.ts +3487 -0
  74. package/src/globe-line-angle-extension.ts +131 -0
  75. package/src/glyph-font-renderer.ts +173 -0
  76. package/src/glyph-pbf.ts +164 -0
  77. package/src/glyph-store.ts +326 -0
  78. package/src/grid-index.ts +177 -0
  79. package/src/hillshade-layer.ts +775 -0
  80. package/src/index.ts +18 -0
  81. package/src/is-globe-viewport.ts +24 -0
  82. package/src/label-font-renderer.ts +159 -0
  83. package/src/label-placement.ts +727 -0
  84. package/src/label-text-layer.ts +913 -0
  85. package/src/line-blur-extension.ts +237 -0
  86. package/src/line-glyphs.ts +364 -0
  87. package/src/line-placement.ts +446 -0
  88. package/src/map-style-loader.ts +56 -0
  89. package/src/map-style-schema.ts +83 -0
  90. package/src/map-style.ts +23 -0
  91. package/src/mapbox-style.ts +168 -0
  92. package/src/mvt-label-layer.ts +3744 -0
  93. package/src/polylabel.ts +312 -0
  94. package/src/raster-color-extension.ts +245 -0
  95. package/src/shared-texture.ts +153 -0
  96. package/src/sort-key.ts +63 -0
  97. package/src/sprite.ts +244 -0
  98. package/src/style-accessor.ts +95 -0
  99. package/src/style-expression.ts +327 -0
  100. package/src/style-resolver.ts +301 -0
  101. package/src/text-font.ts +150 -0
  102. package/src/text-halo-blur-extension.ts +115 -0
  103. package/src/text-layout.ts +441 -0
  104. package/src/util.ts +47 -0
  105. package/src/zoom-paint-extension.ts +207 -0
@@ -0,0 +1,446 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /**
6
+ * Label anchors along lines, for `symbol-placement: line` and `line-center`. The placement follows
7
+ * MapLibre GL JS (`getAnchors`, `getCenterAnchor` and `checkMaxAngle` in its symbol code): labels
8
+ * repeat every `symbol-spacing` pixels where the line is long enough for them, and a placement is
9
+ * skipped where the line bends more than `text-max-angle` within the label.
10
+ *
11
+ * Lines are given in pixels, with y pointing down. An anchor is returned as the segment it lies
12
+ * on and its position along that segment, so the caller can interpolate the anchor in its own
13
+ * coordinates.
14
+ */
15
+
16
+ /** An anchor on a line: `t` along the segment from `line[segment]` to `line[segment + 1]`. */
17
+ export type LineAnchor = {
18
+ segment: number;
19
+ t: number;
20
+ /** The direction of the segment, in degrees counter-clockwise from the +x axis on screen. */
21
+ angle: number;
22
+ };
23
+
24
+ export type LineAnchorOptions = {
25
+ /** `line` repeats labels along the line; `line-center` places one at its middle. */
26
+ placement: 'line' | 'line-center';
27
+ /** `symbol-spacing`: pixels between two labels of a line. */
28
+ spacing: number;
29
+ /** `text-max-angle`: the largest bend, in degrees, within any window of the label. */
30
+ maxAngle: number;
31
+ /** The label's length along the line, in pixels. */
32
+ labelLength: number;
33
+ /** `text-size` in pixels; the bend window is 3/5 of it, as in MapLibre. */
34
+ textSize: number;
35
+ /**
36
+ * Whether the symbol has text. As in MapLibre, a symbol with only an icon has no bend window:
37
+ * the line may bend any amount under it. With `line` placement it must still fit on the line.
38
+ * Default: true.
39
+ */
40
+ hasText?: boolean;
41
+ /**
42
+ * Whether the line continues in a neighbouring tile. MapLibre then starts the first label half
43
+ * a spacing in, so labels line up across tile edges, and does not fall back to the middle.
44
+ */
45
+ isContinued: boolean;
46
+ /**
47
+ * Work the placement may spend, in steps along the line and vertices checked for bends. It is
48
+ * drawn down as the placement runs; once it is spent, no further anchors are placed. Tile
49
+ * geometry is untrusted, so callers share one budget across a tile.
50
+ */
51
+ budget?: LineAnchorBudget;
52
+ };
53
+
54
+ /** Remaining placement work; see `LineAnchorOptions.budget`. */
55
+ export type LineAnchorBudget = {steps: number};
56
+
57
+ /** Work one feature's line labels may spend, unless the tile's budget has less left. */
58
+ export const DEFAULT_LINE_ANCHOR_STEPS = 1e5;
59
+
60
+ /**
61
+ * The least distance, in pixels, between two labels of a line beyond the label's own length. It
62
+ * only matters for a `symbol-spacing` far below any label's length: with a spacing of 1, a short
63
+ * or empty label would otherwise be placed at every pixel of the line.
64
+ */
65
+ export const MIN_LABEL_GAP = 16;
66
+
67
+ /**
68
+ * MapLibre's first label on a line that starts in this tile is placed `2 * glyphSize` pixels past
69
+ * the label's half length, where the glyph size is the 24 px of its SDF glyphs, scaled to the text
70
+ * size.
71
+ */
72
+ const FIXED_EXTRA_OFFSET_EMS = 2;
73
+
74
+ function distance(a: number[], b: number[]): number {
75
+ return Math.hypot(b[0] - a[0], b[1] - a[1]);
76
+ }
77
+
78
+ /** Direction from `a` to `b`, in radians counter-clockwise on screen (y down). */
79
+ function direction(a: number[], b: number[]): number {
80
+ return Math.atan2(a[1] - b[1], b[0] - a[0]);
81
+ }
82
+
83
+ function getLineLength(line: number[][]): number {
84
+ let length = 0;
85
+ for (let i = 0; i < line.length - 1; i++) {
86
+ length += distance(line[i], line[i + 1]);
87
+ }
88
+ return length;
89
+ }
90
+
91
+ /**
92
+ * Whether the line bends no more than `maxAngle` (radians) within any `windowSize` pixels of the
93
+ * `labelLength` centred on the anchor. The label must also fit on the line around the anchor.
94
+ */
95
+ function checkMaxAngle(
96
+ line: number[][],
97
+ anchor: {point: number[]; segment: number},
98
+ labelLength: number,
99
+ windowSize: number,
100
+ maxAngle: number,
101
+ budget: LineAnchorBudget
102
+ ): boolean {
103
+ let point = anchor.point;
104
+ let index = anchor.segment + 1;
105
+ let anchorDistance = 0;
106
+
107
+ // Move back along the line to the first vertex under the label.
108
+ while (anchorDistance > -labelLength / 2) {
109
+ index--;
110
+ if (index < 0 || --budget.steps < 0) {
111
+ return false;
112
+ }
113
+ anchorDistance -= distance(line[index], point);
114
+ point = line[index];
115
+ }
116
+ if (!line[index + 1]) {
117
+ return false;
118
+ }
119
+ anchorDistance += distance(line[index], line[index + 1]);
120
+ index++;
121
+
122
+ // Move forward along the label, summing the bends within the window.
123
+ // A queue: corners leave from `oldest` rather than by `shift`, which moves every later entry.
124
+ const recentCorners: {distance: number; angleDelta: number}[] = [];
125
+ let oldest = 0;
126
+ let recentAngleDelta = 0;
127
+ while (anchorDistance < labelLength / 2) {
128
+ const previous = line[index - 1];
129
+ const current = line[index];
130
+ const next = line[index + 1];
131
+ if (!next || --budget.steps < 0) {
132
+ return false;
133
+ }
134
+ let angleDelta = direction(previous, current) - direction(current, next);
135
+ angleDelta = Math.abs(((angleDelta + 3 * Math.PI) % (Math.PI * 2)) - Math.PI);
136
+ recentCorners.push({distance: anchorDistance, angleDelta});
137
+ recentAngleDelta += angleDelta;
138
+ while (anchorDistance - recentCorners[oldest].distance > windowSize) {
139
+ recentAngleDelta -= recentCorners[oldest++].angleDelta;
140
+ }
141
+ if (recentAngleDelta > maxAngle) {
142
+ return false;
143
+ }
144
+ index++;
145
+ anchorDistance += distance(current, next);
146
+ }
147
+ return true;
148
+ }
149
+
150
+ function interpolate(a: number[], b: number[], t: number): number[] {
151
+ return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
152
+ }
153
+
154
+ /** The window within which bends are summed: 3/5 of the text size, or 0 for an icon alone. */
155
+ function getAngleWindowSize(options: LineAnchorOptions): number {
156
+ return options.hasText === false ? 0 : (options.textSize * 3) / 5;
157
+ }
158
+
159
+ /** Anchors every `spacing` pixels from `offset`, where the label fits and the line is straight. */
160
+ function resample(
161
+ line: number[][],
162
+ offset: number,
163
+ spacing: number,
164
+ options: LineAnchorOptions,
165
+ placeAtMiddle: boolean,
166
+ budget: LineAnchorBudget
167
+ ): LineAnchor[] {
168
+ const {labelLength, isContinued} = options;
169
+ const windowSize = getAngleWindowSize(options);
170
+ const maxAngle = (options.maxAngle * Math.PI) / 180;
171
+ const lineLength = getLineLength(line);
172
+ const anchors: LineAnchor[] = [];
173
+ let travelled = 0;
174
+ let markedDistance = offset - spacing;
175
+
176
+ for (let i = 0; i < line.length - 1; i++) {
177
+ const a = line[i];
178
+ const b = line[i + 1];
179
+ const segmentLength = distance(a, b);
180
+ if (--budget.steps < 0) {
181
+ return anchors;
182
+ }
183
+ while (segmentLength > 0 && markedDistance + spacing < travelled + segmentLength) {
184
+ if (--budget.steps < 0) {
185
+ return anchors;
186
+ }
187
+ markedDistance += spacing;
188
+ const t = (markedDistance - travelled) / segmentLength;
189
+ const fits =
190
+ markedDistance - labelLength / 2 >= 0 && markedDistance + labelLength / 2 <= lineLength;
191
+ if (
192
+ fits &&
193
+ (!windowSize ||
194
+ checkMaxAngle(
195
+ line,
196
+ {point: interpolate(a, b, t), segment: i},
197
+ labelLength,
198
+ windowSize,
199
+ maxAngle,
200
+ budget
201
+ ))
202
+ ) {
203
+ anchors.push({segment: i, t, angle: (direction(a, b) * 180) / Math.PI});
204
+ }
205
+ }
206
+ travelled += segmentLength;
207
+ }
208
+
209
+ // No label fits at the regular positions: try one at the middle of a line that starts and ends
210
+ // in this tile.
211
+ if (!placeAtMiddle && anchors.length === 0 && !isContinued) {
212
+ return resample(line, travelled / 2, spacing, options, true, budget);
213
+ }
214
+ return anchors;
215
+ }
216
+
217
+ /**
218
+ * Label anchors along a line in pixels. `line` repeats labels every `spacing` pixels, starting
219
+ * where MapLibre starts them; `line-center` places one at the middle of the line by length. Either
220
+ * skips a placement where the label does not fit on the line or where the line bends more than
221
+ * `maxAngle` within the label.
222
+ */
223
+ export function getLineAnchors(line: number[][], options: LineAnchorOptions): LineAnchor[] {
224
+ const {labelLength, textSize} = options;
225
+ if (
226
+ !Array.isArray(line) ||
227
+ line.length < 2 ||
228
+ !line.every(isFinitePoint) ||
229
+ ![options.spacing, options.maxAngle, labelLength, textSize].every(Number.isFinite) ||
230
+ !(labelLength > 0)
231
+ ) {
232
+ return [];
233
+ }
234
+ const budget = options.budget || {steps: DEFAULT_LINE_ANCHOR_STEPS};
235
+
236
+ if (options.placement === 'line-center') {
237
+ const center = getLineLength(line) / 2;
238
+ const windowSize = getAngleWindowSize(options);
239
+ let travelled = 0;
240
+ for (let i = 0; i < line.length - 1; i++) {
241
+ const segmentLength = distance(line[i], line[i + 1]);
242
+ if (--budget.steps < 0) {
243
+ return [];
244
+ }
245
+ if (travelled + segmentLength > center) {
246
+ const t = (center - travelled) / segmentLength;
247
+ const anchor = {point: interpolate(line[i], line[i + 1], t), segment: i};
248
+ const maxAngle = (options.maxAngle * Math.PI) / 180;
249
+ // Without a bend window the middle is taken as it is: MapLibre's `getCenterAnchor` skips
250
+ // `checkMaxAngle`, and with it the fit, then.
251
+ const placed =
252
+ !windowSize || checkMaxAngle(line, anchor, labelLength, windowSize, maxAngle, budget);
253
+ return placed
254
+ ? [{segment: i, t, angle: (direction(line[i], line[i + 1]) * 180) / Math.PI}]
255
+ : [];
256
+ }
257
+ travelled += segmentLength;
258
+ }
259
+ return [];
260
+ }
261
+
262
+ // A spacing shorter than the label is widened, as in MapLibre, and never leaves less than
263
+ // `MIN_LABEL_GAP` between labels.
264
+ let spacing = Math.max(1, options.spacing);
265
+ if (spacing - labelLength < spacing / 4) {
266
+ spacing = labelLength + spacing / 4;
267
+ }
268
+ spacing = Math.max(spacing, labelLength + MIN_LABEL_GAP);
269
+ const offset = options.isContinued
270
+ ? (spacing / 2) % spacing
271
+ : (labelLength / 2 + FIXED_EXTRA_OFFSET_EMS * textSize) % spacing;
272
+ return resample(line, offset, spacing, options, false, budget);
273
+ }
274
+
275
+ /** A line to merge: its label text, or none, and its vertices. */
276
+ export type MergeableLine = {text: string | null | undefined; line: number[][]};
277
+
278
+ /**
279
+ * Joins lines with the same label text whose ends meet exactly, as MapLibre GL JS's `mergeLines`
280
+ * does before it places labels along lines. Vector tiles split a street or river into many short
281
+ * features (one per block, bridge or name change), and a label longer than each piece fits on
282
+ * none of them; MapLibre places it on the joined line.
283
+ *
284
+ * Returns one entry per input line: the joined line at the line that started each chain (whose
285
+ * feature the labels belong to, as in MapLibre), `null` for a line merged into a chain started by
286
+ * another, and the line unchanged when nothing joins it. Lines without text or vertices are
287
+ * never merged. The work is linear in the number of vertices.
288
+ */
289
+ export function mergeLines(lines: MergeableLine[]): (number[][] | null)[] {
290
+ type Chunk = {line: number[][]; next: Chunk | null};
291
+ type Chain = {first: Chunk; last: Chunk; owner: number};
292
+ const chains: (Chain | null)[] = [];
293
+ const result: (number[][] | null)[] = lines.map(({line}) => line);
294
+ // Chains by `text` and the position of their first and last vertex.
295
+ const byStart = new Map<string, number>();
296
+ const byEnd = new Map<string, number>();
297
+ const key = (text: string, [x, y]: number[]) => `${x},${y},${text}`;
298
+
299
+ lines.forEach(({text, line}, index) => {
300
+ // A single vertex joins too, as in MapLibre: it can carry a MultiLineString into a chain.
301
+ if (!text || !Array.isArray(line) || line.length === 0) {
302
+ return;
303
+ }
304
+ const startKey = key(text, line[0]);
305
+ const endKey = key(text, line[line.length - 1]);
306
+ const chunk: Chunk = {line, next: null};
307
+ const before = byEnd.get(startKey);
308
+ const after = byStart.get(endKey);
309
+
310
+ if (before !== undefined && after !== undefined && before !== after) {
311
+ // Joins the chain ending where this line starts to the one starting where it ends.
312
+ const head = chains[before]!;
313
+ const tail = chains[after]!;
314
+ head.last.next = chunk;
315
+ chunk.next = tail.first;
316
+ head.last = tail.last;
317
+ byEnd.delete(startKey);
318
+ byStart.delete(endKey);
319
+ byEnd.set(key(text, lastPoint(tail.last.line)), before);
320
+ chains[after] = null;
321
+ // As in MapLibre, the joined line keeps the feature of the chain it was appended to.
322
+ result[index] = null;
323
+ result[tail.owner] = null;
324
+ } else if (before !== undefined) {
325
+ const head = chains[before]!;
326
+ head.last.next = chunk;
327
+ head.last = chunk;
328
+ byEnd.delete(startKey);
329
+ byEnd.set(endKey, before);
330
+ result[index] = null;
331
+ } else if (after !== undefined) {
332
+ const tail = chains[after]!;
333
+ chunk.next = tail.first;
334
+ tail.first = chunk;
335
+ byStart.delete(endKey);
336
+ byStart.set(startKey, after);
337
+ result[index] = null;
338
+ } else {
339
+ const chainIndex = chains.push({first: chunk, last: chunk, owner: index}) - 1;
340
+ byStart.set(startKey, chainIndex);
341
+ byEnd.set(endKey, chainIndex);
342
+ }
343
+ });
344
+
345
+ for (const chain of chains) {
346
+ if (chain && chain.first.next) {
347
+ // Each chunk after the first repeats the vertex it shares with the previous one.
348
+ const joined = chain.first.line.slice();
349
+ for (let chunk: Chunk | null = chain.first.next; chunk; chunk = chunk.next) {
350
+ for (let i = 1; i < chunk.line.length; i++) {
351
+ joined.push(chunk.line[i]);
352
+ }
353
+ }
354
+ result[chain.owner] = joined;
355
+ }
356
+ }
357
+ return result;
358
+ }
359
+
360
+ function lastPoint(line: number[][]): number[] {
361
+ return line[line.length - 1];
362
+ }
363
+
364
+ /**
365
+ * The angle at which a label along a line is drawn: the line's direction, turned by 180 degrees
366
+ * when `keepUpright` is set and the text would otherwise read upside down on a map rotated by
367
+ * `bearing` degrees.
368
+ */
369
+ export function getUprightAngle(angle: number, bearing: number, keepUpright: boolean): number {
370
+ if (!keepUpright) {
371
+ return angle;
372
+ }
373
+ const onScreen = ((((angle + bearing + 180) % 360) + 360) % 360) - 180;
374
+ return onScreen > 90 || onScreen <= -90 ? angle + 180 : angle;
375
+ }
376
+
377
+ function isFinitePoint(point: number[]): boolean {
378
+ return Array.isArray(point) && Number.isFinite(point[0]) && Number.isFinite(point[1]);
379
+ }
380
+
381
+ /**
382
+ * `point` with the coordinate of box `edge` (0 to 3: min x, max x, min y, max y) set exactly, as
383
+ * rounding can leave a point clipped to that edge just inside or outside it.
384
+ */
385
+ function snapToEdge(point: number[], edge: number, [minX, minY, maxX, maxY]: number[]): number[] {
386
+ if (edge === 0 || edge === 1) {
387
+ point[0] = edge === 0 ? minX : maxX;
388
+ } else if (edge === 2 || edge === 3) {
389
+ point[1] = edge === 2 ? minY : maxY;
390
+ }
391
+ return point;
392
+ }
393
+
394
+ /**
395
+ * The parts of `line` inside `[minX, minY, maxX, maxY]`, clipped segment by segment
396
+ * (Liang-Barsky). A line that leaves the box and comes back gives one part per stay inside.
397
+ */
398
+ export function clipLine(line: number[][], box: number[]): number[][][] {
399
+ const [minX, minY, maxX, maxY] = box;
400
+ const parts: number[][][] = [];
401
+ let part: number[][] | null = null;
402
+ for (let i = 0; i < line.length - 1; i++) {
403
+ const [ax, ay] = line[i];
404
+ const [bx, by] = line[i + 1];
405
+ const dx = bx - ax;
406
+ const dy = by - ay;
407
+ let t0 = 0;
408
+ let t1 = 1;
409
+ const edges: [number, number][] = [
410
+ [-dx, ax - minX],
411
+ [dx, maxX - ax],
412
+ [-dy, ay - minY],
413
+ [dy, maxY - ay]
414
+ ];
415
+ let inside = true;
416
+ // The edges a clipped segment starts and ends on, -1 where it is not clipped.
417
+ let startEdge = -1;
418
+ let endEdge = -1;
419
+ edges.forEach(([p, q], edge) => {
420
+ if (p === 0) {
421
+ inside = inside && q >= 0;
422
+ } else if (p < 0 && q / p > t0) {
423
+ t0 = q / p;
424
+ startEdge = edge;
425
+ } else if (p > 0 && q / p < t1) {
426
+ t1 = q / p;
427
+ endEdge = edge;
428
+ }
429
+ });
430
+ if (!inside || t0 > t1) {
431
+ part = null;
432
+ continue;
433
+ }
434
+ const start = snapToEdge([ax + dx * t0, ay + dy * t0], startEdge, box);
435
+ const end = snapToEdge([ax + dx * t1, ay + dy * t1], endEdge, box);
436
+ if (!part || t0 > 0) {
437
+ part = [start];
438
+ parts.push(part);
439
+ }
440
+ part.push(end);
441
+ if (t1 < 1) {
442
+ part = null;
443
+ }
444
+ }
445
+ return parts.filter(clipped => clipped.length > 1);
446
+ }
@@ -0,0 +1,56 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {LoaderContext, LoaderOptions, LoaderWithParser} from '@loaders.gl/loader-utils';
6
+ import {ResolvedBasemapStyleSchema} from './map-style-schema';
7
+ import {resolveBasemapStyle} from './style-resolver';
8
+ import type {BasemapLoadOptions, BasemapStyle, ResolvedBasemapStyle} from './style-resolver';
9
+
10
+ // __VERSION__ is injected by babel-plugin-version-inline
11
+ // @ts-ignore TS2304: Cannot find name '__VERSION__'.
12
+ const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest';
13
+
14
+ /** Namespaced loaders.gl options for {@link MapStyleLoader}. */
15
+ export type MapStyleLoaderOptions = LoaderOptions & {
16
+ mapStyle?: NonNullable<BasemapLoadOptions>;
17
+ };
18
+
19
+ function getMapStyleLoadOptions(
20
+ options?: MapStyleLoaderOptions,
21
+ context?: LoaderContext
22
+ ): NonNullable<BasemapLoadOptions> {
23
+ return {
24
+ ...options?.mapStyle,
25
+ baseUrl: options?.mapStyle?.baseUrl || context?.url || context?.baseUrl,
26
+ fetch: options?.mapStyle?.fetch || (context?.fetch as typeof fetch | undefined)
27
+ };
28
+ }
29
+
30
+ /** loaders.gl-compatible loader that resolves and validates map-style documents. */
31
+ export const MapStyleLoader = {
32
+ dataType: null as unknown as ResolvedBasemapStyle,
33
+ batchType: null as never,
34
+
35
+ name: 'Map Style',
36
+ id: 'map-style',
37
+ module: 'basemap-layers',
38
+ version: VERSION,
39
+ worker: false,
40
+ extensions: ['json'],
41
+ mimeTypes: ['application/json', 'application/vnd.mapbox.style+json'],
42
+ text: true,
43
+ options: {
44
+ mapStyle: {}
45
+ },
46
+ parse: async (
47
+ data: ArrayBuffer | string,
48
+ options?: MapStyleLoaderOptions,
49
+ context?: LoaderContext
50
+ ) => {
51
+ const text = typeof data === 'string' ? data : new TextDecoder().decode(data);
52
+ const style = JSON.parse(text) as BasemapStyle;
53
+ const resolved = await resolveBasemapStyle(style, getMapStyleLoadOptions(options, context));
54
+ return ResolvedBasemapStyleSchema.parse(resolved);
55
+ }
56
+ } as const satisfies LoaderWithParser<ResolvedBasemapStyle, never, MapStyleLoaderOptions>;
@@ -0,0 +1,83 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {z} from 'zod';
6
+ import type {
7
+ BasemapSource,
8
+ BasemapStyle,
9
+ BasemapStyleLayer,
10
+ ResolvedBasemapStyleLayer,
11
+ ResolvedBasemapStyle
12
+ } from './style-resolver';
13
+
14
+ /** Zod schema for a basemap source entry. */
15
+ export const BasemapSourceSchema = z
16
+ .object({
17
+ type: z.string().optional(),
18
+ url: z.string().optional(),
19
+ tiles: z.array(z.string()).optional(),
20
+ minzoom: z.number().optional(),
21
+ maxzoom: z.number().optional(),
22
+ tileSize: z.number().optional()
23
+ })
24
+ .catchall(z.unknown()) satisfies z.ZodType<BasemapSource>;
25
+
26
+ /** Zod schema for a basemap style layer entry. */
27
+ export const BasemapStyleLayerSchema = z
28
+ .object({
29
+ id: z.string(),
30
+ type: z.string().optional(),
31
+ ref: z.string().optional(),
32
+ source: z.string().optional(),
33
+ 'source-layer': z.string().optional(),
34
+ minzoom: z.number().optional(),
35
+ maxzoom: z.number().optional(),
36
+ filter: z.array(z.unknown()).optional(),
37
+ paint: z.record(z.string(), z.unknown()).optional(),
38
+ layout: z.record(z.string(), z.unknown()).optional()
39
+ })
40
+ .catchall(z.unknown())
41
+ .superRefine((layer, context) => {
42
+ if (!layer.type && !layer.ref) {
43
+ context.addIssue({
44
+ code: z.ZodIssueCode.custom,
45
+ message: 'Style layers must define either "type" or "ref".',
46
+ path: ['type']
47
+ });
48
+ }
49
+ }) satisfies z.ZodType<BasemapStyleLayer>;
50
+
51
+ const ResolvedBasemapStyleLayerSchema = z
52
+ .object({
53
+ id: z.string(),
54
+ type: z.string(),
55
+ source: z.string().optional(),
56
+ 'source-layer': z.string().optional(),
57
+ minzoom: z.number().optional(),
58
+ maxzoom: z.number().optional(),
59
+ filter: z.array(z.unknown()).optional(),
60
+ paint: z.record(z.string(), z.unknown()).optional(),
61
+ layout: z.record(z.string(), z.unknown()).optional()
62
+ })
63
+ .catchall(z.unknown()) satisfies z.ZodType<ResolvedBasemapStyleLayer>;
64
+
65
+ /** Zod schema for a MapLibre / Mapbox style document. */
66
+ export const BasemapStyleSchema = z
67
+ .object({
68
+ version: z.number().optional(),
69
+ metadata: z.record(z.string(), z.unknown()).optional(),
70
+ sources: z.record(z.string(), BasemapSourceSchema).optional(),
71
+ layers: z.array(BasemapStyleLayerSchema).optional()
72
+ })
73
+ .catchall(z.unknown()) satisfies z.ZodType<BasemapStyle>;
74
+
75
+ /** Zod schema for a fully resolved basemap style document. */
76
+ export const ResolvedBasemapStyleSchema = z
77
+ .object({
78
+ version: z.number().optional(),
79
+ metadata: z.record(z.string(), z.unknown()).optional(),
80
+ sources: z.record(z.string(), BasemapSourceSchema),
81
+ layers: z.array(ResolvedBasemapStyleLayerSchema)
82
+ })
83
+ .catchall(z.unknown()) satisfies z.ZodType<ResolvedBasemapStyle>;
@@ -0,0 +1,23 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /** Map-style helpers exported from `@deck.gl-community/basemap-layers/map-style`. */
6
+ export {filterFeatures, findFeaturesStyledByLayer, parseProperties} from './mapbox-style';
7
+ export {
8
+ BasemapSourceSchema,
9
+ BasemapStyleLayerSchema,
10
+ BasemapStyleSchema,
11
+ ResolvedBasemapStyleSchema
12
+ } from './map-style-schema';
13
+ export {MapStyleLoader} from './map-style-loader';
14
+ export type {MapStyleLoaderOptions} from './map-style-loader';
15
+ export {
16
+ resolveBasemapStyle,
17
+ type BasemapLoadOptions,
18
+ type BasemapSource,
19
+ type BasemapStyle,
20
+ type BasemapStyleLayer,
21
+ type ResolvedBasemapStyleLayer,
22
+ type ResolvedBasemapStyle
23
+ } from './style-resolver';