@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.
- package/README.md +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/src/color/abstract.ts +0 -202
- package/src/color/hsl.test.ts +0 -176
- package/src/color/hsl.ts +0 -193
- package/src/color/hsv.test.ts +0 -169
- package/src/color/hsv.ts +0 -189
- package/src/color/index.test.ts +0 -187
- package/src/color/index.ts +0 -37
- package/src/color/random.test.ts +0 -111
- package/src/color/random.ts +0 -269
- package/src/color/rgb.test.ts +0 -218
- package/src/color/rgb.ts +0 -371
- package/src/color/utils.test.ts +0 -85
- package/src/color/utils.ts +0 -17
- package/src/guess_style/guess_style.test.ts +0 -140
- package/src/guess_style/guess_style.ts +0 -251
- package/src/guess_style/index.ts +0 -2
- package/src/index.test.ts +0 -131
- package/src/index.ts +0 -123
- package/src/lib/utils.test.ts +0 -281
- package/src/lib/utils.ts +0 -123
- package/src/shortbread/index.ts +0 -2
- package/src/shortbread/layers.test.ts +0 -81
- package/src/shortbread/layers.ts +0 -602
- package/src/shortbread/properties.test.ts +0 -44
- package/src/shortbread/properties.ts +0 -156
- package/src/shortbread/template.test.ts +0 -49
- package/src/shortbread/template.ts +0 -336
- package/src/style_builder/decorator.test.ts +0 -68
- package/src/style_builder/decorator.ts +0 -150
- package/src/style_builder/recolor.test.ts +0 -328
- package/src/style_builder/recolor.ts +0 -244
- package/src/style_builder/style_builder.test.ts +0 -148
- package/src/style_builder/style_builder.ts +0 -141
- package/src/style_builder/types.ts +0 -154
- package/src/styles/LICENSE.md +0 -41
- package/src/styles/colorful.test.ts +0 -91
- package/src/styles/colorful.ts +0 -1177
- package/src/styles/eclipse.ts +0 -11
- package/src/styles/empty.ts +0 -10
- package/src/styles/graybeard.ts +0 -11
- package/src/styles/index.ts +0 -34
- package/src/styles/neutrino.ts +0 -427
- package/src/styles/satellite.test.ts +0 -146
- package/src/styles/satellite.ts +0 -106
- package/src/styles/shadow.ts +0 -11
- package/src/types/index.ts +0 -5
- package/src/types/maplibre.ts +0 -25
- package/src/types/tilejson.test.ts +0 -96
- package/src/types/tilejson.ts +0 -147
- package/src/types/vector_layer.test.ts +0 -68
- package/src/types/vector_layer.ts +0 -67
package/src/styles/satellite.ts
DELETED
|
@@ -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
|
-
}
|
package/src/styles/shadow.ts
DELETED
|
@@ -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
|
-
}
|
package/src/types/index.ts
DELETED
|
@@ -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';
|
package/src/types/maplibre.ts
DELETED
|
@@ -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
|
-
});
|
package/src/types/tilejson.ts
DELETED
|
@@ -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
|
-
}
|