@versatiles/style 5.9.0 → 5.9.2

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 +1 -0
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.js +4 -11
  4. package/dist/index.js.map +1 -1
  5. package/package.json +2 -3
  6. package/src/color/abstract.ts +0 -202
  7. package/src/color/hsl.test.ts +0 -176
  8. package/src/color/hsl.ts +0 -193
  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 -187
  12. package/src/color/index.ts +0 -37
  13. package/src/color/random.test.ts +0 -111
  14. package/src/color/random.ts +0 -269
  15. package/src/color/rgb.test.ts +0 -218
  16. package/src/color/rgb.ts +0 -371
  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 -251
  21. package/src/guess_style/index.ts +0 -2
  22. package/src/index.test.ts +0 -131
  23. package/src/index.ts +0 -123
  24. package/src/lib/utils.test.ts +0 -281
  25. package/src/lib/utils.ts +0 -123
  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 -150
  35. package/src/style_builder/recolor.test.ts +0 -328
  36. package/src/style_builder/recolor.ts +0 -244
  37. package/src/style_builder/style_builder.test.ts +0 -148
  38. package/src/style_builder/style_builder.ts +0 -141
  39. package/src/style_builder/types.ts +0 -154
  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 -34
  47. package/src/styles/neutrino.ts +0 -427
  48. package/src/styles/satellite.test.ts +0 -146
  49. package/src/styles/satellite.ts +0 -106
  50. package/src/styles/shadow.ts +0 -11
  51. package/src/types/index.ts +0 -5
  52. package/src/types/maplibre.ts +0 -25
  53. package/src/types/tilejson.test.ts +0 -96
  54. package/src/types/tilejson.ts +0 -147
  55. package/src/types/vector_layer.test.ts +0 -68
  56. package/src/types/vector_layer.ts +0 -67
@@ -1,106 +0,0 @@
1
- import Graybeard from './graybeard.js';
2
- import type { StyleSpecification } from '../types/maplibre.js';
3
- import type { Language } from '../style_builder/types.js';
4
-
5
- export interface SatelliteStyleOptions {
6
- baseUrl?: string;
7
- rasterTiles?: string[];
8
- overlayTiles?: string[];
9
- overlay?: boolean;
10
- language?: Language;
11
- rasterOpacity?: number;
12
- rasterHueRotate?: number;
13
- rasterBrightnessMin?: number;
14
- rasterBrightnessMax?: number;
15
- rasterSaturation?: number;
16
- rasterContrast?: number;
17
- }
18
-
19
- export function buildSatelliteStyle(options?: SatelliteStyleOptions): StyleSpecification {
20
- options ??= {};
21
- const baseUrl = options.baseUrl ?? 'https://tiles.versatiles.org';
22
- const rasterTiles = options.rasterTiles ?? [`${baseUrl}/tiles/satellite/{z}/{x}/{y}`];
23
- const overlay = options.overlay ?? true;
24
-
25
- let style: StyleSpecification;
26
-
27
- if (overlay) {
28
- // Generate graybeard style for overlay
29
- style = new Graybeard().build({
30
- baseUrl,
31
- tiles: options.overlayTiles,
32
- language: options.language,
33
- });
34
-
35
- // Filter out background, fill layers, and unwanted layer groups
36
- style.layers = style.layers.filter(
37
- (l) => l.id !== 'background' && l.type !== 'fill' && !/^(land|water|site|airport|tunnel)-/.test(l.id)
38
- );
39
-
40
- // Modify remaining layers
41
- for (const layer of style.layers) {
42
- if (layer.type === 'symbol') {
43
- // Bold font, white text, black halo
44
- if (layer.layout?.['text-font']) {
45
- layer.layout['text-font'] = ['noto_sans_bold'];
46
- }
47
- if (layer.paint) {
48
- layer.paint['text-color'] = '#fff';
49
- layer.paint['text-halo-color'] = '#000';
50
- if ('text-halo-blur' in layer.paint) layer.paint['text-halo-blur'] = 0;
51
- if ('text-halo-width' in layer.paint) layer.paint['text-halo-width'] = 1;
52
- }
53
- }
54
-
55
- if (layer.type === 'line' && layer.paint) {
56
- // Multiply existing opacity by 0.2
57
- const v = layer.paint['line-opacity'];
58
- if (v == null) {
59
- layer.paint['line-opacity'] = 0.2;
60
- } else if (typeof v === 'number') {
61
- layer.paint['line-opacity'] = v * 0.2;
62
- } else if (typeof v === 'object' && 'stops' in v) {
63
- (v as { stops: [number, number][] }).stops = (v as { stops: [number, number][] }).stops.map(
64
- (s: [number, number]): [number, number] => [s[0], s[1] * 0.2]
65
- );
66
- }
67
- }
68
- }
69
- } else {
70
- // Minimal style with no overlay
71
- style = { version: 8, sources: {}, layers: [] } as unknown as StyleSpecification;
72
- }
73
-
74
- // Build raster paint properties
75
- const rasterPaint: Record<string, number> = {};
76
- if (options.rasterOpacity != null) rasterPaint['raster-opacity'] = options.rasterOpacity;
77
- if (options.rasterHueRotate != null) rasterPaint['raster-hue-rotate'] = options.rasterHueRotate;
78
- if (options.rasterBrightnessMin != null) rasterPaint['raster-brightness-min'] = options.rasterBrightnessMin;
79
- if (options.rasterBrightnessMax != null) rasterPaint['raster-brightness-max'] = options.rasterBrightnessMax;
80
- if (options.rasterSaturation != null) rasterPaint['raster-saturation'] = options.rasterSaturation;
81
- if (options.rasterContrast != null) rasterPaint['raster-contrast'] = options.rasterContrast;
82
-
83
- // Add raster source
84
- style.sources.satellite = {
85
- type: 'raster',
86
- tiles: rasterTiles,
87
- tileSize: 512,
88
- attribution: "<a href='https://versatiles.org/sources/'>VersaTiles sources</a>",
89
- bounds: [-178.187256, -21.401934, 55.846252, 58.061897],
90
- minzoom: 0,
91
- maxzoom: 17,
92
- };
93
-
94
- // Add raster layer at bottom
95
- style.layers.unshift({
96
- id: 'satellite',
97
- type: 'raster',
98
- source: 'satellite',
99
- minzoom: 0,
100
- ...(Object.keys(rasterPaint).length > 0 ? { paint: rasterPaint } : {}),
101
- } as StyleSpecification['layers'][number]);
102
-
103
- style.name = 'versatiles-satellite';
104
-
105
- return style;
106
- }
@@ -1,11 +0,0 @@
1
- import Colorful from './colorful.js';
2
-
3
- export default class Shadow extends Colorful {
4
- public readonly name: string = 'Shadow';
5
-
6
- public constructor() {
7
- super();
8
-
9
- this.transformDefaultColors((color) => color.saturate(-1).invert().brightness(0.2));
10
- }
11
- }
@@ -1,5 +0,0 @@
1
- export type { MaplibreLayerDefinition, MaplibreLayer } from './maplibre.js';
2
- export type { VectorLayer } from './vector_layer.js';
3
- export { isTileJSONSpecification } from './tilejson.js';
4
- export type { TileJSONSpecification, TileJSONSpecificationRaster, TileJSONSpecificationVector } from './tilejson.js';
5
- export { isVectorLayers } from './vector_layer.js';
@@ -1,25 +0,0 @@
1
- import type {
2
- BackgroundLayerSpecification,
3
- FillLayerSpecification,
4
- FilterSpecification,
5
- LineLayerSpecification,
6
- SymbolLayerSpecification,
7
- } from '@maplibre/maplibre-gl-style-spec';
8
- export type { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
9
-
10
- /** Type for Maplibre layers, including background, fill, line, and symbol specifications. */
11
- export type MaplibreLayer =
12
- | BackgroundLayerSpecification
13
- | FillLayerSpecification
14
- | LineLayerSpecification
15
- | SymbolLayerSpecification;
16
-
17
- /** Defines the structure of Maplibre layer definitions, omitting the 'source' property for fill, line, and symbol specifications. */
18
- export type MaplibreLayerDefinition =
19
- | BackgroundLayerSpecification
20
- | Omit<FillLayerSpecification, 'source'>
21
- | Omit<LineLayerSpecification, 'source'>
22
- | Omit<SymbolLayerSpecification, 'source'>;
23
-
24
- /** Represents a filter specification in Maplibre styles. */
25
- export type MaplibreFilter = FilterSpecification;
@@ -1,96 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { isTileJSONSpecification } from './tilejson.js';
3
-
4
- describe('isTileJSONSpecification', () => {
5
- const validVectorSpec = {
6
- tilejson: '3.0.0',
7
- type: 'vector',
8
- format: 'pbf',
9
- tiles: ['http://example.com/{z}/{x}/{y}.pbf'],
10
- vector_layers: [{ id: 'layer1', fields: { property1: 'Number' }, description: 'A test layer' }],
11
- };
12
- const validRasterSpec = {
13
- tilejson: '3.0.0',
14
- type: 'raster',
15
- format: 'png',
16
- tiles: ['http://example.com/{z}/{x}/{y}.png'],
17
- };
18
-
19
- it('should return true for a valid raster source', () => {
20
- expect(isTileJSONSpecification({ ...validRasterSpec })).toBeTruthy();
21
- });
22
-
23
- it('should return true for a valid vector source', () => {
24
- expect(isTileJSONSpecification({ ...validVectorSpec })).toBeTruthy();
25
- });
26
-
27
- it('should throw an error if not object', () => {
28
- expect(() => isTileJSONSpecification(null)).toThrow('spec must be an object');
29
- expect(() => isTileJSONSpecification(1)).toThrow('spec must be an object');
30
- });
31
-
32
- it('should throw an error if the tiles property is missing', () => {
33
- expect(() => isTileJSONSpecification({ ...validRasterSpec, tiles: undefined })).toThrow(
34
- 'spec.tiles must be a non-empty array of strings'
35
- );
36
- });
37
-
38
- it('should throw an error if the bounds property is invalid', () => {
39
- [
40
- { bounds: [-181, -90, 180, 90], errorMessage: 'spec.bounds[0]' },
41
- { bounds: [-180, -91, 180, 90], errorMessage: 'spec.bounds[1]' },
42
- { bounds: [-180, -90, 181, 90], errorMessage: 'spec.bounds[2]' },
43
- { bounds: [-180, -90, 180, 91], errorMessage: 'spec.bounds[3]' },
44
- { bounds: [180, -90, -180, 90], errorMessage: 'spec.bounds[0] must be smaller than spec.bounds[2]' },
45
- { bounds: [-180, 90, 180, -90], errorMessage: 'spec.bounds[1] must be smaller than spec.bounds[3]' },
46
- ].forEach(({ bounds, errorMessage }) => {
47
- expect(() => isTileJSONSpecification({ ...validVectorSpec, bounds })).toThrow(errorMessage);
48
- });
49
- });
50
-
51
- it('should throw an error if the center property is invalid', () => {
52
- [
53
- { center: [-181, 0], errorMessage: 'spec.center[0]' },
54
- { center: [181, 0], errorMessage: 'spec.center[0]' },
55
- { center: [0, -91], errorMessage: 'spec.center[1]' },
56
- { center: [0, 91], errorMessage: 'spec.center[1]' },
57
- ].forEach(({ center, errorMessage }) => {
58
- expect(() => isTileJSONSpecification({ ...validVectorSpec, center })).toThrow(errorMessage);
59
- });
60
- });
61
-
62
- describe('check every property', () => {
63
- [
64
- ['tiles', 'a non-empty array of strings', ['url'], 'url', [], [1], 1],
65
- ['attribution', 'a string if present', 'valid', 1],
66
- ['bounds', 'an array of four numbers if present', [1, 2, 3, 4], ['1', '2', '3', '4'], [1, 2, 3], [], 'invalid'],
67
- ['center', 'an array of two numbers if present', [1, 2], ['1', '2'], [1, 2, 3], [], 'invalid'],
68
- ['data', 'an array of strings if present', ['url'], 'url', [1], 1],
69
- ['description', 'a string if present', 'valid', 1],
70
- ['fillzoom', 'a positive integer if present', 5, 'invalid', -1],
71
- ['grids', 'an array of strings if present', ['1', '2', '3', '4'], [1, 2, 3, 4], 'invalid'],
72
- ['legend', 'a string if present', 'valid', 1],
73
- ['maxzoom', 'a positive integer if present', 5, 'invalid', -1],
74
- ['minzoom', 'a positive integer if present', 5, 'invalid', -1],
75
- ['name', 'a string if present', 'valid', 1],
76
- ['scheme', '"tms" or "xyz" if present', 'xyz', 'invalid', 1],
77
- ['template', 'a string if present', 'valid', 1],
78
- ].forEach((test) => {
79
- const key = test[0] as string;
80
- const errorMessage = test[1] as string;
81
- const values = test.slice(2) as unknown[];
82
- it(key, () => {
83
- for (let i = 0; i < values.length; i++) {
84
- const value = values[i];
85
- if (i === 0) {
86
- expect(isTileJSONSpecification({ ...validVectorSpec, [key]: value })).toBe(true);
87
- } else {
88
- expect(() => isTileJSONSpecification({ ...validVectorSpec, [key]: value })).toThrow(
89
- `spec.${key} must be ${errorMessage}`
90
- );
91
- }
92
- }
93
- });
94
- });
95
- });
96
- });
@@ -1,147 +0,0 @@
1
- import type { VectorLayer } from './vector_layer.js';
2
-
3
- /** Basic structure for TileJSON specification, applicable to both raster and vector types. */
4
- export interface TileJSONSpecificationRaster {
5
- tilejson?: '3.0.0';
6
- tiles: string[];
7
-
8
- attribution?: string;
9
- bounds?: [number, number, number, number];
10
- center?: [number, number];
11
- data?: string[];
12
- description?: string;
13
- fillzoom?: number;
14
- grids?: string[];
15
- legend?: string;
16
- maxzoom?: number;
17
- minzoom?: number;
18
- name?: string;
19
- scheme?: 'tms' | 'xyz';
20
- template?: string;
21
- version?: string;
22
- }
23
-
24
- /** Structure for TileJSON specification of vector type, specifying vector-specific properties. */
25
- export interface TileJSONSpecificationVector extends TileJSONSpecificationRaster {
26
- vector_layers: VectorLayer[];
27
- }
28
-
29
- /** Represents a TileJSON specification, which can be either raster or vector. */
30
- export type TileJSONSpecification = TileJSONSpecificationRaster | TileJSONSpecificationVector;
31
-
32
- /**
33
- * Checks if an object adheres to the TileJSON specification.
34
- * Throws errors if the object does not conform to the expected structure or types.
35
- */
36
- export function isTileJSONSpecification(spec: unknown): spec is TileJSONSpecification {
37
- if (typeof spec !== 'object' || spec === null) {
38
- throw new Error(`TileJSON validation: spec must be an object, but got ${typeof spec}`);
39
- }
40
-
41
- const obj = spec as Record<string, unknown>;
42
-
43
- // Common property validation
44
- if (obj.data != null && obj.tilejson !== '3.0.0') {
45
- throw new Error(`TileJSON validation: spec.tilejson must be "3.0.0", but got "${obj.tilejson}"`);
46
- }
47
-
48
- if (obj.attribution != null && typeof obj.attribution !== 'string') {
49
- throw new Error(
50
- `TileJSON validation: spec.attribution must be a string if present, but got ${typeof obj.attribution}`
51
- );
52
- }
53
-
54
- if (obj.bounds != null) {
55
- if (!Array.isArray(obj.bounds) || obj.bounds.length !== 4 || obj.bounds.some((num) => typeof num !== 'number')) {
56
- throw new Error(
57
- `TileJSON validation: spec.bounds must be an array of four numbers if present, but got ${JSON.stringify(obj.bounds)}`
58
- );
59
- }
60
- const a = obj.bounds as [number, number, number, number];
61
- if (a[0] < -180 || a[0] > 180)
62
- throw new Error(`TileJSON validation: spec.bounds[0] (longitude) must be between -180 and 180, but got ${a[0]}`);
63
- if (a[1] < -90 || a[1] > 90)
64
- throw new Error(`TileJSON validation: spec.bounds[1] (latitude) must be between -90 and 90, but got ${a[1]}`);
65
- if (a[2] < -180 || a[2] > 180)
66
- throw new Error(`TileJSON validation: spec.bounds[2] (longitude) must be between -180 and 180, but got ${a[2]}`);
67
- if (a[3] < -90 || a[3] > 90)
68
- throw new Error(`TileJSON validation: spec.bounds[3] (latitude) must be between -90 and 90, but got ${a[3]}`);
69
- if (a[0] > a[2])
70
- throw new Error(
71
- `TileJSON validation: spec.bounds[0] must be smaller than spec.bounds[2] (min longitude < max longitude), but got [${a[0]}, ${a[2]}]`
72
- );
73
- if (a[1] > a[3])
74
- throw new Error(
75
- `TileJSON validation: spec.bounds[1] must be smaller than spec.bounds[3] (min latitude < max latitude), but got [${a[1]}, ${a[3]}]`
76
- );
77
- }
78
-
79
- if (obj.center != null) {
80
- if (!Array.isArray(obj.center) || obj.center.length !== 2 || obj.center.some((num) => typeof num !== 'number')) {
81
- throw new Error(
82
- `TileJSON validation: spec.center must be an array of two numbers if present, but got ${JSON.stringify(obj.center)}`
83
- );
84
- }
85
- const a = obj.center as [number, number];
86
- if (a[0] < -180 || a[0] > 180)
87
- throw new Error(`TileJSON validation: spec.center[0] (longitude) must be between -180 and 180, but got ${a[0]}`);
88
- if (a[1] < -90 || a[1] > 90)
89
- throw new Error(`TileJSON validation: spec.center[1] (latitude) must be between -90 and 90, but got ${a[1]}`);
90
- }
91
-
92
- if (obj.data != null && (!Array.isArray(obj.data) || obj.data.some((url) => typeof url !== 'string'))) {
93
- throw new Error('TileJSON validation: spec.data must be an array of strings if present');
94
- }
95
-
96
- if (obj.description != null && typeof obj.description !== 'string') {
97
- throw new Error(
98
- `TileJSON validation: spec.description must be a string if present, but got ${typeof obj.description}`
99
- );
100
- }
101
-
102
- if (obj.fillzoom != null && (typeof obj.fillzoom !== 'number' || obj.fillzoom < 0)) {
103
- throw new Error(
104
- `TileJSON validation: spec.fillzoom must be a positive integer if present, but got ${obj.fillzoom}`
105
- );
106
- }
107
-
108
- if (obj.grids != null && (!Array.isArray(obj.grids) || obj.grids.some((url) => typeof url !== 'string'))) {
109
- throw new Error('TileJSON validation: spec.grids must be an array of strings if present');
110
- }
111
-
112
- if (obj.legend != null && typeof obj.legend !== 'string') {
113
- throw new Error(`TileJSON validation: spec.legend must be a string if present, but got ${typeof obj.legend}`);
114
- }
115
-
116
- if (obj.minzoom != null && (typeof obj.minzoom !== 'number' || obj.minzoom < 0)) {
117
- throw new Error(`TileJSON validation: spec.minzoom must be a positive integer if present, but got ${obj.minzoom}`);
118
- }
119
-
120
- if (obj.maxzoom != null && (typeof obj.maxzoom !== 'number' || obj.maxzoom < 0)) {
121
- throw new Error(`TileJSON validation: spec.maxzoom must be a positive integer if present, but got ${obj.maxzoom}`);
122
- }
123
-
124
- if (obj.name != null && typeof obj.name !== 'string') {
125
- throw new Error(`TileJSON validation: spec.name must be a string if present, but got ${typeof obj.name}`);
126
- }
127
-
128
- if (obj.scheme != null && obj.scheme !== 'xyz' && obj.scheme !== 'tms') {
129
- throw new Error(`TileJSON validation: spec.scheme must be "tms" or "xyz" if present, but got "${obj.scheme}"`);
130
- }
131
-
132
- if (obj.template != null && typeof obj.template !== 'string') {
133
- throw new Error(`TileJSON validation: spec.template must be a string if present, but got ${typeof obj.template}`);
134
- }
135
-
136
- if (!Array.isArray(obj.tiles) || obj.tiles.length === 0 || obj.tiles.some((url) => typeof url !== 'string')) {
137
- throw new Error('TileJSON validation: spec.tiles must be a non-empty array of strings');
138
- }
139
-
140
- return true;
141
- }
142
-
143
- export function isRasterTileJSONSpecification(spec: unknown): spec is TileJSONSpecificationRaster {
144
- if (!isTileJSONSpecification(spec)) return false;
145
- if ('vector_layers' in spec && spec.vector_layers != null) return false;
146
- return true;
147
- }
@@ -1,68 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import type { VectorLayer } from './vector_layer.js';
3
- import { isVectorLayer, isVectorLayers } from './vector_layer.js';
4
-
5
- describe('isVectorLayer', () => {
6
- it('should validate a correct VectorLayer object', () => {
7
- const validLayer: VectorLayer = { id: 'test-layer', fields: { field1: 'Number', field2: 'String' } };
8
-
9
- expect(() => isVectorLayer(validLayer)).not.toThrow();
10
- });
11
-
12
- it('should throw an error for non-object inputs', () => {
13
- verifyError(null, 'Layer must be a non-null object');
14
- verifyError(42, 'Layer must be a non-null object');
15
- });
16
-
17
- it('should throw an error for invalid id types', () => {
18
- verifyError({ id: 123, fields: {} }, 'Layer.id must be a string');
19
- });
20
-
21
- it('should throw an error for invalid fields', () => {
22
- verifyError({ id: 'test', fields: null }, 'Layer.fields must be a non-null object');
23
- verifyError(
24
- { id: 'test', fields: { field1: 'InvalidType' } },
25
- "Layer.fields values must be one of 'Boolean', 'Number', or 'String'"
26
- );
27
- });
28
-
29
- it('should throw an error for invalid optional properties', () => {
30
- verifyError({ id: 'test', fields: {}, description: 123 }, 'Layer.description must be a string if present');
31
- verifyError({ id: 'test', fields: {}, minzoom: -1 }, 'Layer.minzoom must be a non-negative number if present');
32
- verifyError({ id: 'test', fields: {}, maxzoom: 'high' }, 'Layer.maxzoom must be a non-negative number if present');
33
- verifyError({ id: 'test', fields: {}, maxzoom: -1 }, 'Layer.maxzoom must be a non-negative number if present');
34
- });
35
-
36
- function verifyError(layer: unknown, message: string): void {
37
- expect(() => isVectorLayer(layer)).toThrow(message);
38
- }
39
- });
40
-
41
- describe('isVectorLayers', () => {
42
- it('should validate an array of correct VectorLayer objects', () => {
43
- const validLayers = [
44
- { id: 'layer1', fields: { field1: 'Number' } },
45
- { id: 'layer2', fields: { field2: 'String' }, description: 'A test layer' },
46
- ];
47
-
48
- expect(() => isVectorLayers(validLayers)).not.toThrow();
49
- });
50
-
51
- it('should throw an error for non-array inputs', () => {
52
- expect(() => isVectorLayers(null)).toThrow('Expected an array of layers');
53
- expect(() => isVectorLayers({})).toThrow('Expected an array of layers');
54
- });
55
-
56
- it('should throw an error for empty arrays', () => {
57
- expect(() => isVectorLayers([])).toThrow('Array of layers cannot be empty');
58
- });
59
-
60
- it('should throw an error for arrays containing invalid layers', () => {
61
- const invalidLayers = [
62
- { id: 'layer1', fields: { field1: 'Number' } },
63
- { id: 'layer2', fields: { field2: 'InvalidType' } },
64
- ];
65
-
66
- expect(() => isVectorLayers(invalidLayers)).toThrow('Layer[1] is invalid');
67
- });
68
- });
@@ -1,67 +0,0 @@
1
- /** Represents the structure of a vector layer in a TileJSON specification. */
2
- export interface VectorLayer {
3
- id: string;
4
- fields: Record<string, 'Boolean' | 'Number' | 'String'>;
5
- description?: string;
6
- minzoom?: number;
7
- maxzoom?: number;
8
- }
9
-
10
- /**
11
- * Verifies if an object conforms to the VectorLayer structure.
12
- * Throws errors for any deviations from the expected structure or types.
13
- */
14
- export function isVectorLayer(layer: unknown): layer is VectorLayer {
15
- if (typeof layer !== 'object' || layer === null) {
16
- throw new Error('Layer must be a non-null object');
17
- }
18
-
19
- const obj = layer as Record<string, unknown>;
20
-
21
- if (typeof obj.id !== 'string') {
22
- throw new Error('Layer.id must be a string');
23
- }
24
-
25
- if (typeof obj.fields !== 'object' || obj.fields === null) {
26
- throw new Error('Layer.fields must be a non-null object');
27
- }
28
- if (Object.values(obj.fields).some((type) => !['Boolean', 'Number', 'String'].includes(type as string))) {
29
- throw new Error("Layer.fields values must be one of 'Boolean', 'Number', or 'String'");
30
- }
31
-
32
- if ('description' in obj && typeof obj.description !== 'string') {
33
- throw new Error('Layer.description must be a string if present');
34
- }
35
-
36
- if ('minzoom' in obj && (typeof obj.minzoom !== 'number' || obj.minzoom < 0)) {
37
- throw new Error('Layer.minzoom must be a non-negative number if present');
38
- }
39
-
40
- if ('maxzoom' in obj && (typeof obj.maxzoom !== 'number' || obj.maxzoom < 0)) {
41
- throw new Error('Layer.maxzoom must be a non-negative number if present');
42
- }
43
-
44
- return true;
45
- }
46
-
47
- export function isVectorLayers(layers: unknown): layers is VectorLayer[] {
48
- if (!Array.isArray(layers)) {
49
- throw new Error('Expected an array of layers');
50
- }
51
-
52
- if (layers.length === 0) {
53
- throw new Error('Array of layers cannot be empty');
54
- }
55
-
56
- layers.forEach((layer, index) => {
57
- try {
58
- if (!isVectorLayer(layer)) {
59
- throw new Error(`Layer[${index}] is invalid`);
60
- }
61
- } catch (cause) {
62
- throw new Error(`Layer[${index}] is invalid`, { cause });
63
- }
64
- });
65
-
66
- return true;
67
- }