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
package/src/gpkg.js ADDED
@@ -0,0 +1,567 @@
1
+ import { existsSync, unlinkSync } from 'node:fs';
2
+
3
+ import Database from 'better-sqlite3';
4
+
5
+ import { LAYER_DEFINITIONS } from './layers.js';
6
+ import { geometryBbox, quoteIdentifier } from './utils.js';
7
+
8
+ const SRS_ID = 4326;
9
+
10
+ /**
11
+ * @typedef {{ type: string, coordinates: unknown }} Geometry
12
+ * @typedef {{ geometry: Geometry, properties: Record<string, string | null>, bbox?: [number, number, number, number] }} Feature
13
+ */
14
+
15
+ /**
16
+ * Write selected features into a GeoPackage.
17
+ *
18
+ * @param {string} filePath
19
+ * @param {Record<string, Feature[]>} featuresByLayer
20
+ * @param {string[]} layers
21
+ * @param {[number, number, number, number]} packageBbox
22
+ */
23
+ export function writeGeoPackage(filePath, featuresByLayer, layers, packageBbox) {
24
+ const writer = openGeoPackageWriter(filePath, layers, packageBbox);
25
+
26
+ try {
27
+ writer.transaction(() => {
28
+ for (const layer of layers) {
29
+ for (const feature of featuresByLayer[layer] ?? []) {
30
+ writer.insertFeature(layer, feature);
31
+ }
32
+ }
33
+ });
34
+ } finally {
35
+ writer.close();
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Open a GeoPackage writer that accepts features incrementally.
41
+ *
42
+ * @param {string} filePath
43
+ * @param {string[]} layers
44
+ * @param {[number, number, number, number]} packageBbox
45
+ * @returns {{
46
+ * insertFeature: (layer: string, feature: Feature) => void,
47
+ * transaction: (fn: () => void) => void,
48
+ * counts: () => Record<string, number>,
49
+ * close: () => void
50
+ * }}
51
+ */
52
+ export function openGeoPackageWriter(filePath, layers, packageBbox) {
53
+ if (existsSync(filePath)) {
54
+ unlinkSync(filePath);
55
+ }
56
+
57
+ const db = new Database(filePath);
58
+ /** @type {Record<string, { insert: Database.Statement, spatialIndex: { insert: Database.Statement } | null, propertyColumns: string[] }>} */
59
+ const layerWriters = {};
60
+ /** @type {Record<string, number>} */
61
+ const featureCounts = Object.fromEntries(layers.map((layer) => [layer, 0]));
62
+ let closed = false;
63
+
64
+ db.pragma('journal_mode = WAL');
65
+ db.pragma('foreign_keys = ON');
66
+ db.pragma('application_id = 1196444487');
67
+ db.pragma('user_version = 10400');
68
+
69
+ db.transaction(() => {
70
+ createCoreTables(db);
71
+
72
+ for (const layer of layers) {
73
+ createFeatureTable(db, layer);
74
+ registerFeatureTable(db, layer, [], packageBbox);
75
+ layerWriters[layer] = createLayerWriter(db, layer);
76
+ }
77
+ })();
78
+
79
+ return {
80
+ insertFeature(layer, feature) {
81
+ const writer = layerWriters[layer];
82
+ if (!writer) {
83
+ throw new Error(`unknown GeoPackage layer: ${layer}`);
84
+ }
85
+
86
+ const values = [
87
+ encodeGeoPackageGeometry(feature.geometry),
88
+ ...writer.propertyColumns.map((column) => feature.properties[column] ?? null)
89
+ ];
90
+ const info = writer.insert.run(values);
91
+ featureCounts[layer] += 1;
92
+
93
+ if (writer.spatialIndex) {
94
+ const bbox = feature.bbox ?? geometryBbox(feature.geometry);
95
+ if (bbox) {
96
+ writer.spatialIndex.insert.run(info.lastInsertRowid, bbox[0], bbox[2], bbox[1], bbox[3]);
97
+ }
98
+ }
99
+ },
100
+ transaction(fn) {
101
+ db.transaction(fn)();
102
+ },
103
+ counts() {
104
+ return { ...featureCounts };
105
+ },
106
+ close() {
107
+ if (closed) {
108
+ return;
109
+ }
110
+
111
+ for (const layer of layers) {
112
+ if (layerWriters[layer]?.spatialIndex) {
113
+ createSpatialIndexMetadata(db, layer);
114
+ }
115
+ }
116
+
117
+ closed = true;
118
+ db.close();
119
+ }
120
+ };
121
+ }
122
+
123
+ /**
124
+ * @param {Database.Database} db
125
+ */
126
+ function createCoreTables(db) {
127
+ db.exec(`
128
+ CREATE TABLE gpkg_spatial_ref_sys (
129
+ srs_name TEXT NOT NULL,
130
+ srs_id INTEGER NOT NULL PRIMARY KEY,
131
+ organization TEXT NOT NULL,
132
+ organization_coordsys_id INTEGER NOT NULL,
133
+ definition TEXT NOT NULL,
134
+ description TEXT
135
+ );
136
+
137
+ INSERT INTO gpkg_spatial_ref_sys (
138
+ srs_name,
139
+ srs_id,
140
+ organization,
141
+ organization_coordsys_id,
142
+ definition,
143
+ description
144
+ ) VALUES
145
+ ('Undefined Cartesian', -1, 'NONE', -1, 'undefined', 'undefined Cartesian coordinate reference system'),
146
+ ('Undefined Geographic', 0, 'NONE', 0, 'undefined', 'undefined geographic coordinate reference system'),
147
+ (
148
+ 'WGS 84 geodetic',
149
+ 4326,
150
+ 'EPSG',
151
+ 4326,
152
+ 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]',
153
+ 'longitude/latitude coordinates in decimal degrees on the WGS 84 datum'
154
+ );
155
+
156
+ CREATE TABLE gpkg_contents (
157
+ table_name TEXT NOT NULL PRIMARY KEY,
158
+ data_type TEXT NOT NULL,
159
+ identifier TEXT UNIQUE,
160
+ description TEXT DEFAULT '',
161
+ last_change DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
162
+ min_x DOUBLE,
163
+ min_y DOUBLE,
164
+ max_x DOUBLE,
165
+ max_y DOUBLE,
166
+ srs_id INTEGER,
167
+ CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys(srs_id)
168
+ );
169
+
170
+ CREATE TABLE gpkg_geometry_columns (
171
+ table_name TEXT NOT NULL,
172
+ column_name TEXT NOT NULL,
173
+ geometry_type_name TEXT NOT NULL,
174
+ srs_id INTEGER NOT NULL,
175
+ z TINYINT NOT NULL,
176
+ m TINYINT NOT NULL,
177
+ PRIMARY KEY (table_name, column_name),
178
+ CONSTRAINT fk_ggc_tn FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name),
179
+ CONSTRAINT fk_ggc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys(srs_id)
180
+ );
181
+ `);
182
+ }
183
+
184
+ /**
185
+ * @param {Database.Database} db
186
+ * @param {string} layer
187
+ */
188
+ function createFeatureTable(db, layer) {
189
+ const definition = LAYER_DEFINITIONS[layer];
190
+ const columns = definition.columns
191
+ .map((column) => `${quoteIdentifier(column)} TEXT`)
192
+ .join(',\n ');
193
+
194
+ db.exec(`
195
+ CREATE TABLE ${quoteIdentifier(layer)} (
196
+ ${quoteIdentifier('fid')} INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
197
+ ${quoteIdentifier('geom')} BLOB NOT NULL,
198
+ ${columns}
199
+ );
200
+ `);
201
+ }
202
+
203
+ /**
204
+ * @param {Database.Database} db
205
+ * @param {string} layer
206
+ * @param {Feature[]} features
207
+ * @param {[number, number, number, number]} packageBbox
208
+ */
209
+ function registerFeatureTable(db, layer, features, packageBbox) {
210
+ const definition = LAYER_DEFINITIONS[layer];
211
+ const bbox = features.length > 0 ? mergeFeatureBboxes(features) : packageBbox;
212
+
213
+ db.prepare(`
214
+ INSERT INTO gpkg_contents (
215
+ table_name,
216
+ data_type,
217
+ identifier,
218
+ description,
219
+ min_x,
220
+ min_y,
221
+ max_x,
222
+ max_y,
223
+ srs_id
224
+ ) VALUES (?, 'features', ?, ?, ?, ?, ?, ?, ?)
225
+ `).run(
226
+ layer,
227
+ layer,
228
+ `map-zero ${layer}`,
229
+ bbox[0],
230
+ bbox[1],
231
+ bbox[2],
232
+ bbox[3],
233
+ SRS_ID
234
+ );
235
+
236
+ db.prepare(`
237
+ INSERT INTO gpkg_geometry_columns (
238
+ table_name,
239
+ column_name,
240
+ geometry_type_name,
241
+ srs_id,
242
+ z,
243
+ m
244
+ ) VALUES (?, 'geom', ?, ?, 0, 0)
245
+ `).run(layer, definition.gpkgGeometryType, SRS_ID);
246
+ }
247
+
248
+ /**
249
+ * @param {Database.Database} db
250
+ * @param {string} layer
251
+ * @param {Feature[]} features
252
+ */
253
+ function insertFeatures(db, layer, features) {
254
+ const writer = createLayerWriter(db, layer);
255
+
256
+ for (const feature of features) {
257
+ const values = [
258
+ encodeGeoPackageGeometry(feature.geometry),
259
+ ...writer.propertyColumns.map((column) => feature.properties[column] ?? null)
260
+ ];
261
+ const info = writer.insert.run(values);
262
+
263
+ if (writer.spatialIndex) {
264
+ const bbox = feature.bbox ?? geometryBbox(feature.geometry);
265
+ if (bbox) {
266
+ writer.spatialIndex.insert.run(info.lastInsertRowid, bbox[0], bbox[2], bbox[1], bbox[3]);
267
+ }
268
+ }
269
+ }
270
+
271
+ if (writer.spatialIndex) {
272
+ createSpatialIndexMetadata(db, layer);
273
+ }
274
+ }
275
+
276
+ /**
277
+ * @param {Database.Database} db
278
+ * @param {string} layer
279
+ * @returns {{ insert: Database.Statement, spatialIndex: { insert: Database.Statement } | null, propertyColumns: string[] }}
280
+ */
281
+ function createLayerWriter(db, layer) {
282
+ const definition = LAYER_DEFINITIONS[layer];
283
+ const table = quoteIdentifier(layer);
284
+ const propertyColumns = definition.columns;
285
+ const columns = ['geom', ...propertyColumns];
286
+ const insertSql = `
287
+ INSERT INTO ${table} (${columns.map(quoteIdentifier).join(', ')})
288
+ VALUES (${columns.map(() => '?').join(', ')})
289
+ `;
290
+
291
+ return {
292
+ insert: db.prepare(insertSql),
293
+ spatialIndex: createSpatialIndex(db, layer),
294
+ propertyColumns
295
+ };
296
+ }
297
+
298
+ /**
299
+ * @param {Database.Database} db
300
+ * @param {string} layer
301
+ * @returns {{ insert: Database.Statement } | null}
302
+ */
303
+ function createSpatialIndex(db, layer) {
304
+ const rtree = quoteIdentifier(`rtree_${layer}_geom`);
305
+
306
+ try {
307
+ db.exec(`CREATE VIRTUAL TABLE ${rtree} USING rtree(id, minx, maxx, miny, maxy);`);
308
+ return {
309
+ insert: db.prepare(`INSERT INTO ${rtree} (id, minx, maxx, miny, maxy) VALUES (?, ?, ?, ?, ?)`)
310
+ };
311
+ } catch {
312
+ return null;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Register the GeoPackage RTree extension after manual index population.
318
+ *
319
+ * @param {Database.Database} db
320
+ * @param {string} layer
321
+ */
322
+ function createSpatialIndexMetadata(db, layer) {
323
+ const table = quoteIdentifier(layer);
324
+ const rtree = quoteIdentifier(`rtree_${layer}_geom`);
325
+ const fid = quoteIdentifier('fid');
326
+ const geom = quoteIdentifier('geom');
327
+
328
+ db.exec(`
329
+ CREATE TABLE IF NOT EXISTS gpkg_extensions (
330
+ table_name TEXT,
331
+ column_name TEXT,
332
+ extension_name TEXT NOT NULL,
333
+ definition TEXT NOT NULL,
334
+ scope TEXT NOT NULL,
335
+ CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)
336
+ );
337
+ `);
338
+
339
+ db.prepare(`
340
+ INSERT OR REPLACE INTO gpkg_extensions (
341
+ table_name,
342
+ column_name,
343
+ extension_name,
344
+ definition,
345
+ scope
346
+ ) VALUES (?, 'geom', 'gpkg_rtree_index', 'http://www.geopackage.org/spec/#extension_rtree', 'write-only')
347
+ `).run(layer);
348
+
349
+ db.exec(`
350
+ CREATE TRIGGER ${quoteIdentifier(`rtree_${layer}_geom_insert`)}
351
+ AFTER INSERT ON ${table}
352
+ WHEN (NEW.${geom} NOT NULL AND NOT ST_IsEmpty(NEW.${geom}))
353
+ BEGIN
354
+ INSERT OR REPLACE INTO ${rtree}
355
+ VALUES (
356
+ NEW.${fid},
357
+ ST_MinX(NEW.${geom}),
358
+ ST_MaxX(NEW.${geom}),
359
+ ST_MinY(NEW.${geom}),
360
+ ST_MaxY(NEW.${geom})
361
+ );
362
+ END;
363
+
364
+ CREATE TRIGGER ${quoteIdentifier(`rtree_${layer}_geom_update1`)}
365
+ AFTER UPDATE OF ${geom} ON ${table}
366
+ WHEN OLD.${fid} = NEW.${fid} AND (NEW.${geom} NOT NULL AND NOT ST_IsEmpty(NEW.${geom}))
367
+ BEGIN
368
+ INSERT OR REPLACE INTO ${rtree}
369
+ VALUES (
370
+ NEW.${fid},
371
+ ST_MinX(NEW.${geom}),
372
+ ST_MaxX(NEW.${geom}),
373
+ ST_MinY(NEW.${geom}),
374
+ ST_MaxY(NEW.${geom})
375
+ );
376
+ END;
377
+
378
+ CREATE TRIGGER ${quoteIdentifier(`rtree_${layer}_geom_update2`)}
379
+ AFTER UPDATE OF ${geom} ON ${table}
380
+ WHEN OLD.${fid} = NEW.${fid} AND (NEW.${geom} ISNULL OR ST_IsEmpty(NEW.${geom}))
381
+ BEGIN
382
+ DELETE FROM ${rtree} WHERE id = OLD.${fid};
383
+ END;
384
+
385
+ CREATE TRIGGER ${quoteIdentifier(`rtree_${layer}_geom_update3`)}
386
+ AFTER UPDATE ON ${table}
387
+ WHEN OLD.${fid} != NEW.${fid} AND (NEW.${geom} NOT NULL AND NOT ST_IsEmpty(NEW.${geom}))
388
+ BEGIN
389
+ DELETE FROM ${rtree} WHERE id = OLD.${fid};
390
+ INSERT OR REPLACE INTO ${rtree}
391
+ VALUES (
392
+ NEW.${fid},
393
+ ST_MinX(NEW.${geom}),
394
+ ST_MaxX(NEW.${geom}),
395
+ ST_MinY(NEW.${geom}),
396
+ ST_MaxY(NEW.${geom})
397
+ );
398
+ END;
399
+
400
+ CREATE TRIGGER ${quoteIdentifier(`rtree_${layer}_geom_update4`)}
401
+ AFTER UPDATE ON ${table}
402
+ WHEN OLD.${fid} != NEW.${fid} AND (NEW.${geom} ISNULL OR ST_IsEmpty(NEW.${geom}))
403
+ BEGIN
404
+ DELETE FROM ${rtree} WHERE id IN (OLD.${fid}, NEW.${fid});
405
+ END;
406
+
407
+ CREATE TRIGGER ${quoteIdentifier(`rtree_${layer}_geom_delete`)}
408
+ AFTER DELETE ON ${table}
409
+ WHEN OLD.${geom} NOT NULL
410
+ BEGIN
411
+ DELETE FROM ${rtree} WHERE id = OLD.${fid};
412
+ END;
413
+ `);
414
+ }
415
+
416
+ /**
417
+ * @param {Feature[]} features
418
+ * @returns {[number, number, number, number]}
419
+ */
420
+ function mergeFeatureBboxes(features) {
421
+ /** @type {[number, number, number, number] | null} */
422
+ let bbox = null;
423
+
424
+ for (const feature of features) {
425
+ const featureBbox = feature.bbox ?? geometryBbox(feature.geometry);
426
+ if (!featureBbox) {
427
+ continue;
428
+ }
429
+
430
+ if (!bbox) {
431
+ bbox = [...featureBbox];
432
+ continue;
433
+ }
434
+
435
+ bbox[0] = Math.min(bbox[0], featureBbox[0]);
436
+ bbox[1] = Math.min(bbox[1], featureBbox[1]);
437
+ bbox[2] = Math.max(bbox[2], featureBbox[2]);
438
+ bbox[3] = Math.max(bbox[3], featureBbox[3]);
439
+ }
440
+
441
+ return bbox ?? [0, 0, 0, 0];
442
+ }
443
+
444
+ /**
445
+ * Encode a GeoJSON-like geometry as GeoPackage binary geometry.
446
+ *
447
+ * @param {Geometry} geometry
448
+ * @returns {Buffer}
449
+ */
450
+ function encodeGeoPackageGeometry(geometry) {
451
+ const header = Buffer.alloc(8);
452
+ header.writeUInt8(0x47, 0);
453
+ header.writeUInt8(0x50, 1);
454
+ header.writeUInt8(0, 2);
455
+ header.writeUInt8(1, 3);
456
+ header.writeInt32LE(SRS_ID, 4);
457
+
458
+ return Buffer.concat([header, encodeWkbGeometry(geometry)]);
459
+ }
460
+
461
+ /**
462
+ * @param {Geometry} geometry
463
+ * @returns {Buffer}
464
+ */
465
+ function encodeWkbGeometry(geometry) {
466
+ switch (geometry.type) {
467
+ case 'Point':
468
+ return concatWkb([
469
+ uint8(1),
470
+ uint32(1),
471
+ double(/** @type {[number, number]} */ (geometry.coordinates)[0]),
472
+ double(/** @type {[number, number]} */ (geometry.coordinates)[1])
473
+ ]);
474
+
475
+ case 'LineString':
476
+ return encodeWkbLineString(/** @type {Array<[number, number]>} */ (geometry.coordinates));
477
+
478
+ case 'MultiLineString':
479
+ return concatWkb([
480
+ uint8(1),
481
+ uint32(5),
482
+ uint32(/** @type {Array<Array<[number, number]>>} */ (geometry.coordinates).length),
483
+ .../** @type {Array<Array<[number, number]>>} */ (geometry.coordinates).map(encodeWkbLineString)
484
+ ]);
485
+
486
+ case 'Polygon':
487
+ return encodeWkbPolygon(/** @type {Array<Array<[number, number]>>} */ (geometry.coordinates));
488
+
489
+ case 'MultiPolygon':
490
+ return concatWkb([
491
+ uint8(1),
492
+ uint32(6),
493
+ uint32(/** @type {Array<Array<Array<[number, number]>>>} */ (geometry.coordinates).length),
494
+ .../** @type {Array<Array<Array<[number, number]>>>} */ (geometry.coordinates).map(encodeWkbPolygon)
495
+ ]);
496
+
497
+ default:
498
+ throw new Error(`unsupported geometry type: ${geometry.type}`);
499
+ }
500
+ }
501
+
502
+ /**
503
+ * @param {Array<[number, number]>} coordinates
504
+ * @returns {Buffer}
505
+ */
506
+ function encodeWkbLineString(coordinates) {
507
+ return concatWkb([
508
+ uint8(1),
509
+ uint32(2),
510
+ uint32(coordinates.length),
511
+ ...coordinates.flatMap(([x, y]) => [double(x), double(y)])
512
+ ]);
513
+ }
514
+
515
+ /**
516
+ * @param {Array<Array<[number, number]>>} rings
517
+ * @returns {Buffer}
518
+ */
519
+ function encodeWkbPolygon(rings) {
520
+ return concatWkb([
521
+ uint8(1),
522
+ uint32(3),
523
+ uint32(rings.length),
524
+ ...rings.flatMap((ring) => [
525
+ uint32(ring.length),
526
+ ...ring.flatMap(([x, y]) => [double(x), double(y)])
527
+ ])
528
+ ]);
529
+ }
530
+
531
+ /**
532
+ * @param {Buffer[]} parts
533
+ * @returns {Buffer}
534
+ */
535
+ function concatWkb(parts) {
536
+ return Buffer.concat(parts);
537
+ }
538
+
539
+ /**
540
+ * @param {number} value
541
+ * @returns {Buffer}
542
+ */
543
+ function uint8(value) {
544
+ const buffer = Buffer.alloc(1);
545
+ buffer.writeUInt8(value);
546
+ return buffer;
547
+ }
548
+
549
+ /**
550
+ * @param {number} value
551
+ * @returns {Buffer}
552
+ */
553
+ function uint32(value) {
554
+ const buffer = Buffer.alloc(4);
555
+ buffer.writeUInt32LE(value);
556
+ return buffer;
557
+ }
558
+
559
+ /**
560
+ * @param {number} value
561
+ * @returns {Buffer}
562
+ */
563
+ function double(value) {
564
+ const buffer = Buffer.alloc(8);
565
+ buffer.writeDoubleLE(value);
566
+ return buffer;
567
+ }