@mapbox/mapbox-gl-style-spec 14.0.0-beta.4 → 14.0.0-rc.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.
package/diff.js CHANGED
@@ -134,14 +134,24 @@ export const operations: {[_: string]: string} = {
134
134
  setProjection: 'setProjection',
135
135
 
136
136
  /*
137
- * { command: 'addImport', args: [importProperties] }
137
+ * { command: 'addImport', args: [import] }
138
138
  */
139
139
  addImport: 'addImport',
140
140
 
141
141
  /*
142
142
  * { command: 'removeImport', args: [importId] }
143
143
  */
144
- removeImport: 'removeImport'
144
+ removeImport: 'removeImport',
145
+
146
+ /*
147
+ * { command: 'setImportUrl', args: [importId, styleUrl] }
148
+ */
149
+ setImportUrl: 'setImportUrl',
150
+
151
+ /*
152
+ * { command: 'setImportData', args: [importId, stylesheet] }
153
+ */
154
+ setImportData: 'setImportData'
145
155
  };
146
156
 
147
157
  function addSource(sourceId: string, after: Sources, commands: Array<Command>) {
@@ -226,12 +236,12 @@ function diffLayerPropertyChanges(before: any, after: any, commands: Array<Comma
226
236
  }
227
237
  }
228
238
 
229
- function pluckId(layer: LayerSpecification) {
230
- return layer.id;
239
+ function pluckId<T: {id: string}>(item: T): string {
240
+ return item.id;
231
241
  }
232
242
 
233
- function indexById(group: {[string]: LayerSpecification}, layer: LayerSpecification) {
234
- group[layer.id] = layer;
243
+ function indexById<T: {id: string}>(group: {[string]: T}, item: T): {[id: string]: T} {
244
+ group[item.id] = item;
235
245
  return group;
236
246
  }
237
247
 
@@ -345,14 +355,72 @@ function diffLayers(before: Array<LayerSpecification>, after: Array<LayerSpecifi
345
355
  }
346
356
  }
347
357
 
348
- function diffImports(before: Array<ImportSpecification> = [], after: Array<ImportSpecification> = [], commands: Array<Command>) {
349
- // no diff for the imports, must remove then add
350
- for (const beforeImport of before) {
351
- commands.push({command: operations.removeImport, args: [beforeImport.id]});
358
+ export function diffImports(before: Array<ImportSpecification> = [], after: Array<ImportSpecification> = [], commands: Array<Command>) {
359
+ before = before || [];
360
+ after = after || [];
361
+
362
+ // order imports by id
363
+ const beforeOrder = before.map(pluckId);
364
+ const afterOrder = after.map(pluckId);
365
+
366
+ // index imports by id
367
+ const beforeIndex = before.reduce(indexById, {});
368
+ const afterIndex = after.reduce(indexById, {});
369
+
370
+ // track order of imports as if they have been mutated
371
+ const tracker = beforeOrder.slice();
372
+
373
+ let i, d, importId, insertBefore;
374
+
375
+ // remove imports
376
+ for (i = 0, d = 0; i < beforeOrder.length; i++) {
377
+ importId = beforeOrder[i];
378
+ if (!afterIndex.hasOwnProperty(importId)) {
379
+ commands.push({command: operations.removeImport, args: [importId]});
380
+ tracker.splice(tracker.indexOf(importId, d), 1);
381
+ } else {
382
+ // limit where in tracker we need to look for a match
383
+ d++;
384
+ }
385
+ }
386
+
387
+ // add/reorder imports
388
+ for (i = 0, d = 0; i < afterOrder.length; i++) {
389
+ // work backwards as insert is before an existing import
390
+ importId = afterOrder[afterOrder.length - 1 - i];
391
+
392
+ if (tracker[tracker.length - 1 - i] === importId) continue;
393
+
394
+ if (beforeIndex.hasOwnProperty(importId)) {
395
+ // remove the import before we insert at the correct position
396
+ commands.push({command: operations.removeImport, args: [importId]});
397
+ tracker.splice(tracker.lastIndexOf(importId, tracker.length - d), 1);
398
+ } else {
399
+ // limit where in tracker we need to look for a match
400
+ d++;
401
+ }
402
+
403
+ // add import at correct position
404
+ insertBefore = tracker[tracker.length - i];
405
+ commands.push({command: operations.addImport, args: [afterIndex[importId], insertBefore]});
406
+ tracker.splice(tracker.length - i, 0, importId);
352
407
  }
353
408
 
409
+ // update imports
354
410
  for (const afterImport of after) {
355
- commands.push({command: operations.addImport, args: [afterImport]});
411
+ const beforeImport = beforeIndex[afterImport.id];
412
+ if (!beforeImport || isEqual(beforeImport, afterImport)) continue;
413
+
414
+ if (!isEqual(beforeImport.url, afterImport.url)) {
415
+ commands.push({command: operations.setImportUrl, args: [afterImport.id, afterImport.url]});
416
+ }
417
+
418
+ const beforeData = beforeImport && beforeImport.data;
419
+ const afterData = afterImport.data;
420
+
421
+ if (!isEqual(beforeData, afterData)) {
422
+ commands.push({command: operations.setImportData, args: [afterImport.id, afterData]});
423
+ }
356
424
  }
357
425
  }
358
426
 
package/dist/index.cjs CHANGED
@@ -5150,7 +5150,10 @@
5150
5150
  minimum: 0,
5151
5151
  transition: true,
5152
5152
  units: "intensity",
5153
- doc: "Controls the intensity of light emitted on the source features. This property works only with 3D light, i.e. when `lights` root property is defined.",
5153
+ doc: "Controls the intensity of light emitted on the source features.",
5154
+ requires: [
5155
+ "lights"
5156
+ ],
5154
5157
  "sdk-support": {
5155
5158
  "basic functionality": {
5156
5159
  js: "3.0.0",
@@ -5576,7 +5579,10 @@
5576
5579
  minimum: 0,
5577
5580
  transition: true,
5578
5581
  units: "intensity",
5579
- doc: "Controls the intensity of light emitted on the source features. This property works only with 3D light, i.e. when `lights` root property is defined.",
5582
+ doc: "Controls the intensity of light emitted on the source features.",
5583
+ requires: [
5584
+ "lights"
5585
+ ],
5580
5586
  "sdk-support": {
5581
5587
  "basic functionality": {
5582
5588
  js: "3.0.0",
@@ -5990,7 +5996,10 @@
5990
5996
  minimum: 0,
5991
5997
  transition: true,
5992
5998
  units: "intensity",
5993
- doc: "Controls the intensity of light emitted on the source features. This property works only with 3D light, i.e. when `lights` root property is defined.",
5999
+ doc: "Controls the intensity of light emitted on the source features.",
6000
+ requires: [
6001
+ "lights"
6002
+ ],
5994
6003
  "sdk-support": {
5995
6004
  "basic functionality": {
5996
6005
  js: "3.0.0",
@@ -6203,7 +6212,10 @@
6203
6212
  minimum: 0,
6204
6213
  transition: true,
6205
6214
  units: "intensity",
6206
- doc: "Controls the intensity of light emitted on the source features. This property works only with 3D light, i.e. when `lights` root property is defined.",
6215
+ doc: "Controls the intensity of light emitted on the source features.",
6216
+ requires: [
6217
+ "lights"
6218
+ ],
6207
6219
  "sdk-support": {
6208
6220
  "basic functionality": {
6209
6221
  js: "3.0.0",
@@ -6231,7 +6243,10 @@
6231
6243
  minimum: 0,
6232
6244
  transition: true,
6233
6245
  units: "intensity",
6234
- doc: "Controls the intensity of light emitted on the source features. This property works only with 3D light, i.e. when `lights` root property is defined.",
6246
+ doc: "Controls the intensity of light emitted on the source features.",
6247
+ requires: [
6248
+ "lights"
6249
+ ],
6235
6250
  "sdk-support": {
6236
6251
  "basic functionality": {
6237
6252
  js: "3.0.0",
@@ -7002,7 +7017,7 @@
7002
7017
  "default": 335,
7003
7018
  minimum: 0,
7004
7019
  maximum: 359,
7005
- doc: "The direction of the light source used to generate the hillshading with 0 as the top of the viewport if `hillshade-illumination-anchor` is set to `viewport` and due north if `hillshade-illumination-anchor` is set to `map`.",
7020
+ doc: "The direction of the light source used to generate the hillshading with 0 as the top of the viewport if `hillshade-illumination-anchor` is set to `viewport` and due north if `hillshade-illumination-anchor` is set to `map` and no 3d lights enabled. If `hillshade-illumination-anchor` is set to `map` and 3d lights enabled, the direction from 3d lights is used instead.",
7006
7021
  transition: false,
7007
7022
  "sdk-support": {
7008
7023
  "basic functionality": {
@@ -7237,7 +7252,10 @@
7237
7252
  minimum: 0,
7238
7253
  transition: true,
7239
7254
  units: "intensity",
7240
- doc: "Controls the intensity of light emitted on the source features. This property works only with 3D light, i.e. when `lights` root property is defined.",
7255
+ doc: "Controls the intensity of light emitted on the source features.",
7256
+ requires: [
7257
+ "lights"
7258
+ ],
7241
7259
  "sdk-support": {
7242
7260
  "basic functionality": {
7243
7261
  js: "3.0.0",
@@ -7544,7 +7562,8 @@
7544
7562
  interpolated: true,
7545
7563
  parameters: [
7546
7564
  "feature",
7547
- "feature-state"
7565
+ "feature-state",
7566
+ "zoom"
7548
7567
  ]
7549
7568
  },
7550
7569
  "sdk-support": {
@@ -7609,7 +7628,8 @@
7609
7628
  interpolated: true,
7610
7629
  parameters: [
7611
7630
  "feature",
7612
- "feature-state"
7631
+ "feature-state",
7632
+ "zoom"
7613
7633
  ]
7614
7634
  },
7615
7635
  "sdk-support": {
@@ -7637,7 +7657,8 @@
7637
7657
  parameters: [
7638
7658
  "feature",
7639
7659
  "feature-state",
7640
- "measure-light"
7660
+ "measure-light",
7661
+ "zoom"
7641
7662
  ]
7642
7663
  },
7643
7664
  "sdk-support": {
@@ -8392,6 +8413,12 @@
8392
8413
  },
8393
8414
  transition: true,
8394
8415
  doc: "Controls the intensity of shading near ground and concave angles between walls. Default value 0.0 disables ambient occlusion and values around 0.3 provide the most plausible results for buildings.",
8416
+ requires: [
8417
+ "lights",
8418
+ {
8419
+ "!": "fill-extrusion-flood-light-intensity"
8420
+ }
8421
+ ],
8395
8422
  "sdk-support": {
8396
8423
  "basic functionality": {
8397
8424
  js: "3.0.0",
@@ -8415,7 +8442,10 @@
8415
8442
  transition: true,
8416
8443
  doc: "Shades area near ground and concave angles between walls where the radius defines only vertical impact. Default value 3.0 corresponds to height of one floor and brings the most plausible results for buildings. This property works only with legacy light. When 3D light is enabled `fill-extrusion-ambient-occlusion-wall-radius` and `fill-extrusion-ambient-occlusion-ground-radius` are used instead.",
8417
8444
  requires: [
8418
- "fill-extrusion-edge-radius"
8445
+ "fill-extrusion-edge-radius",
8446
+ {
8447
+ "!": "fill-extrusion-flood-light-intensity"
8448
+ }
8419
8449
  ],
8420
8450
  "sdk-support": {
8421
8451
  "basic functionality": {
@@ -8437,9 +8467,13 @@
8437
8467
  ]
8438
8468
  },
8439
8469
  transition: true,
8440
- doc: "Shades area near ground and concave angles between walls where the radius defines only vertical impact. Default value 3.0 corresponds to height of one floor and brings the most plausible results for buildings. This property works only with 3D light, i.e. when `lights` root property is defined.",
8470
+ doc: "Shades area near ground and concave angles between walls where the radius defines only vertical impact. Default value 3.0 corresponds to height of one floor and brings the most plausible results for buildings.",
8441
8471
  requires: [
8442
- "fill-extrusion-edge-radius"
8472
+ "lights",
8473
+ "fill-extrusion-edge-radius",
8474
+ {
8475
+ "!": "fill-extrusion-flood-light-intensity"
8476
+ }
8443
8477
  ],
8444
8478
  "sdk-support": {
8445
8479
  "basic functionality": {
@@ -8461,7 +8495,13 @@
8461
8495
  ]
8462
8496
  },
8463
8497
  transition: true,
8464
- doc: "The extent of the ambient occlusion effect on the ground beneath the extruded buildings in meters. This property works only with 3D light, i.e. when `lights` root property is defined.",
8498
+ doc: "The extent of the ambient occlusion effect on the ground beneath the extruded buildings in meters.",
8499
+ requires: [
8500
+ "lights",
8501
+ {
8502
+ "!": "fill-extrusion-flood-light-intensity"
8503
+ }
8504
+ ],
8465
8505
  "sdk-support": {
8466
8506
  "basic functionality": {
8467
8507
  js: "3.0.0",
@@ -8476,7 +8516,13 @@
8476
8516
  "default": 0.69,
8477
8517
  minimum: 0,
8478
8518
  maximum: 1,
8479
- doc: "Provides a control to futher fine-tune the look of the ambient occlusion on the ground beneath the extruded buildings. Lower values give the effect a more solid look while higher values make it smoother. This property works only with 3D light, i.e. when `lights` root property is defined.",
8519
+ doc: "Provides a control to futher fine-tune the look of the ambient occlusion on the ground beneath the extruded buildings. Lower values give the effect a more solid look while higher values make it smoother.",
8520
+ requires: [
8521
+ "lights",
8522
+ {
8523
+ "!": "fill-extrusion-flood-light-intensity"
8524
+ }
8525
+ ],
8480
8526
  transition: true,
8481
8527
  expression: {
8482
8528
  interpolated: true,
@@ -8496,7 +8542,13 @@
8496
8542
  "property-type": "data-constant",
8497
8543
  type: "color",
8498
8544
  "default": "#ffffff",
8499
- doc: "The color of the flood light effect on the walls of the extruded buildings. This property works only with 3D light, i.e. when `lights` root property is defined.",
8545
+ doc: "The color of the flood light effect on the walls of the extruded buildings.",
8546
+ requires: [
8547
+ "lights",
8548
+ {
8549
+ "!": "fill-extrusion-ambient-occlusion-intensity"
8550
+ }
8551
+ ],
8500
8552
  transition: true,
8501
8553
  expression: {
8502
8554
  interpolated: true,
@@ -8519,7 +8571,13 @@
8519
8571
  "default": 0,
8520
8572
  minimum: 0,
8521
8573
  maximum: 1,
8522
- doc: "The intensity of the flood light color. This property works only with 3D light, i.e. when `lights` root property is defined.",
8574
+ doc: "The intensity of the flood light color.",
8575
+ requires: [
8576
+ "lights",
8577
+ {
8578
+ "!": "fill-extrusion-ambient-occlusion-intensity"
8579
+ }
8580
+ ],
8523
8581
  transition: true,
8524
8582
  expression: {
8525
8583
  interpolated: true,
@@ -8542,7 +8600,13 @@
8542
8600
  units: "meters",
8543
8601
  "default": 0,
8544
8602
  minimum: 0,
8545
- doc: "The extent of the flood light effect on the walls of the extruded buildings in meters. This property works only with 3D light, i.e. when `lights` root property is defined.",
8603
+ doc: "The extent of the flood light effect on the walls of the extruded buildings in meters.",
8604
+ requires: [
8605
+ "lights",
8606
+ {
8607
+ "!": "fill-extrusion-ambient-occlusion-intensity"
8608
+ }
8609
+ ],
8546
8610
  transition: true,
8547
8611
  expression: {
8548
8612
  interpolated: true,
@@ -8570,7 +8634,13 @@
8570
8634
  units: "meters",
8571
8635
  "default": 0,
8572
8636
  minimum: 0,
8573
- doc: "The extent of the flood light effect on the ground beneath the extruded buildings in meters. This property works only with 3D light, i.e. when `lights` root property is defined.",
8637
+ doc: "The extent of the flood light effect on the ground beneath the extruded buildings in meters.",
8638
+ requires: [
8639
+ "lights",
8640
+ {
8641
+ "!": "fill-extrusion-ambient-occlusion-intensity"
8642
+ }
8643
+ ],
8574
8644
  transition: true,
8575
8645
  expression: {
8576
8646
  interpolated: true,
@@ -8598,7 +8668,13 @@
8598
8668
  "default": 0.69,
8599
8669
  minimum: 0,
8600
8670
  maximum: 1,
8601
- doc: "Provides a control to futher fine-tune the look of the flood light on the ground beneath the extruded buildings. Lower values give the effect a more solid look while higher values make it smoother. This property works only with 3D light, i.e. when `lights` root property is defined.",
8671
+ doc: "Provides a control to futher fine-tune the look of the flood light on the ground beneath the extruded buildings. Lower values give the effect a more solid look while higher values make it smoother.",
8672
+ requires: [
8673
+ "lights",
8674
+ {
8675
+ "!": "fill-extrusion-ambient-occlusion-intensity"
8676
+ }
8677
+ ],
8602
8678
  transition: true,
8603
8679
  expression: {
8604
8680
  interpolated: true,
@@ -18579,13 +18655,21 @@ ${ JSON.stringify(filterExp, null, 2) }
18579
18655
  */
18580
18656
  setProjection: 'setProjection',
18581
18657
  /*
18582
- * { command: 'addImport', args: [importProperties] }
18658
+ * { command: 'addImport', args: [import] }
18583
18659
  */
18584
18660
  addImport: 'addImport',
18585
18661
  /*
18586
18662
  * { command: 'removeImport', args: [importId] }
18587
18663
  */
18588
- removeImport: 'removeImport'
18664
+ removeImport: 'removeImport',
18665
+ /*
18666
+ * { command: 'setImportUrl', args: [importId, styleUrl] }
18667
+ */
18668
+ setImportUrl: 'setImportUrl',
18669
+ /*
18670
+ * { command: 'setImportData', args: [importId, stylesheet] }
18671
+ */
18672
+ setImportData: 'setImportData'
18589
18673
  };
18590
18674
  function addSource(sourceId, after, commands) {
18591
18675
  commands.push({
@@ -18695,11 +18779,11 @@ ${ JSON.stringify(filterExp, null, 2) }
18695
18779
  }
18696
18780
  }
18697
18781
  }
18698
- function pluckId(layer) {
18699
- return layer.id;
18782
+ function pluckId(item) {
18783
+ return item.id;
18700
18784
  }
18701
- function indexById(group, layer) {
18702
- group[layer.id] = layer;
18785
+ function indexById(group, item) {
18786
+ group[item.id] = item;
18703
18787
  return group;
18704
18788
  }
18705
18789
  function diffLayers(before, after, commands) {
@@ -18849,18 +18933,84 @@ ${ JSON.stringify(filterExp, null, 2) }
18849
18933
  }
18850
18934
  }
18851
18935
  function diffImports(before = [], after = [], commands) {
18852
- // no diff for the imports, must remove then add
18853
- for (const beforeImport of before) {
18854
- commands.push({
18855
- command: operations.removeImport,
18856
- args: [beforeImport.id]
18857
- });
18936
+ before = before || [];
18937
+ after = after || [];
18938
+ // order imports by id
18939
+ const beforeOrder = before.map(pluckId);
18940
+ const afterOrder = after.map(pluckId);
18941
+ // index imports by id
18942
+ const beforeIndex = before.reduce(indexById, {});
18943
+ const afterIndex = after.reduce(indexById, {});
18944
+ // track order of imports as if they have been mutated
18945
+ const tracker = beforeOrder.slice();
18946
+ let i, d, importId, insertBefore;
18947
+ // remove imports
18948
+ for (i = 0, d = 0; i < beforeOrder.length; i++) {
18949
+ importId = beforeOrder[i];
18950
+ if (!afterIndex.hasOwnProperty(importId)) {
18951
+ commands.push({
18952
+ command: operations.removeImport,
18953
+ args: [importId]
18954
+ });
18955
+ tracker.splice(tracker.indexOf(importId, d), 1);
18956
+ } else {
18957
+ // limit where in tracker we need to look for a match
18958
+ d++;
18959
+ }
18858
18960
  }
18859
- for (const afterImport of after) {
18961
+ // add/reorder imports
18962
+ for (i = 0, d = 0; i < afterOrder.length; i++) {
18963
+ // work backwards as insert is before an existing import
18964
+ importId = afterOrder[afterOrder.length - 1 - i];
18965
+ if (tracker[tracker.length - 1 - i] === importId)
18966
+ continue;
18967
+ if (beforeIndex.hasOwnProperty(importId)) {
18968
+ // remove the import before we insert at the correct position
18969
+ commands.push({
18970
+ command: operations.removeImport,
18971
+ args: [importId]
18972
+ });
18973
+ tracker.splice(tracker.lastIndexOf(importId, tracker.length - d), 1);
18974
+ } else {
18975
+ // limit where in tracker we need to look for a match
18976
+ d++;
18977
+ }
18978
+ // add import at correct position
18979
+ insertBefore = tracker[tracker.length - i];
18860
18980
  commands.push({
18861
18981
  command: operations.addImport,
18862
- args: [afterImport]
18982
+ args: [
18983
+ afterIndex[importId],
18984
+ insertBefore
18985
+ ]
18863
18986
  });
18987
+ tracker.splice(tracker.length - i, 0, importId);
18988
+ }
18989
+ // update imports
18990
+ for (const afterImport of after) {
18991
+ const beforeImport = beforeIndex[afterImport.id];
18992
+ if (!beforeImport || deepEqual(beforeImport, afterImport))
18993
+ continue;
18994
+ if (!deepEqual(beforeImport.url, afterImport.url)) {
18995
+ commands.push({
18996
+ command: operations.setImportUrl,
18997
+ args: [
18998
+ afterImport.id,
18999
+ afterImport.url
19000
+ ]
19001
+ });
19002
+ }
19003
+ const beforeData = beforeImport && beforeImport.data;
19004
+ const afterData = afterImport.data;
19005
+ if (!deepEqual(beforeData, afterData)) {
19006
+ commands.push({
19007
+ command: operations.setImportData,
19008
+ args: [
19009
+ afterImport.id,
19010
+ afterData
19011
+ ]
19012
+ });
19013
+ }
18864
19014
  }
18865
19015
  }
18866
19016
  /**
@@ -19040,6 +19190,8 @@ ${ JSON.stringify(filterExp, null, 2) }
19040
19190
  }
19041
19191
  }
19042
19192
  }
19193
+ class ValidationWarning extends ValidationError {
19194
+ }
19043
19195
 
19044
19196
  //
19045
19197
  // Note: Do not inherit from Error. It breaks when transpiling to ES5.
@@ -19080,7 +19232,7 @@ ${ JSON.stringify(filterExp, null, 2) }
19080
19232
  validateElement = validate;
19081
19233
  }
19082
19234
  if (!validateElement) {
19083
- errors.push(new ValidationError(key, object[objectKey], `unknown property "${ objectKey }"`));
19235
+ errors.push(new ValidationWarning(key, object[objectKey], `unknown property "${ objectKey }"`));
19084
19236
  continue;
19085
19237
  }
19086
19238
  errors = errors.concat(validateElement({
@@ -19596,7 +19748,7 @@ ${ JSON.stringify(filterExp, null, 2) }
19596
19748
  }
19597
19749
  const valueSpec = options.valueSpec || layerSpec[propertyKey];
19598
19750
  if (!valueSpec) {
19599
- return [new ValidationError(key, value, `unknown property "${ propertyKey }"`)];
19751
+ return [new ValidationWarning(key, value, `unknown property "${ propertyKey }"`)];
19600
19752
  }
19601
19753
  let tokenMatch;
19602
19754
  if (getType(value) === 'string' && supportsPropertyExpression(valueSpec) && !valueSpec.tokens && (tokenMatch = /^{([^}]+)}$/.exec(value))) {
@@ -19616,8 +19768,8 @@ ${ JSON.stringify(filterExp, null, 2) }
19616
19768
  // Performance related style spec limitation: zoom and light expressions are not allowed for e.g. trees.
19617
19769
  const expression = createPropertyExpression(deepUnbundle(value), valueSpec);
19618
19770
  const expressionObj = expression.value.expression || expression.value._styleExpression.expression;
19619
- if (expressionObj && (!isGlobalPropertyConstant(expressionObj, ['zoom']) || !isGlobalPropertyConstant(expressionObj, ['measure-light']))) {
19620
- errors.push(new ValidationError(key, value, `${ propertyKey } does not support zoom or measure-light expressions when the model layer source is vector tile or GeoJSON.`));
19771
+ if (expressionObj && !isGlobalPropertyConstant(expressionObj, ['measure-light'])) {
19772
+ errors.push(new ValidationError(key, value, `${ propertyKey } does not support measure-light expressions when the model layer source is vector tile or GeoJSON.`));
19621
19773
  }
19622
19774
  }
19623
19775
  }
@@ -19980,7 +20132,9 @@ ${ JSON.stringify(filterExp, null, 2) }
19980
20132
  }
19981
20133
  const styleSpec = options.styleSpec;
19982
20134
  const lightSpec = styleSpec['light-3d'];
20135
+ const key = options.key;
19983
20136
  const style = options.style;
20137
+ const lights = options.style.lights;
19984
20138
  for (const key of [
19985
20139
  'type',
19986
20140
  'id'
@@ -19990,6 +20144,16 @@ ${ JSON.stringify(filterExp, null, 2) }
19990
20144
  return errors;
19991
20145
  }
19992
20146
  }
20147
+ if (light.type && lights) {
20148
+ for (let i = 0; i < options.arrayIndex; i++) {
20149
+ const lightType = unbundle(light.type);
20150
+ const otherLight = lights[i];
20151
+ if (unbundle(otherLight.type) === lightType) {
20152
+ // $FlowFixMe[prop-missing] - id.__line__ is added dynamically during the readStyle step
20153
+ errors.push(new ValidationError(key, light.id, `duplicate light type "${ light.type }", previously defined at line ${ otherLight.id.__line__ }`));
20154
+ }
20155
+ }
20156
+ }
19993
20157
  const lightType = `properties_light_${ light['type'] }`;
19994
20158
  if (!(lightType in styleSpec)) {
19995
20159
  errors = errors.concat([new ValidationError('light-3d', light, `Invalid light type ${ light['type'] }`)]);
@@ -20032,7 +20196,7 @@ ${ JSON.stringify(filterExp, null, 2) }
20032
20196
  styleSpec
20033
20197
  }));
20034
20198
  } else {
20035
- errors = errors.concat([new ValidationError(key, light[key], `unknown property "${ key }"`)]);
20199
+ errors = errors.concat([new ValidationWarning(key, light[key], `unknown property "${ key }"`)]);
20036
20200
  }
20037
20201
  }
20038
20202
  }
@@ -20073,7 +20237,7 @@ ${ JSON.stringify(filterExp, null, 2) }
20073
20237
  styleSpec
20074
20238
  }));
20075
20239
  } else {
20076
- errors = errors.concat([new ValidationError(key, terrain[key], `unknown property "${ key }"`)]);
20240
+ errors = errors.concat([new ValidationWarning(key, terrain[key], `unknown property "${ key }"`)]);
20077
20241
  }
20078
20242
  }
20079
20243
  if (!terrain.source) {
@@ -20123,7 +20287,7 @@ ${ JSON.stringify(filterExp, null, 2) }
20123
20287
  styleSpec
20124
20288
  }));
20125
20289
  } else {
20126
- errors = errors.concat([new ValidationError(key, fog[key], `unknown property "${ key }"`)]);
20290
+ errors = errors.concat([new ValidationWarning(key, fog[key], `unknown property "${ key }"`)]);
20127
20291
  }
20128
20292
  }
20129
20293
  return errors;
@@ -21487,6 +21651,9 @@ ${ JSON.stringify(filterExp, null, 2) }
21487
21651
  }
21488
21652
  };
21489
21653
  validateImports(imports);
21654
+ if (imports.length !== new Set(imports.map(i => i.id)).size) {
21655
+ errors.push(new ValidationError(null, null, 'Duplicate ids of imports'));
21656
+ }
21490
21657
  return {
21491
21658
  errors,
21492
21659
  sourcesCount