styled-map-package-api 5.0.0-pre.3 → 5.0.0-pre.4

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 (94) hide show
  1. package/README.md +94 -0
  2. package/dist/download.d.ts +11 -21
  3. package/dist/fallbacks.d.ts +32 -0
  4. package/dist/from-mbtiles.d.ts +1 -3
  5. package/dist/index.d.ts +11 -24
  6. package/dist/reader.d.ts +28 -12
  7. package/dist/server.d.ts +23 -14
  8. package/dist/style-downloader.d.ts +13 -19
  9. package/dist/tile-downloader.d.ts +13 -23
  10. package/dist/types.d.ts +61 -0
  11. package/dist/utils/errors.d.ts +2 -4
  12. package/dist/utils/fetch.d.ts +3 -8
  13. package/dist/utils/file-formats.d.ts +3 -10
  14. package/dist/utils/geo.d.ts +17 -9
  15. package/dist/utils/mapbox.d.ts +8 -10
  16. package/dist/utils/misc.d.ts +3 -5
  17. package/dist/utils/streams.d.ts +6 -10
  18. package/dist/utils/style.d.ts +27 -16
  19. package/dist/utils/templates.d.ts +30 -25
  20. package/dist/validator.d.ts +66 -0
  21. package/dist/writer.d.ts +157 -4
  22. package/lib/download.js +125 -0
  23. package/lib/fallbacks.js +157 -0
  24. package/lib/from-mbtiles.js +131 -0
  25. package/lib/index.js +12 -0
  26. package/lib/reader.js +360 -0
  27. package/lib/server.js +222 -0
  28. package/lib/style-downloader.js +369 -0
  29. package/lib/tile-downloader.js +189 -0
  30. package/lib/types.ts +99 -0
  31. package/lib/utils/errors.js +24 -0
  32. package/lib/utils/fetch.js +104 -0
  33. package/lib/utils/file-formats.js +92 -0
  34. package/lib/utils/geo.js +97 -0
  35. package/lib/utils/mapbox.js +155 -0
  36. package/{dist/utils/misc.d.cts → lib/utils/misc.js} +9 -5
  37. package/lib/utils/streams.js +101 -0
  38. package/lib/utils/style.js +206 -0
  39. package/lib/utils/templates.js +165 -0
  40. package/lib/validator.js +789 -0
  41. package/lib/writer.js +652 -0
  42. package/package.json +30 -78
  43. package/dist/download.cjs +0 -100
  44. package/dist/download.d.cts +0 -63
  45. package/dist/download.js +0 -76
  46. package/dist/from-mbtiles.cjs +0 -127
  47. package/dist/from-mbtiles.d.cts +0 -14
  48. package/dist/from-mbtiles.js +0 -103
  49. package/dist/index.cjs +0 -46
  50. package/dist/index.d.cts +0 -24
  51. package/dist/index.js +0 -16
  52. package/dist/reader.cjs +0 -287
  53. package/dist/reader.d.cts +0 -67
  54. package/dist/reader.js +0 -259
  55. package/dist/server.cjs +0 -73
  56. package/dist/server.d.cts +0 -45
  57. package/dist/server.js +0 -49
  58. package/dist/style-downloader.cjs +0 -314
  59. package/dist/style-downloader.d.cts +0 -118
  60. package/dist/style-downloader.js +0 -290
  61. package/dist/tile-downloader.cjs +0 -156
  62. package/dist/tile-downloader.d.cts +0 -82
  63. package/dist/tile-downloader.js +0 -124
  64. package/dist/types-Bhn0-Ldk.d.cts +0 -201
  65. package/dist/types-Bhn0-Ldk.d.ts +0 -201
  66. package/dist/utils/errors.cjs +0 -41
  67. package/dist/utils/errors.d.cts +0 -18
  68. package/dist/utils/errors.js +0 -16
  69. package/dist/utils/fetch.cjs +0 -97
  70. package/dist/utils/fetch.d.cts +0 -50
  71. package/dist/utils/fetch.js +0 -63
  72. package/dist/utils/file-formats.cjs +0 -96
  73. package/dist/utils/file-formats.d.cts +0 -32
  74. package/dist/utils/file-formats.js +0 -70
  75. package/dist/utils/geo.cjs +0 -84
  76. package/dist/utils/geo.d.cts +0 -46
  77. package/dist/utils/geo.js +0 -56
  78. package/dist/utils/mapbox.cjs +0 -121
  79. package/dist/utils/mapbox.d.cts +0 -43
  80. package/dist/utils/mapbox.js +0 -91
  81. package/dist/utils/misc.cjs +0 -39
  82. package/dist/utils/misc.js +0 -13
  83. package/dist/utils/streams.cjs +0 -99
  84. package/dist/utils/streams.d.cts +0 -49
  85. package/dist/utils/streams.js +0 -73
  86. package/dist/utils/style.cjs +0 -126
  87. package/dist/utils/style.d.cts +0 -66
  88. package/dist/utils/style.js +0 -98
  89. package/dist/utils/templates.cjs +0 -124
  90. package/dist/utils/templates.d.cts +0 -79
  91. package/dist/utils/templates.js +0 -85
  92. package/dist/writer.cjs +0 -539
  93. package/dist/writer.d.cts +0 -4
  94. package/dist/writer.js +0 -516
@@ -0,0 +1,206 @@
1
+ import { expressions, validateStyleMin } from '@maplibre/maplibre-gl-style-spec'
2
+
3
+ /** @import {StyleSpecification, ExpressionSpecification, ValidationError} from '@maplibre/maplibre-gl-style-spec' */
4
+
5
+ /**
6
+ * For a given style, replace all font stacks (`text-field` properties) with the
7
+ * provided fonts. If no matching font is found, the first font in the stack is
8
+ * used.
9
+ *
10
+ * *Modifies the input style object*
11
+ *
12
+ * @param {StyleSpecification} style
13
+ * @param {string[]} fonts
14
+ */
15
+ export function replaceFontStacks(style, fonts) {
16
+ const mappedLayers = mapFontStacks(style.layers, (fontStack) => {
17
+ let match
18
+ for (const font of fontStack) {
19
+ if (fonts.includes(font)) {
20
+ match = font
21
+ break
22
+ }
23
+ }
24
+ return [match || fonts[0]]
25
+ })
26
+ style.layers = mappedLayers
27
+ return style
28
+ }
29
+
30
+ /**
31
+ * From given style layers, create a new style by calling the provided callback
32
+ * function on every font stack defined in the style.
33
+ *
34
+ * @param {StyleSpecification['layers']} layers
35
+ * @param {(fontStack: string[]) => string[]} callbackFn
36
+ * @returns {StyleSpecification['layers']}
37
+ */
38
+ export function mapFontStacks(layers, callbackFn) {
39
+ return layers.map((layer) => {
40
+ if (layer.type !== 'symbol' || !layer.layout || !layer.layout['text-font'])
41
+ return layer
42
+ const textFont = layer.layout['text-font']
43
+ let mappedValue
44
+ if (isExpression(textFont)) {
45
+ mappedValue = mapArrayExpressionValue(textFont, callbackFn)
46
+ } else if (Array.isArray(textFont)) {
47
+ mappedValue = callbackFn(textFont)
48
+ } else {
49
+ // Deprecated property function, unsupported, but within this module
50
+ // functions will have been migrated to expressions anyway.
51
+ console.warn(
52
+ 'Deprecated function definitions are not supported, font stack has not been transformed.',
53
+ )
54
+ console.dir(textFont, { depth: null })
55
+ return layer
56
+ }
57
+ return {
58
+ ...layer,
59
+ layout: {
60
+ ...layer.layout,
61
+ 'text-font': mappedValue,
62
+ },
63
+ }
64
+ })
65
+ }
66
+
67
+ /**
68
+ * See https://github.com/maplibre/maplibre-style-spec/blob/c2f01dbaa6c5fb8409126258b9464b450018e939/src/expression/index.ts#L128
69
+ *
70
+ * @param {unknown} value
71
+ * @returns {value is ExpressionSpecification}
72
+ */
73
+ function isExpression(value) {
74
+ return (
75
+ Array.isArray(value) &&
76
+ value.length > 0 &&
77
+ typeof value[0] === 'string' &&
78
+ value[0] in expressions
79
+ )
80
+ }
81
+
82
+ /**
83
+ * For an expression whose value is an array, map the array to a new array using
84
+ * the given callbackFn.
85
+ *
86
+ * @param {ExpressionSpecification} expression
87
+ * @param {(value: string[]) => string[]} callbackFn
88
+ * @returns {ExpressionSpecification}
89
+ */
90
+ function mapArrayExpressionValue(expression, callbackFn) {
91
+ // This only works for properties whose value is an array, because it relies
92
+ // on the style specification that array values must be declared with the
93
+ // `literal` expression.
94
+ if (expression[0] === 'literal' && Array.isArray(expression[1])) {
95
+ return ['literal', callbackFn(expression[1])]
96
+ } else {
97
+ // @ts-ignore
98
+ return [
99
+ expression[0],
100
+ ...expression.slice(1).map(
101
+ // @ts-ignore
102
+ (x) => {
103
+ if (isExpression(x)) {
104
+ return mapArrayExpressionValue(x, callbackFn)
105
+ } else {
106
+ return x
107
+ }
108
+ },
109
+ ),
110
+ ]
111
+ }
112
+ }
113
+
114
+ /**
115
+ * PBF glyph ranges rendered client-side by MapLibre GL via
116
+ * `localIdeographFontFamily` (enabled by default as 'sans-serif'). SMP files
117
+ * do not need to include glyph data for these ranges. Each entry is a
118
+ * half-open interval [start, end) of PBF range start codepoints.
119
+ *
120
+ * Based on `codePointUsesLocalIdeographFontFamily()` in MapLibre GL JS.
121
+ * Only ranges where the ENTIRE 256-codepoint PBF range is locally rendered
122
+ * are listed here; partially-local ranges are conservatively kept as required.
123
+ */
124
+ export const LOCAL_GLYPH_RANGES = [
125
+ [0x3000, 0x3400], // CJK Symbols, Hiragana, Katakana, Bopomofo, CJK Strokes, Enclosed CJK, CJK Compat
126
+ [0x3400, 0x4e00], // CJK Unified Ideographs Extension A
127
+ [0x4e00, 0xa000], // CJK Unified Ideographs
128
+ [0xa000, 0xa400], // Yi Syllables + Yi Radicals
129
+ [0xac00, 0xd800], // Hangul Syllables + Hangul Jamo Extended-B
130
+ [0xf900, 0xfb00], // CJK Compatibility Ideographs
131
+ [0xff00, 0x10000], // Halfwidth and Fullwidth Forms
132
+ ]
133
+
134
+ /**
135
+ * Check whether a PBF glyph range (identified by its start codepoint) is
136
+ * rendered client-side by MapLibre GL and does not need a server-side PBF file.
137
+ * @param {number} rangeStart
138
+ * @returns {boolean}
139
+ */
140
+ export function isLocallyRenderedRange(rangeStart) {
141
+ return LOCAL_GLYPH_RANGES.some(
142
+ ([start, end]) => rangeStart >= start && rangeStart < end,
143
+ )
144
+ }
145
+
146
+ /**
147
+ * @typedef {object} TileJSONPartial
148
+ * @property {string[]} tiles
149
+ * @property {string} [description]
150
+ * @property {string} [attribution]
151
+ * @property {object[]} [vector_layers]
152
+ * @property {import('./geo.js').BBox} [bounds]
153
+ * @property {number} [maxzoom]
154
+ * @property {number} [minzoom]
155
+ */
156
+
157
+ /**
158
+ *
159
+ * @param {unknown} tilejson
160
+ * @returns {asserts tilejson is TileJSONPartial}
161
+ */
162
+ export function assertTileJSON(tilejson) {
163
+ if (typeof tilejson !== 'object' || tilejson === null) {
164
+ throw new Error('Invalid TileJSON')
165
+ }
166
+ if (
167
+ !('tiles' in tilejson) ||
168
+ !Array.isArray(tilejson.tiles) ||
169
+ tilejson.tiles.length === 0 ||
170
+ tilejson.tiles.some((tile) => typeof tile !== 'string')
171
+ ) {
172
+ throw new Error('Invalid TileJSON: missing or invalid tiles property')
173
+ }
174
+ }
175
+
176
+ export const validateStyle =
177
+ /** @type {{ (style: unknown): style is StyleSpecification, errors: ValidationError[] }} */ (
178
+ (style) => {
179
+ validateStyle.errors = validateStyleMin(
180
+ /** @type {StyleSpecification} */ (style),
181
+ )
182
+ if (validateStyle.errors.length) return false
183
+ return true
184
+ }
185
+ )
186
+
187
+ /**
188
+ * Check whether a source is already inlined (e.g. does not reference a TileJSON or GeoJSON url)
189
+ *
190
+ * @param {import('@maplibre/maplibre-gl-style-spec').SourceSpecification} source
191
+ * @returns {source is import('../types.js').InlinedSource}
192
+ */
193
+ export function isInlinedSource(source) {
194
+ if (source.type === 'geojson') {
195
+ return typeof source.data === 'object'
196
+ } else if (
197
+ source.type === 'vector' ||
198
+ source.type === 'raster' ||
199
+ source.type === 'raster-dem'
200
+ ) {
201
+ return 'tiles' in source
202
+ } else {
203
+ // Video and image sources are not strictly "inlined", but we treat them as such.
204
+ return true
205
+ }
206
+ }
@@ -0,0 +1,165 @@
1
+ export const URI_SCHEME = 'smp' // "Styled Map Package"
2
+ export const URI_BASE = URI_SCHEME + '://maps.v1/'
3
+
4
+ // These constants determine the file format structure
5
+ export const VERSION_FILE = 'VERSION'
6
+ export const FORMAT_VERSION = '1.0'
7
+ export const STYLE_FILE = 'style.json'
8
+ export const SOURCES_FOLDER = 's'
9
+ const SPRITES_FOLDER = 'sprites'
10
+ export const FONTS_FOLDER = 'fonts'
11
+
12
+ // This must include placeholders `{z}`, `{x}`, `{y}`, since these are used to
13
+ // define the tile URL, and this is a TileJSON standard.
14
+ // The folder here is just `s` to minimize bytes used for filenames, which are
15
+ // included in the header of every tile in the zip file.
16
+ const TILE_FILE = SOURCES_FOLDER + '/{sourceId}/{z}/{x}/{y}{ext}'
17
+ // The pixel ratio and ext placeholders must be at the end of the string with no
18
+ // data between them, because this is the format defined in the MapLibre style spec.
19
+ const SPRITE_FILE = SPRITES_FOLDER + '/{id}/sprite{pixelRatio}{ext}'
20
+ // This must include placeholders `{fontstack}` and `{range}`, since these are
21
+ // part of the MapLibre style spec.
22
+ const GLYPH_FILE = FONTS_FOLDER + '/{fontstack}/{range}.pbf.gz'
23
+ export const GLYPH_URI = URI_BASE + GLYPH_FILE
24
+
25
+ const pathToResouceType = /** @type {const} */ ({
26
+ [TILE_FILE.split('/')[0] + '/']: 'tile',
27
+ [SPRITE_FILE.split('/')[0] + '/']: 'sprite',
28
+ [GLYPH_FILE.split('/')[0] + '/']: 'glyph',
29
+ })
30
+
31
+ /**
32
+ * @param {string} path
33
+ * @returns
34
+ */
35
+ export function getResourceType(path) {
36
+ if (path === 'style.json') return 'style'
37
+ for (const [prefix, type] of Object.entries(pathToResouceType)) {
38
+ if (path.startsWith(prefix)) return type
39
+ }
40
+ throw new Error(`Unknown resource type for path: ${path}`)
41
+ }
42
+
43
+ /**
44
+ * Determine the content type of a file based on its extension.
45
+ *
46
+ * @param {string} path
47
+ */
48
+ export function getContentType(path) {
49
+ if (path.endsWith('.json') || path.endsWith('.geojson'))
50
+ return 'application/json; charset=utf-8'
51
+ if (path.endsWith('.pbf.gz') || path.endsWith('.pbf'))
52
+ return 'application/x-protobuf'
53
+ if (path.endsWith('.png')) return 'image/png'
54
+ if (path.endsWith('.jpg')) return 'image/jpeg'
55
+ if (path.endsWith('.webp')) return 'image/webp'
56
+ if (path.endsWith('.mvt.gz') || path.endsWith('.mvt'))
57
+ return 'application/vnd.mapbox-vector-tile'
58
+ throw new Error(`Unknown content type for path: ${path}`)
59
+ }
60
+
61
+ /**
62
+ * Get the filename for a tile, given the TileInfo
63
+ *
64
+ * @param {import("type-fest").SetRequired<import("../writer.js").TileInfo, 'format'>} tileInfo
65
+ * @returns
66
+ */
67
+ export function getTileFilename({ sourceId, z, x, y, format }) {
68
+ const ext = '.' + format + (format === 'mvt' ? '.gz' : '')
69
+ return replaceVariables(TILE_FILE, { sourceId, z, x, y, ext })
70
+ }
71
+
72
+ /**
73
+ * Get a filename for a sprite file, given the sprite id, pixel ratio and extension
74
+ *
75
+ * @param {{ id: string, pixelRatio: number, ext: '.json' | '.png'}} spriteInfo
76
+ */
77
+ export function getSpriteFilename({ id, pixelRatio, ext }) {
78
+ return replaceVariables(SPRITE_FILE, {
79
+ id,
80
+ pixelRatio: getPixelRatioString(pixelRatio),
81
+ ext,
82
+ })
83
+ }
84
+
85
+ /**
86
+ * Get the filename for a glyph file, given the fontstack and range
87
+ *
88
+ * @param {object} options
89
+ * @param {string} options.fontstack
90
+ * @param {import("../writer.js").GlyphRange} options.range
91
+ */
92
+ export function getGlyphFilename({ fontstack, range }) {
93
+ return replaceVariables(GLYPH_FILE, { fontstack, range })
94
+ }
95
+
96
+ /**
97
+ * Get the URI template for the sprites in the style
98
+ */
99
+ export function getSpriteUri(id = 'default') {
100
+ return (
101
+ URI_BASE + replaceVariables(SPRITE_FILE, { id, pixelRatio: '', ext: '' })
102
+ )
103
+ }
104
+
105
+ /**
106
+ * Get the URI template for tiles in the style
107
+ *
108
+ * @param {object} opts
109
+ * @param {string} opts.sourceId
110
+ * @param {import("../writer.js").TileFormat} opts.format
111
+ * @returns
112
+ */
113
+ export function getTileUri({ sourceId, format }) {
114
+ const ext = '.' + format + (format === 'mvt' ? '.gz' : '')
115
+ return (
116
+ URI_BASE + TILE_FILE.replace('{sourceId}', sourceId).replace('{ext}', ext)
117
+ )
118
+ }
119
+
120
+ /**
121
+ * @param {number} pixelRatio
122
+ */
123
+ function getPixelRatioString(pixelRatio) {
124
+ return pixelRatio === 1 ? '' : `@${pixelRatio}x`
125
+ }
126
+
127
+ /**
128
+ * Replaces variables in a string with values provided in an object. Variables
129
+ * in the string are denoted by curly braces, e.g., {variableName}.
130
+ *
131
+ * @param {string} template - The string containing variables wrapped in curly braces.
132
+ * @param {Record<string, string | number>} variables - An object where the keys correspond to variable names and values correspond to the replacement values.
133
+ * @returns {string} The string with the variables replaced by their corresponding values.
134
+ */
135
+ export function replaceVariables(template, variables) {
136
+ return template.replace(/{(.*?)}/g, (match, varName) => {
137
+ return varName in variables ? String(variables[varName]) : match
138
+ })
139
+ }
140
+
141
+ /**
142
+ * Inverse of {@link replaceVariables}. Converts a template string into a RegExp
143
+ * that captures the placeholder values. Only placeholders present in the
144
+ * `placeholders` map become named capture groups; unknown placeholders are
145
+ * treated as literal text and escaped.
146
+ *
147
+ * @param {string} template - The template string with `{name}` placeholders.
148
+ * @param {Record<string, string>} placeholders - Map of placeholder
149
+ * name → regex pattern (e.g. `{ z: '\\d+' }`).
150
+ * @returns {RegExp} A RegExp with named capture groups for each known placeholder.
151
+ */
152
+ export function templateToRegex(template, placeholders) {
153
+ const parts = template.split(/\{(\w+)\}/)
154
+ let pattern = ''
155
+ for (let i = 0; i < parts.length; i++) {
156
+ if (i % 2 === 0) {
157
+ pattern += parts[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
158
+ } else if (parts[i] in placeholders) {
159
+ pattern += `(?<${parts[i]}>${placeholders[parts[i]]})`
160
+ } else {
161
+ pattern += `\\{${parts[i]}\\}`
162
+ }
163
+ }
164
+ return new RegExp(`^${pattern}$`)
165
+ }