@mapbox/mapbox-gl-style-spec 13.22.0-beta.1 → 13.23.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.
package/dist/index.cjs CHANGED
@@ -99,6 +99,21 @@
99
99
  delay: 0
100
100
  }
101
101
  },
102
+ projection: {
103
+ type: "projection",
104
+ doc: "The projection the map should be rendered in. Suported projections are Albers, Equal Earth, Equirectangular (WGS84), Lambert conformal conic, Mercator, Natural Earth, and Winkel Tripel. Terrain, fog, sky and CustomLayerInterface are not supported for projections other than mercator.",
105
+ example: {
106
+ name: "albers",
107
+ center: [
108
+ -154,
109
+ 50
110
+ ],
111
+ parallels: [
112
+ 55,
113
+ 65
114
+ ]
115
+ }
116
+ },
102
117
  layers: {
103
118
  required: true,
104
119
  type: "array",
@@ -657,7 +672,7 @@
657
672
  },
658
673
  filter: {
659
674
  type: "filter",
660
- doc: "A expression specifying conditions on source features. Only features that match the filter are displayed. Zoom expressions in filters are only evaluated at integer zoom levels. The `feature-state` expression is not supported in filter expressions."
675
+ doc: "An expression specifying conditions on source features. Only features that match the filter are displayed. Zoom expressions in filters are only evaluated at integer zoom levels. The `[\"feature-state\", ...]` expression is not supported in filter expressions. The `[\"pitch\"]` and `[\"distance-from-center\"]` expressions are supported only for filter expressions on the symbol layer."
661
676
  },
662
677
  layout: {
663
678
  type: "layout",
@@ -2481,6 +2496,78 @@
2481
2496
  value: "*",
2482
2497
  doc: "A filter selects specific features from a layer."
2483
2498
  };
2499
+ var filter_symbol = {
2500
+ type: "boolean",
2501
+ doc: "Expression which determines whether or not to display a symbol. Symbols support dynamic filtering, meaning this expression can use the `[\"pitch\"]` and `[\"distance-from-center\"]` expressions to reference the current state of the view.",
2502
+ "default": false,
2503
+ transition: false,
2504
+ "property-type": "data-driven",
2505
+ expression: {
2506
+ interpolated: false,
2507
+ parameters: [
2508
+ "zoom",
2509
+ "feature",
2510
+ "pitch",
2511
+ "distance-from-center"
2512
+ ]
2513
+ }
2514
+ };
2515
+ var filter_fill = {
2516
+ type: "boolean",
2517
+ doc: "Expression which determines whether or not to display a polygon. Fill layer does NOT support dynamic filtering, meaning this expression can NOT use the `[\"pitch\"]` and `[\"distance-from-center\"]` expressions to reference the current state of the view.",
2518
+ "default": false,
2519
+ transition: false,
2520
+ "property-type": "data-driven",
2521
+ expression: {
2522
+ interpolated: false,
2523
+ parameters: [
2524
+ "zoom",
2525
+ "feature"
2526
+ ]
2527
+ }
2528
+ };
2529
+ var filter_line = {
2530
+ type: "boolean",
2531
+ doc: "Expression which determines whether or not to display a Polygon or LineString. Line layer does NOT support dynamic filtering, meaning this expression can NOT use the `[\"pitch\"]` and `[\"distance-from-center\"]` expressions to reference the current state of the view.",
2532
+ "default": false,
2533
+ transition: false,
2534
+ "property-type": "data-driven",
2535
+ expression: {
2536
+ interpolated: false,
2537
+ parameters: [
2538
+ "zoom",
2539
+ "feature"
2540
+ ]
2541
+ }
2542
+ };
2543
+ var filter_circle = {
2544
+ type: "boolean",
2545
+ doc: "Expression which determines whether or not to display a circle. Circle layer does NOT support dynamic filtering, meaning this expression can NOT use the `[\"pitch\"]` and `[\"distance-from-center\"]` expressions to reference the current state of the view.",
2546
+ "default": false,
2547
+ transition: false,
2548
+ "property-type": "data-driven",
2549
+ expression: {
2550
+ interpolated: false,
2551
+ parameters: [
2552
+ "zoom",
2553
+ "feature"
2554
+ ]
2555
+ }
2556
+ };
2557
+ var filter_heatmap = {
2558
+ type: "boolean",
2559
+ doc: "Expression used to determine whether a point is being displayed or not. Heatmap layer does NOT support dynamic filtering, meaning this expression can NOT use the `[\"pitch\"]` and `[\"distance-from-center\"]` expressions to reference the current state of the view.",
2560
+ "default": false,
2561
+ transition: false,
2562
+ "property-type": "data-driven",
2563
+ expression: {
2564
+ interpolated: false,
2565
+ parameters: [
2566
+ "zoom",
2567
+ "feature"
2568
+ ]
2569
+ }
2570
+ };
2484
2571
  var filter_operator = {
2485
2572
  type: "enum",
2486
2573
  values: {
@@ -3010,7 +3097,7 @@
3010
3097
  }
3011
3098
  },
3012
3099
  length: {
3013
- doc: "Gets the length of an array or string.",
3100
+ doc: "Returns the length of an array or string.",
3014
3101
  group: "Lookup",
3015
3102
  "sdk-support": {
3016
3103
  "basic functionality": {
@@ -3022,7 +3109,7 @@
3022
3109
  }
3023
3110
  },
3024
3111
  properties: {
3025
- doc: "Gets the feature properties object. Note that in some cases, it may be more efficient to use [\"get\", \"property_name\"] directly.",
3112
+ doc: "Returns the feature properties object. Note that in some cases, it may be more efficient to use `[\"get\", \"property_name\"]` directly.",
3026
3113
  group: "Feature data",
3027
3114
  "sdk-support": {
3028
3115
  "basic functionality": {
@@ -3043,7 +3130,7 @@
3043
3130
  }
3044
3131
  },
3045
3132
  "geometry-type": {
3046
- doc: "Gets the feature's geometry type: `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`. `Multi*` feature types are only returned in GeoJSON sources. When working with vector tile sources, use the singular forms.",
3133
+ doc: "Returns the feature's geometry type: `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`. `Multi*` feature types are only returned in GeoJSON sources. When working with vector tile sources, use the singular forms.",
3047
3134
  group: "Feature data",
3048
3135
  "sdk-support": {
3049
3136
  "basic functionality": {
@@ -3055,7 +3142,7 @@
3055
3142
  }
3056
3143
  },
3057
3144
  id: {
3058
- doc: "Gets the feature's id, if it has one.",
3145
+ doc: "Returns the feature's id, if it has one.",
3059
3146
  group: "Feature data",
3060
3147
  "sdk-support": {
3061
3148
  "basic functionality": {
@@ -3067,8 +3154,8 @@
3067
3154
  }
3068
3155
  },
3069
3156
  zoom: {
3070
- doc: "Gets the current zoom level. Note that in style layout and paint properties, [\"zoom\"] may only appear as the input to a top-level \"step\" or \"interpolate\" expression.",
3071
- group: "Zoom",
3157
+ doc: "Returns the current zoom level. Note that in style layout and paint properties, [\"zoom\"] may only appear as the input to a top-level \"step\" or \"interpolate\" expression.",
3158
+ group: "Camera",
3072
3159
  "sdk-support": {
3073
3160
  "basic functionality": {
3074
3161
  js: "0.41.0",
@@ -3078,8 +3165,26 @@
3078
3165
  }
3079
3166
  }
3080
3167
  },
3168
+ pitch: {
3169
+ doc: "Returns the current pitch in degrees. `[\"pitch\"]` may only be used in the `filter` expression for a `symbol` layer.",
3170
+ group: "Camera",
3171
+ "sdk-support": {
3172
+ "basic functionality": {
3173
+ js: "2.6.0"
3174
+ }
3175
+ }
3176
+ },
3177
+ "distance-from-center": {
3178
+ doc: "Returns the distance of a `symbol` instance from the center of the map. The distance is measured in pixels divided by the height of the map container. It measures 0 at the center, decreases towards the camera and increase away from the camera. For example, if the height of the map is 1000px, a value of -1 means 1000px away from the center towards the camera, and a value of 1 means a distance of 1000px away from the camera from the center. `[\"distance-from-center\"]` may only be used in the `filter` expression for a `symbol` layer.",
3179
+ group: "Camera",
3180
+ "sdk-support": {
3181
+ "basic functionality": {
3182
+ js: "2.6.0"
3183
+ }
3184
+ }
3185
+ },
3081
3186
  "heatmap-density": {
3082
- doc: "Gets the kernel density estimation of a pixel in a heatmap layer, which is a relative measure of how many data points are crowded around a particular pixel. Can only be used in the `heatmap-color` property.",
3187
+ doc: "Returns the kernel density estimation of a pixel in a heatmap layer, which is a relative measure of how many data points are crowded around a particular pixel. Can only be used in the `heatmap-color` property.",
3083
3188
  group: "Heatmap",
3084
3189
  "sdk-support": {
3085
3190
  "basic functionality": {
@@ -3091,7 +3196,7 @@
3091
3196
  }
3092
3197
  },
3093
3198
  "line-progress": {
3094
- doc: "Gets the progress along a gradient line. Can only be used in the `line-gradient` property.",
3199
+ doc: "Returns the progress along a gradient line. Can only be used in the `line-gradient` property.",
3095
3200
  group: "Feature data",
3096
3201
  "sdk-support": {
3097
3202
  "basic functionality": {
@@ -3103,7 +3208,7 @@
3103
3208
  }
3104
3209
  },
3105
3210
  "sky-radial-progress": {
3106
- doc: "Gets the distance of a point on the sky from the sun position. Returns 0 at sun position and 1 when the distance reaches `sky-gradient-radius`. Can only be used in the `sky-gradient` property.",
3211
+ doc: "Returns the distance of a point on the sky from the sun position. Returns 0 at sun position and 1 when the distance reaches `sky-gradient-radius`. Can only be used in the `sky-gradient` property.",
3107
3212
  group: "sky",
3108
3213
  "sdk-support": {
3109
3214
  "basic functionality": {
@@ -3114,7 +3219,7 @@
3114
3219
  }
3115
3220
  },
3116
3221
  accumulated: {
3117
- doc: "Gets the value of a cluster property accumulated so far. Can only be used in the `clusterProperties` option of a clustered GeoJSON source.",
3222
+ doc: "Returns the value of a cluster property accumulated so far. Can only be used in the `clusterProperties` option of a clustered GeoJSON source.",
3118
3223
  group: "Feature data",
3119
3224
  "sdk-support": {
3120
3225
  "basic functionality": {
@@ -3793,6 +3898,92 @@
3793
3898
  }
3794
3899
  }
3795
3900
  };
3901
+ var projection = {
3902
+ name: {
3903
+ type: "enum",
3904
+ values: {
3905
+ albers: {
3906
+ doc: "An Albers equal-area projection centered on the continental United States. You can configure the projection for a different region by setting `center` and `parallels` properties. You may want to set max bounds to constrain the map to the relevant region."
3907
+ },
3908
+ equalEarth: {
3909
+ doc: "An Equal Earth projection."
3910
+ },
3911
+ equirectangular: {
3912
+ doc: "An Equirectangular projection. This projection is very similar to the Plate Carrée projection."
3913
+ },
3914
+ lambertConformalConic: {
3915
+ doc: "A Lambert conformal conic projection. You can configure the projection for a region by setting `center` and `parallels` properties. You may want to set max bounds to constrain the map to the relevant region."
3916
+ },
3917
+ mercator: {
3918
+ doc: "The Mercator projection is the default projection."
3919
+ },
3920
+ naturalEarth: {
3921
+ doc: "A Natural Earth projection."
3922
+ },
3923
+ winkelTripel: {
3924
+ doc: "A Winkel Tripel projection."
3925
+ }
3926
+ },
3927
+ "default": "mercator",
3928
+ doc: "The name of the projection to be used for rendering the map.",
3929
+ required: true,
3930
+ "sdk-support": {
3931
+ "basic functionality": {
3932
+ js: "2.6.0"
3933
+ }
3934
+ }
3935
+ },
3936
+ center: {
3937
+ type: "array",
3938
+ length: 2,
3939
+ value: "number",
3940
+ "property-type": "data-constant",
3941
+ transition: false,
3942
+ doc: "The reference longitude and latitude of the projection. `center` takes the form of [lng, lat]. This property is only configurable for conic projections (Albers and Lambert Conformal Conic). All other projections are centered on [0, 0].",
3943
+ example: [
3944
+ -96,
3945
+ 37.5
3946
+ ],
3947
+ requires: [
3948
+ {
3949
+ name: [
3950
+ "albers",
3951
+ "lambertConformalConic"
3952
+ ]
3953
+ }
3954
+ ],
3955
+ "sdk-support": {
3956
+ "basic functionality": {
3957
+ js: "2.6.0"
3958
+ }
3959
+ }
3960
+ },
3961
+ parallels: {
3962
+ type: "array",
3963
+ length: 2,
3964
+ value: "number",
3965
+ "property-type": "data-constant",
3966
+ transition: false,
3967
+ doc: "The standard parallels of the projection, denoting the desired latitude range with minimal distortion. `parallels` takes the form of [lat0, lat1]. This property is only configurable for conic projections (Albers and Lambert Conformal Conic).",
3968
+ example: [
3969
+ 29.5,
3970
+ 45.5
3971
+ ],
3972
+ requires: [
3973
+ {
3974
+ name: [
3975
+ "albers",
3976
+ "lambertConformalConic"
3977
+ ]
3978
+ }
3979
+ ],
3980
+ "sdk-support": {
3981
+ "basic functionality": {
3982
+ js: "2.6.0"
3983
+ }
3984
+ }
3985
+ }
3986
+ };
3796
3987
  var terrain = {
3797
3988
  source: {
3798
3989
  type: "string",
@@ -6048,6 +6239,25 @@
6048
6239
  layout_raster: layout_raster,
6049
6240
  layout_hillshade: layout_hillshade,
6050
6241
  filter: filter,
6242
+ filter_symbol: filter_symbol,
6243
+ filter_fill: filter_fill,
6244
+ filter_line: filter_line,
6245
+ filter_circle: filter_circle,
6246
+ "filter_fill-extrusion": {
6247
+ type: "boolean",
6248
+ doc: "Expression which determines whether or not to display a Polygon. Fill-extrusion layer does NOT support dynamic filtering, meaning this expression can NOT use the `[\"pitch\"]` and `[\"distance-from-center\"]` expressions to reference the current state of the view.",
6249
+ "default": false,
6250
+ transition: false,
6251
+ "property-type": "data-driven",
6252
+ expression: {
6253
+ interpolated: false,
6254
+ parameters: [
6255
+ "zoom",
6256
+ "feature"
6257
+ ]
6258
+ }
6259
+ },
6260
+ filter_heatmap: filter_heatmap,
6051
6261
  filter_operator: filter_operator,
6052
6262
  geometry_type: geometry_type,
6053
6263
  "function": {
@@ -6117,6 +6327,7 @@
6117
6327
  expression_name: expression_name,
6118
6328
  fog: fog,
6119
6329
  light: light,
6330
+ projection: projection,
6120
6331
  terrain: terrain,
6121
6332
  paint: paint,
6122
6333
  paint_fill: paint_fill,
@@ -9060,6 +9271,8 @@
9060
9271
  this._parseColorCache = {};
9061
9272
  this.availableImages = null;
9062
9273
  this.canonical = null;
9274
+ this.featureTileCoord = null;
9275
+ this.featureDistanceData = null;
9063
9276
  }
9064
9277
  id() {
9065
9278
  return this.feature && 'id' in this.feature ? this.feature.id : null;
@@ -9076,6 +9289,20 @@
9076
9289
  properties() {
9077
9290
  return this.feature && this.feature.properties || {};
9078
9291
  }
9292
+ distanceFromCenter() {
9293
+ if (this.featureTileCoord && this.featureDistanceData) {
9294
+ const c = this.featureDistanceData.center;
9295
+ const scale = this.featureDistanceData.scale;
9296
+ const {x, y} = this.featureTileCoord;
9297
+ const dX = x * scale - c[0];
9298
+ const dY = y * scale - c[1];
9299
+ const bX = this.featureDistanceData.bearing[0];
9300
+ const bY = this.featureDistanceData.bearing[1];
9301
+ const dist = bX * dX + bY * dY;
9302
+ return dist;
9303
+ }
9304
+ return 0;
9305
+ }
9079
9306
  parseColor(input) {
9080
9307
  let cached = this._parseColorCache[input];
9081
9308
  if (!cached) {
@@ -9769,7 +9996,9 @@
9769
9996
  'line-progress',
9770
9997
  'sky-radial-progress',
9771
9998
  'accumulated',
9772
- 'is-supported-script'
9999
+ 'is-supported-script',
10000
+ 'pitch',
10001
+ 'distance-from-center'
9773
10002
  ]);
9774
10003
  }
9775
10004
 
@@ -11251,6 +11480,16 @@
11251
11480
  [],
11252
11481
  ctx => ctx.globals.zoom
11253
11482
  ],
11483
+ 'pitch': [
11484
+ NumberType,
11485
+ [],
11486
+ ctx => ctx.globals.pitch || 0
11487
+ ],
11488
+ 'distance-from-center': [
11489
+ NumberType,
11490
+ [],
11491
+ ctx => ctx.distanceFromCenter()
11492
+ ],
11254
11493
  'heatmap-density': [
11255
11494
  NumberType,
11256
11495
  [],
@@ -11897,22 +12136,26 @@
11897
12136
  this._defaultValue = propertySpec ? getDefaultValue(propertySpec) : null;
11898
12137
  this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null;
11899
12138
  }
11900
- evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) {
12139
+ evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection, featureTileCoord, featureDistanceData) {
11901
12140
  this._evaluator.globals = globals;
11902
12141
  this._evaluator.feature = feature;
11903
12142
  this._evaluator.featureState = featureState;
11904
12143
  this._evaluator.canonical = canonical;
11905
12144
  this._evaluator.availableImages = availableImages || null;
11906
12145
  this._evaluator.formattedSection = formattedSection;
12146
+ this._evaluator.featureTileCoord = featureTileCoord || null;
12147
+ this._evaluator.featureDistanceData = featureDistanceData || null;
11907
12148
  return this.expression.evaluate(this._evaluator);
11908
12149
  }
11909
- evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) {
12150
+ evaluate(globals, feature, featureState, canonical, availableImages, formattedSection, featureTileCoord, featureDistanceData) {
11910
12151
  this._evaluator.globals = globals;
11911
12152
  this._evaluator.feature = feature || null;
11912
12153
  this._evaluator.featureState = featureState || null;
11913
12154
  this._evaluator.canonical = canonical;
11914
12155
  this._evaluator.availableImages = availableImages || null;
11915
12156
  this._evaluator.formattedSection = formattedSection || null;
12157
+ this._evaluator.featureTileCoord = featureTileCoord || null;
12158
+ this._evaluator.featureDistanceData = featureDistanceData || null;
11916
12159
  try {
11917
12160
  const val = this.expression.evaluate(this._evaluator);
11918
12161
  if (val === null || val === undefined || typeof val === 'number' && val !== val) {
@@ -11989,7 +12232,11 @@
11989
12232
  if (!isFeatureConstant$1 && !supportsPropertyExpression(propertySpec)) {
11990
12233
  return error([new ParsingError('', 'data expressions not supported')]);
11991
12234
  }
11992
- const isZoomConstant = isGlobalPropertyConstant(parsed, ['zoom']);
12235
+ const isZoomConstant = isGlobalPropertyConstant(parsed, [
12236
+ 'zoom',
12237
+ 'pitch',
12238
+ 'distance-from-center'
12239
+ ]);
11993
12240
  if (!isZoomConstant && !supportsZoomExpression(propertySpec)) {
11994
12241
  return error([new ParsingError('', 'zoom expressions not supported')]);
11995
12242
  }
@@ -12397,6 +12644,26 @@
12397
12644
  return result;
12398
12645
  }
12399
12646
 
12647
+ function unbundle(value) {
12648
+ if (value instanceof Number || value instanceof String || value instanceof Boolean) {
12649
+ return value.valueOf();
12650
+ } else {
12651
+ return value;
12652
+ }
12653
+ }
12654
+ function deepUnbundle(value) {
12655
+ if (Array.isArray(value)) {
12656
+ return value.map(deepUnbundle);
12657
+ } else if (value instanceof Object && !(value instanceof Number || value instanceof String || value instanceof Boolean)) {
12658
+ const unbundledValue = {};
12659
+ for (const key in value) {
12660
+ unbundledValue[key] = deepUnbundle(value[key]);
12661
+ }
12662
+ return unbundledValue;
12663
+ }
12664
+ return unbundle(value);
12665
+ }
12666
+
12400
12667
  function isExpressionFilter(filter) {
12401
12668
  if (filter === true || filter === false) {
12402
12669
  return true;
@@ -12432,39 +12699,145 @@
12432
12699
  return true;
12433
12700
  }
12434
12701
  }
12435
- const filterSpec = {
12436
- 'type': 'boolean',
12437
- 'default': false,
12438
- 'transition': false,
12439
- 'property-type': 'data-driven',
12440
- 'expression': {
12441
- 'interpolated': false,
12442
- 'parameters': [
12443
- 'zoom',
12444
- 'feature'
12445
- ]
12446
- }
12447
- };
12448
- function createFilter(filter) {
12702
+ function createFilter(filter, layerType = 'fill') {
12449
12703
  if (filter === null || filter === undefined) {
12450
12704
  return {
12451
12705
  filter: () => true,
12452
- needGeometry: false
12706
+ needGeometry: false,
12707
+ needFeature: false
12453
12708
  };
12454
12709
  }
12455
12710
  if (!isExpressionFilter(filter)) {
12456
12711
  filter = convertFilter(filter);
12457
12712
  }
12458
- const compiled = createExpression(filter, filterSpec);
12459
- if (compiled.result === 'error') {
12460
- throw new Error(compiled.value.map(err => `${ err.key }: ${ err.message }`).join(', '));
12713
+ const filterExp = filter;
12714
+ let staticFilter = true;
12715
+ try {
12716
+ staticFilter = extractStaticFilter(filterExp);
12717
+ } catch (e) {
12718
+ console.warn(`Failed to extract static filter. Filter will continue working, but at higher memory usage and slower framerate.
12719
+ This is most likely a bug, please report this via https://github.com/mapbox/mapbox-gl-js/issues/new?assignees=&labels=&template=Bug_report.md
12720
+ and paste the contents of this message in the report.
12721
+ Thank you!
12722
+ Filter Expression:
12723
+ ${ JSON.stringify(filterExp, null, 2) }
12724
+ `);
12725
+ }
12726
+ const filterSpec = v8[`filter_${ layerType }`];
12727
+ const compiledStaticFilter = createExpression(staticFilter, filterSpec);
12728
+ let filterFunc = null;
12729
+ if (compiledStaticFilter.result === 'error') {
12730
+ throw new Error(compiledStaticFilter.value.map(err => `${ err.key }: ${ err.message }`).join(', '));
12461
12731
  } else {
12462
- const needGeometry = geometryNeeded(filter);
12463
- return {
12464
- filter: (globalProperties, feature, canonical) => compiled.value.evaluate(globalProperties, feature, {}, canonical),
12465
- needGeometry
12466
- };
12732
+ filterFunc = (globalProperties, feature, canonical) => compiledStaticFilter.value.evaluate(globalProperties, feature, {}, canonical);
12733
+ }
12734
+ let dynamicFilterFunc = null;
12735
+ let needFeature = null;
12736
+ if (staticFilter !== filterExp) {
12737
+ const compiledDynamicFilter = createExpression(filterExp, filterSpec);
12738
+ if (compiledDynamicFilter.result === 'error') {
12739
+ throw new Error(compiledDynamicFilter.value.map(err => `${ err.key }: ${ err.message }`).join(', '));
12740
+ } else {
12741
+ dynamicFilterFunc = (globalProperties, feature, canonical, featureTileCoord, featureDistanceData) => compiledDynamicFilter.value.evaluate(globalProperties, feature, {}, canonical, undefined, undefined, featureTileCoord, featureDistanceData);
12742
+ needFeature = !isFeatureConstant(compiledDynamicFilter.value.expression);
12743
+ }
12467
12744
  }
12745
+ filterFunc = filterFunc;
12746
+ const needGeometry = geometryNeeded(staticFilter);
12747
+ return {
12748
+ filter: filterFunc,
12749
+ dynamicFilter: dynamicFilterFunc ? dynamicFilterFunc : undefined,
12750
+ needGeometry,
12751
+ needFeature: !!needFeature
12752
+ };
12753
+ }
12754
+ function extractStaticFilter(filter) {
12755
+ if (!isDynamicFilter(filter)) {
12756
+ return filter;
12757
+ }
12758
+ let result = deepUnbundle(filter);
12759
+ unionDynamicBranches(result);
12760
+ result = collapseDynamicBooleanExpressions(result);
12761
+ return result;
12762
+ }
12763
+ function collapseDynamicBooleanExpressions(expression) {
12764
+ if (!Array.isArray(expression)) {
12765
+ return expression;
12766
+ }
12767
+ const collapsed = collapsedExpression(expression);
12768
+ if (collapsed === true) {
12769
+ return collapsed;
12770
+ } else {
12771
+ return collapsed.map(subExpression => collapseDynamicBooleanExpressions(subExpression));
12772
+ }
12773
+ }
12774
+ function unionDynamicBranches(filter) {
12775
+ let isBranchingDynamically = false;
12776
+ const branches = [];
12777
+ if (filter[0] === 'case') {
12778
+ for (let i = 1; i < filter.length - 1; i += 2) {
12779
+ isBranchingDynamically = isBranchingDynamically || isDynamicFilter(filter[i]);
12780
+ branches.push(filter[i + 1]);
12781
+ }
12782
+ branches.push(filter[filter.length - 1]);
12783
+ } else if (filter[0] === 'match') {
12784
+ isBranchingDynamically = isBranchingDynamically || isDynamicFilter(filter[1]);
12785
+ for (let i = 2; i < filter.length - 1; i += 2) {
12786
+ branches.push(filter[i + 1]);
12787
+ }
12788
+ branches.push(filter[filter.length - 1]);
12789
+ } else if (filter[0] === 'step') {
12790
+ isBranchingDynamically = isBranchingDynamically || isDynamicFilter(filter[1]);
12791
+ for (let i = 1; i < filter.length - 1; i += 2) {
12792
+ branches.push(filter[i + 1]);
12793
+ }
12794
+ }
12795
+ if (isBranchingDynamically) {
12796
+ filter.length = 0;
12797
+ filter.push('any', ...branches);
12798
+ }
12799
+ for (let i = 1; i < filter.length; i++) {
12800
+ unionDynamicBranches(filter[i]);
12801
+ }
12802
+ }
12803
+ function isDynamicFilter(filter) {
12804
+ if (!Array.isArray(filter)) {
12805
+ return false;
12806
+ }
12807
+ if (isRootExpressionDynamic(filter[0])) {
12808
+ return true;
12809
+ }
12810
+ for (let i = 1; i < filter.length; i++) {
12811
+ const child = filter[i];
12812
+ if (isDynamicFilter(child)) {
12813
+ return true;
12814
+ }
12815
+ }
12816
+ return false;
12817
+ }
12818
+ function isRootExpressionDynamic(expression) {
12819
+ return expression === 'pitch' || expression === 'distance-from-center';
12820
+ }
12821
+ const dynamicConditionExpressions = new Set([
12822
+ 'in',
12823
+ '==',
12824
+ '!=',
12825
+ '>',
12826
+ '>=',
12827
+ '<',
12828
+ '<=',
12829
+ 'to-boolean'
12830
+ ]);
12831
+ function collapsedExpression(expression) {
12832
+ if (dynamicConditionExpressions.has(expression[0])) {
12833
+ for (let i = 1; i < expression.length; i++) {
12834
+ const param = expression[i];
12835
+ if (isDynamicFilter(param)) {
12836
+ return true;
12837
+ }
12838
+ }
12839
+ }
12840
+ return expression;
12468
12841
  }
12469
12842
  function compare(a, b) {
12470
12843
  return a < b ? -1 : a > b ? 1 : 0;
@@ -12923,7 +13296,8 @@
12923
13296
  setTransition: 'setTransition',
12924
13297
  setLight: 'setLight',
12925
13298
  setTerrain: 'setTerrain',
12926
- setFog: 'setFog'
13299
+ setFog: 'setFog',
13300
+ setProjection: 'setProjection'
12927
13301
  };
12928
13302
  function addSource(sourceId, after, commands) {
12929
13303
  commands.push({
@@ -13230,6 +13604,12 @@
13230
13604
  args: [after.fog]
13231
13605
  });
13232
13606
  }
13607
+ if (!deepEqual(before.projection, after.projection)) {
13608
+ commands.push({
13609
+ command: operations.setProjection,
13610
+ args: [after.projection]
13611
+ });
13612
+ }
13233
13613
  const sourcesRemoved = {};
13234
13614
  const removeOrAddSourceCommands = [];
13235
13615
  diffSources(before.sources, after.sources, removeOrAddSourceCommands, sourcesRemoved);
@@ -13304,26 +13684,6 @@
13304
13684
  }
13305
13685
  }
13306
13686
 
13307
- function unbundle(value) {
13308
- if (value instanceof Number || value instanceof String || value instanceof Boolean) {
13309
- return value.valueOf();
13310
- } else {
13311
- return value;
13312
- }
13313
- }
13314
- function deepUnbundle(value) {
13315
- if (Array.isArray(value)) {
13316
- return value.map(deepUnbundle);
13317
- } else if (value instanceof Object && !(value instanceof Number || value instanceof String || value instanceof Boolean)) {
13318
- const unbundledValue = {};
13319
- for (const key in value) {
13320
- unbundledValue[key] = deepUnbundle(value[key]);
13321
- }
13322
- return unbundledValue;
13323
- }
13324
- return unbundle(value);
13325
- }
13326
-
13327
13687
  function validateObject(options) {
13328
13688
  const key = options.key;
13329
13689
  const object = options.value;
@@ -13628,8 +13988,8 @@
13628
13988
  if (options.expressionContext === 'property' && options.propertyType === 'layout' && !isStateConstant(expressionObj)) {
13629
13989
  return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with layout properties.')];
13630
13990
  }
13631
- if (options.expressionContext === 'filter' && !isStateConstant(expressionObj)) {
13632
- return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with filters.')];
13991
+ if (options.expressionContext === 'filter') {
13992
+ return disallowedFilterParameters(expressionObj, options);
13633
13993
  }
13634
13994
  if (options.expressionContext && options.expressionContext.indexOf('cluster') === 0) {
13635
13995
  if (!isGlobalPropertyConstant(expressionObj, [
@@ -13644,6 +14004,30 @@
13644
14004
  }
13645
14005
  return [];
13646
14006
  }
14007
+ function disallowedFilterParameters(e, options) {
14008
+ const disallowedParameters = new Set([
14009
+ 'zoom',
14010
+ 'feature-state',
14011
+ 'pitch',
14012
+ 'distance-from-center'
14013
+ ]);
14014
+ for (const param of options.valueSpec.expression.parameters) {
14015
+ disallowedParameters.delete(param);
14016
+ }
14017
+ if (disallowedParameters.size === 0) {
14018
+ return [];
14019
+ }
14020
+ const errors = [];
14021
+ if (e instanceof CompoundExpression) {
14022
+ if (disallowedParameters.has(e.name)) {
14023
+ return [new ValidationError(options.key, options.value, `["${ e.name }"] expression is not supported in a filter for a ${ options.object.type } layer with id: ${ options.object.id }`)];
14024
+ }
14025
+ }
14026
+ e.eachChild(arg => {
14027
+ errors.push(...disallowedFilterParameters(arg, options));
14028
+ });
14029
+ return errors;
14030
+ }
13647
14031
 
13648
14032
  function validateBoolean(options) {
13649
14033
  const value = options.value;
@@ -13687,9 +14071,10 @@
13687
14071
 
13688
14072
  function validateFilter(options) {
13689
14073
  if (isExpressionFilter(deepUnbundle(options.value))) {
14074
+ const layerType = deepUnbundle(options.layerType);
13690
14075
  return validateExpression(extend({}, options, {
13691
14076
  expressionContext: 'filter',
13692
- valueSpec: { value: 'boolean' }
14077
+ valueSpec: options.styleSpec[`filter_${ layerType || 'fill' }`]
13693
14078
  }));
13694
14079
  } else {
13695
14080
  return validateNonExpressionFilter(options);
@@ -13925,7 +14310,9 @@
13925
14310
  objectKey: 'type'
13926
14311
  });
13927
14312
  },
13928
- filter: validateFilter,
14313
+ filter(options) {
14314
+ return validateFilter(extend({ layerType: type }, options));
14315
+ },
13929
14316
  layout(options) {
13930
14317
  return validateObject({
13931
14318
  layer,
@@ -14221,6 +14608,29 @@
14221
14608
  return validateExpression(options);
14222
14609
  }
14223
14610
 
14611
+ function validateProjection(options) {
14612
+ const projection = options.value;
14613
+ const styleSpec = options.styleSpec;
14614
+ const projectionSpec = styleSpec.projection;
14615
+ const style = options.style;
14616
+ let errors = [];
14617
+ const rootType = getType(projection);
14618
+ if (rootType === 'object') {
14619
+ for (const key in projection) {
14620
+ errors = errors.concat(validate({
14621
+ key,
14622
+ value: projection[key],
14623
+ valueSpec: projectionSpec[key],
14624
+ style,
14625
+ styleSpec
14626
+ }));
14627
+ }
14628
+ } else if (rootType !== 'string') {
14629
+ errors = errors.concat([new ValidationError('projection', projection, `object or string expected, ${ rootType } found`)]);
14630
+ }
14631
+ return errors;
14632
+ }
14633
+
14224
14634
  const VALIDATORS = {
14225
14635
  '*'() {
14226
14636
  return [];
@@ -14241,7 +14651,8 @@
14241
14651
  'fog': validateFog,
14242
14652
  'string': validateString,
14243
14653
  'formatted': validateFormatted,
14244
- 'resolvedImage': validateImage
14654
+ 'resolvedImage': validateImage,
14655
+ 'projection': validateProjection
14245
14656
  };
14246
14657
  function validate(options) {
14247
14658
  const value = options.value;