styled-map-package 4.1.0 → 5.0.0-pre.1
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 +47 -7
- package/bin/smp-download.js +27 -151
- package/bin/smp-mbtiles.js +12 -7
- package/bin/smp-view.js +32 -54
- package/lib/commands/download.js +188 -0
- package/lib/commands/mbtiles.js +21 -0
- package/lib/commands/view.js +57 -0
- package/lib/reporters.js +92 -0
- package/package.json +9 -181
- package/LICENSE.md +0 -7
- package/dist/download.cjs +0 -101
- package/dist/download.d.cts +0 -65
- package/dist/download.d.ts +0 -65
- package/dist/download.js +0 -77
- package/dist/from-mbtiles.cjs +0 -91
- package/dist/from-mbtiles.d.cts +0 -17
- package/dist/from-mbtiles.d.ts +0 -17
- package/dist/from-mbtiles.js +0 -57
- package/dist/index.cjs +0 -49
- package/dist/index.d.cts +0 -27
- package/dist/index.d.ts +0 -27
- package/dist/index.js +0 -18
- package/dist/reader-watch.cjs +0 -135
- package/dist/reader-watch.d.cts +0 -24
- package/dist/reader-watch.d.ts +0 -24
- package/dist/reader-watch.js +0 -101
- package/dist/reader.cjs +0 -168
- package/dist/reader.d.cts +0 -62
- package/dist/reader.d.ts +0 -62
- package/dist/reader.js +0 -139
- package/dist/reporters.cjs +0 -122
- package/dist/reporters.d.cts +0 -10
- package/dist/reporters.d.ts +0 -10
- package/dist/reporters.js +0 -88
- package/dist/server.cjs +0 -78
- package/dist/server.d.cts +0 -48
- package/dist/server.d.ts +0 -48
- package/dist/server.js +0 -54
- package/dist/style-downloader.cjs +0 -312
- package/dist/style-downloader.d.cts +0 -120
- package/dist/style-downloader.d.ts +0 -120
- package/dist/style-downloader.js +0 -288
- package/dist/tile-downloader.cjs +0 -158
- package/dist/tile-downloader.d.cts +0 -84
- package/dist/tile-downloader.d.ts +0 -84
- package/dist/tile-downloader.js +0 -126
- package/dist/types-yLQy3AKR.d.cts +0 -192
- package/dist/types-yLQy3AKR.d.ts +0 -192
- package/dist/utils/errors.cjs +0 -41
- package/dist/utils/errors.d.cts +0 -18
- package/dist/utils/errors.d.ts +0 -18
- package/dist/utils/errors.js +0 -16
- package/dist/utils/fetch.cjs +0 -96
- package/dist/utils/fetch.d.cts +0 -51
- package/dist/utils/fetch.d.ts +0 -51
- package/dist/utils/fetch.js +0 -62
- package/dist/utils/file-formats.cjs +0 -98
- package/dist/utils/file-formats.d.cts +0 -35
- package/dist/utils/file-formats.d.ts +0 -35
- package/dist/utils/file-formats.js +0 -62
- package/dist/utils/geo.cjs +0 -84
- package/dist/utils/geo.d.cts +0 -46
- package/dist/utils/geo.d.ts +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.d.ts +0 -43
- package/dist/utils/mapbox.js +0 -91
- package/dist/utils/misc.cjs +0 -39
- package/dist/utils/misc.d.cts +0 -22
- package/dist/utils/misc.d.ts +0 -22
- package/dist/utils/misc.js +0 -13
- package/dist/utils/streams.cjs +0 -130
- package/dist/utils/streams.d.cts +0 -73
- package/dist/utils/streams.d.ts +0 -73
- package/dist/utils/streams.js +0 -103
- package/dist/utils/style.cjs +0 -126
- package/dist/utils/style.d.cts +0 -69
- package/dist/utils/style.d.ts +0 -69
- package/dist/utils/style.js +0 -98
- package/dist/utils/templates.cjs +0 -118
- package/dist/utils/templates.d.cts +0 -80
- package/dist/utils/templates.d.ts +0 -80
- package/dist/utils/templates.js +0 -81
- package/dist/writer.cjs +0 -449
- package/dist/writer.d.cts +0 -7
- package/dist/writer.d.ts +0 -7
- package/dist/writer.js +0 -424
- package/node_modules/@node-rs/crc32/LICENSE +0 -21
- package/node_modules/@node-rs/crc32/README.md +0 -61
- package/node_modules/@node-rs/crc32/browser.js +0 -1
- package/node_modules/@node-rs/crc32/index.d.ts +0 -5
- package/node_modules/@node-rs/crc32/index.js +0 -368
- package/node_modules/@node-rs/crc32/package.json +0 -97
- package/node_modules/@node-rs/crc32-linux-x64-gnu/README.md +0 -3
- package/node_modules/@node-rs/crc32-linux-x64-gnu/crc32.linux-x64-gnu.node +0 -0
- package/node_modules/@node-rs/crc32-linux-x64-gnu/package.json +0 -43
- package/node_modules/@node-rs/crc32-linux-x64-musl/README.md +0 -3
- package/node_modules/@node-rs/crc32-linux-x64-musl/crc32.linux-x64-musl.node +0 -0
- package/node_modules/@node-rs/crc32-linux-x64-musl/package.json +0 -43
- package/node_modules/define-data-property/.eslintrc +0 -24
- package/node_modules/define-data-property/.github/FUNDING.yml +0 -12
- package/node_modules/define-data-property/.nycrc +0 -13
- package/node_modules/define-data-property/CHANGELOG.md +0 -70
- package/node_modules/define-data-property/LICENSE +0 -21
- package/node_modules/define-data-property/README.md +0 -67
- package/node_modules/define-data-property/index.d.ts +0 -12
- package/node_modules/define-data-property/index.js +0 -56
- package/node_modules/define-data-property/package.json +0 -106
- package/node_modules/define-data-property/test/index.js +0 -392
- package/node_modules/define-data-property/tsconfig.json +0 -59
- package/node_modules/define-properties/.editorconfig +0 -13
- package/node_modules/define-properties/.eslintrc +0 -19
- package/node_modules/define-properties/.github/FUNDING.yml +0 -12
- package/node_modules/define-properties/.nycrc +0 -9
- package/node_modules/define-properties/CHANGELOG.md +0 -91
- package/node_modules/define-properties/LICENSE +0 -21
- package/node_modules/define-properties/README.md +0 -84
- package/node_modules/define-properties/index.js +0 -47
- package/node_modules/define-properties/package.json +0 -88
- package/node_modules/es-define-property/.eslintrc +0 -13
- package/node_modules/es-define-property/.github/FUNDING.yml +0 -12
- package/node_modules/es-define-property/.nycrc +0 -9
- package/node_modules/es-define-property/CHANGELOG.md +0 -29
- package/node_modules/es-define-property/LICENSE +0 -21
- package/node_modules/es-define-property/README.md +0 -49
- package/node_modules/es-define-property/index.d.ts +0 -3
- package/node_modules/es-define-property/index.js +0 -14
- package/node_modules/es-define-property/package.json +0 -81
- package/node_modules/es-define-property/test/index.js +0 -56
- package/node_modules/es-define-property/tsconfig.json +0 -10
- package/node_modules/es-errors/.eslintrc +0 -5
- package/node_modules/es-errors/.github/FUNDING.yml +0 -12
- package/node_modules/es-errors/CHANGELOG.md +0 -40
- package/node_modules/es-errors/LICENSE +0 -21
- package/node_modules/es-errors/README.md +0 -55
- package/node_modules/es-errors/eval.d.ts +0 -3
- package/node_modules/es-errors/eval.js +0 -4
- package/node_modules/es-errors/index.d.ts +0 -3
- package/node_modules/es-errors/index.js +0 -4
- package/node_modules/es-errors/package.json +0 -80
- package/node_modules/es-errors/range.d.ts +0 -3
- package/node_modules/es-errors/range.js +0 -4
- package/node_modules/es-errors/ref.d.ts +0 -3
- package/node_modules/es-errors/ref.js +0 -4
- package/node_modules/es-errors/syntax.d.ts +0 -3
- package/node_modules/es-errors/syntax.js +0 -4
- package/node_modules/es-errors/test/index.js +0 -19
- package/node_modules/es-errors/tsconfig.json +0 -49
- package/node_modules/es-errors/type.d.ts +0 -3
- package/node_modules/es-errors/type.js +0 -4
- package/node_modules/es-errors/uri.d.ts +0 -3
- package/node_modules/es-errors/uri.js +0 -4
- package/node_modules/globalthis/.eslintrc +0 -18
- package/node_modules/globalthis/.nycrc +0 -10
- package/node_modules/globalthis/CHANGELOG.md +0 -109
- package/node_modules/globalthis/LICENSE +0 -21
- package/node_modules/globalthis/README.md +0 -70
- package/node_modules/globalthis/auto.js +0 -3
- package/node_modules/globalthis/implementation.browser.js +0 -11
- package/node_modules/globalthis/implementation.js +0 -3
- package/node_modules/globalthis/index.js +0 -19
- package/node_modules/globalthis/package.json +0 -99
- package/node_modules/globalthis/polyfill.js +0 -10
- package/node_modules/globalthis/shim.js +0 -29
- package/node_modules/globalthis/test/implementation.js +0 -11
- package/node_modules/globalthis/test/index.js +0 -11
- package/node_modules/globalthis/test/native.js +0 -26
- package/node_modules/globalthis/test/shimmed.js +0 -29
- package/node_modules/globalthis/test/tests.js +0 -36
- package/node_modules/gopd/.eslintrc +0 -16
- package/node_modules/gopd/.github/FUNDING.yml +0 -12
- package/node_modules/gopd/CHANGELOG.md +0 -45
- package/node_modules/gopd/LICENSE +0 -21
- package/node_modules/gopd/README.md +0 -40
- package/node_modules/gopd/gOPD.d.ts +0 -1
- package/node_modules/gopd/gOPD.js +0 -4
- package/node_modules/gopd/index.d.ts +0 -5
- package/node_modules/gopd/index.js +0 -15
- package/node_modules/gopd/package.json +0 -77
- package/node_modules/gopd/test/index.js +0 -36
- package/node_modules/gopd/tsconfig.json +0 -9
- package/node_modules/has-property-descriptors/.eslintrc +0 -13
- package/node_modules/has-property-descriptors/.github/FUNDING.yml +0 -12
- package/node_modules/has-property-descriptors/.nycrc +0 -9
- package/node_modules/has-property-descriptors/CHANGELOG.md +0 -35
- package/node_modules/has-property-descriptors/LICENSE +0 -21
- package/node_modules/has-property-descriptors/README.md +0 -43
- package/node_modules/has-property-descriptors/index.js +0 -22
- package/node_modules/has-property-descriptors/package.json +0 -77
- package/node_modules/has-property-descriptors/test/index.js +0 -57
- package/node_modules/is-it-type/License +0 -19
- package/node_modules/is-it-type/README.md +0 -102
- package/node_modules/is-it-type/changelog.md +0 -239
- package/node_modules/is-it-type/dist/cjs/is-it-type.js +0 -173
- package/node_modules/is-it-type/dist/cjs/is-it-type.js.map +0 -1
- package/node_modules/is-it-type/dist/cjs/is-it-type.min.js +0 -2
- package/node_modules/is-it-type/dist/cjs/is-it-type.min.js.map +0 -1
- package/node_modules/is-it-type/dist/esm/is-it-type.js +0 -143
- package/node_modules/is-it-type/dist/esm/is-it-type.js.map +0 -1
- package/node_modules/is-it-type/dist/esm/is-it-type.min.js +0 -2
- package/node_modules/is-it-type/dist/esm/is-it-type.min.js.map +0 -1
- package/node_modules/is-it-type/dist/esm/package.json +0 -3
- package/node_modules/is-it-type/dist/umd/is-it-type.js +0 -450
- package/node_modules/is-it-type/dist/umd/is-it-type.js.map +0 -1
- package/node_modules/is-it-type/dist/umd/is-it-type.min.js +0 -2
- package/node_modules/is-it-type/dist/umd/is-it-type.min.js.map +0 -1
- package/node_modules/is-it-type/es/index.js +0 -8
- package/node_modules/is-it-type/es/package.json +0 -3
- package/node_modules/is-it-type/index.js +0 -10
- package/node_modules/is-it-type/package.json +0 -87
- package/node_modules/is-it-type/src/index.js +0 -169
- package/node_modules/object-keys/.editorconfig +0 -13
- package/node_modules/object-keys/.eslintrc +0 -17
- package/node_modules/object-keys/.travis.yml +0 -277
- package/node_modules/object-keys/CHANGELOG.md +0 -232
- package/node_modules/object-keys/LICENSE +0 -21
- package/node_modules/object-keys/README.md +0 -76
- package/node_modules/object-keys/implementation.js +0 -122
- package/node_modules/object-keys/index.js +0 -32
- package/node_modules/object-keys/isArguments.js +0 -17
- package/node_modules/object-keys/package.json +0 -88
- package/node_modules/object-keys/test/index.js +0 -5
- package/node_modules/simple-invariant/License +0 -19
- package/node_modules/simple-invariant/README.md +0 -64
- package/node_modules/simple-invariant/changelog.md +0 -31
- package/node_modules/simple-invariant/index.js +0 -19
- package/node_modules/simple-invariant/package.json +0 -50
- package/node_modules/yauzl-promise/License +0 -19
- package/node_modules/yauzl-promise/README.md +0 -440
- package/node_modules/yauzl-promise/index.js +0 -10
- package/node_modules/yauzl-promise/lib/entry.js +0 -312
- package/node_modules/yauzl-promise/lib/index.js +0 -160
- package/node_modules/yauzl-promise/lib/reader.js +0 -289
- package/node_modules/yauzl-promise/lib/shared.js +0 -20
- package/node_modules/yauzl-promise/lib/utils.js +0 -105
- package/node_modules/yauzl-promise/lib/zip.js +0 -1224
- package/node_modules/yauzl-promise/package.json +0 -56
package/README.md
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# styled-map-package
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for creating, viewing, and converting Styled Map Package (`.smp`) files.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
An `.smp` file is a ZIP archive containing all the resources needed to serve a MapLibre vector styled map offline: style JSON, vector and raster tiles, glyphs (fonts), sprites, and metadata.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
npm install --global styled-map-package
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### `smp download`
|
|
14
16
|
|
|
15
|
-
Download an online map to a
|
|
17
|
+
Download an online map style to a `.smp` file for offline use.
|
|
16
18
|
|
|
17
19
|
```sh
|
|
18
20
|
smp download https://demotiles.maplibre.org/style.json \
|
|
@@ -21,8 +23,46 @@ smp download https://demotiles.maplibre.org/style.json \
|
|
|
21
23
|
--output demotiles.smp
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
**Options:**
|
|
27
|
+
|
|
28
|
+
| Option | Description |
|
|
29
|
+
| ---------------------- | ------------------------------------------------ |
|
|
30
|
+
| `-o, --output <file>` | Output file (writes to stdout if omitted) |
|
|
31
|
+
| `-b, --bbox <w,s,e,n>` | Bounding box (west, south, east, north) |
|
|
32
|
+
| `-z, --zoom <number>` | Max zoom level (0-22) |
|
|
33
|
+
| `-t, --token <token>` | Mapbox access token (required for Mapbox styles) |
|
|
34
|
+
|
|
35
|
+
When run interactively, missing options are prompted for.
|
|
36
|
+
|
|
37
|
+
### `smp view`
|
|
38
|
+
|
|
39
|
+
Preview a `.smp` file in a web browser.
|
|
25
40
|
|
|
26
41
|
```sh
|
|
27
42
|
smp view demotiles.smp --open
|
|
28
43
|
```
|
|
44
|
+
|
|
45
|
+
**Options:**
|
|
46
|
+
|
|
47
|
+
| Option | Description |
|
|
48
|
+
| --------------------- | -------------------------------- |
|
|
49
|
+
| `-o, --open` | Open in the default web browser |
|
|
50
|
+
| `-p, --port <number>` | Port to serve on (default: 3000) |
|
|
51
|
+
|
|
52
|
+
### `smp mbtiles`
|
|
53
|
+
|
|
54
|
+
Convert an MBTiles file to a `.smp` file.
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
smp mbtiles tiles.mbtiles --output map.smp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Options:**
|
|
61
|
+
|
|
62
|
+
| Option | Description |
|
|
63
|
+
| --------------------- | ------------------------------------------------ |
|
|
64
|
+
| `-o, --output <file>` | Output `.smp` file (writes to stdout if omitted) |
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT
|
package/bin/smp-download.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { input, number } from '@inquirer/prompts'
|
|
3
|
-
import { Command
|
|
3
|
+
import { Command } from 'commander'
|
|
4
4
|
import fs from 'fs'
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
import { download } from 'styled-map-package-api/download'
|
|
6
|
+
import {
|
|
7
|
+
isMapboxURL,
|
|
8
|
+
API_URL as MAPBOX_API_URL,
|
|
9
|
+
} from 'styled-map-package-api/utils/mapbox'
|
|
10
|
+
|
|
11
|
+
import { Writable } from 'node:stream'
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
parseBbox,
|
|
15
|
+
parseUrl,
|
|
16
|
+
parseZoom,
|
|
17
|
+
runDownload,
|
|
18
|
+
} from '../lib/commands/download.js'
|
|
19
|
+
import { ttyReporter } from '../lib/reporters.js'
|
|
10
20
|
|
|
11
21
|
const program = new Command()
|
|
12
22
|
|
|
@@ -25,152 +35,18 @@ program
|
|
|
25
35
|
)
|
|
26
36
|
.argument('[styleUrl]', 'URL to style to download', parseUrl)
|
|
27
37
|
.action(async (styleUrl, { bbox, zoom, output, token }) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
new URL(value)
|
|
40
|
-
return true
|
|
41
|
-
} catch {
|
|
42
|
-
return 'Please enter a valid URL.'
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!bbox) {
|
|
49
|
-
const west = await number({
|
|
50
|
-
message: 'Bounding box west',
|
|
51
|
-
required: true,
|
|
52
|
-
step: 'any',
|
|
53
|
-
min: -180,
|
|
54
|
-
max: 180,
|
|
55
|
-
})
|
|
56
|
-
const south = await number({
|
|
57
|
-
message: 'Bounding box south',
|
|
58
|
-
required: true,
|
|
59
|
-
step: 'any',
|
|
60
|
-
min: -90,
|
|
61
|
-
max: 90,
|
|
62
|
-
})
|
|
63
|
-
const east = await number({
|
|
64
|
-
message: 'Bounding box east',
|
|
65
|
-
required: true,
|
|
66
|
-
step: 'any',
|
|
67
|
-
min: -180,
|
|
68
|
-
max: 180,
|
|
69
|
-
})
|
|
70
|
-
const north = await number({
|
|
71
|
-
message: 'Bounding box north',
|
|
72
|
-
required: true,
|
|
73
|
-
step: 'any',
|
|
74
|
-
min: -90,
|
|
75
|
-
max: 90,
|
|
76
|
-
})
|
|
77
|
-
if (
|
|
78
|
-
west === undefined ||
|
|
79
|
-
south === undefined ||
|
|
80
|
-
east === undefined ||
|
|
81
|
-
north === undefined
|
|
82
|
-
) {
|
|
83
|
-
throw new InvalidArgumentError('Bounding box values are required.')
|
|
84
|
-
}
|
|
85
|
-
bbox = [west, south, east, north]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (zoom === undefined) {
|
|
89
|
-
zoom = await number({
|
|
90
|
-
message: 'Max zoom level to download',
|
|
91
|
-
required: true,
|
|
92
|
-
min: 0,
|
|
93
|
-
max: 22,
|
|
94
|
-
})
|
|
95
|
-
if (zoom === undefined) {
|
|
96
|
-
throw new InvalidArgumentError('Zoom level is required.')
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
(isMapboxURL(styleUrl) || styleUrl.startsWith(MAPBOX_API_URL)) &&
|
|
102
|
-
!token
|
|
103
|
-
) {
|
|
104
|
-
token = await input({
|
|
105
|
-
message: 'Mapbox access token',
|
|
106
|
-
required: true,
|
|
107
|
-
})
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (promptOutput) {
|
|
111
|
-
output = await input({
|
|
112
|
-
message: 'Output filename (.smp extension will be added)',
|
|
113
|
-
required: true,
|
|
114
|
-
transformer: (value) =>
|
|
115
|
-
value.endsWith('.smp') ? value : `${value}.smp`,
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (output && !output.endsWith('.smp')) {
|
|
120
|
-
output += '.smp'
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const reporter = ttyReporter()
|
|
124
|
-
const readStream = download({
|
|
125
|
-
bbox,
|
|
126
|
-
maxzoom: zoom,
|
|
127
|
-
styleUrl,
|
|
128
|
-
onprogress: (p) => reporter.write(p),
|
|
129
|
-
accessToken: token,
|
|
38
|
+
await runDownload({ styleUrl, bbox, zoom, output, token }, {
|
|
39
|
+
download,
|
|
40
|
+
prompt: { input, number },
|
|
41
|
+
createOutputStream: (output) =>
|
|
42
|
+
output
|
|
43
|
+
? Writable.toWeb(fs.createWriteStream(output))
|
|
44
|
+
: Writable.toWeb(process.stdout),
|
|
45
|
+
reporter: ttyReporter,
|
|
46
|
+
isMapboxURL,
|
|
47
|
+
mapboxApiUrl: MAPBOX_API_URL,
|
|
48
|
+
isTTY: !!process.stdout.isTTY,
|
|
130
49
|
})
|
|
131
|
-
const outputStream = output ? fs.createWriteStream(output) : process.stdout
|
|
132
|
-
await pipeline(readStream, outputStream)
|
|
133
50
|
})
|
|
134
51
|
|
|
135
52
|
program.parseAsync(process.argv)
|
|
136
|
-
|
|
137
|
-
/** @param {string} z */
|
|
138
|
-
function parseZoom(z) {
|
|
139
|
-
const zoom = parseInt(z)
|
|
140
|
-
if (isNaN(zoom) || zoom < 0 || zoom > 22) {
|
|
141
|
-
throw new InvalidArgumentError(
|
|
142
|
-
'Zoom must be a whole number (integer) between 0 and 22.',
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
return zoom
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** @param {string} bbox */
|
|
149
|
-
function parseBbox(bbox) {
|
|
150
|
-
const bounds = bbox.split(',').map((s) => parseFloat(s.trim()))
|
|
151
|
-
if (bounds.length !== 4) {
|
|
152
|
-
throw new InvalidArgumentError(
|
|
153
|
-
'Bounding box must have 4 values separated by commas.',
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
if (bounds.some(isNaN)) {
|
|
157
|
-
throw new InvalidArgumentError('Bounding box values must be numbers.')
|
|
158
|
-
}
|
|
159
|
-
return /** @type {[number, number, number, number]} */ (bounds)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** @param {string} url */
|
|
163
|
-
function parseUrl(url) {
|
|
164
|
-
try {
|
|
165
|
-
return new URL(url).toString()
|
|
166
|
-
} catch (e) {
|
|
167
|
-
const message =
|
|
168
|
-
e !== null &&
|
|
169
|
-
typeof e === 'object' &&
|
|
170
|
-
'message' in e &&
|
|
171
|
-
typeof e.message === 'string'
|
|
172
|
-
? e.message
|
|
173
|
-
: 'Invalid URL'
|
|
174
|
-
throw new InvalidArgumentError(message)
|
|
175
|
-
}
|
|
176
|
-
}
|
package/bin/smp-mbtiles.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander'
|
|
3
|
-
import {
|
|
3
|
+
import { fromMBTiles } from 'styled-map-package-api/from-mbtiles'
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import { Writable } from 'node:stream'
|
|
7
|
+
|
|
8
|
+
import { runMbtiles } from '../lib/commands/mbtiles.js'
|
|
6
9
|
|
|
7
10
|
const program = new Command()
|
|
8
11
|
|
|
@@ -11,11 +14,13 @@ program
|
|
|
11
14
|
.option('-o, --output <file>', 'output smp file')
|
|
12
15
|
.argument('<mbtiles>', 'MBTiles file to convert')
|
|
13
16
|
.action(async (mbtilesPath, { output }) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
await runMbtiles({ mbtilesPath, output }, {
|
|
18
|
+
fromMBTiles,
|
|
19
|
+
createOutputStream: (output) =>
|
|
20
|
+
output
|
|
21
|
+
? Writable.toWeb(fs.createWriteStream(output))
|
|
22
|
+
: Writable.toWeb(process.stdout),
|
|
23
|
+
})
|
|
19
24
|
})
|
|
20
25
|
|
|
21
26
|
program.parseAsync(process.argv)
|
package/bin/smp-view.js
CHANGED
|
@@ -1,73 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createServerAdapter } from '@whatwg-node/server'
|
|
3
3
|
import { Command } from 'commander'
|
|
4
|
-
import fsPromises from 'fs/promises'
|
|
5
4
|
import http from 'http'
|
|
6
|
-
import { AutoRouter } from 'itty-router'
|
|
7
5
|
import openApp from 'open'
|
|
6
|
+
import { Reader } from 'styled-map-package-api/reader'
|
|
7
|
+
import { createServer } from 'styled-map-package-api/server'
|
|
8
8
|
|
|
9
|
+
import fsPromises from 'node:fs/promises'
|
|
9
10
|
import path from 'node:path'
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
-
import { createServer } from '../dist/server.js'
|
|
12
|
+
import { runView } from '../lib/commands/view.js'
|
|
13
13
|
|
|
14
14
|
const program = new Command()
|
|
15
15
|
|
|
16
16
|
program
|
|
17
17
|
.description('Preview a styled map package in a web browser')
|
|
18
18
|
.option('-o, --open', 'open in the default web browser')
|
|
19
|
-
.option('-p, --port <number>', 'port to serve on', parseInt, 3000)
|
|
19
|
+
.option('-p, --port <number>', 'port to serve on', (v) => parseInt(v), 3000)
|
|
20
20
|
.argument('<file>', 'file to serve')
|
|
21
21
|
.action(async (filepath, { open, port }) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
await runView(
|
|
23
|
+
{ port, filepath: path.relative(process.cwd(), filepath), open },
|
|
24
|
+
{
|
|
25
|
+
Reader,
|
|
26
|
+
createServer,
|
|
27
|
+
openApp,
|
|
28
|
+
log: (msg) => console.log(msg),
|
|
29
|
+
readViewerHtml: (filepath) => fsPromises.readFile(filepath),
|
|
30
|
+
listen: (port, handler) =>
|
|
31
|
+
new Promise((resolve, reject) => {
|
|
32
|
+
const server = http.createServer(
|
|
33
|
+
createServerAdapter(handler),
|
|
34
|
+
)
|
|
35
|
+
server.listen(port, '127.0.0.1', () => {
|
|
36
|
+
const address = server.address()
|
|
37
|
+
if (typeof address === 'string') {
|
|
38
|
+
resolve(`http://${address}`)
|
|
39
|
+
} else if (address === null) {
|
|
40
|
+
reject(new Error('Failed to get server address'))
|
|
41
|
+
} else {
|
|
42
|
+
resolve(`http://${address.address}:${address.port}`)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
server.on('error', reject)
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
)
|
|
27
49
|
})
|
|
28
50
|
|
|
29
51
|
program.parseAsync(process.argv)
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Serve a styled map package on the given port (defaults to 3000).
|
|
33
|
-
*
|
|
34
|
-
* @param {object} opts
|
|
35
|
-
* @param {number} [opts.port]
|
|
36
|
-
* @param {string} opts.filepath
|
|
37
|
-
* @returns
|
|
38
|
-
*/
|
|
39
|
-
async function serve({ port = 3000, filepath }) {
|
|
40
|
-
const reader = new Reader(path.relative(process.cwd(), filepath))
|
|
41
|
-
const smpServer = createServer({ base: '/map' })
|
|
42
|
-
|
|
43
|
-
const router = AutoRouter()
|
|
44
|
-
router.get('/', async () => {
|
|
45
|
-
const index = await fsPromises.readFile(
|
|
46
|
-
new URL('../map-viewer/index.html', import.meta.url),
|
|
47
|
-
)
|
|
48
|
-
return new Response(new Uint8Array(index), {
|
|
49
|
-
headers: {
|
|
50
|
-
'Content-Type': 'text/html',
|
|
51
|
-
'Content-Length': String(index.byteLength),
|
|
52
|
-
'Cache-Control': 'public, max-age=0',
|
|
53
|
-
},
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
router.all('/map/*', (request) => {
|
|
57
|
-
return smpServer.fetch(request, reader)
|
|
58
|
-
})
|
|
59
|
-
const server = http.createServer(createServerAdapter(router.fetch))
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
server.listen(port, '127.0.0.1', () => {
|
|
62
|
-
const address = server.address()
|
|
63
|
-
if (typeof address === 'string') {
|
|
64
|
-
resolve(`http://${address}`)
|
|
65
|
-
} else if (address === null) {
|
|
66
|
-
reject(new Error('Failed to get server address'))
|
|
67
|
-
} else {
|
|
68
|
-
resolve(`http://${address.address}:${address.port}`)
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
server.on('error', reject)
|
|
72
|
-
})
|
|
73
|
-
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { InvalidArgumentError } from 'commander'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} z
|
|
5
|
+
* @returns {number}
|
|
6
|
+
*/
|
|
7
|
+
export function parseZoom(z) {
|
|
8
|
+
const zoom = parseInt(z)
|
|
9
|
+
if (isNaN(zoom) || zoom < 0 || zoom > 22) {
|
|
10
|
+
throw new InvalidArgumentError(
|
|
11
|
+
'Zoom must be a whole number (integer) between 0 and 22.',
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
return zoom
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} bbox
|
|
19
|
+
* @returns {[number, number, number, number]}
|
|
20
|
+
*/
|
|
21
|
+
export function parseBbox(bbox) {
|
|
22
|
+
const bounds = bbox.split(',').map((s) => parseFloat(s.trim()))
|
|
23
|
+
if (bounds.length !== 4) {
|
|
24
|
+
throw new InvalidArgumentError(
|
|
25
|
+
'Bounding box must have 4 values separated by commas.',
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
if (bounds.some(isNaN)) {
|
|
29
|
+
throw new InvalidArgumentError('Bounding box values must be numbers.')
|
|
30
|
+
}
|
|
31
|
+
return /** @type {[number, number, number, number]} */ (bounds)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} url
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
export function parseUrl(url) {
|
|
39
|
+
try {
|
|
40
|
+
return new URL(url).toString()
|
|
41
|
+
} catch (e) {
|
|
42
|
+
const message =
|
|
43
|
+
e !== null &&
|
|
44
|
+
typeof e === 'object' &&
|
|
45
|
+
'message' in e &&
|
|
46
|
+
typeof e.message === 'string'
|
|
47
|
+
? e.message
|
|
48
|
+
: 'Invalid URL'
|
|
49
|
+
throw new InvalidArgumentError(message)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {object} DownloadOptions
|
|
55
|
+
* @property {string | undefined} styleUrl
|
|
56
|
+
* @property {[number, number, number, number] | undefined} bbox
|
|
57
|
+
* @property {number | undefined} zoom
|
|
58
|
+
* @property {string | undefined} output
|
|
59
|
+
* @property {string | undefined} token
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {object} DownloadDeps
|
|
64
|
+
* @property {(opts: any) => ReadableStream} download
|
|
65
|
+
* @property {{ input: (opts: any) => Promise<string>, number: (opts: any) => Promise<number | undefined> }} prompt
|
|
66
|
+
* @property {(output: string | undefined) => WritableStream | import('node:fs').WriteStream} createOutputStream
|
|
67
|
+
* @property {() => { write: (p: any) => void }} reporter
|
|
68
|
+
* @property {(url: string) => boolean} isMapboxURL
|
|
69
|
+
* @property {string} mapboxApiUrl
|
|
70
|
+
* @property {boolean} isTTY
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {DownloadOptions} options
|
|
75
|
+
* @param {DownloadDeps} deps
|
|
76
|
+
*/
|
|
77
|
+
export async function runDownload(
|
|
78
|
+
{ styleUrl, bbox, zoom, output, token },
|
|
79
|
+
deps,
|
|
80
|
+
) {
|
|
81
|
+
const { download, prompt, isMapboxURL, mapboxApiUrl, isTTY } = deps
|
|
82
|
+
|
|
83
|
+
const promptOutput =
|
|
84
|
+
!output && isTTY && (!styleUrl || !bbox || zoom === undefined)
|
|
85
|
+
|
|
86
|
+
if (!styleUrl) {
|
|
87
|
+
styleUrl = await prompt.input({
|
|
88
|
+
message: 'Style URL to download',
|
|
89
|
+
required: true,
|
|
90
|
+
validate: (/** @type {string} */ value) => {
|
|
91
|
+
try {
|
|
92
|
+
new URL(value)
|
|
93
|
+
return true
|
|
94
|
+
} catch {
|
|
95
|
+
return 'Please enter a valid URL.'
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!bbox) {
|
|
102
|
+
const west = await prompt.number({
|
|
103
|
+
message: 'Bounding box west',
|
|
104
|
+
required: true,
|
|
105
|
+
step: 'any',
|
|
106
|
+
min: -180,
|
|
107
|
+
max: 180,
|
|
108
|
+
})
|
|
109
|
+
const south = await prompt.number({
|
|
110
|
+
message: 'Bounding box south',
|
|
111
|
+
required: true,
|
|
112
|
+
step: 'any',
|
|
113
|
+
min: -90,
|
|
114
|
+
max: 90,
|
|
115
|
+
})
|
|
116
|
+
const east = await prompt.number({
|
|
117
|
+
message: 'Bounding box east',
|
|
118
|
+
required: true,
|
|
119
|
+
step: 'any',
|
|
120
|
+
min: -180,
|
|
121
|
+
max: 180,
|
|
122
|
+
})
|
|
123
|
+
const north = await prompt.number({
|
|
124
|
+
message: 'Bounding box north',
|
|
125
|
+
required: true,
|
|
126
|
+
step: 'any',
|
|
127
|
+
min: -90,
|
|
128
|
+
max: 90,
|
|
129
|
+
})
|
|
130
|
+
if (
|
|
131
|
+
west === undefined ||
|
|
132
|
+
south === undefined ||
|
|
133
|
+
east === undefined ||
|
|
134
|
+
north === undefined
|
|
135
|
+
) {
|
|
136
|
+
throw new InvalidArgumentError('Bounding box values are required.')
|
|
137
|
+
}
|
|
138
|
+
bbox = [west, south, east, north]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (zoom === undefined) {
|
|
142
|
+
zoom = await prompt.number({
|
|
143
|
+
message: 'Max zoom level to download',
|
|
144
|
+
required: true,
|
|
145
|
+
min: 0,
|
|
146
|
+
max: 22,
|
|
147
|
+
})
|
|
148
|
+
if (zoom === undefined) {
|
|
149
|
+
throw new InvalidArgumentError('Zoom level is required.')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if ((isMapboxURL(styleUrl) || styleUrl.startsWith(mapboxApiUrl)) && !token) {
|
|
154
|
+
token = await prompt.input({
|
|
155
|
+
message: 'Mapbox access token',
|
|
156
|
+
required: true,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (promptOutput) {
|
|
161
|
+
output = await prompt.input({
|
|
162
|
+
message: 'Output filename (.smp extension will be added)',
|
|
163
|
+
required: true,
|
|
164
|
+
transformer: (/** @type {string} */ value) =>
|
|
165
|
+
value.endsWith('.smp') ? value : `${value}.smp`,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (output && !output.endsWith('.smp')) {
|
|
170
|
+
output += '.smp'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const reporter = deps.reporter()
|
|
174
|
+
const readStream = download({
|
|
175
|
+
bbox,
|
|
176
|
+
maxzoom: zoom,
|
|
177
|
+
styleUrl,
|
|
178
|
+
onprogress: (/** @type {any} */ p) => reporter.write(p),
|
|
179
|
+
accessToken: token,
|
|
180
|
+
})
|
|
181
|
+
const outputStream = deps.createOutputStream(output)
|
|
182
|
+
await readStream.pipeTo(
|
|
183
|
+
outputStream instanceof WritableStream
|
|
184
|
+
? outputStream
|
|
185
|
+
: // @ts-ignore - Writable.toWeb compatibility
|
|
186
|
+
outputStream,
|
|
187
|
+
)
|
|
188
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} MbtilesOptions
|
|
3
|
+
* @property {string} mbtilesPath
|
|
4
|
+
* @property {string | undefined} output
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} MbtilesDeps
|
|
9
|
+
* @property {(path: string) => ReadableStream} fromMBTiles
|
|
10
|
+
* @property {(output: string | undefined) => WritableStream} createOutputStream
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {MbtilesOptions} options
|
|
15
|
+
* @param {MbtilesDeps} deps
|
|
16
|
+
*/
|
|
17
|
+
export async function runMbtiles({ mbtilesPath, output }, deps) {
|
|
18
|
+
const readStream = deps.fromMBTiles(mbtilesPath)
|
|
19
|
+
const outputStream = deps.createOutputStream(output)
|
|
20
|
+
await readStream.pipeTo(outputStream)
|
|
21
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} ViewOptions
|
|
3
|
+
* @property {number} port
|
|
4
|
+
* @property {string} filepath
|
|
5
|
+
* @property {boolean} [open]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} ViewDeps
|
|
10
|
+
* @property {new (filepath: string) => any} Reader
|
|
11
|
+
* @property {(opts: any) => { fetch: (req: any, reader: any) => any }} createServer
|
|
12
|
+
* @property {(port: number, handler: any) => Promise<import('node:http').Server>} listen
|
|
13
|
+
* @property {(url: string) => Promise<void>} openApp
|
|
14
|
+
* @property {(url: string) => void} log
|
|
15
|
+
* @property {(url: string) => Promise<Uint8Array>} readViewerHtml
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {ViewOptions} options
|
|
20
|
+
* @param {ViewDeps} deps
|
|
21
|
+
* @returns {Promise<string>} The address the server is listening on
|
|
22
|
+
*/
|
|
23
|
+
export async function runView({ port, filepath, open }, deps) {
|
|
24
|
+
const { Reader, createServer, openApp, log, readViewerHtml } = deps
|
|
25
|
+
|
|
26
|
+
const reader = new Reader(filepath)
|
|
27
|
+
const smpServer = createServer({ base: '/map' })
|
|
28
|
+
|
|
29
|
+
/** @param {Request} request */
|
|
30
|
+
const handler = async (request) => {
|
|
31
|
+
const url = new URL(request.url)
|
|
32
|
+
if (url.pathname === '/') {
|
|
33
|
+
const index = await readViewerHtml(
|
|
34
|
+
new URL('../../map-viewer/index.html', import.meta.url).pathname,
|
|
35
|
+
)
|
|
36
|
+
return new Response(index, {
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'text/html',
|
|
39
|
+
'Content-Length': String(index.byteLength),
|
|
40
|
+
'Cache-Control': 'public, max-age=0',
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
if (url.pathname.startsWith('/map/')) {
|
|
45
|
+
return smpServer.fetch(request, reader)
|
|
46
|
+
}
|
|
47
|
+
return new Response('Not found', { status: 404 })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const address = await deps.listen(port, handler)
|
|
51
|
+
|
|
52
|
+
log(`server listening on ${address}`)
|
|
53
|
+
if (open) {
|
|
54
|
+
await openApp(address)
|
|
55
|
+
}
|
|
56
|
+
return address
|
|
57
|
+
}
|