@versatiles/style 3.4.0

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 (41) hide show
  1. package/LICENSE.md +24 -0
  2. package/README.MD +94 -0
  3. package/dist/index.d.ts +4 -0
  4. package/dist/index.js +3 -0
  5. package/dist/index.test.d.ts +1 -0
  6. package/dist/index.test.js +38 -0
  7. package/dist/lib/decorator.d.ts +3 -0
  8. package/dist/lib/decorator.js +123 -0
  9. package/dist/lib/decorator.test.d.ts +1 -0
  10. package/dist/lib/decorator.test.js +56 -0
  11. package/dist/lib/recolor.d.ts +13 -0
  12. package/dist/lib/recolor.js +92 -0
  13. package/dist/lib/recolor.test.d.ts +1 -0
  14. package/dist/lib/recolor.test.js +179 -0
  15. package/dist/lib/shortbread/layers.d.ts +7 -0
  16. package/dist/lib/shortbread/layers.js +508 -0
  17. package/dist/lib/shortbread/layers.test.d.ts +1 -0
  18. package/dist/lib/shortbread/layers.test.js +27 -0
  19. package/dist/lib/shortbread/properties.d.ts +7 -0
  20. package/dist/lib/shortbread/properties.js +124 -0
  21. package/dist/lib/shortbread/properties.test.d.ts +1 -0
  22. package/dist/lib/shortbread/properties.test.js +36 -0
  23. package/dist/lib/shortbread/template.d.ts +2 -0
  24. package/dist/lib/shortbread/template.js +339 -0
  25. package/dist/lib/shortbread/template.test.d.ts +1 -0
  26. package/dist/lib/shortbread/template.test.js +57 -0
  27. package/dist/lib/style_builder.d.ts +32 -0
  28. package/dist/lib/style_builder.js +84 -0
  29. package/dist/lib/style_builder.test.d.ts +1 -0
  30. package/dist/lib/style_builder.test.js +77 -0
  31. package/dist/lib/utils.d.ts +4 -0
  32. package/dist/lib/utils.js +106 -0
  33. package/dist/lib/utils.test.d.ts +1 -0
  34. package/dist/lib/utils.test.js +97 -0
  35. package/dist/styles/colorful.d.ts +53 -0
  36. package/dist/styles/colorful.js +871 -0
  37. package/dist/styles/graybeard.d.ts +5 -0
  38. package/dist/styles/graybeard.js +9 -0
  39. package/dist/styles/neutrino.d.ts +24 -0
  40. package/dist/styles/neutrino.js +356 -0
  41. package/package.json +60 -0
@@ -0,0 +1,84 @@
1
+ // Import necessary modules and files
2
+ import Color from 'color';
3
+ import getShortbreadTemplate from './shortbread/template.js';
4
+ import getShortbreadLayers from './shortbread/layers.js';
5
+ import { decorate } from './decorator.js';
6
+ import { getDefaultRecolorFlags, recolor } from './recolor.js';
7
+ // Stylemaker class definition
8
+ export default class StyleBuilder {
9
+ baseUrl;
10
+ glyphsUrl = '/assets/fonts/{fontstack}/{range}.pbf';
11
+ spriteUrl = '/assets/sprites/sprites';
12
+ tilesUrls = ['/tiles/osm/{z}/{x}/{y}'];
13
+ hideLabels = false;
14
+ languageSuffix = '';
15
+ recolor = getDefaultRecolorFlags();
16
+ #sourceName = 'versatiles-shortbread';
17
+ // Constructor
18
+ constructor() {
19
+ try {
20
+ // @ts-expect-error: I'm not sure if I'm in a browser
21
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
22
+ this.baseUrl = document.location.href;
23
+ }
24
+ catch (e) {
25
+ this.baseUrl = 'https://tiles.versatiles.org'; // set me in the browser
26
+ }
27
+ }
28
+ build() {
29
+ // get empty shortbread style
30
+ const style = getShortbreadTemplate();
31
+ const colors = Object.fromEntries(Object.entries(this.colors)
32
+ .map(([name, colorString]) => [name, Color(colorString)]));
33
+ // transform colors
34
+ recolor(colors, this.recolor);
35
+ // get layer style rules from child class
36
+ const layerStyleRules = this.getStyleRules({
37
+ colors,
38
+ fonts: this.fonts,
39
+ languageSuffix: this.languageSuffix,
40
+ });
41
+ // get shortbread layers
42
+ let layers = getShortbreadLayers({ languageSuffix: this.languageSuffix });
43
+ // apply layer rules
44
+ layers = decorate(layers, layerStyleRules);
45
+ // hide labels, if wanted
46
+ if (this.hideLabels)
47
+ layers = layers.filter(l => l.type !== 'symbol');
48
+ // set source, if needed
49
+ layers.forEach(layer => {
50
+ switch (layer.type) {
51
+ case 'background':
52
+ delete layer.source;
53
+ return;
54
+ case 'fill':
55
+ case 'line':
56
+ case 'symbol':
57
+ layer.source = this.#sourceName;
58
+ return;
59
+ }
60
+ throw Error('unknown layer type');
61
+ });
62
+ style.layers = layers;
63
+ style.name = 'versatiles-' + this.name;
64
+ style.glyphs = resolveUrl(this.baseUrl, this.glyphsUrl);
65
+ style.sprite = resolveUrl(this.baseUrl, this.spriteUrl);
66
+ const source = style.sources[this.#sourceName];
67
+ if ('tiles' in source)
68
+ source.tiles = this.tilesUrls.map(url => resolveUrl(this.baseUrl, url));
69
+ return style;
70
+ function resolveUrl(baseUrl, url) {
71
+ if (!Boolean(baseUrl))
72
+ return url;
73
+ url = new URL(url, baseUrl).href;
74
+ url = url.replace(/%7B/gi, '{');
75
+ url = url.replace(/%7D/gi, '}');
76
+ return url;
77
+ }
78
+ }
79
+ resetColors(callback) {
80
+ const colors = Object.fromEntries(Object.entries(this.colors)
81
+ .map(([name, color]) => [name, callback(Color(color)).hexa()]));
82
+ this.colors = colors;
83
+ }
84
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ /* eslint-disable @typescript-eslint/naming-convention */
2
+ import Color from 'color';
3
+ import StyleBuilder from './style_builder.js';
4
+ // Mock class for abstract class StyleBuilder
5
+ class MockStyleBuilder extends StyleBuilder {
6
+ name = 'mock';
7
+ fonts = { regular: 'Arial' };
8
+ colors = { primary: '#FF8800' };
9
+ negateColors() {
10
+ this.resetColors((color) => color.negate());
11
+ }
12
+ getStyleRules(opt) {
13
+ for (const color of Object.values(opt.colors))
14
+ if (!(color instanceof Color))
15
+ throw Error();
16
+ for (const font of Object.values(opt.fonts))
17
+ if (typeof font !== 'string')
18
+ throw Error();
19
+ return {
20
+ 'water-area': {
21
+ textColor: opt.colors.primary,
22
+ textSize: 12,
23
+ textFont: [opt.fonts.Arial],
24
+ },
25
+ };
26
+ }
27
+ }
28
+ describe('StyleBuilder', () => {
29
+ // eslint-disable-next-line @typescript-eslint/init-declarations
30
+ let builder;
31
+ beforeEach(() => {
32
+ builder = new MockStyleBuilder();
33
+ });
34
+ it('should create an instance of StyleBuilder', () => {
35
+ expect(builder).toBeInstanceOf(StyleBuilder);
36
+ });
37
+ it('should build a MaplibreStyle object', () => {
38
+ const style = builder.build();
39
+ expect(style).toBeDefined();
40
+ expect(style).toHaveProperty('name');
41
+ expect(style).toHaveProperty('layers');
42
+ expect(style).toHaveProperty('glyphs');
43
+ expect(style).toHaveProperty('sprite');
44
+ });
45
+ it('should reset colors correctly', () => {
46
+ const initialColor = builder.colors.primary;
47
+ builder.negateColors();
48
+ expect(builder.colors.primary).not.toBe(initialColor);
49
+ expect(builder.colors.primary).toBe(Color(initialColor).negate().hexa());
50
+ });
51
+ describe('build method', () => {
52
+ it('should create a style object', () => {
53
+ const style = builder.build();
54
+ expect(style).toBeDefined();
55
+ expect(style).toHaveProperty('layers');
56
+ expect(style).toHaveProperty('name');
57
+ expect(style).toHaveProperty('glyphs');
58
+ expect(style).toHaveProperty('sprite');
59
+ });
60
+ it('should convert color strings to Color instances', () => {
61
+ builder.build();
62
+ const colors = Object.values(builder.colors);
63
+ expect(colors.every((color) => typeof color === 'string')).toBe(true);
64
+ });
65
+ it('should resolve urls correctly', () => {
66
+ builder.baseUrl = 'https://my.base.url/';
67
+ const style = builder.build();
68
+ expect(style.glyphs).toBe('https://my.base.url/assets/fonts/{fontstack}/{range}.pbf');
69
+ expect(style.sprite).toBe('https://my.base.url/assets/sprites/sprites');
70
+ const source = style.sources['versatiles-shortbread'];
71
+ expect(source).toHaveProperty('tiles');
72
+ if (!source.tiles)
73
+ return;
74
+ expect(source.tiles[0]).toBe('https://my.base.url/tiles/osm/{z}/{x}/{y}');
75
+ });
76
+ });
77
+ });
@@ -0,0 +1,4 @@
1
+ export declare function deepClone<T>(obj: T): T;
2
+ export declare function isSimpleObject(item: unknown): boolean;
3
+ export declare function isBasicType(item: unknown): boolean;
4
+ export declare function deepMerge<T extends object>(source0: T, ...sources: T[]): T;
@@ -0,0 +1,106 @@
1
+ import Color from 'color';
2
+ // Utility function to deep clone an object
3
+ export function deepClone(obj) {
4
+ const type = typeof obj;
5
+ if (type !== 'object') {
6
+ switch (type) {
7
+ case 'boolean':
8
+ case 'number':
9
+ case 'string':
10
+ case 'undefined':
11
+ return obj;
12
+ default: throw new Error(`Not implemented yet: "${type}" case`);
13
+ }
14
+ }
15
+ if (isSimpleObject(obj)) {
16
+ // @ts-expect-error: Too complicated to handle
17
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, deepClone(value)]));
18
+ }
19
+ if (obj instanceof Array) {
20
+ // @ts-expect-error: Too complicated to handle
21
+ return obj.map((e) => deepClone(e));
22
+ }
23
+ if (obj instanceof Color) {
24
+ // @ts-expect-error: Too complicated to handle
25
+ return Color(obj);
26
+ }
27
+ console.log('obj', obj);
28
+ console.log('obj.prototype', Object.getPrototypeOf(obj));
29
+ throw Error();
30
+ }
31
+ export function isSimpleObject(item) {
32
+ if (item === null)
33
+ return false;
34
+ if (typeof item !== 'object')
35
+ return false;
36
+ if (Array.isArray(item))
37
+ return false;
38
+ const prototypeKeyCount = Object.keys(Object.getPrototypeOf(item)).length;
39
+ if (prototypeKeyCount !== 0)
40
+ return false;
41
+ if (item.constructor.name !== 'Object')
42
+ return false;
43
+ return true;
44
+ }
45
+ export function isBasicType(item) {
46
+ switch (typeof item) {
47
+ case 'boolean':
48
+ case 'number':
49
+ case 'string':
50
+ case 'undefined':
51
+ return true;
52
+ case 'object':
53
+ return false;
54
+ default:
55
+ throw Error('unknown type: ' + typeof item);
56
+ }
57
+ }
58
+ export function deepMerge(source0, ...sources) {
59
+ const target = deepClone(source0);
60
+ for (const source of sources) {
61
+ if (typeof source !== 'object')
62
+ continue;
63
+ for (const key in source) {
64
+ if (!Object.hasOwn(source, key))
65
+ continue;
66
+ const sourceValue = source[key];
67
+ // *********
68
+ // overwrite
69
+ // *********
70
+ switch (typeof sourceValue) {
71
+ case 'number':
72
+ case 'string':
73
+ case 'boolean':
74
+ target[key] = sourceValue;
75
+ continue;
76
+ default:
77
+ }
78
+ if (isBasicType(target[key])) {
79
+ target[key] = deepClone(sourceValue);
80
+ continue;
81
+ }
82
+ if (sourceValue instanceof Color) {
83
+ // @ts-expect-error: Too complicated to handle
84
+ target[key] = Color(sourceValue);
85
+ continue;
86
+ }
87
+ if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
88
+ // @ts-expect-error: Too complicated to handle
89
+ target[key] = deepMerge(target[key], sourceValue);
90
+ continue;
91
+ }
92
+ // *********
93
+ // merge
94
+ // *********
95
+ if (isSimpleObject(target[key]) && isSimpleObject(sourceValue)) {
96
+ // @ts-expect-error: Too complicated to handle
97
+ target[key] = deepMerge(target[key], sourceValue);
98
+ continue;
99
+ }
100
+ console.log('target[key]:', target[key]);
101
+ console.log('source[key]:', source[key]);
102
+ throw Error('unpredicted case');
103
+ }
104
+ }
105
+ return target;
106
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,97 @@
1
+ import { deepClone, isSimpleObject, isBasicType, deepMerge } from './utils.js';
2
+ import Color from 'color';
3
+ describe('Utilities tests', () => {
4
+ describe('deepClone', () => {
5
+ it('clones primitive types correctly', () => {
6
+ expect(deepClone(10)).toBe(10);
7
+ expect(deepClone(true)).toBe(true);
8
+ expect(deepClone('string')).toBe('string');
9
+ });
10
+ it('clones an array correctly', () => {
11
+ const array = [1, 2, { a: 'b' }];
12
+ const clonedArray = deepClone(array);
13
+ expect(clonedArray).toEqual(array);
14
+ expect(clonedArray).not.toBe(array);
15
+ });
16
+ it('clones a simple object correctly', () => {
17
+ const obj = { a: 'b', c: 1, d: true };
18
+ const clonedObj = deepClone(obj);
19
+ expect(clonedObj).toEqual(obj);
20
+ expect(clonedObj).not.toBe(obj);
21
+ });
22
+ it('clones a Color instance correctly', () => {
23
+ const color = new Color('#FF5733');
24
+ const clonedColor = deepClone(color);
25
+ expect(clonedColor.hex()).toBe(color.hex());
26
+ });
27
+ it('throws an error for non-implemented types', () => {
28
+ const func = () => true;
29
+ expect(() => deepClone(func)).toThrow('Not implemented yet: "function" case');
30
+ });
31
+ });
32
+ describe('isSimpleObject', () => {
33
+ it('identifies simple objects correctly', () => {
34
+ expect(isSimpleObject({ a: 1 })).toBe(true);
35
+ });
36
+ it('rejects non-objects', () => {
37
+ expect(isSimpleObject(1)).toBe(false);
38
+ expect(isSimpleObject('a')).toBe(false);
39
+ expect(isSimpleObject(true)).toBe(false);
40
+ });
41
+ it('rejects arrays', () => {
42
+ expect(isSimpleObject([1, 2, 3])).toBe(false);
43
+ });
44
+ it('rejects objects with prototype properties', () => {
45
+ class MyClass {
46
+ #property = true;
47
+ }
48
+ expect(isSimpleObject(new MyClass())).toBe(false);
49
+ });
50
+ });
51
+ describe('isBasicType', () => {
52
+ it('returns true for basic types', () => {
53
+ expect(isBasicType(1)).toBe(true);
54
+ expect(isBasicType('string')).toBe(true);
55
+ expect(isBasicType(true)).toBe(true);
56
+ expect(isBasicType(undefined)).toBe(true);
57
+ });
58
+ it('returns false for objects', () => {
59
+ expect(isBasicType({})).toBe(false);
60
+ expect(isBasicType([])).toBe(false);
61
+ });
62
+ it('throws error for functions', () => {
63
+ expect(() => isBasicType(() => true)).toThrow();
64
+ });
65
+ });
66
+ describe('deepMerge', () => {
67
+ it('merges simple objects correctly', () => {
68
+ const target = { a: 1, b: 2 };
69
+ const source = { b: 3, c: 4 };
70
+ const result = deepMerge(target, source);
71
+ expect(result).toEqual({ a: 1, b: 3, c: 4 });
72
+ });
73
+ it('merges nested objects correctly', () => {
74
+ const target = { a: { d: 4 }, b: 2 };
75
+ const source = { a: { e: 5 }, b: 3 };
76
+ const result = deepMerge(target, source);
77
+ expect(result).toEqual({ a: { d: 4, e: 5 }, b: 3 });
78
+ });
79
+ it('overrides primitives with object types', () => {
80
+ const target = { a: 1, b: 2 };
81
+ const source = { a: { d: 4 }, b: { e: 5 } };
82
+ const result = deepMerge(target, source);
83
+ expect(result).toEqual({ a: { d: 4 }, b: { e: 5 } });
84
+ });
85
+ it('merges Color instances correctly', () => {
86
+ const target = { color: new Color('#FF5733') };
87
+ const source = { color: new Color('#33FF57') };
88
+ const result = deepMerge(target, source);
89
+ expect(result.color.hex()).toBe(source.color.hex());
90
+ });
91
+ it('throws error for unpredicted cases', () => {
92
+ const target = { a: () => false };
93
+ const source = { a: { b: 1 } };
94
+ expect(() => deepMerge(target, source)).toThrow('Not implemented yet: "function" case');
95
+ });
96
+ });
97
+ });
@@ -0,0 +1,53 @@
1
+ import StyleBuilder from '../lib/style_builder.js';
2
+ import type { StyleRules, StyleRulesOptions } from '../lib/style_builder.js';
3
+ export default class Colorful extends StyleBuilder {
4
+ readonly name: string;
5
+ fonts: {
6
+ regular: string;
7
+ bold: string;
8
+ };
9
+ colors: {
10
+ land: string;
11
+ water: string;
12
+ glacier: string;
13
+ wood: string;
14
+ grass: string;
15
+ park: string;
16
+ street: string;
17
+ streetbg: string;
18
+ motorway: string;
19
+ motorwaybg: string;
20
+ trunk: string;
21
+ trunkbg: string;
22
+ buildingbg: string;
23
+ building: string;
24
+ boundary: string;
25
+ disputed: string;
26
+ residential: string;
27
+ commercial: string;
28
+ industrial: string;
29
+ foot: string;
30
+ label: string;
31
+ labelHalo: string;
32
+ shield: string;
33
+ agriculture: string;
34
+ rail: string;
35
+ subway: string;
36
+ cycle: string;
37
+ waste: string;
38
+ burial: string;
39
+ sand: string;
40
+ rock: string;
41
+ leisure: string;
42
+ wetland: string;
43
+ symbol: string;
44
+ danger: string;
45
+ prison: string;
46
+ parking: string;
47
+ construction: string;
48
+ education: string;
49
+ hospital: string;
50
+ poi: string;
51
+ };
52
+ protected getStyleRules(options: StyleRulesOptions): StyleRules;
53
+ }