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/tile-downloader.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import SphericalMercator from '@mapbox/sphericalmercator'
|
|
2
|
-
import Queue from 'yocto-queue'
|
|
3
|
-
import zlib from 'zlib'
|
|
4
|
-
|
|
5
|
-
import { FetchQueue } from './utils/fetch.js'
|
|
6
|
-
import {
|
|
7
|
-
getFormatFromMimeType,
|
|
8
|
-
getTileFormatFromStream,
|
|
9
|
-
} from './utils/file-formats.js'
|
|
10
|
-
import { getTileUrl, MAX_BOUNDS } from './utils/geo.js'
|
|
11
|
-
import { noop } from './utils/misc.js'
|
|
12
|
-
|
|
13
|
-
/** @typedef {Omit<import('./writer.js').TileInfo, 'sourceId'>} TileInfo */
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {object} TileDownloadStats
|
|
16
|
-
* @property {number} total
|
|
17
|
-
* @property {number} downloaded
|
|
18
|
-
* @property {number} skipped
|
|
19
|
-
* @property {number} totalBytes
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Download tiles from a list of tile URLs within a bounding box and zoom range.
|
|
24
|
-
* Returns an async generator of tile data as readable streams and tile info objects.
|
|
25
|
-
*
|
|
26
|
-
* @param {object} opts
|
|
27
|
-
* @param {string[]} opts.tileUrls Array of tile URL templates. Use `{x}`, `{y}`, `{z}` placeholders, and optional `{scheme}` placeholder which can be `xyz` or `tms`.
|
|
28
|
-
* @param {import('./utils/geo.js').BBox} opts.bounds Bounding box of the area to download
|
|
29
|
-
* @param {number} opts.maxzoom Maximum zoom level to download
|
|
30
|
-
* @param {(progress: TileDownloadStats) => void} [opts.onprogress] Callback to report download progress
|
|
31
|
-
* @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.
|
|
32
|
-
* @param {import('./utils/geo.js').BBox} [opts.sourceBounds=MAX_BOUNDS] Bounding box of source data.
|
|
33
|
-
* @param {boolean} [opts.boundsBuffer=false] Buffer the bounds by one tile at each zoom level to ensure no tiles are missed at the edges. With this set to false, in most instances the map will appear incomplete when viewed because the downloaded tiles at lower zoom levels will not cover the map view area.
|
|
34
|
-
* @param {number} [opts.minzoom=0] Minimum zoom level to download (for most cases this should be left as `0` - the size overhead is minimal, because each zoom level has 4x as many tiles)
|
|
35
|
-
* @param {number} [opts.concurrency=8] Number of concurrent downloads (ignored if `fetchQueue` is provided)
|
|
36
|
-
* @param {FetchQueue} [opts.fetchQueue=new FetchQueue(concurrency)] Optional fetch queue to use for downloading tiles
|
|
37
|
-
* @param {'xyz' | 'tms'} [opts.scheme='xyz'] Tile scheme to use for tile URLs
|
|
38
|
-
* @returns {AsyncGenerator<[import('stream').Readable, TileInfo]> & { readonly skipped: Array<TileInfo & { error?: Error }>, readonly stats: TileDownloadStats }}
|
|
39
|
-
*/
|
|
40
|
-
export function downloadTiles({
|
|
41
|
-
tileUrls,
|
|
42
|
-
bounds,
|
|
43
|
-
maxzoom,
|
|
44
|
-
onprogress = noop,
|
|
45
|
-
trackErrors = false,
|
|
46
|
-
sourceBounds = MAX_BOUNDS,
|
|
47
|
-
boundsBuffer = false,
|
|
48
|
-
minzoom = 0,
|
|
49
|
-
concurrency = 8,
|
|
50
|
-
fetchQueue = new FetchQueue(concurrency),
|
|
51
|
-
scheme = 'xyz',
|
|
52
|
-
}) {
|
|
53
|
-
/** @type {Array<TileInfo & { error?: Error }>} */
|
|
54
|
-
const skipped = []
|
|
55
|
-
let completed = 0
|
|
56
|
-
/** @type {TileDownloadStats} */
|
|
57
|
-
let stats = {
|
|
58
|
-
total: 0,
|
|
59
|
-
downloaded: 0,
|
|
60
|
-
skipped: 0,
|
|
61
|
-
totalBytes: 0,
|
|
62
|
-
}
|
|
63
|
-
/** @type {import('./utils/streams.js').ProgressCallback} */
|
|
64
|
-
function onDownloadProgress({ chunkBytes }) {
|
|
65
|
-
stats.totalBytes += chunkBytes
|
|
66
|
-
onprogress(stats)
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
* @param {Error} error
|
|
71
|
-
* @param {TileInfo} tileInfo
|
|
72
|
-
*/
|
|
73
|
-
function onDownloadError(error, tileInfo) {
|
|
74
|
-
if (trackErrors) {
|
|
75
|
-
skipped.push({ ...tileInfo, error })
|
|
76
|
-
} else {
|
|
77
|
-
skipped.push(tileInfo)
|
|
78
|
-
}
|
|
79
|
-
onprogress(stats)
|
|
80
|
-
}
|
|
81
|
-
function onDownloadComplete() {
|
|
82
|
-
stats.downloaded = ++completed - skipped.length
|
|
83
|
-
stats.skipped = skipped.length
|
|
84
|
-
onprogress(stats)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** @type {ReturnType<downloadTiles>} */
|
|
88
|
-
const tiles = (async function* () {
|
|
89
|
-
/** @type {Queue<[Promise<void | import('./utils/fetch.js').DownloadResponse>, TileInfo]>} */
|
|
90
|
-
const queue = new Queue()
|
|
91
|
-
const tiles = tileIterator({
|
|
92
|
-
bounds,
|
|
93
|
-
minzoom,
|
|
94
|
-
maxzoom,
|
|
95
|
-
sourceBounds,
|
|
96
|
-
boundsBuffer,
|
|
97
|
-
})
|
|
98
|
-
for (const { x, y, z } of tiles) {
|
|
99
|
-
const tileURL = getTileUrl(tileUrls, { x, y, z, scheme })
|
|
100
|
-
const tileInfo = { z, x, y }
|
|
101
|
-
const result = fetchQueue
|
|
102
|
-
.fetch(tileURL, { onprogress: onDownloadProgress })
|
|
103
|
-
// We handle error here rather than below to avoid uncaught errors
|
|
104
|
-
.catch((err) => onDownloadError(err, tileInfo))
|
|
105
|
-
queue.enqueue([result, tileInfo])
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
stats.total = queue.size
|
|
109
|
-
if (onprogress) onprogress(stats)
|
|
110
|
-
|
|
111
|
-
for (const [result, tileInfo] of queue) {
|
|
112
|
-
// We handle any error above and add to `skipped`
|
|
113
|
-
const downloadResponse = await result.catch(noop)
|
|
114
|
-
if (!downloadResponse) continue
|
|
115
|
-
let { body, mimeType } = downloadResponse
|
|
116
|
-
body.on('end', onDownloadComplete)
|
|
117
|
-
body.on('error', (err) => onDownloadError(err, tileInfo))
|
|
118
|
-
/** @type {import('./writer.js').TileFormat} */
|
|
119
|
-
let format
|
|
120
|
-
if (mimeType) {
|
|
121
|
-
format = getFormatFromMimeType(mimeType)
|
|
122
|
-
} else {
|
|
123
|
-
;[format, body] = await getTileFormatFromStream(body)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let stream = body
|
|
127
|
-
if (format === 'mvt') {
|
|
128
|
-
// MVT tiles are always gzipped. Unfortunately we can't stop fetch from
|
|
129
|
-
// ungzipping the data during download, so we need to re-gzip it.
|
|
130
|
-
const gzipStream = zlib.createGzip()
|
|
131
|
-
stream = body.pipe(gzipStream)
|
|
132
|
-
stream.on('error', (err) => gzipStream.destroy(err))
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
yield [stream, { ...tileInfo, format }]
|
|
136
|
-
}
|
|
137
|
-
})()
|
|
138
|
-
|
|
139
|
-
Object.defineProperty(tiles, 'skipped', {
|
|
140
|
-
get() {
|
|
141
|
-
return skipped
|
|
142
|
-
},
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
Object.defineProperty(tiles, 'stats', {
|
|
146
|
-
get() {
|
|
147
|
-
return stats
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
return tiles
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
*
|
|
156
|
-
* @param {object} opts
|
|
157
|
-
* @param {import('./utils/geo.js').BBox} [opts.bounds]
|
|
158
|
-
* @param {import('./utils/geo.js').BBox} [opts.sourceBounds]
|
|
159
|
-
* @param {boolean} [opts.boundsBuffer]
|
|
160
|
-
* @param {number} [opts.minzoom]
|
|
161
|
-
* @param {number} opts.maxzoom
|
|
162
|
-
*/
|
|
163
|
-
export function* tileIterator({
|
|
164
|
-
bounds = [...MAX_BOUNDS],
|
|
165
|
-
minzoom = 0,
|
|
166
|
-
maxzoom,
|
|
167
|
-
sourceBounds,
|
|
168
|
-
boundsBuffer = false,
|
|
169
|
-
}) {
|
|
170
|
-
const sm = new SphericalMercator({ size: 256 })
|
|
171
|
-
for (let z = minzoom; z <= maxzoom; z++) {
|
|
172
|
-
// Cloning bounds passed to sm.xyz because no guarantee it won't mutate the array
|
|
173
|
-
let { minX, minY, maxX, maxY } = sm.xyz([...bounds], z)
|
|
174
|
-
let sourceXYBounds = sourceBounds
|
|
175
|
-
? sm.xyz([...sourceBounds], z)
|
|
176
|
-
: { minX, minY, maxX, maxY }
|
|
177
|
-
const buffer = boundsBuffer ? 1 : 0
|
|
178
|
-
minX = Math.max(0, minX - buffer, sourceXYBounds.minX)
|
|
179
|
-
minY = Math.max(0, minY - buffer, sourceXYBounds.minY)
|
|
180
|
-
maxX = Math.min(Math.pow(2, z) - 1, maxX + buffer, sourceXYBounds.maxX)
|
|
181
|
-
maxY = Math.min(Math.pow(2, z) - 1, maxY + buffer, sourceXYBounds.maxY)
|
|
182
|
-
for (let x = minX; x <= maxX; x++) {
|
|
183
|
-
for (let y = minY; y <= maxY; y++) {
|
|
184
|
-
yield { x, y, z }
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
package/lib/types.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SourceSpecification,
|
|
3
|
-
StyleSpecification,
|
|
4
|
-
ValidationError,
|
|
5
|
-
GeoJSONSourceSpecification,
|
|
6
|
-
VectorSourceSpecification,
|
|
7
|
-
RasterSourceSpecification,
|
|
8
|
-
RasterDEMSourceSpecification,
|
|
9
|
-
} from '@maplibre/maplibre-gl-style-spec'
|
|
10
|
-
import type { GeoJSON, BBox } from 'geojson'
|
|
11
|
-
import type { Readable } from 'stream'
|
|
12
|
-
import type { Except, SetRequired, Simplify } from 'type-fest'
|
|
13
|
-
|
|
14
|
-
import { SUPPORTED_SOURCE_TYPES } from './writer.js'
|
|
15
|
-
|
|
16
|
-
export type InputSource = Extract<
|
|
17
|
-
SourceSpecification,
|
|
18
|
-
{ type: (typeof SUPPORTED_SOURCE_TYPES)[number] }
|
|
19
|
-
>
|
|
20
|
-
type TransformInlinedSource<T extends SourceSpecification> =
|
|
21
|
-
T extends GeoJSONSourceSpecification
|
|
22
|
-
? OmitUnion<T, 'data'> & { data: GeoJSON }
|
|
23
|
-
: T extends
|
|
24
|
-
| VectorSourceSpecification
|
|
25
|
-
| RasterSourceSpecification
|
|
26
|
-
| RasterDEMSourceSpecification
|
|
27
|
-
? SetRequired<OmitUnion<T, 'url'>, 'tiles'>
|
|
28
|
-
: T
|
|
29
|
-
/**
|
|
30
|
-
* This is a slightly stricter version of SourceSpecification that requires
|
|
31
|
-
* sources to be inlined (e.g. no urls to TileJSON or GeoJSON files).
|
|
32
|
-
*/
|
|
33
|
-
export type InlinedSource = TransformInlinedSource<SourceSpecification>
|
|
34
|
-
type SupportedInlinedSource = Extract<
|
|
35
|
-
InlinedSource,
|
|
36
|
-
{ type: (typeof SUPPORTED_SOURCE_TYPES)[number] }
|
|
37
|
-
>
|
|
38
|
-
/**
|
|
39
|
-
* This is a slightly stricter version of StyleSpecification that requires
|
|
40
|
-
* sources to be inlined (e.g. no urls to TileJSON or GeoJSON files).
|
|
41
|
-
*/
|
|
42
|
-
export type StyleInlinedSources = Omit<StyleSpecification, 'sources'> & {
|
|
43
|
-
sources: {
|
|
44
|
-
[_: string]: InlinedSource
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type SMPSource = TransformSMPInputSource<SupportedInlinedSource>
|
|
49
|
-
/**
|
|
50
|
-
* This is a slightly stricter version of StyleSpecification that is provided in
|
|
51
|
-
* a Styled Map Package. Tile sources must have tile URLs inlined (they cannot
|
|
52
|
-
* refer to a TileJSON url), and they must have bounds, minzoom, and maxzoom.
|
|
53
|
-
* GeoJSON sources must have inlined GeoJSON (not a URL to a GeoJSON file).
|
|
54
|
-
*/
|
|
55
|
-
export type SMPStyle = TransformSMPStyle<StyleSpecification>
|
|
56
|
-
|
|
57
|
-
export type TransformSMPInputSource<T extends SupportedInlinedSource> =
|
|
58
|
-
T extends GeoJSONSourceSpecification
|
|
59
|
-
? T & { data: { bbox: BBox } }
|
|
60
|
-
: T extends RasterSourceSpecification | VectorSourceSpecification
|
|
61
|
-
? SetRequired<T, 'bounds' | 'minzoom' | 'maxzoom'>
|
|
62
|
-
: T
|
|
63
|
-
|
|
64
|
-
type TransformSMPStyle<T extends StyleSpecification> = Omit<T, 'sources'> & {
|
|
65
|
-
metadata: {
|
|
66
|
-
'smp:bounds': [number, number, number, number]
|
|
67
|
-
'smp:maxzoom': 0
|
|
68
|
-
'smp:sourceFolders': { [_: string]: string }
|
|
69
|
-
}
|
|
70
|
-
sources: {
|
|
71
|
-
[_: string]: SMPSource
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface ValidateStyle {
|
|
76
|
-
(style: unknown): style is StyleSpecification
|
|
77
|
-
errors: Array<ValidationError>
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface DownloadStream extends Readable {
|
|
81
|
-
iterator(
|
|
82
|
-
...args: Parameters<Readable['iterator']>
|
|
83
|
-
): AsyncIterableIterator<Buffer>
|
|
84
|
-
[Symbol.asyncIterator](): AsyncIterableIterator<Buffer>
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export type RequiredUnion<T> = T extends any ? Required<T> : never
|
|
88
|
-
export type OmitUnion<T, K extends keyof any> = T extends unknown
|
|
89
|
-
? Omit<T, K>
|
|
90
|
-
: never
|
|
91
|
-
|
|
92
|
-
type SetRequiredIfPresent<
|
|
93
|
-
BaseType,
|
|
94
|
-
Keys extends keyof any,
|
|
95
|
-
> = BaseType extends unknown
|
|
96
|
-
? Keys extends keyof BaseType
|
|
97
|
-
? Simplify<
|
|
98
|
-
// Pick just the keys that are optional from the base type.
|
|
99
|
-
Except<BaseType, Keys> &
|
|
100
|
-
// Pick the keys that should be required from the base type and make them required.
|
|
101
|
-
Required<Pick<BaseType, Keys>>
|
|
102
|
-
>
|
|
103
|
-
: never
|
|
104
|
-
: never
|
package/lib/utils/errors.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export class ENOENT extends Error {
|
|
2
|
-
code = 'ENOENT'
|
|
3
|
-
/** @param {string} path */
|
|
4
|
-
constructor(path) {
|
|
5
|
-
const message = `ENOENT: no such file or directory, open '${path}'`
|
|
6
|
-
super(message)
|
|
7
|
-
this.path = path
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Returns true if the error if because a file is not found. On Windows, some
|
|
13
|
-
* operations like fs.watch() throw an EPERM error rather than ENOENT.
|
|
14
|
-
*
|
|
15
|
-
* @param {unknown} error
|
|
16
|
-
* @returns {error is Error & { code: 'ENOENT' | 'EPERM' }}
|
|
17
|
-
*/
|
|
18
|
-
export function isFileNotThereError(error) {
|
|
19
|
-
return (
|
|
20
|
-
error instanceof Error &&
|
|
21
|
-
'code' in error &&
|
|
22
|
-
(error.code === 'ENOENT' || error.code === 'EPERM')
|
|
23
|
-
)
|
|
24
|
-
}
|
package/lib/utils/fetch.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import ky from 'ky'
|
|
2
|
-
import { pEvent } from 'p-event'
|
|
3
|
-
import pLimit from 'p-limit'
|
|
4
|
-
|
|
5
|
-
import { fromWebReadableStream, ProgressStream } from './streams.js'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {object} DownloadResponse
|
|
9
|
-
* @property {import('stream').Readable} body Node ReadableStream of the response body
|
|
10
|
-
* @property {string | null} mimeType Content mime-type (from http content-type header)
|
|
11
|
-
* @property {number | null} contentLength Content length in bytes (from http content-length header)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* A wrapper for fetch that limits the number of concurrent downloads.
|
|
16
|
-
*/
|
|
17
|
-
export class FetchQueue {
|
|
18
|
-
/** @type {import('p-limit').LimitFunction} */
|
|
19
|
-
#limit
|
|
20
|
-
/** @param {number} concurrency */
|
|
21
|
-
constructor(concurrency) {
|
|
22
|
-
this.#limit = pLimit(concurrency)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
get activeCount() {
|
|
26
|
-
return this.#limit.activeCount
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Fetch a URL, limiting the number of concurrent downloads. Resolves with a
|
|
31
|
-
* `DownloadResponse`, which is a parsed from the Fetch `Response` objects,
|
|
32
|
-
* with `body` as a Node readable stream, and the MIME type and content length
|
|
33
|
-
* of the response.
|
|
34
|
-
*
|
|
35
|
-
* NB: The response body stream must be consumed to the end, otherwise the
|
|
36
|
-
* queue will never be emptied.
|
|
37
|
-
*
|
|
38
|
-
* @param {string} url
|
|
39
|
-
* @param {{ onprogress?: import('./streams.js').ProgressCallback }} opts
|
|
40
|
-
* @returns {Promise<DownloadResponse>}
|
|
41
|
-
*/
|
|
42
|
-
fetch(url, { onprogress } = {}) {
|
|
43
|
-
// This is wrapped like this so that pLimit limits concurrent `fetchStream`
|
|
44
|
-
// calls, which only resolve when the body is completely downloaded, but
|
|
45
|
-
// this method will return a response as soon as it is available. NB: If the
|
|
46
|
-
// body of a response is never "consumed" (e.g. by reading it to the end),
|
|
47
|
-
// the fetchStream function will never resolve, and the limit will never be
|
|
48
|
-
// released.
|
|
49
|
-
return new Promise((resolveResponse, rejectResponse) => {
|
|
50
|
-
this.#limit(fetchStream, {
|
|
51
|
-
url,
|
|
52
|
-
onresponse: resolveResponse,
|
|
53
|
-
onerror: rejectResponse,
|
|
54
|
-
onprogress,
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* This will resolve when the download is complete, regardless of success or
|
|
62
|
-
* failure, but a readable stream is available before download via the
|
|
63
|
-
* onReadStream param. This strange function signature is used for limiting the
|
|
64
|
-
* number of simultaneous downloads, but still being able to expose the Response
|
|
65
|
-
* as soon as it is available. This is implmented this way to avoid creating
|
|
66
|
-
* unnecessary closures, which is important here because we can have thousands
|
|
67
|
-
* of tile requests.
|
|
68
|
-
*
|
|
69
|
-
* @param {object} opts
|
|
70
|
-
* @param {string} opts.url
|
|
71
|
-
* @param {(response: DownloadResponse) => void} opts.onresponse
|
|
72
|
-
* @param {(err: Error) => void} opts.onerror
|
|
73
|
-
* @param {import('./streams.js').ProgressCallback} [opts.onprogress]
|
|
74
|
-
* @returns {Promise<void>}
|
|
75
|
-
*/
|
|
76
|
-
async function fetchStream({ url, onresponse, onerror, onprogress }) {
|
|
77
|
-
try {
|
|
78
|
-
const response = await ky(url, { retry: 3 })
|
|
79
|
-
if (!response.body) {
|
|
80
|
-
throw new Error('No body in response')
|
|
81
|
-
}
|
|
82
|
-
const body = fromWebReadableStream(response.body)
|
|
83
|
-
const contentType = response.headers.get('content-type')
|
|
84
|
-
const mimeType =
|
|
85
|
-
typeof contentType === 'string' ? contentType.split(';')[0] : null
|
|
86
|
-
const contentLengthHeader = response.headers.get('content-length')
|
|
87
|
-
const contentLength =
|
|
88
|
-
contentLengthHeader === null ? null : parseInt(contentLengthHeader, 10)
|
|
89
|
-
onresponse({
|
|
90
|
-
body: onprogress ? body.pipe(new ProgressStream({ onprogress })) : body,
|
|
91
|
-
mimeType,
|
|
92
|
-
contentLength,
|
|
93
|
-
})
|
|
94
|
-
// Wait for the read stream to end before resolving this function, so that
|
|
95
|
-
// we limit concurrent downloads
|
|
96
|
-
await pEvent(body, 'end')
|
|
97
|
-
} catch (err) {
|
|
98
|
-
onerror(err instanceof Error ? err : new Error('Unknown error'))
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import BufferPeerStream from 'buffer-peek-stream'
|
|
2
|
-
|
|
3
|
-
import { hasOwn } from './misc.js'
|
|
4
|
-
|
|
5
|
-
const peek = BufferPeerStream.promise
|
|
6
|
-
|
|
7
|
-
const MAGIC_BYTES = /** @type {const} */ ({
|
|
8
|
-
png: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
|
|
9
|
-
jpg: [0xff, 0xd8, 0xff],
|
|
10
|
-
// eslint-disable-next-line no-sparse-arrays
|
|
11
|
-
webp: [0x52, 0x49, 0x46, 0x46, , , , , 0x57, 0x45, 0x42, 0x50],
|
|
12
|
-
// Include the compression-type byte, which is always 0x08 (DEFLATE) for gzip
|
|
13
|
-
gz: [0x1f, 0x8b, 0x08],
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
const MIME_TYPES = /** @type {const} */ ({
|
|
17
|
-
'image/png': 'png',
|
|
18
|
-
'image/jpeg': 'jpg',
|
|
19
|
-
'image/webp': 'webp',
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
/** @type {Map<number, keyof typeof MAGIC_BYTES>} */
|
|
23
|
-
const magicByteMap = new Map()
|
|
24
|
-
for (const [ext, bytes] of Object.entries(MAGIC_BYTES)) {
|
|
25
|
-
magicByteMap.set(
|
|
26
|
-
bytes[0],
|
|
27
|
-
// @ts-ignore
|
|
28
|
-
ext,
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* For a given buffer, determine the tile format based on the magic bytes.
|
|
33
|
-
* Will throw for unknown file types.
|
|
34
|
-
* Smaller and faster version of magic-bytes.js due to the limited use case.
|
|
35
|
-
*
|
|
36
|
-
* @param {Buffer | Uint8Array} buf
|
|
37
|
-
* @returns {import("../writer.js").TileFormat}
|
|
38
|
-
*/
|
|
39
|
-
export function getTileFormatFromBuffer(buf) {
|
|
40
|
-
const ext = magicByteMap.get(buf[0])
|
|
41
|
-
if (!ext) {
|
|
42
|
-
throw new Error('Unknown file type')
|
|
43
|
-
}
|
|
44
|
-
const sig = MAGIC_BYTES[ext]
|
|
45
|
-
for (let i = 1; i < sig.length; i++) {
|
|
46
|
-
if (typeof sig[i] !== 'undefined' && sig[i] !== buf[i]) {
|
|
47
|
-
throw new Error('Unknown file type')
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (ext === 'gz') {
|
|
51
|
-
// Gzipped tiles are always MVT
|
|
52
|
-
return 'mvt'
|
|
53
|
-
}
|
|
54
|
-
return ext
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Determine the tile format from either a readable stream, buffer or Uint8Array
|
|
59
|
-
* from the magic bytes at the start of the file. Used if data is served without
|
|
60
|
-
* a content-type header.
|
|
61
|
-
*
|
|
62
|
-
* @param {import('stream').Readable} tileData
|
|
63
|
-
* @returns {Promise<[import("../writer.js").TileFormat, import('stream').Readable]>}
|
|
64
|
-
*/
|
|
65
|
-
export async function getTileFormatFromStream(tileData) {
|
|
66
|
-
// NB: The buffer-peek-stream library uses the peeked bytes as the high
|
|
67
|
-
// water mark for the transform stream, so we set this to the default high
|
|
68
|
-
// water mark of 16KB
|
|
69
|
-
const [peekedData, outputStream] = await peek(tileData, 16 * 1024)
|
|
70
|
-
tileData = outputStream
|
|
71
|
-
const format = getTileFormatFromBuffer(peekedData)
|
|
72
|
-
return [format, outputStream]
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Get the tile format from a MIME type. Throws for unsupported types.
|
|
77
|
-
*
|
|
78
|
-
* @param {string} mimeType
|
|
79
|
-
* @returns {import("../writer.js").TileFormat}
|
|
80
|
-
*/
|
|
81
|
-
export function getFormatFromMimeType(mimeType) {
|
|
82
|
-
if (mimeType.startsWith('application/')) return 'mvt'
|
|
83
|
-
if (hasOwn(MIME_TYPES, mimeType)) return MIME_TYPES[mimeType]
|
|
84
|
-
throw new Error('Unsupported MIME type ' + mimeType)
|
|
85
|
-
}
|
package/lib/utils/geo.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// Adapted from https://github.com/mapbox/tilebelt
|
|
2
|
-
|
|
3
|
-
const r2d = 180 / Math.PI
|
|
4
|
-
|
|
5
|
-
/** Spherical Mercator max bounds, rounded to 6 decimal places */
|
|
6
|
-
export const MAX_BOUNDS = /** @type {BBox} */ ([
|
|
7
|
-
-180, -85.051129, 180, 85.051129,
|
|
8
|
-
])
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {[number, number, number, number]} BBox
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Return the bounding box for the given tile.
|
|
16
|
-
*
|
|
17
|
-
* @param {{ x: number, y: number, z: number }} tile
|
|
18
|
-
* @returns {BBox} Bounding Box [w, s, e, n]
|
|
19
|
-
*/
|
|
20
|
-
export function tileToBBox({ x, y, z }) {
|
|
21
|
-
const e = tile2lon({ x: x + 1, z })
|
|
22
|
-
const w = tile2lon({ x, z })
|
|
23
|
-
const s = tile2lat({ y: y + 1, z })
|
|
24
|
-
const n = tile2lat({ y, z })
|
|
25
|
-
return [w, s, e, n]
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @param {{ x: number, y: number, z: number }} tile
|
|
30
|
-
*/
|
|
31
|
-
export function getQuadkey({ x, y, z }) {
|
|
32
|
-
let quadkey = ''
|
|
33
|
-
let mask
|
|
34
|
-
for (let i = z; i > 0; i--) {
|
|
35
|
-
mask = 1 << (i - 1)
|
|
36
|
-
quadkey += (x & mask ? 1 : 0) + (y & mask ? 2 : 0)
|
|
37
|
-
}
|
|
38
|
-
return quadkey
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* From an array of tile URL templates, get the URL for the given tile.
|
|
43
|
-
*
|
|
44
|
-
* @param {string[]} urls
|
|
45
|
-
* @param {{ x: number, y: number, z: number, scheme?: 'xyz' | 'tms' }} opts
|
|
46
|
-
*/
|
|
47
|
-
export function getTileUrl(urls, { x, y, z, scheme = 'xyz' }) {
|
|
48
|
-
const bboxEspg3857 = tileToBBox({ x, y: Math.pow(2, z) - y - 1, z })
|
|
49
|
-
const quadkey = getQuadkey({ x, y, z })
|
|
50
|
-
|
|
51
|
-
return urls[(x + y) % urls.length]
|
|
52
|
-
.replace('{prefix}', (x % 16).toString(16) + (y % 16).toString(16))
|
|
53
|
-
.replace(/{z}/g, String(z))
|
|
54
|
-
.replace(/{x}/g, String(x))
|
|
55
|
-
.replace(/{y}/g, String(scheme === 'tms' ? Math.pow(2, z) - y - 1 : y))
|
|
56
|
-
.replace('{quadkey}', quadkey)
|
|
57
|
-
.replace('{bbox-epsg-3857}', bboxEspg3857.join(','))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Returns a bbox that is the smallest bounding box that contains all the input bboxes.
|
|
62
|
-
*
|
|
63
|
-
* @param {[BBox, ...BBox[]]} bboxes
|
|
64
|
-
* @returns {BBox} Bounding Box [w, s, e, n]
|
|
65
|
-
*/
|
|
66
|
-
export function unionBBox(bboxes) {
|
|
67
|
-
let [w, s, e, n] = bboxes[0]
|
|
68
|
-
for (let i = 1; i < bboxes.length; i++) {
|
|
69
|
-
const [w1, s1, e1, n1] = bboxes[i]
|
|
70
|
-
w = Math.min(w, w1)
|
|
71
|
-
s = Math.min(s, s1)
|
|
72
|
-
e = Math.max(e, e1)
|
|
73
|
-
n = Math.max(n, n1)
|
|
74
|
-
}
|
|
75
|
-
return [w, s, e, n]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** @param {{ x: number, z: number }} opts */
|
|
79
|
-
function tile2lon({ x, z }) {
|
|
80
|
-
return (x / Math.pow(2, z)) * 360 - 180
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** @param {{ y: number, z: number }} opts */
|
|
84
|
-
function tile2lat({ y, z }) {
|
|
85
|
-
const n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z)
|
|
86
|
-
return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)))
|
|
87
|
-
}
|