@versatiles/style 3.5.2 → 3.6.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.
@@ -1,19 +1,12 @@
1
1
  import Color from 'color';
2
- import type { LanguageSuffix, MaplibreStyle, RecolorOptions, StyleRules, StyleRulesOptions } from './types.js';
3
- export default abstract class StyleBuilder {
2
+ import type { MaplibreStyle, StyleRules, StyleRulesOptions, StylemakerColorStrings, StylemakerColors, StylemakerFontStrings, StylemakerOptions } from './types.js';
3
+ export default abstract class StyleBuilder<Subclass extends StyleBuilder<Subclass>> {
4
4
  #private;
5
- baseUrl: string;
6
- glyphsUrl: string;
7
- spriteUrl: string;
8
- tilesUrls: string[];
9
- hideLabels: boolean;
10
- languageSuffix: LanguageSuffix;
11
- recolor: RecolorOptions;
12
5
  abstract readonly name: string;
13
- abstract fonts: Record<string, string>;
14
- abstract colors: Record<string, string>;
15
- constructor();
16
- build(): MaplibreStyle;
17
- protected resetColors(callback: (color: Color) => Color): void;
18
- protected abstract getStyleRules(opt: StyleRulesOptions): StyleRules;
6
+ abstract readonly defaultColors: StylemakerColorStrings<Subclass>;
7
+ abstract readonly defaultFonts: StylemakerFontStrings<Subclass>;
8
+ build(options?: StylemakerOptions<Subclass>): MaplibreStyle;
9
+ getColors(colors: StylemakerColorStrings<Subclass>): StylemakerColors<Subclass>;
10
+ protected transformDefaultColors(callback: (color: Color) => Color): void;
11
+ protected abstract getStyleRules(options: StyleRulesOptions<Subclass>): StyleRules;
19
12
  }
@@ -3,42 +3,46 @@ import getShortbreadTemplate from './shortbread/template.js';
3
3
  import getShortbreadLayers from './shortbread/layers.js';
4
4
  import { decorate } from './decorator.js';
5
5
  import { getDefaultRecolorFlags, recolor } from './recolor.js';
6
+ import { deepClone } from './utils.js';
6
7
  // Stylemaker class definition
7
8
  export default class StyleBuilder {
8
- baseUrl;
9
- glyphsUrl = '/assets/fonts/{fontstack}/{range}.pbf';
10
- spriteUrl = '/assets/sprites/sprites';
11
- tilesUrls = ['/tiles/osm/{z}/{x}/{y}'];
12
- hideLabels = false;
13
- languageSuffix = '';
14
- recolor = getDefaultRecolorFlags();
15
9
  #sourceName = 'versatiles-shortbread';
16
- // Constructor
17
- constructor() {
18
- try {
19
- // @ts-expect-error: I'm not sure if I'm in a browser
20
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
21
- this.baseUrl = document.location.href;
10
+ build(options) {
11
+ options ??= {};
12
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
13
+ const baseUrl = options.baseUrl ?? globalThis?.document?.location?.href ?? 'https://tiles.versatiles.org';
14
+ const glyphsUrl = options.glyphsUrl ?? '/assets/fonts/{fontstack}/{range}.pbf';
15
+ const spriteUrl = options.spriteUrl ?? '/assets/sprites/sprites';
16
+ const tilesUrls = options.tilesUrls ?? ['/tiles/osm/{z}/{x}/{y}'];
17
+ const hideLabels = options.hideLabels ?? false;
18
+ const languageSuffix = options.languageSuffix ?? '';
19
+ const recolorOptions = options.recolor ?? getDefaultRecolorFlags();
20
+ const colors = this.getColors(this.defaultColors);
21
+ if (options.colors) {
22
+ for (const key in options.colors)
23
+ colors[key] = Color(options.colors[key]);
22
24
  }
23
- catch (e) {
24
- this.baseUrl = 'https://tiles.versatiles.org'; // set me in the browser
25
+ // transform colors
26
+ recolor(colors, recolorOptions);
27
+ const fonts = deepClone(this.defaultColors);
28
+ if (options.fonts) {
29
+ for (const key in options.fonts) {
30
+ const fontName = options.fonts[key];
31
+ if (fontName != null)
32
+ fonts[key] = fontName;
33
+ }
25
34
  }
26
- }
27
- build() {
28
35
  // get empty shortbread style
29
36
  const style = getShortbreadTemplate();
30
- const colors = Object.fromEntries(Object.entries(this.colors)
31
- .map(([name, colorString]) => [name, Color(colorString)]));
32
- // transform colors
33
- recolor(colors, this.recolor);
34
- // get layer style rules from child class
35
- const layerStyleRules = this.getStyleRules({
37
+ const styleRuleOptions = {
36
38
  colors,
37
- fonts: this.fonts,
38
- languageSuffix: this.languageSuffix,
39
- });
39
+ fonts: deepClone(this.defaultFonts),
40
+ languageSuffix,
41
+ };
42
+ // get layer style rules from child class
43
+ const layerStyleRules = this.getStyleRules(styleRuleOptions);
40
44
  // get shortbread layers
41
- const layerDefinitions = getShortbreadLayers({ languageSuffix: this.languageSuffix });
45
+ const layerDefinitions = getShortbreadLayers({ languageSuffix });
42
46
  let layers = layerDefinitions.map(layer => {
43
47
  switch (layer.type) {
44
48
  case 'background':
@@ -56,28 +60,35 @@ export default class StyleBuilder {
56
60
  // apply layer rules
57
61
  layers = decorate(layers, layerStyleRules);
58
62
  // hide labels, if wanted
59
- if (this.hideLabels)
63
+ if (hideLabels)
60
64
  layers = layers.filter(l => l.type !== 'symbol');
61
65
  style.layers = layers;
62
- style.name = 'versatiles-' + this.name;
63
- style.glyphs = resolveUrl(this.baseUrl, this.glyphsUrl);
64
- style.sprite = resolveUrl(this.baseUrl, this.spriteUrl);
66
+ style.name = 'versatiles-' + this.name.toLowerCase();
67
+ style.glyphs = resolveUrl(baseUrl, glyphsUrl);
68
+ style.sprite = resolveUrl(baseUrl, spriteUrl);
65
69
  const source = style.sources[this.#sourceName];
66
70
  if ('tiles' in source)
67
- source.tiles = this.tilesUrls.map(url => resolveUrl(this.baseUrl, url));
71
+ source.tiles = tilesUrls.map(url => resolveUrl(baseUrl, url));
68
72
  return style;
69
- function resolveUrl(baseUrl, url) {
70
- if (!Boolean(baseUrl))
73
+ function resolveUrl(base, url) {
74
+ if (!Boolean(base))
71
75
  return url;
72
- url = new URL(url, baseUrl).href;
76
+ url = new URL(url, base).href;
73
77
  url = url.replace(/%7B/gi, '{');
74
78
  url = url.replace(/%7D/gi, '}');
75
79
  return url;
76
80
  }
77
81
  }
78
- resetColors(callback) {
79
- const colors = Object.fromEntries(Object.entries(this.colors)
80
- .map(([name, color]) => [name, callback(Color(color)).hexa()]));
81
- this.colors = colors;
82
+ getColors(colors) {
83
+ const entriesString = Object.entries(colors);
84
+ const entriesColor = entriesString.map(([key, value]) => [key, Color(value)]);
85
+ const result = Object.fromEntries(entriesColor);
86
+ return result;
87
+ }
88
+ transformDefaultColors(callback) {
89
+ const colors = this.getColors(this.defaultColors);
90
+ for (const key in colors) {
91
+ this.defaultColors[key] = callback(colors[key]).hexa();
92
+ }
82
93
  }
83
94
  }
@@ -4,10 +4,10 @@ import StyleBuilder from './style_builder.js';
4
4
  // Mock class for abstract class StyleBuilder
5
5
  class MockStyleBuilder extends StyleBuilder {
6
6
  name = 'mock';
7
- fonts = { regular: 'Arial' };
8
- colors = { primary: '#FF8800' };
7
+ defaultFonts = { regular: 'Arial' };
8
+ defaultColors = { primary: '#FF8800' };
9
9
  negateColors() {
10
- this.resetColors((color) => color.negate());
10
+ this.transformDefaultColors(color => color.negate());
11
11
  }
12
12
  getStyleRules(opt) {
13
13
  for (const color of Object.values(opt.colors))
@@ -20,7 +20,7 @@ class MockStyleBuilder extends StyleBuilder {
20
20
  'water-area': {
21
21
  textColor: opt.colors.primary,
22
22
  textSize: 12,
23
- textFont: [opt.fonts.Arial],
23
+ textFont: opt.fonts.regular,
24
24
  },
25
25
  };
26
26
  }
@@ -41,11 +41,11 @@ describe('StyleBuilder', () => {
41
41
  expect(style).toHaveProperty('glyphs');
42
42
  expect(style).toHaveProperty('sprite');
43
43
  });
44
- it('should reset colors correctly', () => {
45
- const initialColor = builder.colors.primary;
44
+ it('should transform colors correctly', () => {
45
+ const initialColor = builder.defaultColors.primary;
46
46
  builder.negateColors();
47
- expect(builder.colors.primary).not.toBe(initialColor);
48
- expect(builder.colors.primary).toBe(Color(initialColor).negate().hexa());
47
+ expect(builder.defaultColors.primary).not.toBe(initialColor);
48
+ expect(builder.defaultColors.primary).toBe(Color(initialColor).negate().hexa());
49
49
  });
50
50
  describe('build method', () => {
51
51
  it('should create a style object', () => {
@@ -56,14 +56,8 @@ describe('StyleBuilder', () => {
56
56
  expect(style).toHaveProperty('glyphs');
57
57
  expect(style).toHaveProperty('sprite');
58
58
  });
59
- it('should convert color strings to Color instances', () => {
60
- builder.build();
61
- const colors = Object.values(builder.colors);
62
- expect(colors.every((color) => typeof color === 'string')).toBe(true);
63
- });
64
59
  it('should resolve urls correctly', () => {
65
- builder.baseUrl = 'https://my.base.url/';
66
- const style = builder.build();
60
+ const style = builder.build({ baseUrl: 'https://my.base.url/' });
67
61
  expect(style.glyphs).toBe('https://my.base.url/assets/fonts/{fontstack}/{range}.pbf');
68
62
  expect(style.sprite).toBe('https://my.base.url/assets/sprites/sprites');
69
63
  const source = style.sources['versatiles-shortbread'];
@@ -0,0 +1,2 @@
1
+ import type { MaplibreStyle, TileJSONOption } from './types.js';
2
+ export default function guess(spec: TileJSONOption): MaplibreStyle;
@@ -0,0 +1,148 @@
1
+ /* eslint-disable @typescript-eslint/prefer-includes */
2
+ /* eslint-disable @typescript-eslint/naming-convention */
3
+ import { isTileJSONSpecification } from './types.js';
4
+ import randomColorGenerator from './random_color.js';
5
+ import Colorful from '../style/colorful.js';
6
+ export default function guess(spec) {
7
+ let tilejson;
8
+ const { format } = spec;
9
+ switch (format) {
10
+ case 'avif':
11
+ case 'jpg':
12
+ case 'png':
13
+ case 'webp':
14
+ tilejson = {
15
+ tilejson: '3.0.0',
16
+ type: 'raster',
17
+ ...spec,
18
+ format,
19
+ };
20
+ break;
21
+ case 'pbf':
22
+ const { vector_layers } = spec;
23
+ if (vector_layers == null) {
24
+ throw Error('property vector_layers is required for vector tiles');
25
+ }
26
+ tilejson = {
27
+ tilejson: '3.0.0',
28
+ type: 'vector',
29
+ ...spec,
30
+ format,
31
+ vector_layers,
32
+ };
33
+ break;
34
+ }
35
+ if (!isTileJSONSpecification(tilejson))
36
+ throw Error();
37
+ switch (tilejson.type) {
38
+ case 'raster':
39
+ return getImageStyle(tilejson);
40
+ case 'vector':
41
+ if (isShortbread(tilejson)) {
42
+ return getShortbreadStyle(tilejson);
43
+ }
44
+ else {
45
+ return getInspectorStyle(tilejson);
46
+ }
47
+ }
48
+ }
49
+ function isShortbread(spec) {
50
+ if (typeof spec !== 'object')
51
+ return false;
52
+ if (!('vector_layers' in spec))
53
+ return false;
54
+ if (!Array.isArray(spec.vector_layers))
55
+ return false;
56
+ const layerIds = new Set(spec.vector_layers.map(l => String(l.id)));
57
+ 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'];
58
+ return shortbreadIds.every(id => layerIds.has(id));
59
+ }
60
+ function getShortbreadStyle(spec) {
61
+ return new Colorful().build({
62
+ hideLabels: true,
63
+ tilesUrls: spec.tiles,
64
+ });
65
+ }
66
+ function getInspectorStyle(spec) {
67
+ const sourceName = 'vectorSource';
68
+ const layers = { background: [], circle: [], line: [], fill: [] };
69
+ layers.background.push({ 'id': 'background', 'type': 'background', 'paint': { 'background-color': '#fff' } });
70
+ const randomColor = randomColorGenerator();
71
+ spec.vector_layers.forEach((vector_layer) => {
72
+ let luminosity = 'bright', saturation, hue;
73
+ if (/water|ocean|lake|sea|river/.test(vector_layer.id))
74
+ hue = 'blue';
75
+ if (/state|country|place/.test(vector_layer.id))
76
+ hue = 'pink';
77
+ if (/road|highway|transport|streets/.test(vector_layer.id))
78
+ hue = 'orange';
79
+ if (/contour|building/.test(vector_layer.id))
80
+ hue = 'monochrome';
81
+ if (/building/.test(vector_layer.id))
82
+ luminosity = 'dark';
83
+ if (/contour|landuse/.test(vector_layer.id))
84
+ hue = 'yellow';
85
+ if (/wood|forest|park|landcover|land/.test(vector_layer.id))
86
+ hue = 'green';
87
+ if (/point/.test(vector_layer.id)) {
88
+ saturation = 'strong';
89
+ luminosity = 'light';
90
+ }
91
+ const color = randomColor({
92
+ hue,
93
+ luminosity,
94
+ saturation,
95
+ seed: vector_layer.id,
96
+ opacity: 0.6,
97
+ });
98
+ layers.circle.push({
99
+ id: `${sourceName}-${vector_layer.id}-circle`,
100
+ 'source-layer': vector_layer.id,
101
+ source: sourceName,
102
+ type: 'circle',
103
+ filter: ['==', '$type', 'Point'],
104
+ paint: { 'circle-color': color, 'circle-radius': 2 },
105
+ });
106
+ layers.line.push({
107
+ id: `${sourceName}-${vector_layer.id}-line`,
108
+ 'source-layer': vector_layer.id,
109
+ source: sourceName,
110
+ type: 'line',
111
+ filter: ['==', '$type', 'LineString'],
112
+ layout: { 'line-join': 'round', 'line-cap': 'round' },
113
+ paint: { 'line-color': color },
114
+ });
115
+ layers.fill.push({
116
+ id: `${sourceName}-${vector_layer.id}-fill`,
117
+ 'source-layer': vector_layer.id,
118
+ source: sourceName,
119
+ type: 'fill',
120
+ filter: ['==', '$type', 'Polygon'],
121
+ paint: { 'fill-color': color, 'fill-opacity': 0.3, 'fill-antialias': true, 'fill-outline-color': color },
122
+ });
123
+ });
124
+ return {
125
+ version: 8,
126
+ sources: {
127
+ [sourceName]: spec,
128
+ },
129
+ layers: [
130
+ ...layers.background,
131
+ ...layers.fill,
132
+ ...layers.line,
133
+ ...layers.circle,
134
+ ],
135
+ };
136
+ }
137
+ function getImageStyle(spec) {
138
+ const sourceName = 'rasterSource';
139
+ return {
140
+ version: 8,
141
+ sources: { [sourceName]: spec },
142
+ layers: [{
143
+ id: 'raster',
144
+ type: 'raster',
145
+ source: sourceName,
146
+ }],
147
+ };
148
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ /* eslint-disable @typescript-eslint/naming-convention */
2
+ import guessStyle from './style_guesser.js';
3
+ describe('guessStyle', () => {
4
+ const tiles = ['https://example.com/tiles/{z}/{x}/{y}'];
5
+ it('should build raster styles', () => {
6
+ const type = 'raster';
7
+ const format = 'avif';
8
+ expect(guessStyle({ tiles, format }))
9
+ .toStrictEqual({
10
+ version: 8,
11
+ sources: { rasterSource: { format, tilejson: '3.0.0', tiles, type } },
12
+ layers: [{ id: 'raster', source: 'rasterSource', type }],
13
+ });
14
+ });
15
+ it('should build vector styles', () => {
16
+ const type = 'vector';
17
+ const format = 'pbf';
18
+ const vector_layers = [{ id: 'geometry', fields: { label: 'String', height: 'Number' } }];
19
+ expect(guessStyle({ tiles, format, vector_layers }))
20
+ .toStrictEqual({
21
+ version: 8,
22
+ sources: { vectorSource: { format, tilejson: '3.0.0', tiles, type, vector_layers } },
23
+ layers: [
24
+ {
25
+ id: 'background',
26
+ type: 'background',
27
+ paint: { 'background-color': '#fff' },
28
+ },
29
+ {
30
+ id: 'vectorSource-geometry-fill',
31
+ type: 'fill',
32
+ source: 'vectorSource',
33
+ 'source-layer': 'geometry',
34
+ filter: ['==', '$type', 'Polygon'],
35
+ paint: { 'fill-antialias': true, 'fill-color': 'hsla(7,57%,56%,0.6)', 'fill-opacity': 0.3, 'fill-outline-color': 'hsla(7,57%,56%,0.6)' },
36
+ },
37
+ {
38
+ id: 'vectorSource-geometry-line',
39
+ type: 'line',
40
+ source: 'vectorSource',
41
+ 'source-layer': 'geometry',
42
+ filter: ['==', '$type', 'LineString'],
43
+ layout: { 'line-cap': 'round', 'line-join': 'round' },
44
+ paint: { 'line-color': 'hsla(7,57%,56%,0.6)' },
45
+ },
46
+ {
47
+ id: 'vectorSource-geometry-circle',
48
+ type: 'circle',
49
+ source: 'vectorSource',
50
+ 'source-layer': 'geometry',
51
+ filter: ['==', '$type', 'Point'],
52
+ paint: { 'circle-color': 'hsla(7,57%,56%,0.6)', 'circle-radius': 2 },
53
+ },
54
+ ],
55
+ });
56
+ });
57
+ });
@@ -1,5 +1,6 @@
1
1
  import type { BackgroundLayerSpecification, FillLayerSpecification, FilterSpecification, LineLayerSpecification, StyleSpecification, SymbolLayerSpecification } from '@maplibre/maplibre-gl-style-spec';
2
2
  import type Color from 'color';
3
+ import type StyleBuilder from './style_builder.ts';
3
4
  export type TileFormat = 'avif' | 'bin' | 'geojson' | 'jpg' | 'json' | 'pbf' | 'png' | 'svg' | 'topojson' | 'webp';
4
5
  export type MaplibreLayer = BackgroundLayerSpecification | FillLayerSpecification | LineLayerSpecification | SymbolLayerSpecification;
5
6
  export type MaplibreLayerDefinition = BackgroundLayerSpecification | Omit<FillLayerSpecification, 'source'> | Omit<LineLayerSpecification, 'source'> | Omit<SymbolLayerSpecification, 'source'>;
@@ -11,9 +12,8 @@ export interface VectorLayer {
11
12
  minzoom?: number;
12
13
  maxzoom?: number;
13
14
  }
14
- export interface TileJSONSpecificationRaster {
15
+ export interface TileJSONSpecificationBasic {
15
16
  tilejson?: '3.0.0';
16
- type: 'raster';
17
17
  attribution?: string;
18
18
  tiles: string[];
19
19
  scheme?: 'tms' | 'xyz';
@@ -27,14 +27,21 @@ export interface TileJSONSpecificationRaster {
27
27
  maxzoom?: number;
28
28
  name?: string;
29
29
  template?: string;
30
+ }
31
+ export interface TileJSONSpecificationRaster extends TileJSONSpecificationBasic {
32
+ type: 'raster';
30
33
  format: 'avif' | 'jpg' | 'png' | 'webp';
31
34
  }
32
- export interface TileJSONSpecificationVector extends Omit<TileJSONSpecificationRaster, 'format' | 'type'> {
33
- vector_layers: VectorLayer[];
34
- format: 'pbf';
35
+ export interface TileJSONSpecificationVector extends TileJSONSpecificationBasic {
35
36
  type: 'vector';
37
+ format: 'pbf';
38
+ vector_layers: VectorLayer[];
36
39
  }
37
40
  export type TileJSONSpecification = TileJSONSpecificationRaster | TileJSONSpecificationVector;
41
+ export interface TileJSONOption extends TileJSONSpecificationBasic {
42
+ format: 'avif' | 'jpg' | 'pbf' | 'png' | 'webp';
43
+ vector_layers?: VectorLayer[];
44
+ }
38
45
  export type MaplibreStyleRaster = Omit<StyleSpecification, 'sources'> & {
39
46
  'sources': {
40
47
  [_: string]: TileJSONSpecificationRaster;
@@ -47,14 +54,18 @@ export type MaplibreStyleVector = Omit<StyleSpecification, 'sources'> & {
47
54
  };
48
55
  export type MaplibreStyle = MaplibreStyleRaster | MaplibreStyleVector;
49
56
  export type StyleRuleValue = boolean | number | object | string;
50
- export type StyleRule = Record<string, StyleRuleValue>;
51
- export type StyleRules = Record<string, StyleRule>;
52
- export type StylemakerColorLookup = Record<string, Color>;
53
- export type StylemakerStringLookup = Record<string, string>;
57
+ export type StyleRule = Record<string, StyleRuleValue | undefined>;
58
+ export type StyleRules = Record<string, StyleRule | undefined>;
54
59
  export type LanguageSuffix = '_de' | '_en' | '';
55
- export interface StyleRulesOptions {
56
- colors: StylemakerColorLookup;
57
- fonts: StylemakerStringLookup;
60
+ export type StylemakerColorKeys<T extends StyleBuilder<T>> = keyof T['defaultColors'];
61
+ export type StylemakerFontKeys<T extends StyleBuilder<T>> = keyof T['defaultFonts'];
62
+ export type StylemakerColorStrings<T extends StyleBuilder<T>> = Record<StylemakerColorKeys<T>, string>;
63
+ export type StylemakerFontStrings<T extends StyleBuilder<T>> = Record<StylemakerFontKeys<T>, string>;
64
+ export type StylemakerColors<T extends StyleBuilder<T>> = Record<StylemakerColorKeys<T>, Color>;
65
+ export type StylemakerFonts<T extends StyleBuilder<T>> = Record<StylemakerFontKeys<T>, string>;
66
+ export interface StyleRulesOptions<T extends StyleBuilder<T>> {
67
+ colors: StylemakerColors<T>;
68
+ fonts: StylemakerFontStrings<T>;
58
69
  languageSuffix: string;
59
70
  }
60
71
  export interface RecolorOptions {
@@ -67,4 +78,15 @@ export interface RecolorOptions {
67
78
  tint?: number;
68
79
  tintColor?: string;
69
80
  }
81
+ export interface StylemakerOptions<T extends StyleBuilder<T>> {
82
+ baseUrl?: string;
83
+ glyphsUrl?: string;
84
+ spriteUrl?: string;
85
+ tilesUrls?: string[];
86
+ hideLabels?: boolean;
87
+ languageSuffix?: LanguageSuffix;
88
+ colors?: Partial<StylemakerColorStrings<T>>;
89
+ fonts?: Partial<StylemakerFontStrings<T>>;
90
+ recolor?: RecolorOptions;
91
+ }
70
92
  export declare function isTileJSONSpecification(obj: unknown): obj is TileJSONSpecification;
package/dist/lib/types.js CHANGED
@@ -44,19 +44,19 @@ export function isTileJSONSpecification(obj) {
44
44
  if (typeof spec.template !== 'undefined' && typeof spec.template !== 'string') {
45
45
  throw Error('spec.template must be a string if present');
46
46
  }
47
+ if (!Array.isArray(spec.tiles) || spec.tiles.length === 0 || spec.tiles.some(url => typeof url !== 'string')) {
48
+ throw Error('spec.tiles must be an array of strings');
49
+ }
47
50
  if (spec.type === 'raster') {
48
51
  if (!['avif', 'jpg', 'png', 'webp'].includes(spec.format)) {
49
52
  throw Error('spec.format must be "avif", "jpg", "png", or "webp"');
50
53
  }
51
- if (!Array.isArray(spec.tiles) || spec.tiles.some(url => typeof url !== 'string')) {
52
- throw Error('spec.tiles must be an array of strings');
53
- }
54
54
  }
55
55
  else if (spec.type === 'vector') {
56
56
  if (spec.format !== 'pbf') {
57
57
  throw Error('spec.format must be "pbf"');
58
58
  }
59
- if (!Array.isArray(spec.vector_layers) || spec.vector_layers.some(layer => !validateVectorLayer(layer))) {
59
+ if (!Array.isArray(spec.vector_layers) || spec.vector_layers.length === 0 || spec.vector_layers.some(layer => !validateVectorLayer(layer))) {
60
60
  throw Error('spec.vector_layers must be an array of VectorLayer');
61
61
  }
62
62
  }
@@ -1,12 +1,12 @@
1
1
  import StyleBuilder from '../lib/style_builder.js';
2
2
  import type { StyleRules, StyleRulesOptions } from '../lib/types.js';
3
- export default class Colorful extends StyleBuilder {
3
+ export default class Colorful extends StyleBuilder<Colorful> {
4
4
  readonly name: string;
5
- fonts: {
5
+ defaultFonts: {
6
6
  regular: string;
7
7
  bold: string;
8
8
  };
9
- colors: {
9
+ defaultColors: {
10
10
  land: string;
11
11
  water: string;
12
12
  glacier: string;
@@ -49,5 +49,5 @@ export default class Colorful extends StyleBuilder {
49
49
  hospital: string;
50
50
  poi: string;
51
51
  };
52
- protected getStyleRules(options: StyleRulesOptions): StyleRules;
52
+ protected getStyleRules(options: StyleRulesOptions<Colorful>): StyleRules;
53
53
  }
@@ -1,12 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/naming-convention */
2
2
  import StyleBuilder from '../lib/style_builder.js';
3
3
  export default class Colorful extends StyleBuilder {
4
- name = 'colorful';
5
- fonts = {
4
+ name = 'Colorful';
5
+ defaultFonts = {
6
6
  regular: 'noto_sans_regular',
7
7
  bold: 'noto_sans_bold',
8
8
  };
9
- colors = {
9
+ defaultColors = {
10
10
  land: '#F9F4EE',
11
11
  water: '#BEDDF3',
12
12
  glacier: '#FFFFFF',
@@ -1,8 +1,8 @@
1
1
  import Colorful from './colorful.js';
2
2
  export default class Graybeard extends Colorful {
3
- name = 'graybeard';
3
+ name = 'Graybeard';
4
4
  constructor() {
5
5
  super();
6
- this.resetColors(color => color.desaturate(1));
6
+ this.transformDefaultColors(color => color.desaturate(1));
7
7
  }
8
8
  }
@@ -1,12 +1,12 @@
1
1
  import StyleBuilder from '../lib/style_builder.js';
2
2
  import type { StyleRules, StyleRulesOptions } from '../lib/types.js';
3
- export default class Neutrino extends StyleBuilder {
3
+ export default class Neutrino extends StyleBuilder<Neutrino> {
4
4
  readonly name: string;
5
- fonts: {
5
+ defaultFonts: {
6
6
  regular: string;
7
7
  bold: string;
8
8
  };
9
- colors: {
9
+ defaultColors: {
10
10
  land: string;
11
11
  water: string;
12
12
  grass: string;
@@ -20,5 +20,5 @@ export default class Neutrino extends StyleBuilder {
20
20
  rail: string;
21
21
  label: string;
22
22
  };
23
- protected getStyleRules(options: StyleRulesOptions): StyleRules;
23
+ protected getStyleRules(options: StyleRulesOptions<Neutrino>): StyleRules;
24
24
  }
@@ -1,12 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/naming-convention */
2
2
  import StyleBuilder from '../lib/style_builder.js';
3
3
  export default class Neutrino extends StyleBuilder {
4
- name = 'neutrino';
5
- fonts = {
4
+ name = 'Neutrino';
5
+ defaultFonts = {
6
6
  regular: 'noto_sans_regular',
7
7
  bold: 'noto_sans_bold',
8
8
  };
9
- colors = {
9
+ defaultColors = {
10
10
  land: '#f6f0f6',
11
11
  water: '#cbd2df',
12
12
  grass: '#e7e9e5',