@mapwhit/tilerenderer 1.3.0 → 1.4.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 (40) hide show
  1. package/build/min/package.json +1 -1
  2. package/package.json +1 -1
  3. package/src/data/array_types.js +115 -64
  4. package/src/data/bucket/circle_bucket.js +42 -5
  5. package/src/data/bucket/fill_bucket.js +31 -13
  6. package/src/data/bucket/fill_extrusion_bucket.js +8 -6
  7. package/src/data/bucket/line_bucket.js +38 -14
  8. package/src/data/bucket/symbol_attributes.js +13 -5
  9. package/src/data/bucket/symbol_bucket.js +87 -33
  10. package/src/data/bucket/symbol_collision_buffers.js +1 -1
  11. package/src/data/bucket.js +3 -1
  12. package/src/data/feature_index.js +24 -11
  13. package/src/data/segment.js +15 -7
  14. package/src/render/draw_circle.js +45 -4
  15. package/src/render/draw_symbol.js +190 -22
  16. package/src/render/painter.js +1 -1
  17. package/src/source/geojson_source.js +118 -21
  18. package/src/source/geojson_source_diff.js +148 -0
  19. package/src/source/geojson_tiler.js +89 -0
  20. package/src/source/source.js +16 -5
  21. package/src/source/source_cache.js +6 -6
  22. package/src/source/source_state.js +4 -2
  23. package/src/source/tile.js +5 -3
  24. package/src/source/vector_tile_source.js +2 -0
  25. package/src/source/worker_tile.js +4 -2
  26. package/src/style/pauseable_placement.js +39 -7
  27. package/src/style/style.js +17 -12
  28. package/src/style/style_layer/circle_style_layer_properties.js +8 -1
  29. package/src/style/style_layer/fill_style_layer_properties.js +8 -1
  30. package/src/style/style_layer/line_style_layer_properties.js +4 -0
  31. package/src/style/style_layer/symbol_style_layer_properties.js +17 -2
  32. package/src/style-spec/reference/v8.json +161 -4
  33. package/src/symbol/one_em.js +4 -0
  34. package/src/symbol/placement.js +406 -173
  35. package/src/symbol/projection.js +3 -3
  36. package/src/symbol/quads.js +1 -6
  37. package/src/symbol/shaping.js +16 -27
  38. package/src/symbol/symbol_layout.js +243 -81
  39. package/src/util/vectortile_to_geojson.js +3 -4
  40. package/src/source/geojson_worker_source.js +0 -97
@@ -0,0 +1,89 @@
1
+ import rewind from '@mapwhit/geojson-rewind';
2
+ import geojsonvt from 'geojson-vt';
3
+ import Supercluster from 'supercluster';
4
+ import FeatureIndex from '../data/feature_index.js';
5
+ import GeoJSONWrapper from './geojson_wrapper.js';
6
+ import { makeSingleSourceLayerWorkerTile as makeWorkerTile } from './worker_tile.js';
7
+
8
+ /**
9
+ * Creates tiles from GeoJSON data.
10
+ */
11
+ export default function makeTiler(resources, layerIndex) {
12
+ let geoJSONIndex;
13
+ let createGeoJSONIndex;
14
+
15
+ /**
16
+ * Fetches (if appropriate), parses, and index geojson data into tiles. This
17
+ * preparatory method must be called before {@link GeoJSONWorkerSource#loadTile}
18
+ * can correctly serve up tiles.
19
+ *
20
+ * Defers to {@link GeoJSONWorkerSource#loadGeoJSON} for the fetching/parsing,
21
+ * expecting `callback(error, data)` to be called with either an error or a
22
+ * parsed GeoJSON object.
23
+ *
24
+ * @param params
25
+ */
26
+ function loadData(params) {
27
+ const { cluster, data, geojsonVtOptions, superclusterOptions } = params;
28
+ geoJSONIndex = undefined;
29
+ createGeoJSONIndex = cluster
30
+ ? () => {
31
+ rewind(data, true);
32
+ return new Supercluster(superclusterOptions).load(data.features);
33
+ }
34
+ : () => {
35
+ rewind(data, true);
36
+ return geojsonvt(data, geojsonVtOptions);
37
+ };
38
+ }
39
+
40
+ function getTile(tileID) {
41
+ if (!geoJSONIndex) {
42
+ if (!createGeoJSONIndex) {
43
+ return; // we couldn't load the file
44
+ }
45
+
46
+ try {
47
+ geoJSONIndex = createGeoJSONIndex();
48
+ } finally {
49
+ createGeoJSONIndex = undefined;
50
+ }
51
+ }
52
+ const { z, x, y } = tileID.canonical;
53
+ return geoJSONIndex.getTile(z, x, y);
54
+ }
55
+
56
+ /**
57
+ * Returns a single tile.
58
+ */
59
+ async function loadTile(params) {
60
+ const { tileID, source, promoteId } = params;
61
+ const geoJSONTile = getTile(tileID);
62
+ if (!geoJSONTile) {
63
+ return; // nothing in the given tile
64
+ }
65
+ const sourceLayer = new GeoJSONWrapper(geoJSONTile.features);
66
+ const layerFamilies = layerIndex.familiesBySource.get(source);
67
+ if (!layerFamilies) {
68
+ return;
69
+ }
70
+ params.featureIndex = new FeatureIndex(tileID, promoteId);
71
+ const { name } = sourceLayer;
72
+ const features = new Array(sourceLayer.length);
73
+ for (let index = 0; index < sourceLayer.length; index++) {
74
+ const feature = sourceLayer.feature(index);
75
+ const id = params.featureIndex.getId(feature, name);
76
+ features[index] = { feature, id, index, sourceLayerIndex: 0 };
77
+ }
78
+
79
+ const result = await makeWorkerTile(params, features, layerFamilies.get(name), resources);
80
+
81
+ result.vectorTile = sourceLayer;
82
+ return result;
83
+ }
84
+
85
+ return {
86
+ loadData,
87
+ loadTile
88
+ };
89
+ }
@@ -25,6 +25,7 @@ import { bindAll } from '../util/object.js';
25
25
  */
26
26
 
27
27
  import geojson from './geojson_source.js';
28
+ import geojsonTiler from './geojson_tiler.js';
28
29
  import image from './image_source.js';
29
30
  import rasterDem from './raster_dem_tile_source.js';
30
31
  import raster from './raster_tile_source.js';
@@ -38,6 +39,9 @@ const sourceTypes = {
38
39
  image
39
40
  };
40
41
 
42
+ const tilerTypes = {
43
+ geojson: geojsonTiler
44
+ };
41
45
  /*
42
46
  * Creates a tiled data source instance given an options object.
43
47
  *
@@ -48,11 +52,18 @@ const sourceTypes = {
48
52
  * @returns {Source}
49
53
  */
50
54
  export function create(id, specification, eventedParent, { resources, layerIndex, showTileBoundaries }) {
51
- const source = new sourceTypes[specification.type](id, specification, eventedParent, {
52
- resources,
53
- layerIndex,
54
- showTileBoundaries
55
- });
55
+ const tiler = tilerTypes[specification.type]?.(resources, layerIndex);
56
+
57
+ const source = new sourceTypes[specification.type](
58
+ id,
59
+ specification,
60
+ eventedParent,
61
+ tiler ?? {
62
+ resources,
63
+ layerIndex,
64
+ showTileBoundaries
65
+ }
66
+ );
56
67
 
57
68
  bindAll(['load', 'abort', 'unload', 'serialize', 'prepare'], source);
58
69
  return source;
@@ -799,27 +799,27 @@ class SourceCache extends Evented {
799
799
  * Set the value of a particular state for a feature
800
800
  * @private
801
801
  */
802
- setFeatureState(sourceLayer, feature, state) {
802
+ setFeatureState(sourceLayer, featureId, state) {
803
803
  sourceLayer = sourceLayer || '_geojsonTileLayer';
804
- this._state.updateState(sourceLayer, feature, state);
804
+ this._state.updateState(sourceLayer, featureId, state);
805
805
  }
806
806
 
807
807
  /**
808
808
  * Resets the value of a particular state key for a feature
809
809
  * @private
810
810
  */
811
- removeFeatureState(sourceLayer, feature, key) {
811
+ removeFeatureState(sourceLayer, featureId, key) {
812
812
  sourceLayer = sourceLayer || '_geojsonTileLayer';
813
- this._state.removeFeatureState(sourceLayer, feature, key);
813
+ this._state.removeFeatureState(sourceLayer, featureId, key);
814
814
  }
815
815
 
816
816
  /**
817
817
  * Get the entire state object for a feature
818
818
  * @private
819
819
  */
820
- getFeatureState(sourceLayer, feature) {
820
+ getFeatureState(sourceLayer, featureId) {
821
821
  sourceLayer = sourceLayer || '_geojsonTileLayer';
822
- return this._state.getState(sourceLayer, feature);
822
+ return this._state.getState(sourceLayer, featureId);
823
823
  }
824
824
  }
825
825
 
@@ -13,7 +13,8 @@ class SourceFeatureState {
13
13
  #stateChanges = {};
14
14
  #deletedStates = {};
15
15
 
16
- updateState(sourceLayer, feature, newState) {
16
+ updateState(sourceLayer, featureId, newState) {
17
+ const feature = String(featureId);
17
18
  const changes = (this.#stateChanges[sourceLayer] ??= {});
18
19
  const featureState = (changes[feature] ??= {});
19
20
  Object.assign(featureState, newState);
@@ -76,7 +77,8 @@ class SourceFeatureState {
76
77
  }
77
78
  }
78
79
 
79
- getState(sourceLayer, feature) {
80
+ getState(sourceLayer, featureId) {
81
+ const feature = String(featureId);
80
82
  const base = this.#state[sourceLayer];
81
83
  const changes = this.#stateChanges[sourceLayer];
82
84
  const reconciledState = Object.assign({}, base?.[feature], changes?.[feature]);
@@ -191,11 +191,12 @@ class Tile {
191
191
  }
192
192
 
193
193
  querySourceFeatures(result, params) {
194
- if (!this.latestFeatureIndex?.vectorTile) {
194
+ const featureIndex = this.latestFeatureIndex;
195
+ if (!featureIndex?.vectorTile) {
195
196
  return;
196
197
  }
197
198
 
198
- const vtLayers = this.latestFeatureIndex.loadVTLayers();
199
+ const vtLayers = featureIndex.loadVTLayers();
199
200
 
200
201
  const sourceLayer = params ? params.sourceLayer : '';
201
202
  const layer = vtLayers._geojsonTileLayer || vtLayers[sourceLayer];
@@ -211,7 +212,8 @@ class Tile {
211
212
  for (let i = 0; i < layer.length; i++) {
212
213
  const feature = layer.feature(i);
213
214
  if (filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) {
214
- const geojsonFeature = new GeoJSONFeature(feature, z, x, y);
215
+ const id = featureIndex.getId(feature, sourceLayer);
216
+ const geojsonFeature = new GeoJSONFeature(feature, z, x, y, id);
215
217
  geojsonFeature.tile = coord;
216
218
  result.push(geojsonFeature);
217
219
  }
@@ -22,6 +22,7 @@ class VectorTileSource extends Evented {
22
22
  this.url = options.url;
23
23
  this.scheme = options.scheme ?? 'xyz';
24
24
  this.tileSize = options.tileSize ?? 512;
25
+ this.promoteId = options.promoteId;
25
26
 
26
27
  if (this.tileSize !== 512) {
27
28
  throw new Error('vector tile sources must have a tileSize of 512');
@@ -93,6 +94,7 @@ class VectorTileSource extends Evented {
93
94
  source: this.id,
94
95
  pixelRatio: browser.devicePixelRatio,
95
96
  showCollisionBoxes: this.map.showCollisionBoxes,
97
+ promoteId: this.promoteId,
96
98
  painter: this.map.painter
97
99
  };
98
100
  tile.workerID ??= true;
@@ -27,7 +27,9 @@ async function makeWorkerTile(params, { layers }, layerIndex, resources) {
27
27
  const sourceLayerIndex = sourceLayerCoder.encode(sourceLayerId);
28
28
  const features = new Array(sourceLayer.length);
29
29
  for (let index = 0; index < sourceLayer.length; index++) {
30
- features[index] = { feature: sourceLayer.feature(index), index, sourceLayerIndex };
30
+ const feature = sourceLayer.feature(index);
31
+ const id = options.featureIndex.getId(feature, sourceLayerId);
32
+ features[index] = { feature, id, index, sourceLayerIndex };
31
33
  }
32
34
 
33
35
  makeBucketsForSourceLayer(sourceLayerFamilies, sourceLayerIndex, features, params, options);
@@ -47,7 +49,7 @@ export async function makeSingleSourceLayerWorkerTile(params, features, sourceLa
47
49
  function initializeBucketsOptions(params) {
48
50
  const tileID = createTileID(params);
49
51
 
50
- const featureIndex = new FeatureIndex(tileID);
52
+ const featureIndex = params.featureIndex ?? new FeatureIndex(tileID, params.promoteId);
51
53
  featureIndex.bucketLayerIDs = [];
52
54
 
53
55
  return {
@@ -2,27 +2,59 @@ import { Placement } from '../symbol/placement.js';
2
2
  import browser from '../util/browser.js';
3
3
 
4
4
  class LayerPlacement {
5
- constructor() {
5
+ constructor(styleLayer) {
6
+ this._sortAcrossTiles =
7
+ styleLayer._layout.get('symbol-z-order') !== 'viewport-y' &&
8
+ styleLayer._layout.get('symbol-sort-key').constantOr(1) !== undefined;
9
+
6
10
  this._currentTileIndex = 0;
11
+ this._currentPartIndex = 0;
7
12
  this._seenCrossTileIDs = {};
13
+ this._bucketParts = [];
8
14
  }
9
15
 
10
16
  continuePlacement(tiles, placement, showCollisionBoxes, styleLayer, shouldPausePlacement) {
17
+ const bucketParts = this._bucketParts;
18
+
11
19
  while (this._currentTileIndex < tiles.length) {
12
20
  const tile = tiles[this._currentTileIndex];
13
- placement.placeLayerTile(styleLayer, tile, showCollisionBoxes, this._seenCrossTileIDs);
21
+ placement.getBucketParts(bucketParts, styleLayer, tile, this._sortAcrossTiles);
14
22
 
15
23
  this._currentTileIndex++;
16
24
  if (shouldPausePlacement()) {
17
25
  return true;
18
26
  }
19
27
  }
28
+
29
+ if (this._sortAcrossTiles) {
30
+ this._sortAcrossTiles = false;
31
+ bucketParts.sort((a, b) => a.sortKey - b.sortKey);
32
+ }
33
+
34
+ while (this._currentPartIndex < bucketParts.length) {
35
+ const bucketPart = bucketParts[this._currentPartIndex];
36
+ placement.placeLayerBucketPart(bucketPart, this._seenCrossTileIDs, showCollisionBoxes);
37
+
38
+ this._currentPartIndex++;
39
+ if (shouldPausePlacement()) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
20
44
  }
21
45
  }
22
46
 
23
47
  class PauseablePlacement {
24
- constructor(transform, maxIndex, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions) {
25
- this.placement = new Placement(transform, fadeDuration, crossSourceCollisions);
48
+ constructor(
49
+ transform,
50
+ maxIndex,
51
+ forceFullPlacement,
52
+ showCollisionBoxes,
53
+ fadeDuration,
54
+ crossSourceCollisions,
55
+ prevPlacement
56
+ ) {
57
+ this.placement = new Placement(transform, fadeDuration, crossSourceCollisions, prevPlacement);
26
58
  this._currentPlacementIndex = maxIndex;
27
59
  this._forceFullPlacement = forceFullPlacement;
28
60
  this._showCollisionBoxes = showCollisionBoxes;
@@ -50,7 +82,7 @@ class PauseablePlacement {
50
82
  (!layer.maxzoom || layer.maxzoom > placementZoom)
51
83
  ) {
52
84
  if (!this._inProgressLayer) {
53
- this._inProgressLayer = new LayerPlacement();
85
+ this._inProgressLayer = new LayerPlacement(layer);
54
86
  }
55
87
 
56
88
  const pausePlacement = this._inProgressLayer.continuePlacement(
@@ -77,8 +109,8 @@ class PauseablePlacement {
77
109
  this._done = true;
78
110
  }
79
111
 
80
- commit(previousPlacement, now) {
81
- this.placement.commit(previousPlacement, now);
112
+ commit(now) {
113
+ this.placement.commit(now);
82
114
  return this.placement;
83
115
  }
84
116
  }
@@ -784,13 +784,13 @@ class Style extends Evented {
784
784
  return this.getLayer(layer).getPaintProperty(name);
785
785
  }
786
786
 
787
- setFeatureState(feature, state) {
787
+ setFeatureState(target, state) {
788
788
  if (!this._loaded) {
789
- this.#opsQueue.push(() => this.setFeatureState(feature, state));
789
+ this.#opsQueue.push(() => this.setFeatureState(target, state));
790
790
  return;
791
791
  }
792
- const sourceId = feature.source;
793
- const sourceLayer = feature.sourceLayer;
792
+ const sourceId = target.source;
793
+ const sourceLayer = target.sourceLayer;
794
794
  const sourceCache = this._sources[sourceId];
795
795
 
796
796
  if (sourceCache === undefined) {
@@ -806,12 +806,12 @@ class Style extends Evented {
806
806
  this.fire(new ErrorEvent(new Error('The sourceLayer parameter must be provided for vector source types.')));
807
807
  return;
808
808
  }
809
- if (feature.id == null || feature.id === '') {
809
+ if (target.id === undefined) {
810
810
  this.fire(new ErrorEvent(new Error('The feature id parameter must be provided.')));
811
811
  return;
812
812
  }
813
813
 
814
- sourceCache.setFeatureState(sourceLayer, feature.id, state);
814
+ sourceCache.setFeatureState(sourceLayer, target.id, state);
815
815
  }
816
816
 
817
817
  removeFeatureState(target, key) {
@@ -843,12 +843,12 @@ class Style extends Evented {
843
843
  sourceCache.removeFeatureState(sourceLayer, target.id, key);
844
844
  }
845
845
 
846
- getFeatureState(feature) {
846
+ getFeatureState(target) {
847
847
  if (!this._loaded) {
848
848
  return;
849
849
  }
850
- const sourceId = feature.source;
851
- const sourceLayer = feature.sourceLayer;
850
+ const sourceId = target.source;
851
+ const sourceLayer = target.sourceLayer;
852
852
  const sourceCache = this._sources[sourceId];
853
853
 
854
854
  if (sourceCache === undefined) {
@@ -861,7 +861,11 @@ class Style extends Evented {
861
861
  return;
862
862
  }
863
863
 
864
- return sourceCache.getFeatureState(sourceLayer, feature.id);
864
+ if (target.id === undefined) {
865
+ this.fire(new ErrorEvent(new Error('The feature id parameter must be provided.')));
866
+ }
867
+
868
+ return sourceCache.getFeatureState(sourceLayer, target.id);
865
869
  }
866
870
 
867
871
  getTransition() {
@@ -1117,7 +1121,8 @@ class Style extends Evented {
1117
1121
  forceFullPlacement,
1118
1122
  showCollisionBoxes,
1119
1123
  fadeDuration,
1120
- crossSourceCollisions
1124
+ crossSourceCollisions,
1125
+ this.placement
1121
1126
  );
1122
1127
  this._layerOrderChanged = false;
1123
1128
  }
@@ -1132,7 +1137,7 @@ class Style extends Evented {
1132
1137
  this.pauseablePlacement.continuePlacement(Array.from(this._layers.values()), layerTiles);
1133
1138
 
1134
1139
  if (this.pauseablePlacement.isDone()) {
1135
- this.placement = this.pauseablePlacement.commit(this.placement, browser.now());
1140
+ this.placement = this.pauseablePlacement.commit(browser.now());
1136
1141
  placementCommitted = true;
1137
1142
  }
1138
1143
 
@@ -2,6 +2,13 @@
2
2
 
3
3
  import { DataConstantProperty, DataDrivenProperty, Properties } from '../properties.js';
4
4
 
5
+ const layout = new Properties({
6
+ 'circle-sort-key': new DataDrivenProperty({
7
+ type: 'number',
8
+ expression: { parameters: ['zoom', 'feature'] }
9
+ })
10
+ });
11
+
5
12
  const paint = new Properties({
6
13
  'circle-radius': new DataDrivenProperty({
7
14
  type: 'number',
@@ -73,4 +80,4 @@ const paint = new Properties({
73
80
  })
74
81
  });
75
82
 
76
- export default { paint };
83
+ export default { paint, layout };
@@ -2,6 +2,13 @@
2
2
 
3
3
  import { CrossFadedDataDrivenProperty, DataConstantProperty, DataDrivenProperty, Properties } from '../properties.js';
4
4
 
5
+ const layout = new Properties({
6
+ 'fill-sort-key': new DataDrivenProperty({
7
+ type: 'number',
8
+ expression: { parameters: ['zoom', 'feature'] }
9
+ })
10
+ });
11
+
5
12
  const paint = new Properties({
6
13
  'fill-antialias': new DataConstantProperty({ type: 'boolean', default: true, expression: { parameters: ['zoom'] } }),
7
14
  'fill-opacity': new DataDrivenProperty({
@@ -42,4 +49,4 @@ const paint = new Properties({
42
49
  })
43
50
  });
44
51
 
45
- export default { paint };
52
+ export default { paint, layout };
@@ -31,6 +31,10 @@ const layout = new Properties({
31
31
  type: 'number',
32
32
  default: 1.05,
33
33
  expression: { interpolated: true, parameters: ['zoom'] }
34
+ }),
35
+ 'line-sort-key': new DataDrivenProperty({
36
+ type: 'number',
37
+ expression: { parameters: ['zoom', 'feature'] }
34
38
  })
35
39
  });
36
40
  const paint = new Properties({
@@ -19,10 +19,14 @@ const layout = new Properties({
19
19
  default: false,
20
20
  expression: { parameters: ['zoom'] }
21
21
  }),
22
+ 'symbol-sort-key': new DataDrivenProperty({
23
+ type: 'number',
24
+ expression: { parameters: ['zoom', 'feature'] }
25
+ }),
22
26
  'symbol-z-order': new DataConstantProperty({
23
27
  type: 'enum',
24
- values: ['viewport-y', 'source'],
25
- default: 'viewport-y',
28
+ values: ['auto', 'viewport-y', 'source'],
29
+ default: 'auto',
26
30
  expression: { parameters: ['zoom'] }
27
31
  }),
28
32
  'icon-allow-overlap': new DataConstantProperty({
@@ -149,6 +153,17 @@ const layout = new Properties({
149
153
  default: 'center',
150
154
  expression: { parameters: ['zoom', 'feature'] }
151
155
  }),
156
+ 'text-radial-offset': new DataDrivenProperty({
157
+ type: 'number',
158
+ default: 0,
159
+ expression: { interpolated: true, parameters: ['zoom', 'feature'] }
160
+ }),
161
+ 'text-variable-anchor': new DataConstantProperty({
162
+ type: 'array',
163
+ value: 'enum',
164
+ values: ['center', 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],
165
+ expression: { parameters: ['zoom'] }
166
+ }),
152
167
  'text-anchor': new DataDrivenProperty({
153
168
  type: 'enum',
154
169
  values: ['center', 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],