@ferrybig/cgeodata-reader 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +313 -2
- package/binary-format.md +2366 -0
- package/dist/cache.d.ts +24 -0
- package/dist/cache.js +60 -0
- package/dist/cellBounds.d.ts +19 -0
- package/dist/cellBounds.js +36 -0
- package/dist/chunk.d.ts +34 -0
- package/dist/chunk.js +269 -0
- package/dist/chunkDecoder.d.ts +39 -0
- package/dist/chunkDecoder.js +116 -0
- package/dist/chunkDecoderWasm.d.ts +2 -0
- package/dist/chunkDecoderWasm.js +4 -0
- package/dist/cli/buildLocateIndex.d.ts +2 -0
- package/dist/cli/buildLocateIndex.js +46 -0
- package/dist/cli/buildSearchIndex.d.ts +2 -0
- package/dist/cli/buildSearchIndex.js +90 -0
- package/dist/cli/chunk.d.ts +2 -0
- package/dist/cli/chunk.js +67 -0
- package/dist/cli/info.d.ts +2 -0
- package/dist/cli/info.js +87 -0
- package/dist/cli/list.d.ts +2 -0
- package/dist/cli/list.js +71 -0
- package/dist/cli/locate.d.ts +2 -0
- package/dist/cli/locate.js +64 -0
- package/dist/cli/lookup.d.ts +2 -0
- package/dist/cli/lookup.js +128 -0
- package/dist/cli/search.d.ts +2 -0
- package/dist/cli/search.js +119 -0
- package/dist/cli/shared.d.ts +50 -0
- package/dist/cli/shared.js +140 -0
- package/dist/cli/show.d.ts +2 -0
- package/dist/cli/show.js +38 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/container.d.ts +17 -0
- package/dist/container.js +68 -0
- package/dist/directory.d.ts +21 -0
- package/dist/directory.js +31 -0
- package/dist/errors.d.ts +52 -0
- package/dist/errors.js +56 -0
- package/dist/format.d.ts +21 -0
- package/dist/format.js +22 -0
- package/dist/header.d.ts +29 -0
- package/dist/header.js +69 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +18 -0
- package/dist/indexes.d.ts +11 -0
- package/dist/indexes.js +12 -0
- package/dist/inflate.d.ts +21 -0
- package/dist/inflate.js +61 -0
- package/dist/locateIndex.d.ts +142 -0
- package/dist/locateIndex.js +561 -0
- package/dist/morton.d.ts +28 -0
- package/dist/morton.js +51 -0
- package/dist/node.d.ts +65 -0
- package/dist/node.js +137 -0
- package/dist/placeTable.d.ts +69 -0
- package/dist/placeTable.js +177 -0
- package/dist/reader.d.ts +534 -0
- package/dist/reader.js +1128 -0
- package/dist/relevance.d.ts +184 -0
- package/dist/relevance.js +266 -0
- package/dist/searchIndex.d.ts +148 -0
- package/dist/searchIndex.js +786 -0
- package/dist/source.d.ts +49 -0
- package/dist/source.js +50 -0
- package/dist/turns.d.ts +19 -0
- package/dist/turns.js +40 -0
- package/dist/varint.d.ts +52 -0
- package/dist/varint.js +137 -0
- package/package.json +76 -5
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A least-recently-used cache bounded by the byte size of what it holds,
|
|
3
|
+
* not by entry count: decoded chunks range from a few hundred bytes to
|
|
4
|
+
* several megabytes (5.4 MB for the largest on a worldwide build), so
|
|
5
|
+
* only a byte budget answers "how much memory does the reader need".
|
|
6
|
+
*
|
|
7
|
+
* A `Map` iterates in insertion order, so re-inserting an entry on every
|
|
8
|
+
* hit keeps the least recently used one first, where eviction takes it.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ByteBudgetCache<V> {
|
|
11
|
+
private readonly entries;
|
|
12
|
+
private totalBytes;
|
|
13
|
+
readonly maxBytes: number;
|
|
14
|
+
constructor(maxBytes: number);
|
|
15
|
+
/** Bytes currently held. */
|
|
16
|
+
get size(): number;
|
|
17
|
+
get(key: number): V | undefined;
|
|
18
|
+
/** Caches `value` as `bytes` bytes, evicting least recently used entries
|
|
19
|
+
* to stay within budget. A value bigger than the whole budget isn't
|
|
20
|
+
* cached at all, rather than emptying the cache for it. */
|
|
21
|
+
set(key: number, value: V, bytes: number): void;
|
|
22
|
+
delete(key: number): void;
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A least-recently-used cache bounded by the byte size of what it holds,
|
|
3
|
+
* not by entry count: decoded chunks range from a few hundred bytes to
|
|
4
|
+
* several megabytes (5.4 MB for the largest on a worldwide build), so
|
|
5
|
+
* only a byte budget answers "how much memory does the reader need".
|
|
6
|
+
*
|
|
7
|
+
* A `Map` iterates in insertion order, so re-inserting an entry on every
|
|
8
|
+
* hit keeps the least recently used one first, where eviction takes it.
|
|
9
|
+
*/
|
|
10
|
+
export class ByteBudgetCache {
|
|
11
|
+
entries = new Map();
|
|
12
|
+
totalBytes = 0;
|
|
13
|
+
maxBytes;
|
|
14
|
+
constructor(maxBytes) {
|
|
15
|
+
if (!(maxBytes >= 0)) {
|
|
16
|
+
throw new RangeError(`cache size must be a non-negative number of bytes, not ${maxBytes}`);
|
|
17
|
+
}
|
|
18
|
+
this.maxBytes = maxBytes;
|
|
19
|
+
}
|
|
20
|
+
/** Bytes currently held. */
|
|
21
|
+
get size() {
|
|
22
|
+
return this.totalBytes;
|
|
23
|
+
}
|
|
24
|
+
get(key) {
|
|
25
|
+
const entry = this.entries.get(key);
|
|
26
|
+
if (entry === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
this.entries.delete(key);
|
|
29
|
+
this.entries.set(key, entry);
|
|
30
|
+
return entry.value;
|
|
31
|
+
}
|
|
32
|
+
/** Caches `value` as `bytes` bytes, evicting least recently used entries
|
|
33
|
+
* to stay within budget. A value bigger than the whole budget isn't
|
|
34
|
+
* cached at all, rather than emptying the cache for it. */
|
|
35
|
+
set(key, value, bytes) {
|
|
36
|
+
this.delete(key);
|
|
37
|
+
if (bytes > this.maxBytes)
|
|
38
|
+
return;
|
|
39
|
+
this.entries.set(key, { value, bytes });
|
|
40
|
+
this.totalBytes += bytes;
|
|
41
|
+
for (const [oldest, { bytes: oldBytes }] of this.entries) {
|
|
42
|
+
if (this.totalBytes <= this.maxBytes)
|
|
43
|
+
break;
|
|
44
|
+
this.entries.delete(oldest);
|
|
45
|
+
this.totalBytes -= oldBytes;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
delete(key) {
|
|
49
|
+
const entry = this.entries.get(key);
|
|
50
|
+
if (entry === undefined)
|
|
51
|
+
return;
|
|
52
|
+
this.entries.delete(key);
|
|
53
|
+
this.totalBytes -= entry.bytes;
|
|
54
|
+
}
|
|
55
|
+
clear() {
|
|
56
|
+
this.entries.clear();
|
|
57
|
+
this.totalBytes = 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A lat/lon rectangle, half-open like the rest of the format (Section
|
|
2
|
+
* 2.3): `minLat <= lat < maxLat`, `minLon <= lon < maxLon`. */
|
|
3
|
+
export interface BoundingBox {
|
|
4
|
+
/** Southern edge, included. */
|
|
5
|
+
minLat: number;
|
|
6
|
+
/** Northern edge, excluded. */
|
|
7
|
+
maxLat: number;
|
|
8
|
+
/** Western edge, included. */
|
|
9
|
+
minLon: number;
|
|
10
|
+
/** Eastern edge, excluded. */
|
|
11
|
+
maxLon: number;
|
|
12
|
+
}
|
|
13
|
+
/** The lat/lon extent of chunk (c, r) in a W x H grid (Section 6.1). */
|
|
14
|
+
export declare function chunkBoundingBox(c: number, r: number, width: number, height: number): BoundingBox;
|
|
15
|
+
/** The lat/lon extent of cell (cellX, cellY) at the given depth within a
|
|
16
|
+
* chunk (Section 8.1: cell (0, 0) is the chunk's own south-west corner). */
|
|
17
|
+
export declare function cellBoundingBox(chunkBox: BoundingBox, cellX: number, cellY: number, depth: number): BoundingBox;
|
|
18
|
+
/** Whether `box` contains the point, by the half-open rule above. */
|
|
19
|
+
export declare function boundingBoxContains(box: BoundingBox, lat: number, lon: number): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** The lat/lon extent of chunk (c, r) in a W x H grid (Section 6.1). */
|
|
2
|
+
export function chunkBoundingBox(c, r, width, height) {
|
|
3
|
+
const lonWidth = 360 / width;
|
|
4
|
+
const latHeight = 180 / height;
|
|
5
|
+
const minLon = -180 + c * lonWidth;
|
|
6
|
+
const minLat = -90 + r * latHeight;
|
|
7
|
+
return {
|
|
8
|
+
minLon,
|
|
9
|
+
maxLon: minLon + lonWidth,
|
|
10
|
+
minLat,
|
|
11
|
+
maxLat: minLat + latHeight,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** The lat/lon extent of cell (cellX, cellY) at the given depth within a
|
|
15
|
+
* chunk (Section 8.1: cell (0, 0) is the chunk's own south-west corner). */
|
|
16
|
+
export function cellBoundingBox(chunkBox, cellX, cellY, depth) {
|
|
17
|
+
const cellsPerSide = 2 ** depth;
|
|
18
|
+
const lonWidth = (chunkBox.maxLon - chunkBox.minLon) / cellsPerSide;
|
|
19
|
+
const latHeight = (chunkBox.maxLat - chunkBox.minLat) / cellsPerSide;
|
|
20
|
+
const minLon = chunkBox.minLon + cellX * lonWidth;
|
|
21
|
+
const minLat = chunkBox.minLat + cellY * latHeight;
|
|
22
|
+
return {
|
|
23
|
+
minLon,
|
|
24
|
+
maxLon: minLon + lonWidth,
|
|
25
|
+
minLat,
|
|
26
|
+
maxLat: minLat + latHeight,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Whether `box` contains the point, by the half-open rule above. */
|
|
30
|
+
export function boundingBoxContains(box, lat, lon) {
|
|
31
|
+
return (lat >= box.minLat &&
|
|
32
|
+
lat < box.maxLat &&
|
|
33
|
+
lon >= box.minLon &&
|
|
34
|
+
lon < box.maxLon);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=cellBounds.js.map
|
package/dist/chunk.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** One chunk's records (Section 8), decoded: record `j` is the cell
|
|
2
|
+
* `codes[j]` at depth `depths[j]`, attributed to
|
|
3
|
+
* `palette[paletteIndices[j] - 1]` (or to no place for index 0). */
|
|
4
|
+
export interface ChunkRecords {
|
|
5
|
+
/** Global place ids referenced by this chunk, indexed 0..P-1. */
|
|
6
|
+
palette: number[];
|
|
7
|
+
/** Stored Morton codes, chunk-local, strictly increasing. Typed arrays
|
|
8
|
+
* rather than plain ones for this and the other per-record fields: a
|
|
9
|
+
* big chunk holds over a million records, and parsing and holding them
|
|
10
|
+
* as boxed JS arrays costs several times as much. 32 bits, as every
|
|
11
|
+
* code is below 4^`MAX_SUPPORTED_DEPTH`. */
|
|
12
|
+
codes: Uint32Array;
|
|
13
|
+
/** Each record's depth in the chunk's quadtree, 0 to `max_depth`. */
|
|
14
|
+
depths: Uint8Array;
|
|
15
|
+
/** Local palette index per record: 0 = "no place", k = palette[k-1].
|
|
16
|
+
* As wide as the file stores them (Section 8.3's S), rounding 3 bytes
|
|
17
|
+
* up to a `Uint32Array`. */
|
|
18
|
+
paletteIndices: Uint8Array | Uint16Array | Uint32Array;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The deepest `max_depth` this reader supports, though the format allows
|
|
22
|
+
* up to 26: every code is then below 4^16 = 2^32, so a decoded chunk
|
|
23
|
+
* holds its codes in 32 bits. Deeper files aren't worth reading in
|
|
24
|
+
* place: one chunk is one zlib block that a lookup inflates whole, and
|
|
25
|
+
* at depth 16 a chunk already spans up to 4^16 cells, so a lookup could
|
|
26
|
+
* inflate gigabytes. Finer cells should come from a bigger chunk grid
|
|
27
|
+
* (`width_exp`, `height_exp`) instead (binary-format.md Section 10).
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_SUPPORTED_DEPTH = 16;
|
|
30
|
+
/** Parses one already-inflated chunk block (Section 8.2, or Section 8.6's
|
|
31
|
+
* layout for a `version` 0 file) and checks the cheap Section 8.5
|
|
32
|
+
* invariants a reader can verify without external context (place ids
|
|
33
|
+
* are validated by the caller, which has the place table). */
|
|
34
|
+
export declare function parseChunk(buf: Uint8Array, maxDepth: number, version: number): ChunkRecords;
|
package/dist/chunk.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { decodeChunkCodes, WASM_MIN_RECORDS } from "./chunkDecoder.js";
|
|
2
|
+
import { CGeoDataFormatError } from "./errors.js";
|
|
3
|
+
import { readDeltaVarintList, readDeltaVarints32Into, readVarint, } from "./varint.js";
|
|
4
|
+
/**
|
|
5
|
+
* The deepest `max_depth` this reader supports, though the format allows
|
|
6
|
+
* up to 26: every code is then below 4^16 = 2^32, so a decoded chunk
|
|
7
|
+
* holds its codes in 32 bits. Deeper files aren't worth reading in
|
|
8
|
+
* place: one chunk is one zlib block that a lookup inflates whole, and
|
|
9
|
+
* at depth 16 a chunk already spans up to 4^16 cells, so a lookup could
|
|
10
|
+
* inflate gigabytes. Finer cells should come from a bigger chunk grid
|
|
11
|
+
* (`width_exp`, `height_exp`) instead (binary-format.md Section 10).
|
|
12
|
+
*/
|
|
13
|
+
export const MAX_SUPPORTED_DEPTH = 16;
|
|
14
|
+
/** The index width S from Section 8.3's table. */
|
|
15
|
+
function paletteIndexWidth(paletteCount) {
|
|
16
|
+
if (paletteCount <= 255)
|
|
17
|
+
return 1;
|
|
18
|
+
if (paletteCount <= 65535)
|
|
19
|
+
return 2;
|
|
20
|
+
if (paletteCount <= 2 ** 24 - 1)
|
|
21
|
+
return 3;
|
|
22
|
+
return 4;
|
|
23
|
+
}
|
|
24
|
+
/** Whether typed arrays here are little-endian, as the file's integers
|
|
25
|
+
* are (true on every mainstream platform), so 2- and 4-byte palette
|
|
26
|
+
* indices can be copied in rather than assembled byte by byte. */
|
|
27
|
+
const LITTLE_ENDIAN = new Uint8Array(Uint16Array.of(1).buffer)[0] === 1;
|
|
28
|
+
/** `n` unsigned `width`-byte little-endian integers (Section 8.3's
|
|
29
|
+
* palette indices) from `buf` at `offset`, in the narrowest typed array
|
|
30
|
+
* that holds `width` bytes. Always a copy, so the inflated block can be
|
|
31
|
+
* freed. */
|
|
32
|
+
function readLittleEndian(buf, offset, width, n) {
|
|
33
|
+
switch (width) {
|
|
34
|
+
case 1:
|
|
35
|
+
// Not `buf.slice()`: the block is a Buffer, whose `slice` returns
|
|
36
|
+
// a view.
|
|
37
|
+
return new Uint8Array(buf.subarray(offset, offset + n));
|
|
38
|
+
case 2: {
|
|
39
|
+
const out = new Uint16Array(n);
|
|
40
|
+
if (LITTLE_ENDIAN) {
|
|
41
|
+
new Uint8Array(out.buffer).set(buf.subarray(offset, offset + 2 * n));
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0, b = offset; i < n; i++, b += 2)
|
|
45
|
+
out[i] = buf[b] | (buf[b + 1] << 8);
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
case 3: {
|
|
49
|
+
const out = new Uint32Array(n);
|
|
50
|
+
for (let i = 0, b = offset; i < n; i++, b += 3) {
|
|
51
|
+
out[i] = buf[b] | (buf[b + 1] << 8) | (buf[b + 2] << 16);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
default: {
|
|
56
|
+
const out = new Uint32Array(n);
|
|
57
|
+
if (LITTLE_ENDIAN) {
|
|
58
|
+
new Uint8Array(out.buffer).set(buf.subarray(offset, offset + 4 * n));
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0, b = offset; i < n; i++, b += 4) {
|
|
62
|
+
out[i] =
|
|
63
|
+
buf[b] |
|
|
64
|
+
(buf[b + 1] << 8) |
|
|
65
|
+
(buf[b + 2] << 16) |
|
|
66
|
+
(buf[b + 3] << 24);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Whether the power of two `span` divides `value`, both below 2^53: a
|
|
73
|
+
* bitwise check while they fit in 31 bits (max_depth 15 and under), else
|
|
74
|
+
* a double modulo, which is exact but several times slower. */
|
|
75
|
+
function isMultipleOf(value, span) {
|
|
76
|
+
return value < 0x80000000 ? (value & (span - 1)) === 0 : value % span === 0;
|
|
77
|
+
}
|
|
78
|
+
/** Parses one already-inflated chunk block (Section 8.2, or Section 8.6's
|
|
79
|
+
* layout for a `version` 0 file) and checks the cheap Section 8.5
|
|
80
|
+
* invariants a reader can verify without external context (place ids
|
|
81
|
+
* are validated by the caller, which has the place table). */
|
|
82
|
+
export function parseChunk(buf, maxDepth, version) {
|
|
83
|
+
if (maxDepth > MAX_SUPPORTED_DEPTH) {
|
|
84
|
+
throw new RangeError(`max_depth ${maxDepth} is past the ${MAX_SUPPORTED_DEPTH} this reader supports`);
|
|
85
|
+
}
|
|
86
|
+
let offset = 0;
|
|
87
|
+
const { value: paletteCount, bytesRead: b1 } = readVarint(buf, offset);
|
|
88
|
+
offset += b1;
|
|
89
|
+
const { values: palette, bytesRead: b2 } = readDeltaVarintList(buf, offset, paletteCount);
|
|
90
|
+
offset += b2;
|
|
91
|
+
const { value: recordCount, bytesRead: b3 } = readVarint(buf, offset);
|
|
92
|
+
offset += b3;
|
|
93
|
+
if (version !== 0) {
|
|
94
|
+
return parseRecords(buf, offset, palette, recordCount, maxDepth);
|
|
95
|
+
}
|
|
96
|
+
const { value: codesLength, bytesRead: b4 } = readVarint(buf, offset);
|
|
97
|
+
offset += b4;
|
|
98
|
+
// Every code takes at least one byte, so a count the block can't hold
|
|
99
|
+
// fails here instead of allocating for it.
|
|
100
|
+
if (offset + recordCount > buf.length) {
|
|
101
|
+
throw new CGeoDataFormatError("chunk codes run past end of block");
|
|
102
|
+
}
|
|
103
|
+
const codesStart = offset;
|
|
104
|
+
const indexWidth = paletteIndexWidth(paletteCount);
|
|
105
|
+
const depthsStart = codesStart + codesLength;
|
|
106
|
+
if (recordCount >= WASM_MIN_RECORDS &&
|
|
107
|
+
paletteCount < 2 ** 32 &&
|
|
108
|
+
depthsStart + recordCount * (1 + indexWidth) === buf.length) {
|
|
109
|
+
// The fast path (./chunkDecoder.ts) for a block whose size adds up;
|
|
110
|
+
// a declined block falls through to be parsed below.
|
|
111
|
+
const codes = decodeChunkCodes(buf, codesStart, codesLength, recordCount, maxDepth, paletteCount, indexWidth);
|
|
112
|
+
if (codes) {
|
|
113
|
+
return {
|
|
114
|
+
palette,
|
|
115
|
+
codes,
|
|
116
|
+
depths: new Uint8Array(buf.subarray(depthsStart, depthsStart + recordCount)),
|
|
117
|
+
paletteIndices: readLittleEndian(buf, depthsStart + recordCount, indexWidth, recordCount),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const codes = new Uint32Array(recordCount);
|
|
122
|
+
const codesBytesRead = readDeltaVarints32Into(buf, codesStart, codes) - codesStart;
|
|
123
|
+
if (codesBytesRead !== codesLength) {
|
|
124
|
+
throw new CGeoDataFormatError(`chunk codes_length mismatch: header says ${codesLength}, decoding consumed ${codesBytesRead}`);
|
|
125
|
+
}
|
|
126
|
+
offset = codesStart + codesLength;
|
|
127
|
+
if (offset + recordCount > buf.length) {
|
|
128
|
+
throw new CGeoDataFormatError("chunk depths array runs past end of block");
|
|
129
|
+
}
|
|
130
|
+
// A copy, not a view, so the inflated block can be freed. Not
|
|
131
|
+
// `buf.slice()`: the block is a Buffer, whose `slice` returns a view.
|
|
132
|
+
const depths = new Uint8Array(buf.subarray(offset, offset + recordCount));
|
|
133
|
+
offset += recordCount;
|
|
134
|
+
const indicesBytes = recordCount * indexWidth;
|
|
135
|
+
if (offset + indicesBytes > buf.length) {
|
|
136
|
+
throw new CGeoDataFormatError("chunk palette_indices array runs past end of block");
|
|
137
|
+
}
|
|
138
|
+
const paletteIndices = readLittleEndian(buf, offset, indexWidth, recordCount);
|
|
139
|
+
offset += indicesBytes;
|
|
140
|
+
if (offset !== buf.length) {
|
|
141
|
+
throw new CGeoDataFormatError("chunk block has trailing bytes after palette_indices");
|
|
142
|
+
}
|
|
143
|
+
// span[d]: how many max-depth cells a depth-d record covers, 4^(D-d).
|
|
144
|
+
// A stored code is valid for depth d exactly when its low 2 x (D-d) bits
|
|
145
|
+
// are zero, i.e. when span[d] divides it -- exact in doubles, since codes
|
|
146
|
+
// and spans are both below 2^53.
|
|
147
|
+
const span = [];
|
|
148
|
+
for (let d = 0; d <= maxDepth; d++)
|
|
149
|
+
span.push(4 ** (maxDepth - d));
|
|
150
|
+
const maxCode = 4 ** maxDepth;
|
|
151
|
+
for (let j = 0; j < recordCount; j++) {
|
|
152
|
+
const depth = depths[j];
|
|
153
|
+
const code = codes[j];
|
|
154
|
+
if (depth > maxDepth) {
|
|
155
|
+
throw new CGeoDataFormatError(`record ${j} has depth ${depth} > max_depth ${maxDepth}`);
|
|
156
|
+
}
|
|
157
|
+
if (!isMultipleOf(code, span[depth])) {
|
|
158
|
+
throw new CGeoDataFormatError(`record ${j}'s code is not a valid stored code for depth ${depth}`);
|
|
159
|
+
}
|
|
160
|
+
if (code >= maxCode) {
|
|
161
|
+
throw new CGeoDataFormatError(`record ${j}'s code ${code} is not less than 4^max_depth`);
|
|
162
|
+
}
|
|
163
|
+
if (paletteIndices[j] > paletteCount) {
|
|
164
|
+
throw new CGeoDataFormatError(`record ${j} has palette index ${paletteIndices[j]} > P (${paletteCount})`);
|
|
165
|
+
}
|
|
166
|
+
if (j > 0) {
|
|
167
|
+
const prevCode = codes[j - 1];
|
|
168
|
+
if (!(prevCode < code)) {
|
|
169
|
+
throw new CGeoDataFormatError(`chunk codes are not strictly increasing at record ${j}`);
|
|
170
|
+
}
|
|
171
|
+
if (prevCode + span[depths[j - 1]] > code) {
|
|
172
|
+
throw new CGeoDataFormatError(`record ${j - 1} overlaps record ${j}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { palette, codes, depths, paletteIndices };
|
|
177
|
+
}
|
|
178
|
+
/** The rest of a chunk block from its `depths` on (Section 8.2): the
|
|
179
|
+
* records tile the chunk from code 0, so each code is where the record
|
|
180
|
+
* before it ends (Section 8.4). */
|
|
181
|
+
function parseRecords(buf, offset, palette, recordCount, maxDepth) {
|
|
182
|
+
const paletteCount = palette.length;
|
|
183
|
+
const indexWidth = paletteIndexWidth(paletteCount);
|
|
184
|
+
const end = offset + recordCount * (1 + indexWidth);
|
|
185
|
+
if (end > buf.length) {
|
|
186
|
+
throw new CGeoDataFormatError("chunk depths and palette_indices run past end of block");
|
|
187
|
+
}
|
|
188
|
+
if (end !== buf.length) {
|
|
189
|
+
throw new CGeoDataFormatError("chunk block has trailing bytes after palette_indices");
|
|
190
|
+
}
|
|
191
|
+
// A copy, not a view, so the inflated block can be freed. Not
|
|
192
|
+
// `buf.slice()`: the block is a Buffer, whose `slice` returns a view.
|
|
193
|
+
const depths = new Uint8Array(buf.subarray(offset, offset + recordCount));
|
|
194
|
+
const paletteIndices = readLittleEndian(buf, offset + recordCount, indexWidth, recordCount);
|
|
195
|
+
const codes = new Uint32Array(recordCount);
|
|
196
|
+
if (!deriveCodes(depths, maxDepth, codes)) {
|
|
197
|
+
throw recordError(depths, maxDepth);
|
|
198
|
+
}
|
|
199
|
+
let maxIndex = 0;
|
|
200
|
+
for (let j = 0; j < recordCount; j++) {
|
|
201
|
+
const k = paletteIndices[j];
|
|
202
|
+
if (k > maxIndex)
|
|
203
|
+
maxIndex = k;
|
|
204
|
+
}
|
|
205
|
+
if (maxIndex > paletteCount) {
|
|
206
|
+
const j = paletteIndices.findIndex((k) => k > paletteCount);
|
|
207
|
+
throw new CGeoDataFormatError(`record ${j} has palette index ${paletteIndices[j]} > P (${paletteCount})`);
|
|
208
|
+
}
|
|
209
|
+
return { palette, codes, depths, paletteIndices };
|
|
210
|
+
}
|
|
211
|
+
/** Fills `codes` with each record's code (Section 8.4), or returns false
|
|
212
|
+
* when a record breaks invariant 1, 2 or 3 of Section 8.5, for
|
|
213
|
+
* `recordError` to say which. In integers while every code and span fits
|
|
214
|
+
* in 31 bits (max_depth 15 and under), else in doubles. */
|
|
215
|
+
function deriveCodes(depths, maxDepth, codes) {
|
|
216
|
+
const n = depths.length;
|
|
217
|
+
// span[d]: how many max-depth cells a depth-d record covers, 4^(D-d);
|
|
218
|
+
// 0 past max_depth.
|
|
219
|
+
if (maxDepth <= 15) {
|
|
220
|
+
const span = new Int32Array(256);
|
|
221
|
+
for (let d = 0; d <= maxDepth; d++)
|
|
222
|
+
span[d] = 1 << (2 * (maxDepth - d));
|
|
223
|
+
const maxCode = 1 << (2 * maxDepth);
|
|
224
|
+
let code = 0;
|
|
225
|
+
for (let j = 0; j < n; j++) {
|
|
226
|
+
const size = span[depths[j]];
|
|
227
|
+
if (size === 0 || (code & (size - 1)) !== 0 || code >= maxCode) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
codes[j] = code;
|
|
231
|
+
code += size;
|
|
232
|
+
}
|
|
233
|
+
return code <= maxCode;
|
|
234
|
+
}
|
|
235
|
+
const span = new Float64Array(256);
|
|
236
|
+
for (let d = 0; d <= maxDepth; d++)
|
|
237
|
+
span[d] = 4 ** (maxDepth - d);
|
|
238
|
+
const maxCode = 4 ** maxDepth;
|
|
239
|
+
let code = 0;
|
|
240
|
+
for (let j = 0; j < n; j++) {
|
|
241
|
+
const size = span[depths[j]];
|
|
242
|
+
if (size === 0 || code % size !== 0 || code >= maxCode)
|
|
243
|
+
return false;
|
|
244
|
+
codes[j] = code;
|
|
245
|
+
code += size;
|
|
246
|
+
}
|
|
247
|
+
return code <= maxCode;
|
|
248
|
+
}
|
|
249
|
+
/** The error for the first record `deriveCodes` refused. */
|
|
250
|
+
function recordError(depths, maxDepth) {
|
|
251
|
+
const maxCode = 4 ** maxDepth;
|
|
252
|
+
let code = 0;
|
|
253
|
+
for (let j = 0; j < depths.length; j++) {
|
|
254
|
+
const depth = depths[j];
|
|
255
|
+
if (depth > maxDepth) {
|
|
256
|
+
return new CGeoDataFormatError(`record ${j} has depth ${depth} > max_depth ${maxDepth}`);
|
|
257
|
+
}
|
|
258
|
+
if (code >= maxCode) {
|
|
259
|
+
return new CGeoDataFormatError(`record ${j} starts past the end of the chunk`);
|
|
260
|
+
}
|
|
261
|
+
const size = 4 ** (maxDepth - depth);
|
|
262
|
+
if (code % size !== 0) {
|
|
263
|
+
return new CGeoDataFormatError(`record ${j} at code ${code} is not aligned to its depth ${depth}`);
|
|
264
|
+
}
|
|
265
|
+
code += size;
|
|
266
|
+
}
|
|
267
|
+
return new CGeoDataFormatError(`record ${depths.length - 1} runs past the end of the chunk`);
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=chunk.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `parseChunk`'s fast path for a version 0 file, whose chunks store their
|
|
3
|
+
* codes (binary-format.md Section 8.6): wasm/chunkDecoder.wat, a
|
|
4
|
+
* WebAssembly SIMD decoder for those codes that checks every invariant
|
|
5
|
+
* `parseChunk` does. A version 1 chunk has no codes to decode. It only
|
|
6
|
+
* ever accepts or declines a block: a declined one --
|
|
7
|
+
* malformed, or valid but unusual -- is parsed again in JavaScript,
|
|
8
|
+
* which throws the error or returns the records. Platforms without
|
|
9
|
+
* WebAssembly SIMD (or that forbid compiling it, such as a page whose
|
|
10
|
+
* CSP lacks `'wasm-unsafe-eval'`) always take the JavaScript path.
|
|
11
|
+
*
|
|
12
|
+
* Memory: one instance, its memory grown to the largest block decoded
|
|
13
|
+
* so far (3.3 MB for world.bin's largest chunk), kept between
|
|
14
|
+
* decodes so a burst of them reuses it -- allocating WebAssembly memory
|
|
15
|
+
* costs 25-100 µs -- and dropped once no decode has used it for
|
|
16
|
+
* `IDLE_RELEASE_MS`, so an idle reader holds none.
|
|
17
|
+
*/
|
|
18
|
+
/** Below this many records the JavaScript parser is as fast: on
|
|
19
|
+
* world.bin the fast path is level at 16-32 records, 2x faster at 128,
|
|
20
|
+
* and 5-6x faster from 4096 up. */
|
|
21
|
+
export declare const WASM_MIN_RECORDS = 64;
|
|
22
|
+
/** Turns the fast path off (or back on), for tests and benchmarks that
|
|
23
|
+
* compare it against the JavaScript parser. */
|
|
24
|
+
export declare function useWasmChunkDecoder(on: boolean): void;
|
|
25
|
+
/** How many bytes of WebAssembly memory the fast path holds right now:
|
|
26
|
+
* 0 while idle. */
|
|
27
|
+
export declare function wasmChunkDecoderBytes(): number;
|
|
28
|
+
/** Whether this platform runs the fast path. */
|
|
29
|
+
export declare function wasmChunkDecoderAvailable(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The codes of the version 0 chunk block `buf` (Section 8.6), whose
|
|
32
|
+
* codes start at `codesStart`, checked against every Section 8.5 invariant `parseChunk`
|
|
33
|
+
* checks -- or undefined when the fast path declined it or isn't
|
|
34
|
+
* available, and the caller should parse the block in JavaScript. The
|
|
35
|
+
* caller has checked that the block is exactly as long as its counts
|
|
36
|
+
* say, that `maxDepth` is at most 16 (`parseChunk` refuses more) and
|
|
37
|
+
* `paletteCount` below 2^32.
|
|
38
|
+
*/
|
|
39
|
+
export declare function decodeChunkCodes(buf: Uint8Array, codesStart: number, codesLength: number, recordCount: number, maxDepth: number, paletteCount: number, indexWidth: number): Uint32Array | undefined;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { CHUNK_DECODER_WASM } from "./chunkDecoderWasm.js";
|
|
2
|
+
/**
|
|
3
|
+
* `parseChunk`'s fast path for a version 0 file, whose chunks store their
|
|
4
|
+
* codes (binary-format.md Section 8.6): wasm/chunkDecoder.wat, a
|
|
5
|
+
* WebAssembly SIMD decoder for those codes that checks every invariant
|
|
6
|
+
* `parseChunk` does. A version 1 chunk has no codes to decode. It only
|
|
7
|
+
* ever accepts or declines a block: a declined one --
|
|
8
|
+
* malformed, or valid but unusual -- is parsed again in JavaScript,
|
|
9
|
+
* which throws the error or returns the records. Platforms without
|
|
10
|
+
* WebAssembly SIMD (or that forbid compiling it, such as a page whose
|
|
11
|
+
* CSP lacks `'wasm-unsafe-eval'`) always take the JavaScript path.
|
|
12
|
+
*
|
|
13
|
+
* Memory: one instance, its memory grown to the largest block decoded
|
|
14
|
+
* so far (3.3 MB for world.bin's largest chunk), kept between
|
|
15
|
+
* decodes so a burst of them reuses it -- allocating WebAssembly memory
|
|
16
|
+
* costs 25-100 µs -- and dropped once no decode has used it for
|
|
17
|
+
* `IDLE_RELEASE_MS`, so an idle reader holds none.
|
|
18
|
+
*/
|
|
19
|
+
/** Below this many records the JavaScript parser is as fast: on
|
|
20
|
+
* world.bin the fast path is level at 16-32 records, 2x faster at 128,
|
|
21
|
+
* and 5-6x faster from 4096 up. */
|
|
22
|
+
export const WASM_MIN_RECORDS = 64;
|
|
23
|
+
/** How long the instance outlives its last decode. */
|
|
24
|
+
const IDLE_RELEASE_MS = 1000;
|
|
25
|
+
const PAGE_BYTES = 65536;
|
|
26
|
+
/** The compiled module; null once compiling failed. */
|
|
27
|
+
let compiled;
|
|
28
|
+
let instance;
|
|
29
|
+
let releaseTimer;
|
|
30
|
+
let enabled = true;
|
|
31
|
+
/** Turns the fast path off (or back on), for tests and benchmarks that
|
|
32
|
+
* compare it against the JavaScript parser. */
|
|
33
|
+
export function useWasmChunkDecoder(on) {
|
|
34
|
+
enabled = on;
|
|
35
|
+
if (!on)
|
|
36
|
+
release();
|
|
37
|
+
}
|
|
38
|
+
/** How many bytes of WebAssembly memory the fast path holds right now:
|
|
39
|
+
* 0 while idle. */
|
|
40
|
+
export function wasmChunkDecoderBytes() {
|
|
41
|
+
return instance?.memory.buffer.byteLength ?? 0;
|
|
42
|
+
}
|
|
43
|
+
/** Whether this platform runs the fast path. */
|
|
44
|
+
export function wasmChunkDecoderAvailable() {
|
|
45
|
+
return enabled && getInstance() !== undefined;
|
|
46
|
+
}
|
|
47
|
+
function getInstance() {
|
|
48
|
+
if (instance)
|
|
49
|
+
return instance;
|
|
50
|
+
if (compiled === undefined) {
|
|
51
|
+
try {
|
|
52
|
+
// Throws a ReferenceError where there's no WebAssembly at all.
|
|
53
|
+
compiled = new WebAssembly.Module(Uint8Array.from(atob(CHUNK_DECODER_WASM), (c) => c.charCodeAt(0)));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
compiled = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (compiled === null)
|
|
60
|
+
return undefined;
|
|
61
|
+
const exports = new WebAssembly.Instance(compiled).exports;
|
|
62
|
+
instance = {
|
|
63
|
+
memory: exports.memory,
|
|
64
|
+
parse: exports.parse,
|
|
65
|
+
};
|
|
66
|
+
return instance;
|
|
67
|
+
}
|
|
68
|
+
function release() {
|
|
69
|
+
instance = undefined;
|
|
70
|
+
if (releaseTimer !== undefined)
|
|
71
|
+
clearTimeout(releaseTimer);
|
|
72
|
+
releaseTimer = undefined;
|
|
73
|
+
}
|
|
74
|
+
function releaseWhenIdle() {
|
|
75
|
+
if (releaseTimer !== undefined)
|
|
76
|
+
clearTimeout(releaseTimer);
|
|
77
|
+
releaseTimer = setTimeout(release, IDLE_RELEASE_MS);
|
|
78
|
+
// Node.js: don't keep the process alive just to free this.
|
|
79
|
+
releaseTimer.unref?.();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The codes of the version 0 chunk block `buf` (Section 8.6), whose
|
|
83
|
+
* codes start at `codesStart`, checked against every Section 8.5 invariant `parseChunk`
|
|
84
|
+
* checks -- or undefined when the fast path declined it or isn't
|
|
85
|
+
* available, and the caller should parse the block in JavaScript. The
|
|
86
|
+
* caller has checked that the block is exactly as long as its counts
|
|
87
|
+
* say, that `maxDepth` is at most 16 (`parseChunk` refuses more) and
|
|
88
|
+
* `paletteCount` below 2^32.
|
|
89
|
+
*/
|
|
90
|
+
export function decodeChunkCodes(buf, codesStart, codesLength, recordCount, maxDepth, paletteCount, indexWidth) {
|
|
91
|
+
if (!enabled)
|
|
92
|
+
return undefined;
|
|
93
|
+
const decoder = getInstance();
|
|
94
|
+
if (!decoder)
|
|
95
|
+
return undefined;
|
|
96
|
+
const blockBytes = buf.length - codesStart;
|
|
97
|
+
// 16 bytes of slack past the block: a 3-byte index is read as 4.
|
|
98
|
+
const out = Math.ceil((blockBytes + 16) / 16) * 16;
|
|
99
|
+
const needed = out + 4 * recordCount;
|
|
100
|
+
const { memory } = decoder;
|
|
101
|
+
if (memory.buffer.byteLength < needed) {
|
|
102
|
+
try {
|
|
103
|
+
memory.grow(Math.ceil((needed - memory.buffer.byteLength) / PAGE_BYTES));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined; // out of memory: leave it to the JavaScript path
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
new Uint8Array(memory.buffer, 0, blockBytes).set(buf.subarray(codesStart));
|
|
110
|
+
const ok = decoder.parse(codesLength, recordCount, maxDepth, paletteCount, indexWidth, out);
|
|
111
|
+
releaseWhenIdle();
|
|
112
|
+
if (!ok)
|
|
113
|
+
return undefined;
|
|
114
|
+
return new Uint32Array(memory.buffer, out, recordCount).slice();
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=chunkDecoder.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** chunkDecoder.wat's compiled module (1553 bytes), base64. */
|
|
2
|
+
export declare const CHUNK_DECODER_WASM = "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";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Generated by `npm run build:wasm -w reader` from wasm/chunkDecoder.wat; do not edit.
|
|
2
|
+
/** chunkDecoder.wat's compiled module (1553 bytes), base64. */
|
|
3
|
+
export const CHUNK_DECODER_WASM = "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";
|
|
4
|
+
//# sourceMappingURL=chunkDecoderWasm.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { buildLocateIndex } from "../locateIndex.js";
|
|
3
|
+
import { EXIT_OK, openReader, parse, UsageError, } from "./shared.js";
|
|
4
|
+
export const buildLocateIndexCmd = {
|
|
5
|
+
name: "build-locate-index",
|
|
6
|
+
summary: "build a place location index for a .bin file",
|
|
7
|
+
usage: `usage: cgeodata build-locate-index <file.bin> [--output <index.bin>]
|
|
8
|
+
|
|
9
|
+
Computes a representative cell for every place in <file.bin> and writes
|
|
10
|
+
the result as a locate index to <index.bin> (or stdout when --output is
|
|
11
|
+
omitted). Written next to <file.bin> as <file>.locate-index.bin,
|
|
12
|
+
\`cgeodata locate\` and \`search --locate\` (and any CGeoDataReader opened
|
|
13
|
+
from that path) use it automatically; elsewhere, pass it with
|
|
14
|
+
\`locate --index\`. Turns a locate from a scan of every chunk into a
|
|
15
|
+
read of one entry.
|
|
16
|
+
|
|
17
|
+
--output F write to F instead of stdout`,
|
|
18
|
+
async run(argv) {
|
|
19
|
+
const { values, positionals } = parse(argv, {
|
|
20
|
+
output: { type: "string" },
|
|
21
|
+
});
|
|
22
|
+
const [file, ...extra] = positionals;
|
|
23
|
+
if (extra.length > 0)
|
|
24
|
+
throw new UsageError(`unexpected argument ${JSON.stringify(extra[0])}`);
|
|
25
|
+
const reader = await openReader(file);
|
|
26
|
+
const { countries, regions, cities } = reader.placeCounts;
|
|
27
|
+
process.stderr.write(`building locate index over ${countries + regions + cities} places...\n`);
|
|
28
|
+
const t0 = performance.now();
|
|
29
|
+
let bytes;
|
|
30
|
+
try {
|
|
31
|
+
bytes = await buildLocateIndex(reader);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
await reader.close();
|
|
35
|
+
}
|
|
36
|
+
process.stderr.write(`done in ${((performance.now() - t0) / 1000).toFixed(1)} s, ${(bytes.length / 1024 / 1024).toFixed(1)} MiB\n`);
|
|
37
|
+
if (values.output !== undefined) {
|
|
38
|
+
writeFileSync(values.output, bytes);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
process.stdout.write(bytes);
|
|
42
|
+
}
|
|
43
|
+
return EXIT_OK;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=buildLocateIndex.js.map
|