styled-map-package 2.2.1 → 4.0.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.
- package/bin/smp-download.js +3 -3
- package/bin/smp-mbtiles.js +1 -1
- package/bin/smp-view.js +15 -4
- package/dist/download.cjs +101 -0
- package/dist/download.d.cts +65 -0
- package/dist/{lib/download.d.ts → download.d.ts} +22 -6
- package/dist/download.js +77 -0
- package/dist/from-mbtiles.cjs +91 -0
- package/dist/from-mbtiles.d.cts +17 -0
- package/dist/from-mbtiles.d.ts +17 -0
- package/dist/from-mbtiles.js +57 -0
- package/dist/index.cjs +49 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +18 -0
- package/dist/reader-watch.cjs +135 -0
- package/dist/reader-watch.d.cts +24 -0
- package/dist/reader-watch.d.ts +24 -0
- package/dist/reader-watch.js +101 -0
- package/dist/reader.cjs +167 -0
- package/dist/reader.d.cts +62 -0
- package/dist/{lib/reader.d.ts → reader.d.ts} +16 -5
- package/dist/reader.js +138 -0
- package/dist/reporters.cjs +122 -0
- package/dist/reporters.d.cts +10 -0
- package/dist/{lib/reporters.d.ts → reporters.d.ts} +5 -2
- package/dist/reporters.js +88 -0
- package/dist/server.cjs +79 -0
- package/dist/server.d.cts +48 -0
- package/dist/server.d.ts +48 -0
- package/dist/server.js +55 -0
- package/dist/style-downloader.cjs +312 -0
- package/dist/style-downloader.d.cts +120 -0
- package/dist/{lib/style-downloader.d.ts → style-downloader.d.ts} +23 -13
- package/dist/style-downloader.js +288 -0
- package/dist/tile-downloader.cjs +158 -0
- package/dist/tile-downloader.d.cts +84 -0
- package/dist/{lib/tile-downloader.d.ts → tile-downloader.d.ts} +22 -10
- package/dist/tile-downloader.js +126 -0
- package/dist/{lib/writer.d.ts → types-B4Xn1F9K.d.cts} +74 -14
- package/dist/types-B4Xn1F9K.d.ts +189 -0
- package/dist/utils/errors.cjs +41 -0
- package/dist/utils/errors.d.cts +18 -0
- package/dist/{lib/utils → utils}/errors.d.ts +4 -2
- package/dist/utils/errors.js +16 -0
- package/dist/utils/fetch.cjs +96 -0
- package/dist/utils/fetch.d.cts +51 -0
- package/dist/{lib/utils → utils}/fetch.d.ts +10 -4
- package/dist/utils/fetch.js +62 -0
- package/dist/utils/file-formats.cjs +98 -0
- package/dist/utils/file-formats.d.cts +35 -0
- package/dist/{lib/utils → utils}/file-formats.d.ts +13 -3
- package/dist/utils/file-formats.js +62 -0
- package/dist/utils/geo.cjs +84 -0
- package/dist/utils/geo.d.cts +46 -0
- package/dist/{lib/utils → utils}/geo.d.ts +8 -6
- package/dist/utils/geo.js +56 -0
- package/dist/utils/mapbox.cjs +121 -0
- package/dist/utils/mapbox.d.cts +43 -0
- package/dist/utils/mapbox.d.ts +43 -0
- package/dist/utils/mapbox.js +91 -0
- package/dist/utils/misc.cjs +39 -0
- package/{lib/utils/misc.js → dist/utils/misc.d.cts} +5 -9
- package/dist/{lib/utils → utils}/misc.d.ts +5 -3
- package/dist/utils/misc.js +13 -0
- package/dist/utils/streams.cjs +130 -0
- package/dist/utils/streams.d.cts +73 -0
- package/dist/{lib/utils → utils}/streams.d.ts +14 -10
- package/dist/utils/streams.js +103 -0
- package/dist/utils/style.cjs +126 -0
- package/dist/utils/style.d.cts +69 -0
- package/dist/{lib/utils → utils}/style.d.ts +19 -9
- package/dist/utils/style.js +98 -0
- package/dist/utils/templates.cjs +114 -0
- package/dist/utils/templates.d.cts +78 -0
- package/dist/{lib/utils → utils}/templates.d.ts +24 -14
- package/dist/utils/templates.js +79 -0
- package/dist/writer.cjs +401 -0
- package/dist/writer.d.cts +7 -0
- package/dist/writer.d.ts +7 -0
- package/dist/writer.js +374 -0
- package/package.json +107 -41
- package/dist/lib/from-mbtiles.d.ts +0 -13
- package/dist/lib/index.d.ts +0 -10
- package/dist/lib/reader-watch.d.ts +0 -13
- package/dist/lib/server.d.ts +0 -15
- package/dist/lib/types.d.ts +0 -64
- package/dist/lib/utils/mapbox.d.ts +0 -41
- package/lib/download.js +0 -114
- package/lib/from-mbtiles.js +0 -83
- package/lib/index.js +0 -11
- package/lib/reader-watch.js +0 -133
- package/lib/reader.js +0 -165
- package/lib/reporters.js +0 -92
- package/lib/server.js +0 -81
- package/lib/style-downloader.js +0 -363
- package/lib/tile-downloader.js +0 -188
- package/lib/types.ts +0 -104
- package/lib/utils/errors.js +0 -24
- package/lib/utils/fetch.js +0 -100
- package/lib/utils/file-formats.js +0 -85
- package/lib/utils/geo.js +0 -87
- package/lib/utils/mapbox.js +0 -155
- package/lib/utils/streams.js +0 -165
- package/lib/utils/style.js +0 -174
- package/lib/utils/templates.js +0 -136
- package/lib/writer.js +0 -478
package/lib/reporters.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import chalk, { chalkStderr } from 'chalk'
|
|
2
|
-
import logSymbols from 'log-symbols'
|
|
3
|
-
import ora from 'ora'
|
|
4
|
-
import prettyBytes from 'pretty-bytes'
|
|
5
|
-
import prettyMilliseconds from 'pretty-ms'
|
|
6
|
-
import { Writable } from 'readable-stream'
|
|
7
|
-
|
|
8
|
-
chalk.level = chalkStderr.level
|
|
9
|
-
|
|
10
|
-
const TASKS = /** @type {const} */ ([
|
|
11
|
-
'style',
|
|
12
|
-
'sprites',
|
|
13
|
-
'tiles',
|
|
14
|
-
'glyphs',
|
|
15
|
-
'output',
|
|
16
|
-
])
|
|
17
|
-
|
|
18
|
-
const TASK_LABEL = /** @type {const} */ ({
|
|
19
|
-
style: 'Downloading Map Style',
|
|
20
|
-
sprites: 'Downloading Sprites',
|
|
21
|
-
tiles: 'Downloading Tiles',
|
|
22
|
-
glyphs: 'Downloading Glyphs',
|
|
23
|
-
output: 'Writing Styled Map Package',
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const TASK_SUFFIX =
|
|
27
|
-
/** @type {{ [K in (typeof TASKS)[number]]: (progress: import('./download.js').DownloadProgress[K]) => string }} */ ({
|
|
28
|
-
style: () => '',
|
|
29
|
-
sprites: ({ downloaded }) => `${downloaded}`,
|
|
30
|
-
tiles: ({ total, skipped, totalBytes, downloaded }) => {
|
|
31
|
-
const formattedTotal = total.toLocaleString()
|
|
32
|
-
const formattedCompleted = (downloaded + skipped)
|
|
33
|
-
.toLocaleString()
|
|
34
|
-
.padStart(formattedTotal.length)
|
|
35
|
-
return `${formattedCompleted}/${formattedTotal} (${prettyBytes(totalBytes)})`
|
|
36
|
-
},
|
|
37
|
-
glyphs: ({ total, downloaded, totalBytes }) =>
|
|
38
|
-
`${downloaded}/${total} (${prettyBytes(totalBytes)})`,
|
|
39
|
-
output: ({ totalBytes }) => `${prettyBytes(totalBytes)}`,
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* A writable stream to reporting download progress to a TTY terminal. Write
|
|
44
|
-
* progress messages to this stream for a pretty-printed progress task-list in
|
|
45
|
-
* the terminal.
|
|
46
|
-
*/
|
|
47
|
-
export function ttyReporter() {
|
|
48
|
-
/** @type {import('./download.js').DownloadProgress | undefined} */
|
|
49
|
-
let stats
|
|
50
|
-
let current = 0
|
|
51
|
-
/** @type {import('ora').Ora} */
|
|
52
|
-
let spinner
|
|
53
|
-
return new Writable({
|
|
54
|
-
objectMode: true,
|
|
55
|
-
// @ts-ignore - missing type def
|
|
56
|
-
construct(cb) {
|
|
57
|
-
process.stderr.write('\n')
|
|
58
|
-
spinner = ora(TASK_LABEL[TASKS[current]]).start()
|
|
59
|
-
cb()
|
|
60
|
-
},
|
|
61
|
-
/** @param {ArrayLike<{ chunk: import('./download.js').DownloadProgress, encoding: string }>} chunks */
|
|
62
|
-
writev(chunks, cb) {
|
|
63
|
-
stats = chunks[chunks.length - 1].chunk
|
|
64
|
-
while (current < TASKS.length && stats[TASKS[current]].done) {
|
|
65
|
-
spinner.suffixText = chalk.dim(
|
|
66
|
-
TASK_SUFFIX[TASKS[current]](
|
|
67
|
-
// @ts-ignore - too complicated for TS
|
|
68
|
-
stats[TASKS[current]],
|
|
69
|
-
),
|
|
70
|
-
)
|
|
71
|
-
spinner.succeed()
|
|
72
|
-
if (++current < TASKS.length) {
|
|
73
|
-
spinner = ora(TASK_LABEL[TASKS[current]]).start()
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (current < TASKS.length) {
|
|
77
|
-
spinner.suffixText = chalk.dim(
|
|
78
|
-
TASK_SUFFIX[TASKS[current]](
|
|
79
|
-
// @ts-ignore - too complicated for TS
|
|
80
|
-
stats[TASKS[current]],
|
|
81
|
-
),
|
|
82
|
-
)
|
|
83
|
-
} else {
|
|
84
|
-
process.stderr.write(
|
|
85
|
-
`${chalk.green(logSymbols.success)} Completed in ${prettyMilliseconds(stats.elapsedMs)}\n`,
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
cb()
|
|
90
|
-
},
|
|
91
|
-
})
|
|
92
|
-
}
|
package/lib/server.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import createError from 'http-errors'
|
|
2
|
-
|
|
3
|
-
import Reader from './reader.js'
|
|
4
|
-
import { isFileNotThereError } from './utils/errors.js'
|
|
5
|
-
import { noop } from './utils/misc.js'
|
|
6
|
-
|
|
7
|
-
/** @import { FastifyPluginCallback, FastifyReply } from 'fastify' */
|
|
8
|
-
/** @import { Resource } from './reader.js' */
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {object} PluginOptionsFilepath
|
|
12
|
-
* @property {string} filepath Path to styled map package (`.smp`) file
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {object} PluginOptionsReader
|
|
16
|
-
* @property {Pick<Reader, keyof Reader>} reader SMP Reader interface (also supports ReaderWatch)
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* @typedef {PluginOptionsFilepath | PluginOptionsReader} PluginOptions
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {FastifyReply} reply
|
|
24
|
-
* @param {Resource} resource
|
|
25
|
-
* @returns {FastifyReply} reply
|
|
26
|
-
*/
|
|
27
|
-
function sendResource(reply, resource) {
|
|
28
|
-
reply
|
|
29
|
-
.type(resource.contentType)
|
|
30
|
-
.header('content-length', resource.contentLength)
|
|
31
|
-
if (resource.contentEncoding) {
|
|
32
|
-
reply.header('content-encoding', resource.contentEncoding)
|
|
33
|
-
}
|
|
34
|
-
return reply.send(resource.stream)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Fastify plugin for serving a styled map package.
|
|
39
|
-
*
|
|
40
|
-
* If you provide a `Reader` (or `ReaderWatch`) instance via the `reader` opt,
|
|
41
|
-
* you must manually close the instance yourself.
|
|
42
|
-
*
|
|
43
|
-
* @type {FastifyPluginCallback<PluginOptions>}
|
|
44
|
-
*/
|
|
45
|
-
export default function (fastify, opts, done) {
|
|
46
|
-
const reader = 'reader' in opts ? opts.reader : new Reader(opts.filepath)
|
|
47
|
-
|
|
48
|
-
// Only close the reader if it was created by this plugin
|
|
49
|
-
if (!('reader' in opts)) {
|
|
50
|
-
fastify.addHook('onClose', () => reader.close().catch(noop))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
fastify.get('/style.json', async () => {
|
|
54
|
-
try {
|
|
55
|
-
const baseUrl = new URL(fastify.prefix, fastify.listeningOrigin)
|
|
56
|
-
const style = await reader.getStyle(baseUrl.href)
|
|
57
|
-
return style
|
|
58
|
-
} catch (error) {
|
|
59
|
-
if (isFileNotThereError(error)) {
|
|
60
|
-
throw createError(404, error.message)
|
|
61
|
-
}
|
|
62
|
-
throw error
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
fastify.get('*', async (request, reply) => {
|
|
67
|
-
// @ts-expect-error - not worth the hassle of type casting this
|
|
68
|
-
const path = request.params['*']
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const resource = await reader.getResource(path)
|
|
72
|
-
return sendResource(reply, resource)
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (isFileNotThereError(error)) {
|
|
75
|
-
throw createError(404, error.message)
|
|
76
|
-
}
|
|
77
|
-
throw error
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
done()
|
|
81
|
-
}
|
package/lib/style-downloader.js
DELETED
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
import { check as checkGeoJson } from '@placemarkio/check-geojson'
|
|
2
|
-
import { includeKeys } from 'filter-obj'
|
|
3
|
-
import ky from 'ky'
|
|
4
|
-
import Queue from 'yocto-queue'
|
|
5
|
-
import zlib from 'zlib'
|
|
6
|
-
|
|
7
|
-
import { downloadTiles } from './tile-downloader.js'
|
|
8
|
-
import { FetchQueue } from './utils/fetch.js'
|
|
9
|
-
import {
|
|
10
|
-
normalizeGlyphsURL,
|
|
11
|
-
normalizeSourceURL,
|
|
12
|
-
normalizeSpriteURL,
|
|
13
|
-
normalizeStyleURL,
|
|
14
|
-
} from './utils/mapbox.js'
|
|
15
|
-
import { clone, noop } from './utils/misc.js'
|
|
16
|
-
import {
|
|
17
|
-
assertTileJSON,
|
|
18
|
-
isInlinedSource,
|
|
19
|
-
mapFontStacks,
|
|
20
|
-
validateStyle,
|
|
21
|
-
} from './utils/style.js'
|
|
22
|
-
|
|
23
|
-
/** @import { SourceSpecification, StyleSpecification } from '@maplibre/maplibre-gl-style-spec' */
|
|
24
|
-
/** @import { TileInfo, GlyphInfo, GlyphRange } from './writer.js' */
|
|
25
|
-
/** @import { TileDownloadStats } from './tile-downloader.js' */
|
|
26
|
-
/** @import { StyleInlinedSources, InlinedSource } from './types.js'*/
|
|
27
|
-
|
|
28
|
-
/** @typedef { import('ky').ResponsePromise & { body: ReadableStream<Uint8Array> } } ResponsePromise */
|
|
29
|
-
/** @import { DownloadResponse } from './utils/fetch.js' */
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @typedef {object} GlyphDownloadStats
|
|
33
|
-
* @property {number} total
|
|
34
|
-
* @property {number} downloaded
|
|
35
|
-
* @property {number} totalBytes
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Download a style and its resources for offline use. Please check the terms of
|
|
40
|
-
* service of the map provider you are using before downloading any resources.
|
|
41
|
-
*/
|
|
42
|
-
export default class StyleDownloader {
|
|
43
|
-
/** @type {null | string} */
|
|
44
|
-
#styleURL = null
|
|
45
|
-
/** @type {null | StyleSpecification} */
|
|
46
|
-
#inputStyle = null
|
|
47
|
-
/** @type {FetchQueue} */
|
|
48
|
-
#fetchQueue
|
|
49
|
-
#mapboxAccessToken
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @param {string | StyleSpecification} style A url to a style JSON file or a style object
|
|
53
|
-
* @param {object} [opts]
|
|
54
|
-
* @param {number} [opts.concurrency=8]
|
|
55
|
-
* @param {string} [opts.mapboxAccessToken] Downloading a style from Mapbox requires an access token
|
|
56
|
-
*/
|
|
57
|
-
constructor(style, { concurrency = 8, mapboxAccessToken } = {}) {
|
|
58
|
-
if (typeof style === 'string') {
|
|
59
|
-
const { searchParams } = new URL(style)
|
|
60
|
-
this.#mapboxAccessToken =
|
|
61
|
-
searchParams.get('access_token') || mapboxAccessToken
|
|
62
|
-
this.#styleURL = normalizeStyleURL(style, this.#mapboxAccessToken)
|
|
63
|
-
} else if (validateStyle(style)) {
|
|
64
|
-
this.#inputStyle = clone(style)
|
|
65
|
-
} else {
|
|
66
|
-
throw new AggregateError(validateStyle.errors, 'Invalid style')
|
|
67
|
-
}
|
|
68
|
-
this.#fetchQueue = new FetchQueue(concurrency)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Number of active downloads.
|
|
73
|
-
*/
|
|
74
|
-
get active() {
|
|
75
|
-
return this.#fetchQueue.activeCount
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Download the style JSON for this style and inline the sources
|
|
80
|
-
*
|
|
81
|
-
* @returns {Promise<StyleInlinedSources>}
|
|
82
|
-
*/
|
|
83
|
-
async getStyle() {
|
|
84
|
-
if (!this.#inputStyle && this.#styleURL) {
|
|
85
|
-
const downloadedStyle = await ky(this.#styleURL).json()
|
|
86
|
-
if (!validateStyle(downloadedStyle)) {
|
|
87
|
-
throw new AggregateError(
|
|
88
|
-
validateStyle.errors,
|
|
89
|
-
'Invalid style: ' + this.#styleURL,
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
this.#inputStyle = downloadedStyle
|
|
93
|
-
} else if (!this.#inputStyle) {
|
|
94
|
-
throw new Error('Unexpected state: no style or style URL provided')
|
|
95
|
-
}
|
|
96
|
-
/** @type {{ [_:string]: InlinedSource }} */
|
|
97
|
-
const inlinedSources = {}
|
|
98
|
-
for (const [sourceId, source] of Object.entries(this.#inputStyle.sources)) {
|
|
99
|
-
inlinedSources[sourceId] = await this.#getInlinedSource(source)
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
...this.#inputStyle,
|
|
103
|
-
sources: inlinedSources,
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @param {SourceSpecification} source
|
|
109
|
-
* @returns {Promise<InlinedSource>}
|
|
110
|
-
*/
|
|
111
|
-
async #getInlinedSource(source) {
|
|
112
|
-
if (isInlinedSource(source)) {
|
|
113
|
-
return source
|
|
114
|
-
}
|
|
115
|
-
if (
|
|
116
|
-
source.type === 'raster' ||
|
|
117
|
-
source.type === 'vector' ||
|
|
118
|
-
source.type === 'raster-dem'
|
|
119
|
-
) {
|
|
120
|
-
if (!source.url) {
|
|
121
|
-
throw new Error('Source is missing both url and tiles properties')
|
|
122
|
-
}
|
|
123
|
-
const sourceUrl = normalizeSourceURL(source.url, this.#mapboxAccessToken)
|
|
124
|
-
const tilejson = await ky(sourceUrl).json()
|
|
125
|
-
assertTileJSON(tilejson)
|
|
126
|
-
return {
|
|
127
|
-
...source,
|
|
128
|
-
...includeKeys(tilejson, [
|
|
129
|
-
'bounds',
|
|
130
|
-
'maxzoom',
|
|
131
|
-
'minzoom',
|
|
132
|
-
'tiles',
|
|
133
|
-
'description',
|
|
134
|
-
'attribution',
|
|
135
|
-
'vector_layers',
|
|
136
|
-
]),
|
|
137
|
-
}
|
|
138
|
-
} else if (source.type === 'geojson') {
|
|
139
|
-
if (typeof source.data !== 'string') {
|
|
140
|
-
// Shouldn't get here because of the `isInlineSource()` check above, but
|
|
141
|
-
// Typescript can't fiture that out.
|
|
142
|
-
throw new Error('Unexpected data property for GeoJson source')
|
|
143
|
-
}
|
|
144
|
-
const geojsonUrl = normalizeSourceURL(
|
|
145
|
-
source.data,
|
|
146
|
-
this.#mapboxAccessToken,
|
|
147
|
-
)
|
|
148
|
-
const data = checkGeoJson(await ky(geojsonUrl).text())
|
|
149
|
-
return {
|
|
150
|
-
...source,
|
|
151
|
-
data,
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return source
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Download the sprite PNGs and JSON files for this style. Returns an async
|
|
159
|
-
* generator of json and png readable streams, and the sprite id and pixel
|
|
160
|
-
* ratio. Downloads pixel ratios `1` and `2`.
|
|
161
|
-
*
|
|
162
|
-
* @returns {AsyncGenerator<{ json: import('stream').Readable, png: import('stream').Readable, id: string, pixelRatio: number }>}
|
|
163
|
-
*/
|
|
164
|
-
async *getSprites() {
|
|
165
|
-
const style = await this.getStyle()
|
|
166
|
-
if (!style.sprite) return
|
|
167
|
-
const accessToken = this.#mapboxAccessToken
|
|
168
|
-
const spriteDefs = Array.isArray(style.sprite)
|
|
169
|
-
? style.sprite
|
|
170
|
-
: [{ id: 'default', url: style.sprite }]
|
|
171
|
-
for (const { id, url } of spriteDefs) {
|
|
172
|
-
for (const pixelRatio of [1, 2]) {
|
|
173
|
-
const format = pixelRatio === 1 ? '' : '@2x'
|
|
174
|
-
const jsonUrl = normalizeSpriteURL(url, format, '.json', accessToken)
|
|
175
|
-
const pngUrl = normalizeSpriteURL(url, format, '.png', accessToken)
|
|
176
|
-
const [{ body: json }, { body: png }] = await Promise.all([
|
|
177
|
-
this.#fetchQueue.fetch(jsonUrl),
|
|
178
|
-
this.#fetchQueue.fetch(pngUrl),
|
|
179
|
-
])
|
|
180
|
-
yield { json, png, id, pixelRatio }
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Download all the glyphs for the fonts used in this style. When font stacks
|
|
187
|
-
* are used in the style.json (e.g. lists of prefered fonts like with CSS),
|
|
188
|
-
* then the first font in the stack is downloaded. Defaults to downloading all
|
|
189
|
-
* UTF character ranges, which may be overkill for some styles. TODO: add more
|
|
190
|
-
* options here.
|
|
191
|
-
*
|
|
192
|
-
* Returns an async generator of readable streams of glyph data and glyph info
|
|
193
|
-
* objects.
|
|
194
|
-
*
|
|
195
|
-
* @param {object} opts
|
|
196
|
-
* @param {(progress: GlyphDownloadStats) => void} [opts.onprogress]
|
|
197
|
-
* @returns {AsyncGenerator<[import('stream').Readable, GlyphInfo]>}
|
|
198
|
-
*/
|
|
199
|
-
async *getGlyphs({ onprogress = noop } = {}) {
|
|
200
|
-
const style = await this.getStyle()
|
|
201
|
-
if (!style.glyphs) return
|
|
202
|
-
|
|
203
|
-
let completed = 0
|
|
204
|
-
/** @type {GlyphDownloadStats} */
|
|
205
|
-
let stats = {
|
|
206
|
-
total: 0,
|
|
207
|
-
downloaded: 0,
|
|
208
|
-
totalBytes: 0,
|
|
209
|
-
}
|
|
210
|
-
/** @type {import('./utils/streams.js').ProgressCallback} */
|
|
211
|
-
function onDownloadProgress({ chunkBytes }) {
|
|
212
|
-
stats.totalBytes += chunkBytes
|
|
213
|
-
onprogress(stats)
|
|
214
|
-
}
|
|
215
|
-
function onDownloadComplete() {
|
|
216
|
-
stats.downloaded = ++completed
|
|
217
|
-
onprogress(stats)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/** @type {Queue<[Promise<void | DownloadResponse>, GlyphInfo]>} */
|
|
221
|
-
const queue = new Queue()
|
|
222
|
-
/** @type {Map<string, string>} */
|
|
223
|
-
const fontStacks = new Map()
|
|
224
|
-
mapFontStacks(style.layers, (fontStack) => {
|
|
225
|
-
// Assume that the font we get back from the API is the first font in the
|
|
226
|
-
// font stack. TODO: When we know the API, we can check this font is
|
|
227
|
-
// actually available.
|
|
228
|
-
fontStacks.set(fontStack[0], fontStack.join(','))
|
|
229
|
-
return []
|
|
230
|
-
})
|
|
231
|
-
const glyphUrl = normalizeGlyphsURL(style.glyphs, this.#mapboxAccessToken)
|
|
232
|
-
|
|
233
|
-
for (const [font, fontStack] of fontStacks.entries()) {
|
|
234
|
-
for (let i = 0; i < Math.pow(2, 16); i += 256) {
|
|
235
|
-
/** @type {GlyphRange} */
|
|
236
|
-
const range = `${i}-${i + 255}`
|
|
237
|
-
const url = glyphUrl
|
|
238
|
-
.replace('{fontstack}', fontStack)
|
|
239
|
-
.replace('{range}', range)
|
|
240
|
-
const result = this.#fetchQueue
|
|
241
|
-
.fetch(url, { onprogress: onDownloadProgress })
|
|
242
|
-
// TODO: Handle errors downloading glyphs
|
|
243
|
-
.catch(noop)
|
|
244
|
-
queue.enqueue([result, { font, range }])
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
stats.total = queue.size
|
|
249
|
-
if (onprogress) onprogress(stats)
|
|
250
|
-
|
|
251
|
-
for (const [result, glyphInfo] of queue) {
|
|
252
|
-
// TODO: Handle errors downloading glyphs
|
|
253
|
-
const downloadResponse = await result.catch(noop)
|
|
254
|
-
if (!downloadResponse) continue
|
|
255
|
-
const { body } = downloadResponse
|
|
256
|
-
// Glyphs are always gzipped. Unfortunately we can't stop fetch from ungzipping, so we need to re-gzip it.
|
|
257
|
-
const gzipper = zlib.createGzip()
|
|
258
|
-
// Cleanup on error, assumes byteCounter won't error.
|
|
259
|
-
body.on('error', (err) => gzipper.destroy(err))
|
|
260
|
-
body.on('end', onDownloadComplete)
|
|
261
|
-
yield [body.pipe(gzipper), glyphInfo]
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Get all the tiles for this style within the given bounds and zoom range.
|
|
267
|
-
* Returns an async generator of readable streams of tile data and tile info
|
|
268
|
-
* objects.
|
|
269
|
-
*
|
|
270
|
-
* The returned iterator also has a `skipped` property which is an
|
|
271
|
-
* array of tiles which could not be downloaded, and a `stats` property which
|
|
272
|
-
* is an object with the total number of tiles, downloaded tiles, and total
|
|
273
|
-
* bytes downloaded.
|
|
274
|
-
*
|
|
275
|
-
* @param {object} opts
|
|
276
|
-
* @param {import('./utils/geo.js').BBox} opts.bounds
|
|
277
|
-
* @param {number} opts.maxzoom
|
|
278
|
-
* @param {(progress: TileDownloadStats) => void} [opts.onprogress]
|
|
279
|
-
* @param {boolean} [opts.trackErrors=false] Include errors in the returned array of skipped tiles - this has memory overhead so should only be used for debugging.
|
|
280
|
-
* @returns {AsyncGenerator<[import('stream').Readable, TileInfo]> & { readonly skipped: Array<TileInfo & { error?: Error }>, readonly stats: TileDownloadStats }}
|
|
281
|
-
*/
|
|
282
|
-
getTiles({ bounds, maxzoom, onprogress = noop, trackErrors = false }) {
|
|
283
|
-
const _this = this
|
|
284
|
-
/** @type {Array<TileInfo & { error?: Error }>} */
|
|
285
|
-
const skipped = []
|
|
286
|
-
/** @type {TileDownloadStats} */
|
|
287
|
-
let stats = {
|
|
288
|
-
total: 0,
|
|
289
|
-
downloaded: 0,
|
|
290
|
-
skipped: 0,
|
|
291
|
-
totalBytes: 0,
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/** @type {ReturnType<StyleDownloader['getTiles']>} */
|
|
295
|
-
const tiles = (async function* () {
|
|
296
|
-
const inlinedStyle = await _this.getStyle()
|
|
297
|
-
for await (const [sourceId, source] of Object.entries(
|
|
298
|
-
inlinedStyle.sources,
|
|
299
|
-
)) {
|
|
300
|
-
if (source.type !== 'raster' && source.type !== 'vector') {
|
|
301
|
-
continue
|
|
302
|
-
}
|
|
303
|
-
// Baseline stats for this source, used in the `onprogress` closure
|
|
304
|
-
// below. Sorry for the hard-to-follow code! `onprogress` can be called
|
|
305
|
-
// after we are already reading the next source, hence the need for a
|
|
306
|
-
// closure.
|
|
307
|
-
const statsBaseline = { ...stats }
|
|
308
|
-
const sourceTiles = downloadTiles({
|
|
309
|
-
tileUrls: source.tiles,
|
|
310
|
-
bounds,
|
|
311
|
-
maxzoom: Math.min(maxzoom, source.maxzoom || maxzoom),
|
|
312
|
-
minzoom: source.minzoom,
|
|
313
|
-
sourceBounds: source.bounds,
|
|
314
|
-
boundsBuffer: true,
|
|
315
|
-
scheme: source.scheme,
|
|
316
|
-
fetchQueue: _this.#fetchQueue,
|
|
317
|
-
onprogress: (sourceStats) => {
|
|
318
|
-
stats = addStats(statsBaseline, sourceStats)
|
|
319
|
-
onprogress(stats)
|
|
320
|
-
},
|
|
321
|
-
trackErrors,
|
|
322
|
-
})
|
|
323
|
-
for await (const [tileDataStream, tileInfo] of sourceTiles) {
|
|
324
|
-
yield [tileDataStream, { ...tileInfo, sourceId }]
|
|
325
|
-
}
|
|
326
|
-
Array.prototype.push.apply(
|
|
327
|
-
skipped,
|
|
328
|
-
sourceTiles.skipped.map((tile) => ({ ...tile, sourceId })),
|
|
329
|
-
)
|
|
330
|
-
}
|
|
331
|
-
})()
|
|
332
|
-
|
|
333
|
-
Object.defineProperty(tiles, 'skipped', {
|
|
334
|
-
get() {
|
|
335
|
-
return skipped
|
|
336
|
-
},
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
Object.defineProperty(tiles, 'stats', {
|
|
340
|
-
get() {
|
|
341
|
-
return stats
|
|
342
|
-
},
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
return tiles
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Add two TileDownloadStats objects together.
|
|
351
|
-
*
|
|
352
|
-
* @param {TileDownloadStats} statsA
|
|
353
|
-
* @param {TileDownloadStats} statsB
|
|
354
|
-
* @returns {TileDownloadStats}
|
|
355
|
-
*/
|
|
356
|
-
function addStats(statsA, statsB) {
|
|
357
|
-
return {
|
|
358
|
-
total: statsA.total + statsB.total,
|
|
359
|
-
downloaded: statsA.downloaded + statsB.downloaded,
|
|
360
|
-
skipped: statsA.skipped + statsB.skipped,
|
|
361
|
-
totalBytes: statsA.totalBytes + statsB.totalBytes,
|
|
362
|
-
}
|
|
363
|
-
}
|