@versatiles/style 5.8.3 → 5.9.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 (56) hide show
  1. package/README.md +38 -32
  2. package/dist/index.d.ts +21 -15
  3. package/dist/index.js +719 -419
  4. package/dist/index.js.map +1 -1
  5. package/package.json +22 -17
  6. package/src/color/abstract.ts +1 -1
  7. package/src/color/hsl.test.ts +11 -18
  8. package/src/color/hsl.ts +11 -23
  9. package/src/color/hsv.test.ts +4 -10
  10. package/src/color/hsv.ts +38 -22
  11. package/src/color/index.test.ts +91 -5
  12. package/src/color/index.ts +4 -2
  13. package/src/color/random.test.ts +55 -1
  14. package/src/color/random.ts +130 -26
  15. package/src/color/rgb.test.ts +60 -38
  16. package/src/color/rgb.ts +34 -56
  17. package/src/color/utils.test.ts +4 -6
  18. package/src/color/utils.ts +2 -4
  19. package/src/guess_style/guess_style.test.ts +49 -43
  20. package/src/guess_style/guess_style.ts +67 -22
  21. package/src/guess_style/index.ts +0 -1
  22. package/src/index.test.ts +35 -8
  23. package/src/index.ts +41 -21
  24. package/src/lib/utils.test.ts +86 -3
  25. package/src/lib/utils.ts +12 -20
  26. package/src/shortbread/index.ts +0 -1
  27. package/src/shortbread/layers.test.ts +15 -1
  28. package/src/shortbread/layers.ts +204 -199
  29. package/src/shortbread/properties.test.ts +3 -4
  30. package/src/shortbread/properties.ts +18 -4
  31. package/src/shortbread/template.test.ts +7 -2
  32. package/src/shortbread/template.ts +7 -14
  33. package/src/style_builder/decorator.test.ts +4 -4
  34. package/src/style_builder/decorator.ts +46 -31
  35. package/src/style_builder/recolor.test.ts +6 -31
  36. package/src/style_builder/recolor.ts +38 -31
  37. package/src/style_builder/style_builder.test.ts +50 -13
  38. package/src/style_builder/style_builder.ts +35 -34
  39. package/src/style_builder/types.ts +44 -2
  40. package/src/styles/LICENSE.md +15 -15
  41. package/src/styles/colorful.test.ts +91 -0
  42. package/src/styles/colorful.ts +229 -122
  43. package/src/styles/eclipse.ts +1 -1
  44. package/src/styles/empty.ts +1 -1
  45. package/src/styles/graybeard.ts +2 -2
  46. package/src/styles/index.ts +2 -3
  47. package/src/styles/neutrino.ts +14 -16
  48. package/src/styles/satellite.test.ts +146 -0
  49. package/src/styles/satellite.ts +106 -0
  50. package/src/styles/shadow.ts +2 -2
  51. package/src/types/index.ts +0 -1
  52. package/src/types/maplibre.ts +17 -3
  53. package/src/types/tilejson.test.ts +17 -14
  54. package/src/types/tilejson.ts +59 -37
  55. package/src/types/vector_layer.test.ts +5 -2
  56. package/src/types/vector_layer.ts +8 -10
package/dist/index.js CHANGED
@@ -145,7 +145,7 @@ function mod(value, max) {
145
145
  value = value % max;
146
146
  if (value < 0)
147
147
  value += max;
148
- if (value == 0)
148
+ if (value === 0)
149
149
  return 0;
150
150
  return value;
151
151
  }
@@ -236,7 +236,7 @@ function randomColor(options) {
236
236
  function randomWithin(range) {
237
237
  //Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
238
238
  seed = (seed * 9301 + 49297) % 233280;
239
- return Math.floor(range[0] + seed / 233280.0 * (range[1] - range[0]));
239
+ return Math.floor(range[0] + (seed / 233280.0) * (range[1] - range[0]));
240
240
  }
241
241
  }
242
242
  function inputToSeed(input) {
@@ -261,14 +261,81 @@ function initColorDictionary() {
261
261
  brightnessRange: [colorful[1], greyest[1]],
262
262
  });
263
263
  };
264
- defineColor('monochrome', null, [[0, 0], [100, 0]]);
265
- defineColor('red', [-26, 18], [[20, 100], [30, 92], [40, 89], [50, 85], [60, 78], [70, 70], [80, 60], [90, 55], [100, 50]]);
266
- defineColor('orange', [18, 46], [[20, 100], [30, 93], [40, 88], [50, 86], [60, 85], [70, 70], [100, 70]]);
267
- defineColor('yellow', [46, 62], [[25, 100], [40, 94], [50, 89], [60, 86], [70, 84], [80, 82], [90, 80], [100, 75]]);
268
- defineColor('green', [62, 178], [[30, 100], [40, 90], [50, 85], [60, 81], [70, 74], [80, 64], [90, 50], [100, 40]]);
269
- defineColor('blue', [178, 257], [[20, 100], [30, 86], [40, 80], [50, 74], [60, 60], [70, 52], [80, 44], [90, 39], [100, 35]]);
270
- defineColor('purple', [257, 282], [[20, 100], [30, 87], [40, 79], [50, 70], [60, 65], [70, 59], [80, 52], [90, 45], [100, 42]]);
271
- defineColor('pink', [282, 334], [[20, 100], [30, 90], [40, 86], [60, 84], [80, 80], [90, 75], [100, 73]]);
264
+ defineColor('monochrome', null, [
265
+ [0, 0],
266
+ [100, 0],
267
+ ]);
268
+ defineColor('red', [-26, 18], [
269
+ [20, 100],
270
+ [30, 92],
271
+ [40, 89],
272
+ [50, 85],
273
+ [60, 78],
274
+ [70, 70],
275
+ [80, 60],
276
+ [90, 55],
277
+ [100, 50],
278
+ ]);
279
+ defineColor('orange', [18, 46], [
280
+ [20, 100],
281
+ [30, 93],
282
+ [40, 88],
283
+ [50, 86],
284
+ [60, 85],
285
+ [70, 70],
286
+ [100, 70],
287
+ ]);
288
+ defineColor('yellow', [46, 62], [
289
+ [25, 100],
290
+ [40, 94],
291
+ [50, 89],
292
+ [60, 86],
293
+ [70, 84],
294
+ [80, 82],
295
+ [90, 80],
296
+ [100, 75],
297
+ ]);
298
+ defineColor('green', [62, 178], [
299
+ [30, 100],
300
+ [40, 90],
301
+ [50, 85],
302
+ [60, 81],
303
+ [70, 74],
304
+ [80, 64],
305
+ [90, 50],
306
+ [100, 40],
307
+ ]);
308
+ defineColor('blue', [178, 257], [
309
+ [20, 100],
310
+ [30, 86],
311
+ [40, 80],
312
+ [50, 74],
313
+ [60, 60],
314
+ [70, 52],
315
+ [80, 44],
316
+ [90, 39],
317
+ [100, 35],
318
+ ]);
319
+ defineColor('purple', [257, 282], [
320
+ [20, 100],
321
+ [30, 87],
322
+ [40, 79],
323
+ [50, 70],
324
+ [60, 65],
325
+ [70, 59],
326
+ [80, 52],
327
+ [90, 45],
328
+ [100, 42],
329
+ ]);
330
+ defineColor('pink', [282, 334], [
331
+ [20, 100],
332
+ [30, 90],
333
+ [40, 86],
334
+ [60, 84],
335
+ [80, 80],
336
+ [90, 75],
337
+ [100, 73],
338
+ ]);
272
339
  return dict;
273
340
  }
274
341
  function getColorInfo(hue) {
@@ -280,7 +347,7 @@ function getColorInfo(hue) {
280
347
  return color;
281
348
  }
282
349
  }
283
- throw Error('Color hue value not found');
350
+ throw new Error(`getColorInfo: No color info found for hue value ${hue}. This indicates a gap in the color dictionary hue ranges.`);
284
351
  }
285
352
 
286
353
  /**
@@ -370,7 +437,9 @@ class RGB extends Color {
370
437
  return `#${r}${g}${b}`.toUpperCase();
371
438
  }
372
439
  else {
373
- const a = Math.round(this.a * 255).toString(16).padStart(2, '0');
440
+ const a = Math.round(this.a * 255)
441
+ .toString(16)
442
+ .padStart(2, '0');
374
443
  return `#${r}${g}${b}${a}`.toUpperCase();
375
444
  }
376
445
  }
@@ -408,7 +477,6 @@ class RGB extends Color {
408
477
  s = delta / (2 - max - min);
409
478
  return new HSL(h, s * 100, l * 100, this.a);
410
479
  }
411
- ;
412
480
  /**
413
481
  * Converts the RGB color to an HSV color.
414
482
  *
@@ -433,9 +501,9 @@ class RGB extends Color {
433
501
  if (r === v)
434
502
  h = bdif - gdif;
435
503
  else if (g === v)
436
- h = (1 / 3) + rdif - bdif;
504
+ h = 1 / 3 + rdif - bdif;
437
505
  else if (b === v)
438
- h = (2 / 3) + gdif - rdif;
506
+ h = 2 / 3 + gdif - rdif;
439
507
  if (h < 0)
440
508
  h += 1;
441
509
  else if (h > 1)
@@ -451,14 +519,6 @@ class RGB extends Color {
451
519
  asRGB() {
452
520
  return this.clone();
453
521
  }
454
- /**
455
- * Returns the RGB color.
456
- *
457
- * @returns The current RGB instance.
458
- */
459
- toRGB() {
460
- return this;
461
- }
462
522
  /**
463
523
  * Parses a string or Color instance into an RGB color.
464
524
  *
@@ -551,7 +611,7 @@ class RGB extends Color {
551
611
  if (value > 1)
552
612
  value = 1;
553
613
  const a = 1 - Math.abs(value);
554
- const b = (value < 0) ? 0 : 255 * value;
614
+ const b = value < 0 ? 0 : 255 * value;
555
615
  return new RGB(this.r * a + b, this.g * a + b, this.b * a + b, this.a);
556
616
  }
557
617
  /**
@@ -682,7 +742,7 @@ class HSV extends Color {
682
742
  const v = this.v / 100;
683
743
  const k = (2 - s) * v;
684
744
  const q = k < 1 ? k : 2 - k;
685
- return new HSL(this.h, q == 0 ? 0 : 100 * s * v / q, 100 * k / 2, this.a);
745
+ return new HSL(this.h, q === 0 ? 0 : (100 * s * v) / q, (100 * k) / 2, this.a);
686
746
  }
687
747
  /**
688
748
  * Returns the current HSV color.
@@ -849,13 +909,6 @@ class HSL extends Color {
849
909
  asHSL() {
850
910
  return this.clone();
851
911
  }
852
- /**
853
- * Returns the current HSL color.
854
- * @returns The current HSL color.
855
- */
856
- toHSL() {
857
- return this;
858
- }
859
912
  /**
860
913
  * Converts the HSL color to an HSV color.
861
914
  * @returns A new HSV color representing the same color.
@@ -963,7 +1016,7 @@ Color.parse = function (input) {
963
1016
  case 'hsla(':
964
1017
  return HSL.parse(input);
965
1018
  default:
966
- throw Error('Unknown color format: ' + input);
1019
+ throw new Error(`Color.parse: Unknown color format "${input}". Expected formats: "#RRGGBB", "#RGB", "rgb(...)", "rgba(...)", "hsl(...)", or "hsla(...)".`);
967
1020
  }
968
1021
  };
969
1022
  Color.HSL = HSL;
@@ -983,19 +1036,12 @@ function getShortbreadTemplate() {
983
1036
  sources: {
984
1037
  'versatiles-shortbread': {
985
1038
  attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
986
- tiles: [
987
- 'https://tiles.versatiles.org/tiles/osm/{z}/{x}/{y}',
988
- ],
1039
+ tiles: ['https://tiles.versatiles.org/tiles/osm/{z}/{x}/{y}'],
989
1040
  type: 'vector',
990
1041
  scheme: 'xyz',
991
- bounds: [
992
- -180,
993
- -85.0511287798066,
994
- 180,
995
- 85.0511287798066,
996
- ],
1042
+ bounds: [-180, -85.0511287798066, 180, 85.0511287798066],
997
1043
  minzoom: 0,
998
- maxzoom
1044
+ maxzoom,
999
1045
  },
1000
1046
  },
1001
1047
  layers: [],
@@ -1006,12 +1052,7 @@ function getShortbreadLayers(option) {
1006
1052
  const { language } = option;
1007
1053
  let nameField = ['get', 'name'];
1008
1054
  if (language) {
1009
- nameField = [
1010
- 'case',
1011
- ['to-boolean', ['get', 'name_' + language]],
1012
- ['get', 'name_' + language],
1013
- ['get', 'name']
1014
- ];
1055
+ nameField = ['case', ['to-boolean', ['get', 'name_' + language]], ['get', 'name_' + language], ['get', 'name']];
1015
1056
  }
1016
1057
  return [
1017
1058
  // background
@@ -1021,14 +1062,27 @@ function getShortbreadLayers(option) {
1021
1062
  // land
1022
1063
  {
1023
1064
  id: 'land-glacier',
1024
- type: 'fill', 'source-layer': 'water_polygons',
1065
+ type: 'fill',
1066
+ 'source-layer': 'water_polygons',
1025
1067
  filter: ['all', ['==', 'kind', 'glacier']],
1026
1068
  },
1027
1069
  ...[
1028
1070
  { id: 'commercial', kinds: ['commercial', 'retail'] },
1029
1071
  { id: 'industrial', kinds: ['industrial', 'quarry', 'railway'] },
1030
1072
  { id: 'residential', kinds: ['garages', 'residential'] },
1031
- { id: 'agriculture', kinds: ['brownfield', 'farmland', 'farmyard', 'greenfield', 'greenhouse_horticulture', 'orchard', 'plant_nursery', 'vineyard'] },
1073
+ {
1074
+ id: 'agriculture',
1075
+ kinds: [
1076
+ 'brownfield',
1077
+ 'farmland',
1078
+ 'farmyard',
1079
+ 'greenfield',
1080
+ 'greenhouse_horticulture',
1081
+ 'orchard',
1082
+ 'plant_nursery',
1083
+ 'vineyard',
1084
+ ],
1085
+ },
1032
1086
  { id: 'waste', kinds: ['landfill'] },
1033
1087
  { id: 'park', kinds: ['park', 'village_green', 'recreation_ground'] },
1034
1088
  { id: 'garden', kinds: ['allotments', 'garden'] },
@@ -1044,45 +1098,63 @@ function getShortbreadLayers(option) {
1044
1098
  id: 'land-' + id,
1045
1099
  type: 'fill',
1046
1100
  'source-layer': 'land',
1047
- filter: ['all',
1048
- ['in', 'kind', ...kinds],
1049
- ],
1101
+ filter: ['all', ['in', 'kind', ...kinds]],
1050
1102
  })),
1051
1103
  // water-lines
1052
1104
  ...['river', 'canal', 'stream', 'ditch'].map((t) => ({
1053
1105
  id: 'water-' + t,
1054
1106
  type: 'line',
1055
1107
  'source-layer': 'water_lines',
1056
- filter: ['all',
1057
- ['in', 'kind', t],
1058
- ['!=', 'tunnel', true],
1059
- ['!=', 'bridge', true],
1060
- ],
1108
+ filter: ['all', ['in', 'kind', t], ['!=', 'tunnel', true], ['!=', 'bridge', true]],
1061
1109
  })),
1062
1110
  // water polygons
1063
1111
  {
1064
1112
  id: 'water-area',
1065
- type: 'fill', 'source-layer': 'water_polygons',
1113
+ type: 'fill',
1114
+ 'source-layer': 'water_polygons',
1066
1115
  filter: ['==', 'kind', 'water'],
1067
1116
  },
1068
1117
  {
1069
1118
  id: 'water-area-river',
1070
- type: 'fill', 'source-layer': 'water_polygons',
1119
+ type: 'fill',
1120
+ 'source-layer': 'water_polygons',
1071
1121
  filter: ['==', 'kind', 'river'],
1072
1122
  },
1073
1123
  {
1074
1124
  id: 'water-area-small',
1075
- type: 'fill', 'source-layer': 'water_polygons',
1125
+ type: 'fill',
1126
+ 'source-layer': 'water_polygons',
1076
1127
  filter: ['in', 'kind', 'reservoir', 'basin', 'dock'],
1077
1128
  },
1078
1129
  // dam
1079
1130
  { id: 'water-dam-area', type: 'fill', 'source-layer': 'dam_polygons', filter: ['==', 'kind', 'dam'] },
1080
1131
  { id: 'water-dam', type: 'line', 'source-layer': 'dam_lines', filter: ['==', 'kind', 'dam'] },
1081
1132
  // pier
1082
- { id: 'water-pier-area', type: 'fill', 'source-layer': 'pier_polygons', filter: ['in', 'kind', 'pier', 'breakwater', 'groyne'] },
1083
- { id: 'water-pier', type: 'line', 'source-layer': 'pier_lines', filter: ['in', 'kind', 'pier', 'breakwater', 'groyne'] },
1133
+ {
1134
+ id: 'water-pier-area',
1135
+ type: 'fill',
1136
+ 'source-layer': 'pier_polygons',
1137
+ filter: ['in', 'kind', 'pier', 'breakwater', 'groyne'],
1138
+ },
1139
+ {
1140
+ id: 'water-pier',
1141
+ type: 'line',
1142
+ 'source-layer': 'pier_lines',
1143
+ filter: ['in', 'kind', 'pier', 'breakwater', 'groyne'],
1144
+ },
1084
1145
  // site
1085
- ...['danger_area', 'sports_center', 'university', 'college', 'school', 'hospital', 'prison', 'parking', 'bicycle_parking', 'construction'].map((t) => ({
1146
+ ...[
1147
+ 'danger_area',
1148
+ 'sports_center',
1149
+ 'university',
1150
+ 'college',
1151
+ 'school',
1152
+ 'hospital',
1153
+ 'prison',
1154
+ 'parking',
1155
+ 'bicycle_parking',
1156
+ 'construction',
1157
+ ].map((t) => ({
1086
1158
  id: 'site-' + t.replace(/_/g, ''),
1087
1159
  type: 'fill',
1088
1160
  'source-layer': 'sites',
@@ -1091,32 +1163,44 @@ function getShortbreadLayers(option) {
1091
1163
  // airport
1092
1164
  {
1093
1165
  id: 'airport-area',
1094
- type: 'fill', 'source-layer': 'street_polygons', filter: ['in', 'kind', 'runway', 'taxiway'],
1166
+ type: 'fill',
1167
+ 'source-layer': 'street_polygons',
1168
+ filter: ['in', 'kind', 'runway', 'taxiway'],
1095
1169
  },
1096
1170
  {
1097
1171
  id: 'airport-taxiway:outline',
1098
- type: 'line', 'source-layer': 'streets', filter: ['==', 'kind', 'taxiway'],
1172
+ type: 'line',
1173
+ 'source-layer': 'streets',
1174
+ filter: ['==', 'kind', 'taxiway'],
1099
1175
  },
1100
1176
  {
1101
1177
  id: 'airport-runway:outline',
1102
- type: 'line', 'source-layer': 'streets', filter: ['==', 'kind', 'runway'],
1178
+ type: 'line',
1179
+ 'source-layer': 'streets',
1180
+ filter: ['==', 'kind', 'runway'],
1103
1181
  },
1104
1182
  {
1105
1183
  id: 'airport-taxiway',
1106
- type: 'line', 'source-layer': 'streets', filter: ['==', 'kind', 'taxiway'],
1184
+ type: 'line',
1185
+ 'source-layer': 'streets',
1186
+ filter: ['==', 'kind', 'taxiway'],
1107
1187
  },
1108
1188
  {
1109
1189
  id: 'airport-runway',
1110
- type: 'line', 'source-layer': 'streets', filter: ['==', 'kind', 'runway'],
1190
+ type: 'line',
1191
+ 'source-layer': 'streets',
1192
+ filter: ['==', 'kind', 'runway'],
1111
1193
  },
1112
1194
  // building
1113
1195
  {
1114
1196
  id: 'building:outline',
1115
- type: 'fill', 'source-layer': 'buildings',
1197
+ type: 'fill',
1198
+ 'source-layer': 'buildings',
1116
1199
  },
1117
1200
  {
1118
1201
  id: 'building',
1119
- type: 'fill', 'source-layer': 'buildings',
1202
+ type: 'fill',
1203
+ 'source-layer': 'buildings',
1120
1204
  },
1121
1205
  // tunnel-, street-, bridges-bridge
1122
1206
  ...['tunnel', 'street', 'bridge'].flatMap((c) => {
@@ -1131,7 +1215,10 @@ function getShortbreadLayers(option) {
1131
1215
  suffixes = [':outline', ''];
1132
1216
  break;
1133
1217
  case 'street':
1134
- filter = [['!=', 'bridge', true], ['!=', 'tunnel', true]];
1218
+ filter = [
1219
+ ['!=', 'bridge', true],
1220
+ ['!=', 'tunnel', true],
1221
+ ];
1135
1222
  prefix = '';
1136
1223
  suffixes = [':outline', ''];
1137
1224
  break;
@@ -1155,85 +1242,64 @@ function getShortbreadLayers(option) {
1155
1242
  type: 'fill',
1156
1243
  'source-layer': 'bridges',
1157
1244
  });
1158
- suffixes.forEach(suffix => {
1245
+ suffixes.forEach((suffix) => {
1159
1246
  // pedestrian zone — no outline
1160
1247
  if (suffix === ':outline')
1161
1248
  results.push({
1162
1249
  id: prefix + 'street-pedestrian-zone',
1163
1250
  type: 'fill',
1164
1251
  'source-layer': 'street_polygons',
1165
- filter: ['all',
1166
- ...filter,
1167
- ['==', 'kind', 'pedestrian'],
1168
- ],
1252
+ filter: ['all', ...filter, ['==', 'kind', 'pedestrian']],
1169
1253
  });
1170
1254
  // non-car streets
1171
- ['footway', 'steps', 'path', 'cycleway'].forEach(t => {
1255
+ ['footway', 'steps', 'path', 'cycleway'].forEach((t) => {
1172
1256
  results.push({
1173
1257
  id: prefix + 'way-' + t.replace(/_/g, '') + suffix,
1174
1258
  type: 'line',
1175
1259
  'source-layer': 'streets',
1176
- filter: ['all',
1177
- ...filter,
1178
- ['in', 'kind', t],
1179
- ],
1260
+ filter: ['all', ...filter, ['in', 'kind', t]],
1180
1261
  });
1181
1262
  });
1182
1263
  // no links
1183
- ['track', 'pedestrian', 'service', 'living_street', 'residential', 'unclassified'].forEach(t => {
1264
+ ['track', 'pedestrian', 'service', 'living_street', 'residential', 'unclassified'].forEach((t) => {
1184
1265
  results.push({
1185
1266
  id: prefix + 'street-' + t.replace(/_/g, '') + suffix,
1186
1267
  type: 'line',
1187
1268
  'source-layer': 'streets',
1188
- filter: ['all',
1189
- ['==', 'kind', t],
1190
- ...filter,
1191
- ],
1269
+ filter: ['all', ['==', 'kind', t], ...filter],
1192
1270
  });
1193
1271
  });
1194
1272
  // no links, bicycle=designated
1195
1273
  if (suffix === '')
1196
- ['track', 'pedestrian', 'service', 'living_street', 'residential', 'unclassified'].forEach(t => {
1274
+ ['track', 'pedestrian', 'service', 'living_street', 'residential', 'unclassified'].forEach((t) => {
1197
1275
  results.push({
1198
1276
  id: prefix + 'street-' + t.replace(/_/g, '') + '-bicycle',
1199
1277
  type: 'line',
1200
1278
  'source-layer': 'streets',
1201
- filter: ['all',
1202
- ['==', 'kind', t],
1203
- ['==', 'bicycle', 'designated'],
1204
- ...filter,
1205
- ],
1279
+ filter: ['all', ['==', 'kind', t], ['==', 'bicycle', 'designated'], ...filter],
1206
1280
  });
1207
1281
  });
1208
1282
  // links
1209
- ['tertiary', 'secondary', 'primary', 'trunk', 'motorway'].forEach(t => {
1283
+ ['tertiary', 'secondary', 'primary', 'trunk', 'motorway'].forEach((t) => {
1210
1284
  results.push({
1211
1285
  id: prefix + 'street-' + t.replace(/_/g, '') + '-link' + suffix,
1212
1286
  type: 'line',
1213
1287
  'source-layer': 'streets',
1214
- filter: ['all',
1215
- ...filter,
1216
- ['in', 'kind', t],
1217
- ['==', 'link', true],
1218
- ],
1288
+ filter: ['all', ...filter, ['in', 'kind', t], ['==', 'link', true]],
1219
1289
  });
1220
1290
  });
1221
1291
  // main
1222
- ['tertiary', 'secondary', 'primary', 'trunk', 'motorway'].forEach(t => {
1292
+ ['tertiary', 'secondary', 'primary', 'trunk', 'motorway'].forEach((t) => {
1223
1293
  results.push({
1224
1294
  id: prefix + 'street-' + t.replace(/_/g, '') + suffix,
1225
1295
  type: 'line',
1226
1296
  'source-layer': 'streets',
1227
- filter: ['all',
1228
- ...filter,
1229
- ['in', 'kind', t],
1230
- ['!=', 'link', true],
1231
- ],
1297
+ filter: ['all', ...filter, ['in', 'kind', t], ['!=', 'link', true]],
1232
1298
  });
1233
1299
  });
1234
1300
  });
1235
1301
  // separate outline for trains
1236
- [':outline', ''].forEach(suffix => {
1302
+ [':outline', ''].forEach((suffix) => {
1237
1303
  // with service distinction
1238
1304
  ['rail', 'light_rail', 'subway', 'narrow_gauge', 'tram'].reverse().forEach((t) => {
1239
1305
  // main rail
@@ -1241,22 +1307,14 @@ function getShortbreadLayers(option) {
1241
1307
  id: prefix + 'transport-' + t.replace(/_/g, '') + suffix,
1242
1308
  type: 'line',
1243
1309
  'source-layer': 'streets',
1244
- filter: ['all',
1245
- ['in', 'kind', t],
1246
- ['!has', 'service'],
1247
- ...filter,
1248
- ],
1310
+ filter: ['all', ['in', 'kind', t], ['!has', 'service'], ...filter],
1249
1311
  });
1250
1312
  // service rail (crossover, siding, spur, yard)
1251
1313
  results.push({
1252
1314
  id: prefix + 'transport-' + t.replace(/_/g, '') + '-service' + suffix,
1253
1315
  type: 'line',
1254
1316
  'source-layer': 'streets',
1255
- filter: ['all',
1256
- ['in', 'kind', t],
1257
- ['has', 'service'],
1258
- ...filter,
1259
- ],
1317
+ filter: ['all', ['in', 'kind', t], ['has', 'service'], ...filter],
1260
1318
  });
1261
1319
  });
1262
1320
  // other transport
@@ -1265,23 +1323,19 @@ function getShortbreadLayers(option) {
1265
1323
  id: prefix + 'transport-' + t.replace(/_/g, '') + suffix,
1266
1324
  type: 'line',
1267
1325
  'source-layer': 'streets',
1268
- filter: ['all',
1269
- ['in', 'kind', t],
1270
- ...filter,
1271
- ],
1326
+ filter: ['all', ['in', 'kind', t], ...filter],
1272
1327
  });
1273
1328
  });
1274
1329
  if (c === 'street') {
1275
1330
  // aerialway, no bridges, above street evel
1276
- ['cable_car', 'gondola', 'goods', 'chair_lift', 'drag_lift', 't-bar', 'j-bar', 'platter', 'rope-tow'].reverse().forEach((t) => {
1331
+ ['cable_car', 'gondola', 'goods', 'chair_lift', 'drag_lift', 't-bar', 'j-bar', 'platter', 'rope-tow']
1332
+ .reverse()
1333
+ .forEach((t) => {
1277
1334
  results.push({
1278
1335
  id: 'aerialway-' + t.replace(/[_-]+/g, '') + suffix,
1279
1336
  type: 'line',
1280
1337
  'source-layer': 'aerialways',
1281
- filter: ['all',
1282
- ...filter,
1283
- ['in', 'kind', t],
1284
- ],
1338
+ filter: ['all', ...filter, ['in', 'kind', t]],
1285
1339
  });
1286
1340
  });
1287
1341
  // ferry — only on street level
@@ -1307,7 +1361,8 @@ function getShortbreadLayers(option) {
1307
1361
  id: 'boundary-country' + suffix,
1308
1362
  type: 'line',
1309
1363
  'source-layer': 'boundaries',
1310
- filter: ['all',
1364
+ filter: [
1365
+ 'all',
1311
1366
  ['==', 'admin_level', 2],
1312
1367
  ['!=', 'maritime', true],
1313
1368
  ['!=', 'disputed', true],
@@ -1318,7 +1373,8 @@ function getShortbreadLayers(option) {
1318
1373
  id: 'boundary-country-disputed' + suffix,
1319
1374
  type: 'line',
1320
1375
  'source-layer': 'boundaries',
1321
- filter: ['all',
1376
+ filter: [
1377
+ 'all',
1322
1378
  ['==', 'admin_level', 2],
1323
1379
  ['==', 'disputed', true],
1324
1380
  ['!=', 'maritime', true],
@@ -1329,7 +1385,8 @@ function getShortbreadLayers(option) {
1329
1385
  id: 'boundary-country-maritime' + suffix,
1330
1386
  type: 'line',
1331
1387
  'source-layer': 'boundaries',
1332
- filter: ['all',
1388
+ filter: [
1389
+ 'all',
1333
1390
  ['==', 'admin_level', 2],
1334
1391
  ['==', 'maritime', true],
1335
1392
  ['!=', 'disputed', true],
@@ -1340,7 +1397,8 @@ function getShortbreadLayers(option) {
1340
1397
  id: 'boundary-state' + suffix,
1341
1398
  type: 'line',
1342
1399
  'source-layer': 'boundaries',
1343
- filter: ['all',
1400
+ filter: [
1401
+ 'all',
1344
1402
  ['==', 'admin_level', 4],
1345
1403
  ['!=', 'maritime', true],
1346
1404
  ['!=', 'disputed', true],
@@ -1382,7 +1440,14 @@ function getShortbreadLayers(option) {
1382
1440
  layout: { 'text-field': nameField },
1383
1441
  })),
1384
1442
  // label-place of small places
1385
- ...[/*'locality', 'island', 'farm', 'dwelling',*/ 'neighbourhood', 'quarter', 'suburb', 'hamlet', 'village', 'town'].map((id) => ({
1443
+ ...[
1444
+ /*'locality', 'island', 'farm', 'dwelling',*/ 'neighbourhood',
1445
+ 'quarter',
1446
+ 'suburb',
1447
+ 'hamlet',
1448
+ 'village',
1449
+ 'town',
1450
+ ].map((id) => ({
1386
1451
  id: 'label-place-' + id.replace(/_/g, ''),
1387
1452
  type: 'symbol',
1388
1453
  'source-layer': 'place_labels',
@@ -1409,31 +1474,21 @@ function getShortbreadLayers(option) {
1409
1474
  id: 'label-boundary-country-small',
1410
1475
  type: 'symbol',
1411
1476
  'source-layer': 'boundary_labels',
1412
- filter: ['all',
1413
- ['in', 'admin_level', 2, '2'],
1414
- ['<=', 'way_area', 10000000],
1415
- ],
1477
+ filter: ['all', ['in', 'admin_level', 2, '2'], ['<=', 'way_area', 10000000]],
1416
1478
  layout: { 'text-field': nameField },
1417
1479
  },
1418
1480
  {
1419
1481
  id: 'label-boundary-country-medium',
1420
1482
  type: 'symbol',
1421
1483
  'source-layer': 'boundary_labels',
1422
- filter: ['all',
1423
- ['in', 'admin_level', 2, '2'],
1424
- ['<', 'way_area', 90000000],
1425
- ['>', 'way_area', 10000000],
1426
- ],
1484
+ filter: ['all', ['in', 'admin_level', 2, '2'], ['<', 'way_area', 90000000], ['>', 'way_area', 10000000]],
1427
1485
  layout: { 'text-field': nameField },
1428
1486
  },
1429
1487
  {
1430
1488
  id: 'label-boundary-country-large',
1431
1489
  type: 'symbol',
1432
1490
  'source-layer': 'boundary_labels',
1433
- filter: ['all',
1434
- ['in', 'admin_level', 2, '2'],
1435
- ['>=', 'way_area', 90000000],
1436
- ],
1491
+ filter: ['all', ['in', 'admin_level', 2, '2'], ['>=', 'way_area', 90000000]],
1437
1492
  layout: { 'text-field': nameField },
1438
1493
  },
1439
1494
  // marking
@@ -1441,7 +1496,8 @@ function getShortbreadLayers(option) {
1441
1496
  id: 'marking-oneway', // streets → oneway
1442
1497
  type: 'symbol',
1443
1498
  'source-layer': 'streets',
1444
- filter: ['all',
1499
+ filter: [
1500
+ 'all',
1445
1501
  ['==', 'oneway', true],
1446
1502
  ['in', 'kind', 'trunk', 'primary', 'secondary', 'tertiary', 'unclassified', 'residential', 'living_street'],
1447
1503
  ],
@@ -1458,7 +1514,8 @@ function getShortbreadLayers(option) {
1458
1514
  id: 'marking-oneway-reverse', // oneway_reverse
1459
1515
  type: 'symbol',
1460
1516
  'source-layer': 'streets',
1461
- filter: ['all',
1517
+ filter: [
1518
+ 'all',
1462
1519
  ['==', 'oneway_reverse', true],
1463
1520
  ['in', 'kind', 'trunk', 'primary', 'secondary', 'tertiary', 'unclassified', 'residential', 'living_street'],
1464
1521
  ],
@@ -1475,10 +1532,7 @@ function getShortbreadLayers(option) {
1475
1532
  id: 'marking-bicycle', // bicycle=designated or kind=cycleway
1476
1533
  type: 'symbol',
1477
1534
  'source-layer': 'streets',
1478
- filter: ['all',
1479
- ['==', 'bicycle', 'designated'],
1480
- ['==', 'kind', 'cycleway'],
1481
- ],
1535
+ filter: ['all', ['==', 'bicycle', 'designated'], ['==', 'kind', 'cycleway']],
1482
1536
  layout: {
1483
1537
  'symbol-placement': 'line',
1484
1538
  'symbol-spacing': 50,
@@ -1503,50 +1557,35 @@ function getShortbreadLayers(option) {
1503
1557
  id: 'symbol-transit-subway',
1504
1558
  type: 'symbol',
1505
1559
  'source-layer': 'public_transport',
1506
- filter: ['all',
1507
- ['in', 'kind', 'station', 'halt'],
1508
- ['==', 'station', 'subway'],
1509
- ],
1560
+ filter: ['all', ['in', 'kind', 'station', 'halt'], ['==', 'station', 'subway']],
1510
1561
  layout: { 'text-field': nameField },
1511
1562
  },
1512
1563
  {
1513
1564
  id: 'symbol-transit-lightrail',
1514
1565
  type: 'symbol',
1515
1566
  'source-layer': 'public_transport',
1516
- filter: ['all',
1517
- ['in', 'kind', 'station', 'halt'],
1518
- ['==', 'station', 'light_rail'],
1519
- ],
1567
+ filter: ['all', ['in', 'kind', 'station', 'halt'], ['==', 'station', 'light_rail']],
1520
1568
  layout: { 'text-field': nameField },
1521
1569
  },
1522
1570
  {
1523
1571
  id: 'symbol-transit-station',
1524
1572
  type: 'symbol',
1525
1573
  'source-layer': 'public_transport',
1526
- filter: ['all',
1527
- ['in', 'kind', 'station', 'halt'],
1528
- ['!in', 'station', 'light_rail', 'subway'],
1529
- ],
1574
+ filter: ['all', ['in', 'kind', 'station', 'halt'], ['!in', 'station', 'light_rail', 'subway']],
1530
1575
  layout: { 'text-field': nameField },
1531
1576
  },
1532
1577
  {
1533
1578
  id: 'symbol-transit-airfield',
1534
1579
  type: 'symbol',
1535
1580
  'source-layer': 'public_transport',
1536
- filter: ['all',
1537
- ['==', 'kind', 'aerodrome'],
1538
- ['!has', 'iata'],
1539
- ],
1581
+ filter: ['all', ['==', 'kind', 'aerodrome'], ['!has', 'iata']],
1540
1582
  layout: { 'text-field': nameField },
1541
1583
  },
1542
1584
  {
1543
1585
  id: 'symbol-transit-airport',
1544
1586
  type: 'symbol',
1545
1587
  'source-layer': 'public_transport',
1546
- filter: ['all',
1547
- ['==', 'kind', 'aerodrome'],
1548
- ['has', 'iata'],
1549
- ],
1588
+ filter: ['all', ['==', 'kind', 'aerodrome'], ['has', 'iata']],
1550
1589
  layout: { 'text-field': nameField },
1551
1590
  },
1552
1591
  ];
@@ -1915,7 +1954,12 @@ const propertyDefs = [
1915
1954
  { parent: 'layout', types: 'symbol', key: 'text-rotation-alignment', valueType: 'enum' },
1916
1955
  { parent: 'layout', types: 'symbol', key: 'text-size', short: 'size', valueType: 'number' },
1917
1956
  { parent: 'layout', types: 'symbol', key: 'text-transform', valueType: 'enum' },
1918
- { parent: 'layout', types: 'symbol', key: 'text-variable-anchor-offset', valueType: 'variableAnchorOffsetCollection' },
1957
+ {
1958
+ parent: 'layout',
1959
+ types: 'symbol',
1960
+ key: 'text-variable-anchor-offset',
1961
+ valueType: 'variableAnchorOffsetCollection',
1962
+ },
1919
1963
  { parent: 'layout', types: 'symbol', key: 'text-variable-anchor', valueType: 'array' },
1920
1964
  { parent: 'layout', types: 'symbol', key: 'text-writing-mode', valueType: 'array' },
1921
1965
  { parent: 'paint', types: 'background', key: 'background-color', short: 'color', valueType: 'color' },
@@ -1996,7 +2040,8 @@ function deepClone(obj) {
1996
2040
  case 'string':
1997
2041
  case 'undefined':
1998
2042
  return obj;
1999
- default: throw new Error(`Not implemented yet: "${type}" case`);
2043
+ default:
2044
+ throw new Error(`Not implemented yet: "${type}" case`);
2000
2045
  }
2001
2046
  }
2002
2047
  if (isSimpleObject(obj)) {
@@ -2010,9 +2055,7 @@ function deepClone(obj) {
2010
2055
  }
2011
2056
  if (obj == null)
2012
2057
  return obj;
2013
- console.log('obj', obj);
2014
- console.log('obj.prototype', Object.getPrototypeOf(obj));
2015
- throw Error();
2058
+ throw new Error(`deepClone: Unsupported object type "${Object.getPrototypeOf(obj)?.constructor?.name ?? 'unknown'}"`);
2016
2059
  }
2017
2060
  function isSimpleObject(item) {
2018
2061
  if (item === null)
@@ -2038,7 +2081,7 @@ function isBasicType(item) {
2038
2081
  case 'object':
2039
2082
  return false;
2040
2083
  default:
2041
- throw Error('unknown type: ' + typeof item);
2084
+ throw new Error(`isBasicType: Unknown type "${typeof item}"`);
2042
2085
  }
2043
2086
  }
2044
2087
  function deepMerge(source0, ...sources) {
@@ -2050,9 +2093,7 @@ function deepMerge(source0, ...sources) {
2050
2093
  if (!(key in source))
2051
2094
  continue;
2052
2095
  const sourceValue = source[key];
2053
- // *********
2054
- // overwrite
2055
- // *********
2096
+ // Handle basic types (number, string, boolean) - always overwrite
2056
2097
  switch (typeof sourceValue) {
2057
2098
  case 'number':
2058
2099
  case 'string':
@@ -2060,28 +2101,23 @@ function deepMerge(source0, ...sources) {
2060
2101
  target[key] = sourceValue;
2061
2102
  continue;
2062
2103
  }
2104
+ // If target is a basic type, overwrite with deep clone of source
2063
2105
  if (isBasicType(target[key])) {
2064
2106
  target[key] = deepClone(sourceValue);
2065
2107
  continue;
2066
2108
  }
2109
+ // Handle Color instances - clone the source color
2067
2110
  if (sourceValue instanceof Color) {
2068
2111
  target[key] = sourceValue.clone();
2069
2112
  continue;
2070
2113
  }
2114
+ // If both are simple objects, merge them recursively
2071
2115
  if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
2072
2116
  target[key] = deepMerge(target[key], sourceValue);
2073
2117
  continue;
2074
2118
  }
2075
- // *********
2076
- // merge
2077
- // *********
2078
- if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
2079
- target[key] = deepMerge(target[key], sourceValue);
2080
- continue;
2081
- }
2082
- console.log('target[key]:', target[key]);
2083
- console.log('source[key]:', source[key]);
2084
- throw Error('unpredicted case');
2119
+ // Incompatible types - throw error
2120
+ throw new Error(`deepMerge: Cannot merge incompatible types for key "${String(key)}" (target: ${typeof target[key]}, source: ${typeof sourceValue})`);
2085
2121
  }
2086
2122
  }
2087
2123
  return target;
@@ -2108,7 +2144,7 @@ function basename(url) {
2108
2144
  }
2109
2145
 
2110
2146
  function decorate(layers, rules, recolor) {
2111
- const layerIds = layers.map(l => l.id);
2147
+ const layerIds = layers.map((l) => l.id);
2112
2148
  const layerIdSet = new Set(layerIds);
2113
2149
  // Initialize a new map to hold final styles for layers
2114
2150
  const layerStyles = new Map();
@@ -2117,25 +2153,25 @@ function decorate(layers, rules, recolor) {
2117
2153
  if (layerStyle == null)
2118
2154
  return;
2119
2155
  // Expand any braces in IDs and filter them through a RegExp if necessary
2120
- const ids = expandTop(idDef).flatMap(id => {
2156
+ const ids = expandTop(idDef).flatMap((id) => {
2121
2157
  if (!id.includes('*'))
2122
2158
  return id;
2123
- const regExpString = id.replace(/[^a-z_:-]/g, c => {
2159
+ const regExpString = id.replace(/[^a-z_:-]/g, (c) => {
2124
2160
  if (c === '*')
2125
2161
  return '[a-z_-]*';
2126
- throw new Error('unknown char to process. Do not know how to make a RegExp from: ' + JSON.stringify(c));
2162
+ throw new Error(`decorator: Invalid character ${JSON.stringify(c)} in layer ID pattern "${id}". Only alphanumeric, underscore, colon, hyphen, and asterisk are allowed.`);
2127
2163
  });
2128
2164
  const regExp = new RegExp(`^${regExpString}$`, 'i');
2129
- return layerIds.filter(layerId => regExp.test(layerId));
2165
+ return layerIds.filter((layerId) => regExp.test(layerId));
2130
2166
  });
2131
- ids.forEach(id => {
2167
+ ids.forEach((id) => {
2132
2168
  if (!layerIdSet.has(id))
2133
2169
  return;
2134
2170
  layerStyles.set(id, deepMerge(layerStyles.get(id) ?? {}, layerStyle));
2135
2171
  });
2136
2172
  });
2137
2173
  // Deep clone the original layers and apply styles
2138
- return layers.flatMap(layer => {
2174
+ return layers.flatMap((layer) => {
2139
2175
  // Get the id and style of the layer
2140
2176
  const layerStyle = layerStyles.get(layer.id);
2141
2177
  // Don't export layers that have no style
@@ -2150,11 +2186,11 @@ function decorate(layers, rules, recolor) {
2150
2186
  if (ruleValue == null)
2151
2187
  continue;
2152
2188
  // CamelCase to not-camel-case
2153
- const ruleKey = ruleKeyCamelCase.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
2189
+ const ruleKey = ruleKeyCamelCase.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
2154
2190
  const propertyDefs = propertyLookup.get(layer.type + '/' + ruleKey);
2155
2191
  if (!propertyDefs)
2156
2192
  continue;
2157
- propertyDefs.forEach(propertyDef => {
2193
+ propertyDefs.forEach((propertyDef) => {
2158
2194
  const { key } = propertyDef;
2159
2195
  let value = ruleValue;
2160
2196
  switch (propertyDef.valueType) {
@@ -2172,27 +2208,25 @@ function decorate(layers, rules, recolor) {
2172
2208
  case 'number':
2173
2209
  value = processExpression(value);
2174
2210
  break;
2175
- default: throw new Error(`unknown propertyDef.valueType "${propertyDef.valueType}" for key "${key}"`);
2211
+ default:
2212
+ throw new Error(`decorator: Unknown property value type "${propertyDef.valueType}" for key "${key}" on layer type "${layer.type}". This may indicate a MapLibre property definition mismatch.`);
2176
2213
  }
2177
2214
  switch (propertyDef.parent) {
2178
2215
  case 'layer':
2179
- // @ts-expect-error: too complex to handle
2180
2216
  layer[key] = value;
2181
2217
  break;
2182
2218
  case 'layout':
2183
2219
  if (!layer.layout)
2184
2220
  layer.layout = {};
2185
- // @ts-expect-error: too complex to handle
2186
2221
  layer.layout[key] = value;
2187
2222
  break;
2188
2223
  case 'paint':
2189
2224
  if (!layer.paint)
2190
2225
  layer.paint = {};
2191
- // @ts-expect-error: too complex to handle
2192
2226
  layer.paint[key] = value;
2193
2227
  break;
2194
2228
  default:
2195
- throw new Error(`unknown parent "${propertyDef.parent}" for key "${key}"`);
2229
+ throw new Error(`decorator: Unknown property parent "${propertyDef.parent}" for key "${key}" on layer type "${layer.type}". Expected "layer", "layout", or "paint".`);
2196
2230
  }
2197
2231
  });
2198
2232
  }
@@ -2203,12 +2237,12 @@ function decorate(layers, rules, recolor) {
2203
2237
  const color = recolor.do(value);
2204
2238
  return color.asString();
2205
2239
  }
2206
- throw new Error(`unknown color type "${typeof value}"`);
2240
+ throw new Error(`decorator.processColor: Expected a color string or Color instance, but got ${typeof value}. Value: ${JSON.stringify(value)}`);
2207
2241
  }
2208
2242
  function processFont(value) {
2209
2243
  if (typeof value === 'string')
2210
2244
  return [value];
2211
- throw new Error(`unknown font type "${typeof value}"`);
2245
+ throw new Error(`decorator.processFont: Expected a font name string, but got ${typeof value}. Value: ${JSON.stringify(value)}`);
2212
2246
  }
2213
2247
  function processExpression(value, cbValue) {
2214
2248
  if (typeof value === 'object') {
@@ -2255,7 +2289,7 @@ function getDefaultRecolorFlags() {
2255
2289
  * Checks if the given options object contains any active recolor transformations.
2256
2290
  * @param opt The recolor options to validate.
2257
2291
  */
2258
- function isValidRecolorOptions(opt) {
2292
+ function hasActiveRecolorOptions(opt) {
2259
2293
  if (!opt)
2260
2294
  return false;
2261
2295
  return (opt.invertBrightness ||
@@ -2276,14 +2310,20 @@ class CachedRecolor {
2276
2310
  skip;
2277
2311
  opt;
2278
2312
  cache;
2313
+ parsedTintColor;
2314
+ parsedBlendColor;
2279
2315
  /**
2280
2316
  * Creates a cached recolor instance.
2281
2317
  * @param opt Optional recolor options.
2282
2318
  */
2283
2319
  constructor(opt) {
2284
- this.skip = !isValidRecolorOptions(opt);
2320
+ this.skip = !hasActiveRecolorOptions(opt);
2285
2321
  this.cache = new Map();
2286
2322
  this.opt = opt;
2323
+ if (opt?.tint && opt.tintColor != null)
2324
+ this.parsedTintColor = Color.parse(opt.tintColor);
2325
+ if (opt?.blend && opt.blendColor != null)
2326
+ this.parsedBlendColor = Color.parse(opt.blendColor);
2287
2327
  }
2288
2328
  /**
2289
2329
  * Applies cached recoloring to a given color.
@@ -2296,7 +2336,7 @@ class CachedRecolor {
2296
2336
  const key = color.asHex();
2297
2337
  if (this.cache.has(key))
2298
2338
  return this.cache.get(key);
2299
- const recolored = recolor(color, this.opt);
2339
+ const recolored = recolor(color, this.opt, this.parsedTintColor, this.parsedBlendColor);
2300
2340
  this.cache.set(key, recolored);
2301
2341
  return recolored;
2302
2342
  }
@@ -2305,10 +2345,12 @@ class CachedRecolor {
2305
2345
  * Applies the specified recoloring transformations to a single color.
2306
2346
  * @param color The original color.
2307
2347
  * @param opt Optional recolor options.
2348
+ * @param parsedTintColor Optional pre-parsed tint color to avoid repeated parsing.
2349
+ * @param parsedBlendColor Optional pre-parsed blend color to avoid repeated parsing.
2308
2350
  * @returns A new `Color` instance with applied transformations.
2309
2351
  */
2310
- function recolor(color, opt) {
2311
- if (!isValidRecolorOptions(opt))
2352
+ function recolor(color, opt, parsedTintColor, parsedBlendColor) {
2353
+ if (!hasActiveRecolorOptions(opt))
2312
2354
  return color;
2313
2355
  if (opt.invertBrightness)
2314
2356
  color = color.invertLuminosity();
@@ -2316,35 +2358,77 @@ function recolor(color, opt) {
2316
2358
  color = color.rotateHue(opt.rotate);
2317
2359
  if (opt.saturate)
2318
2360
  color = color.saturate(opt.saturate);
2319
- if (opt.gamma != null && opt.gamma != 1)
2361
+ if (opt.gamma != null && opt.gamma !== 1)
2320
2362
  color = color.gamma(opt.gamma);
2321
- if (opt.contrast != null && opt.contrast != 1)
2363
+ if (opt.contrast != null && opt.contrast !== 1)
2322
2364
  color = color.contrast(opt.contrast);
2323
2365
  if (opt.brightness)
2324
2366
  color = color.brightness(opt.brightness);
2325
2367
  if (opt.tint && opt.tintColor != null)
2326
- color = color.tint(opt.tint, Color.parse(opt.tintColor));
2368
+ color = color.tint(opt.tint, parsedTintColor ?? Color.parse(opt.tintColor));
2327
2369
  if (opt.blend && opt.blendColor != null)
2328
- color = color.blend(opt.blend, Color.parse(opt.blendColor));
2370
+ color = color.blend(opt.blend, parsedBlendColor ?? Color.parse(opt.blendColor));
2329
2371
  return color;
2330
2372
  }
2331
2373
 
2332
- const styleBuilderColorKeys = ['agriculture', 'boundary', 'building', 'buildingbg', 'burial', 'commercial', 'construction', 'cycle', 'danger', 'disputed', 'education', 'foot', 'glacier', 'grass', 'hospital', 'industrial', 'label', 'labelHalo', 'land', 'leisure', 'motorway', 'motorwaybg', 'park', 'parking', 'poi', 'prison', 'rail', 'residential', 'rock', 'sand', 'shield', 'street', 'streetbg', 'subway', 'symbol', 'trunk', 'trunkbg', 'waste', 'water', 'wetland', 'wood'];
2374
+ const styleBuilderColorKeys = [
2375
+ 'agriculture',
2376
+ 'boundary',
2377
+ 'building',
2378
+ 'buildingbg',
2379
+ 'burial',
2380
+ 'commercial',
2381
+ 'construction',
2382
+ 'cycle',
2383
+ 'danger',
2384
+ 'disputed',
2385
+ 'education',
2386
+ 'foot',
2387
+ 'glacier',
2388
+ 'grass',
2389
+ 'hospital',
2390
+ 'industrial',
2391
+ 'label',
2392
+ 'labelHalo',
2393
+ 'land',
2394
+ 'leisure',
2395
+ 'motorway',
2396
+ 'motorwaybg',
2397
+ 'park',
2398
+ 'parking',
2399
+ 'poi',
2400
+ 'prison',
2401
+ 'rail',
2402
+ 'residential',
2403
+ 'rock',
2404
+ 'sand',
2405
+ 'shield',
2406
+ 'street',
2407
+ 'streetbg',
2408
+ 'subway',
2409
+ 'symbol',
2410
+ 'trunk',
2411
+ 'trunkbg',
2412
+ 'waste',
2413
+ 'water',
2414
+ 'wetland',
2415
+ 'wood',
2416
+ ];
2333
2417
 
2334
2418
  // StyleBuilder class definition
2335
2419
  class StyleBuilder {
2336
2420
  #sourceName = 'versatiles-shortbread';
2337
2421
  build(options) {
2338
2422
  options ??= {};
2339
- // @ts-expect-error globalThis may be undefined in some environments
2340
- const baseUrl = options.baseUrl ?? globalThis?.document?.location?.origin ?? 'https://tiles.versatiles.org';
2341
- const glyphs = options.glyphs ?? '/assets/glyphs/{fontstack}/{range}.pbf';
2342
- const sprite = options.sprite ?? [{ id: 'basics', url: '/assets/sprites/basics/sprites' }];
2343
- const tiles = options.tiles ?? ['/tiles/osm/{z}/{x}/{y}'];
2344
- const bounds = options.bounds ?? [-180, -85.0511287798066, 180, 85.0511287798066];
2345
- const hideLabels = options.hideLabels ?? false;
2346
- const language = options.language ?? null;
2347
- const recolorOptions = options.recolor ?? getDefaultRecolorFlags();
2423
+ const defaults = this.getDefaultOptions();
2424
+ const baseUrl = options.baseUrl ?? defaults.baseUrl;
2425
+ const glyphs = options.glyphs ?? defaults.glyphs;
2426
+ const sprite = options.sprite ?? defaults.sprite;
2427
+ const tiles = options.tiles ?? defaults.tiles;
2428
+ const bounds = options.bounds ?? defaults.bounds;
2429
+ const hideLabels = options.hideLabels ?? defaults.hideLabels;
2430
+ const language = options.language ?? defaults.language;
2431
+ const recolorOptions = options.recolor ?? defaults.recolor;
2348
2432
  const colors = this.getColors(this.defaultColors);
2349
2433
  if (options.colors) {
2350
2434
  let key;
@@ -2374,8 +2458,9 @@ class StyleBuilder {
2374
2458
  const layerStyleRules = this.getStyleRules(styleRuleOptions);
2375
2459
  // get shortbread layers
2376
2460
  const layerDefinitions = getShortbreadLayers({ language });
2377
- let layers = layerDefinitions.map(layer => {
2378
- switch (layer.type) {
2461
+ let layers = layerDefinitions.map((layer) => {
2462
+ const { type, id } = layer;
2463
+ switch (type) {
2379
2464
  case 'background':
2380
2465
  return layer;
2381
2466
  case 'fill':
@@ -2386,17 +2471,17 @@ class StyleBuilder {
2386
2471
  ...layer,
2387
2472
  };
2388
2473
  }
2389
- throw Error('unknown layer type');
2474
+ throw new Error(`StyleBuilder: Unknown layer type "${type}" for layer "${id}". Expected "background", "fill", "line", or "symbol".`);
2390
2475
  });
2391
2476
  // apply layer rules
2392
2477
  layers = decorate(layers, layerStyleRules, new CachedRecolor(recolorOptions));
2393
2478
  // hide labels, if wanted
2394
2479
  if (hideLabels)
2395
- layers = layers.filter(l => l.type !== 'symbol');
2480
+ layers = layers.filter((l) => l.type !== 'symbol');
2396
2481
  style.layers = layers;
2397
2482
  style.name = 'versatiles-' + this.name.toLowerCase();
2398
2483
  style.glyphs = resolveUrl(baseUrl, glyphs);
2399
- if (typeof sprite == 'string') {
2484
+ if (typeof sprite === 'string') {
2400
2485
  style.sprite = [{ id: basename(sprite), url: resolveUrl(baseUrl, sprite) }];
2401
2486
  }
2402
2487
  else {
@@ -2404,7 +2489,7 @@ class StyleBuilder {
2404
2489
  }
2405
2490
  const source = style.sources[this.#sourceName];
2406
2491
  if ('tiles' in source)
2407
- source.tiles = tiles.map(url => resolveUrl(baseUrl, url));
2492
+ source.tiles = tiles.map((url) => resolveUrl(baseUrl, url));
2408
2493
  if ('bounds' in source)
2409
2494
  source.bounds = bounds;
2410
2495
  return style;
@@ -2416,18 +2501,14 @@ class StyleBuilder {
2416
2501
  }
2417
2502
  getDefaultOptions() {
2418
2503
  return {
2419
- baseUrl: '',
2420
- bounds: [
2421
- -180,
2422
- -85.0511287798066,
2423
- 180,
2424
- 85.0511287798066
2425
- ],
2426
- glyphs: '',
2427
- sprite: '',
2428
- tiles: [],
2504
+ // @ts-expect-error globalThis may be undefined in some environments
2505
+ baseUrl: globalThis?.document?.location?.origin ?? 'https://tiles.versatiles.org',
2506
+ bounds: [-180, -85.0511287798066, 180, 85.0511287798066],
2507
+ glyphs: '/assets/glyphs/{fontstack}/{range}.pbf',
2508
+ sprite: [{ id: 'basics', url: '/assets/sprites/basics/sprites' }],
2509
+ tiles: ['/tiles/osm/{z}/{x}/{y}'],
2429
2510
  hideLabels: false,
2430
- language: undefined,
2511
+ language: '',
2431
2512
  colors: deepClone(this.defaultColors),
2432
2513
  fonts: deepClone(this.defaultFonts),
2433
2514
  recolor: getDefaultRecolorFlags(),
@@ -2535,7 +2616,7 @@ class Colorful extends StyleBuilder {
2535
2616
  const { colors, fonts } = options;
2536
2617
  return {
2537
2618
  // background
2538
- 'background': {
2619
+ background: {
2539
2620
  color: colors.land,
2540
2621
  },
2541
2622
  // boundary
@@ -2689,7 +2770,8 @@ class Colorful extends StyleBuilder {
2689
2770
  color: colors.buildingbg,
2690
2771
  opacity: { 14: 0, 15: 1 },
2691
2772
  },
2692
- 'building': {
2773
+ building: {
2774
+ // fake 2.5d with translate
2693
2775
  color: colors.building,
2694
2776
  opacity: { 14: 0, 15: 1 },
2695
2777
  fillTranslate: [-2, -2],
@@ -2722,7 +2804,7 @@ class Colorful extends StyleBuilder {
2722
2804
  opacity: { 13: 0, 14: 1 },
2723
2805
  },
2724
2806
  // bridge
2725
- 'bridge': {
2807
+ bridge: {
2726
2808
  color: colors.land.darken(0.02),
2727
2809
  fillAntialias: true,
2728
2810
  opacity: 0.8,
@@ -2768,37 +2850,37 @@ class Colorful extends StyleBuilder {
2768
2850
  opacity: 0.5,
2769
2851
  },
2770
2852
  'bridge-street-motorway:bridge': {
2771
- size: { '5': 0, '6': 3, '10': 7, '14': 7, '16': 20, '18': 53, '19': 118, '20': 235 }
2853
+ size: { '5': 0, '6': 3, '10': 7, '14': 7, '16': 20, '18': 53, '19': 118, '20': 235 },
2772
2854
  },
2773
2855
  'bridge-street-trunk:bridge': {
2774
- size: { '7': 0, '8': 3, '10': 6, '14': 8, '16': 17, '18': 50, '19': 104, '20': 202 }
2856
+ size: { '7': 0, '8': 3, '10': 6, '14': 8, '16': 17, '18': 50, '19': 104, '20': 202 },
2775
2857
  },
2776
2858
  'bridge-street-primary:bridge': {
2777
- size: { '8': 0, '9': 1, '10': 6, '14': 8, '16': 17, '18': 50, '19': 104, '20': 202 }
2859
+ size: { '8': 0, '9': 1, '10': 6, '14': 8, '16': 17, '18': 50, '19': 104, '20': 202 },
2778
2860
  },
2779
2861
  'bridge-street-secondary:bridge': {
2780
2862
  size: { '11': 3, '14': 7, '16': 11, '18': 42, '19': 95, '20': 193 },
2781
- opacity: { '11': 0, '12': 1 }
2863
+ opacity: { '11': 0, '12': 1 },
2782
2864
  },
2783
2865
  'bridge-street-motorway-link:bridge': {
2784
2866
  minzoom: 12,
2785
- size: { '12': 3, '14': 4, '16': 10, '18': 20, '20': 56 }
2867
+ size: { '12': 3, '14': 4, '16': 10, '18': 20, '20': 56 },
2786
2868
  },
2787
2869
  'bridge-street-{trunk,primary,secondary}-link:bridge': {
2788
2870
  minzoom: 13,
2789
- size: { '12': 3, '14': 4, '16': 10, '18': 20, '20': 56 }
2871
+ size: { '12': 3, '14': 4, '16': 10, '18': 20, '20': 56 },
2790
2872
  },
2791
2873
  'bridge-street-{tertiary,tertiary-link,unclassified,residential,livingstreet,pedestrian}*:bridge': {
2792
2874
  size: { '12': 3, '14': 4, '16': 8, '18': 36, '19': 90, '20': 179 },
2793
- opacity: { '12': 0, '13': 1 }
2875
+ opacity: { '12': 0, '13': 1 },
2794
2876
  },
2795
2877
  'bridge-street-{service,track}:bridge': {
2796
2878
  size: { '14': 3, '16': 6, '18': 25, '19': 67, '20': 134 },
2797
- opacity: { '14': 0, '15': 1 }
2879
+ opacity: { '14': 0, '15': 1 },
2798
2880
  },
2799
2881
  'bridge-way-*:bridge': {
2800
2882
  size: { '15': 0, '16': 7, '18': 10, '19': 17, '20': 31 },
2801
- minzoom: 15
2883
+ minzoom: 15,
2802
2884
  },
2803
2885
  // special color: motorway
2804
2886
  '{bridge-,}street-motorway{-link,}:outline': {
@@ -3228,177 +3310,285 @@ class Colorful extends StyleBuilder {
3228
3310
  color: colors.poi,
3229
3311
  },
3230
3312
  'poi-amenity': {
3231
- image: ['match',
3313
+ image: [
3314
+ 'match',
3232
3315
  ['get', 'amenity'],
3233
- 'arts_centre', 'basics:icon-art_gallery',
3234
- 'atm', 'basics:icon-atm',
3235
- 'bank', 'basics:icon-bank',
3236
- 'bar', 'basics:icon-bar',
3237
- 'bench', 'basics:icon-bench',
3238
- 'bicycle_rental', 'basics:icon-bicycle_share',
3239
- 'biergarten', 'basics:icon-beergarden',
3240
- 'cafe', 'basics:icon-cafe',
3241
- 'car_rental', 'basics:icon-car_rental',
3242
- 'car_sharing', 'basics:icon-car_rental',
3243
- 'car_wash', 'basics:icon-car_wash',
3244
- 'cinema', 'basics:icon-cinema',
3316
+ 'arts_centre',
3317
+ 'basics:icon-art_gallery',
3318
+ 'atm',
3319
+ 'basics:icon-atm',
3320
+ 'bank',
3321
+ 'basics:icon-bank',
3322
+ 'bar',
3323
+ 'basics:icon-bar',
3324
+ 'bench',
3325
+ 'basics:icon-bench',
3326
+ 'bicycle_rental',
3327
+ 'basics:icon-bicycle_share',
3328
+ 'biergarten',
3329
+ 'basics:icon-beergarden',
3330
+ 'cafe',
3331
+ 'basics:icon-cafe',
3332
+ 'car_rental',
3333
+ 'basics:icon-car_rental',
3334
+ 'car_sharing',
3335
+ 'basics:icon-car_rental',
3336
+ 'car_wash',
3337
+ 'basics:icon-car_wash',
3338
+ 'cinema',
3339
+ 'basics:icon-cinema',
3245
3340
  //'clinic', 'basics:icon-clinic',
3246
- 'college', 'basics:icon-college',
3247
- 'community_centre', 'basics:icon-community',
3341
+ 'college',
3342
+ 'basics:icon-college',
3343
+ 'community_centre',
3344
+ 'basics:icon-community',
3248
3345
  //'courthouse', 'basics:icon-courthouse',
3249
- 'dentist', 'basics:icon-dentist',
3250
- 'doctors', 'basics:icon-doctor',
3251
- 'dog_park', 'basics:icon-dog_park',
3252
- 'drinking_water', 'basics:icon-drinking_water',
3253
- 'embassy', 'basics:icon-embassy',
3254
- 'fast_food', 'basics:icon-fast_food',
3255
- 'fire_station', 'basics:icon-fire_station',
3346
+ 'dentist',
3347
+ 'basics:icon-dentist',
3348
+ 'doctors',
3349
+ 'basics:icon-doctor',
3350
+ 'dog_park',
3351
+ 'basics:icon-dog_park',
3352
+ 'drinking_water',
3353
+ 'basics:icon-drinking_water',
3354
+ 'embassy',
3355
+ 'basics:icon-embassy',
3356
+ 'fast_food',
3357
+ 'basics:icon-fast_food',
3358
+ 'fire_station',
3359
+ 'basics:icon-fire_station',
3256
3360
  //'food_court', 'basics:icon-food_court',
3257
- 'fountain', 'basics:icon-fountain',
3258
- 'grave_yard', 'basics:icon-cemetery',
3259
- 'hospital', 'basics:icon-hospital',
3260
- 'hunting_stand', 'basics:icon-huntingstand',
3261
- 'library', 'basics:icon-library',
3262
- 'marketplace', 'basics:icon-marketplace',
3263
- 'nightclub', 'basics:icon-nightclub',
3264
- 'nursing_home', 'basics:icon-nursinghome',
3265
- 'pharmacy', 'basics:icon-pharmacy',
3266
- 'place_of_worship', 'basics:icon-place_of_worship',
3267
- 'playground', 'basics:icon-playground',
3268
- 'police', 'basics:icon-police',
3269
- 'post_box', 'basics:icon-postbox',
3270
- 'post_office', 'basics:icon-post',
3271
- 'prison', 'basics:icon-prison',
3272
- 'pub', 'basics:icon-beer',
3361
+ 'fountain',
3362
+ 'basics:icon-fountain',
3363
+ 'grave_yard',
3364
+ 'basics:icon-cemetery',
3365
+ 'hospital',
3366
+ 'basics:icon-hospital',
3367
+ 'hunting_stand',
3368
+ 'basics:icon-huntingstand',
3369
+ 'library',
3370
+ 'basics:icon-library',
3371
+ 'marketplace',
3372
+ 'basics:icon-marketplace',
3373
+ 'nightclub',
3374
+ 'basics:icon-nightclub',
3375
+ 'nursing_home',
3376
+ 'basics:icon-nursinghome',
3377
+ 'pharmacy',
3378
+ 'basics:icon-pharmacy',
3379
+ 'place_of_worship',
3380
+ 'basics:icon-place_of_worship',
3381
+ 'playground',
3382
+ 'basics:icon-playground',
3383
+ 'police',
3384
+ 'basics:icon-police',
3385
+ 'post_box',
3386
+ 'basics:icon-postbox',
3387
+ 'post_office',
3388
+ 'basics:icon-post',
3389
+ 'prison',
3390
+ 'basics:icon-prison',
3391
+ 'pub',
3392
+ 'basics:icon-beer',
3273
3393
  //'public_building', 'basics:icon-public_building',
3274
- 'recycling', 'basics:icon-recycling',
3275
- 'restaurant', 'basics:icon-restaurant',
3276
- 'school', 'basics:icon-school',
3277
- 'shelter', 'basics:icon-shelter',
3278
- 'telephone', 'basics:icon-telephone',
3279
- 'theatre', 'basics:icon-theatre',
3280
- 'toilets', 'basics:icon-toilet',
3281
- 'townhall', 'basics:icon-town_hall',
3394
+ 'recycling',
3395
+ 'basics:icon-recycling',
3396
+ 'restaurant',
3397
+ 'basics:icon-restaurant',
3398
+ 'school',
3399
+ 'basics:icon-school',
3400
+ 'shelter',
3401
+ 'basics:icon-shelter',
3402
+ 'telephone',
3403
+ 'basics:icon-telephone',
3404
+ 'theatre',
3405
+ 'basics:icon-theatre',
3406
+ 'toilets',
3407
+ 'basics:icon-toilet',
3408
+ 'townhall',
3409
+ 'basics:icon-town_hall',
3282
3410
  //'university', 'basics:icon-university',
3283
- 'vending_machine', 'basics:icon-vendingmachine',
3284
- 'veterinary', 'basics:icon-veterinary',
3285
- 'waste_basket', 'basics:icon-waste_basket',
3411
+ 'vending_machine',
3412
+ 'basics:icon-vendingmachine',
3413
+ 'veterinary',
3414
+ 'basics:icon-veterinary',
3415
+ 'waste_basket',
3416
+ 'basics:icon-waste_basket',
3286
3417
  '',
3287
3418
  ],
3288
3419
  },
3289
3420
  'poi-leisure': {
3290
- image: ['match',
3421
+ image: [
3422
+ 'match',
3291
3423
  ['get', 'leisure'],
3292
- 'golf_course', 'basics:icon-golf',
3293
- 'ice_rink', 'basics:icon-icerink',
3294
- 'pitch', 'basics:icon-pitch',
3424
+ 'golf_course',
3425
+ 'basics:icon-golf',
3426
+ 'ice_rink',
3427
+ 'basics:icon-icerink',
3428
+ 'pitch',
3429
+ 'basics:icon-pitch',
3295
3430
  //'sports_centre', 'basics:icon-sports_centre',
3296
- 'stadium', 'basics:icon-stadium',
3297
- 'swimming_pool', 'basics:icon-swimming',
3298
- 'water_park', 'basics:icon-waterpark',
3431
+ 'stadium',
3432
+ 'basics:icon-stadium',
3433
+ 'swimming_pool',
3434
+ 'basics:icon-swimming',
3435
+ 'water_park',
3436
+ 'basics:icon-waterpark',
3299
3437
  'basics:icon-sports',
3300
3438
  ],
3301
3439
  },
3302
3440
  'poi-tourism': {
3303
- image: ['match',
3441
+ image: [
3442
+ 'match',
3304
3443
  ['get', 'tourism'],
3305
3444
  //'alpine_hut', 'basics:icon-alpine_hut',
3306
3445
  //'bed_and_breakfast', 'basics:icon-bed_and_breakfast',
3307
3446
  //'camp_site', 'basics:icon-camp_site',
3308
3447
  //'caravan_site', 'basics:icon-caravan_site',
3309
- 'chalet', 'basics:icon-chalet',
3448
+ 'chalet',
3449
+ 'basics:icon-chalet',
3310
3450
  //'guest_house', 'basics:icon-guest_house',
3311
3451
  //'hostel', 'basics:icon-hostel',
3312
3452
  //'hotel', 'basics:icon-hotel',
3313
- 'information', 'basics:transport-information',
3453
+ 'information',
3454
+ 'basics:transport-information',
3314
3455
  //'motel', 'basics:icon-motel',
3315
- 'picnic_site', 'basics:icon-picnic_site',
3456
+ 'picnic_site',
3457
+ 'basics:icon-picnic_site',
3316
3458
  //'theme_park', 'basics:icon-theme_park',
3317
- 'viewpoint', 'basics:icon-viewpoint',
3318
- 'zoo', 'basics:icon-zoo',
3459
+ 'viewpoint',
3460
+ 'basics:icon-viewpoint',
3461
+ 'zoo',
3462
+ 'basics:icon-zoo',
3319
3463
  '',
3320
3464
  ],
3321
3465
  },
3322
3466
  'poi-shop': {
3323
- image: ['match',
3467
+ image: [
3468
+ 'match',
3324
3469
  ['get', 'shop'],
3325
- 'alcohol', 'basics:icon-alcohol_shop',
3326
- 'bakery', 'basics:icon-bakery',
3327
- 'beauty', 'basics:icon-beauty',
3328
- 'beverages', 'basics:icon-beverages',
3470
+ 'alcohol',
3471
+ 'basics:icon-alcohol_shop',
3472
+ 'bakery',
3473
+ 'basics:icon-bakery',
3474
+ 'beauty',
3475
+ 'basics:icon-beauty',
3476
+ 'beverages',
3477
+ 'basics:icon-beverages',
3329
3478
  //'bicycle', 'basics:icon-bicycle',
3330
- 'books', 'basics:icon-books',
3331
- 'butcher', 'basics:icon-butcher',
3479
+ 'books',
3480
+ 'basics:icon-books',
3481
+ 'butcher',
3482
+ 'basics:icon-butcher',
3332
3483
  //'car', 'basics:icon-car',
3333
- 'chemist', 'basics:icon-chemist',
3334
- 'clothes', 'basics:icon-clothes',
3484
+ 'chemist',
3485
+ 'basics:icon-chemist',
3486
+ 'clothes',
3487
+ 'basics:icon-clothes',
3335
3488
  //'computer', 'basics:icon-computer',
3336
3489
  //'convinience', 'basics:icon-convinience',
3337
3490
  //'department_store', 'basics:icon-department_store',
3338
- 'doityourself', 'basics:icon-doityourself',
3339
- 'dry_cleaning', 'basics:icon-drycleaning',
3340
- 'florist', 'basics:icon-florist',
3341
- 'furniture', 'basics:icon-furniture',
3342
- 'garden_centre', 'basics:icon-garden_centre',
3343
- 'general', 'basics:icon-shop',
3344
- 'gift', 'basics:icon-gift',
3345
- 'greengrocer', 'basics:icon-greengrocer',
3346
- 'hairdresser', 'basics:icon-hairdresser',
3347
- 'hardware', 'basics:icon-hardware',
3348
- 'jewelry', 'basics:icon-jewelry_store',
3349
- 'kiosk', 'basics:icon-kiosk',
3350
- 'laundry', 'basics:icon-laundry',
3491
+ 'doityourself',
3492
+ 'basics:icon-doityourself',
3493
+ 'dry_cleaning',
3494
+ 'basics:icon-drycleaning',
3495
+ 'florist',
3496
+ 'basics:icon-florist',
3497
+ 'furniture',
3498
+ 'basics:icon-furniture',
3499
+ 'garden_centre',
3500
+ 'basics:icon-garden_centre',
3501
+ 'general',
3502
+ 'basics:icon-shop',
3503
+ 'gift',
3504
+ 'basics:icon-gift',
3505
+ 'greengrocer',
3506
+ 'basics:icon-greengrocer',
3507
+ 'hairdresser',
3508
+ 'basics:icon-hairdresser',
3509
+ 'hardware',
3510
+ 'basics:icon-hardware',
3511
+ 'jewelry',
3512
+ 'basics:icon-jewelry_store',
3513
+ 'kiosk',
3514
+ 'basics:icon-kiosk',
3515
+ 'laundry',
3516
+ 'basics:icon-laundry',
3351
3517
  //'mall', 'basics:icon-mall',
3352
3518
  //'mobile_phone', 'basics:icon-mobile_phone',
3353
- 'newsagent', 'basics:icon-newsagent',
3354
- 'optican', 'basics:icon-optician',
3355
- 'outdoor', 'basics:icon-outdoor',
3356
- 'shoes', 'basics:icon-shoes',
3357
- 'sports', 'basics:icon-sports',
3358
- 'stationery', 'basics:icon-stationery',
3519
+ 'newsagent',
3520
+ 'basics:icon-newsagent',
3521
+ 'optican',
3522
+ 'basics:icon-optician',
3523
+ 'outdoor',
3524
+ 'basics:icon-outdoor',
3525
+ 'shoes',
3526
+ 'basics:icon-shoes',
3527
+ 'sports',
3528
+ 'basics:icon-sports',
3529
+ 'stationery',
3530
+ 'basics:icon-stationery',
3359
3531
  //'supermarket', 'basics:icon-supermarket',
3360
- 'toys', 'basics:icon-toys',
3361
- 'travel_agency', 'basics:icon-travel_agent',
3362
- 'video', 'basics:icon-video',
3532
+ 'toys',
3533
+ 'basics:icon-toys',
3534
+ 'travel_agency',
3535
+ 'basics:icon-travel_agent',
3536
+ 'video',
3537
+ 'basics:icon-video',
3363
3538
  'basics:icon-shop',
3364
3539
  ],
3365
3540
  },
3366
3541
  'poi-man_made': {
3367
- image: ['match',
3542
+ image: [
3543
+ 'match',
3368
3544
  ['get', 'man_made'],
3369
- 'lighthouse', 'basics:icon-lighthouse',
3370
- 'surveillance', 'basics:icon-surveillance',
3371
- 'tower', 'basics:icon-observation_tower',
3545
+ 'lighthouse',
3546
+ 'basics:icon-lighthouse',
3547
+ 'surveillance',
3548
+ 'basics:icon-surveillance',
3549
+ 'tower',
3550
+ 'basics:icon-observation_tower',
3372
3551
  //'wastewater_plant', 'basics:icon-wastewater_plant',
3373
3552
  //'water_well', 'basics:icon-water_well',
3374
3553
  //'water_works', 'basics:icon-water_works',
3375
- 'watermill', 'basics:icon-watermill',
3376
- 'windmill', 'basics:icon-windmill',
3554
+ 'watermill',
3555
+ 'basics:icon-watermill',
3556
+ 'windmill',
3557
+ 'basics:icon-windmill',
3377
3558
  '',
3378
3559
  ],
3379
3560
  },
3380
3561
  'poi-historic': {
3381
- image: ['match',
3562
+ image: [
3563
+ 'match',
3382
3564
  ['get', 'historic'],
3383
3565
  //'archaelogical_site', 'basics:icon-archaelogical_site',
3384
- 'artwork', 'basics:icon-artwork',
3566
+ 'artwork',
3567
+ 'basics:icon-artwork',
3385
3568
  //'battlefield', 'basics:icon-battlefield',
3386
- 'castle', 'basics:icon-castle',
3569
+ 'castle',
3570
+ 'basics:icon-castle',
3387
3571
  //'fort', 'basics:icon-fort',
3388
3572
  //'memorial', 'basics:icon-memorial',
3389
- 'monument', 'basics:icon-monument',
3573
+ 'monument',
3574
+ 'basics:icon-monument',
3390
3575
  //'ruins', 'basics:icon-ruins',
3391
3576
  //'wayside_cross', 'basics:icon-wayside_cross',
3392
- 'wayside_shrine', 'basics:icon-shrine',
3577
+ 'wayside_shrine',
3578
+ 'basics:icon-shrine',
3393
3579
  'basics:icon-historic',
3394
3580
  ],
3395
3581
  },
3396
3582
  'poi-emergency': {
3397
- image: ['match',
3583
+ image: [
3584
+ 'match',
3398
3585
  ['get', 'emergency'],
3399
- 'defibrillator', 'basics:icon-defibrillator',
3400
- 'fire_hydrant', 'basics:icon-hydrant',
3401
- 'phone', 'basics:icon-emergency_phone',
3586
+ 'defibrillator',
3587
+ 'basics:icon-defibrillator',
3588
+ 'fire_hydrant',
3589
+ 'basics:icon-hydrant',
3590
+ 'phone',
3591
+ 'basics:icon-emergency_phone',
3402
3592
  '',
3403
3593
  ],
3404
3594
  },
@@ -3426,7 +3616,7 @@ class Eclipse extends Colorful {
3426
3616
  name = 'Eclipse';
3427
3617
  constructor() {
3428
3618
  super();
3429
- this.transformDefaultColors(color => color.invertLuminosity());
3619
+ this.transformDefaultColors((color) => color.invertLuminosity());
3430
3620
  }
3431
3621
  }
3432
3622
 
@@ -3434,7 +3624,7 @@ class Graybeard extends Colorful {
3434
3624
  name = 'Graybeard';
3435
3625
  constructor() {
3436
3626
  super();
3437
- this.transformDefaultColors(color => color.saturate(-1));
3627
+ this.transformDefaultColors((color) => color.saturate(-1));
3438
3628
  }
3439
3629
  }
3440
3630
 
@@ -3442,7 +3632,7 @@ class Shadow extends Colorful {
3442
3632
  name = 'Shadow';
3443
3633
  constructor() {
3444
3634
  super();
3445
- this.transformDefaultColors(color => color.saturate(-1).invert().brightness(0.2));
3635
+ this.transformDefaultColors((color) => color.saturate(-1).invert().brightness(0.2));
3446
3636
  }
3447
3637
  }
3448
3638
 
@@ -3511,7 +3701,7 @@ class Neutrino extends Colorful {
3511
3701
  getStyleRules(options) {
3512
3702
  const { colors, fonts } = options;
3513
3703
  return {
3514
- 'background': {
3704
+ background: {
3515
3705
  color: colors.land,
3516
3706
  },
3517
3707
  'boundary-{country,state}': {
@@ -3582,11 +3772,11 @@ class Neutrino extends Colorful {
3582
3772
  'site-{bicycleparking,parking}': {
3583
3773
  color: colors.commercial,
3584
3774
  },
3585
- 'building': {
3775
+ building: {
3586
3776
  color: colors.building,
3587
3777
  opacity: { 14: 0, 15: 1 },
3588
3778
  },
3589
- 'bridge': {
3779
+ bridge: {
3590
3780
  color: colors.land.darken(0.01),
3591
3781
  },
3592
3782
  '{tunnel-,bridge-,}street-*': {
@@ -3853,6 +4043,92 @@ class Empty extends Colorful {
3853
4043
  }
3854
4044
  }
3855
4045
 
4046
+ function buildSatelliteStyle(options) {
4047
+ options ??= {};
4048
+ const baseUrl = options.baseUrl ?? 'https://tiles.versatiles.org';
4049
+ const rasterTiles = options.rasterTiles ?? [`${baseUrl}/tiles/satellite/{z}/{x}/{y}`];
4050
+ const overlay = options.overlay ?? true;
4051
+ let style;
4052
+ if (overlay) {
4053
+ // Generate graybeard style for overlay
4054
+ style = new Graybeard().build({
4055
+ baseUrl,
4056
+ tiles: options.overlayTiles,
4057
+ language: options.language,
4058
+ });
4059
+ // Filter out background, fill layers, and unwanted layer groups
4060
+ style.layers = style.layers.filter((l) => l.id !== 'background' && l.type !== 'fill' && !/^(land|water|site|airport|tunnel)-/.test(l.id));
4061
+ // Modify remaining layers
4062
+ for (const layer of style.layers) {
4063
+ if (layer.type === 'symbol') {
4064
+ // Bold font, white text, black halo
4065
+ if (layer.layout?.['text-font']) {
4066
+ layer.layout['text-font'] = ['noto_sans_bold'];
4067
+ }
4068
+ if (layer.paint) {
4069
+ layer.paint['text-color'] = '#fff';
4070
+ layer.paint['text-halo-color'] = '#000';
4071
+ if ('text-halo-blur' in layer.paint)
4072
+ layer.paint['text-halo-blur'] = 0;
4073
+ if ('text-halo-width' in layer.paint)
4074
+ layer.paint['text-halo-width'] = 1;
4075
+ }
4076
+ }
4077
+ if (layer.type === 'line' && layer.paint) {
4078
+ // Multiply existing opacity by 0.2
4079
+ const v = layer.paint['line-opacity'];
4080
+ if (v == null) {
4081
+ layer.paint['line-opacity'] = 0.2;
4082
+ }
4083
+ else if (typeof v === 'number') {
4084
+ layer.paint['line-opacity'] = v * 0.2;
4085
+ }
4086
+ else if (typeof v === 'object' && 'stops' in v) {
4087
+ v.stops = v.stops.map((s) => [s[0], s[1] * 0.2]);
4088
+ }
4089
+ }
4090
+ }
4091
+ }
4092
+ else {
4093
+ // Minimal style with no overlay
4094
+ style = { version: 8, sources: {}, layers: [] };
4095
+ }
4096
+ // Build raster paint properties
4097
+ const rasterPaint = {};
4098
+ if (options.rasterOpacity != null)
4099
+ rasterPaint['raster-opacity'] = options.rasterOpacity;
4100
+ if (options.rasterHueRotate != null)
4101
+ rasterPaint['raster-hue-rotate'] = options.rasterHueRotate;
4102
+ if (options.rasterBrightnessMin != null)
4103
+ rasterPaint['raster-brightness-min'] = options.rasterBrightnessMin;
4104
+ if (options.rasterBrightnessMax != null)
4105
+ rasterPaint['raster-brightness-max'] = options.rasterBrightnessMax;
4106
+ if (options.rasterSaturation != null)
4107
+ rasterPaint['raster-saturation'] = options.rasterSaturation;
4108
+ if (options.rasterContrast != null)
4109
+ rasterPaint['raster-contrast'] = options.rasterContrast;
4110
+ // Add raster source
4111
+ style.sources.satellite = {
4112
+ type: 'raster',
4113
+ tiles: rasterTiles,
4114
+ tileSize: 512,
4115
+ attribution: "<a href='https://versatiles.org/sources/'>VersaTiles sources</a>",
4116
+ bounds: [-178.187256, -21.401934, 55.846252, 58.061897],
4117
+ minzoom: 0,
4118
+ maxzoom: 17,
4119
+ };
4120
+ // Add raster layer at bottom
4121
+ style.layers.unshift({
4122
+ id: 'satellite',
4123
+ type: 'raster',
4124
+ source: 'satellite',
4125
+ minzoom: 0,
4126
+ ...(Object.keys(rasterPaint).length > 0 ? { paint: rasterPaint } : {}),
4127
+ });
4128
+ style.name = 'versatiles-satellite';
4129
+ return style;
4130
+ }
4131
+
3856
4132
  // import styles
3857
4133
  function getStyleBuilder(styleBuilder) {
3858
4134
  const fn = function (options) {
@@ -3870,88 +4146,88 @@ const neutrino = getStyleBuilder(Neutrino);
3870
4146
  getStyleBuilder(Empty);
3871
4147
 
3872
4148
  /**
3873
- * Checks if an object adheres to the TileJSON specification.
3874
- * Throws errors if the object does not conform to the expected structure or types.
3875
- */
4149
+ * Checks if an object adheres to the TileJSON specification.
4150
+ * Throws errors if the object does not conform to the expected structure or types.
4151
+ */
3876
4152
  function isTileJSONSpecification(spec) {
3877
4153
  if (typeof spec !== 'object' || spec === null) {
3878
- throw Error('spec must be an object');
4154
+ throw new Error(`TileJSON validation: spec must be an object, but got ${typeof spec}`);
3879
4155
  }
3880
4156
  const obj = spec;
3881
4157
  // Common property validation
3882
4158
  if (obj.data != null && obj.tilejson !== '3.0.0') {
3883
- throw Error('spec.tilejson must be "3.0.0"');
4159
+ throw new Error(`TileJSON validation: spec.tilejson must be "3.0.0", but got "${obj.tilejson}"`);
3884
4160
  }
3885
4161
  if (obj.attribution != null && typeof obj.attribution !== 'string') {
3886
- throw Error('spec.attribution must be a string if present');
4162
+ throw new Error(`TileJSON validation: spec.attribution must be a string if present, but got ${typeof obj.attribution}`);
3887
4163
  }
3888
4164
  if (obj.bounds != null) {
3889
- if (!Array.isArray(obj.bounds) || obj.bounds.length !== 4 || obj.bounds.some(num => typeof num !== 'number')) {
3890
- throw Error('spec.bounds must be an array of four numbers if present');
4165
+ if (!Array.isArray(obj.bounds) || obj.bounds.length !== 4 || obj.bounds.some((num) => typeof num !== 'number')) {
4166
+ throw new Error(`TileJSON validation: spec.bounds must be an array of four numbers if present, but got ${JSON.stringify(obj.bounds)}`);
3891
4167
  }
3892
4168
  const a = obj.bounds;
3893
4169
  if (a[0] < -180 || a[0] > 180)
3894
- throw Error('spec.bounds[0] must be between -180 and 180');
4170
+ throw new Error(`TileJSON validation: spec.bounds[0] (longitude) must be between -180 and 180, but got ${a[0]}`);
3895
4171
  if (a[1] < -90 || a[1] > 90)
3896
- throw Error('spec.bounds[1] must be between -90 and 90');
4172
+ throw new Error(`TileJSON validation: spec.bounds[1] (latitude) must be between -90 and 90, but got ${a[1]}`);
3897
4173
  if (a[2] < -180 || a[2] > 180)
3898
- throw Error('spec.bounds[2] must be between -180 and 180');
4174
+ throw new Error(`TileJSON validation: spec.bounds[2] (longitude) must be between -180 and 180, but got ${a[2]}`);
3899
4175
  if (a[3] < -90 || a[3] > 90)
3900
- throw Error('spec.bounds[3] must be between -90 and 90');
4176
+ throw new Error(`TileJSON validation: spec.bounds[3] (latitude) must be between -90 and 90, but got ${a[3]}`);
3901
4177
  if (a[0] > a[2])
3902
- throw Error('spec.bounds[0] must be smaller than spec.bounds[2]');
4178
+ throw new Error(`TileJSON validation: spec.bounds[0] must be smaller than spec.bounds[2] (min longitude < max longitude), but got [${a[0]}, ${a[2]}]`);
3903
4179
  if (a[1] > a[3])
3904
- throw Error('spec.bounds[1] must be smaller than spec.bounds[3]');
4180
+ throw new Error(`TileJSON validation: spec.bounds[1] must be smaller than spec.bounds[3] (min latitude < max latitude), but got [${a[1]}, ${a[3]}]`);
3905
4181
  }
3906
4182
  if (obj.center != null) {
3907
- if (!Array.isArray(obj.center) || obj.center.length !== 2 || obj.center.some(num => typeof num !== 'number')) {
3908
- throw Error('spec.center must be an array of two numbers if present');
4183
+ if (!Array.isArray(obj.center) || obj.center.length !== 2 || obj.center.some((num) => typeof num !== 'number')) {
4184
+ throw new Error(`TileJSON validation: spec.center must be an array of two numbers if present, but got ${JSON.stringify(obj.center)}`);
3909
4185
  }
3910
4186
  const a = obj.center;
3911
4187
  if (a[0] < -180 || a[0] > 180)
3912
- throw Error('spec.center[0] must be between -180 and 180');
4188
+ throw new Error(`TileJSON validation: spec.center[0] (longitude) must be between -180 and 180, but got ${a[0]}`);
3913
4189
  if (a[1] < -90 || a[1] > 90)
3914
- throw Error('spec.center[1] must be between -90 and 90');
4190
+ throw new Error(`TileJSON validation: spec.center[1] (latitude) must be between -90 and 90, but got ${a[1]}`);
3915
4191
  }
3916
- if (obj.data != null && (!Array.isArray(obj.data) || obj.data.some(url => typeof url !== 'string'))) {
3917
- throw Error('spec.data must be an array of strings if present');
4192
+ if (obj.data != null && (!Array.isArray(obj.data) || obj.data.some((url) => typeof url !== 'string'))) {
4193
+ throw new Error('TileJSON validation: spec.data must be an array of strings if present');
3918
4194
  }
3919
4195
  if (obj.description != null && typeof obj.description !== 'string') {
3920
- throw Error('spec.description must be a string if present');
4196
+ throw new Error(`TileJSON validation: spec.description must be a string if present, but got ${typeof obj.description}`);
3921
4197
  }
3922
- if (obj.fillzoom != null && (typeof obj.fillzoom !== 'number' || (obj.fillzoom < 0))) {
3923
- throw Error('spec.fillzoom must be a positive integer if present');
4198
+ if (obj.fillzoom != null && (typeof obj.fillzoom !== 'number' || obj.fillzoom < 0)) {
4199
+ throw new Error(`TileJSON validation: spec.fillzoom must be a positive integer if present, but got ${obj.fillzoom}`);
3924
4200
  }
3925
- if (obj.grids != null && (!Array.isArray(obj.grids) || obj.grids.some(url => typeof url !== 'string'))) {
3926
- throw Error('spec.grids must be an array of strings if present');
4201
+ if (obj.grids != null && (!Array.isArray(obj.grids) || obj.grids.some((url) => typeof url !== 'string'))) {
4202
+ throw new Error('TileJSON validation: spec.grids must be an array of strings if present');
3927
4203
  }
3928
4204
  if (obj.legend != null && typeof obj.legend !== 'string') {
3929
- throw Error('spec.legend must be a string if present');
4205
+ throw new Error(`TileJSON validation: spec.legend must be a string if present, but got ${typeof obj.legend}`);
3930
4206
  }
3931
- if (obj.minzoom != null && (typeof obj.minzoom !== 'number' || (obj.minzoom < 0))) {
3932
- throw Error('spec.minzoom must be a positive integer if present');
4207
+ if (obj.minzoom != null && (typeof obj.minzoom !== 'number' || obj.minzoom < 0)) {
4208
+ throw new Error(`TileJSON validation: spec.minzoom must be a positive integer if present, but got ${obj.minzoom}`);
3933
4209
  }
3934
- if (obj.maxzoom != null && (typeof obj.maxzoom !== 'number' || (obj.maxzoom < 0))) {
3935
- throw Error('spec.maxzoom must be a positive integer if present');
4210
+ if (obj.maxzoom != null && (typeof obj.maxzoom !== 'number' || obj.maxzoom < 0)) {
4211
+ throw new Error(`TileJSON validation: spec.maxzoom must be a positive integer if present, but got ${obj.maxzoom}`);
3936
4212
  }
3937
4213
  if (obj.name != null && typeof obj.name !== 'string') {
3938
- throw Error('spec.name must be a string if present');
4214
+ throw new Error(`TileJSON validation: spec.name must be a string if present, but got ${typeof obj.name}`);
3939
4215
  }
3940
4216
  if (obj.scheme != null && obj.scheme !== 'xyz' && obj.scheme !== 'tms') {
3941
- throw Error('spec.scheme must be "tms" or "xyz" if present');
4217
+ throw new Error(`TileJSON validation: spec.scheme must be "tms" or "xyz" if present, but got "${obj.scheme}"`);
3942
4218
  }
3943
4219
  if (obj.template != null && typeof obj.template !== 'string') {
3944
- throw Error('spec.template must be a string if present');
4220
+ throw new Error(`TileJSON validation: spec.template must be a string if present, but got ${typeof obj.template}`);
3945
4221
  }
3946
- if (!Array.isArray(obj.tiles) || obj.tiles.length === 0 || obj.tiles.some(url => typeof url !== 'string')) {
3947
- throw Error('spec.tiles must be an array of strings');
4222
+ if (!Array.isArray(obj.tiles) || obj.tiles.length === 0 || obj.tiles.some((url) => typeof url !== 'string')) {
4223
+ throw new Error('TileJSON validation: spec.tiles must be a non-empty array of strings');
3948
4224
  }
3949
4225
  return true;
3950
4226
  }
3951
4227
  function isRasterTileJSONSpecification(spec) {
3952
4228
  if (!isTileJSONSpecification(spec))
3953
4229
  ;
3954
- if (('vector_layers' in spec) && (spec.vector_layers != null))
4230
+ if ('vector_layers' in spec && spec.vector_layers != null)
3955
4231
  return false;
3956
4232
  return true;
3957
4233
  }
@@ -3971,10 +4247,9 @@ function guessStyle(tileJSON, options) {
3971
4247
  tileJSON = deepClone(tileJSON);
3972
4248
  if (options && options.baseUrl) {
3973
4249
  const { baseUrl } = options;
3974
- tileJSON.tiles = tileJSON.tiles.map(url => resolveUrl(baseUrl, url));
4250
+ tileJSON.tiles = tileJSON.tiles.map((url) => resolveUrl(baseUrl, url));
3975
4251
  }
3976
- if (!isTileJSONSpecification(tileJSON))
3977
- ;
4252
+ if (!isTileJSONSpecification(tileJSON)) ;
3978
4253
  let style;
3979
4254
  if (isRasterTileJSONSpecification(tileJSON)) {
3980
4255
  style = getRasterStyle(tileJSON);
@@ -4000,9 +4275,36 @@ function isShortbread(spec) {
4000
4275
  return false;
4001
4276
  if (!Array.isArray(spec.vector_layers))
4002
4277
  return false;
4003
- const layerIds = new Set(spec.vector_layers.map(l => String(l.id)));
4004
- const shortbreadIds = ['place_labels', 'boundaries', 'boundary_labels', 'addresses', 'water_lines', 'water_lines_labels', 'dam_lines', 'dam_polygons', 'pier_lines', 'pier_polygons', 'bridges', 'street_polygons', 'streets_polygons_labels', 'ferries', 'streets', 'street_labels', 'street_labels_points', 'aerialways', 'public_transport', 'buildings', 'water_polygons', 'ocean', 'water_polygons_labels', 'land', 'sites', 'pois'];
4005
- return shortbreadIds.every(id => layerIds.has(id));
4278
+ const layerIds = new Set(spec.vector_layers.map((l) => String(l.id)));
4279
+ const shortbreadIds = [
4280
+ 'place_labels',
4281
+ 'boundaries',
4282
+ 'boundary_labels',
4283
+ 'addresses',
4284
+ 'water_lines',
4285
+ 'water_lines_labels',
4286
+ 'dam_lines',
4287
+ 'dam_polygons',
4288
+ 'pier_lines',
4289
+ 'pier_polygons',
4290
+ 'bridges',
4291
+ 'street_polygons',
4292
+ 'streets_polygons_labels',
4293
+ 'ferries',
4294
+ 'streets',
4295
+ 'street_labels',
4296
+ 'street_labels_points',
4297
+ 'aerialways',
4298
+ 'public_transport',
4299
+ 'buildings',
4300
+ 'water_polygons',
4301
+ 'ocean',
4302
+ 'water_polygons_labels',
4303
+ 'land',
4304
+ 'sites',
4305
+ 'pois',
4306
+ ];
4307
+ return shortbreadIds.every((id) => layerIds.has(id));
4006
4308
  }
4007
4309
  function getShortbreadStyle(spec, builderOption) {
4008
4310
  return colorful({
@@ -4032,7 +4334,7 @@ function getShortbreadStyle(spec, builderOption) {
4032
4334
  function getInspectorStyle(spec) {
4033
4335
  const sourceName = 'vectorSource';
4034
4336
  const layers = { background: [], circle: [], line: [], fill: [] };
4035
- layers.background.push({ 'id': 'background', 'type': 'background', 'paint': { 'background-color': '#fff' } });
4337
+ layers.background.push({ id: 'background', type: 'background', paint: { 'background-color': '#fff' } });
4036
4338
  spec.vector_layers.forEach((vector_layer) => {
4037
4339
  let luminosity = 'bright', saturation, hue;
4038
4340
  if (/water|ocean|lake|sea|river/.test(vector_layer.id))
@@ -4091,12 +4393,7 @@ function getInspectorStyle(spec) {
4091
4393
  sources: {
4092
4394
  [sourceName]: sourceFromSpec(spec, 'vector'),
4093
4395
  },
4094
- layers: [
4095
- ...layers.background,
4096
- ...layers.fill,
4097
- ...layers.line,
4098
- ...layers.circle,
4099
- ],
4396
+ layers: [...layers.background, ...layers.fill, ...layers.line, ...layers.circle],
4100
4397
  };
4101
4398
  }
4102
4399
  function getRasterStyle(spec) {
@@ -4106,11 +4403,13 @@ function getRasterStyle(spec) {
4106
4403
  sources: {
4107
4404
  [sourceName]: sourceFromSpec(spec, 'raster'),
4108
4405
  },
4109
- layers: [{
4406
+ layers: [
4407
+ {
4110
4408
  id: 'raster',
4111
4409
  type: 'raster',
4112
4410
  source: sourceName,
4113
- }],
4411
+ },
4412
+ ],
4114
4413
  };
4115
4414
  }
4116
4415
  function sourceFromSpec(spec, type) {
@@ -4213,7 +4512,8 @@ const styles = {
4213
4512
  graybeard,
4214
4513
  shadow,
4215
4514
  neutrino,
4515
+ satellite: buildSatelliteStyle,
4216
4516
  };
4217
4517
 
4218
- export { Color, colorful, eclipse, graybeard, guessStyle, neutrino, shadow, styles };
4518
+ export { Color, colorful, eclipse, graybeard, guessStyle, neutrino, buildSatelliteStyle as satellite, shadow, styles };
4219
4519
  //# sourceMappingURL=index.js.map