@versatiles/style 5.8.4 → 5.9.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.
Files changed (54) hide show
  1. package/README.md +24 -18
  2. package/dist/index.d.ts +20 -14
  3. package/dist/index.js +151 -83
  4. package/dist/index.js.map +1 -1
  5. package/package.json +17 -18
  6. package/src/color/abstract.ts +0 -202
  7. package/src/color/hsl.test.ts +0 -177
  8. package/src/color/hsl.ts +0 -201
  9. package/src/color/hsv.test.ts +0 -169
  10. package/src/color/hsv.ts +0 -189
  11. package/src/color/index.test.ts +0 -99
  12. package/src/color/index.ts +0 -35
  13. package/src/color/random.test.ts +0 -57
  14. package/src/color/random.ts +0 -267
  15. package/src/color/rgb.test.ts +0 -215
  16. package/src/color/rgb.ts +0 -380
  17. package/src/color/utils.test.ts +0 -85
  18. package/src/color/utils.ts +0 -17
  19. package/src/guess_style/guess_style.test.ts +0 -140
  20. package/src/guess_style/guess_style.ts +0 -249
  21. package/src/guess_style/index.ts +0 -2
  22. package/src/index.test.ts +0 -131
  23. package/src/index.ts +0 -113
  24. package/src/lib/utils.test.ts +0 -198
  25. package/src/lib/utils.ts +0 -132
  26. package/src/shortbread/index.ts +0 -2
  27. package/src/shortbread/layers.test.ts +0 -81
  28. package/src/shortbread/layers.ts +0 -602
  29. package/src/shortbread/properties.test.ts +0 -44
  30. package/src/shortbread/properties.ts +0 -156
  31. package/src/shortbread/template.test.ts +0 -49
  32. package/src/shortbread/template.ts +0 -336
  33. package/src/style_builder/decorator.test.ts +0 -68
  34. package/src/style_builder/decorator.ts +0 -143
  35. package/src/style_builder/recolor.test.ts +0 -328
  36. package/src/style_builder/recolor.ts +0 -237
  37. package/src/style_builder/style_builder.test.ts +0 -148
  38. package/src/style_builder/style_builder.ts +0 -138
  39. package/src/style_builder/types.ts +0 -195
  40. package/src/styles/LICENSE.md +0 -41
  41. package/src/styles/colorful.test.ts +0 -91
  42. package/src/styles/colorful.ts +0 -1177
  43. package/src/styles/eclipse.ts +0 -11
  44. package/src/styles/empty.ts +0 -10
  45. package/src/styles/graybeard.ts +0 -11
  46. package/src/styles/index.ts +0 -32
  47. package/src/styles/neutrino.ts +0 -427
  48. package/src/styles/shadow.ts +0 -11
  49. package/src/types/index.ts +0 -5
  50. package/src/types/maplibre.ts +0 -25
  51. package/src/types/tilejson.test.ts +0 -96
  52. package/src/types/tilejson.ts +0 -125
  53. package/src/types/vector_layer.test.ts +0 -68
  54. package/src/types/vector_layer.ts +0 -69
package/README.md CHANGED
@@ -18,6 +18,7 @@
18
18
  | **eclipse** | <img width="384" src="https://versatiles.org/versatiles-style/eclipse.png" alt="eclipse style" /> |
19
19
  | **neutrino** | <img width="384" src="https://versatiles.org/versatiles-style/neutrino.png" alt="neutrino style" /> |
20
20
  | **shadow** | <img width="384" src="https://versatiles.org/versatiles-style/shadow.png" alt="shadow style" /> |
21
+ | **satellite** | <img width="384" src="https://versatiles.org/versatiles-style/satellite.png" alt="satellite style" /> |
21
22
 
22
23
  ---
23
24
 
@@ -95,8 +96,10 @@ The library offers the following style generation methods:
95
96
  - `graybeard(options)` - [Documentation](https://versatiles.org/versatiles-style/functions/graybeard.html)
96
97
  - `neutrino(options)` - [Documentation](https://versatiles.org/versatiles-style/functions/neutrino.html)
97
98
  - `shadow(options)` - [Documentation](https://versatiles.org/versatiles-style/functions/shadow.html)
99
+ - `satellite(options)` - [Documentation](https://versatiles.org/versatiles-style/functions/satellite.html)
98
100
 
99
101
  **`options`**: An optional object to customize the styles. [Learn more](https://versatiles.org/versatiles-style/interfaces/StyleBuilderOptions.html)
102
+ `satellite` uses a different options type: [SatelliteStyleOptions](https://versatiles.org/versatiles-style/interfaces/SatelliteStyleOptions.html)
100
103
 
101
104
  ### Guess Style Method
102
105
 
@@ -159,7 +162,7 @@ subgraph 1["color"]
159
162
  end
160
163
  subgraph 9["guess_style"]
161
164
  A["guess_style.ts"]
162
- Z["index.ts"]
165
+ 10["index.ts"]
163
166
  end
164
167
  subgraph B["lib"]
165
168
  C["utils.ts"]
@@ -171,7 +174,8 @@ Q["eclipse.ts"]
171
174
  R["empty.ts"]
172
175
  S["graybeard.ts"]
173
176
  T["neutrino.ts"]
174
- U["shadow.ts"]
177
+ U["satellite.ts"]
178
+ V["shadow.ts"]
175
179
  end
176
180
  subgraph G["style_builder"]
177
181
  H["style_builder.ts"]
@@ -185,13 +189,13 @@ K["layers.ts"]
185
189
  L["template.ts"]
186
190
  N["properties.ts"]
187
191
  end
188
- subgraph V["types"]
189
- W["index.ts"]
190
- X["tilejson.ts"]
191
- Y["vector_layer.ts"]
192
- 11["maplibre.ts"]
192
+ subgraph W["types"]
193
+ X["index.ts"]
194
+ Y["tilejson.ts"]
195
+ Z["vector_layer.ts"]
196
+ 12["maplibre.ts"]
193
197
  end
194
- 10["index.ts"]
198
+ 11["index.ts"]
195
199
  end
196
200
  3-->2
197
201
  3-->4
@@ -215,8 +219,8 @@ end
215
219
  A-->5
216
220
  A-->C
217
221
  A-->E
218
- A-->W
219
222
  A-->X
223
+ A-->Y
220
224
  C-->8
221
225
  E-->F
222
226
  E-->Q
@@ -224,6 +228,7 @@ E-->R
224
228
  E-->S
225
229
  E-->T
226
230
  E-->U
231
+ E-->V
227
232
  F-->H
228
233
  H-->8
229
234
  H-->C
@@ -241,15 +246,16 @@ Q-->F
241
246
  R-->F
242
247
  S-->F
243
248
  T-->F
244
- U-->F
245
- W-->X
246
- W-->Y
247
- Z-->A
248
- 10-->8
249
- 10-->Z
250
- 10-->E
251
-
252
- class 0,1,9,B,D,G,I,V subgraphs;
249
+ U-->S
250
+ V-->F
251
+ X-->Y
252
+ X-->Z
253
+ 10-->A
254
+ 11-->8
255
+ 11-->10
256
+ 11-->E
257
+
258
+ class 0,1,9,B,D,G,I,W subgraphs;
253
259
  classDef subgraphs fill-opacity:0.1, fill:#888, color:#888, stroke:#888;
254
260
  ```
255
261
 
package/dist/index.d.ts CHANGED
@@ -88,12 +88,6 @@ declare class RGB extends Color {
88
88
  * @returns The current RGB instance.
89
89
  */
90
90
  asRGB(): RGB;
91
- /**
92
- * Returns the RGB color.
93
- *
94
- * @returns The current RGB instance.
95
- */
96
- toRGB(): RGB;
97
91
  /**
98
92
  * Parses a string or Color instance into an RGB color.
99
93
  *
@@ -306,11 +300,6 @@ declare class HSL extends Color {
306
300
  * @returns The current HSL color.
307
301
  */
308
302
  asHSL(): HSL;
309
- /**
310
- * Returns the current HSL color.
311
- * @returns The current HSL color.
312
- */
313
- toHSL(): HSL;
314
303
  /**
315
304
  * Converts the HSL color to an HSV color.
316
305
  * @returns A new HSV color representing the same color.
@@ -667,7 +656,8 @@ interface StyleBuilderOptions {
667
656
  */
668
657
  recolor?: RecolorOptions;
669
658
  }
670
- type StyleBuilderColorKey = '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';
659
+ declare const styleBuilderColorKeys: readonly ["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"];
660
+ type StyleBuilderColorKey = (typeof styleBuilderColorKeys)[number];
671
661
  /** Records string values for color properties in a style builder. */
672
662
  type StyleBuilderColors<T = Color | string> = Record<StyleBuilderColorKey, T>;
673
663
  /** Records string values for font properties in a style builder. */
@@ -676,6 +666,21 @@ type StyleBuilderFonts = {
676
666
  bold: string;
677
667
  };
678
668
 
669
+ interface SatelliteStyleOptions {
670
+ baseUrl?: string;
671
+ rasterTiles?: string[];
672
+ overlayTiles?: string[];
673
+ overlay?: boolean;
674
+ language?: Language;
675
+ rasterOpacity?: number;
676
+ rasterHueRotate?: number;
677
+ rasterBrightnessMin?: number;
678
+ rasterBrightnessMax?: number;
679
+ rasterSaturation?: number;
680
+ rasterContrast?: number;
681
+ }
682
+ declare function buildSatelliteStyle(options?: SatelliteStyleOptions): StyleSpecification;
683
+
679
684
  interface StyleBuilderFunction {
680
685
  (options?: StyleBuilderOptions): StyleSpecification;
681
686
  getOptions(): StyleBuilderOptions;
@@ -757,7 +762,8 @@ declare const styles: {
757
762
  graybeard: StyleBuilderFunction;
758
763
  shadow: StyleBuilderFunction;
759
764
  neutrino: StyleBuilderFunction;
765
+ satellite: typeof buildSatelliteStyle;
760
766
  };
761
767
 
762
- export { Color, HSL, HSV, RGB, colorful, eclipse, graybeard, guessStyle, neutrino, shadow, styles };
763
- export type { GuessStyleOptions, Language, RandomColorOptions, RecolorOptions, StyleBuilderColorKey, StyleBuilderColors, StyleBuilderFonts, StyleBuilderFunction, StyleBuilderOptions, TileJSONSpecification, TileJSONSpecificationRaster, TileJSONSpecificationVector, VectorLayer };
768
+ export { Color, HSL, HSV, RGB, colorful, eclipse, graybeard, guessStyle, neutrino, buildSatelliteStyle as satellite, shadow, styles };
769
+ export type { GuessStyleOptions, Language, RandomColorOptions, RecolorOptions, SatelliteStyleOptions, StyleBuilderColorKey, StyleBuilderColors, StyleBuilderFonts, StyleBuilderFunction, StyleBuilderOptions, TileJSONSpecification, TileJSONSpecificationRaster, TileJSONSpecificationVector, VectorLayer };
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
  }
@@ -347,7 +347,7 @@ function getColorInfo(hue) {
347
347
  return color;
348
348
  }
349
349
  }
350
- 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.`);
351
351
  }
352
352
 
353
353
  /**
@@ -519,14 +519,6 @@ class RGB extends Color {
519
519
  asRGB() {
520
520
  return this.clone();
521
521
  }
522
- /**
523
- * Returns the RGB color.
524
- *
525
- * @returns The current RGB instance.
526
- */
527
- toRGB() {
528
- return this;
529
- }
530
522
  /**
531
523
  * Parses a string or Color instance into an RGB color.
532
524
  *
@@ -750,7 +742,7 @@ class HSV extends Color {
750
742
  const v = this.v / 100;
751
743
  const k = (2 - s) * v;
752
744
  const q = k < 1 ? k : 2 - k;
753
- 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);
754
746
  }
755
747
  /**
756
748
  * Returns the current HSV color.
@@ -917,13 +909,6 @@ class HSL extends Color {
917
909
  asHSL() {
918
910
  return this.clone();
919
911
  }
920
- /**
921
- * Returns the current HSL color.
922
- * @returns The current HSL color.
923
- */
924
- toHSL() {
925
- return this;
926
- }
927
912
  /**
928
913
  * Converts the HSL color to an HSV color.
929
914
  * @returns A new HSV color representing the same color.
@@ -1031,7 +1016,7 @@ Color.parse = function (input) {
1031
1016
  case 'hsla(':
1032
1017
  return HSL.parse(input);
1033
1018
  default:
1034
- 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(...)".`);
1035
1020
  }
1036
1021
  };
1037
1022
  Color.HSL = HSL;
@@ -2070,9 +2055,7 @@ function deepClone(obj) {
2070
2055
  }
2071
2056
  if (obj == null)
2072
2057
  return obj;
2073
- console.log('obj', obj);
2074
- console.log('obj.prototype', Object.getPrototypeOf(obj));
2075
- throw Error();
2058
+ throw new Error(`deepClone: Unsupported object type "${Object.getPrototypeOf(obj)?.constructor?.name ?? 'unknown'}"`);
2076
2059
  }
2077
2060
  function isSimpleObject(item) {
2078
2061
  if (item === null)
@@ -2098,7 +2081,7 @@ function isBasicType(item) {
2098
2081
  case 'object':
2099
2082
  return false;
2100
2083
  default:
2101
- throw Error('unknown type: ' + typeof item);
2084
+ throw new Error(`isBasicType: Unknown type "${typeof item}"`);
2102
2085
  }
2103
2086
  }
2104
2087
  function deepMerge(source0, ...sources) {
@@ -2110,9 +2093,7 @@ function deepMerge(source0, ...sources) {
2110
2093
  if (!(key in source))
2111
2094
  continue;
2112
2095
  const sourceValue = source[key];
2113
- // *********
2114
- // overwrite
2115
- // *********
2096
+ // Handle basic types (number, string, boolean) - always overwrite
2116
2097
  switch (typeof sourceValue) {
2117
2098
  case 'number':
2118
2099
  case 'string':
@@ -2120,28 +2101,23 @@ function deepMerge(source0, ...sources) {
2120
2101
  target[key] = sourceValue;
2121
2102
  continue;
2122
2103
  }
2104
+ // If target is a basic type, overwrite with deep clone of source
2123
2105
  if (isBasicType(target[key])) {
2124
2106
  target[key] = deepClone(sourceValue);
2125
2107
  continue;
2126
2108
  }
2109
+ // Handle Color instances - clone the source color
2127
2110
  if (sourceValue instanceof Color) {
2128
2111
  target[key] = sourceValue.clone();
2129
2112
  continue;
2130
2113
  }
2114
+ // If both are simple objects, merge them recursively
2131
2115
  if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
2132
2116
  target[key] = deepMerge(target[key], sourceValue);
2133
2117
  continue;
2134
2118
  }
2135
- // *********
2136
- // merge
2137
- // *********
2138
- if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
2139
- target[key] = deepMerge(target[key], sourceValue);
2140
- continue;
2141
- }
2142
- console.log('target[key]:', target[key]);
2143
- console.log('source[key]:', source[key]);
2144
- 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})`);
2145
2121
  }
2146
2122
  }
2147
2123
  return target;
@@ -2183,7 +2159,7 @@ function decorate(layers, rules, recolor) {
2183
2159
  const regExpString = id.replace(/[^a-z_:-]/g, (c) => {
2184
2160
  if (c === '*')
2185
2161
  return '[a-z_-]*';
2186
- 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.`);
2187
2163
  });
2188
2164
  const regExp = new RegExp(`^${regExpString}$`, 'i');
2189
2165
  return layerIds.filter((layerId) => regExp.test(layerId));
@@ -2233,27 +2209,24 @@ function decorate(layers, rules, recolor) {
2233
2209
  value = processExpression(value);
2234
2210
  break;
2235
2211
  default:
2236
- throw new Error(`unknown propertyDef.valueType "${propertyDef.valueType}" for key "${key}"`);
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.`);
2237
2213
  }
2238
2214
  switch (propertyDef.parent) {
2239
2215
  case 'layer':
2240
- // @ts-expect-error: too complex to handle
2241
2216
  layer[key] = value;
2242
2217
  break;
2243
2218
  case 'layout':
2244
2219
  if (!layer.layout)
2245
2220
  layer.layout = {};
2246
- // @ts-expect-error: too complex to handle
2247
2221
  layer.layout[key] = value;
2248
2222
  break;
2249
2223
  case 'paint':
2250
2224
  if (!layer.paint)
2251
2225
  layer.paint = {};
2252
- // @ts-expect-error: too complex to handle
2253
2226
  layer.paint[key] = value;
2254
2227
  break;
2255
2228
  default:
2256
- 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".`);
2257
2230
  }
2258
2231
  });
2259
2232
  }
@@ -2264,12 +2237,12 @@ function decorate(layers, rules, recolor) {
2264
2237
  const color = recolor.do(value);
2265
2238
  return color.asString();
2266
2239
  }
2267
- 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)}`);
2268
2241
  }
2269
2242
  function processFont(value) {
2270
2243
  if (typeof value === 'string')
2271
2244
  return [value];
2272
- 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)}`);
2273
2246
  }
2274
2247
  function processExpression(value, cbValue) {
2275
2248
  if (typeof value === 'object') {
@@ -2316,7 +2289,7 @@ function getDefaultRecolorFlags() {
2316
2289
  * Checks if the given options object contains any active recolor transformations.
2317
2290
  * @param opt The recolor options to validate.
2318
2291
  */
2319
- function isValidRecolorOptions(opt) {
2292
+ function hasActiveRecolorOptions(opt) {
2320
2293
  if (!opt)
2321
2294
  return false;
2322
2295
  return (opt.invertBrightness ||
@@ -2337,14 +2310,20 @@ class CachedRecolor {
2337
2310
  skip;
2338
2311
  opt;
2339
2312
  cache;
2313
+ parsedTintColor;
2314
+ parsedBlendColor;
2340
2315
  /**
2341
2316
  * Creates a cached recolor instance.
2342
2317
  * @param opt Optional recolor options.
2343
2318
  */
2344
2319
  constructor(opt) {
2345
- this.skip = !isValidRecolorOptions(opt);
2320
+ this.skip = !hasActiveRecolorOptions(opt);
2346
2321
  this.cache = new Map();
2347
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);
2348
2327
  }
2349
2328
  /**
2350
2329
  * Applies cached recoloring to a given color.
@@ -2357,7 +2336,7 @@ class CachedRecolor {
2357
2336
  const key = color.asHex();
2358
2337
  if (this.cache.has(key))
2359
2338
  return this.cache.get(key);
2360
- const recolored = recolor(color, this.opt);
2339
+ const recolored = recolor(color, this.opt, this.parsedTintColor, this.parsedBlendColor);
2361
2340
  this.cache.set(key, recolored);
2362
2341
  return recolored;
2363
2342
  }
@@ -2366,10 +2345,12 @@ class CachedRecolor {
2366
2345
  * Applies the specified recoloring transformations to a single color.
2367
2346
  * @param color The original color.
2368
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.
2369
2350
  * @returns A new `Color` instance with applied transformations.
2370
2351
  */
2371
- function recolor(color, opt) {
2372
- if (!isValidRecolorOptions(opt))
2352
+ function recolor(color, opt, parsedTintColor, parsedBlendColor) {
2353
+ if (!hasActiveRecolorOptions(opt))
2373
2354
  return color;
2374
2355
  if (opt.invertBrightness)
2375
2356
  color = color.invertLuminosity();
@@ -2377,16 +2358,16 @@ function recolor(color, opt) {
2377
2358
  color = color.rotateHue(opt.rotate);
2378
2359
  if (opt.saturate)
2379
2360
  color = color.saturate(opt.saturate);
2380
- if (opt.gamma != null && opt.gamma != 1)
2361
+ if (opt.gamma != null && opt.gamma !== 1)
2381
2362
  color = color.gamma(opt.gamma);
2382
- if (opt.contrast != null && opt.contrast != 1)
2363
+ if (opt.contrast != null && opt.contrast !== 1)
2383
2364
  color = color.contrast(opt.contrast);
2384
2365
  if (opt.brightness)
2385
2366
  color = color.brightness(opt.brightness);
2386
2367
  if (opt.tint && opt.tintColor != null)
2387
- color = color.tint(opt.tint, Color.parse(opt.tintColor));
2368
+ color = color.tint(opt.tint, parsedTintColor ?? Color.parse(opt.tintColor));
2388
2369
  if (opt.blend && opt.blendColor != null)
2389
- color = color.blend(opt.blend, Color.parse(opt.blendColor));
2370
+ color = color.blend(opt.blend, parsedBlendColor ?? Color.parse(opt.blendColor));
2390
2371
  return color;
2391
2372
  }
2392
2373
 
@@ -2478,7 +2459,8 @@ class StyleBuilder {
2478
2459
  // get shortbread layers
2479
2460
  const layerDefinitions = getShortbreadLayers({ language });
2480
2461
  let layers = layerDefinitions.map((layer) => {
2481
- switch (layer.type) {
2462
+ const { type, id } = layer;
2463
+ switch (type) {
2482
2464
  case 'background':
2483
2465
  return layer;
2484
2466
  case 'fill':
@@ -2489,7 +2471,7 @@ class StyleBuilder {
2489
2471
  ...layer,
2490
2472
  };
2491
2473
  }
2492
- throw Error('unknown layer type');
2474
+ throw new Error(`StyleBuilder: Unknown layer type "${type}" for layer "${id}". Expected "background", "fill", "line", or "symbol".`);
2493
2475
  });
2494
2476
  // apply layer rules
2495
2477
  layers = decorate(layers, layerStyleRules, new CachedRecolor(recolorOptions));
@@ -2499,7 +2481,7 @@ class StyleBuilder {
2499
2481
  style.layers = layers;
2500
2482
  style.name = 'versatiles-' + this.name.toLowerCase();
2501
2483
  style.glyphs = resolveUrl(baseUrl, glyphs);
2502
- if (typeof sprite == 'string') {
2484
+ if (typeof sprite === 'string') {
2503
2485
  style.sprite = [{ id: basename(sprite), url: resolveUrl(baseUrl, sprite) }];
2504
2486
  }
2505
2487
  else {
@@ -4061,6 +4043,92 @@ class Empty extends Colorful {
4061
4043
  }
4062
4044
  }
4063
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: [-180, -86, 180, 86],
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
+
4064
4132
  // import styles
4065
4133
  function getStyleBuilder(styleBuilder) {
4066
4134
  const fn = function (options) {
@@ -4083,76 +4151,76 @@ getStyleBuilder(Empty);
4083
4151
  */
4084
4152
  function isTileJSONSpecification(spec) {
4085
4153
  if (typeof spec !== 'object' || spec === null) {
4086
- throw Error('spec must be an object');
4154
+ throw new Error(`TileJSON validation: spec must be an object, but got ${typeof spec}`);
4087
4155
  }
4088
4156
  const obj = spec;
4089
4157
  // Common property validation
4090
4158
  if (obj.data != null && obj.tilejson !== '3.0.0') {
4091
- 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}"`);
4092
4160
  }
4093
4161
  if (obj.attribution != null && typeof obj.attribution !== 'string') {
4094
- 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}`);
4095
4163
  }
4096
4164
  if (obj.bounds != null) {
4097
4165
  if (!Array.isArray(obj.bounds) || obj.bounds.length !== 4 || obj.bounds.some((num) => typeof num !== 'number')) {
4098
- throw Error('spec.bounds must be an array of four numbers if present');
4166
+ throw new Error(`TileJSON validation: spec.bounds must be an array of four numbers if present, but got ${JSON.stringify(obj.bounds)}`);
4099
4167
  }
4100
4168
  const a = obj.bounds;
4101
4169
  if (a[0] < -180 || a[0] > 180)
4102
- 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]}`);
4103
4171
  if (a[1] < -90 || a[1] > 90)
4104
- 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]}`);
4105
4173
  if (a[2] < -180 || a[2] > 180)
4106
- 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]}`);
4107
4175
  if (a[3] < -90 || a[3] > 90)
4108
- 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]}`);
4109
4177
  if (a[0] > a[2])
4110
- 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]}]`);
4111
4179
  if (a[1] > a[3])
4112
- 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]}]`);
4113
4181
  }
4114
4182
  if (obj.center != null) {
4115
4183
  if (!Array.isArray(obj.center) || obj.center.length !== 2 || obj.center.some((num) => typeof num !== 'number')) {
4116
- throw Error('spec.center must be an array of two numbers if present');
4184
+ throw new Error(`TileJSON validation: spec.center must be an array of two numbers if present, but got ${JSON.stringify(obj.center)}`);
4117
4185
  }
4118
4186
  const a = obj.center;
4119
4187
  if (a[0] < -180 || a[0] > 180)
4120
- 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]}`);
4121
4189
  if (a[1] < -90 || a[1] > 90)
4122
- 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]}`);
4123
4191
  }
4124
4192
  if (obj.data != null && (!Array.isArray(obj.data) || obj.data.some((url) => typeof url !== 'string'))) {
4125
- throw Error('spec.data must be an array of strings if present');
4193
+ throw new Error('TileJSON validation: spec.data must be an array of strings if present');
4126
4194
  }
4127
4195
  if (obj.description != null && typeof obj.description !== 'string') {
4128
- 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}`);
4129
4197
  }
4130
4198
  if (obj.fillzoom != null && (typeof obj.fillzoom !== 'number' || obj.fillzoom < 0)) {
4131
- throw Error('spec.fillzoom must be a positive integer if present');
4199
+ throw new Error(`TileJSON validation: spec.fillzoom must be a positive integer if present, but got ${obj.fillzoom}`);
4132
4200
  }
4133
4201
  if (obj.grids != null && (!Array.isArray(obj.grids) || obj.grids.some((url) => typeof url !== 'string'))) {
4134
- throw Error('spec.grids must be an array of strings if present');
4202
+ throw new Error('TileJSON validation: spec.grids must be an array of strings if present');
4135
4203
  }
4136
4204
  if (obj.legend != null && typeof obj.legend !== 'string') {
4137
- 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}`);
4138
4206
  }
4139
4207
  if (obj.minzoom != null && (typeof obj.minzoom !== 'number' || obj.minzoom < 0)) {
4140
- throw Error('spec.minzoom must be a positive integer if present');
4208
+ throw new Error(`TileJSON validation: spec.minzoom must be a positive integer if present, but got ${obj.minzoom}`);
4141
4209
  }
4142
4210
  if (obj.maxzoom != null && (typeof obj.maxzoom !== 'number' || obj.maxzoom < 0)) {
4143
- throw Error('spec.maxzoom must be a positive integer if present');
4211
+ throw new Error(`TileJSON validation: spec.maxzoom must be a positive integer if present, but got ${obj.maxzoom}`);
4144
4212
  }
4145
4213
  if (obj.name != null && typeof obj.name !== 'string') {
4146
- 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}`);
4147
4215
  }
4148
4216
  if (obj.scheme != null && obj.scheme !== 'xyz' && obj.scheme !== 'tms') {
4149
- 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}"`);
4150
4218
  }
4151
4219
  if (obj.template != null && typeof obj.template !== 'string') {
4152
- 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}`);
4153
4221
  }
4154
4222
  if (!Array.isArray(obj.tiles) || obj.tiles.length === 0 || obj.tiles.some((url) => typeof url !== 'string')) {
4155
- throw Error('spec.tiles must be an array of strings');
4223
+ throw new Error('TileJSON validation: spec.tiles must be a non-empty array of strings');
4156
4224
  }
4157
4225
  return true;
4158
4226
  }
@@ -4181,8 +4249,7 @@ function guessStyle(tileJSON, options) {
4181
4249
  const { baseUrl } = options;
4182
4250
  tileJSON.tiles = tileJSON.tiles.map((url) => resolveUrl(baseUrl, url));
4183
4251
  }
4184
- if (!isTileJSONSpecification(tileJSON))
4185
- ;
4252
+ if (!isTileJSONSpecification(tileJSON)) ;
4186
4253
  let style;
4187
4254
  if (isRasterTileJSONSpecification(tileJSON)) {
4188
4255
  style = getRasterStyle(tileJSON);
@@ -4445,7 +4512,8 @@ const styles = {
4445
4512
  graybeard,
4446
4513
  shadow,
4447
4514
  neutrino,
4515
+ satellite: buildSatelliteStyle,
4448
4516
  };
4449
4517
 
4450
- export { Color, colorful, eclipse, graybeard, guessStyle, neutrino, shadow, styles };
4518
+ export { Color, colorful, eclipse, graybeard, guessStyle, neutrino, buildSatelliteStyle as satellite, shadow, styles };
4451
4519
  //# sourceMappingURL=index.js.map