map-zero 0.1.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +220 -0
  4. package/docs/api.md +66 -0
  5. package/docs/architecture.md +87 -0
  6. package/docs/cartography.md +77 -0
  7. package/docs/cesium.md +107 -0
  8. package/docs/openlayers.md +98 -0
  9. package/docs/styles.md +103 -0
  10. package/package.json +51 -0
  11. package/packages/cesium/package.json +13 -0
  12. package/packages/cesium/src/index.js +405 -0
  13. package/packages/ol/package.json +14 -0
  14. package/packages/ol/src/index.js +1705 -0
  15. package/packages/ol/src/labels.js +977 -0
  16. package/src/3dtiles/b3dm.js +38 -0
  17. package/src/3dtiles/clipper-surfaces.js +317 -0
  18. package/src/3dtiles/export.js +768 -0
  19. package/src/3dtiles/extrude.js +301 -0
  20. package/src/3dtiles/flat.js +531 -0
  21. package/src/3dtiles/glb.js +178 -0
  22. package/src/3dtiles/gpkg-buildings.js +240 -0
  23. package/src/3dtiles/gpkg-features.js +157 -0
  24. package/src/3dtiles/tileset.js +75 -0
  25. package/src/build.js +134 -0
  26. package/src/cli.js +656 -0
  27. package/src/export-pmtiles.js +962 -0
  28. package/src/geometry-read.js +50 -0
  29. package/src/gpkg-read.js +460 -0
  30. package/src/gpkg.js +567 -0
  31. package/src/html.js +593 -0
  32. package/src/layers.js +357 -0
  33. package/src/manifest.js +29 -0
  34. package/src/mvt.js +2593 -0
  35. package/src/ol.js +5 -0
  36. package/src/osm.js +2110 -0
  37. package/src/pmtiles-worker.js +70 -0
  38. package/src/pmtiles.js +260 -0
  39. package/src/server.js +720 -0
  40. package/src/style-command.js +78 -0
  41. package/src/style-filters.js +76 -0
  42. package/src/style-presets.js +93 -0
  43. package/src/style-themes.js +235 -0
  44. package/src/style.js +13 -0
  45. package/src/tile-cache.js +59 -0
  46. package/src/utils.js +222 -0
  47. package/styles/presets/light.json +4655 -0
  48. package/styles/presets/monochrome.json +4655 -0
  49. package/styles/presets/neon-dark-3d.json +90 -0
  50. package/styles/presets/neon-dark.json +4690 -0
  51. package/styles/presets/tactical.json +4690 -0
  52. package/styles/themes/neon-dark.theme.json +20 -0
@@ -0,0 +1,70 @@
1
+ import { parentPort, workerData } from 'node:worker_threads';
2
+
3
+ import { openGeoPackageReader } from './gpkg-read.js';
4
+ import { encodeMvtTileSetWithStats } from './mvt.js';
5
+ import { createHiddenFilters } from './style-filters.js';
6
+
7
+ if (!parentPort) {
8
+ throw new Error('pmtiles worker must run inside worker_threads');
9
+ }
10
+
11
+ const reader = openGeoPackageReader({
12
+ gpkgPath: workerData.gpkgPath,
13
+ manifest: workerData.manifest,
14
+ hiddenFilters: createHiddenFilters(workerData.manifest, workerData.defaultStyle)
15
+ });
16
+
17
+ parentPort.on('message', (job) => {
18
+ if (job?.type === 'close') {
19
+ reader.close();
20
+ parentPort.postMessage({ type: 'closed' });
21
+ return;
22
+ }
23
+
24
+ try {
25
+ const result = encodeMvtTileSetWithStats(reader, job.z, job.x, job.y, job.layerIds, {
26
+ detail: job.detail,
27
+ style: workerData.defaultStyle
28
+ });
29
+
30
+ if (result.encodedFeatureCount === 0) {
31
+ parentPort.postMessage({
32
+ type: 'tile',
33
+ id: job.id,
34
+ z: job.z,
35
+ x: job.x,
36
+ y: job.y,
37
+ tileId: job.tileId,
38
+ empty: true,
39
+ stats: result
40
+ });
41
+ return;
42
+ }
43
+
44
+ const transferable = result.buffer.buffer.slice(
45
+ result.buffer.byteOffset,
46
+ result.buffer.byteOffset + result.buffer.byteLength
47
+ );
48
+ parentPort.postMessage({
49
+ type: 'tile',
50
+ id: job.id,
51
+ z: job.z,
52
+ x: job.x,
53
+ y: job.y,
54
+ tileId: job.tileId,
55
+ empty: false,
56
+ buffer: transferable,
57
+ stats: {
58
+ ...result,
59
+ buffer: undefined
60
+ }
61
+ }, [transferable]);
62
+ } catch (error) {
63
+ parentPort.postMessage({
64
+ type: 'error',
65
+ id: job.id,
66
+ message: error instanceof Error ? error.message : String(error),
67
+ stack: error instanceof Error ? error.stack : undefined
68
+ });
69
+ }
70
+ });
package/src/pmtiles.js ADDED
@@ -0,0 +1,260 @@
1
+ import { createReadStream, createWriteStream, promises as fs } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { pipeline } from 'node:stream/promises';
4
+
5
+ import { Compression, TileType, zxyToTileId } from 'pmtiles';
6
+
7
+ const HEADER_SIZE_BYTES = 127;
8
+ const SPEC_VERSION = 3;
9
+ const COORDINATE_SCALE = 10_000_000;
10
+ const ROOT_LEAF_SIZE = 512;
11
+
12
+ /**
13
+ * @typedef {{
14
+ * tileId: number,
15
+ * offset: number,
16
+ * length: number,
17
+ * runLength: number
18
+ * }} PmtilesEntry
19
+ */
20
+
21
+ /**
22
+ * Convert XYZ tile coordinates to the PMTiles Hilbert tile id.
23
+ *
24
+ * @param {number} z
25
+ * @param {number} x
26
+ * @param {number} y
27
+ * @returns {number}
28
+ */
29
+ export function tileIdForZxy(z, x, y) {
30
+ return zxyToTileId(z, x, y);
31
+ }
32
+
33
+ /**
34
+ * Write a PMTiles v3 archive with one root directory and uncompressed MVT tile data.
35
+ *
36
+ * @param {{
37
+ * outPath: string,
38
+ * tileDataPath: string,
39
+ * entries: PmtilesEntry[],
40
+ * metadata: Record<string, unknown>,
41
+ * minZoom: number,
42
+ * maxZoom: number,
43
+ * bbox: [number, number, number, number],
44
+ * centerZoom?: number
45
+ * }} options
46
+ * @returns {Promise<{ bytes: number, entries: number }>}
47
+ */
48
+ export async function writePmtilesArchive(options) {
49
+ const sortedEntries = [...options.entries].sort((a, b) => a.tileId - b.tileId);
50
+ const directories = createDirectories(sortedEntries);
51
+ const metadata = Buffer.from(`${JSON.stringify(options.metadata)}\n`);
52
+ const tileDataStat = await fs.stat(options.tileDataPath);
53
+
54
+ const rootDirectoryOffset = HEADER_SIZE_BYTES;
55
+ const jsonMetadataOffset = rootDirectoryOffset + directories.root.length;
56
+ const leafDirectoryOffset = jsonMetadataOffset + metadata.length;
57
+ const tileDataOffset = leafDirectoryOffset + directories.leaves.length;
58
+ const header = createHeader({
59
+ rootDirectoryOffset,
60
+ rootDirectoryLength: directories.root.length,
61
+ jsonMetadataOffset,
62
+ jsonMetadataLength: metadata.length,
63
+ leafDirectoryOffset,
64
+ leafDirectoryLength: directories.leaves.length,
65
+ tileDataOffset,
66
+ tileDataLength: tileDataStat.size,
67
+ numAddressedTiles: sortedEntries.length,
68
+ numTileEntries: sortedEntries.length,
69
+ numTileContents: sortedEntries.length,
70
+ minZoom: options.minZoom,
71
+ maxZoom: options.maxZoom,
72
+ bbox: options.bbox,
73
+ centerZoom: options.centerZoom ?? options.minZoom
74
+ });
75
+
76
+ await fs.mkdir(dirname(options.outPath), { recursive: true });
77
+ const tmpOut = `${options.outPath}.tmp-${process.pid}-${Date.now()}`;
78
+ const handle = await fs.open(tmpOut, 'w');
79
+ try {
80
+ await handle.write(header);
81
+ await handle.write(directories.root);
82
+ await handle.write(metadata);
83
+ await handle.write(directories.leaves);
84
+ } finally {
85
+ await handle.close();
86
+ }
87
+
88
+ await pipeline(
89
+ createReadStream(options.tileDataPath),
90
+ createWriteStream(tmpOut, { flags: 'a' })
91
+ );
92
+ await fs.rename(tmpOut, options.outPath);
93
+ const stat = await fs.stat(options.outPath);
94
+
95
+ return {
96
+ bytes: stat.size,
97
+ entries: sortedEntries.length
98
+ };
99
+ }
100
+
101
+ /**
102
+ * @param {PmtilesEntry[]} entries
103
+ * @returns {{ root: Buffer, leaves: Buffer }}
104
+ */
105
+ function createDirectories(entries) {
106
+ if (entries.length <= ROOT_LEAF_SIZE) {
107
+ return {
108
+ root: serializeDirectory(entries),
109
+ leaves: Buffer.alloc(0)
110
+ };
111
+ }
112
+
113
+ const rootEntries = [];
114
+ const leafBuffers = [];
115
+ let leafOffset = 0;
116
+ for (let index = 0; index < entries.length; index += ROOT_LEAF_SIZE) {
117
+ const leafEntries = entries.slice(index, index + ROOT_LEAF_SIZE);
118
+ const leaf = serializeDirectory(leafEntries);
119
+ rootEntries.push({
120
+ tileId: leafEntries[0].tileId,
121
+ offset: leafOffset,
122
+ length: leaf.length,
123
+ runLength: 0
124
+ });
125
+ leafBuffers.push(leaf);
126
+ leafOffset += leaf.length;
127
+ }
128
+
129
+ return {
130
+ root: serializeDirectory(rootEntries),
131
+ leaves: Buffer.concat(leafBuffers)
132
+ };
133
+ }
134
+
135
+ /**
136
+ * @param {PmtilesEntry[]} entries
137
+ * @returns {Buffer}
138
+ */
139
+ function serializeDirectory(entries) {
140
+ const chunks = [];
141
+ pushVarint(chunks, entries.length);
142
+
143
+ let lastTileId = 0;
144
+ for (const entry of entries) {
145
+ pushVarint(chunks, entry.tileId - lastTileId);
146
+ lastTileId = entry.tileId;
147
+ }
148
+
149
+ for (const entry of entries) {
150
+ pushVarint(chunks, entry.runLength);
151
+ }
152
+
153
+ for (const entry of entries) {
154
+ pushVarint(chunks, entry.length);
155
+ }
156
+
157
+ for (let index = 0; index < entries.length; index += 1) {
158
+ const entry = entries[index];
159
+ const previous = entries[index - 1];
160
+ if (previous && entry.offset === previous.offset + previous.length) {
161
+ pushVarint(chunks, 0);
162
+ } else {
163
+ pushVarint(chunks, entry.offset + 1);
164
+ }
165
+ }
166
+
167
+ return Buffer.from(chunks);
168
+ }
169
+
170
+ /**
171
+ * @param {number[]} chunks
172
+ * @param {number} value
173
+ */
174
+ function pushVarint(chunks, value) {
175
+ if (!Number.isSafeInteger(value) || value < 0) {
176
+ throw new Error(`PMTiles varint value must be a safe non-negative integer: ${value}`);
177
+ }
178
+
179
+ let remaining = value;
180
+ while (remaining >= 0x80) {
181
+ chunks.push((remaining % 0x80) | 0x80);
182
+ remaining = Math.floor(remaining / 0x80);
183
+ }
184
+ chunks.push(remaining);
185
+ }
186
+
187
+ /**
188
+ * @param {{
189
+ * rootDirectoryOffset: number,
190
+ * rootDirectoryLength: number,
191
+ * jsonMetadataOffset: number,
192
+ * jsonMetadataLength: number,
193
+ * leafDirectoryOffset: number,
194
+ * leafDirectoryLength: number,
195
+ * tileDataOffset: number,
196
+ * tileDataLength: number,
197
+ * numAddressedTiles: number,
198
+ * numTileEntries: number,
199
+ * numTileContents: number,
200
+ * minZoom: number,
201
+ * maxZoom: number,
202
+ * bbox: [number, number, number, number],
203
+ * centerZoom: number
204
+ * }} options
205
+ * @returns {Buffer}
206
+ */
207
+ function createHeader(options) {
208
+ const header = Buffer.alloc(HEADER_SIZE_BYTES);
209
+ header.write('PMTiles', 0, 'ascii');
210
+ header.writeUInt8(SPEC_VERSION, 7);
211
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
212
+
213
+ setUint64(view, 8, options.rootDirectoryOffset);
214
+ setUint64(view, 16, options.rootDirectoryLength);
215
+ setUint64(view, 24, options.jsonMetadataOffset);
216
+ setUint64(view, 32, options.jsonMetadataLength);
217
+ setUint64(view, 40, options.leafDirectoryOffset);
218
+ setUint64(view, 48, options.leafDirectoryLength);
219
+ setUint64(view, 56, options.tileDataOffset);
220
+ setUint64(view, 64, options.tileDataLength);
221
+ setUint64(view, 72, options.numAddressedTiles);
222
+ setUint64(view, 80, options.numTileEntries);
223
+ setUint64(view, 88, options.numTileContents);
224
+ header.writeUInt8(1, 96);
225
+ header.writeUInt8(Compression.None, 97);
226
+ header.writeUInt8(Compression.None, 98);
227
+ header.writeUInt8(TileType.Mvt, 99);
228
+ header.writeUInt8(options.minZoom, 100);
229
+ header.writeUInt8(options.maxZoom, 101);
230
+ view.setInt32(102, scaledCoordinate(options.bbox[0]), true);
231
+ view.setInt32(106, scaledCoordinate(options.bbox[1]), true);
232
+ view.setInt32(110, scaledCoordinate(options.bbox[2]), true);
233
+ view.setInt32(114, scaledCoordinate(options.bbox[3]), true);
234
+ header.writeUInt8(options.centerZoom, 118);
235
+ view.setInt32(119, scaledCoordinate((options.bbox[0] + options.bbox[2]) / 2), true);
236
+ view.setInt32(123, scaledCoordinate((options.bbox[1] + options.bbox[3]) / 2), true);
237
+
238
+ return header;
239
+ }
240
+
241
+ /**
242
+ * @param {DataView} view
243
+ * @param {number} offset
244
+ * @param {number} value
245
+ */
246
+ function setUint64(view, offset, value) {
247
+ if (!Number.isSafeInteger(value) || value < 0) {
248
+ throw new Error(`PMTiles header value must be a safe non-negative integer: ${value}`);
249
+ }
250
+
251
+ view.setBigUint64(offset, BigInt(value), true);
252
+ }
253
+
254
+ /**
255
+ * @param {number} value
256
+ * @returns {number}
257
+ */
258
+ function scaledCoordinate(value) {
259
+ return Math.round(value * COORDINATE_SCALE);
260
+ }