@mapbox/mapbox-gl-style-spec 14.28.0 → 14.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/composite.ts +5 -5
  2. package/diff.ts +38 -40
  3. package/dist/index.cjs +362 -238
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +101 -13
  6. package/dist/index.es.js +362 -238
  7. package/dist/index.es.js.map +1 -1
  8. package/expression/compound_expression.ts +6 -6
  9. package/expression/definitions/assertion.ts +9 -12
  10. package/expression/definitions/at.ts +1 -1
  11. package/expression/definitions/at_interpolated.ts +1 -1
  12. package/expression/definitions/case.ts +1 -2
  13. package/expression/definitions/coalesce.ts +1 -1
  14. package/expression/definitions/coercion.ts +10 -12
  15. package/expression/definitions/collator.ts +8 -8
  16. package/expression/definitions/comparison.ts +0 -1
  17. package/expression/definitions/distance.ts +135 -73
  18. package/expression/definitions/format.ts +10 -13
  19. package/expression/definitions/image.ts +8 -8
  20. package/expression/definitions/index.ts +92 -90
  21. package/expression/definitions/interpolate.ts +18 -19
  22. package/expression/definitions/let.ts +1 -2
  23. package/expression/definitions/literal.ts +1 -1
  24. package/expression/definitions/match.ts +7 -8
  25. package/expression/definitions/number_format.ts +7 -8
  26. package/expression/definitions/step.ts +11 -11
  27. package/expression/definitions/within.ts +23 -24
  28. package/expression/evaluation_context.ts +4 -4
  29. package/expression/expression.ts +1 -1
  30. package/expression/index.ts +43 -21
  31. package/expression/parsing_context.ts +2 -2
  32. package/expression/stops.ts +2 -2
  33. package/expression/values.ts +2 -2
  34. package/feature_filter/index.ts +3 -3
  35. package/function/convert.ts +8 -8
  36. package/function/index.ts +4 -2
  37. package/group_by_layout.ts +6 -4
  38. package/package.json +1 -1
  39. package/read_style.ts +2 -2
  40. package/reference/v8.json +86 -5
  41. package/types/config_options.ts +1 -1
  42. package/types.ts +16 -6
  43. package/util/properties.ts +1 -1
  44. package/validate/validate.ts +15 -5
  45. package/validate/validate_appearance.ts +7 -7
  46. package/validate/validate_array.ts +7 -7
  47. package/validate/validate_enum.ts +2 -3
  48. package/validate/validate_fog.ts +2 -2
  49. package/validate/validate_function.ts +14 -13
  50. package/validate/validate_layer.ts +10 -9
  51. package/validate/validate_light.ts +2 -2
  52. package/validate/validate_lights.ts +3 -3
  53. package/validate/validate_number.ts +10 -7
  54. package/validate/validate_object.ts +11 -10
  55. package/validate/validate_option.ts +24 -9
  56. package/validate/validate_property.ts +4 -4
  57. package/validate/validate_rain.ts +1 -1
  58. package/validate/validate_snow.ts +1 -1
  59. package/validate/validate_style.ts +2 -1
  60. package/validate/validate_terrain.ts +2 -2
  61. package/validate_mapbox_api_supported.ts +5 -6
  62. package/validate_style.min.ts +2 -1
  63. package/visit.ts +11 -10
@@ -1,9 +1,11 @@
1
1
  import {isValue} from '../values';
2
- import {NumberType} from '../types';
2
+ import {NumberType, ValueType} from '../types';
3
3
  import {classifyRings, updateBBox, boxWithinBox, pointWithinPolygon, segmentIntersectSegment} from '../../util/geometry_util';
4
4
  import {lngFromMercatorX, latFromMercatorY} from '../../util/mercator';
5
5
  import TinyQueue from "tinyqueue";
6
6
  import EXTENT from '../../data/extent';
7
+ import Literal from './literal';
8
+ import {isGlobalPropertyConstant, isStateConstant} from '../is_constant';
7
9
 
8
10
  // Geodesic scale factors (cheap-ruler math): meters per degree lon/lat at a given latitude.
9
11
  // Only the three distance operations used in this file are implemented.
@@ -56,21 +58,21 @@ function rulerPointToSegmentDistance(p: [number, number], a: [number, number], b
56
58
 
57
59
  function rulerPointOnLine(line: Array<[number, number]>, p: [number, number], kx: number, ky: number): [number, number] {
58
60
  let minDist = Infinity;
59
- let minX = line[0][0];
60
- let minY = line[0][1];
61
+ let minX = line[0]![0];
62
+ let minY = line[0]![1];
61
63
 
62
64
  for (let i = 0; i < line.length - 1; i++) {
63
- let x = line[i][0];
64
- let y = line[i][1];
65
- let dx = wrapLng(line[i + 1][0] - x) * kx;
66
- let dy = (line[i + 1][1] - y) * ky;
65
+ let x = line[i]![0];
66
+ let y = line[i]![1];
67
+ let dx = wrapLng(line[i + 1]![0] - x) * kx;
68
+ let dy = (line[i + 1]![1] - y) * ky;
67
69
  let t = 0;
68
70
 
69
71
  if (dx !== 0 || dy !== 0) {
70
72
  t = (wrapLng(p[0] - x) * kx * dx + (p[1] - y) * ky * dy) / (dx * dx + dy * dy);
71
73
  if (t > 1) {
72
- x = line[i + 1][0];
73
- y = line[i + 1][1];
74
+ x = line[i + 1]![0];
75
+ y = line[i + 1]![1];
74
76
  } else if (t > 0) {
75
77
  x += (dx / kx) * t;
76
78
  y += (dy / ky) * t;
@@ -142,7 +144,7 @@ function isRangeSafe(range: IndexRange, threshold: number) {
142
144
  // Split the point set(points or linestring) into two halves, using IndexRange to do in-place splitting.
143
145
  // If geometry is a line, the last point(here is the second index) of range1 needs to be included as the first point(here is the first index) of range2.
144
146
  // If geometry are points, just split the points equally(if possible) into two new point sets(here are two index ranges).
145
- function splitRange(range: IndexRange, isLine: boolean) {
147
+ function splitRange(range: IndexRange, isLine: boolean): [IndexRange | null, IndexRange | null] {
146
148
  if (range[0] > range[1]) return [null, null];
147
149
  const size = getRangeSize(range);
148
150
  if (isLine) {
@@ -168,7 +170,7 @@ function getBBox(pointSets: Array<[number, number]>, range: IndexRange) {
168
170
  const bbox: BBox = [Infinity, Infinity, -Infinity, -Infinity];
169
171
  if (!isRangeSafe(range, pointSets.length)) return bbox;
170
172
  for (let i = range[0]; i <= range[1]; ++i) {
171
- updateBBox(bbox, pointSets[i]);
173
+ updateBBox(bbox, pointSets[i]!);
172
174
  }
173
175
  return bbox;
174
176
  }
@@ -176,8 +178,8 @@ function getBBox(pointSets: Array<[number, number]>, range: IndexRange) {
176
178
  function getPolygonBBox(polygon: Array<Array<[number, number]>>) {
177
179
  const bbox: BBox = [Infinity, Infinity, -Infinity, -Infinity];
178
180
  for (let i = 0; i < polygon.length; ++i) {
179
- for (let j = 0; j < polygon[i].length; ++j) {
180
- updateBBox(bbox, polygon[i][j]);
181
+ for (let j = 0; j < polygon[i]!.length; ++j) {
182
+ updateBBox(bbox, polygon[i]![j]!);
181
183
  }
182
184
  }
183
185
  return bbox;
@@ -221,7 +223,7 @@ export function getLngLatPoint(coord: Point, canonical: CanonicalTileID, extent:
221
223
  function getLngLatPoints(coordinates: Array<Point>, canonical: CanonicalTileID): Array<[number, number]> {
222
224
  const coords: Array<[number, number]> = [];
223
225
  for (let i = 0; i < coordinates.length; ++i) {
224
- coords.push(getLngLatPoint(coordinates[i], canonical));
226
+ coords.push(getLngLatPoint(coordinates[i]!, canonical));
225
227
  }
226
228
  return coords;
227
229
  }
@@ -235,7 +237,7 @@ function pointsToLineDistance(points: Array<[number, number]>, rangeA: IndexRang
235
237
  const subLine = line.slice(rangeB[0], rangeB[1] + 1);
236
238
  let dist = Infinity;
237
239
  for (let i = rangeA[0]; i <= rangeA[1]; ++i) {
238
- if ((dist = Math.min(dist, pointToLineDistance(points[i], subLine, kx, ky))) === 0.0) return 0.0;
240
+ if ((dist = Math.min(dist, pointToLineDistance(points[i]!, subLine, kx, ky))) === 0.0) return 0.0;
239
241
  }
240
242
  return dist;
241
243
  }
@@ -261,8 +263,8 @@ function lineToLineDistance(line1: Array<[number, number]>, range1: IndexRange,
261
263
  let dist = Infinity;
262
264
  for (let i = range1[0]; i < range1[1]; ++i) {
263
265
  for (let j = range2[0]; j < range2[1]; ++j) {
264
- if (segmentIntersectSegment(line1[i], line1[i + 1], line2[j], line2[j + 1])) return 0.0;
265
- dist = Math.min(dist, segmentToSegmentDistance(line1[i], line1[i + 1], line2[j], line2[j + 1], kx, ky));
266
+ if (segmentIntersectSegment(line1[i]!, line1[i + 1]!, line2[j]!, line2[j + 1]!)) return 0.0;
267
+ dist = Math.min(dist, segmentToSegmentDistance(line1[i]!, line1[i + 1]!, line2[j]!, line2[j + 1]!, kx, ky));
266
268
  }
267
269
  }
268
270
  return dist;
@@ -275,7 +277,7 @@ function pointsToPointsDistance(pointSet1: Array<[number, number]>, range1: Inde
275
277
  let dist = Infinity;
276
278
  for (let i = range1[0]; i <= range1[1]; ++i) {
277
279
  for (let j = range2[0]; j <= range2[1]; ++j) {
278
- if ((dist = Math.min(dist, rulerDistance(pointSet1[i], pointSet2[j], kx, ky))) === 0.0) return dist;
280
+ if ((dist = Math.min(dist, rulerDistance(pointSet1[i]!, pointSet2[j]!, kx, ky))) === 0.0) return dist;
279
281
  }
280
282
  }
281
283
  return dist;
@@ -291,7 +293,7 @@ function pointToPolygonDistance(point: [number, number], polygon: Array<Array<[n
291
293
  return NaN;
292
294
  }
293
295
  if (ring[0] !== ring[ringLen - 1]) {
294
- if ((dist = Math.min(dist, rulerPointToSegmentDistance(point, ring[ringLen - 1], ring[0], kx, ky))) === 0.0) return dist;
296
+ if ((dist = Math.min(dist, rulerPointToSegmentDistance(point, ring[ringLen - 1]!, ring[0]!, kx, ky))) === 0.0) return dist;
295
297
  }
296
298
  if ((dist = Math.min(dist, pointToLineDistance(point, ring, kx, ky))) === 0.0) return dist;
297
299
  }
@@ -303,14 +305,14 @@ function lineToPolygonDistance(line: Array<[number, number]>, range: IndexRange,
303
305
  return NaN;
304
306
  }
305
307
  for (let i = range[0]; i <= range[1]; ++i) {
306
- if (pointWithinPolygon(line[i], polygon, true /*trueOnBoundary*/)) return 0.0;
308
+ if (pointWithinPolygon(line[i]!, polygon, true /*trueOnBoundary*/)) return 0.0;
307
309
  }
308
310
  let dist = Infinity;
309
311
  for (let i = range[0]; i < range[1]; ++i) {
310
312
  for (const ring of polygon) {
311
313
  for (let j = 0, len = ring.length, k = len - 1; j < len; k = j++) {
312
- if (segmentIntersectSegment(line[i], line[i + 1], ring[k], ring[j])) return 0.0;
313
- dist = Math.min(dist, segmentToSegmentDistance(line[i], line[i + 1], ring[k], ring[j], kx, ky));
314
+ if (segmentIntersectSegment(line[i]!, line[i + 1]!, ring[k]!, ring[j]!)) return 0.0;
315
+ dist = Math.min(dist, segmentToSegmentDistance(line[i]!, line[i + 1]!, ring[k]!, ring[j]!, kx, ky));
314
316
  }
315
317
  }
316
318
  }
@@ -320,7 +322,7 @@ function lineToPolygonDistance(line: Array<[number, number]>, range: IndexRange,
320
322
  function polygonIntersect(polygon1: Array<Array<[number, number]>>, polygon2: Array<Array<[number, number]>>) {
321
323
  for (const ring of polygon1) {
322
324
  for (let i = 0; i <= ring.length - 1; ++i) {
323
- if (pointWithinPolygon(ring[i], polygon2, true /*trueOnBoundary*/)) return true;
325
+ if (pointWithinPolygon(ring[i]!, polygon2, true /*trueOnBoundary*/)) return true;
324
326
  }
325
327
  }
326
328
  return false;
@@ -342,8 +344,8 @@ function polygonToPolygonDistance(polygon1: Array<Array<[number, number]>>, poly
342
344
  for (let i = 0, len1 = ring1.length, l = len1 - 1; i < len1; l = i++) {
343
345
  for (const ring2 of polygon2) {
344
346
  for (let j = 0, len2 = ring2.length, k = len2 - 1; j < len2; k = j++) {
345
- if (segmentIntersectSegment(ring1[l], ring1[i], ring2[k], ring2[j])) return 0.0;
346
- dist = Math.min(dist, segmentToSegmentDistance(ring1[l], ring1[i], ring2[k], ring2[j], kx, ky));
347
+ if (segmentIntersectSegment(ring1[l]!, ring1[i]!, ring2[k]!, ring2[j]!)) return 0.0;
348
+ dist = Math.min(dist, segmentToSegmentDistance(ring1[l]!, ring1[i]!, ring2[k]!, ring2[j]!, kx, ky));
347
349
  }
348
350
  }
349
351
  }
@@ -361,7 +363,7 @@ function updateQueue(distQueue: TinyQueue<DistPair>, miniDist: number, kx: numbe
361
363
  // Divide and conquer, the time complexity is O(n*lgn), faster than Brute force O(n*n)
362
364
  // Most of the time, use index for in-place processing.
363
365
  function pointSetToPolygonDistance(pointSets: Array<[number, number]>, isLine: boolean, polygon: Array<Array<[number, number]>>, kx: number, ky: number, currentMiniDist: number = Infinity) {
364
- let miniDist = Math.min(rulerDistance(pointSets[0], polygon[0][0], kx, ky), currentMiniDist);
366
+ let miniDist = Math.min(rulerDistance(pointSets[0]!, polygon[0]![0]!, kx, ky), currentMiniDist);
365
367
  if (miniDist === 0.0) return miniDist;
366
368
  const initialDistPair: DistPair = {
367
369
  dist: 0,
@@ -374,7 +376,7 @@ function pointSetToPolygonDistance(pointSets: Array<[number, number]>, isLine: b
374
376
  const polyBBox = getPolygonBBox(polygon);
375
377
 
376
378
  while (distQueue.length) {
377
- const distPair = distQueue.pop();
379
+ const distPair = distQueue.pop()!;
378
380
  if (distPair.dist >= miniDist) continue;
379
381
  const range = distPair.range1;
380
382
  // In case the set size are relatively small, we could use brute-force directly
@@ -385,7 +387,7 @@ function pointSetToPolygonDistance(pointSets: Array<[number, number]>, isLine: b
385
387
  if ((miniDist = Math.min(miniDist, tempDist)) === 0.0) return miniDist;
386
388
  } else {
387
389
  for (let i = range[0]; i <= range[1]; ++i) {
388
- const tempDist = pointToPolygonDistance(pointSets[i], polygon, kx, ky);
390
+ const tempDist = pointToPolygonDistance(pointSets[i]!, polygon, kx, ky);
389
391
  if ((miniDist = Math.min(miniDist, tempDist)) === 0.0) return miniDist;
390
392
  }
391
393
  }
@@ -405,7 +407,7 @@ function pointSetToPolygonDistance(pointSets: Array<[number, number]>, isLine: b
405
407
  }
406
408
 
407
409
  function pointSetsDistance(pointSet1: Array<[number, number]>, isLine1: boolean, pointSet2: Array<[number, number]>, isLine2: boolean, kx: number, ky: number, currentMiniDist: number = Infinity) {
408
- let miniDist = Math.min(currentMiniDist, rulerDistance(pointSet1[0], pointSet2[0], kx, ky));
410
+ let miniDist = Math.min(currentMiniDist, rulerDistance(pointSet1[0]!, pointSet2[0]!, kx, ky));
409
411
  if (miniDist === 0.0) return miniDist;
410
412
  const initialDistPair: DistPair = {
411
413
  dist: 0,
@@ -418,7 +420,7 @@ function pointSetsDistance(pointSet1: Array<[number, number]>, isLine1: boolean,
418
420
  const set2Threshold = isLine2 ? MIN_LINE_POINT_SIZE : MIN_POINT_SIZE;
419
421
 
420
422
  while (distQueue.length) {
421
- const distPair = distQueue.pop();
423
+ const distPair = distQueue.pop()!;
422
424
  if (distPair.dist >= miniDist) continue;
423
425
  const rangeA = distPair.range1;
424
426
  const rangeB = distPair.range2;
@@ -491,7 +493,7 @@ function pointsToGeometryDistance(originGeometry: Array<Array<Point>>, canonical
491
493
  lngLatPoints.push(getLngLatPoint(point, canonical));
492
494
  }
493
495
  }
494
- const [kx, ky] = lngLatScale(lngLatPoints[0][1]);
496
+ const [kx, ky] = lngLatScale(lngLatPoints[0]![1]);
495
497
  if (geometry.type === 'Point' || geometry.type === 'MultiPoint' || geometry.type === 'LineString') {
496
498
  return pointSetsDistance(lngLatPoints, false /*isLine*/,
497
499
  (geometry.type === 'Point' ? [geometry.coordinates] : geometry.coordinates) as Array<[number, number]>,
@@ -516,7 +518,7 @@ function linesToGeometryDistance(originGeometry: Array<Array<Point>>, canonical:
516
518
  }
517
519
  lngLatLines.push(lngLatLine);
518
520
  }
519
- const [kx, ky] = lngLatScale(lngLatLines[0][0][1]);
521
+ const [kx, ky] = lngLatScale(lngLatLines[0]![0]![1]);
520
522
  if (geometry.type === 'Point' || geometry.type === 'MultiPoint' || geometry.type === 'LineString') {
521
523
  return pointSetToLinesDistance(
522
524
  (geometry.type === 'Point' ? [geometry.coordinates] : geometry.coordinates) as Array<[number, number]>,
@@ -525,7 +527,7 @@ function linesToGeometryDistance(originGeometry: Array<Array<Point>>, canonical:
525
527
  if (geometry.type === 'MultiLineString') {
526
528
  let dist = Infinity;
527
529
  for (let i = 0; i < geometry.coordinates.length; i++) {
528
- const tempDist = pointSetToLinesDistance(geometry.coordinates[i] as Array<[number, number]>, true /*isLine*/, lngLatLines, kx, ky, dist);
530
+ const tempDist = pointSetToLinesDistance(geometry.coordinates[i]! as Array<[number, number]>, true /*isLine*/, lngLatLines, kx, ky, dist);
529
531
  if (isNaN(tempDist)) return tempDist;
530
532
  if ((dist = Math.min(dist, tempDist)) === 0.0) return dist;
531
533
  }
@@ -534,7 +536,7 @@ function linesToGeometryDistance(originGeometry: Array<Array<Point>>, canonical:
534
536
  if (geometry.type === 'Polygon' || geometry.type === 'MultiPolygon') {
535
537
  let dist = Infinity;
536
538
  for (let i = 0; i < lngLatLines.length; i++) {
537
- const tempDist = pointSetToPolygonsDistance(lngLatLines[i], true /*isLine*/,
539
+ const tempDist = pointSetToPolygonsDistance(lngLatLines[i]!, true /*isLine*/,
538
540
  (geometry.type === 'Polygon' ? [geometry.coordinates] : geometry.coordinates) as Array<Array<Array<[number, number]>>>,
539
541
  kx, ky, dist);
540
542
  if (isNaN(tempDist)) return tempDist;
@@ -550,11 +552,11 @@ function polygonsToGeometryDistance(originGeometry: Array<Array<Point>>, canonic
550
552
  for (const polygon of classifyRings(originGeometry, 0)) {
551
553
  const lngLatPolygon: Array<Array<[number, number]>> = [];
552
554
  for (let i = 0; i < polygon.length; ++i) {
553
- lngLatPolygon.push(getLngLatPoints(polygon[i], canonical));
555
+ lngLatPolygon.push(getLngLatPoints(polygon[i]!, canonical));
554
556
  }
555
557
  lngLatPolygons.push(lngLatPolygon);
556
558
  }
557
- const [kx, ky] = lngLatScale(lngLatPolygons[0][0][0][1]);
559
+ const [kx, ky] = lngLatScale(lngLatPolygons[0]![0]![0]![1]);
558
560
  if (geometry.type === 'Point' || geometry.type === 'MultiPoint' || geometry.type === 'LineString') {
559
561
  return pointSetToPolygonsDistance(
560
562
  (geometry.type === 'Point' ? [geometry.coordinates] : geometry.coordinates) as Array<[number, number]>,
@@ -563,7 +565,7 @@ function polygonsToGeometryDistance(originGeometry: Array<Array<Point>>, canonic
563
565
  if (geometry.type === 'MultiLineString') {
564
566
  let dist = Infinity;
565
567
  for (let i = 0; i < geometry.coordinates.length; i++) {
566
- const tempDist = pointSetToPolygonsDistance(geometry.coordinates[i] as Array<[number, number]>, true /*isLine*/, lngLatPolygons, kx, ky, dist);
568
+ const tempDist = pointSetToPolygonsDistance(geometry.coordinates[i]! as Array<[number, number]>, true /*isLine*/, lngLatPolygons, kx, ky, dist);
567
569
  if (isNaN(tempDist)) return tempDist;
568
570
  if ((dist = Math.min(dist, tempDist)) === 0.0) return dist;
569
571
  }
@@ -587,70 +589,130 @@ function isTypeValid(type: string) {
587
589
  type === "MultiPolygon"
588
590
  );
589
591
  }
592
+
593
+ // Resolves a GeoJSON value (Feature / FeatureCollection / bare geometry) down
594
+ // to the geometries `distance` measures against -- one per feature for a
595
+ // FeatureCollection, one otherwise. Shared by parse-time (literal argument)
596
+ // and evaluate-time (e.g. a `["config", ...]` argument, whose value isn't
597
+ // known until evaluation) resolution. Returns null if the value isn't valid
598
+ // GeoJSON, or if any feature has a geometry type other than
599
+ // Point/LineString/Polygon (and their Multi* variants).
600
+ function extractDistanceGeometry(value: unknown): Array<DistanceGeometry> | null {
601
+ if (!isValue(value) || typeof value !== 'object' || value === null || Array.isArray(value)) {
602
+ return null;
603
+ }
604
+ const geojson = value as GeoJSON.GeoJSON;
605
+ if (geojson.type === 'FeatureCollection') {
606
+ if (geojson.features.length === 0) return null;
607
+ const geometries: Array<DistanceGeometry> = [];
608
+ for (const feature of geojson.features) {
609
+ if (!isTypeValid(feature.geometry.type)) return null;
610
+ geometries.push(feature.geometry as DistanceGeometry);
611
+ }
612
+ return geometries;
613
+ }
614
+ if (geojson.type === 'Feature') {
615
+ return isTypeValid(geojson.geometry.type) ? [geojson.geometry as DistanceGeometry] : null;
616
+ }
617
+ if (isTypeValid(geojson.type)) {
618
+ return [geojson as DistanceGeometry];
619
+ }
620
+ return null;
621
+ }
622
+
590
623
  class Distance implements Expression {
591
624
  type: Type;
592
- geojson: GeoJSON.GeoJSON;
593
- geometries: DistanceGeometry;
625
+ geojson: Expression;
594
626
 
595
- constructor(geojson: GeoJSON.GeoJSON, geometries: DistanceGeometry) {
627
+ constructor(geojson: Expression) {
596
628
  this.type = NumberType;
597
629
  this.geojson = geojson;
598
- this.geometries = geometries;
599
630
  }
600
631
 
601
632
  static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Distance | null | void {
602
633
  if (args.length !== 2) {
603
634
  return context.error(`'distance' expression requires either one argument, but found ' ${args.length - 1} instead.`);
604
635
  }
605
- if (isValue(args[1])) {
606
- const geojson = args[1] as GeoJSON.GeoJSON;
607
- if (geojson.type === 'FeatureCollection') {
608
- for (let i = 0; i < geojson.features.length; ++i) {
609
- if (isTypeValid(geojson.features[i].geometry.type)) {
610
- return new Distance(geojson, geojson.features[i].geometry as DistanceGeometry);
611
- }
612
- }
613
- } else if (geojson.type === 'Feature') {
614
- if (isTypeValid(geojson.geometry.type)) {
615
- return new Distance(geojson, geojson.geometry as DistanceGeometry);
616
- }
617
- } else if (isTypeValid(geojson.type)) {
618
- return new Distance(geojson, geojson as DistanceGeometry);
636
+
637
+ const arg = args[1];
638
+ // A bare GeoJSON value (Feature / FeatureCollection / bare geometry)
639
+ // isn't valid expression syntax on its own, so wrap it as a literal
640
+ // like ["literal", {...}] would be. Anything else -- e.g.
641
+ // `["config", "key"]` -- is left as-is and parsed as a regular
642
+ // sub-expression, resolved to GeoJSON at evaluation time so a config
643
+ // value can change at runtime without re-parsing the style.
644
+ const isBareGeoJSON = isValue(arg) && !Array.isArray(arg);
645
+ const parsed = context.parse(isBareGeoJSON ? ['literal', arg] : arg, 1, ValueType);
646
+ if (!parsed) return null;
647
+
648
+ for (const [globalProperties, name] of [
649
+ [['measure-light'], 'brightness'],
650
+ [['pitch'], 'pitch'],
651
+ [['distance-from-center'], 'distance-from-center'],
652
+ ] as Array<[Array<string>, string]>) {
653
+ if (!isGlobalPropertyConstant(parsed, globalProperties)) {
654
+ return context.error(`'distance' expression may not depend on ${name}.`);
619
655
  }
620
656
  }
621
- return context.error(
622
- "'distance' expression needs to be an array with format [\'Distance\', GeoJSONObj]."
623
- );
657
+ if (!isStateConstant(parsed)) {
658
+ return context.error(`'distance' expression may not depend on feature-state.`);
659
+ }
660
+
661
+ // Literal arguments can be validated eagerly; a config-driven
662
+ // argument can't be checked until it's evaluated.
663
+ if (parsed instanceof Literal && !extractDistanceGeometry(parsed.value)) {
664
+ return context.error(
665
+ "'distance' expression needs to be an array with format [\'Distance\', GeoJSONObj]."
666
+ );
667
+ }
668
+
669
+ return new Distance(parsed);
624
670
  }
625
671
 
626
672
  evaluate(ctx: EvaluationContext): number | null {
673
+ const geometries = extractDistanceGeometry(this.geojson.evaluate(ctx));
674
+ if (!geometries) {
675
+ console.warn("Distance Expression: could not resolve a valid Point/LineString/Polygon GeoJSON geometry.");
676
+ return null;
677
+ }
678
+
627
679
  const geometry = ctx.geometry();
628
680
  const canonical = ctx.canonicalID();
629
- if (geometry != null && canonical != null) {
630
- if (ctx.geometryType() === 'Point') {
631
- return pointsToGeometryDistance(geometry, canonical, this.geometries);
632
- }
633
- if (ctx.geometryType() === 'LineString') {
634
- return linesToGeometryDistance(geometry, canonical, this.geometries);
635
- }
636
- if (ctx.geometryType() === 'Polygon') {
637
- return polygonsToGeometryDistance(geometry, canonical, this.geometries);
638
- }
639
- console.warn("Distance Expression: currently only evaluates valid Point/LineString/Polygon geometries.");
640
- } else {
681
+ if (geometry == null || canonical == null) {
641
682
  console.warn("Distance Expression: requires valid feature and canonical information.");
683
+ return null;
642
684
  }
643
- return null;
685
+
686
+ const geometryType = ctx.geometryType();
687
+ const distanceTo = geometryType === 'Point' ? pointsToGeometryDistance :
688
+ geometryType === 'LineString' ? linesToGeometryDistance :
689
+ geometryType === 'Polygon' ? polygonsToGeometryDistance : null;
690
+ if (!distanceTo) {
691
+ console.warn("Distance Expression: currently only evaluates valid Point/LineString/Polygon geometries.");
692
+ return null;
693
+ }
694
+
695
+ // A FeatureCollection reference resolves to one geometry per feature;
696
+ // report the distance to the closest one.
697
+ let dist = Infinity;
698
+ for (const g of geometries) {
699
+ const tempDist = distanceTo(geometry, canonical, g);
700
+ if (tempDist == null || isNaN(tempDist)) return tempDist;
701
+ if ((dist = Math.min(dist, tempDist)) === 0) break;
702
+ }
703
+ return dist;
644
704
  }
645
705
 
646
- eachChild() {}
706
+ eachChild(fn: (_: Expression) => void) {
707
+ fn(this.geojson);
708
+ }
647
709
 
648
710
  outputDefined(): boolean {
649
711
  return true;
650
712
  }
651
713
 
652
714
  serialize(): Array<unknown> {
653
- return ['distance', this.geojson];
715
+ return ['distance', this.geojson.serialize()];
654
716
  }
655
717
  }
656
718
 
@@ -47,35 +47,32 @@ export default class FormatExpression implements Expression {
47
47
  const sections: Array<FormattedSectionExpression> = [];
48
48
  let nextTokenMayBeObject = false;
49
49
  for (let i = 1; i <= args.length - 1; ++i) {
50
- const arg = args[i];
50
+ const arg = args[i] as Record<string, unknown> | unknown[] | string | number | boolean | null | undefined;
51
51
 
52
52
  if (nextTokenMayBeObject && typeof arg === "object" && !Array.isArray(arg)) {
53
53
  nextTokenMayBeObject = false;
54
54
 
55
55
  let scale = null;
56
- if (arg['font-scale']) {
57
- scale = context.parseObjectValue(arg['font-scale'], i, 'font-scale', NumberType);
56
+ if (arg!['font-scale']) {
57
+ scale = context.parseObjectValue(arg!['font-scale'], i, 'font-scale', NumberType);
58
58
  if (!scale) return null;
59
59
  }
60
60
 
61
61
  let font = null;
62
- if (arg['text-font']) {
63
- font = context.parseObjectValue(arg['text-font'], i, 'text-font', array(StringType));
62
+ if (arg!['text-font']) {
63
+ font = context.parseObjectValue(arg!['text-font'], i, 'text-font', array(StringType));
64
64
  if (!font) return null;
65
65
  }
66
66
 
67
67
  let textColor = null;
68
- if (arg['text-color']) {
69
- textColor = context.parseObjectValue(arg['text-color'], i, 'text-color', ColorType);
68
+ if (arg!['text-color']) {
69
+ textColor = context.parseObjectValue(arg!['text-color'], i, 'text-color', ColorType);
70
70
  if (!textColor) return null;
71
71
  }
72
72
 
73
- const lastExpression = sections.at(-1);
74
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
73
+ const lastExpression = sections.at(-1)!;
75
74
  lastExpression.scale = scale;
76
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
77
75
  lastExpression.font = font;
78
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
79
76
  lastExpression.textColor = textColor;
80
77
  } else {
81
78
  const content = context.parse(args[i], i, ValueType);
@@ -140,10 +137,10 @@ export default class FormatExpression implements Expression {
140
137
  }
141
138
 
142
139
  serialize(): SerializedExpression {
143
- const serialized: SerializedExpression[] = ["format"];
140
+ const serialized: Array<unknown> = ["format"];
144
141
  for (const section of this.sections) {
145
142
  serialized.push(section.content.serialize());
146
- const options = {} as SerializedExpression;
143
+ const options: Record<string, SerializedExpression> = {};
147
144
  if (section.scale) {
148
145
  options['font-scale'] = section.scale.serialize();
149
146
  }
@@ -32,7 +32,7 @@ export default class ImageExpression implements Expression {
32
32
  paramsPrimary?: ImageParams;
33
33
  iconsetIdPrimary?: string;
34
34
 
35
- nameSecondary?: Expression;
35
+ nameSecondary?: Expression | null;
36
36
  paramsSecondary?: ImageParams;
37
37
  iconsetIdSecondary?: string;
38
38
 
@@ -121,7 +121,7 @@ export default class ImageExpression implements Expression {
121
121
  parsedParams[key] = value;
122
122
  }
123
123
 
124
- imageExpression.at(-1).options.params = parsedParams;
124
+ imageExpression.at(-1)!.options!.params = parsedParams;
125
125
  }
126
126
 
127
127
  // Validate the iconset image options
@@ -136,7 +136,7 @@ export default class ImageExpression implements Expression {
136
136
  return false;
137
137
  }
138
138
 
139
- imageExpression.at(-1).options.iconset = iconset;
139
+ imageExpression.at(-1)!.options!.iconset = iconset;
140
140
  }
141
141
 
142
142
  nextArgId++;
@@ -154,14 +154,14 @@ export default class ImageExpression implements Expression {
154
154
  }
155
155
 
156
156
  return new ImageExpression(
157
- imageExpression[0].image,
157
+ imageExpression[0]!.image,
158
158
  imageExpression[1] ? imageExpression[1].image : undefined,
159
- imageExpression[0].options,
159
+ imageExpression[0]!.options,
160
160
  imageExpression[1] ? imageExpression[1].options : undefined
161
161
  );
162
162
  }
163
163
 
164
- evaluateParams(ctx: EvaluationContext, params: Record<string, Expression> | undefined): {params: Record<string, Color>} {
164
+ evaluateParams(ctx: EvaluationContext, params: Record<string, Expression> | undefined): {params: Record<string, Color>} | undefined {
165
165
  const result: Record<string, Color> = {};
166
166
  if (params) {
167
167
  for (const key in params) {
@@ -210,7 +210,7 @@ export default class ImageExpression implements Expression {
210
210
  value.available = ctx.availableImages.some((id) => ImageId.isEqual(id, primaryId));
211
211
  if (value.available) {
212
212
  // If there's a secondary variant, only mark it available if both are present
213
- const secondaryId = value.getSecondary() ? value.getSecondary().id : null;
213
+ const secondaryId = value.getSecondary() ? value.getSecondary()!.id : null;
214
214
  if (secondaryId) value.available = ctx.availableImages.some((id) => ImageId.isEqual(id, secondaryId));
215
215
  }
216
216
  }
@@ -246,7 +246,7 @@ export default class ImageExpression implements Expression {
246
246
  return false;
247
247
  }
248
248
 
249
- serializeOptions(params: ImageParams, iconsetId: string): SerializedImageOptions | undefined {
249
+ serializeOptions(params?: ImageParams, iconsetId?: string): SerializedImageOptions | undefined {
250
250
  const result: SerializedImageOptions = {};
251
251
 
252
252
  if (iconsetId) {