styled-map-package-api 5.0.0-pre.2 → 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.
- package/README.md +94 -0
- package/dist/download.d.ts +11 -21
- package/dist/fallbacks.d.ts +32 -0
- package/dist/from-mbtiles.d.ts +1 -3
- package/dist/index.d.ts +11 -24
- package/dist/reader.d.ts +28 -12
- package/dist/server.d.ts +23 -14
- package/dist/style-downloader.d.ts +13 -19
- package/dist/tile-downloader.d.ts +13 -23
- package/dist/types.d.ts +61 -0
- package/dist/utils/errors.d.ts +2 -4
- package/dist/utils/fetch.d.ts +3 -8
- package/dist/utils/file-formats.d.ts +3 -10
- package/dist/utils/geo.d.ts +17 -9
- package/dist/utils/mapbox.d.ts +8 -10
- package/dist/utils/misc.d.ts +3 -5
- package/dist/utils/streams.d.ts +6 -10
- package/dist/utils/style.d.ts +27 -16
- package/dist/utils/templates.d.ts +30 -25
- package/dist/validator.d.ts +66 -0
- package/dist/writer.d.ts +157 -4
- package/lib/download.js +125 -0
- package/lib/fallbacks.js +157 -0
- package/lib/from-mbtiles.js +131 -0
- package/lib/index.js +12 -0
- package/lib/reader.js +360 -0
- package/lib/server.js +222 -0
- package/lib/style-downloader.js +369 -0
- package/lib/tile-downloader.js +189 -0
- package/lib/types.ts +99 -0
- package/lib/utils/errors.js +24 -0
- package/lib/utils/fetch.js +104 -0
- package/lib/utils/file-formats.js +92 -0
- package/lib/utils/geo.js +97 -0
- package/lib/utils/mapbox.js +155 -0
- package/{dist/utils/misc.d.cts → lib/utils/misc.js} +9 -5
- package/lib/utils/streams.js +101 -0
- package/lib/utils/style.js +206 -0
- package/lib/utils/templates.js +165 -0
- package/lib/validator.js +789 -0
- package/lib/writer.js +652 -0
- package/package.json +30 -78
- package/dist/download.cjs +0 -100
- package/dist/download.d.cts +0 -63
- package/dist/download.js +0 -76
- package/dist/from-mbtiles.cjs +0 -108
- package/dist/from-mbtiles.d.cts +0 -14
- package/dist/from-mbtiles.js +0 -84
- package/dist/index.cjs +0 -46
- package/dist/index.d.cts +0 -24
- package/dist/index.js +0 -16
- package/dist/reader.cjs +0 -287
- package/dist/reader.d.cts +0 -67
- package/dist/reader.js +0 -259
- package/dist/server.cjs +0 -73
- package/dist/server.d.cts +0 -45
- package/dist/server.js +0 -49
- package/dist/style-downloader.cjs +0 -314
- package/dist/style-downloader.d.cts +0 -118
- package/dist/style-downloader.js +0 -290
- package/dist/tile-downloader.cjs +0 -156
- package/dist/tile-downloader.d.cts +0 -82
- package/dist/tile-downloader.js +0 -124
- package/dist/types-qfyJk4ot.d.cts +0 -200
- package/dist/types-qfyJk4ot.d.ts +0 -200
- package/dist/utils/errors.cjs +0 -41
- package/dist/utils/errors.d.cts +0 -18
- package/dist/utils/errors.js +0 -16
- package/dist/utils/fetch.cjs +0 -97
- package/dist/utils/fetch.d.cts +0 -50
- package/dist/utils/fetch.js +0 -63
- package/dist/utils/file-formats.cjs +0 -96
- package/dist/utils/file-formats.d.cts +0 -32
- package/dist/utils/file-formats.js +0 -70
- package/dist/utils/geo.cjs +0 -84
- package/dist/utils/geo.d.cts +0 -46
- package/dist/utils/geo.js +0 -56
- package/dist/utils/mapbox.cjs +0 -121
- package/dist/utils/mapbox.d.cts +0 -43
- package/dist/utils/mapbox.js +0 -91
- package/dist/utils/misc.cjs +0 -39
- package/dist/utils/misc.js +0 -13
- package/dist/utils/streams.cjs +0 -99
- package/dist/utils/streams.d.cts +0 -49
- package/dist/utils/streams.js +0 -73
- package/dist/utils/style.cjs +0 -126
- package/dist/utils/style.d.cts +0 -66
- package/dist/utils/style.js +0 -98
- package/dist/utils/templates.cjs +0 -124
- package/dist/utils/templates.d.cts +0 -79
- package/dist/utils/templates.js +0 -85
- package/dist/writer.cjs +0 -539
- package/dist/writer.d.cts +0 -4
- package/dist/writer.js +0 -516
package/lib/download.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { StyleDownloader } from './style-downloader.js'
|
|
2
|
+
import { readableFromAsync } from './utils/streams.js'
|
|
3
|
+
import { Writer } from './writer.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} DownloadProgress
|
|
7
|
+
* @property {import('./tile-downloader.js').TileDownloadStats & { done: boolean }} tiles
|
|
8
|
+
* @property {{ done: boolean }} style
|
|
9
|
+
* @property {{ downloaded: number, done: boolean }} sprites
|
|
10
|
+
* @property {import('./style-downloader.js').GlyphDownloadStats & { done: boolean }} glyphs
|
|
11
|
+
* @property {{ totalBytes: number, done: boolean }} output
|
|
12
|
+
* @property {number} elapsedMs
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Download a map style and its resources for a given bounding box and max zoom
|
|
17
|
+
* level. Returns a readable stream of a "styled map package", a zip file
|
|
18
|
+
* containing all the resources needed to serve the style offline.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} opts
|
|
21
|
+
* @param {Readonly<import("./utils/geo.js").BBox>} opts.bbox Bounding box to download tiles for
|
|
22
|
+
* @param {number} opts.maxzoom Max zoom level to download tiles for
|
|
23
|
+
* @param {string} opts.styleUrl URL of the style to download
|
|
24
|
+
* @param { (progress: DownloadProgress) => void } [opts.onprogress] Optional callback for reporting progress
|
|
25
|
+
* @param {string} [opts.accessToken]
|
|
26
|
+
* @param {boolean} [opts.skipLocalGlyphs] Skip glyph ranges rendered client-side by MapLibre GL via localIdeographFontFamily (CJK, Hangul, Kana, Yi, etc.)
|
|
27
|
+
* @param {boolean} [opts.dedupe] When true, duplicate tiles are stored only once (see {@link Writer})
|
|
28
|
+
* @returns {import('./types.js').DownloadStream} Readable stream of the output styled map file
|
|
29
|
+
*/
|
|
30
|
+
export function download({
|
|
31
|
+
bbox,
|
|
32
|
+
maxzoom,
|
|
33
|
+
styleUrl,
|
|
34
|
+
onprogress,
|
|
35
|
+
accessToken,
|
|
36
|
+
skipLocalGlyphs,
|
|
37
|
+
dedupe,
|
|
38
|
+
}) {
|
|
39
|
+
const downloader = new StyleDownloader(styleUrl, {
|
|
40
|
+
concurrency: 24,
|
|
41
|
+
mapboxAccessToken: accessToken,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
let start = Date.now()
|
|
45
|
+
/** @type {DownloadProgress} */
|
|
46
|
+
let progress = {
|
|
47
|
+
tiles: { downloaded: 0, totalBytes: 0, total: 0, skipped: 0, done: false },
|
|
48
|
+
style: { done: false },
|
|
49
|
+
sprites: { downloaded: 0, done: false },
|
|
50
|
+
glyphs: { downloaded: 0, total: 0, totalBytes: 0, done: false },
|
|
51
|
+
output: { totalBytes: 0, done: false },
|
|
52
|
+
elapsedMs: 0,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @param {Partial<DownloadProgress>} update */
|
|
56
|
+
function handleProgress(update) {
|
|
57
|
+
progress = { ...progress, ...update, elapsedMs: Date.now() - start }
|
|
58
|
+
onprogress?.(progress)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const sizeCounter = new TransformStream({
|
|
62
|
+
transform(chunk, controller) {
|
|
63
|
+
handleProgress({
|
|
64
|
+
output: {
|
|
65
|
+
totalBytes: progress.output.totalBytes + chunk.byteLength,
|
|
66
|
+
done: false,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
controller.enqueue(chunk)
|
|
70
|
+
},
|
|
71
|
+
flush() {
|
|
72
|
+
handleProgress({ output: { ...progress.output, done: true } })
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
;(async () => {
|
|
77
|
+
/** @type {Writer | undefined} */
|
|
78
|
+
let writer
|
|
79
|
+
try {
|
|
80
|
+
const style = await downloader.getStyle()
|
|
81
|
+
writer = new Writer(style, { dedupe: !!dedupe })
|
|
82
|
+
handleProgress({ style: { done: true } })
|
|
83
|
+
// Pipe the output stream through the size counter (fire-and-forget;
|
|
84
|
+
// errors propagate via writer.abort())
|
|
85
|
+
writer.outputStream.pipeTo(sizeCounter.writable).catch(() => {})
|
|
86
|
+
|
|
87
|
+
for await (const spriteInfo of downloader.getSprites()) {
|
|
88
|
+
await writer.addSprite(spriteInfo)
|
|
89
|
+
handleProgress({
|
|
90
|
+
sprites: { downloaded: progress.sprites.downloaded + 1, done: false },
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
handleProgress({ sprites: { ...progress.sprites, done: true } })
|
|
94
|
+
|
|
95
|
+
const tiles = downloader.getTiles({
|
|
96
|
+
bounds: bbox,
|
|
97
|
+
maxzoom,
|
|
98
|
+
onprogress: (tileStats) =>
|
|
99
|
+
handleProgress({ tiles: { ...tileStats, done: false } }),
|
|
100
|
+
})
|
|
101
|
+
await readableFromAsync(tiles).pipeTo(
|
|
102
|
+
writer.createTileWriteStream({ concurrency: 24 }),
|
|
103
|
+
)
|
|
104
|
+
handleProgress({ tiles: { ...progress.tiles, done: true } })
|
|
105
|
+
|
|
106
|
+
const glyphs = downloader.getGlyphs({
|
|
107
|
+
skipLocalGlyphs,
|
|
108
|
+
onprogress: (glyphStats) =>
|
|
109
|
+
handleProgress({ glyphs: { ...glyphStats, done: false } }),
|
|
110
|
+
})
|
|
111
|
+
await readableFromAsync(glyphs).pipeTo(writer.createGlyphWriteStream())
|
|
112
|
+
handleProgress({ glyphs: { ...progress.glyphs, done: true } })
|
|
113
|
+
|
|
114
|
+
writer.finish()
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (writer) {
|
|
117
|
+
writer.abort(/** @type {Error} */ (err))
|
|
118
|
+
} else {
|
|
119
|
+
sizeCounter.writable.abort(/** @type {Error} */ (err))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})()
|
|
123
|
+
|
|
124
|
+
return sizeCounter.readable
|
|
125
|
+
}
|
package/lib/fallbacks.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/** @import { SMPSource } from './types.js' */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal valid 1×1 transparent PNG (67 bytes).
|
|
5
|
+
* @type {Uint8Array}
|
|
6
|
+
*/
|
|
7
|
+
const EMPTY_PNG = /* @__PURE__ */ fromHex(
|
|
8
|
+
'89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489' +
|
|
9
|
+
'0000000a49444154789c626000000002000198e195280000000049454e44ae426082',
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal valid 1×1 transparent WebP (VP8L lossless, 34 bytes).
|
|
14
|
+
* @type {Uint8Array}
|
|
15
|
+
*/
|
|
16
|
+
const EMPTY_WEBP = /* @__PURE__ */ fromHex(
|
|
17
|
+
'524946461a000000574542505650384c0d0000002f00000010071011118888fe0700',
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Empty gzip stream (gzipped empty buffer, 20 bytes). Used for empty MVT
|
|
22
|
+
* tiles and empty glyph PBF ranges.
|
|
23
|
+
* @type {Uint8Array}
|
|
24
|
+
*/
|
|
25
|
+
const EMPTY_GZ = /* @__PURE__ */ fromHex(
|
|
26
|
+
'1f8b080000000000001303000000000000000000',
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Empty GeoJSON FeatureCollection as UTF-8 bytes.
|
|
31
|
+
* @type {Uint8Array}
|
|
32
|
+
*/
|
|
33
|
+
const EMPTY_JSON = /* @__PURE__ */ new TextEncoder().encode(
|
|
34
|
+
'{"type":"FeatureCollection","features":[]}',
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
/** @type {Record<string, { body: Uint8Array, contentType: string, contentEncoding?: string }>} */
|
|
38
|
+
const TILE_FORMATS = {
|
|
39
|
+
mvt: {
|
|
40
|
+
body: EMPTY_GZ,
|
|
41
|
+
contentType: 'application/vnd.mapbox-vector-tile',
|
|
42
|
+
contentEncoding: 'gzip',
|
|
43
|
+
},
|
|
44
|
+
png: {
|
|
45
|
+
body: EMPTY_PNG,
|
|
46
|
+
contentType: 'image/png',
|
|
47
|
+
},
|
|
48
|
+
jpg: {
|
|
49
|
+
// No such thing as "empty" JPEG — serve a transparent PNG instead.
|
|
50
|
+
// MapLibre handles the content-type mismatch gracefully.
|
|
51
|
+
body: EMPTY_PNG,
|
|
52
|
+
contentType: 'image/png',
|
|
53
|
+
},
|
|
54
|
+
webp: {
|
|
55
|
+
body: EMPTY_WEBP,
|
|
56
|
+
contentType: 'image/webp',
|
|
57
|
+
},
|
|
58
|
+
json: {
|
|
59
|
+
body: EMPTY_JSON,
|
|
60
|
+
contentType: 'application/json; charset=utf-8',
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Detect the tile format from a source's tile URL template.
|
|
66
|
+
*
|
|
67
|
+
* @param {SMPSource} source
|
|
68
|
+
* @returns {string | null}
|
|
69
|
+
*/
|
|
70
|
+
function detectTileFormat(source) {
|
|
71
|
+
const tiles = 'tiles' in source ? source.tiles : undefined
|
|
72
|
+
if (!tiles || tiles.length === 0) return null
|
|
73
|
+
const url = tiles[0]
|
|
74
|
+
if (
|
|
75
|
+
url.endsWith('.mvt.gz') ||
|
|
76
|
+
url.endsWith('.mvt') ||
|
|
77
|
+
url.endsWith('.pbf.gz') ||
|
|
78
|
+
url.endsWith('.pbf')
|
|
79
|
+
)
|
|
80
|
+
return 'mvt'
|
|
81
|
+
if (url.endsWith('.png')) return 'png'
|
|
82
|
+
if (url.endsWith('.jpg') || url.endsWith('.jpeg')) return 'jpg'
|
|
83
|
+
if (url.endsWith('.webp')) return 'webp'
|
|
84
|
+
if (url.endsWith('.json') || url.endsWith('.geojson')) return 'json'
|
|
85
|
+
// Default based on source type
|
|
86
|
+
if (source.type === 'vector') return 'mvt'
|
|
87
|
+
if (source.type === 'raster') return 'png'
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fallback tile handler for use with `createServer({ fallbackTile })`.
|
|
93
|
+
* Returns an appropriate empty tile based on the source's tile format:
|
|
94
|
+
* - vector sources → empty gzipped MVT
|
|
95
|
+
* - raster PNG sources → 1×1 transparent PNG
|
|
96
|
+
* - raster WebP sources → 1×1 transparent WebP
|
|
97
|
+
* - raster JPEG sources → 1×1 transparent PNG (no such thing as transparent JPEG)
|
|
98
|
+
*
|
|
99
|
+
* @param {{ x: number, y: number, z: number }} _tileId
|
|
100
|
+
* @param {{ sourceId: string, source: SMPSource }} sourceInfo
|
|
101
|
+
* @returns {Response}
|
|
102
|
+
*/
|
|
103
|
+
export function emptyTileFallback(_tileId, { source }) {
|
|
104
|
+
const format = detectTileFormat(source)
|
|
105
|
+
const tile = format && TILE_FORMATS[format]
|
|
106
|
+
if (!tile) {
|
|
107
|
+
return new Response('Not Found', { status: 404 })
|
|
108
|
+
}
|
|
109
|
+
/** @type {HeadersInit} */
|
|
110
|
+
const headers = {
|
|
111
|
+
'Content-Type': tile.contentType,
|
|
112
|
+
'Content-Length': String(tile.body.byteLength),
|
|
113
|
+
'Cache-Control': 'public, max-age=604800',
|
|
114
|
+
}
|
|
115
|
+
if (tile.contentEncoding) {
|
|
116
|
+
headers['Content-Encoding'] = tile.contentEncoding
|
|
117
|
+
}
|
|
118
|
+
return new Response(/** @type {BodyInit} */ (tile.body), {
|
|
119
|
+
status: 200,
|
|
120
|
+
headers,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Fallback glyph handler for use with `createServer({ fallbackGlyph })`.
|
|
126
|
+
* Returns an empty gzipped PBF (valid protobuf with no glyph entries), which
|
|
127
|
+
* causes MapLibre to render missing characters as blank space instead of
|
|
128
|
+
* erroring on a 404.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} _fontstack
|
|
131
|
+
* @param {string} _range
|
|
132
|
+
* @returns {Response}
|
|
133
|
+
*/
|
|
134
|
+
// eslint-disable-next-line no-unused-vars
|
|
135
|
+
export function emptyGlyphFallback(_fontstack, _range) {
|
|
136
|
+
return new Response(/** @type {BodyInit} */ (EMPTY_GZ), {
|
|
137
|
+
status: 200,
|
|
138
|
+
headers: {
|
|
139
|
+
'Content-Type': 'application/x-protobuf',
|
|
140
|
+
'Content-Encoding': 'gzip',
|
|
141
|
+
'Content-Length': String(EMPTY_GZ.byteLength),
|
|
142
|
+
'Cache-Control': 'public, max-age=604800',
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {string} hex
|
|
149
|
+
* @returns {Uint8Array}
|
|
150
|
+
*/
|
|
151
|
+
function fromHex(hex) {
|
|
152
|
+
const bytes = new Uint8Array(hex.length / 2)
|
|
153
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
154
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16)
|
|
155
|
+
}
|
|
156
|
+
return bytes
|
|
157
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { MBTiles } from 'mbtiles-reader'
|
|
2
|
+
|
|
3
|
+
import { noop } from './utils/misc.js'
|
|
4
|
+
import { readableFromAsync } from './utils/streams.js'
|
|
5
|
+
import { Writer } from './writer.js'
|
|
6
|
+
|
|
7
|
+
const SOURCE_ID = 'mbtiles-source'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Convert a MBTiles file to a styled map package, returned as a web
|
|
11
|
+
* ReadableStream. The async MBTiles.open() happens lazily inside the
|
|
12
|
+
* stream's start(), so this function is synchronous.
|
|
13
|
+
*
|
|
14
|
+
* Requires Node >= 20 (uses better-sqlite3 which dropped Node 18 support).
|
|
15
|
+
*
|
|
16
|
+
* @param {string | ArrayBuffer | Uint8Array} source MBTiles source — file path
|
|
17
|
+
* (Node), OPFS path (browser Worker), or in-memory buffer.
|
|
18
|
+
* @returns {ReadableStream<Uint8Array>}
|
|
19
|
+
*/
|
|
20
|
+
export function fromMBTiles(source) {
|
|
21
|
+
/** @type {ReadableStreamDefaultReader<Uint8Array> | undefined} */
|
|
22
|
+
let outputReader
|
|
23
|
+
/** @type {Promise<void> | undefined} */
|
|
24
|
+
let conversionDone
|
|
25
|
+
const pipeAbort = new AbortController()
|
|
26
|
+
|
|
27
|
+
return new ReadableStream({
|
|
28
|
+
async start() {
|
|
29
|
+
const reader = await MBTiles.open(source)
|
|
30
|
+
if (reader.metadata.format === 'pbf') {
|
|
31
|
+
throw new Error('Vector MBTiles are not yet supported')
|
|
32
|
+
}
|
|
33
|
+
const {
|
|
34
|
+
name,
|
|
35
|
+
minzoom,
|
|
36
|
+
maxzoom,
|
|
37
|
+
scheme,
|
|
38
|
+
attribution,
|
|
39
|
+
description,
|
|
40
|
+
version: tilesetVersion,
|
|
41
|
+
} = reader.metadata
|
|
42
|
+
/** @type {import('@maplibre/maplibre-gl-style-spec').StyleSpecification} */
|
|
43
|
+
const style = {
|
|
44
|
+
version: 8,
|
|
45
|
+
name,
|
|
46
|
+
sources: {
|
|
47
|
+
[SOURCE_ID]: {
|
|
48
|
+
type: 'raster',
|
|
49
|
+
tileSize: 256,
|
|
50
|
+
minzoom,
|
|
51
|
+
maxzoom,
|
|
52
|
+
scheme,
|
|
53
|
+
attribution,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
layers: [
|
|
57
|
+
{
|
|
58
|
+
id: 'background',
|
|
59
|
+
type: 'background',
|
|
60
|
+
paint: {
|
|
61
|
+
'background-color': 'white',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'raster',
|
|
66
|
+
type: 'raster',
|
|
67
|
+
source: SOURCE_ID,
|
|
68
|
+
paint: {
|
|
69
|
+
'raster-opacity': 1,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (description || tilesetVersion) {
|
|
76
|
+
style.metadata = {
|
|
77
|
+
'mbtiles:description': description,
|
|
78
|
+
'mbtiles:version': tilesetVersion,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const writer = new Writer(style)
|
|
83
|
+
outputReader = writer.outputStream.getReader()
|
|
84
|
+
|
|
85
|
+
conversionDone = (async () => {
|
|
86
|
+
try {
|
|
87
|
+
await readableFromAsync(mbtilesToTileArgs(reader)).pipeTo(
|
|
88
|
+
writer.createTileWriteStream(),
|
|
89
|
+
{ signal: pipeAbort.signal },
|
|
90
|
+
)
|
|
91
|
+
writer.finish()
|
|
92
|
+
} catch (err) {
|
|
93
|
+
try {
|
|
94
|
+
writer.abort(err instanceof Error ? err : new Error(String(err)))
|
|
95
|
+
} catch {
|
|
96
|
+
// Output stream may already be cancelled/errored
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})()
|
|
100
|
+
},
|
|
101
|
+
async pull(controller) {
|
|
102
|
+
const { done, value } =
|
|
103
|
+
await /** @type {ReadableStreamDefaultReader<Uint8Array>} */ (
|
|
104
|
+
outputReader
|
|
105
|
+
).read()
|
|
106
|
+
if (done) {
|
|
107
|
+
controller.close()
|
|
108
|
+
} else {
|
|
109
|
+
controller.enqueue(value)
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
async cancel(reason) {
|
|
113
|
+
pipeAbort.abort(reason)
|
|
114
|
+
await conversionDone
|
|
115
|
+
await /** @type {ReadableStreamDefaultReader<Uint8Array>} */ (
|
|
116
|
+
outputReader
|
|
117
|
+
)
|
|
118
|
+
.cancel(reason)
|
|
119
|
+
.catch(noop)
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {MBTiles} mbtiles
|
|
126
|
+
*/
|
|
127
|
+
async function* mbtilesToTileArgs(mbtiles) {
|
|
128
|
+
for (const { z, x, y, data, format } of mbtiles) {
|
|
129
|
+
yield [data, { z, x, y, format, sourceId: SOURCE_ID }]
|
|
130
|
+
}
|
|
131
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @typedef {import('./types.js').SMPSource} SMPSource */
|
|
2
|
+
/** @typedef {import('./types.js').SMPStyle} SMPStyle */
|
|
3
|
+
|
|
4
|
+
export { Reader } from './reader.js'
|
|
5
|
+
export { Writer } from './writer.js'
|
|
6
|
+
export { createServer } from './server.js'
|
|
7
|
+
export { StyleDownloader } from './style-downloader.js'
|
|
8
|
+
export { downloadTiles } from './tile-downloader.js'
|
|
9
|
+
export { download } from './download.js'
|
|
10
|
+
export { fromMBTiles } from './from-mbtiles.js'
|
|
11
|
+
export { emptyTileFallback, emptyGlyphFallback } from './fallbacks.js'
|
|
12
|
+
export { validate } from './validator.js'
|