@versatiles/style 5.1.0 → 5.2.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 (50) hide show
  1. package/dist/color/abstract.d.ts +33 -0
  2. package/dist/color/abstract.js +49 -0
  3. package/dist/color/hsl.d.ts +23 -0
  4. package/dist/color/hsl.js +108 -0
  5. package/dist/color/hsv.d.ts +19 -0
  6. package/dist/color/hsv.js +101 -0
  7. package/dist/color/index.d.ts +6 -0
  8. package/dist/color/index.js +26 -0
  9. package/dist/{guess_style/random_color.d.ts → color/random.d.ts} +2 -2
  10. package/dist/color/random.js +133 -0
  11. package/dist/color/rgb.d.ts +28 -0
  12. package/dist/color/rgb.js +220 -0
  13. package/dist/color/utils.d.ts +3 -0
  14. package/dist/color/utils.js +9 -0
  15. package/dist/guess_style/guess_style.d.ts +8 -5
  16. package/dist/guess_style/guess_style.js +42 -108
  17. package/dist/guess_style/index.d.ts +2 -2
  18. package/dist/guess_style/index.js +1 -1
  19. package/dist/index.d.ts +9 -3
  20. package/dist/index.js +3 -2
  21. package/dist/lib/utils.js +3 -4
  22. package/dist/shortbread/template.d.ts +4 -2
  23. package/dist/shortbread/template.js +308 -309
  24. package/dist/style_builder/decorator.d.ts +1 -1
  25. package/dist/style_builder/decorator.js +3 -4
  26. package/dist/style_builder/recolor.d.ts +2 -1
  27. package/dist/style_builder/recolor.js +15 -49
  28. package/dist/style_builder/style_builder.d.ts +4 -4
  29. package/dist/style_builder/style_builder.js +9 -6
  30. package/dist/style_builder/types.d.ts +4 -6
  31. package/dist/styles/colorful.d.ts +1 -1
  32. package/dist/styles/colorful.js +9 -9
  33. package/dist/styles/eclipse.js +1 -4
  34. package/dist/styles/empty.d.ts +1 -1
  35. package/dist/styles/empty.js +1 -1
  36. package/dist/styles/graybeard.js +1 -1
  37. package/dist/styles/index.d.ts +3 -5
  38. package/dist/styles/neutrino.d.ts +1 -1
  39. package/dist/styles/neutrino.js +7 -7
  40. package/dist/types/index.d.ts +2 -2
  41. package/dist/types/maplibre.d.ts +2 -16
  42. package/dist/types/tilejson.d.ts +5 -10
  43. package/dist/types/tilejson.js +21 -38
  44. package/package.json +5 -8
  45. package/LICENSE +0 -21
  46. package/dist/browser.d.ts +0 -4
  47. package/dist/browser.js +0 -2
  48. package/dist/guess_style/random_color.js +0 -148
  49. package/dist/guess_style/types.d.ts +0 -23
  50. package/dist/guess_style/types.js +0 -1
@@ -0,0 +1,220 @@
1
+ import { HSL } from './hsl.js';
2
+ import { HSV } from './hsv.js';
3
+ import { Color } from './abstract.js';
4
+ import { clamp, formatFloat } from './utils.js';
5
+ export class RGB extends Color {
6
+ r = 0; // between 0 and 255
7
+ g = 0; // between 0 and 255
8
+ b = 0; // between 0 and 255
9
+ a = 1; // between 0 and 1
10
+ constructor(r, g, b, a = 1) {
11
+ super();
12
+ this.r = clamp(r, 0, 255);
13
+ this.g = clamp(g, 0, 255);
14
+ this.b = clamp(b, 0, 255);
15
+ this.a = clamp(a, 0, 1);
16
+ }
17
+ clone() {
18
+ return new RGB(this.r, this.g, this.b, this.a);
19
+ }
20
+ asArray() {
21
+ return [this.r, this.g, this.b, this.a];
22
+ }
23
+ round() {
24
+ this.r = Math.round(this.r);
25
+ this.g = Math.round(this.g);
26
+ this.b = Math.round(this.b);
27
+ this.a = Math.round(this.a * 1000) / 1000;
28
+ return this;
29
+ }
30
+ asString() {
31
+ if (this.a === 1) {
32
+ return `rgb(${this.r.toFixed(0)},${this.g.toFixed(0)},${this.b.toFixed(0)})`;
33
+ }
34
+ else {
35
+ return `rgba(${this.r.toFixed(0)},${this.g.toFixed(0)},${this.b.toFixed(0)},${formatFloat(this.a, 3)})`;
36
+ }
37
+ }
38
+ asHex() {
39
+ const r = Math.round(this.r).toString(16).padStart(2, '0');
40
+ const g = Math.round(this.g).toString(16).padStart(2, '0');
41
+ const b = Math.round(this.b).toString(16).padStart(2, '0');
42
+ if (this.a === 1) {
43
+ return `#${r}${g}${b}`.toUpperCase();
44
+ }
45
+ else {
46
+ const a = Math.round(this.a * 255).toString(16).padStart(2, '0');
47
+ return `#${r}${g}${b}${a}`.toUpperCase();
48
+ }
49
+ }
50
+ asHSL() {
51
+ const r = this.r / 255;
52
+ const g = this.g / 255;
53
+ const b = this.b / 255;
54
+ const min = Math.min(r, g, b);
55
+ const max = Math.max(r, g, b);
56
+ const delta = max - min;
57
+ let h = 0;
58
+ let s = 0;
59
+ if (max === min)
60
+ h = 0;
61
+ else if (r === max)
62
+ h = (g - b) / delta;
63
+ else if (g === max)
64
+ h = 2 + (b - r) / delta;
65
+ else if (b === max)
66
+ h = 4 + (r - g) / delta;
67
+ h = Math.min(h * 60, 360);
68
+ if (h < 0)
69
+ h += 360;
70
+ const l = (min + max) / 2;
71
+ if (max === min)
72
+ s = 0;
73
+ else if (l <= 0.5)
74
+ s = delta / (max + min);
75
+ else
76
+ s = delta / (2 - max - min);
77
+ return new HSL(h, s * 100, l * 100, this.a);
78
+ }
79
+ ;
80
+ asHSV() {
81
+ const r = this.r / 255;
82
+ const g = this.g / 255;
83
+ const b = this.b / 255;
84
+ const v = Math.max(r, g, b);
85
+ const diff = v - Math.min(r, g, b);
86
+ let h = 0;
87
+ let s = 0;
88
+ if (diff !== 0) {
89
+ function diffc(c) {
90
+ return (v - c) / 6 / diff + 1 / 2;
91
+ }
92
+ ;
93
+ s = diff / v;
94
+ const rdif = diffc(r);
95
+ const gdif = diffc(g);
96
+ const bdif = diffc(b);
97
+ if (r === v)
98
+ h = bdif - gdif;
99
+ else if (g === v)
100
+ h = (1 / 3) + rdif - bdif;
101
+ else if (b === v)
102
+ h = (2 / 3) + gdif - rdif;
103
+ if (h < 0)
104
+ h += 1;
105
+ else if (h > 1)
106
+ h -= 1;
107
+ }
108
+ return new HSV(h * 360, s * 100, v * 100, this.a);
109
+ }
110
+ asRGB() {
111
+ return this.clone();
112
+ }
113
+ toRGB() {
114
+ return this;
115
+ }
116
+ static parse(str) {
117
+ str = str.toLowerCase().replaceAll(/[^0-9a-z.#,()]/g, '');
118
+ let match;
119
+ match = str.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/);
120
+ if (match) {
121
+ const r = parseInt(match[1], 16);
122
+ const g = parseInt(match[2], 16);
123
+ const b = parseInt(match[3], 16);
124
+ const a = match[4] ? parseInt(match[4], 16) / 255 : 1;
125
+ return new RGB(r, g, b, a);
126
+ }
127
+ match = str.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/);
128
+ if (match) {
129
+ const r = parseInt(match[1], 16) * 17;
130
+ const g = parseInt(match[2], 16) * 17;
131
+ const b = parseInt(match[3], 16) * 17;
132
+ const a = match[4] ? parseInt(match[4], 16) / 15 : 1;
133
+ return new RGB(r, g, b, a);
134
+ }
135
+ str = str.trim().toLowerCase().replaceAll(' ', '');
136
+ match = str.match(/^rgb\((\d+),(\d+),(\d+)\)$/);
137
+ if (match) {
138
+ const r = parseInt(match[1]);
139
+ const g = parseInt(match[2]);
140
+ const b = parseInt(match[3]);
141
+ return new RGB(r, g, b);
142
+ }
143
+ match = str.match(/^rgba\((\d+),(\d+),(\d+),([.\d]+)\)$/);
144
+ if (match) {
145
+ const r = parseInt(match[1]);
146
+ const g = parseInt(match[2]);
147
+ const b = parseInt(match[3]);
148
+ const a = parseFloat(match[4]);
149
+ return new RGB(r, g, b, a);
150
+ }
151
+ throw new Error(`Invalid RGB color string: "${str}"`);
152
+ }
153
+ gamma(value) {
154
+ if (value < 1e-3)
155
+ value = 1e-3;
156
+ if (value > 1e3)
157
+ value = 1e3;
158
+ this.r = Math.pow(this.r / 255, value) * 255;
159
+ this.g = Math.pow(this.g / 255, value) * 255;
160
+ this.b = Math.pow(this.b / 255, value) * 255;
161
+ return this;
162
+ }
163
+ invert() {
164
+ this.r = 255 - this.r;
165
+ this.g = 255 - this.g;
166
+ this.b = 255 - this.b;
167
+ return this;
168
+ }
169
+ contrast(value) {
170
+ if (value < 0)
171
+ value = 0;
172
+ if (value > 1e6)
173
+ value = 1e6;
174
+ this.r = clamp((this.r - 127.5) * value + 127.5, 0, 255);
175
+ this.g = clamp((this.g - 127.5) * value + 127.5, 0, 255);
176
+ this.b = clamp((this.b - 127.5) * value + 127.5, 0, 255);
177
+ return this;
178
+ }
179
+ brightness(value) {
180
+ if (value < -1)
181
+ value = -1;
182
+ if (value > 1)
183
+ value = 1;
184
+ const a = 1 - Math.abs(value);
185
+ const b = (value < 0) ? 0 : 255 * value;
186
+ this.r = this.r * a + b;
187
+ this.g = this.g * a + b;
188
+ this.b = this.b * a + b;
189
+ return this;
190
+ }
191
+ tint(value, tintColor) {
192
+ if (value < 0)
193
+ value = 0;
194
+ if (value > 1)
195
+ value = 1;
196
+ const hsv = this.asHSV();
197
+ hsv.h = tintColor.toHSV().h;
198
+ const rgbNew = hsv.toRGB();
199
+ rgbNew.r = this.r * (1 - value) + value * rgbNew.r;
200
+ rgbNew.g = this.g * (1 - value) + value * rgbNew.g;
201
+ rgbNew.b = this.b * (1 - value) + value * rgbNew.b;
202
+ return rgbNew;
203
+ }
204
+ lighten(ratio) {
205
+ this.r = clamp(255 - (255 - this.r) * (1 - ratio), 0, 255);
206
+ this.g = clamp(255 - (255 - this.g) * (1 - ratio), 0, 255);
207
+ this.b = clamp(255 - (255 - this.b) * (1 - ratio), 0, 255);
208
+ return this;
209
+ }
210
+ darken(ratio) {
211
+ this.r = clamp(this.r * (1 - ratio), 0, 255);
212
+ this.g = clamp(this.g * (1 - ratio), 0, 255);
213
+ this.b = clamp(this.b * (1 - ratio), 0, 255);
214
+ return this;
215
+ }
216
+ fade(value) {
217
+ this.a *= 1 - value;
218
+ return this;
219
+ }
220
+ }
@@ -0,0 +1,3 @@
1
+ export declare function clamp(num: number, min: number, max: number): number;
2
+ export declare function mod(num: number, max: number): number;
3
+ export declare function formatFloat(num: number, precision: number): string;
@@ -0,0 +1,9 @@
1
+ export function clamp(num, min, max) {
2
+ return Math.min(Math.max(min, num), max);
3
+ }
4
+ export function mod(num, max) {
5
+ return ((num % max) + max) % max;
6
+ }
7
+ export function formatFloat(num, precision) {
8
+ return num.toFixed(precision).replace(/0+$/, '').replace(/\.$/, '');
9
+ }
@@ -1,5 +1,8 @@
1
- import type { MaplibreStyle } from '../types/index.js';
2
- import type { Container } from '@versatiles/container';
3
- import type { GuessContainerOptions, GuessStyleOptions } from './types.js';
4
- export declare function guessStyle(opt: GuessStyleOptions): MaplibreStyle;
5
- export declare function guessStyleFromContainer(container: Container, options: GuessContainerOptions): Promise<MaplibreStyle>;
1
+ import type { TileJSONSpecification } from '../types/index.js';
2
+ import type { SpriteSpecification, StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
3
+ export interface GuessStyleOptions {
4
+ baseUrl?: string;
5
+ glyphs?: string;
6
+ sprite?: SpriteSpecification;
7
+ }
8
+ export declare function guessStyle(tileJSON: TileJSONSpecification, options?: GuessStyleOptions): StyleSpecification;
@@ -1,112 +1,33 @@
1
- import { isTileJSONSpecification, isVectorLayers } from '../types/index.js';
2
- import { resolveUrl } from '../lib/utils.js';
3
- import randomColorGenerator from './random_color.js';
1
+ import { isTileJSONSpecification } from '../types/index.js';
2
+ import { deepClone, resolveUrl } from '../lib/utils.js';
4
3
  import { colorful } from '../styles/index.js';
5
- export function guessStyle(opt) {
6
- const { format } = opt;
7
- const tilejsonBasic = {
8
- tilejson: '3.0.0',
9
- attribution: opt.attribution,
10
- tiles: opt.tiles,
11
- scheme: opt.scheme,
12
- bounds: opt.bounds,
13
- center: opt.center,
14
- description: opt.description,
15
- fillzoom: opt.fillzoom,
16
- grids: opt.grids,
17
- legend: opt.legend,
18
- minzoom: opt.minzoom,
19
- maxzoom: opt.maxzoom,
20
- name: opt.name,
21
- template: opt.template,
22
- };
23
- const { baseUrl } = opt;
24
- if (typeof baseUrl === 'string') {
25
- tilejsonBasic.tiles = tilejsonBasic.tiles.map(url => resolveUrl(baseUrl, url));
26
- }
27
- let k;
28
- for (k in tilejsonBasic) {
29
- if (tilejsonBasic[k] === undefined)
30
- delete tilejsonBasic[k];
31
- }
32
- let tilejson;
33
- let vectorLayers;
34
- switch (format) {
35
- case 'avif':
36
- case 'jpg':
37
- case 'png':
38
- case 'webp':
39
- tilejson = { ...tilejsonBasic, type: 'raster', format };
40
- break;
41
- case 'pbf':
42
- vectorLayers = opt.vectorLayers;
43
- if (!isVectorLayers(vectorLayers))
44
- throw Error('property vector_layers is invalid');
45
- tilejson = { ...tilejsonBasic, type: 'vector', format, vector_layers: vectorLayers };
46
- break;
47
- default:
48
- throw Error(`format "${String(format)}" is not supported`);
4
+ import { Color } from '../color/index.js';
5
+ import { isRasterTileJSONSpecification } from '../types/tilejson.js';
6
+ export function guessStyle(tileJSON, options) {
7
+ tileJSON = deepClone(tileJSON);
8
+ if (options && options.baseUrl) {
9
+ const { baseUrl } = options;
10
+ tileJSON.tiles = tileJSON.tiles.map(url => resolveUrl(baseUrl, url));
49
11
  }
50
- if (!isTileJSONSpecification(tilejson))
51
- throw Error();
12
+ if (!isTileJSONSpecification(tileJSON))
13
+ throw Error('Invalid TileJSON specification');
52
14
  let style;
53
- switch (tilejson.type) {
54
- case 'raster':
55
- style = getImageStyle(tilejson);
56
- break;
57
- case 'vector':
58
- if (isShortbread(tilejson)) {
59
- style = getShortbreadStyle(tilejson, opt);
60
- }
61
- else {
62
- style = getInspectorStyle(tilejson);
63
- }
64
- }
65
- if (opt.minzoom ?? 0)
66
- style.zoom ??= opt.minzoom;
67
- if (opt.bounds)
68
- style.center ??= [
69
- (opt.bounds[0] + opt.bounds[2]) / 2,
70
- (opt.bounds[1] + opt.bounds[3]) / 2,
71
- ];
72
- if (opt.center)
73
- style.center = opt.center;
74
- return style;
75
- }
76
- export async function guessStyleFromContainer(container, options) {
77
- const header = await container.getHeader();
78
- const metadata = await container.getMetadata();
79
- const format = header.tileFormat;
80
- switch (format) {
81
- case 'avif':
82
- case 'jpg':
83
- case 'pbf':
84
- case 'png':
85
- case 'webp':
86
- break;
87
- default:
88
- throw Error(`format "${String(format)}" is not supported`);
15
+ if (isRasterTileJSONSpecification(tileJSON)) {
16
+ style = getRasterStyle(tileJSON);
89
17
  }
90
- let vectorLayers;
91
- if (typeof metadata === 'string') {
92
- try {
93
- const t = JSON.parse(metadata);
94
- if (('vector_layers' in t) && Array.isArray(t.vector_layers))
95
- vectorLayers = t.vector_layers;
18
+ else {
19
+ if (isShortbread(tileJSON)) {
20
+ style = getShortbreadStyle(tileJSON, {
21
+ baseUrl: options?.baseUrl,
22
+ glyphs: options?.glyphs,
23
+ sprite: options?.sprite,
24
+ });
96
25
  }
97
- catch (error) {
98
- console.log(error);
26
+ else {
27
+ style = getInspectorStyle(tileJSON);
99
28
  }
100
29
  }
101
- const guessStyleOptions = {
102
- ...options,
103
- format,
104
- bounds: header.bbox,
105
- minzoom: header.zoomMin,
106
- maxzoom: header.zoomMax,
107
- vectorLayers,
108
- };
109
- return guessStyle(guessStyleOptions);
30
+ return style;
110
31
  }
111
32
  function isShortbread(spec) {
112
33
  if (typeof spec !== 'object')
@@ -131,7 +52,6 @@ function getInspectorStyle(spec) {
131
52
  const sourceName = 'vectorSource';
132
53
  const layers = { background: [], circle: [], line: [], fill: [] };
133
54
  layers.background.push({ 'id': 'background', 'type': 'background', 'paint': { 'background-color': '#fff' } });
134
- const randomColor = randomColorGenerator();
135
55
  spec.vector_layers.forEach((vector_layer) => {
136
56
  let luminosity = 'bright', saturation, hue;
137
57
  if (/water|ocean|lake|sea|river/.test(vector_layer.id))
@@ -152,13 +72,13 @@ function getInspectorStyle(spec) {
152
72
  saturation = 'strong';
153
73
  luminosity = 'light';
154
74
  }
155
- const color = randomColor({
75
+ const color = Color.random({
156
76
  hue,
157
77
  luminosity,
158
78
  saturation,
159
79
  seed: vector_layer.id,
160
80
  opacity: 0.6,
161
- });
81
+ }).asString();
162
82
  layers.circle.push({
163
83
  id: `${sourceName}-${vector_layer.id}-circle`,
164
84
  'source-layer': vector_layer.id,
@@ -188,7 +108,7 @@ function getInspectorStyle(spec) {
188
108
  return {
189
109
  version: 8,
190
110
  sources: {
191
- [sourceName]: spec,
111
+ [sourceName]: sourceFromSpec(spec, 'vector'),
192
112
  },
193
113
  layers: [
194
114
  ...layers.background,
@@ -198,11 +118,13 @@ function getInspectorStyle(spec) {
198
118
  ],
199
119
  };
200
120
  }
201
- function getImageStyle(spec) {
121
+ function getRasterStyle(spec) {
202
122
  const sourceName = 'rasterSource';
203
123
  return {
204
124
  version: 8,
205
- sources: { [sourceName]: spec },
125
+ sources: {
126
+ [sourceName]: sourceFromSpec(spec, 'raster'),
127
+ },
206
128
  layers: [{
207
129
  id: 'raster',
208
130
  type: 'raster',
@@ -210,3 +132,15 @@ function getImageStyle(spec) {
210
132
  }],
211
133
  };
212
134
  }
135
+ function sourceFromSpec(spec, type) {
136
+ const source = { tiles: spec.tiles, type };
137
+ if (spec.minzoom != null)
138
+ source.minzoom = spec.minzoom;
139
+ if (spec.maxzoom != null)
140
+ source.maxzoom = spec.maxzoom;
141
+ if (spec.attribution != null)
142
+ source.attribution = spec.attribution;
143
+ if (spec.scheme != null)
144
+ source.scheme = spec.scheme;
145
+ return source;
146
+ }
@@ -1,2 +1,2 @@
1
- export type { GuessStyleOptions, GuessContainerOptions } from './types.js';
2
- export { guessStyle, guessStyleFromContainer } from './guess_style.js';
1
+ export type { GuessStyleOptions } from './guess_style.js';
2
+ export { guessStyle } from './guess_style.js';
@@ -1 +1 @@
1
- export { guessStyle, guessStyleFromContainer } from './guess_style.js';
1
+ export { guessStyle } from './guess_style.js';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  export type * from './styles/index.js';
2
- export * from './styles/index.js';
3
- export { guessStyle, guessStyleFromContainer } from './guess_style/index.js';
4
- export type { GuessStyleOptions, GuessContainerOptions } from './guess_style/index.js';
2
+ export * as styles from './styles/index.js';
3
+ export type { GuessStyleOptions } from './guess_style/index.js';
4
+ export type { RGB, HSL, HSV, RandomColorOptions } from './color/index.js';
5
+ export type { TileJSONSpecification, TileJSONSpecificationRaster, TileJSONSpecificationVector } from './types/tilejson.js';
5
6
  export type { VectorLayer } from './types/index.js';
7
+ export type { StyleBuilderOptions, StyleBuilderColorStrings, StyleBuilderFontStrings, Language, StyleBuilderColorKeys, StyleBuilderFontKeys } from './style_builder/types.js';
8
+ export type { RecolorOptions } from './style_builder/recolor.js';
9
+ export type { StyleBuilder } from './style_builder/style_builder.js';
10
+ export { guessStyle } from './guess_style/index.js';
11
+ export { Color } from './color/index.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- export * from './styles/index.js';
2
- export { guessStyle, guessStyleFromContainer } from './guess_style/index.js';
1
+ export * as styles from './styles/index.js';
2
+ export { guessStyle } from './guess_style/index.js';
3
+ export { Color } from './color/index.js';
package/dist/lib/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import Color from 'color';
1
+ import { Color } from '../color/index.js';
2
2
  // Utility function to deep clone an object
3
3
  export function deepClone(obj) {
4
4
  const type = typeof obj;
@@ -21,8 +21,7 @@ export function deepClone(obj) {
21
21
  return obj.map((e) => deepClone(e));
22
22
  }
23
23
  if (obj instanceof Color) {
24
- // @ts-expect-error: Too complicated to handle
25
- return Color(obj);
24
+ return obj.clone();
26
25
  }
27
26
  if (obj == null)
28
27
  return obj;
@@ -83,7 +82,7 @@ export function deepMerge(source0, ...sources) {
83
82
  }
84
83
  if (sourceValue instanceof Color) {
85
84
  // @ts-expect-error: Too complicated to handle
86
- target[key] = Color(sourceValue);
85
+ target[key] = sourceValue.clone();
87
86
  continue;
88
87
  }
89
88
  if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
@@ -1,2 +1,4 @@
1
- import type { MaplibreStyleVector } from '../types/index.js';
2
- export declare function getShortbreadTemplate(): MaplibreStyleVector;
1
+ import { StyleSpecification } from "@maplibre/maplibre-gl-style-spec";
2
+ import { VectorLayer } from "../types/vector_layer.js";
3
+ export declare function getShortbreadTemplate(): StyleSpecification;
4
+ export declare function getShortbreadVectorLayers(): VectorLayer[];