@mapwhit/tilerenderer 0.51.1 → 0.52.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.
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.51.0"
2
+ "version": "0.52.0"
3
3
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mapwhit/tilerenderer",
3
3
  "description": "A WebGL interactive maps library",
4
- "version": "0.51.1",
4
+ "version": "0.52.1",
5
5
  "exports": {
6
6
  ".": "./src/index.js",
7
7
  "./worker": "./src/worker.js"
@@ -19,18 +19,18 @@
19
19
  "@mapwhit/events": "^0.0.1",
20
20
  "@mapwhit/geojson-rewind": "^1.0.0",
21
21
  "@mapwhit/pbf": "^1.0.0",
22
- "@mapwhit/style-expressions": "^0.0.2",
23
- "@mapwhit/vector-tile": "^1.0.0",
24
- "@mapwhit/vt-pbf": "^1.0.0",
22
+ "@mapwhit/style-expressions": "^1.1.0",
23
+ "@mapwhit/vector-tile": "^2.0.1",
24
+ "@mapwhit/vt-pbf": "^2.0.0",
25
25
  "@pirxpilot/nanoassert": "~1",
26
26
  "csscolorparser": "^1.0.3",
27
27
  "earcut": "^3.0.1",
28
28
  "geojson-vt": "^4.0.2",
29
29
  "grid-index": "^1.1.0",
30
30
  "murmurhash-js": "^1.0.0",
31
- "potpack": "^1.0.1",
31
+ "potpack": "^2.1.0",
32
32
  "quickselect": "^3.0.0",
33
- "supercluster": "^2.0.1",
33
+ "supercluster": "^8.0.1",
34
34
  "tinyqueue": "^3.0.0"
35
35
  },
36
36
  "browser": {
@@ -23,6 +23,7 @@ function addCircleVertex(layoutVertexArray, x, y, extrudeX, extrudeY) {
23
23
  class CircleBucket {
24
24
  constructor(options) {
25
25
  this.zoom = options.zoom;
26
+ this.globalState = options.globalState;
26
27
  this.overscaling = options.overscaling;
27
28
  this.layers = options.layers;
28
29
  this.layerIds = this.layers.map(layer => layer.id);
@@ -37,7 +38,9 @@ class CircleBucket {
37
38
 
38
39
  populate(features, options) {
39
40
  for (const { feature, index, sourceLayerIndex } of features) {
40
- if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) {
41
+ if (
42
+ this.layers[0]._featureFilter(new EvaluationParameters(this.zoom, { globalState: this.globalState }), feature)
43
+ ) {
41
44
  const geometry = loadGeometry(feature);
42
45
  this.addFeature(feature, geometry, index);
43
46
  options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index);
@@ -47,7 +50,10 @@ class CircleBucket {
47
50
 
48
51
  update(states, vtLayer, imagePositions) {
49
52
  if (!this.stateDependentLayers.length) return;
50
- this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions);
53
+ this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, {
54
+ imagePositions,
55
+ globalState: this.globalState
56
+ });
51
57
  }
52
58
 
53
59
  isEmpty() {
@@ -109,7 +115,10 @@ class CircleBucket {
109
115
  }
110
116
  }
111
117
 
112
- this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {});
118
+ this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {
119
+ imagePositions: {},
120
+ globalState: this.globalState
121
+ });
113
122
  }
114
123
  }
115
124
 
@@ -16,6 +16,7 @@ const EvaluationParameters = require('../../style/evaluation_parameters');
16
16
  class FillBucket {
17
17
  constructor(options) {
18
18
  this.zoom = options.zoom;
19
+ this.globalState = options.globalState;
19
20
  this.overscaling = options.overscaling;
20
21
  this.layers = options.layers;
21
22
  this.layerIds = this.layers.map(layer => layer.id);
@@ -35,7 +36,10 @@ class FillBucket {
35
36
  this.hasPattern = hasPattern('fill', this.layers, options);
36
37
 
37
38
  for (const { feature, index, sourceLayerIndex } of features) {
38
- if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue;
39
+ if (
40
+ !this.layers[0]._featureFilter(new EvaluationParameters(this.zoom, { globalState: this.globalState }), feature)
41
+ )
42
+ continue;
39
43
 
40
44
  const geometry = loadGeometry(feature);
41
45
 
@@ -64,7 +68,10 @@ class FillBucket {
64
68
 
65
69
  update(states, vtLayer, imagePositions) {
66
70
  if (!this.stateDependentLayers.length) return;
67
- this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions);
71
+ this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, {
72
+ imagePositions,
73
+ globalState: this.globalState
74
+ });
68
75
  }
69
76
 
70
77
  addFeatures(options, imagePositions) {
@@ -157,7 +164,10 @@ class FillBucket {
157
164
  triangleSegment.primitiveLength += indices.length / 3;
158
165
  }
159
166
 
160
- this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions);
167
+ this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {
168
+ imagePositions,
169
+ globalState: this.globalState
170
+ });
161
171
  }
162
172
  }
163
173
 
@@ -36,6 +36,7 @@ function addVertex(vertexArray, x, y, nx, ny, nz, t, e) {
36
36
  class FillExtrusionBucket {
37
37
  constructor(options) {
38
38
  this.zoom = options.zoom;
39
+ this.globalState = options.globalState;
39
40
  this.overscaling = options.overscaling;
40
41
  this.layers = options.layers;
41
42
  this.layerIds = this.layers.map(layer => layer.id);
@@ -53,7 +54,10 @@ class FillExtrusionBucket {
53
54
  this.hasPattern = hasPattern('fill-extrusion', this.layers, options);
54
55
 
55
56
  for (const { feature, index, sourceLayerIndex } of features) {
56
- if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue;
57
+ if (
58
+ !this.layers[0]._featureFilter(new EvaluationParameters(this.zoom, { globalState: this.globalState }), feature)
59
+ )
60
+ continue;
57
61
 
58
62
  const geometry = loadGeometry(feature);
59
63
 
@@ -89,7 +93,10 @@ class FillExtrusionBucket {
89
93
 
90
94
  update(states, vtLayer, imagePositions) {
91
95
  if (!this.stateDependentLayers.length) return;
92
- this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions);
96
+ this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, {
97
+ imagePositions,
98
+ globalState: this.globalState
99
+ });
93
100
  }
94
101
 
95
102
  isEmpty() {
@@ -225,7 +232,10 @@ class FillExtrusionBucket {
225
232
  segment.vertexLength += numVertices;
226
233
  }
227
234
 
228
- this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions);
235
+ this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {
236
+ imagePositions,
237
+ globalState: this.globalState
238
+ });
229
239
  }
230
240
  }
231
241
 
@@ -72,6 +72,7 @@ function addLineVertex(layoutVertexBuffer, point, extrude, round, up, dir, lines
72
72
  class LineBucket {
73
73
  constructor(options) {
74
74
  this.zoom = options.zoom;
75
+ this.globalState = options.globalState;
75
76
  this.overscaling = options.overscaling;
76
77
  this.layers = options.layers;
77
78
  this.layerIds = this.layers.map(layer => layer.id);
@@ -90,7 +91,10 @@ class LineBucket {
90
91
  this.hasPattern = hasPattern('line', this.layers, options);
91
92
 
92
93
  for (const { feature, index, sourceLayerIndex } of features) {
93
- if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue;
94
+ if (
95
+ !this.layers[0]._featureFilter(new EvaluationParameters(this.zoom, { globalState: this.globalState }), feature)
96
+ )
97
+ continue;
94
98
 
95
99
  const geometry = loadGeometry(feature);
96
100
 
@@ -119,7 +123,10 @@ class LineBucket {
119
123
 
120
124
  update(states, vtLayer, imagePositions) {
121
125
  if (!this.stateDependentLayers.length) return;
122
- this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions);
126
+ this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, {
127
+ imagePositions,
128
+ globalState: this.globalState
129
+ });
123
130
  }
124
131
 
125
132
  addFeatures(options, imagePositions) {
@@ -494,7 +501,10 @@ class LineBucket {
494
501
  startOfLine = false;
495
502
  }
496
503
 
497
- this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions);
504
+ this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {
505
+ imagePositions,
506
+ globalState: this.globalState
507
+ });
498
508
  }
499
509
 
500
510
  /**
@@ -176,6 +176,7 @@ class SymbolBucket {
176
176
  constructor(options) {
177
177
  this.collisionBoxArray = options.collisionBoxArray;
178
178
  this.zoom = options.zoom;
179
+ this.globalState = options.globalState;
179
180
  this.overscaling = options.overscaling;
180
181
  this.layers = options.layers;
181
182
  this.layerIds = this.layers.map(layer => layer.id);
@@ -258,7 +259,7 @@ class SymbolBucket {
258
259
 
259
260
  const icons = options.iconDependencies;
260
261
  const stacks = options.glyphDependencies;
261
- const globalProperties = new EvaluationParameters(this.zoom);
262
+ const globalProperties = new EvaluationParameters(this.zoom, { globalState: this.globalState });
262
263
 
263
264
  for (const { feature, index, sourceLayerIndex } of features) {
264
265
  if (!layer._featureFilter(globalProperties, feature)) {
@@ -327,8 +328,14 @@ class SymbolBucket {
327
328
 
328
329
  update(states, vtLayer, imagePositions) {
329
330
  if (!this.stateDependentLayers.length) return;
330
- this.text.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, imagePositions);
331
- this.icon.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, imagePositions);
331
+ this.text.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, {
332
+ imagePositions,
333
+ globalState: this.globalState
334
+ });
335
+ this.icon.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, {
336
+ imagePositions,
337
+ globalState: this.globalState
338
+ });
332
339
  }
333
340
 
334
341
  isEmpty() {
@@ -457,7 +464,10 @@ class SymbolBucket {
457
464
  false
458
465
  );
459
466
 
460
- arrays.programConfigurations.populatePaintArrays(arrays.layoutVertexArray.length, feature, feature.index, {});
467
+ arrays.programConfigurations.populatePaintArrays(arrays.layoutVertexArray.length, feature, feature.index, {
468
+ imagePositions: {},
469
+ globalState: this.globalState
470
+ });
461
471
  }
462
472
 
463
473
  _addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, point, anchorX, anchorY, extrude) {
@@ -128,13 +128,13 @@ class SourceExpressionBinder {
128
128
 
129
129
  setConstantPatternPositions() {}
130
130
 
131
- populatePaintArray(newLength, feature, imagePositions) {
131
+ populatePaintArray(newLength, feature, options) {
132
132
  const paintArray = this.paintVertexArray;
133
133
 
134
134
  const start = paintArray.length;
135
135
  paintArray.reserve(newLength);
136
136
 
137
- const value = this.expression.evaluate(new EvaluationParameters(0), feature, {});
137
+ const value = this.expression.evaluate(new EvaluationParameters(0, options), feature, {});
138
138
 
139
139
  if (this.type === 'color') {
140
140
  const color = packColor(value);
@@ -150,9 +150,9 @@ class SourceExpressionBinder {
150
150
  }
151
151
  }
152
152
 
153
- updatePaintArray(start, end, feature, featureState, imagePositions) {
153
+ updatePaintArray(start, end, feature, featureState, options) {
154
154
  const paintArray = this.paintVertexArray;
155
- const value = this.expression.evaluate({ zoom: 0 }, feature, featureState);
155
+ const value = this.expression.evaluate(new EvaluationParameters(0, options), feature, featureState);
156
156
 
157
157
  if (this.type === 'color') {
158
158
  const color = packColor(value);
@@ -224,14 +224,14 @@ class CompositeExpressionBinder {
224
224
 
225
225
  setConstantPatternPositions() {}
226
226
 
227
- populatePaintArray(newLength, feature) {
227
+ populatePaintArray(newLength, feature, options) {
228
228
  const paintArray = this.paintVertexArray;
229
229
 
230
230
  const start = paintArray.length;
231
231
  paintArray.reserve(newLength);
232
232
 
233
- const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {});
234
- const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {});
233
+ const min = this.expression.evaluate(new EvaluationParameters(this.zoom, options), feature, {});
234
+ const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1, options), feature, {});
235
235
 
236
236
  if (this.type === 'color') {
237
237
  const minColor = packColor(min);
@@ -247,11 +247,11 @@ class CompositeExpressionBinder {
247
247
  }
248
248
  }
249
249
 
250
- updatePaintArray(start, end, feature, featureState) {
250
+ updatePaintArray(start, end, feature, featureState, options) {
251
251
  const paintArray = this.paintVertexArray;
252
252
 
253
- const min = this.expression.evaluate({ zoom: this.zoom }, feature, featureState);
254
- const max = this.expression.evaluate({ zoom: this.zoom + 1 }, feature, featureState);
253
+ const min = this.expression.evaluate(new EvaluationParameters(this.zoom, options), feature, featureState);
254
+ const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1, options), feature, featureState);
255
255
 
256
256
  if (this.type === 'color') {
257
257
  const minColor = packColor(min);
@@ -331,7 +331,7 @@ class CrossFadedCompositeBinder {
331
331
 
332
332
  setConstantPatternPositions() {}
333
333
 
334
- populatePaintArray(length, feature, imagePositions) {
334
+ populatePaintArray(length, feature, { imagePositions }) {
335
335
  // We populate two paint arrays because, for cross-faded properties, we don't know which direction
336
336
  // we're cross-fading to at layout time. In order to keep vertex attributes to a minimum and not pass
337
337
  // unnecessary vertex data to the shaders, we determine which to upload at draw time.
@@ -379,7 +379,7 @@ class CrossFadedCompositeBinder {
379
379
  }
380
380
  }
381
381
 
382
- updatePaintArray(start, end, feature, featureState, imagePositions) {
382
+ updatePaintArray(start, end, feature, featureState, { imagePositions }) {
383
383
  // We populate two paint arrays because, for cross-faded properties, we don't know which direction
384
384
  // we're cross-fading to at layout time. In order to keep vertex attributes to a minimum and not pass
385
385
  // unnecessary vertex data to the shaders, we determine which to upload at draw time.
@@ -546,12 +546,12 @@ class ProgramConfiguration {
546
546
  return self;
547
547
  }
548
548
 
549
- populatePaintArrays(newLength, feature, index, imagePositions) {
549
+ populatePaintArrays(newLength, feature, index, options) {
550
550
  for (const property in this.binders) {
551
551
  const binder = this.binders[property];
552
- binder.populatePaintArray(newLength, feature, imagePositions);
552
+ binder.populatePaintArray(newLength, feature, options);
553
553
  }
554
- if (feature.id) {
554
+ if (feature.id !== undefined) {
555
555
  const featureId = String(feature.id);
556
556
  this._idMap[featureId] = this._idMap[featureId] || [];
557
557
  this._idMap[featureId].push({
@@ -571,7 +571,7 @@ class ProgramConfiguration {
571
571
  }
572
572
  }
573
573
 
574
- updatePaintArrays(featureStates, vtLayer, layer, imagePositions) {
574
+ updatePaintArrays(featureStates, vtLayer, layer, options) {
575
575
  let dirty = false;
576
576
  for (const id in featureStates) {
577
577
  const posArray = this._idMap[id];
@@ -588,7 +588,7 @@ class ProgramConfiguration {
588
588
  //AHM: Remove after https://github.com/mapbox/mapbox-gl-js/issues/6255
589
589
  const value = layer.paint.get(property);
590
590
  binder.expression = value.value;
591
- binder.updatePaintArray(pos.start, pos.end, feature, featureState, imagePositions);
591
+ binder.updatePaintArray(pos.start, pos.end, feature, featureState, options);
592
592
  dirty = true;
593
593
  }
594
594
  }
@@ -687,17 +687,17 @@ class ProgramConfigurationSet {
687
687
  this.needsUpload = false;
688
688
  }
689
689
 
690
- populatePaintArrays(length, feature, index, imagePositions) {
690
+ populatePaintArrays(length, feature, index, options) {
691
691
  for (const key in this.programConfigurations) {
692
- this.programConfigurations[key].populatePaintArrays(length, feature, index, imagePositions);
692
+ this.programConfigurations[key].populatePaintArrays(length, feature, index, options);
693
693
  }
694
694
  this.needsUpload = true;
695
695
  }
696
696
 
697
- updatePaintArrays(featureStates, vtLayer, layers, imagePositions) {
697
+ updatePaintArrays(featureStates, vtLayer, layers, options) {
698
698
  for (const layer of layers) {
699
699
  this.needsUpload =
700
- this.programConfigurations[layer.id].updatePaintArrays(featureStates, vtLayer, layer, imagePositions) ||
700
+ this.programConfigurations[layer.id].updatePaintArrays(featureStates, vtLayer, layer, options) ||
701
701
  this.needsUpload;
702
702
  }
703
703
  }
@@ -1,6 +1,6 @@
1
1
  const { AlphaImage } = require('../util/image');
2
2
  const { register } = require('../util/transfer_registry');
3
- const potpack = require('potpack');
3
+ const { default: potpack } = require('potpack');
4
4
 
5
5
  const padding = 1;
6
6
 
@@ -1,6 +1,6 @@
1
1
  const { RGBAImage } = require('../util/image');
2
2
  const { register } = require('../util/transfer_registry');
3
- const potpack = require('potpack');
3
+ const { default: potpack } = require('potpack');
4
4
 
5
5
  const padding = 1;
6
6
 
@@ -1,4 +1,4 @@
1
- const potpack = require('potpack');
1
+ const { default: potpack } = require('potpack');
2
2
 
3
3
  const { RGBAImage } = require('../util/image');
4
4
  const { ImagePosition } = require('./image_atlas');
@@ -184,7 +184,8 @@ class GeoJSONSource extends Evented {
184
184
  tileSize: this.tileSize,
185
185
  source: this.id,
186
186
  pixelRatio: browser.devicePixelRatio,
187
- showCollisionBoxes: this.map.showCollisionBoxes
187
+ showCollisionBoxes: this.map.showCollisionBoxes,
188
+ globalState: this.map.getGlobalState()
188
189
  };
189
190
 
190
191
  const justReloaded = tile.workerID != null;
@@ -1,7 +1,7 @@
1
1
  const rewind = require('@mapwhit/geojson-rewind');
2
2
  const GeoJSONWrapper = require('./geojson_wrapper');
3
- const vtpbf = require('@mapwhit/vt-pbf');
4
- const supercluster = require('supercluster');
3
+ const { fromVectorTileJs } = require('@mapwhit/vt-pbf');
4
+ const { default: Supercluster } = require('supercluster');
5
5
  const { default: geojsonvt } = require('geojson-vt');
6
6
  const VectorTileWorkerSource = require('./vector_tile_worker_source');
7
7
 
@@ -29,7 +29,7 @@ function loadGeoJSONTile(params) {
29
29
  // Encode the geojson-vt tile into binary vector tile form. This
30
30
  // is a convenience that allows `FeatureIndex` to operate the same way
31
31
  // across `VectorTileSource` and `GeoJSONSource` data.
32
- let pbf = vtpbf(geojsonWrapper);
32
+ let pbf = fromVectorTileJs(geojsonWrapper);
33
33
  if (pbf.byteOffset !== 0 || pbf.byteLength !== pbf.buffer.byteLength) {
34
34
  // Compatibility with node Buffer (https://github.com/mapbox/pbf/issues/35)
35
35
  pbf = new Uint8Array(pbf);
@@ -81,7 +81,7 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
81
81
  this._createGeoJSONIndex = params.cluster
82
82
  ? () => {
83
83
  rewind(data, true);
84
- return supercluster(params.superclusterOptions).load(data.features);
84
+ return new Supercluster(params.superclusterOptions).load(data.features);
85
85
  }
86
86
  : () => {
87
87
  rewind(data, true);
@@ -94,7 +94,8 @@ class VectorTileSource extends Evented {
94
94
  type: this.type,
95
95
  source: this.id,
96
96
  pixelRatio: browser.devicePixelRatio,
97
- showCollisionBoxes: this.map.showCollisionBoxes
97
+ showCollisionBoxes: this.map.showCollisionBoxes,
98
+ globalState: this.map.getGlobalState()
98
99
  };
99
100
  tile.workerID ??= this.dispatcher.nextWorkerId();
100
101
  const data = await this.dispatcher.send('loadTile', params, tile.workerID);
@@ -49,6 +49,7 @@ class VectorTileWorkerSource {
49
49
  }
50
50
  const { vectorTile, rawData } = response;
51
51
  const workerTile = new WorkerTile(params);
52
+ workerTile.globalState = params.globalState;
52
53
  workerTile.vectorTile = vectorTile;
53
54
  const result = await workerTile.parse(vectorTile, this.layerIndex, this.resources);
54
55
  if (rawData) {
@@ -31,6 +31,7 @@ class WorkerTile {
31
31
  this.source = params.source;
32
32
  this.overscaling = this.tileID.overscaleFactor();
33
33
  this.showCollisionBoxes = params.showCollisionBoxes;
34
+ this.globalState = params.globalState;
34
35
  }
35
36
 
36
37
  async parse(data, layerIndex, resources) {
@@ -81,7 +82,7 @@ class WorkerTile {
81
82
  if (layer.maxzoom && this.zoom >= layer.maxzoom) continue;
82
83
  if (layer.visibility === 'none') continue;
83
84
 
84
- recalculateLayers(family, this.zoom);
85
+ recalculateLayers(family, this.zoom, this.globalState);
85
86
 
86
87
  const bucket = (buckets[layer.id] = layer.createBucket({
87
88
  index: featureIndex.bucketLayerIDs.length,
@@ -91,7 +92,8 @@ class WorkerTile {
91
92
  overscaling: this.overscaling,
92
93
  collisionBoxArray: this.collisionBoxArray,
93
94
  sourceLayerIndex: sourceLayerIndex,
94
- sourceID: this.source
95
+ sourceID: this.source,
96
+ globalState: this.globalState
95
97
  }));
96
98
 
97
99
  bucket.populate(features, options);
@@ -114,7 +116,7 @@ class WorkerTile {
114
116
  for (const key in buckets) {
115
117
  const bucket = buckets[key];
116
118
  if (bucket instanceof SymbolBucket) {
117
- recalculateLayers(bucket.layers, this.zoom);
119
+ recalculateLayers(bucket.layers, this.zoom, this.globalState);
118
120
  performSymbolLayout(
119
121
  bucket,
120
122
  glyphMap,
@@ -127,7 +129,7 @@ class WorkerTile {
127
129
  bucket.hasPattern &&
128
130
  (bucket instanceof LineBucket || bucket instanceof FillBucket || bucket instanceof FillExtrusionBucket)
129
131
  ) {
130
- recalculateLayers(bucket.layers, this.zoom);
132
+ recalculateLayers(bucket.layers, this.zoom, this.globalState);
131
133
  bucket.addFeatures(options, imageAtlas.patternPositions);
132
134
  }
133
135
  }
@@ -143,9 +145,9 @@ class WorkerTile {
143
145
  }
144
146
  }
145
147
 
146
- function recalculateLayers(layers, zoom) {
148
+ function recalculateLayers(layers, zoom, globalState) {
147
149
  // Layers are shared and may have been used by a WorkerTile with a different zoom.
148
- const parameters = new EvaluationParameters(zoom);
150
+ const parameters = new EvaluationParameters(zoom, { globalState });
149
151
  for (const layer of layers) {
150
152
  layer.recalculate(parameters);
151
153
  }
@@ -8,15 +8,17 @@ class EvaluationParameters {
8
8
  this.zoom = zoom;
9
9
 
10
10
  if (options) {
11
- this.now = options.now;
12
- this.fadeDuration = options.fadeDuration;
13
- this.zoomHistory = options.zoomHistory;
14
- this.transition = options.transition;
11
+ this.now = options.now || 0;
12
+ this.fadeDuration = options.fadeDuration || 0;
13
+ this.zoomHistory = options.zoomHistory || new ZoomHistory();
14
+ this.transition = options.transition || {};
15
+ this.globalState = options.globalState || {};
15
16
  } else {
16
17
  this.now = 0;
17
18
  this.fadeDuration = 0;
18
19
  this.zoomHistory = new ZoomHistory();
19
20
  this.transition = {};
21
+ this.globalState = {};
20
22
  }
21
23
  }
22
24
 
@@ -75,6 +75,10 @@ class PropertyValue {
75
75
  return this.expression.kind === 'source' || this.expression.kind === 'composite';
76
76
  }
77
77
 
78
+ getGlobalStateRefs() {
79
+ return this.expression.globalStateRefs ?? new Set();
80
+ }
81
+
78
82
  possiblyEvaluate(parameters) {
79
83
  return this.property.possiblyEvaluate(this, parameters);
80
84
  }
@@ -41,6 +41,7 @@ class Style extends Evented {
41
41
  this.sourceCaches = {};
42
42
  this.zoomHistory = new ZoomHistory();
43
43
  this._loaded = false;
44
+ this._globalState = {};
44
45
 
45
46
  this._resetUpdates();
46
47
 
@@ -78,6 +79,78 @@ class Style extends Evented {
78
79
  });
79
80
  }
80
81
 
82
+ setGlobalStateProperty(name, value) {
83
+ this._checkLoaded();
84
+
85
+ const newValue = value === null ? (this.stylesheet.state?.[name]?.default ?? null) : value;
86
+
87
+ if (deepEqual(newValue, this._globalState[name])) {
88
+ return this;
89
+ }
90
+
91
+ this._globalState[name] = newValue;
92
+
93
+ this._applyGlobalStateChanges([name]);
94
+ }
95
+
96
+ getGlobalState() {
97
+ return this._globalState;
98
+ }
99
+
100
+ setGlobalState(newStylesheetState) {
101
+ this._checkLoaded();
102
+
103
+ const changedGlobalStateRefs = [];
104
+
105
+ for (const propertyName in newStylesheetState) {
106
+ const didChange = !deepEqual(this._globalState[propertyName], newStylesheetState[propertyName].default);
107
+
108
+ if (didChange) {
109
+ changedGlobalStateRefs.push(propertyName);
110
+ this._globalState[propertyName] = newStylesheetState[propertyName].default;
111
+ }
112
+ }
113
+
114
+ this._applyGlobalStateChanges(changedGlobalStateRefs);
115
+ }
116
+
117
+ /**
118
+ * * Find all sources that are affected by the global state changes and reload them.
119
+ * Find all paint properties that are affected by the global state changes and update them.
120
+ * For example, if a layer filter uses global-state expression, this function will find the source id of that layer.
121
+ */
122
+ _applyGlobalStateChanges(globalStateRefs) {
123
+ if (globalStateRefs.length === 0) {
124
+ return;
125
+ }
126
+
127
+ const sourceIdsToReload = new Set();
128
+
129
+ for (const layerId in this._layers) {
130
+ const layer = this._layers[layerId];
131
+ const layoutAffectingGlobalStateRefs = layer.getLayoutAffectingGlobalStateRefs();
132
+ const paintAffectingGlobalStateRefs = layer.getPaintAffectingGlobalStateRefs();
133
+
134
+ for (const ref of globalStateRefs) {
135
+ if (layoutAffectingGlobalStateRefs.has(ref)) {
136
+ sourceIdsToReload.add(layer.source);
137
+ }
138
+ if (paintAffectingGlobalStateRefs.has(ref)) {
139
+ for (const { name, value } of paintAffectingGlobalStateRefs.get(ref)) {
140
+ this._updatePaintProperty(layer, name, value);
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ for (const id in this.sourceCaches) {
147
+ if (sourceIdsToReload.has(id)) {
148
+ this._reloadSource(id);
149
+ this._changed = true;
150
+ }
151
+ }
152
+ }
153
+
81
154
  loadJSON(json) {
82
155
  this.fire(new Event('dataloading', { dataType: 'style' }));
83
156
 
@@ -128,6 +201,8 @@ class Style extends Evented {
128
201
 
129
202
  this.light = new Light(this.stylesheet.light);
130
203
 
204
+ this.setGlobalState(this.stylesheet.state ?? null);
205
+
131
206
  this.fire(new Event('data', { dataType: 'style' }));
132
207
  this.fire(new Event('style.load'));
133
208
  }
@@ -547,12 +622,12 @@ class Style extends Evented {
547
622
  }
548
623
 
549
624
  if (filter === null || filter === undefined) {
550
- layer.filter = undefined;
625
+ layer.setFilter(undefined);
551
626
  this._updateLayer(layer);
552
627
  return;
553
628
  }
554
629
 
555
- layer.filter = clone(filter);
630
+ layer.setFilter(clone(filter));
556
631
  this._updateLayer(layer);
557
632
  }
558
633
 
@@ -605,13 +680,17 @@ class Style extends Evented {
605
680
 
606
681
  if (deepEqual(layer.getPaintProperty(name), value)) return;
607
682
 
683
+ this._updatePaintProperty(layer, name, value);
684
+ }
685
+
686
+ _updatePaintProperty(layer, name, value) {
608
687
  const requiresRelayout = layer.setPaintProperty(name, value);
609
688
  if (requiresRelayout) {
610
689
  this._updateLayer(layer);
611
690
  }
612
691
 
613
692
  this._changed = true;
614
- this._updatedPaintProps[layerId] = true;
693
+ this._updatedPaintProps[layer.id] = true;
615
694
  }
616
695
 
617
696
  getPaintProperty(layer, name) {
@@ -3,6 +3,7 @@ const { filterObject } = require('../util/object');
3
3
  const { Evented } = require('@mapwhit/events');
4
4
  const { Layout, Transitionable, PossiblyEvaluatedPropertyValue } = require('./properties');
5
5
  const { supportsPropertyExpression } = require('@mapwhit/style-expressions');
6
+ const featureFilter = require('../style-spec/feature_filter');
6
7
 
7
8
  const TRANSITION_SUFFIX = '-transition';
8
9
 
@@ -23,9 +24,10 @@ class StyleLayer extends Evented {
23
24
  this.source = layer.source;
24
25
  this.sourceLayer = layer['source-layer'];
25
26
  this.filter = layer.filter;
27
+ this._featureFilter = featureFilter(layer.filter);
26
28
  }
27
29
 
28
- this._featureFilter = () => true;
30
+ this._featureFilter ??= featureFilter.addGlobalStateRefs(() => true);
29
31
 
30
32
  if (properties.layout) {
31
33
  this._unevaluatedLayout = new Layout(properties.layout);
@@ -43,6 +45,11 @@ class StyleLayer extends Evented {
43
45
  this._transitioningPaint = this._transitionablePaint.untransitioned();
44
46
  }
45
47
 
48
+ setFilter(filter) {
49
+ this.filter = filter;
50
+ this._featureFilter = featureFilter(filter);
51
+ }
52
+
46
53
  getCrossfadeParameters() {
47
54
  return this._crossfadeParameters;
48
55
  }
@@ -55,6 +62,54 @@ class StyleLayer extends Evented {
55
62
  return this._unevaluatedLayout.getValue(name);
56
63
  }
57
64
 
65
+ /**
66
+ * Get list of global state references that are used within layout or filter properties.
67
+ * This is used to determine if layer source need to be reloaded when global state property changes.
68
+ *
69
+ */
70
+ getLayoutAffectingGlobalStateRefs() {
71
+ const globalStateRefs = new Set();
72
+
73
+ if (this._unevaluatedLayout) {
74
+ for (const propertyName in this._unevaluatedLayout._values) {
75
+ const value = this._unevaluatedLayout._values[propertyName];
76
+
77
+ for (const globalStateRef of value.getGlobalStateRefs()) {
78
+ globalStateRefs.add(globalStateRef);
79
+ }
80
+ }
81
+ }
82
+
83
+ for (const globalStateRef of this._featureFilter.getGlobalStateRefs()) {
84
+ globalStateRefs.add(globalStateRef);
85
+ }
86
+
87
+ return globalStateRefs;
88
+ }
89
+
90
+ /**
91
+ * Get list of global state references that are used within paint properties.
92
+ * This is used to determine if layer needs to be repainted when global state property changes.
93
+ *
94
+ */
95
+ getPaintAffectingGlobalStateRefs() {
96
+ const globalStateRefs = new Map();
97
+
98
+ if (this._transitionablePaint) {
99
+ for (const propertyName in this._transitionablePaint._values) {
100
+ const value = this._transitionablePaint._values[propertyName].value;
101
+
102
+ for (const globalStateRef of value.getGlobalStateRefs()) {
103
+ const properties = globalStateRefs.get(globalStateRef) ?? [];
104
+ properties.push({ name: propertyName, value: value.value });
105
+ globalStateRefs.set(globalStateRef, properties);
106
+ }
107
+ }
108
+ }
109
+
110
+ return globalStateRefs;
111
+ }
112
+
58
113
  setLayoutProperty(name, value) {
59
114
  if (name === 'visibility') {
60
115
  this.visibility = value === 'none' ? value : 'visible';
@@ -1,8 +1,9 @@
1
- const { createExpression } = require('@mapwhit/style-expressions');
1
+ const { createExpression, findGlobalStateRefs } = require('@mapwhit/style-expressions');
2
2
 
3
3
  module.exports = createFilter;
4
4
 
5
5
  createFilter.isExpressionFilter = isExpressionFilter;
6
+ createFilter.addGlobalStateRefs = addGlobalStateRefs;
6
7
 
7
8
  function isExpressionFilter(filter) {
8
9
  if (filter === true || filter === false) {
@@ -66,7 +67,7 @@ const filterSpec = {
66
67
  */
67
68
  function createFilter(filter) {
68
69
  if (filter === null || filter === undefined) {
69
- return () => true;
70
+ return addGlobalStateRefs(() => true);
70
71
  }
71
72
 
72
73
  if (!isExpressionFilter(filter)) {
@@ -77,7 +78,15 @@ function createFilter(filter) {
77
78
  if (compiled.result === 'error') {
78
79
  throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', '));
79
80
  }
80
- return (globalProperties, feature) => compiled.value.evaluate(globalProperties, feature);
81
+ return addGlobalStateRefs(
82
+ (globalProperties, feature) => compiled.value.evaluate(globalProperties, feature),
83
+ () => findGlobalStateRefs(compiled.value.expression)
84
+ );
85
+ }
86
+
87
+ function addGlobalStateRefs(filter, getGlobalStateRefs = () => new Set()) {
88
+ filter.getGlobalStateRefs = getGlobalStateRefs;
89
+ return filter;
81
90
  }
82
91
 
83
92
  // Comparison function to sort numbers and strings
@@ -86,7 +95,7 @@ function compare(a, b) {
86
95
  }
87
96
 
88
97
  function convertFilter(filter) {
89
- if (!filter) return true;
98
+ if (!filter || filter.length === 0) return true;
90
99
  const [op, ...args] = filter;
91
100
  if (filter.length <= 1) return op !== 'any';
92
101
  switch (op) {
@@ -48,6 +48,26 @@
48
48
  "doc": "Default pitch, in degrees. Zero is perpendicular to the surface, for a look straight down at the map, while a greater value like 60 looks ahead towards the horizon. The style pitch will be used only if the map has not been positioned by other means (e.g. map options or user interaction).",
49
49
  "example": 50
50
50
  },
51
+ "state": {
52
+ "type": "state",
53
+ "default": {},
54
+ "doc": "An object used to define default values when using the [`global-state`](https://maplibre.org/maplibre-style-spec/expressions/#global-state) expression.",
55
+ "example": {
56
+ "chargerType": {
57
+ "default": ["CCS", "CHAdeMO", "Type2"]
58
+ },
59
+ "minPreferredChargingSpeed": {
60
+ "default": 50
61
+ }
62
+ },
63
+ "sdk-support": {
64
+ "basic functionality": {
65
+ "js": "https://github.com/maplibre/maplibre-gl-js/issues/4964",
66
+ "android": "https://github.com/maplibre/maplibre-native/issues/3302",
67
+ "ios": "https://github.com/maplibre/maplibre-native/issues/3302"
68
+ }
69
+ }
70
+ },
51
71
  "light": {
52
72
  "type": "light",
53
73
  "doc": "The global light source.",
@@ -1293,8 +1313,8 @@
1293
1313
  },
1294
1314
  {
1295
1315
  "symbol-placement": [
1296
- "line",
1297
- "line-center"
1316
+ "line",
1317
+ "line-center"
1298
1318
  ]
1299
1319
  }
1300
1320
  ],
@@ -1816,10 +1836,10 @@
1816
1836
  "requires": [
1817
1837
  "text-field",
1818
1838
  {
1819
- "symbol-placement": [
1820
- "line",
1821
- "line-center"
1822
- ]
1839
+ "symbol-placement": [
1840
+ "line",
1841
+ "line-center"
1842
+ ]
1823
1843
  }
1824
1844
  ],
1825
1845
  "sdk-support": {
@@ -1907,10 +1927,10 @@
1907
1927
  "text-rotation-alignment": "map"
1908
1928
  },
1909
1929
  {
1910
- "symbol-placement": [
1911
- "line",
1912
- "line-center"
1913
- ]
1930
+ "symbol-placement": [
1931
+ "line",
1932
+ "line-center"
1933
+ ]
1914
1934
  }
1915
1935
  ],
1916
1936
  "sdk-support": {
@@ -2554,6 +2574,24 @@
2554
2574
  }
2555
2575
  }
2556
2576
  },
2577
+ "global-state": {
2578
+ "doc": "Retrieves a property value from global state that can be set with platform-specific APIs. Defaults can be provided using the [`state`](https://maplibre.org/maplibre-style-spec/root/#state) root property. Returns `null` if no value nor default value is set for the retrieved property.",
2579
+ "group": "Lookup",
2580
+ "example": {
2581
+ "syntax": {
2582
+ "method": ["string"],
2583
+ "result": "value"
2584
+ },
2585
+ "value": ["global-state", "someProperty"]
2586
+ },
2587
+ "sdk-support": {
2588
+ "basic functionality": {
2589
+ "js": "https://github.com/maplibre/maplibre-gl-js/issues/4964",
2590
+ "android": "https://github.com/maplibre/maplibre-native/issues/3302",
2591
+ "ios": "https://github.com/maplibre/maplibre-native/issues/3302"
2592
+ }
2593
+ }
2594
+ },
2557
2595
  "to-string": {
2558
2596
  "doc": "Converts the input value to a string. If the input is `null`, the result is `\"\"`. If the input is a boolean, the result is `\"true\"` or `\"false\"`. If the input is a number, it is converted to a string as specified by the [\"NumberToString\" algorithm](https://tc39.github.io/ecma262/#sec-tostring-applied-to-the-number-type) of the ECMAScript Language Specification. If the input is a color, it is converted to a string of the form `\"rgba(r,g,b,a)\"`, where `r`, `g`, and `b` are numerals ranging from 0 to 255, and `a` ranges from 0 to 1. Otherwise, the input is converted to a string in the format specified by the [`JSON.stringify`](https://tc39.github.io/ecma262/#sec-json.stringify) function of the ECMAScript Language Specification.",
2559
2597
  "group": "Types",
@@ -3722,7 +3760,9 @@
3722
3760
  },
3723
3761
  "expression": {
3724
3762
  "interpolated": false,
3725
- "parameters": ["zoom"]
3763
+ "parameters": [
3764
+ "zoom"
3765
+ ]
3726
3766
  },
3727
3767
  "property-type": "data-constant"
3728
3768
  }
@@ -5232,10 +5272,10 @@
5232
5272
  "type": "enum",
5233
5273
  "values": {
5234
5274
  "map": {
5235
- "doc": "The hillshade illumination is relative to the north direction."
5275
+ "doc": "The hillshade illumination is relative to the north direction."
5236
5276
  },
5237
5277
  "viewport": {
5238
- "doc": "The hillshade illumination is relative to the top of the viewport."
5278
+ "doc": "The hillshade illumination is relative to the top of the viewport."
5239
5279
  }
5240
5280
  },
5241
5281
  "default": "viewport",
package/src/ui/map.js CHANGED
@@ -201,6 +201,28 @@ class Map extends Camera {
201
201
  this.on('dataloading', this._onDataLoading);
202
202
  }
203
203
 
204
+ /**
205
+ * Sets a global state property that can be retrieved with the [`global-state` expression](https://maplibre.org/maplibre-style-spec/expressions/#global-state).
206
+ * If the value is null, it resets the property to its default value defined in the [`state` style property](https://maplibre.org/maplibre-style-spec/root/#state).
207
+ *
208
+ * @param propertyName - The name of the state property to set.
209
+ * @param value - The value of the state property to set.
210
+ */
211
+ setGlobalStateProperty(propertyName, value) {
212
+ if (!this.style.setGlobalStateProperty(propertyName, value)) {
213
+ return this._update(true);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Returns the global map state
219
+ *
220
+ * @returns The map state object.
221
+ */
222
+ getGlobalState() {
223
+ return this.style.getGlobalState();
224
+ }
225
+
204
226
  /**
205
227
  * Adds a {@link IControl} to the map, calling `control.onAdd(this)`.
206
228
  *
@@ -1248,7 +1270,8 @@ class Map extends Camera {
1248
1270
  now,
1249
1271
  fadeDuration: this._fadeDuration,
1250
1272
  zoomHistory: this.style.zoomHistory,
1251
- transition: this.style.getTransition()
1273
+ transition: this.style.getTransition(),
1274
+ globalState: this.style.getGlobalState()
1252
1275
  });
1253
1276
 
1254
1277
  const factor = parameters.crossFadingFactor();
@@ -27,6 +27,7 @@ function register(name, klass, { omit, shallow } = {}) {
27
27
  }
28
28
 
29
29
  register('Object', Object);
30
+ register('Set', Set);
30
31
 
31
32
  /**
32
33
  * Serialize the given object for transfer to or from a web worker.