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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/docs/api.md +66 -0
- package/docs/architecture.md +87 -0
- package/docs/cartography.md +77 -0
- package/docs/cesium.md +107 -0
- package/docs/openlayers.md +98 -0
- package/docs/styles.md +103 -0
- package/package.json +51 -0
- package/packages/cesium/package.json +13 -0
- package/packages/cesium/src/index.js +405 -0
- package/packages/ol/package.json +14 -0
- package/packages/ol/src/index.js +1705 -0
- package/packages/ol/src/labels.js +977 -0
- package/src/3dtiles/b3dm.js +38 -0
- package/src/3dtiles/clipper-surfaces.js +317 -0
- package/src/3dtiles/export.js +768 -0
- package/src/3dtiles/extrude.js +301 -0
- package/src/3dtiles/flat.js +531 -0
- package/src/3dtiles/glb.js +178 -0
- package/src/3dtiles/gpkg-buildings.js +240 -0
- package/src/3dtiles/gpkg-features.js +157 -0
- package/src/3dtiles/tileset.js +75 -0
- package/src/build.js +134 -0
- package/src/cli.js +656 -0
- package/src/export-pmtiles.js +962 -0
- package/src/geometry-read.js +50 -0
- package/src/gpkg-read.js +460 -0
- package/src/gpkg.js +567 -0
- package/src/html.js +593 -0
- package/src/layers.js +357 -0
- package/src/manifest.js +29 -0
- package/src/mvt.js +2593 -0
- package/src/ol.js +5 -0
- package/src/osm.js +2110 -0
- package/src/pmtiles-worker.js +70 -0
- package/src/pmtiles.js +260 -0
- package/src/server.js +720 -0
- package/src/style-command.js +78 -0
- package/src/style-filters.js +76 -0
- package/src/style-presets.js +93 -0
- package/src/style-themes.js +235 -0
- package/src/style.js +13 -0
- package/src/tile-cache.js +59 -0
- package/src/utils.js +222 -0
- package/styles/presets/light.json +4655 -0
- package/styles/presets/monochrome.json +4655 -0
- package/styles/presets/neon-dark-3d.json +90 -0
- package/styles/presets/neon-dark.json +4690 -0
- package/styles/presets/tactical.json +4690 -0
- 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
|
+
}
|