@mapwhit/tilerenderer 0.51.1 → 0.52.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.
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.51.0"
2
+ "version": "0.51.1"
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.0",
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);
@@ -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
 
@@ -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
 
@@ -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
 
@@ -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)) {
@@ -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, { globalState: 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, { globalState: 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, { globalState: 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, options) {
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, options);
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,80 @@ 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
+ const sourceIdsToReload = this._findGlobalStateAffectedSources([name]);
94
+
95
+ for (const id in this.sourceCaches) {
96
+ if (sourceIdsToReload.has(id)) {
97
+ this._reloadSource(id);
98
+ this._changed = true;
99
+ }
100
+ }
101
+ }
102
+
103
+ getGlobalState() {
104
+ return this._globalState;
105
+ }
106
+
107
+ setGlobalState(newStylesheetState) {
108
+ this._checkLoaded();
109
+
110
+ const changedGlobalStateRefs = [];
111
+
112
+ for (const propertyName in newStylesheetState) {
113
+ const didChange = !deepEqual(this._globalState[propertyName], newStylesheetState[propertyName].default);
114
+
115
+ if (didChange) {
116
+ changedGlobalStateRefs.push(propertyName);
117
+ this._globalState[propertyName] = newStylesheetState[propertyName].default;
118
+ }
119
+ }
120
+
121
+ const sourceIdsToReload = this._findGlobalStateAffectedSources(changedGlobalStateRefs);
122
+
123
+ for (const id in this.sourceCaches) {
124
+ if (sourceIdsToReload.has(id)) {
125
+ this._reloadSource(id);
126
+ this._changed = true;
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Find all sources that are affected by the global state changes.
133
+ * For example, if a layer filter uses global-state expression, this function will return the source id of that layer.
134
+ */
135
+ _findGlobalStateAffectedSources(globalStateRefs) {
136
+ if (globalStateRefs.length === 0) {
137
+ return new Set();
138
+ }
139
+
140
+ const sourceIdsToReload = new Set();
141
+
142
+ for (const layerId in this._layers) {
143
+ const layer = this._layers[layerId];
144
+ const layoutAffectingGlobalStateRefs = layer.getLayoutAffectingGlobalStateRefs();
145
+
146
+ for (const ref of globalStateRefs) {
147
+ if (layoutAffectingGlobalStateRefs.has(ref)) {
148
+ sourceIdsToReload.add(layer.source);
149
+ }
150
+ }
151
+ }
152
+
153
+ return sourceIdsToReload;
154
+ }
155
+
81
156
  loadJSON(json) {
82
157
  this.fire(new Event('dataloading', { dataType: 'style' }));
83
158
 
@@ -128,6 +203,8 @@ class Style extends Evented {
128
203
 
129
204
  this.light = new Light(this.stylesheet.light);
130
205
 
206
+ this.setGlobalState(this.stylesheet.state ?? null);
207
+
131
208
  this.fire(new Event('data', { dataType: 'style' }));
132
209
  this.fire(new Event('style.load'));
133
210
  }
@@ -547,12 +624,12 @@ class Style extends Evented {
547
624
  }
548
625
 
549
626
  if (filter === null || filter === undefined) {
550
- layer.filter = undefined;
627
+ layer.setFilter(undefined);
551
628
  this._updateLayer(layer);
552
629
  return;
553
630
  }
554
631
 
555
- layer.filter = clone(filter);
632
+ layer.setFilter(clone(filter));
556
633
  this._updateLayer(layer);
557
634
  }
558
635
 
@@ -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,31 @@ 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
+
58
90
  setLayoutProperty(name, value) {
59
91
  if (name === 'visibility') {
60
92
  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,29 @@ 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
+ * Note that changing `global-state` values defined in layout properties is not supported, and will be ignored.
209
+ *
210
+ * @param propertyName - The name of the state property to set.
211
+ * @param value - The value of the state property to set.
212
+ */
213
+ setGlobalStateProperty(propertyName, value) {
214
+ this.style.setGlobalStateProperty(propertyName, value);
215
+ return this._update(true);
216
+ }
217
+
218
+ /**
219
+ * Returns the global map state
220
+ *
221
+ * @returns The map state object.
222
+ */
223
+ getGlobalState() {
224
+ return this.style.getGlobalState();
225
+ }
226
+
204
227
  /**
205
228
  * Adds a {@link IControl} to the map, calling `control.onAdd(this)`.
206
229
  *
@@ -1248,7 +1271,8 @@ class Map extends Camera {
1248
1271
  now,
1249
1272
  fadeDuration: this._fadeDuration,
1250
1273
  zoomHistory: this.style.zoomHistory,
1251
- transition: this.style.getTransition()
1274
+ transition: this.style.getTransition(),
1275
+ globalState: this.style.getGlobalState()
1252
1276
  });
1253
1277
 
1254
1278
  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.