styled-map-package-api 5.0.0-pre.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/dist/download.cjs +100 -0
- package/dist/download.d.cts +64 -0
- package/dist/download.d.ts +64 -0
- package/dist/download.js +76 -0
- package/dist/from-mbtiles.cjs +81 -0
- package/dist/from-mbtiles.d.cts +10 -0
- package/dist/from-mbtiles.d.ts +10 -0
- package/dist/from-mbtiles.js +57 -0
- package/dist/index.cjs +46 -0
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +16 -0
- package/dist/reader.cjs +287 -0
- package/dist/reader.d.cts +68 -0
- package/dist/reader.d.ts +68 -0
- package/dist/reader.js +259 -0
- package/dist/server.cjs +73 -0
- package/dist/server.d.cts +46 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.js +49 -0
- package/dist/style-downloader.cjs +314 -0
- package/dist/style-downloader.d.cts +119 -0
- package/dist/style-downloader.d.ts +119 -0
- package/dist/style-downloader.js +290 -0
- package/dist/tile-downloader.cjs +156 -0
- package/dist/tile-downloader.d.cts +83 -0
- package/dist/tile-downloader.d.ts +83 -0
- package/dist/tile-downloader.js +124 -0
- package/dist/types-CJq90eOB.d.cts +184 -0
- package/dist/types-CJq90eOB.d.ts +184 -0
- package/dist/utils/errors.cjs +41 -0
- package/dist/utils/errors.d.cts +18 -0
- package/dist/utils/errors.d.ts +18 -0
- package/dist/utils/errors.js +16 -0
- package/dist/utils/fetch.cjs +97 -0
- package/dist/utils/fetch.d.cts +50 -0
- package/dist/utils/fetch.d.ts +50 -0
- package/dist/utils/fetch.js +63 -0
- package/dist/utils/file-formats.cjs +96 -0
- package/dist/utils/file-formats.d.cts +33 -0
- package/dist/utils/file-formats.d.ts +33 -0
- package/dist/utils/file-formats.js +70 -0
- package/dist/utils/geo.cjs +84 -0
- package/dist/utils/geo.d.cts +46 -0
- package/dist/utils/geo.d.ts +46 -0
- 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/dist/utils/misc.d.cts +22 -0
- package/dist/utils/misc.d.ts +22 -0
- package/dist/utils/misc.js +13 -0
- package/dist/utils/streams.cjs +99 -0
- package/dist/utils/streams.d.cts +49 -0
- package/dist/utils/streams.d.ts +49 -0
- package/dist/utils/streams.js +73 -0
- package/dist/utils/style.cjs +126 -0
- package/dist/utils/style.d.cts +67 -0
- package/dist/utils/style.d.ts +67 -0
- package/dist/utils/style.js +98 -0
- package/dist/utils/templates.cjs +124 -0
- package/dist/utils/templates.d.cts +80 -0
- package/dist/utils/templates.d.ts +80 -0
- package/dist/utils/templates.js +85 -0
- package/dist/writer.cjs +465 -0
- package/dist/writer.d.cts +5 -0
- package/dist/writer.d.ts +5 -0
- package/dist/writer.js +452 -0
- package/package.json +161 -0
package/dist/reader.cjs
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var reader_exports = {};
|
|
30
|
+
__export(reader_exports, {
|
|
31
|
+
Reader: () => Reader
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(reader_exports);
|
|
34
|
+
var import_zip_reader = require("@gmaclennan/zip-reader");
|
|
35
|
+
var import_errors = require('./utils/errors.cjs');
|
|
36
|
+
var import_misc = require('./utils/misc.cjs');
|
|
37
|
+
var import_style = require('./utils/style.cjs');
|
|
38
|
+
var import_templates = require('./utils/templates.cjs');
|
|
39
|
+
function defer() {
|
|
40
|
+
let resolve;
|
|
41
|
+
let reject;
|
|
42
|
+
const promise = new Promise((res, rej) => {
|
|
43
|
+
resolve = res;
|
|
44
|
+
reject = rej;
|
|
45
|
+
});
|
|
46
|
+
return { promise, resolve, reject };
|
|
47
|
+
}
|
|
48
|
+
class Entries {
|
|
49
|
+
/** @type {Map<string, import('@gmaclennan/zip-reader').ZipEntry>} */
|
|
50
|
+
#entries = /* @__PURE__ */ new Map();
|
|
51
|
+
/** @type {Map<string, ReturnType<typeof defer<import('@gmaclennan/zip-reader').ZipEntry | undefined>>>} */
|
|
52
|
+
#deferredEntries = /* @__PURE__ */ new Map();
|
|
53
|
+
#readyPromise;
|
|
54
|
+
#ready = false;
|
|
55
|
+
#closing = false;
|
|
56
|
+
/** @type {unknown} */
|
|
57
|
+
#error;
|
|
58
|
+
/** @param {Promise<import('@gmaclennan/zip-reader').ZipReader>} zipPromise */
|
|
59
|
+
constructor(zipPromise) {
|
|
60
|
+
this.#readyPromise = (async () => {
|
|
61
|
+
try {
|
|
62
|
+
if (this.#closing) return;
|
|
63
|
+
const zip = await zipPromise;
|
|
64
|
+
if (this.#closing) return;
|
|
65
|
+
for await (const entry of zip) {
|
|
66
|
+
if (this.#closing) return;
|
|
67
|
+
this.#entries.set(entry.name, entry);
|
|
68
|
+
this.#deferredEntries.get(entry.name)?.resolve(entry);
|
|
69
|
+
this.#deferredEntries.delete(entry.name);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
this.#error = error;
|
|
73
|
+
for (const deferred of this.#deferredEntries.values()) {
|
|
74
|
+
deferred.reject(error);
|
|
75
|
+
}
|
|
76
|
+
this.#deferredEntries.clear();
|
|
77
|
+
throw error;
|
|
78
|
+
} finally {
|
|
79
|
+
this.#ready = true;
|
|
80
|
+
for (const deferred of this.#deferredEntries.values()) {
|
|
81
|
+
deferred.resolve(void 0);
|
|
82
|
+
}
|
|
83
|
+
this.#deferredEntries.clear();
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
this.#readyPromise.catch(import_misc.noop);
|
|
87
|
+
}
|
|
88
|
+
async ready() {
|
|
89
|
+
await this.#readyPromise;
|
|
90
|
+
}
|
|
91
|
+
/** @param {string} path */
|
|
92
|
+
async get(path) {
|
|
93
|
+
if (this.#entries.has(path)) {
|
|
94
|
+
return this.#entries.get(path);
|
|
95
|
+
}
|
|
96
|
+
if (this.#ready || this.#closing) {
|
|
97
|
+
if (this.#error) throw this.#error;
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
const existingDeferred = this.#deferredEntries.get(path);
|
|
101
|
+
if (existingDeferred) {
|
|
102
|
+
return existingDeferred.promise;
|
|
103
|
+
}
|
|
104
|
+
const deferred = defer();
|
|
105
|
+
this.#deferredEntries.set(path, deferred);
|
|
106
|
+
return deferred.promise;
|
|
107
|
+
}
|
|
108
|
+
async close() {
|
|
109
|
+
this.#closing = true;
|
|
110
|
+
await this.#readyPromise;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function streamToText(readable) {
|
|
114
|
+
const chunks = (
|
|
115
|
+
/** @type {Uint8Array[]} */
|
|
116
|
+
[]
|
|
117
|
+
);
|
|
118
|
+
const reader = readable.getReader();
|
|
119
|
+
try {
|
|
120
|
+
while (true) {
|
|
121
|
+
const { done, value } = await reader.read();
|
|
122
|
+
if (done) break;
|
|
123
|
+
chunks.push(value instanceof Uint8Array ? value : new Uint8Array(value));
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
reader.releaseLock();
|
|
127
|
+
}
|
|
128
|
+
const totalLen = chunks.reduce((s, c) => s + c.byteLength, 0);
|
|
129
|
+
const buf = new Uint8Array(totalLen);
|
|
130
|
+
let off = 0;
|
|
131
|
+
for (const chunk of chunks) {
|
|
132
|
+
buf.set(chunk, off);
|
|
133
|
+
off += chunk.byteLength;
|
|
134
|
+
}
|
|
135
|
+
return new TextDecoder().decode(buf);
|
|
136
|
+
}
|
|
137
|
+
async function streamToJson(readable) {
|
|
138
|
+
return JSON.parse(await streamToText(readable));
|
|
139
|
+
}
|
|
140
|
+
class Reader {
|
|
141
|
+
#entries;
|
|
142
|
+
/** @type {undefined | Promise<void>} */
|
|
143
|
+
#closePromise;
|
|
144
|
+
/** @type {import('@gmaclennan/zip-reader/file-source').FileSource | null} */
|
|
145
|
+
#fileSource = null;
|
|
146
|
+
/**
|
|
147
|
+
* @param {string | import('@gmaclennan/zip-reader').ZipReader} filepathOrZip Path to styled map package (`.styledmap`) file, or a ZipReader instance
|
|
148
|
+
*/
|
|
149
|
+
constructor(filepathOrZip) {
|
|
150
|
+
let zipPromise;
|
|
151
|
+
if (typeof filepathOrZip === "string") {
|
|
152
|
+
const sourcePromise = import("@gmaclennan/zip-reader/file-source").then(
|
|
153
|
+
({ FileSource }) => FileSource.open(filepathOrZip)
|
|
154
|
+
);
|
|
155
|
+
sourcePromise.catch(import_misc.noop);
|
|
156
|
+
zipPromise = sourcePromise.then((source) => {
|
|
157
|
+
this.#fileSource = source;
|
|
158
|
+
return import_zip_reader.ZipReader.from(source);
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
zipPromise = Promise.resolve(filepathOrZip);
|
|
162
|
+
}
|
|
163
|
+
zipPromise.catch(import_misc.noop);
|
|
164
|
+
this.#entries = new Entries(zipPromise);
|
|
165
|
+
this.#entries.ready().catch(() => this.close());
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Resolves when the styled map package has been opened and the entries have
|
|
169
|
+
* been read. Throws any error that occurred during opening.
|
|
170
|
+
*/
|
|
171
|
+
async opened() {
|
|
172
|
+
await this.#entries.ready();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the format version from the VERSION file in the styled map package.
|
|
176
|
+
* Returns "1.0" if no VERSION file exists (older SMP files did not have a
|
|
177
|
+
* VERSION file, so we assume version 1.0).
|
|
178
|
+
*
|
|
179
|
+
* @returns {Promise<string>}
|
|
180
|
+
*/
|
|
181
|
+
async getVersion() {
|
|
182
|
+
const versionEntry = await this.#entries.get(import_templates.VERSION_FILE);
|
|
183
|
+
if (!versionEntry) return "1.0";
|
|
184
|
+
return (await streamToText(versionEntry.readable())).trim();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get the style JSON from the styled map package. The URLs in the style JSON
|
|
188
|
+
* will be transformed to use the provided base URL.
|
|
189
|
+
*
|
|
190
|
+
* @param {string | null} [baseUrl] Base URL where you plan to serve the resources in this styled map package, e.g. `http://localhost:3000/maps/styleA`
|
|
191
|
+
* @returns {Promise<import('./types.js').SMPStyle>}
|
|
192
|
+
*/
|
|
193
|
+
async getStyle(baseUrl = null) {
|
|
194
|
+
const styleEntry = await this.#entries.get(import_templates.STYLE_FILE);
|
|
195
|
+
if (!styleEntry) throw new import_errors.ENOENT(import_templates.STYLE_FILE);
|
|
196
|
+
const style = await streamToJson(styleEntry.readable());
|
|
197
|
+
if (!(0, import_style.validateStyle)(style)) {
|
|
198
|
+
throw new AggregateError(import_style.validateStyle.errors, "Invalid style");
|
|
199
|
+
}
|
|
200
|
+
if (typeof style.glyphs === "string") {
|
|
201
|
+
style.glyphs = getUrl(style.glyphs, baseUrl);
|
|
202
|
+
}
|
|
203
|
+
if (typeof style.sprite === "string") {
|
|
204
|
+
style.sprite = getUrl(style.sprite, baseUrl);
|
|
205
|
+
} else if (Array.isArray(style.sprite)) {
|
|
206
|
+
style.sprite = style.sprite.map(({ id, url }) => {
|
|
207
|
+
return { id, url: getUrl(url, baseUrl) };
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
for (const source of Object.values(style.sources)) {
|
|
211
|
+
if ("tiles" in source && source.tiles) {
|
|
212
|
+
source.tiles = source.tiles.map((tile) => getUrl(tile, baseUrl));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return (
|
|
216
|
+
/** @type {import('./types.js').SMPStyle} */
|
|
217
|
+
style
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get a resource from the styled map package. The path should be relative to
|
|
222
|
+
* the root of the package.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} path
|
|
225
|
+
* @returns {Promise<Resource>}
|
|
226
|
+
*/
|
|
227
|
+
async getResource(path) {
|
|
228
|
+
if (path[0] === "/") path = path.slice(1);
|
|
229
|
+
if (path === import_templates.STYLE_FILE) {
|
|
230
|
+
const styleJSON = JSON.stringify(await this.getStyle());
|
|
231
|
+
const bytes = new TextEncoder().encode(styleJSON);
|
|
232
|
+
return {
|
|
233
|
+
contentType: "application/json; charset=utf-8",
|
|
234
|
+
contentLength: bytes.byteLength,
|
|
235
|
+
resourceType: "style",
|
|
236
|
+
stream: new ReadableStream({
|
|
237
|
+
start(controller) {
|
|
238
|
+
controller.enqueue(bytes);
|
|
239
|
+
controller.close();
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const entry = await this.#entries.get(path);
|
|
245
|
+
if (!entry) throw new import_errors.ENOENT(path);
|
|
246
|
+
const resourceType = (0, import_templates.getResourceType)(path);
|
|
247
|
+
const contentType = (0, import_templates.getContentType)(path);
|
|
248
|
+
const stream = entry.readable();
|
|
249
|
+
const resource = {
|
|
250
|
+
resourceType,
|
|
251
|
+
contentType,
|
|
252
|
+
contentLength: entry.uncompressedSize,
|
|
253
|
+
stream
|
|
254
|
+
};
|
|
255
|
+
if (path.endsWith(".gz")) {
|
|
256
|
+
resource.contentEncoding = "gzip";
|
|
257
|
+
}
|
|
258
|
+
return resource;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Close the styled map package file (should be called after reading the file to avoid memory leaks)
|
|
262
|
+
*/
|
|
263
|
+
async close() {
|
|
264
|
+
if (this.#closePromise) return this.#closePromise;
|
|
265
|
+
this.#closePromise = (async () => {
|
|
266
|
+
await this.#entries.close().catch(import_misc.noop);
|
|
267
|
+
if (this.#fileSource) {
|
|
268
|
+
await this.#fileSource.close().catch(import_misc.noop);
|
|
269
|
+
}
|
|
270
|
+
})();
|
|
271
|
+
return this.#closePromise;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function getUrl(smpUri, baseUrl) {
|
|
275
|
+
if (!smpUri.startsWith(import_templates.URI_BASE)) {
|
|
276
|
+
throw new Error(`Invalid SMP URI: ${smpUri}`);
|
|
277
|
+
}
|
|
278
|
+
if (typeof baseUrl !== "string") return smpUri;
|
|
279
|
+
if (!baseUrl.endsWith("/")) {
|
|
280
|
+
baseUrl += "/";
|
|
281
|
+
}
|
|
282
|
+
return smpUri.replace(import_templates.URI_BASE, baseUrl);
|
|
283
|
+
}
|
|
284
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
285
|
+
0 && (module.exports = {
|
|
286
|
+
Reader
|
|
287
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { a as SMPStyle } from './types-CJq90eOB.cjs';
|
|
2
|
+
import * as _gmaclennan_zip_reader from '@gmaclennan/zip-reader';
|
|
3
|
+
import '@maplibre/maplibre-gl-style-spec';
|
|
4
|
+
import 'geojson';
|
|
5
|
+
import 'type-fest';
|
|
6
|
+
import 'events';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} Resource
|
|
10
|
+
* @property {string} resourceType
|
|
11
|
+
* @property {string} contentType
|
|
12
|
+
* @property {number} contentLength
|
|
13
|
+
* @property {ReadableStream<Uint8Array>} stream
|
|
14
|
+
* @property {'gzip'} [contentEncoding]
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* A low-level reader for styled map packages. Returns resources in the package
|
|
18
|
+
* as readable streams, for serving over HTTP for example.
|
|
19
|
+
*/
|
|
20
|
+
declare class Reader {
|
|
21
|
+
/**
|
|
22
|
+
* @param {string | import('@gmaclennan/zip-reader').ZipReader} filepathOrZip Path to styled map package (`.styledmap`) file, or a ZipReader instance
|
|
23
|
+
*/
|
|
24
|
+
constructor(filepathOrZip: string | _gmaclennan_zip_reader.ZipReader);
|
|
25
|
+
/**
|
|
26
|
+
* Resolves when the styled map package has been opened and the entries have
|
|
27
|
+
* been read. Throws any error that occurred during opening.
|
|
28
|
+
*/
|
|
29
|
+
opened(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the format version from the VERSION file in the styled map package.
|
|
32
|
+
* Returns "1.0" if no VERSION file exists (older SMP files did not have a
|
|
33
|
+
* VERSION file, so we assume version 1.0).
|
|
34
|
+
*
|
|
35
|
+
* @returns {Promise<string>}
|
|
36
|
+
*/
|
|
37
|
+
getVersion(): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Get the style JSON from the styled map package. The URLs in the style JSON
|
|
40
|
+
* will be transformed to use the provided base URL.
|
|
41
|
+
*
|
|
42
|
+
* @param {string | null} [baseUrl] Base URL where you plan to serve the resources in this styled map package, e.g. `http://localhost:3000/maps/styleA`
|
|
43
|
+
* @returns {Promise<import('./types.js').SMPStyle>}
|
|
44
|
+
*/
|
|
45
|
+
getStyle(baseUrl?: string | null): Promise<SMPStyle>;
|
|
46
|
+
/**
|
|
47
|
+
* Get a resource from the styled map package. The path should be relative to
|
|
48
|
+
* the root of the package.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} path
|
|
51
|
+
* @returns {Promise<Resource>}
|
|
52
|
+
*/
|
|
53
|
+
getResource(path: string): Promise<Resource>;
|
|
54
|
+
/**
|
|
55
|
+
* Close the styled map package file (should be called after reading the file to avoid memory leaks)
|
|
56
|
+
*/
|
|
57
|
+
close(): Promise<void>;
|
|
58
|
+
#private;
|
|
59
|
+
}
|
|
60
|
+
type Resource = {
|
|
61
|
+
resourceType: string;
|
|
62
|
+
contentType: string;
|
|
63
|
+
contentLength: number;
|
|
64
|
+
stream: ReadableStream<Uint8Array>;
|
|
65
|
+
contentEncoding?: "gzip" | undefined;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export { Reader, type Resource };
|
package/dist/reader.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { a as SMPStyle } from './types-CJq90eOB.js';
|
|
2
|
+
import * as _gmaclennan_zip_reader from '@gmaclennan/zip-reader';
|
|
3
|
+
import '@maplibre/maplibre-gl-style-spec';
|
|
4
|
+
import 'geojson';
|
|
5
|
+
import 'type-fest';
|
|
6
|
+
import 'events';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} Resource
|
|
10
|
+
* @property {string} resourceType
|
|
11
|
+
* @property {string} contentType
|
|
12
|
+
* @property {number} contentLength
|
|
13
|
+
* @property {ReadableStream<Uint8Array>} stream
|
|
14
|
+
* @property {'gzip'} [contentEncoding]
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* A low-level reader for styled map packages. Returns resources in the package
|
|
18
|
+
* as readable streams, for serving over HTTP for example.
|
|
19
|
+
*/
|
|
20
|
+
declare class Reader {
|
|
21
|
+
/**
|
|
22
|
+
* @param {string | import('@gmaclennan/zip-reader').ZipReader} filepathOrZip Path to styled map package (`.styledmap`) file, or a ZipReader instance
|
|
23
|
+
*/
|
|
24
|
+
constructor(filepathOrZip: string | _gmaclennan_zip_reader.ZipReader);
|
|
25
|
+
/**
|
|
26
|
+
* Resolves when the styled map package has been opened and the entries have
|
|
27
|
+
* been read. Throws any error that occurred during opening.
|
|
28
|
+
*/
|
|
29
|
+
opened(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the format version from the VERSION file in the styled map package.
|
|
32
|
+
* Returns "1.0" if no VERSION file exists (older SMP files did not have a
|
|
33
|
+
* VERSION file, so we assume version 1.0).
|
|
34
|
+
*
|
|
35
|
+
* @returns {Promise<string>}
|
|
36
|
+
*/
|
|
37
|
+
getVersion(): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Get the style JSON from the styled map package. The URLs in the style JSON
|
|
40
|
+
* will be transformed to use the provided base URL.
|
|
41
|
+
*
|
|
42
|
+
* @param {string | null} [baseUrl] Base URL where you plan to serve the resources in this styled map package, e.g. `http://localhost:3000/maps/styleA`
|
|
43
|
+
* @returns {Promise<import('./types.js').SMPStyle>}
|
|
44
|
+
*/
|
|
45
|
+
getStyle(baseUrl?: string | null): Promise<SMPStyle>;
|
|
46
|
+
/**
|
|
47
|
+
* Get a resource from the styled map package. The path should be relative to
|
|
48
|
+
* the root of the package.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} path
|
|
51
|
+
* @returns {Promise<Resource>}
|
|
52
|
+
*/
|
|
53
|
+
getResource(path: string): Promise<Resource>;
|
|
54
|
+
/**
|
|
55
|
+
* Close the styled map package file (should be called after reading the file to avoid memory leaks)
|
|
56
|
+
*/
|
|
57
|
+
close(): Promise<void>;
|
|
58
|
+
#private;
|
|
59
|
+
}
|
|
60
|
+
type Resource = {
|
|
61
|
+
resourceType: string;
|
|
62
|
+
contentType: string;
|
|
63
|
+
contentLength: number;
|
|
64
|
+
stream: ReadableStream<Uint8Array>;
|
|
65
|
+
contentEncoding?: "gzip" | undefined;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export { Reader, type Resource };
|
package/dist/reader.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { ZipReader } from "@gmaclennan/zip-reader";
|
|
2
|
+
import { ENOENT } from "./utils/errors.js";
|
|
3
|
+
import { noop } from "./utils/misc.js";
|
|
4
|
+
import { validateStyle } from "./utils/style.js";
|
|
5
|
+
import {
|
|
6
|
+
getContentType,
|
|
7
|
+
getResourceType,
|
|
8
|
+
STYLE_FILE,
|
|
9
|
+
URI_BASE,
|
|
10
|
+
VERSION_FILE
|
|
11
|
+
} from "./utils/templates.js";
|
|
12
|
+
function defer() {
|
|
13
|
+
let resolve;
|
|
14
|
+
let reject;
|
|
15
|
+
const promise = new Promise((res, rej) => {
|
|
16
|
+
resolve = res;
|
|
17
|
+
reject = rej;
|
|
18
|
+
});
|
|
19
|
+
return { promise, resolve, reject };
|
|
20
|
+
}
|
|
21
|
+
class Entries {
|
|
22
|
+
/** @type {Map<string, import('@gmaclennan/zip-reader').ZipEntry>} */
|
|
23
|
+
#entries = /* @__PURE__ */ new Map();
|
|
24
|
+
/** @type {Map<string, ReturnType<typeof defer<import('@gmaclennan/zip-reader').ZipEntry | undefined>>>} */
|
|
25
|
+
#deferredEntries = /* @__PURE__ */ new Map();
|
|
26
|
+
#readyPromise;
|
|
27
|
+
#ready = false;
|
|
28
|
+
#closing = false;
|
|
29
|
+
/** @type {unknown} */
|
|
30
|
+
#error;
|
|
31
|
+
/** @param {Promise<import('@gmaclennan/zip-reader').ZipReader>} zipPromise */
|
|
32
|
+
constructor(zipPromise) {
|
|
33
|
+
this.#readyPromise = (async () => {
|
|
34
|
+
try {
|
|
35
|
+
if (this.#closing) return;
|
|
36
|
+
const zip = await zipPromise;
|
|
37
|
+
if (this.#closing) return;
|
|
38
|
+
for await (const entry of zip) {
|
|
39
|
+
if (this.#closing) return;
|
|
40
|
+
this.#entries.set(entry.name, entry);
|
|
41
|
+
this.#deferredEntries.get(entry.name)?.resolve(entry);
|
|
42
|
+
this.#deferredEntries.delete(entry.name);
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
this.#error = error;
|
|
46
|
+
for (const deferred of this.#deferredEntries.values()) {
|
|
47
|
+
deferred.reject(error);
|
|
48
|
+
}
|
|
49
|
+
this.#deferredEntries.clear();
|
|
50
|
+
throw error;
|
|
51
|
+
} finally {
|
|
52
|
+
this.#ready = true;
|
|
53
|
+
for (const deferred of this.#deferredEntries.values()) {
|
|
54
|
+
deferred.resolve(void 0);
|
|
55
|
+
}
|
|
56
|
+
this.#deferredEntries.clear();
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
this.#readyPromise.catch(noop);
|
|
60
|
+
}
|
|
61
|
+
async ready() {
|
|
62
|
+
await this.#readyPromise;
|
|
63
|
+
}
|
|
64
|
+
/** @param {string} path */
|
|
65
|
+
async get(path) {
|
|
66
|
+
if (this.#entries.has(path)) {
|
|
67
|
+
return this.#entries.get(path);
|
|
68
|
+
}
|
|
69
|
+
if (this.#ready || this.#closing) {
|
|
70
|
+
if (this.#error) throw this.#error;
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
const existingDeferred = this.#deferredEntries.get(path);
|
|
74
|
+
if (existingDeferred) {
|
|
75
|
+
return existingDeferred.promise;
|
|
76
|
+
}
|
|
77
|
+
const deferred = defer();
|
|
78
|
+
this.#deferredEntries.set(path, deferred);
|
|
79
|
+
return deferred.promise;
|
|
80
|
+
}
|
|
81
|
+
async close() {
|
|
82
|
+
this.#closing = true;
|
|
83
|
+
await this.#readyPromise;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function streamToText(readable) {
|
|
87
|
+
const chunks = (
|
|
88
|
+
/** @type {Uint8Array[]} */
|
|
89
|
+
[]
|
|
90
|
+
);
|
|
91
|
+
const reader = readable.getReader();
|
|
92
|
+
try {
|
|
93
|
+
while (true) {
|
|
94
|
+
const { done, value } = await reader.read();
|
|
95
|
+
if (done) break;
|
|
96
|
+
chunks.push(value instanceof Uint8Array ? value : new Uint8Array(value));
|
|
97
|
+
}
|
|
98
|
+
} finally {
|
|
99
|
+
reader.releaseLock();
|
|
100
|
+
}
|
|
101
|
+
const totalLen = chunks.reduce((s, c) => s + c.byteLength, 0);
|
|
102
|
+
const buf = new Uint8Array(totalLen);
|
|
103
|
+
let off = 0;
|
|
104
|
+
for (const chunk of chunks) {
|
|
105
|
+
buf.set(chunk, off);
|
|
106
|
+
off += chunk.byteLength;
|
|
107
|
+
}
|
|
108
|
+
return new TextDecoder().decode(buf);
|
|
109
|
+
}
|
|
110
|
+
async function streamToJson(readable) {
|
|
111
|
+
return JSON.parse(await streamToText(readable));
|
|
112
|
+
}
|
|
113
|
+
class Reader {
|
|
114
|
+
#entries;
|
|
115
|
+
/** @type {undefined | Promise<void>} */
|
|
116
|
+
#closePromise;
|
|
117
|
+
/** @type {import('@gmaclennan/zip-reader/file-source').FileSource | null} */
|
|
118
|
+
#fileSource = null;
|
|
119
|
+
/**
|
|
120
|
+
* @param {string | import('@gmaclennan/zip-reader').ZipReader} filepathOrZip Path to styled map package (`.styledmap`) file, or a ZipReader instance
|
|
121
|
+
*/
|
|
122
|
+
constructor(filepathOrZip) {
|
|
123
|
+
let zipPromise;
|
|
124
|
+
if (typeof filepathOrZip === "string") {
|
|
125
|
+
const sourcePromise = import("@gmaclennan/zip-reader/file-source").then(
|
|
126
|
+
({ FileSource }) => FileSource.open(filepathOrZip)
|
|
127
|
+
);
|
|
128
|
+
sourcePromise.catch(noop);
|
|
129
|
+
zipPromise = sourcePromise.then((source) => {
|
|
130
|
+
this.#fileSource = source;
|
|
131
|
+
return ZipReader.from(source);
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
zipPromise = Promise.resolve(filepathOrZip);
|
|
135
|
+
}
|
|
136
|
+
zipPromise.catch(noop);
|
|
137
|
+
this.#entries = new Entries(zipPromise);
|
|
138
|
+
this.#entries.ready().catch(() => this.close());
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Resolves when the styled map package has been opened and the entries have
|
|
142
|
+
* been read. Throws any error that occurred during opening.
|
|
143
|
+
*/
|
|
144
|
+
async opened() {
|
|
145
|
+
await this.#entries.ready();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the format version from the VERSION file in the styled map package.
|
|
149
|
+
* Returns "1.0" if no VERSION file exists (older SMP files did not have a
|
|
150
|
+
* VERSION file, so we assume version 1.0).
|
|
151
|
+
*
|
|
152
|
+
* @returns {Promise<string>}
|
|
153
|
+
*/
|
|
154
|
+
async getVersion() {
|
|
155
|
+
const versionEntry = await this.#entries.get(VERSION_FILE);
|
|
156
|
+
if (!versionEntry) return "1.0";
|
|
157
|
+
return (await streamToText(versionEntry.readable())).trim();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get the style JSON from the styled map package. The URLs in the style JSON
|
|
161
|
+
* will be transformed to use the provided base URL.
|
|
162
|
+
*
|
|
163
|
+
* @param {string | null} [baseUrl] Base URL where you plan to serve the resources in this styled map package, e.g. `http://localhost:3000/maps/styleA`
|
|
164
|
+
* @returns {Promise<import('./types.js').SMPStyle>}
|
|
165
|
+
*/
|
|
166
|
+
async getStyle(baseUrl = null) {
|
|
167
|
+
const styleEntry = await this.#entries.get(STYLE_FILE);
|
|
168
|
+
if (!styleEntry) throw new ENOENT(STYLE_FILE);
|
|
169
|
+
const style = await streamToJson(styleEntry.readable());
|
|
170
|
+
if (!validateStyle(style)) {
|
|
171
|
+
throw new AggregateError(validateStyle.errors, "Invalid style");
|
|
172
|
+
}
|
|
173
|
+
if (typeof style.glyphs === "string") {
|
|
174
|
+
style.glyphs = getUrl(style.glyphs, baseUrl);
|
|
175
|
+
}
|
|
176
|
+
if (typeof style.sprite === "string") {
|
|
177
|
+
style.sprite = getUrl(style.sprite, baseUrl);
|
|
178
|
+
} else if (Array.isArray(style.sprite)) {
|
|
179
|
+
style.sprite = style.sprite.map(({ id, url }) => {
|
|
180
|
+
return { id, url: getUrl(url, baseUrl) };
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
for (const source of Object.values(style.sources)) {
|
|
184
|
+
if ("tiles" in source && source.tiles) {
|
|
185
|
+
source.tiles = source.tiles.map((tile) => getUrl(tile, baseUrl));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return (
|
|
189
|
+
/** @type {import('./types.js').SMPStyle} */
|
|
190
|
+
style
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get a resource from the styled map package. The path should be relative to
|
|
195
|
+
* the root of the package.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} path
|
|
198
|
+
* @returns {Promise<Resource>}
|
|
199
|
+
*/
|
|
200
|
+
async getResource(path) {
|
|
201
|
+
if (path[0] === "/") path = path.slice(1);
|
|
202
|
+
if (path === STYLE_FILE) {
|
|
203
|
+
const styleJSON = JSON.stringify(await this.getStyle());
|
|
204
|
+
const bytes = new TextEncoder().encode(styleJSON);
|
|
205
|
+
return {
|
|
206
|
+
contentType: "application/json; charset=utf-8",
|
|
207
|
+
contentLength: bytes.byteLength,
|
|
208
|
+
resourceType: "style",
|
|
209
|
+
stream: new ReadableStream({
|
|
210
|
+
start(controller) {
|
|
211
|
+
controller.enqueue(bytes);
|
|
212
|
+
controller.close();
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const entry = await this.#entries.get(path);
|
|
218
|
+
if (!entry) throw new ENOENT(path);
|
|
219
|
+
const resourceType = getResourceType(path);
|
|
220
|
+
const contentType = getContentType(path);
|
|
221
|
+
const stream = entry.readable();
|
|
222
|
+
const resource = {
|
|
223
|
+
resourceType,
|
|
224
|
+
contentType,
|
|
225
|
+
contentLength: entry.uncompressedSize,
|
|
226
|
+
stream
|
|
227
|
+
};
|
|
228
|
+
if (path.endsWith(".gz")) {
|
|
229
|
+
resource.contentEncoding = "gzip";
|
|
230
|
+
}
|
|
231
|
+
return resource;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Close the styled map package file (should be called after reading the file to avoid memory leaks)
|
|
235
|
+
*/
|
|
236
|
+
async close() {
|
|
237
|
+
if (this.#closePromise) return this.#closePromise;
|
|
238
|
+
this.#closePromise = (async () => {
|
|
239
|
+
await this.#entries.close().catch(noop);
|
|
240
|
+
if (this.#fileSource) {
|
|
241
|
+
await this.#fileSource.close().catch(noop);
|
|
242
|
+
}
|
|
243
|
+
})();
|
|
244
|
+
return this.#closePromise;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function getUrl(smpUri, baseUrl) {
|
|
248
|
+
if (!smpUri.startsWith(URI_BASE)) {
|
|
249
|
+
throw new Error(`Invalid SMP URI: ${smpUri}`);
|
|
250
|
+
}
|
|
251
|
+
if (typeof baseUrl !== "string") return smpUri;
|
|
252
|
+
if (!baseUrl.endsWith("/")) {
|
|
253
|
+
baseUrl += "/";
|
|
254
|
+
}
|
|
255
|
+
return smpUri.replace(URI_BASE, baseUrl);
|
|
256
|
+
}
|
|
257
|
+
export {
|
|
258
|
+
Reader
|
|
259
|
+
};
|