@mapwhit/tilerenderer 1.2.1 → 1.3.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.
Files changed (35) hide show
  1. package/build/min/package.json +1 -1
  2. package/package.json +4 -4
  3. package/src/data/bucket/fill_extrusion_bucket.js +34 -39
  4. package/src/data/bucket/line_bucket.js +40 -48
  5. package/src/data/bucket/symbol_bucket.js +4 -5
  6. package/src/data/feature_index.js +10 -3
  7. package/src/geo/transform.js +13 -13
  8. package/src/index.js +1 -1
  9. package/src/source/geojson_wrapper.js +1 -9
  10. package/src/source/query_features.js +4 -1
  11. package/src/source/rtl_text_plugin.js +3 -1
  12. package/src/source/source_cache.js +3 -3
  13. package/src/source/tile.js +1 -1
  14. package/src/style/evaluation_parameters.js +5 -4
  15. package/src/style/query_utils.js +82 -7
  16. package/src/style/style.js +78 -25
  17. package/src/style/style_layer/circle_style_layer.js +26 -45
  18. package/src/style/style_layer/fill_extrusion_style_layer.js +26 -33
  19. package/src/style/style_layer/heatmap_style_layer.js +21 -7
  20. package/src/style/style_layer/line_style_layer.js +7 -7
  21. package/src/style/style_layer.js +3 -3
  22. package/src/style-spec/feature_filter/index.js +24 -19
  23. package/src/symbol/anchor.js +1 -1
  24. package/src/symbol/check_max_angle.js +6 -6
  25. package/src/symbol/collision_feature.js +9 -15
  26. package/src/symbol/collision_index.js +33 -27
  27. package/src/symbol/get_anchors.js +6 -5
  28. package/src/symbol/projection.js +3 -3
  29. package/src/symbol/quads.js +1 -1
  30. package/src/symbol/symbol_layout.js +1 -2
  31. package/src/ui/camera.js +1 -1
  32. package/src/ui/map.js +24 -24
  33. package/src/util/classify_rings.js +2 -4
  34. package/src/util/vectortile_to_geojson.js +65 -26
  35. package/src/symbol/clip_line.js +0 -72
@@ -64,29 +64,34 @@ const filterSpec = {
64
64
  * @returns {Function} filter-evaluating function
65
65
  */
66
66
  export default function createFilter(filter, globalState) {
67
- if (filter === null || filter === undefined) {
68
- return addGlobalStateRefs(() => true);
67
+ let evaluate;
68
+ const expression = (globalProperties, feature) => evaluate(globalProperties, feature);
69
+ expression.setValue = setValue;
70
+ setValue(filter);
71
+ return expression;
72
+
73
+ function setValue(filter) {
74
+ if (filter === null || filter === undefined) {
75
+ evaluate = () => true;
76
+ addGlobalStateRefs(expression);
77
+ return;
78
+ }
79
+
80
+ if (!isExpressionFilter(filter)) {
81
+ filter = convertFilter(filter);
82
+ }
83
+
84
+ const compiled = createExpression(filter, filterSpec, globalState);
85
+ if (compiled.result === 'error') {
86
+ throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', '));
87
+ }
88
+ evaluate = (globalProperties, feature) => compiled.value.evaluate(globalProperties, feature);
89
+ addGlobalStateRefs(expression, () => findGlobalStateRefs(compiled.value.expression));
69
90
  }
70
-
71
- if (!isExpressionFilter(filter)) {
72
- filter = convertFilter(filter);
73
- }
74
-
75
- const compiled = createExpression(filter, filterSpec, globalState);
76
- if (compiled.result === 'error') {
77
- throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', '));
78
- }
79
- return Object.assign(
80
- addGlobalStateRefs(
81
- (globalProperties, feature) => compiled.value.evaluate(globalProperties, feature),
82
- () => findGlobalStateRefs(compiled.value.expression)
83
- )
84
- );
85
91
  }
86
92
 
87
- export function addGlobalStateRefs(filter, getGlobalStateRefs = () => new Set()) {
93
+ function addGlobalStateRefs(filter, getGlobalStateRefs = () => new Set()) {
88
94
  filter.getGlobalStateRefs = getGlobalStateRefs;
89
- return filter;
90
95
  }
91
96
 
92
97
  // Comparison function to sort numbers and strings
@@ -1,4 +1,4 @@
1
- import Point from '@mapbox/point-geometry';
1
+ import { Point } from '@mapwhit/point-geometry';
2
2
 
3
3
  class Anchor extends Point {
4
4
  constructor(x, y, angle, segment) {
@@ -1,4 +1,4 @@
1
- export default checkMaxAngle;
1
+ import { angleTo, dist } from '@mapwhit/point-geometry';
2
2
 
3
3
  /**
4
4
  * Labels placed around really sharp angles aren't readable. Check if any
@@ -13,7 +13,7 @@ export default checkMaxAngle;
13
13
  * @returns {boolean} whether the label should be placed
14
14
  * @private
15
15
  */
16
- function checkMaxAngle(line, anchor, labelLength, windowSize, maxAngle) {
16
+ export default function checkMaxAngle(line, anchor, labelLength, windowSize, maxAngle) {
17
17
  // horizontal labels always pass
18
18
  if (anchor.segment === undefined) {
19
19
  return true;
@@ -32,11 +32,11 @@ function checkMaxAngle(line, anchor, labelLength, windowSize, maxAngle) {
32
32
  return false;
33
33
  }
34
34
 
35
- anchorDistance -= line[index].dist(p);
35
+ anchorDistance -= dist(line[index], p);
36
36
  p = line[index];
37
37
  }
38
38
 
39
- anchorDistance += line[index].dist(line[index + 1]);
39
+ anchorDistance += dist(line[index], line[index + 1]);
40
40
  index++;
41
41
 
42
42
  // store recent corners and their total angle difference
@@ -54,7 +54,7 @@ function checkMaxAngle(line, anchor, labelLength, windowSize, maxAngle) {
54
54
  return false;
55
55
  }
56
56
 
57
- let angleDelta = prev.angleTo(current) - current.angleTo(next);
57
+ let angleDelta = angleTo(prev, current) - angleTo(current, next);
58
58
  // restrict angle to -pi..pi range
59
59
  angleDelta = Math.abs(((angleDelta + 3 * Math.PI) % (Math.PI * 2)) - Math.PI);
60
60
 
@@ -75,7 +75,7 @@ function checkMaxAngle(line, anchor, labelLength, windowSize, maxAngle) {
75
75
  }
76
76
 
77
77
  index++;
78
- anchorDistance += current.dist(next);
78
+ anchorDistance += dist(current, next);
79
79
  }
80
80
 
81
81
  // no part of the line had an angle greater than the maximum allowed. check passes.
@@ -1,4 +1,4 @@
1
- import Point from '@mapbox/point-geometry';
1
+ import { dist, Point, rotate as rotatePoint } from '@mapwhit/point-geometry';
2
2
 
3
3
  /**
4
4
  * A CollisionFeature represents the area of the tile covered by a single label.
@@ -67,17 +67,11 @@ class CollisionFeature {
67
67
  // See https://github.com/mapbox/mapbox-gl-js/issues/6075
68
68
  // Doesn't account for icon-text-fit
69
69
 
70
- const tl = new Point(x1, y1);
71
- const tr = new Point(x2, y1);
72
- const bl = new Point(x1, y2);
73
- const br = new Point(x2, y2);
74
-
75
70
  const rotateRadians = (rotate * Math.PI) / 180;
76
-
77
- tl._rotate(rotateRadians);
78
- tr._rotate(rotateRadians);
79
- bl._rotate(rotateRadians);
80
- br._rotate(rotateRadians);
71
+ const tl = rotatePoint({ x: x1, y: y1 }, rotateRadians);
72
+ const tr = rotatePoint({ x: x2, y: y1 }, rotateRadians);
73
+ const bl = rotatePoint({ x: x1, y: y2 }, rotateRadians);
74
+ const br = rotatePoint({ x: x2, y: y2 }, rotateRadians);
81
75
 
82
76
  // Collision features require an "on-axis" geometry,
83
77
  // so take the envelope of the rotated geometry
@@ -162,11 +156,11 @@ class CollisionFeature {
162
156
  index = 0;
163
157
  break;
164
158
  }
165
- anchorDistance -= line[index].dist(p);
159
+ anchorDistance -= dist(line[index], p);
166
160
  p = line[index];
167
161
  } while (anchorDistance > paddingStartDistance);
168
162
 
169
- let segmentLength = line[index].dist(line[index + 1]);
163
+ let segmentLength = dist(line[index], line[index + 1]);
170
164
 
171
165
  for (let i = -nPitchPaddingBoxes; i < nBoxes + nPitchPaddingBoxes; i++) {
172
166
  // the distance the box will be from the anchor
@@ -197,7 +191,7 @@ class CollisionFeature {
197
191
  return;
198
192
  }
199
193
 
200
- segmentLength = line[index].dist(line[index + 1]);
194
+ segmentLength = dist(line[index], line[index + 1]);
201
195
  }
202
196
 
203
197
  // the distance the box will be from the beginning of the segment
@@ -205,7 +199,7 @@ class CollisionFeature {
205
199
 
206
200
  const p0 = line[index];
207
201
  const p1 = line[index + 1];
208
- const boxAnchorPoint = p1.sub(p0)._unit()._mult(segmentBoxDistance)._add(p0)._round();
202
+ const boxAnchorPoint = Point.clone(p1)._sub(p0)._unit()._mult(segmentBoxDistance)._add(p0)._round();
209
203
 
210
204
  // If the box is within boxSize of the anchor, force the box to be used
211
205
  // (so even 0-width labels use at least one box)
@@ -1,4 +1,3 @@
1
- import Point from '@mapbox/point-geometry';
2
1
  import { polygonIntersectsPolygon } from '@mapwhit/geometry';
3
2
  import * as projection from '../symbol/projection.js';
4
3
  import Grid from './grid_index.js';
@@ -20,10 +19,9 @@ const viewportPadding = 100;
20
19
  * there's room for a symbol, then insertCollisionBox/Circles actually puts the
21
20
  * symbol in the index. The two step process allows paired symbols to be inserted
22
21
  * together even if they overlap.
23
- *
24
- * @private
25
22
  */
26
- class CollisionIndex {
23
+
24
+ export default class CollisionIndex {
27
25
  constructor(
28
26
  transform,
29
27
  grid = new Grid(transform.width + 2 * viewportPadding, transform.height + 2 * viewportPadding, 25),
@@ -116,7 +114,7 @@ class CollisionIndex {
116
114
  const lineOffsetX = symbol.lineOffsetX * fontSize;
117
115
  const lineOffsetY = symbol.lineOffsetY * fontSize;
118
116
 
119
- const tileUnitAnchorPoint = new Point(symbol.anchorX, symbol.anchorY);
117
+ const tileUnitAnchorPoint = { x: symbol.anchorX, y: symbol.anchorY };
120
118
  // projection.project generates NDC coordinates, as opposed to the
121
119
  // pixel-based grid coordinates generated by this.projectPoint
122
120
  const labelPlaneAnchorPoint = projection.project(tileUnitAnchorPoint, labelPlaneMatrix).point;
@@ -253,18 +251,28 @@ class CollisionIndex {
253
251
  return {};
254
252
  }
255
253
 
256
- const query = [];
254
+ const query = new Array(viewportQueryGeometry.length);
257
255
  let minX = Number.POSITIVE_INFINITY;
258
256
  let minY = Number.POSITIVE_INFINITY;
259
257
  let maxX = Number.NEGATIVE_INFINITY;
260
258
  let maxY = Number.NEGATIVE_INFINITY;
261
- for (const point of viewportQueryGeometry) {
262
- const gridPoint = new Point(point.x + viewportPadding, point.y + viewportPadding);
263
- minX = Math.min(minX, gridPoint.x);
264
- minY = Math.min(minY, gridPoint.y);
265
- maxX = Math.max(maxX, gridPoint.x);
266
- maxY = Math.max(maxY, gridPoint.y);
267
- query.push(gridPoint);
259
+ for (let i = 0; i < viewportQueryGeometry.length; i++) {
260
+ let { x, y } = viewportQueryGeometry[i];
261
+ x += viewportPadding;
262
+ y += viewportPadding;
263
+ if (x < minX) {
264
+ minX = x;
265
+ }
266
+ if (x > maxX) {
267
+ maxX = x;
268
+ }
269
+ if (y < minY) {
270
+ minY = y;
271
+ }
272
+ if (y > maxY) {
273
+ maxY = y;
274
+ }
275
+ query[i] = { x, y };
268
276
  }
269
277
 
270
278
  const features = this.grid.query(minX, minY, maxX, maxY).concat(this.ignoredGrid.query(minX, minY, maxX, maxY));
@@ -288,10 +296,10 @@ class CollisionIndex {
288
296
  // distinction doesn't matter as much, and box geometry is easier
289
297
  // to work with.
290
298
  const bbox = [
291
- new Point(feature.x1, feature.y1),
292
- new Point(feature.x2, feature.y1),
293
- new Point(feature.x2, feature.y2),
294
- new Point(feature.x1, feature.y2)
299
+ { x: feature.x1, y: feature.y1 },
300
+ { x: feature.x2, y: feature.y1 },
301
+ { x: feature.x2, y: feature.y2 },
302
+ { x: feature.x1, y: feature.y2 }
295
303
  ];
296
304
  if (!polygonIntersectsPolygon(query, bbox)) {
297
305
  continue;
@@ -343,19 +351,19 @@ class CollisionIndex {
343
351
  projectPoint(posMatrix, x, y) {
344
352
  const p = [x, y, 0, 1];
345
353
  projection.xyTransformMat4(p, p, posMatrix);
346
- return new Point(
347
- ((p[0] / p[3] + 1) / 2) * this.transform.width + viewportPadding,
348
- ((-p[1] / p[3] + 1) / 2) * this.transform.height + viewportPadding
349
- );
354
+ return {
355
+ x: ((p[0] / p[3] + 1) / 2) * this.transform.width + viewportPadding,
356
+ y: ((-p[1] / p[3] + 1) / 2) * this.transform.height + viewportPadding
357
+ };
350
358
  }
351
359
 
352
360
  projectAndGetPerspectiveRatio(posMatrix, x, y) {
353
361
  const p = [x, y, 0, 1];
354
362
  projection.xyTransformMat4(p, p, posMatrix);
355
- const a = new Point(
356
- ((p[0] / p[3] + 1) / 2) * this.transform.width + viewportPadding,
357
- ((-p[1] / p[3] + 1) / 2) * this.transform.height + viewportPadding
358
- );
363
+ const a = {
364
+ x: ((p[0] / p[3] + 1) / 2) * this.transform.width + viewportPadding,
365
+ y: ((-p[1] / p[3] + 1) / 2) * this.transform.height + viewportPadding
366
+ };
359
367
  return {
360
368
  point: a,
361
369
  // See perspective ratio comment in symbol_sdf.vertex
@@ -379,5 +387,3 @@ class CollisionIndex {
379
387
  function markCollisionCircleUsed(collisionCircles, index, used) {
380
388
  collisionCircles[index + 4] = used ? 1 : 0;
381
389
  }
382
-
383
- export default CollisionIndex;
@@ -1,3 +1,4 @@
1
+ import { angleTo, dist } from '@mapwhit/point-geometry';
1
2
  import Anchor from '../symbol/anchor.js';
2
3
  import interpolate from '../util/interpolate.js';
3
4
  import checkMaxAngle from './check_max_angle.js';
@@ -5,7 +6,7 @@ import checkMaxAngle from './check_max_angle.js';
5
6
  function getLineLength(line) {
6
7
  let lineLength = 0;
7
8
  for (let k = 0; k < line.length - 1; k++) {
8
- lineLength += line[k].dist(line[k + 1]);
9
+ lineLength += dist(line[k], line[k + 1]);
9
10
  }
10
11
  return lineLength;
11
12
  }
@@ -32,7 +33,7 @@ export function getCenterAnchor(line, maxAngle, shapedText, shapedIcon, glyphSiz
32
33
  const a = line[i];
33
34
  const b = line[i + 1];
34
35
 
35
- const segmentDistance = a.dist(b);
36
+ const segmentDistance = dist(a, b);
36
37
 
37
38
  if (prevDistance + segmentDistance > centerDistance) {
38
39
  // The center is on this segment
@@ -40,7 +41,7 @@ export function getCenterAnchor(line, maxAngle, shapedText, shapedIcon, glyphSiz
40
41
  const x = interpolate(a.x, b.x, t);
41
42
  const y = interpolate(a.y, b.y, t);
42
43
 
43
- const anchor = new Anchor(x, y, b.angleTo(a), i);
44
+ const anchor = new Anchor(x, y, angleTo(b, a), i);
44
45
  anchor._round();
45
46
  if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) {
46
47
  return anchor;
@@ -117,8 +118,8 @@ function resample(
117
118
  const a = line[i];
118
119
  const b = line[i + 1];
119
120
 
120
- const segmentDist = a.dist(b);
121
- const angle = b.angleTo(a);
121
+ const segmentDist = dist(a, b);
122
+ const angle = angleTo(b, a);
122
123
 
123
124
  while (markedDistance + spacing < distance + segmentDist) {
124
125
  markedDistance += spacing;
@@ -1,5 +1,5 @@
1
1
  import glMatrix from '@mapbox/gl-matrix';
2
- import Point from '@mapbox/point-geometry';
2
+ import { Point } from '@mapwhit/point-geometry';
3
3
  import { addDynamicAttributes } from '../data/bucket/symbol_bucket.js';
4
4
  import properties from '../style/style_layer/symbol_style_layer_properties.js';
5
5
  import { WritingMode } from '../symbol/shaping.js';
@@ -448,10 +448,10 @@ function projectTruncatedLineSegment(
448
448
  // point near the plane of the camera. We wouldn't be able to render the label anyway once it crossed the
449
449
  // plane of the camera.
450
450
  const projectedUnitVertex = project(
451
- previousTilePoint.add(previousTilePoint.sub(currentTilePoint)._unit()),
451
+ Point.clone(previousTilePoint)._add(Point.clone(previousTilePoint)._sub(currentTilePoint)._unit()),
452
452
  projectionMatrix
453
453
  ).point;
454
- const projectedUnitSegment = previousProjectedPoint.sub(projectedUnitVertex);
454
+ const projectedUnitSegment = Point.clone(previousProjectedPoint)._sub(projectedUnitVertex);
455
455
 
456
456
  return previousProjectedPoint.add(projectedUnitSegment._mult(minimumLength / projectedUnitSegment.mag()));
457
457
  }
@@ -1,4 +1,4 @@
1
- import Point from '@mapbox/point-geometry';
1
+ import { Point } from '@mapwhit/point-geometry';
2
2
  import { GLYPH_PBF_BORDER } from '../style/parse_glyph_pbf.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { findPoleOfInaccessibility } from '@mapwhit/geometry';
1
+ import { clipLine, findPoleOfInaccessibility } from '@mapwhit/geometry';
2
2
  import murmur3 from 'murmurhash-js';
3
3
  import SymbolBucket from '../data/bucket/symbol_bucket.js';
4
4
  import EXTENT from '../data/extent.js';
@@ -7,7 +7,6 @@ import classifyRings from '../util/classify_rings.js';
7
7
  import { allowsLetterSpacing, allowsVerticalWritingMode } from '../util/script_detection.js';
8
8
  import warn from '../util/warn.js';
9
9
  import Anchor from './anchor.js';
10
- import clipLine from './clip_line.js';
11
10
  import CollisionFeature from './collision_feature.js';
12
11
  import { getAnchors, getCenterAnchor } from './get_anchors.js';
13
12
  import { getGlyphQuads, getIconQuads } from './quads.js';
package/src/ui/camera.js CHANGED
@@ -1,5 +1,5 @@
1
- import Point from '@mapbox/point-geometry';
2
1
  import { Event, Evented } from '@mapwhit/events';
2
+ import { Point } from '@mapwhit/point-geometry';
3
3
  import LngLat from '../geo/lng_lat.js';
4
4
  import LngLatBounds from '../geo/lng_lat_bounds.js';
5
5
  import browser from '../util/browser.js';
package/src/ui/map.js CHANGED
@@ -1,4 +1,3 @@
1
- import Point from '@mapbox/point-geometry';
2
1
  import { ErrorEvent, Event } from '@mapwhit/events';
3
2
  import LngLat from '../geo/lng_lat.js';
4
3
  import LngLatBounds from '../geo/lng_lat_bounds.js';
@@ -287,10 +286,10 @@ class Map extends Camera {
287
286
  */
288
287
  getBounds() {
289
288
  return new LngLatBounds()
290
- .extend(this.transform.pointLocation(new Point(0, 0)))
291
- .extend(this.transform.pointLocation(new Point(this.transform.width, 0)))
292
- .extend(this.transform.pointLocation(new Point(this.transform.width, this.transform.height)))
293
- .extend(this.transform.pointLocation(new Point(0, this.transform.height)));
289
+ .extend(this.transform.pointLocation({ x: 0, y: 0 }))
290
+ .extend(this.transform.pointLocation({ x: this.transform.width, y: 0 }))
291
+ .extend(this.transform.pointLocation({ x: this.transform.width, y: this.transform.height }))
292
+ .extend(this.transform.pointLocation({ x: 0, y: this.transform.height }));
294
293
  }
295
294
 
296
295
  /**
@@ -453,7 +452,7 @@ class Map extends Camera {
453
452
  * @see [Show polygon information on click](https://www.mapbox.com/mapbox-gl-js/example/polygon-popup-on-click/)
454
453
  */
455
454
  unproject(point) {
456
- return this.transform.pointLocation(Point.convert(point));
455
+ return this.transform.pointLocation(toPoint(point));
457
456
  }
458
457
 
459
458
  /**
@@ -577,33 +576,26 @@ class Map extends Camera {
577
576
  }
578
577
 
579
578
  return this.style.queryRenderedFeatures(this._makeQueryGeometry(geometry), options, this.transform) || [];
580
-
581
- function isPointLike(input) {
582
- return input instanceof Point || Array.isArray(input);
583
- }
584
579
  }
585
580
 
586
- _makeQueryGeometry(pointOrBox) {
587
- if (pointOrBox === undefined) {
588
- // bounds was omitted: use full viewport
589
- pointOrBox = [Point.convert([0, 0]), Point.convert([this.transform.width, this.transform.height])];
590
- }
591
-
581
+ _makeQueryGeometry(
582
+ pointOrBox = [
583
+ { x: 0, y: 0 },
584
+ { x: this.transform.width, y: this.transform.height }
585
+ ]
586
+ ) {
592
587
  let queryGeometry;
593
588
 
594
- if (pointOrBox instanceof Point || typeof pointOrBox[0] === 'number') {
595
- const point = Point.convert(pointOrBox);
596
- queryGeometry = [point];
589
+ if (typeof pointOrBox.x === 'number' || typeof pointOrBox[0] === 'number') {
590
+ queryGeometry = [toPoint(pointOrBox)];
597
591
  } else {
598
- const box = [Point.convert(pointOrBox[0]), Point.convert(pointOrBox[1])];
599
- queryGeometry = [box[0], new Point(box[1].x, box[0].y), box[1], new Point(box[0].x, box[1].y), box[0]];
592
+ const box = [toPoint(pointOrBox[0]), toPoint(pointOrBox[1])];
593
+ queryGeometry = [box[0], { x: box[1].x, y: box[0].y }, box[1], { x: box[0].x, y: box[1].y }, box[0]];
600
594
  }
601
595
 
602
596
  return {
603
597
  viewport: queryGeometry,
604
- worldCoordinate: queryGeometry.map(p => {
605
- return this.transform.pointCoordinate(p);
606
- })
598
+ worldCoordinate: queryGeometry.map(p => this.transform.pointCoordinate(p))
607
599
  };
608
600
  }
609
601
 
@@ -1558,6 +1550,14 @@ function removeNode(node) {
1558
1550
  }
1559
1551
  }
1560
1552
 
1553
+ function toPoint(p) {
1554
+ return Array.isArray(p) ? { x: p[0], y: p[1] } : p;
1555
+ }
1556
+
1557
+ function isPointLike(input) {
1558
+ return Array.isArray(input) || (typeof input.x === 'number' && typeof input.y === 'number');
1559
+ }
1560
+
1561
1561
  /**
1562
1562
  * Interface for interactive controls added to the map. This is an
1563
1563
  * specification for implementers to model: it is not
@@ -2,7 +2,7 @@ import { calculateSignedArea } from '@mapwhit/geometry';
2
2
  import quickselect from 'quickselect';
3
3
 
4
4
  // classifies an array of rings into polygons with outer rings and holes
5
- export default function classifyRings(rings, maxRings) {
5
+ export default function classifyRings(rings, maxRings = -1) {
6
6
  if (rings.length <= 1) {
7
7
  return [rings];
8
8
  }
@@ -19,9 +19,7 @@ export default function classifyRings(rings, maxRings) {
19
19
 
20
20
  ring.area = Math.abs(area);
21
21
 
22
- if (ccw === undefined) {
23
- ccw = area < 0;
24
- }
22
+ ccw ??= area < 0;
25
23
 
26
24
  if (ccw === area < 0) {
27
25
  append(polygon);
@@ -1,11 +1,16 @@
1
- class Feature {
1
+ import { VectorTileFeature } from '@mapwhit/vector-tile';
2
+ import classifyRings from './classify_rings.js';
3
+
4
+ export default class GeoJSONFeature {
5
+ #vectorTileFeature;
6
+ #geometry;
7
+ #xyz;
8
+
2
9
  constructor(vectorTileFeature, z, x, y) {
3
10
  this.type = 'Feature';
4
11
 
5
- this._vectorTileFeature = vectorTileFeature;
6
- vectorTileFeature._z = z;
7
- vectorTileFeature._x = x;
8
- vectorTileFeature._y = y;
12
+ this.#vectorTileFeature = vectorTileFeature;
13
+ this.#xyz = { z, x, y };
9
14
 
10
15
  this.properties = vectorTileFeature.properties;
11
16
 
@@ -15,32 +20,66 @@ class Feature {
15
20
  }
16
21
 
17
22
  get geometry() {
18
- if (this._geometry === undefined) {
19
- this._geometry = this._vectorTileFeature.toGeoJSON(
20
- this._vectorTileFeature._x,
21
- this._vectorTileFeature._y,
22
- this._vectorTileFeature._z
23
- ).geometry;
23
+ this.#geometry ??= toGeoJSONGeometry(this.#vectorTileFeature, this.#xyz);
24
+ return this.#geometry;
25
+ }
26
+ }
27
+
28
+ const invPi = 360 / Math.PI;
29
+
30
+ function toGeoJSONGeometry(vtf, { x, y, z }) {
31
+ const size = vtf.extent * 2 ** z;
32
+ const scale = 360 / size;
33
+ const x0 = vtf.extent * x;
34
+ const y0 = vtf.extent * y;
35
+ let coords = vtf.loadGeometry();
36
+ let type = VectorTileFeature.types[vtf.type];
37
+
38
+ switch (vtf.type) {
39
+ case 1: {
40
+ const points = new Array(coords.length);
41
+ for (let i = 0; i < coords.length; i++) {
42
+ points[i] = coords[i][0];
43
+ }
44
+ coords = points;
45
+ project(coords);
46
+ break;
24
47
  }
25
- return this._geometry;
48
+
49
+ case 2:
50
+ for (let i = 0; i < coords.length; i++) {
51
+ project(coords[i]);
52
+ }
53
+ break;
54
+
55
+ case 3:
56
+ coords = classifyRings(coords);
57
+ for (let i = 0; i < coords.length; i++) {
58
+ for (let j = 0; j < coords[i].length; j++) {
59
+ project(coords[i][j]);
60
+ }
61
+ }
62
+ break;
26
63
  }
27
64
 
28
- set geometry(g) {
29
- this._geometry = g;
65
+ if (coords.length === 1) {
66
+ coords = coords[0];
67
+ } else {
68
+ type = `Multi${type}`;
30
69
  }
31
70
 
32
- toJSON() {
33
- const json = {
34
- geometry: this.geometry
35
- };
36
- for (const i in this) {
37
- if (i === '_geometry' || i === '_vectorTileFeature') {
38
- continue;
39
- }
40
- json[i] = this[i];
71
+ return {
72
+ type,
73
+ coordinates: coords
74
+ };
75
+
76
+ function project(line) {
77
+ for (let i = 0; i < line.length; i++) {
78
+ const { x, y } = line[i];
79
+ const lon = (x + x0) * scale - 180;
80
+ const y2 = 180 - (y + y0) * scale;
81
+ const lat = invPi * Math.atan(Math.exp((y2 * Math.PI) / 180)) - 90;
82
+ line[i] = [lon, lat];
41
83
  }
42
- return json;
43
84
  }
44
85
  }
45
-
46
- export default Feature;
@@ -1,72 +0,0 @@
1
- import Point from '@mapbox/point-geometry';
2
- export default clipLine;
3
-
4
- /**
5
- * Returns the part of a multiline that intersects with the provided rectangular box.
6
- *
7
- * @param lines
8
- * @param x1 the left edge of the box
9
- * @param y1 the top edge of the box
10
- * @param x2 the right edge of the box
11
- * @param y2 the bottom edge of the box
12
- * @returns lines
13
- * @private
14
- */
15
- function clipLine(lines, x1, y1, x2, y2) {
16
- const clippedLines = [];
17
-
18
- for (let l = 0; l < lines.length; l++) {
19
- const line = lines[l];
20
- let clippedLine;
21
-
22
- for (let i = 0; i < line.length - 1; i++) {
23
- let p0 = line[i];
24
- let p1 = line[i + 1];
25
-
26
- if (p0.x < x1 && p1.x < x1) {
27
- continue;
28
- }
29
- if (p0.x < x1) {
30
- p0 = new Point(x1, p0.y + (p1.y - p0.y) * ((x1 - p0.x) / (p1.x - p0.x)))._round();
31
- } else if (p1.x < x1) {
32
- p1 = new Point(x1, p0.y + (p1.y - p0.y) * ((x1 - p0.x) / (p1.x - p0.x)))._round();
33
- }
34
-
35
- if (p0.y < y1 && p1.y < y1) {
36
- continue;
37
- }
38
- if (p0.y < y1) {
39
- p0 = new Point(p0.x + (p1.x - p0.x) * ((y1 - p0.y) / (p1.y - p0.y)), y1)._round();
40
- } else if (p1.y < y1) {
41
- p1 = new Point(p0.x + (p1.x - p0.x) * ((y1 - p0.y) / (p1.y - p0.y)), y1)._round();
42
- }
43
-
44
- if (p0.x >= x2 && p1.x >= x2) {
45
- continue;
46
- }
47
- if (p0.x >= x2) {
48
- p0 = new Point(x2, p0.y + (p1.y - p0.y) * ((x2 - p0.x) / (p1.x - p0.x)))._round();
49
- } else if (p1.x >= x2) {
50
- p1 = new Point(x2, p0.y + (p1.y - p0.y) * ((x2 - p0.x) / (p1.x - p0.x)))._round();
51
- }
52
-
53
- if (p0.y >= y2 && p1.y >= y2) {
54
- continue;
55
- }
56
- if (p0.y >= y2) {
57
- p0 = new Point(p0.x + (p1.x - p0.x) * ((y2 - p0.y) / (p1.y - p0.y)), y2)._round();
58
- } else if (p1.y >= y2) {
59
- p1 = new Point(p0.x + (p1.x - p0.x) * ((y2 - p0.y) / (p1.y - p0.y)), y2)._round();
60
- }
61
-
62
- if (!clippedLine || !p0.equals(clippedLine[clippedLine.length - 1])) {
63
- clippedLine = [p0];
64
- clippedLines.push(clippedLine);
65
- }
66
-
67
- clippedLine.push(p1);
68
- }
69
- }
70
-
71
- return clippedLines;
72
- }