@ferrybig/cgeodata-reader 0.1.0 → 0.1.1

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/binary-format.md CHANGED
@@ -184,6 +184,11 @@ self-contained: it can be inflated without any other block. Readers
184
184
  locate a block by the byte lengths recorded in the file, not by scanning
185
185
  for stream boundaries.
186
186
 
187
+ In a version `2` file ([Section 5](#5-header)) chunks are not compressed
188
+ blocks: a chunk is stored as [Section 8.7](#87-version-2-chunks) lays it
189
+ out, with its own entropy coding. Name blocks are compressed blocks in
190
+ every version.
191
+
187
192
  ## 4. File Layout
188
193
 
189
194
  A CGEODATA file consists of four consecutive sections, with no padding or
@@ -223,7 +228,7 @@ and a CGEODATA file never refers to them.
223
228
  | Field | Type | Description |
224
229
  |----------------------|------------|-------------|
225
230
  | `signature` | 8 bytes | The ASCII bytes `CGEODATA` (`43 47 45 4f 44 41 54 41`). |
226
- | `version` | varint | File kind and format version, `kind × 16 + version`. A CGEODATA file is kind `0`; this document specifies version `1`, so the field is `1`. See below. |
231
+ | `version` | varint | File kind and format version, `kind × 16 + version`. A CGEODATA file is kind `0`; this document specifies version `2`, so the field is `2`. See below. |
227
232
  | `attribution_length` | varint | Byte length of `attribution`. |
228
233
  | `attribution` | bytes | UTF-8 text, MAY contain Markdown, intended for display to humans, for example `[OpenStreetMap](https://openstreetmap.org/copyright)`. MAY be empty. |
229
234
  | `width_exp` | u8 | The chunk grid has `W = 2^width_exp` columns. |
@@ -253,10 +258,12 @@ Version `0` of any kind is a pre-release version: files written under
253
258
  this document carry no compatibility promise once the version number
254
259
  changes.
255
260
 
256
- Version `1` of kind `0` differs from version `0` only in the layout of a
257
- chunk: version `0` stored every record's Morton code, which version `1`
258
- derives from the depths ([Section 8.6](#86-version-0-chunks)). A reader
259
- MAY read both.
261
+ Versions `0`, `1` and `2` of kind `0` differ only in how a chunk is
262
+ stored. Version `0` stored every record's Morton code, which version `1`
263
+ derives from the depths ([Section 8.6](#86-version-0-chunks)). Version
264
+ `2` keeps version `1`'s records but stores them range coded instead of
265
+ in a zlib stream ([Section 8.7](#87-version-2-chunks)). A reader MAY read
266
+ all three.
260
267
 
261
268
  Constraints:
262
269
 
@@ -641,6 +648,201 @@ million records took 3.77 million index-`0` records (3.5% more records)
641
648
  to fill, and the chunks went from 62.2 MB to 45.6 MB (-26.7%), with the
642
649
  same cell for every place.
643
650
 
651
+ ### 8.7. Version 2 Chunks
652
+
653
+ In a version `2` file ([Section 5](#5-header)), a chunk's records are
654
+ those of a version `1` chunk ([Sections 8.4](#84-records) and
655
+ [8.5](#85-invariants)): they tile the chunk from code `0` in Morton order,
656
+ and MAY end before the chunk does. Only their storage differs. The block
657
+ is not a zlib stream:
658
+
659
+ | Field | Type | Description |
660
+ |-------------------|----------------------------|-------------|
661
+ | `palette_count` | varint | As in Section 8.2, and below `2^26`. |
662
+ | `palette` | `P` varints, delta-coded | As in Section 8.2. |
663
+ | `record_count` | varint | As in Section 8.2. |
664
+ | `stream` | bytes | The rest of the block: the records' depths and palette indices, range coded as described below. |
665
+
666
+ The stream codes the chunk's quadtree as a series of binary decisions:
667
+ whether each node splits, and, for each leaf, which palette index it has.
668
+ Every decision is made with an adaptive probability chosen by a
669
+ **context** from what was already decoded: chiefly the records to the
670
+ west and south of the node, which in Morton order always come first.
671
+ Neighbouring cells usually belong to the same place, so most decisions
672
+ are nearly certain and cost a small fraction of a bit.
673
+
674
+ #### 8.7.1. Range Decoder
675
+
676
+ The decoder keeps two unsigned 32-bit integers, `range` and `code`, and a
677
+ position `pos` in `stream`. Each probability `p` is an integer in
678
+ `[1, 4095]`, the chance out of `4096` that a decision is `0`; every
679
+ probability starts at `2048`.
680
+
681
+ `stream[0]` MUST be `0`. `code` starts as `stream[1..4]` read as a
682
+ big-endian integer, and MUST NOT be `0xFFFFFFFF`; `range` starts as
683
+ `0xFFFFFFFF` and `pos` as `5`. A decision with probability `p` is decoded
684
+ as follows, and `p` is updated in place:
685
+
686
+ ```
687
+ bound = (range >> 12) × p
688
+ if code < bound:
689
+ range = bound
690
+ p = p + ((4096 - p) >> 4)
691
+ bit = 0
692
+ else:
693
+ code = code - bound
694
+ range = range - bound
695
+ p = p - (p >> 4)
696
+ bit = 1
697
+ while range < 2^24:
698
+ range = range × 256
699
+ code = code × 256 + stream[pos] # reading past the end is an error
700
+ pos = pos + 1
701
+ ```
702
+
703
+ After the last record, `pos` MUST equal the length of `stream`. This is
704
+ LZMA's range coder [LZMA] with 12-bit probabilities that adapt by a
705
+ sixteenth each time. The matching encoder (informative):
706
+
707
+ ```
708
+ low = 0 (at least 33 bits), range = 0xFFFFFFFF, cache = 0, pending = 1
709
+
710
+ encode(bit, p):
711
+ bound = (range >> 12) × p
712
+ if bit == 0: range = bound; p = p + ((4096 - p) >> 4)
713
+ else: low = low + bound; range = range - bound; p = p - (p >> 4)
714
+ while range < 2^24: range = range × 256; shift_low()
715
+
716
+ shift_low():
717
+ if low < 0xFF000000 or low >= 2^32:
718
+ carry = low >> 32
719
+ emit (cache + carry) & 0xFF, then pending - 1 bytes of (0xFF + carry) & 0xFF
720
+ pending = 0
721
+ cache = (low >> 24) & 0xFF
722
+ pending = pending + 1
723
+ low = (low & 0x00FFFFFF) × 256
724
+
725
+ finish(): shift_low() five times.
726
+ ```
727
+
728
+ Its first byte is always `0`, and the decoder then reads exactly the
729
+ bytes it wrote.
730
+
731
+ #### 8.7.2. Nodes and Neighbours
732
+
733
+ A node is a cell of the chunk's quadtree: its depth `d` and its south-west
734
+ corner `(x, y)`, in full-precision cells ([Section 8.1](#81-cell-grid-and-morton-codes)).
735
+ Nodes are visited in pre-order from the root `(d = 0, x = 0, y = 0)`. A
736
+ node at depth `d < D` first decodes a **split** decision
737
+ ([Section 8.7.3](#873-contexts)): `1` visits its four children of depth
738
+ `d + 1` in Morton order, south-west `(x, y)`, south-east `(x + h, y)`,
739
+ north-west `(x, y + h)` and north-east `(x + h, y + h)` with
740
+ `h = 2^(D - d - 1)`; `0` makes it a leaf. A node at depth `D` is a leaf
741
+ without a decision. Each leaf is the next record: its depth is `d`, its
742
+ code is where the record before it ends ([Section 8.4](#84-records)), and
743
+ its palette index is decoded as [Section 8.7.4](#874-palette-indices)
744
+ describes. Decoding stops after `record_count` records, wherever the
745
+ traversal is.
746
+
747
+ A node's **west neighbour** is the record that contains the
748
+ full-precision cell `(x - 1, y)`, and its **south neighbour** the one
749
+ that contains `(x, y - 1)`. There is none when `x = 0` (respectively
750
+ `y = 0`): neighbours never come from another chunk. Both cells have
751
+ lower Morton codes than `(x, y)` and so were decoded before the node. A
752
+ decoder can find them by remembering, for every row and every column of
753
+ full-precision cells, the last record decoded in it: in Morton order the
754
+ last record in row `y` before `(x, y)` is the one at `(x - 1, y)`, and
755
+ likewise for columns.
756
+
757
+ A neighbour's **category** relative to a node at depth `d` is `0` when
758
+ there is no neighbour, `1` when its record's depth is at most `d`, `2`
759
+ when it is `d + 1`, and `3` when it is deeper.
760
+
761
+ #### 8.7.3. Contexts
762
+
763
+ There are five tables of probabilities, each indexed by a context:
764
+
765
+ | Table | Entries | Used for |
766
+ |---------------|---------|----------|
767
+ | `split` | 256 | split decisions, at `16 × d + 4 × cw + cs` |
768
+ | `same_west` | 544 | "the index is the west neighbour's", at `c` |
769
+ | `same_south` | 544 | "the index is the south neighbour's", at `c` |
770
+ | `rank_length` | 128 | the unary part of a rank ([Section 8.7.4](#874-palette-indices)) |
771
+ | `rank_bits` | 1024 | the binary part of a rank |
772
+
773
+ `cw` and `cs` are the west and south neighbours' categories. For a leaf,
774
+ let `w` and `s` be the west and south neighbours' palette indices, or
775
+ `-1` for a missing one, and `prev` the previous record's palette index,
776
+ or `-1` for the first record. Then
777
+
778
+ ```
779
+ c = 4 × (8 × d + 4 × [w = s] + 2 × [prev = w] + [prev = s]) + cw
780
+ ```
781
+
782
+ where `[...]` is `1` when the comparison holds and `0` otherwise, and
783
+ `-1` equals `-1`.
784
+
785
+ #### 8.7.4. Palette Indices
786
+
787
+ The decoder keeps a **move-to-front list** of the palette indices
788
+ `0` to `P`, initially in that order. A leaf's palette index `v` is
789
+ decoded as follows:
790
+
791
+ 1. If there is a west neighbour, a decision in `same_west[c]`: `1` means
792
+ `v = w`.
793
+ 2. Otherwise, or if that decision was `0`: if there is a south
794
+ neighbour and `s != w`, a decision in `same_south[c]`: `1` means
795
+ `v = s`.
796
+ 3. Otherwise, a **rank** `r`: `v` is the entry of the move-to-front list
797
+ that has `r` entries before it, not counting entries equal to `w` or
798
+ `s`. `r + 1` is coded in Elias gamma: let `a` be `1` if there is a
799
+ west neighbour plus `2` if there is a south neighbour. Decisions in
800
+ `rank_length[32 × a + k]`, for `k = 0, 1, ...`, are `1` until one is
801
+ `0`; their number of `1`s is `n`, and MUST be below `32`. Then `n`
802
+ decisions in `rank_bits[32 × n + k]`, for `k = n - 1` down to `0`,
803
+ are the bits of `r + 1` below its leading `1`, most significant
804
+ first. The list MUST have such an entry.
805
+
806
+ Then `v` is moved to the front of the list.
807
+
808
+ #### 8.7.5. Bounds and Errors
809
+
810
+ A decoder MUST reject a block where `record_count` exceeds `4^D` (more
811
+ records than cells), or exceeds `2048` times the length of `stream`, and
812
+ MUST check both before allocating for the records. No stream reaches the
813
+ second bound: every record takes at least one decision, a decision
814
+ takes at least `log2(4096 / 4081)` bits, as no probability passes
815
+ `4081` or drops below `15`, so a byte codes at most about 1500 records.
816
+
817
+ It MUST also reject a block whose traversal ends (every node visited)
818
+ before `record_count` records, and any of the conditions marked MUST in
819
+ Sections 8.7.1 and 8.7.4. Every record is valid by construction: its
820
+ depth is at most `D`, it is aligned, it ends within the chunk, and its
821
+ palette index is at most `P`.
822
+
823
+ #### 8.7.6. Design Notes
824
+
825
+ This section is informative.
826
+
827
+ The range coder replaced zlib for chunks because the data has structure
828
+ a general-purpose compressor does not see. A version `1` chunk stores
829
+ each depth in a byte, though the depths are just a pre-order listing of
830
+ the quadtree's split flags, which are well predicted by the neighbours'
831
+ depths. Its palette indices are well predicted by the west and south
832
+ neighbours, which in Morton order are not usually the previous record:
833
+ in the worldwide file, 74% of records have their west neighbour's index
834
+ and 19% their south neighbour's, which zlib, looking at one byte column,
835
+ cannot exploit. Measured on the worldwide build (7,608 chunks,
836
+ 110 million records), the chunks went from 45.6 MB as version `1` to
837
+ 18.5 MB (−59.5%), and the file from 47.6 MB to 20.5 MB.
838
+
839
+ The cost is decoding speed: about 2.5 binary decisions per record, each
840
+ data-dependent, make a chunk about 4 times as slow to decode as to
841
+ inflate and parse in version `1`. Encoding is faster than zlib's highest
842
+ level. Other layouts were measured on the same file: zlib over split
843
+ flags and neighbour-predicted symbols gave 30.2 MB, and Zopfli, a better
844
+ deflate encoder for version `1` unchanged, about 37.4 MB.
845
+
644
846
  ## 9. Lookup Procedure
645
847
 
646
848
  To resolve a coordinate `(lat, lon)` to a place ID, a reader performs the
@@ -667,9 +869,10 @@ following steps. `W`, `H`, and `D` come from the header.
667
869
  avoids inconsistent rounding between the two.
668
870
  4. **Fetch the chunk.** If `chunk_lengths[i]` is `0`, the result is
669
871
  unknown. Otherwise read `length(i)` bytes at `offset(i)`
670
- ([Section 6.3](#63-locating-a-chunk)) and inflate them, and compute
671
- the records' codes ([Section 8.4](#84-records)). Readers SHOULD cache
672
- inflated chunks with their codes.
872
+ ([Section 6.3](#63-locating-a-chunk)) and inflate them (in a version
873
+ `2` file, decode them per [Section 8.7](#87-version-2-chunks)), and
874
+ compute the records' codes ([Section 8.4](#84-records)). Readers
875
+ SHOULD cache decoded chunks with their codes.
673
876
  5. **Compute the query code** `q` from `(x, y)` with the procedure of
674
877
  [Section 8.1](#81-cell-grid-and-morton-codes).
675
878
  6. **Binary search.** Find the largest `j` such that `codes[j] <= q`
@@ -738,12 +941,15 @@ record at all.
738
941
 
739
942
  **Keyframe interval.** See [Section 7.3](#73-name-blocks).
740
943
 
741
- **Compression.** zlib was chosen over zstd or brotli because it is a
742
- zero-dependency built-in in Node.js and browsers. The columnar and
743
- delta-coded layout captures most of the gain regardless of the
744
- compressor; on the prototype, zstd and brotli were only 10–20% smaller.
745
- Writers SHOULD use the highest zlib compression level; compression is a
746
- one-time cost.
944
+ **Compression.** Versions `0` and `1` chose zlib over zstd or brotli
945
+ because it is a zero-dependency built-in in Node.js and browsers. The
946
+ columnar and delta-coded layout captures most of the gain regardless of
947
+ the compressor; on the prototype, zstd and brotli were only 10–20%
948
+ smaller. Writers of those versions SHOULD use the highest zlib
949
+ compression level; compression is a one-time cost. Version `2`'s range
950
+ coder ([Section 8.7](#87-version-2-chunks)) is a model of the data, not
951
+ a general-purpose compressor, and needs no library; see
952
+ [Section 8.7.6](#876-design-notes) for what it gains and costs.
747
953
 
748
954
  ## 11. Robustness and Security Considerations
749
955
 
@@ -765,6 +971,10 @@ assume that a file is well formed.
765
971
  verify the alignment invariant of [Section 8.5](#85-invariants), and
766
972
  in a version `0` file the ordering and non-overlap invariants of
767
973
  [Section 8.6](#86-version-0-chunks).
974
+ - In a version `2` file, readers MUST check [Section 8.7.5](#875-bounds-and-errors)'s
975
+ bounds on `record_count` before allocating for it, and reject a stream
976
+ that runs past the end of its block or ends before it. The records'
977
+ depths, codes and palette indices are valid by construction.
768
978
  - Readers MUST treat the attribution text and place names as untrusted
769
979
  text. Where they are rendered as Markdown or HTML, they MUST be
770
980
  sanitised.
@@ -773,7 +983,7 @@ assume that a file is well formed.
773
983
 
774
984
  ## 12. Open Issues
775
985
 
776
- The following are known gaps or unresolved decisions in version `1`.
986
+ The following are known gaps or unresolved decisions in version `2`.
777
987
 
778
988
  1. **Latitude scaling.** Because the grid is equirectangular, cells
779
989
  shrink in east-west extent towards the poles. A per-row scaling
@@ -801,8 +1011,9 @@ The following are known gaps or unresolved decisions in version `1`.
801
1011
 
802
1012
  ## Appendix A. Worked Example
803
1013
 
804
- This appendix gives a complete, valid, synthetic file of 157 bytes and
805
- walks through several lookups. Its geography is fictional. The file uses
1014
+ This appendix gives a complete, valid, synthetic version `1` file of
1015
+ 157 bytes and walks through several lookups; Section A.6 gives its chunk
1016
+ as version `2` stores it. Its geography is fictional. The file uses
806
1017
  a `2 × 2` chunk grid, a quadtree depth of `2`, and name blocks of `4`
807
1018
  entries.
808
1019
 
@@ -963,6 +1174,46 @@ point lies after the last record: **unknown**.
963
1174
  `c = 1, r = 0`, index `1`. Its length is `0`: **unknown**, with no
964
1175
  decompression performed.
965
1176
 
1177
+ ### A.6. Chunk 3 as Version 2 (11 bytes)
1178
+
1179
+ As a version `2` file, the example is 142 bytes: the header's `version`
1180
+ byte is `02`, the directory's last chunk length `0b`, and chunk `3` is
1181
+ stored as follows ([Section 8.7](#87-version-2-chunks)):
1182
+
1183
+ ```
1184
+ 02 palette_count P = 2
1185
+ 02 03 palette deltas 2, 3 -> IDs 2, 5
1186
+ 07 record_count M = 7
1187
+ 00 a5 11 64 ba 00 00 stream
1188
+ ```
1189
+
1190
+ The stream holds these decisions, in order. `W` and `S` are the west
1191
+ and south neighbours' records, `L` the move-to-front list before the
1192
+ leaf; every probability used is still at `2048` unless it was used
1193
+ before.
1194
+
1195
+ | Node `(d; x, y)` | Neighbours | Decisions | Record |
1196
+ |------------------|-----------------------------|----------------------------------------------------|--------|
1197
+ | `(0; 0, 0)` | none | `split[0] = 1` | |
1198
+ | `(1; 0, 0)` | none | `split[16] = 0`; `L = 0 1 2`, `r = 1`: `rank_length[0] = 1`, `rank_length[1] = 0`, `rank_bits[32] = 0` | 0: depth 1, index 1 |
1199
+ | `(1; 2, 0)` | W record 0 | `split[20] = 1` | |
1200
+ | `(2; 2, 0)` | W record 0 | `same_west[73] = 0`; `L = 1 0 2`, skipping `1`, `r = 1`: `rank_length[32] = 1`, `rank_length[33] = 0`, `rank_bits[32] = 0` | 1: depth 2, index 2 |
1201
+ | `(2; 3, 0)` | W record 1 | `same_west[73] = 0`; `L = 2 1 0`, skipping `2`, `r = 1`: `rank_length[32] = 1`, `rank_length[33] = 0`, `rank_bits[32] = 0` | 2: depth 2, index 0 |
1202
+ | `(2; 2, 1)` | W record 0, S record 1 | `same_west[65] = 0`, `same_south[65] = 0`; `L = 0 2 1`, skipping `1` and `2`, `r = 0`: `rank_length[96] = 0` | 3: depth 2, index 0 |
1203
+ | `(2; 3, 1)` | W record 3, S record 2 | `same_west[93] = 1` | 4: depth 2, index 0 |
1204
+ | `(1; 0, 2)` | S record 0 | `split[17] = 0`; `same_south[32] = 1` | 5: depth 1, index 1 |
1205
+ | `(1; 2, 2)` | W record 5, S record 3 | `split[22] = 1` | |
1206
+ | `(2; 2, 2)` | W record 5, S record 3 | `same_west[73] = 0`, `same_south[73] = 0`; `L = 1 0 2`, skipping `1` and `0`, `r = 0`: `rank_length[96] = 0` | 6: depth 2, index 2 |
1207
+
1208
+ For example, at `(2; 3, 1)` both neighbours have index `0`, as had the
1209
+ previous record, so `c = 4 × (8 × 2 + 4 + 2 + 1) + 1 = 93`, and since
1210
+ `s = w` no `same_south` decision follows. At `(1; 2, 2)` the south
1211
+ neighbour, record 3, is one level deeper than the node (category `2`),
1212
+ so `split[16 × 1 + 4 × 1 + 2] = split[22]`. Decoding stops after the
1213
+ seventh record, with three nodes of the traversal still unvisited, and
1214
+ `pos` is then `7`, the length of the stream. The records are those of
1215
+ Section A.4.
1216
+
966
1217
  ## Appendix B. Text Search Index
967
1218
 
968
1219
  This appendix specifies an OPTIONAL sidecar file that speeds up substring
@@ -2341,6 +2592,9 @@ A full-precision cell of this file is `45°` wide and `22.5°` high,
2341
2592
 
2342
2593
  ## Appendix D. References
2343
2594
 
2595
+ - [LZMA] Pavlov, I., "LZMA SDK", https://www.7-zip.org/sdk.html: the
2596
+ range coder of `LzmaDec.c`, with `kNumBitModelTotalBits` 11 and
2597
+ `kNumMoveBits` 5 where this format uses 12 and 4.
2344
2598
  - [RFC1950] Deutsch, P. and J-L. Gailly, "ZLIB Compressed Data Format
2345
2599
  Specification version 3.3", May 1996.
2346
2600
  - [RFC1951] Deutsch, P., "DEFLATE Compressed Data Format Specification
package/dist/chunk.d.ts CHANGED
@@ -27,8 +27,9 @@ export interface ChunkRecords {
27
27
  * (`width_exp`, `height_exp`) instead (binary-format.md Section 10).
28
28
  */
29
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
30
+ /** Parses one chunk block: already inflated for a `version` 0 or 1 file
31
+ * (Section 8.2, or Section 8.6's layout for `version` 0), as stored for
32
+ * `version` 2, whose blocks are not zlib streams (Section 8.7). It checks the cheap Section 8.5
32
33
  * invariants a reader can verify without external context (place ids
33
34
  * are validated by the caller, which has the place table). */
34
35
  export declare function parseChunk(buf: Uint8Array, maxDepth: number, version: number): ChunkRecords;
package/dist/chunk.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { decodeChunkCodes, WASM_MIN_RECORDS } from "./chunkDecoder.js";
2
2
  import { CGeoDataFormatError } from "./errors.js";
3
+ import { decodeRangeCodedRecords } from "./rangeChunk.js";
3
4
  import { readDeltaVarintList, readDeltaVarints32Into, readVarint, } from "./varint.js";
4
5
  /**
5
6
  * The deepest `max_depth` this reader supports, though the format allows
@@ -75,8 +76,9 @@ function readLittleEndian(buf, offset, width, n) {
75
76
  function isMultipleOf(value, span) {
76
77
  return value < 0x80000000 ? (value & (span - 1)) === 0 : value % span === 0;
77
78
  }
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
79
+ /** Parses one chunk block: already inflated for a `version` 0 or 1 file
80
+ * (Section 8.2, or Section 8.6's layout for `version` 0), as stored for
81
+ * `version` 2, whose blocks are not zlib streams (Section 8.7). It checks the cheap Section 8.5
80
82
  * invariants a reader can verify without external context (place ids
81
83
  * are validated by the caller, which has the place table). */
82
84
  export function parseChunk(buf, maxDepth, version) {
@@ -90,6 +92,12 @@ export function parseChunk(buf, maxDepth, version) {
90
92
  offset += b2;
91
93
  const { value: recordCount, bytesRead: b3 } = readVarint(buf, offset);
92
94
  offset += b3;
95
+ if (version >= 2) {
96
+ return {
97
+ palette,
98
+ ...decodeRangeCodedRecords(buf, offset, paletteCount, recordCount, maxDepth),
99
+ };
100
+ }
93
101
  if (version !== 0) {
94
102
  return parseRecords(buf, offset, palette, recordCount, maxDepth);
95
103
  }
package/dist/format.d.ts CHANGED
@@ -15,6 +15,7 @@ export { type Inflater, inflateBlock, inflateCeiling, streamInflater, } from "./
15
15
  export { decodeLocateEntry, encodeLocateEntry, type LocateCell, type LocateGrid, locateEntryWidth, } from "./locateIndex.ts";
16
16
  export { lowBits, mortonDecode, mortonEncode, shiftRight } from "./morton.ts";
17
17
  export { type PlaceTableInfo, parsePlaceTable, type ResolvedPlace, readNameBlock, readNameEntry, resolveIdentifier, } from "./placeTable.ts";
18
+ export { decodeRangeCodedRecords, encodeRangeCodedRecords, type RangeCodedRecords, } from "./rangeChunk.ts";
18
19
  export { containsTokenRun, EXACT_SPELLING_BONUS, foldDiacritics, LEVEL_BONUS, type Needle, nameMatchQuality, placeMatchQuality, prepareNeedle, QUALITY_EXACT, QUALITY_MAX, QUALITY_SUBSTRING, QUALITY_WHOLE_WORD, tokenize, tokenizeFolded, } from "./relevance.ts";
19
20
  export { fnv1a32, foldSymbols, trigramListIndex, trigramListIndices, } from "./searchIndex.ts";
20
21
  export { toSource } from "./source.ts";
package/dist/format.js CHANGED
@@ -15,6 +15,7 @@ export { inflateBlock, inflateCeiling, streamInflater, } from "./inflate.js";
15
15
  export { decodeLocateEntry, encodeLocateEntry, locateEntryWidth, } from "./locateIndex.js";
16
16
  export { lowBits, mortonDecode, mortonEncode, shiftRight } from "./morton.js";
17
17
  export { parsePlaceTable, readNameBlock, readNameEntry, resolveIdentifier, } from "./placeTable.js";
18
+ export { decodeRangeCodedRecords, encodeRangeCodedRecords, } from "./rangeChunk.js";
18
19
  export { containsTokenRun, EXACT_SPELLING_BONUS, foldDiacritics, LEVEL_BONUS, nameMatchQuality, placeMatchQuality, prepareNeedle, QUALITY_EXACT, QUALITY_MAX, QUALITY_SUBSTRING, QUALITY_WHOLE_WORD, tokenize, tokenizeFolded, } from "./relevance.js";
19
20
  export { fnv1a32, foldSymbols, trigramListIndex, trigramListIndices, } from "./searchIndex.js";
20
21
  export { toSource } from "./source.js";
package/dist/header.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /** A file's header (Section 5). */
2
2
  export interface CGeoDataHeader {
3
- /** Format version: 1, or 0 for files written before chunks dropped
4
- * their codes (Section 8.6). This reader reads both. */
3
+ /** Format version: 2 (range-coded chunks, Section 8.7), 1 (zlib
4
+ * chunks), or 0 for files written before chunks dropped their codes
5
+ * (Section 8.6). This reader reads all three. */
5
6
  version: number;
6
7
  /** The data's attribution notice, as given when the file was built.
7
8
  * Show it wherever results are shown. */
package/dist/header.js CHANGED
@@ -21,7 +21,7 @@ export function parseHeader(buf) {
21
21
  let offset = SIGNATURE.length;
22
22
  const version = readVarint(buf, offset);
23
23
  offset += version.bytesRead;
24
- if (version.value !== 0 && version.value !== 1) {
24
+ if (version.value > 2) {
25
25
  throw new CGeoDataVersionError(`unsupported CGEODATA version ${version.value}`, version.value);
26
26
  }
27
27
  const attrLen = readVarint(buf, offset);
@@ -0,0 +1,46 @@
1
+ import type { ChunkRecords } from "./chunk.ts";
2
+ /**
3
+ * A version 2 chunk's records (binary-format.md Section 8.7): the
4
+ * quadtree's split flags and each leaf's palette index, coded with an
5
+ * adaptive binary range coder whose contexts come from the west and south
6
+ * neighbours of each node. This is the JavaScript decoder, the one that
7
+ * reports errors; `rangeDecoder.ts`'s WebAssembly one only accepts or
8
+ * declines, and a declined block is decoded again here.
9
+ */
10
+ /** A chunk's records as `decodeRangeCodedRecords` decodes them: all of
11
+ * `ChunkRecords` but the palette, which precedes the stream. */
12
+ export type RangeCodedRecords = Omit<ChunkRecords, "palette">;
13
+ /** Context table sizes (Section 8.7.3). */
14
+ export declare const SPLIT_CONTEXTS: number;
15
+ export declare const VALUE_CONTEXTS: number;
16
+ export declare const RANK_LENGTH_CONTEXTS: number;
17
+ export declare const RANK_BIT_CONTEXTS: number;
18
+ /** A version 2 chunk's palette_count is below this, so a palette index
19
+ * and a depth fit in one 32-bit integer together. */
20
+ export declare const MAX_PALETTE_COUNT: number;
21
+ /** At most this many records per byte of coded stream: every leaf takes at
22
+ * least one binary decision, which costs at least log2(4096 / 4081) bits,
23
+ * so 2048 records per byte can't be reached by any stream. Checked before
24
+ * allocating for `record_count`. */
25
+ export declare const MAX_RECORDS_PER_BYTE = 2048;
26
+ /** The narrowest typed array for palette indices up to `paletteCount`
27
+ * (Section 8.3's S, with 3 bytes rounded up to 4). */
28
+ export declare function paletteIndexArray(paletteCount: number, n: number): Uint8Array | Uint16Array | Uint32Array;
29
+ /**
30
+ * Decodes the records of a version 2 chunk from `buf[offset..]`, the
31
+ * range-coded stream that follows `record_count` and runs to the end of
32
+ * the block. Throws a `CGeoDataFormatError` for anything Section 8.7
33
+ * rejects.
34
+ */
35
+ export declare function decodeRangeCodedRecords(buf: Uint8Array, offset: number, paletteCount: number, recordCount: number, maxDepth: number): RangeCodedRecords;
36
+ /** Section 8.7.5's bounds on `record_count`, checked before allocating
37
+ * for it: at most one record per cell, and at most `MAX_RECORDS_PER_BYTE`
38
+ * per byte of stream. */
39
+ export declare function checkRecordCount(recordCount: number, maxDepth: number, streamBytes: number): void;
40
+ /**
41
+ * Encodes records that tile a chunk from code 0 (Section 8.4) as Section
42
+ * 8.7's range-coded stream: `depths[j]` and `paletteIndices[j]` (0..P)
43
+ * per record, in Morton order. The inverse of `decodeRangeCodedRecords`.
44
+ * Throws a RangeError for records that don't tile the chunk.
45
+ */
46
+ export declare function encodeRangeCodedRecords(depths: ArrayLike<number>, paletteIndices: ArrayLike<number>, paletteCount: number, maxDepth: number): Uint8Array;
@@ -0,0 +1,401 @@
1
+ import { CGeoDataFormatError } from "./errors.js";
2
+ import { decodeRangeCodedWasm } from "./rangeDecoder.js";
3
+ /** Probabilities are 12-bit: the chance of a 0, out of 4096. */
4
+ const PROB_BITS = 12;
5
+ const PROB_INIT = 2048;
6
+ /** How fast a probability adapts: it moves 1/16 of the way each time. */
7
+ const ADAPT_SHIFT = 4;
8
+ const TOP = 2 ** 24;
9
+ /** Context table sizes (Section 8.7.3). */
10
+ export const SPLIT_CONTEXTS = 16 * 16;
11
+ export const VALUE_CONTEXTS = 17 * 8 * 4;
12
+ export const RANK_LENGTH_CONTEXTS = 4 * 32;
13
+ export const RANK_BIT_CONTEXTS = 32 * 32;
14
+ /** A version 2 chunk's palette_count is below this, so a palette index
15
+ * and a depth fit in one 32-bit integer together. */
16
+ export const MAX_PALETTE_COUNT = 2 ** 26;
17
+ /** At most this many records per byte of coded stream: every leaf takes at
18
+ * least one binary decision, which costs at least log2(4096 / 4081) bits,
19
+ * so 2048 records per byte can't be reached by any stream. Checked before
20
+ * allocating for `record_count`. */
21
+ export const MAX_RECORDS_PER_BYTE = 2048;
22
+ /** Section 8.7.2's neighbour category: 0 when there is no neighbour, else
23
+ * how much deeper than `d` its record is (1: not deeper, 2: one level, 3:
24
+ * more). `tag` is the neighbour's `index << 5 | depth`, or -1. */
25
+ function category(tag, d) {
26
+ if (tag < 0)
27
+ return 0;
28
+ const e = tag & 31;
29
+ return e <= d ? 1 : e === d + 1 ? 2 : 3;
30
+ }
31
+ class RangeDecoder {
32
+ range = 0xffffffff;
33
+ code = 0;
34
+ pos;
35
+ buf;
36
+ end;
37
+ constructor(buf, start, end) {
38
+ this.buf = buf;
39
+ this.end = end;
40
+ if (end - start < 5) {
41
+ throw new CGeoDataFormatError("chunk range-coded stream is shorter than 5 bytes");
42
+ }
43
+ if (buf[start] !== 0) {
44
+ throw new CGeoDataFormatError("chunk range-coded stream does not start with a 0 byte");
45
+ }
46
+ let code = 0;
47
+ for (let i = 1; i <= 4; i++)
48
+ code = code * 256 + buf[start + i];
49
+ if (code === 0xffffffff) {
50
+ throw new CGeoDataFormatError("chunk range-coded stream starts with code 0xffffffff");
51
+ }
52
+ this.code = code;
53
+ this.pos = start + 5;
54
+ }
55
+ bit(probs, i) {
56
+ const p = probs[i];
57
+ const bound = (this.range >>> PROB_BITS) * p;
58
+ let b;
59
+ if (this.code < bound) {
60
+ this.range = bound;
61
+ probs[i] = p + (((1 << PROB_BITS) - p) >> ADAPT_SHIFT);
62
+ b = 0;
63
+ }
64
+ else {
65
+ this.code -= bound;
66
+ this.range -= bound;
67
+ probs[i] = p - (p >> ADAPT_SHIFT);
68
+ b = 1;
69
+ }
70
+ while (this.range < TOP) {
71
+ if (this.pos >= this.end) {
72
+ throw new CGeoDataFormatError("chunk range-coded stream runs past end of block");
73
+ }
74
+ this.range *= 256;
75
+ this.code = this.code * 256 + this.buf[this.pos++];
76
+ }
77
+ return b;
78
+ }
79
+ }
80
+ /** The narrowest typed array for palette indices up to `paletteCount`
81
+ * (Section 8.3's S, with 3 bytes rounded up to 4). */
82
+ export function paletteIndexArray(paletteCount, n) {
83
+ if (paletteCount <= 255)
84
+ return new Uint8Array(n);
85
+ if (paletteCount <= 65535)
86
+ return new Uint16Array(n);
87
+ return new Uint32Array(n);
88
+ }
89
+ /**
90
+ * Decodes the records of a version 2 chunk from `buf[offset..]`, the
91
+ * range-coded stream that follows `record_count` and runs to the end of
92
+ * the block. Throws a `CGeoDataFormatError` for anything Section 8.7
93
+ * rejects.
94
+ */
95
+ export function decodeRangeCodedRecords(buf, offset, paletteCount, recordCount, maxDepth) {
96
+ if (paletteCount >= MAX_PALETTE_COUNT) {
97
+ throw new CGeoDataFormatError(`chunk palette_count ${paletteCount} is 2^26 or more, past what a version 2 chunk allows`);
98
+ }
99
+ checkRecordCount(recordCount, maxDepth, buf.length - offset);
100
+ const fast = decodeRangeCodedWasm(buf, offset, paletteCount, recordCount, maxDepth);
101
+ if (fast)
102
+ return fast;
103
+ const rc = new RangeDecoder(buf, offset, buf.length);
104
+ const D = maxDepth;
105
+ const N = 2 ** D;
106
+ const split = new Uint16Array(SPLIT_CONTEXTS).fill(PROB_INIT);
107
+ const eqW = new Uint16Array(VALUE_CONTEXTS).fill(PROB_INIT);
108
+ const eqS = new Uint16Array(VALUE_CONTEXTS).fill(PROB_INIT);
109
+ const rankLength = new Uint16Array(RANK_LENGTH_CONTEXTS).fill(PROB_INIT);
110
+ const rankBits = new Uint16Array(RANK_BIT_CONTEXTS).fill(PROB_INIT);
111
+ // row[y] / col[x]: the last record decoded in row y / column x, as
112
+ // `index << 5 | depth`; in Morton order that is the cell west of /
113
+ // south of the next one to be decoded there.
114
+ const row = new Int32Array(N).fill(-1);
115
+ const col = new Int32Array(N).fill(-1);
116
+ // Move-to-front list of palette indices 0..P.
117
+ const mtf = new Int32Array(paletteCount + 1);
118
+ for (let k = 0; k <= paletteCount; k++)
119
+ mtf[k] = k;
120
+ const codes = new Uint32Array(recordCount);
121
+ const depths = new Uint8Array(recordCount);
122
+ const paletteIndices = paletteIndexArray(paletteCount, recordCount);
123
+ // Pending nodes, popped in pre-order: at most 3 per level plus one.
124
+ const stackD = new Int32Array(4 * (D + 1));
125
+ const stackX = new Int32Array(4 * (D + 1));
126
+ const stackY = new Int32Array(4 * (D + 1));
127
+ let sp = 1;
128
+ let j = 0;
129
+ let prev = -1;
130
+ let code = 0;
131
+ while (j < recordCount) {
132
+ if (sp === 0) {
133
+ throw new CGeoDataFormatError(`chunk record_count ${recordCount} is more than the ${j} records its quadtree has`);
134
+ }
135
+ sp--;
136
+ const d = stackD[sp];
137
+ const x = stackX[sp];
138
+ const y = stackY[sp];
139
+ const w = x > 0 ? row[y] : -1;
140
+ const s = y > 0 ? col[x] : -1;
141
+ const wc = category(w, d);
142
+ if (d < D && rc.bit(split, d * 16 + wc * 4 + category(s, d)) === 1) {
143
+ const h = 2 ** (D - d - 1);
144
+ const d1 = d + 1;
145
+ // Pushed in reverse, so the south-west child pops first.
146
+ stackD[sp] = d1;
147
+ stackX[sp] = x + h;
148
+ stackY[sp] = y + h;
149
+ sp++;
150
+ stackD[sp] = d1;
151
+ stackX[sp] = x;
152
+ stackY[sp] = y + h;
153
+ sp++;
154
+ stackD[sp] = d1;
155
+ stackX[sp] = x + h;
156
+ stackY[sp] = y;
157
+ sp++;
158
+ stackD[sp] = d1;
159
+ stackX[sp] = x;
160
+ stackY[sp] = y;
161
+ sp++;
162
+ continue;
163
+ }
164
+ const wv = w < 0 ? -1 : w >>> 5;
165
+ const sv = s < 0 ? -1 : s >>> 5;
166
+ const c = (d * 8 +
167
+ (wv === sv ? 4 : 0) +
168
+ (prev === wv ? 2 : 0) +
169
+ (prev === sv ? 1 : 0)) *
170
+ 4 +
171
+ wc;
172
+ let v = -1;
173
+ let p = 0;
174
+ if (wv >= 0 && rc.bit(eqW, c) === 1)
175
+ v = wv;
176
+ else if (sv >= 0 && sv !== wv && rc.bit(eqS, c) === 1)
177
+ v = sv;
178
+ if (v < 0) {
179
+ const cc = (wv >= 0 ? 32 : 0) + (sv >= 0 ? 64 : 0);
180
+ let nb = 0;
181
+ while (rc.bit(rankLength, cc + nb) === 1) {
182
+ if (++nb === 32) {
183
+ throw new CGeoDataFormatError(`chunk record ${j}'s palette rank is 2^32 or more`);
184
+ }
185
+ }
186
+ let value = 1;
187
+ for (let k = nb - 1; k >= 0; k--) {
188
+ value = value * 2 + rc.bit(rankBits, nb * 32 + k);
189
+ }
190
+ let rank = value - 1;
191
+ for (;; p++) {
192
+ if (p > paletteCount) {
193
+ throw new CGeoDataFormatError(`chunk record ${j}'s palette rank ${value - 1} is past the palette`);
194
+ }
195
+ const t = mtf[p];
196
+ if (t === wv || t === sv)
197
+ continue;
198
+ if (rank === 0)
199
+ break;
200
+ rank--;
201
+ }
202
+ v = mtf[p];
203
+ }
204
+ else {
205
+ while (mtf[p] !== v)
206
+ p++;
207
+ }
208
+ for (let k = p; k > 0; k--)
209
+ mtf[k] = mtf[k - 1];
210
+ mtf[0] = v;
211
+ const size = 2 ** (D - d);
212
+ const tag = v * 32 + d;
213
+ row.fill(tag, y, y + size);
214
+ col.fill(tag, x, x + size);
215
+ codes[j] = code;
216
+ depths[j] = d;
217
+ paletteIndices[j] = v;
218
+ code += size * size;
219
+ prev = v;
220
+ j++;
221
+ }
222
+ if (rc.pos !== rc.end) {
223
+ throw new CGeoDataFormatError(`chunk block has ${rc.end - rc.pos} trailing bytes after its range-coded stream`);
224
+ }
225
+ return { codes, depths, paletteIndices };
226
+ }
227
+ /** Section 8.7.5's bounds on `record_count`, checked before allocating
228
+ * for it: at most one record per cell, and at most `MAX_RECORDS_PER_BYTE`
229
+ * per byte of stream. */
230
+ export function checkRecordCount(recordCount, maxDepth, streamBytes) {
231
+ if (recordCount > 4 ** maxDepth) {
232
+ throw new CGeoDataFormatError(`chunk record_count ${recordCount} is more than the 4^${maxDepth} cells of a chunk`);
233
+ }
234
+ if (recordCount > MAX_RECORDS_PER_BYTE * Math.max(streamBytes, 0)) {
235
+ throw new CGeoDataFormatError(`chunk record_count ${recordCount} is more than a ${streamBytes}-byte stream can code`);
236
+ }
237
+ }
238
+ class RangeEncoder {
239
+ low = 0;
240
+ range = 0xffffffff;
241
+ cache = 0;
242
+ cacheSize = 1;
243
+ out = [];
244
+ bit(probs, i, b) {
245
+ const p = probs[i];
246
+ const bound = (this.range >>> PROB_BITS) * p;
247
+ if (b === 0) {
248
+ this.range = bound;
249
+ probs[i] = p + (((1 << PROB_BITS) - p) >> ADAPT_SHIFT);
250
+ }
251
+ else {
252
+ this.low += bound;
253
+ this.range -= bound;
254
+ probs[i] = p - (p >> ADAPT_SHIFT);
255
+ }
256
+ while (this.range < TOP) {
257
+ this.range *= 256;
258
+ this.shiftLow();
259
+ }
260
+ }
261
+ /** Emits the top byte of `low`, holding back runs of 0xff until a
262
+ * carry out of them is ruled out (LZMA's encoder). */
263
+ shiftLow() {
264
+ if (this.low < 0xff000000 || this.low >= 0x100000000) {
265
+ const carry = this.low >= 0x100000000 ? 1 : 0;
266
+ let c = this.cache;
267
+ do {
268
+ this.out.push((c + carry) & 0xff);
269
+ c = 0xff;
270
+ } while (--this.cacheSize !== 0);
271
+ this.cache = Math.floor(this.low / 0x1000000) & 0xff;
272
+ }
273
+ this.cacheSize++;
274
+ this.low = (this.low % 0x1000000) * 256;
275
+ }
276
+ finish() {
277
+ for (let i = 0; i < 5; i++)
278
+ this.shiftLow();
279
+ return Uint8Array.from(this.out);
280
+ }
281
+ }
282
+ /**
283
+ * Encodes records that tile a chunk from code 0 (Section 8.4) as Section
284
+ * 8.7's range-coded stream: `depths[j]` and `paletteIndices[j]` (0..P)
285
+ * per record, in Morton order. The inverse of `decodeRangeCodedRecords`.
286
+ * Throws a RangeError for records that don't tile the chunk.
287
+ */
288
+ export function encodeRangeCodedRecords(depths, paletteIndices, paletteCount, maxDepth) {
289
+ if (paletteCount >= MAX_PALETTE_COUNT) {
290
+ throw new RangeError(`palette_count ${paletteCount} is 2^26 or more, past what a version 2 chunk allows`);
291
+ }
292
+ const rc = new RangeEncoder();
293
+ const D = maxDepth;
294
+ const N = 2 ** D;
295
+ const recordCount = depths.length;
296
+ const split = new Uint16Array(SPLIT_CONTEXTS).fill(PROB_INIT);
297
+ const eqW = new Uint16Array(VALUE_CONTEXTS).fill(PROB_INIT);
298
+ const eqS = new Uint16Array(VALUE_CONTEXTS).fill(PROB_INIT);
299
+ const rankLength = new Uint16Array(RANK_LENGTH_CONTEXTS).fill(PROB_INIT);
300
+ const rankBits = new Uint16Array(RANK_BIT_CONTEXTS).fill(PROB_INIT);
301
+ const row = new Int32Array(N).fill(-1);
302
+ const col = new Int32Array(N).fill(-1);
303
+ const mtf = new Int32Array(paletteCount + 1);
304
+ for (let k = 0; k <= paletteCount; k++)
305
+ mtf[k] = k;
306
+ const stackD = new Int32Array(4 * (D + 1));
307
+ const stackX = new Int32Array(4 * (D + 1));
308
+ const stackY = new Int32Array(4 * (D + 1));
309
+ let sp = 1;
310
+ let j = 0;
311
+ let prev = -1;
312
+ while (j < recordCount) {
313
+ if (sp === 0)
314
+ throw new RangeError("records run past the end of the chunk");
315
+ sp--;
316
+ const d = stackD[sp];
317
+ const x = stackX[sp];
318
+ const y = stackY[sp];
319
+ const depth = depths[j];
320
+ if (depth < d || depth > D) {
321
+ throw new RangeError(`record ${j} at depth ${depth} doesn't tile the chunk`);
322
+ }
323
+ const w = x > 0 ? row[y] : -1;
324
+ const s = y > 0 ? col[x] : -1;
325
+ const wc = category(w, d);
326
+ if (d < D) {
327
+ const splits = depth > d ? 1 : 0;
328
+ rc.bit(split, d * 16 + wc * 4 + category(s, d), splits);
329
+ if (splits) {
330
+ const h = 2 ** (D - d - 1);
331
+ const d1 = d + 1;
332
+ stackD[sp] = d1;
333
+ stackX[sp] = x + h;
334
+ stackY[sp] = y + h;
335
+ sp++;
336
+ stackD[sp] = d1;
337
+ stackX[sp] = x;
338
+ stackY[sp] = y + h;
339
+ sp++;
340
+ stackD[sp] = d1;
341
+ stackX[sp] = x + h;
342
+ stackY[sp] = y;
343
+ sp++;
344
+ stackD[sp] = d1;
345
+ stackX[sp] = x;
346
+ stackY[sp] = y;
347
+ sp++;
348
+ continue;
349
+ }
350
+ }
351
+ const v = paletteIndices[j];
352
+ if (!(v >= 0 && v <= paletteCount)) {
353
+ throw new RangeError(`record ${j}'s palette index ${v} is past P (${paletteCount})`);
354
+ }
355
+ const wv = w < 0 ? -1 : w >>> 5;
356
+ const sv = s < 0 ? -1 : s >>> 5;
357
+ const c = (d * 8 +
358
+ (wv === sv ? 4 : 0) +
359
+ (prev === wv ? 2 : 0) +
360
+ (prev === sv ? 1 : 0)) *
361
+ 4 +
362
+ wc;
363
+ let coded = false;
364
+ if (wv >= 0) {
365
+ rc.bit(eqW, c, v === wv ? 1 : 0);
366
+ coded = v === wv;
367
+ }
368
+ if (!coded && sv >= 0 && sv !== wv) {
369
+ rc.bit(eqS, c, v === sv ? 1 : 0);
370
+ coded = v === sv;
371
+ }
372
+ let p = 0;
373
+ while (mtf[p] !== v)
374
+ p++;
375
+ if (!coded) {
376
+ let rank = 0;
377
+ for (let k = 0; k < p; k++)
378
+ if (mtf[k] !== wv && mtf[k] !== sv)
379
+ rank++;
380
+ const value = rank + 1;
381
+ const nb = 31 - Math.clz32(value);
382
+ const cc = (wv >= 0 ? 32 : 0) + (sv >= 0 ? 64 : 0);
383
+ for (let k = 0; k < nb; k++)
384
+ rc.bit(rankLength, cc + k, 1);
385
+ rc.bit(rankLength, cc + nb, 0);
386
+ for (let k = nb - 1; k >= 0; k--)
387
+ rc.bit(rankBits, nb * 32 + k, (value >>> k) & 1);
388
+ }
389
+ for (let k = p; k > 0; k--)
390
+ mtf[k] = mtf[k - 1];
391
+ mtf[0] = v;
392
+ const size = 2 ** (D - d);
393
+ const tag = v * 32 + d;
394
+ row.fill(tag, y, y + size);
395
+ col.fill(tag, x, x + size);
396
+ prev = v;
397
+ j++;
398
+ }
399
+ return rc.finish();
400
+ }
401
+ //# sourceMappingURL=rangeChunk.js.map
@@ -0,0 +1,17 @@
1
+ import type { RangeCodedRecords } from "./rangeChunk.ts";
2
+ /** Turns the fast path off (or back on), for tests and benchmarks that
3
+ * compare it against the JavaScript decoder. */
4
+ export declare function useWasmRangeDecoder(on: boolean): void;
5
+ /** How many bytes of WebAssembly memory the fast path holds right now:
6
+ * 0 while idle. */
7
+ export declare function wasmRangeDecoderBytes(): number;
8
+ /** Whether this platform runs the fast path. */
9
+ export declare function wasmRangeDecoderAvailable(): boolean;
10
+ /**
11
+ * The records of the range-coded stream `buf[offset..]` (Section 8.7), or
12
+ * undefined when the fast path declined it or isn't available, and the
13
+ * caller should decode it in JavaScript. The caller has checked Section
14
+ * 8.7.5's bounds on `recordCount`, that `maxDepth` is at most 16 and
15
+ * `paletteCount` below 2^26.
16
+ */
17
+ export declare function decodeRangeCodedWasm(buf: Uint8Array, offset: number, paletteCount: number, recordCount: number, maxDepth: number): RangeCodedRecords | undefined;
@@ -0,0 +1,129 @@
1
+ import { RANGE_DECODER_WASM } from "./rangeDecoderWasm.js";
2
+ /**
3
+ * `decodeRangeCodedRecords`'s fast path (wasm/rangeDecoder.wat), about
4
+ * twice as fast as the JavaScript decoder in ./rangeChunk.ts. It only
5
+ * accepts or declines a stream: a declined one -- malformed, or valid
6
+ * but out of its reach -- is decoded again in JavaScript, which throws
7
+ * the error or returns the records. Platforms without WebAssembly (or
8
+ * that forbid compiling it, such as a page whose CSP lacks
9
+ * `'wasm-unsafe-eval'`) always take the JavaScript path.
10
+ *
11
+ * Memory: one instance, grown to the largest chunk decoded so far and
12
+ * dropped once no decode has used it for `IDLE_RELEASE_MS`, as
13
+ * ./chunkDecoder.ts does for version 0 chunks.
14
+ */
15
+ /** How long the instance outlives its last decode. */
16
+ const IDLE_RELEASE_MS = 1000;
17
+ const PAGE_BYTES = 65536;
18
+ /** Probabilities and node stack (wasm/rangeDecoder.wat's `scratch`). */
19
+ const FIXED_SCRATCH_BYTES = 4992 + 816;
20
+ /** The compiled module; null once compiling failed. */
21
+ let compiled;
22
+ let instance;
23
+ let releaseTimer;
24
+ let enabled = true;
25
+ /** Turns the fast path off (or back on), for tests and benchmarks that
26
+ * compare it against the JavaScript decoder. */
27
+ export function useWasmRangeDecoder(on) {
28
+ enabled = on;
29
+ if (!on)
30
+ release();
31
+ }
32
+ /** How many bytes of WebAssembly memory the fast path holds right now:
33
+ * 0 while idle. */
34
+ export function wasmRangeDecoderBytes() {
35
+ return instance?.memory.buffer.byteLength ?? 0;
36
+ }
37
+ /** Whether this platform runs the fast path. */
38
+ export function wasmRangeDecoderAvailable() {
39
+ return enabled && getInstance() !== undefined;
40
+ }
41
+ function getInstance() {
42
+ if (instance)
43
+ return instance;
44
+ if (compiled === undefined) {
45
+ try {
46
+ // Throws a ReferenceError where there's no WebAssembly at all.
47
+ compiled = new WebAssembly.Module(Uint8Array.from(atob(RANGE_DECODER_WASM), (c) => c.charCodeAt(0)));
48
+ }
49
+ catch {
50
+ compiled = null;
51
+ }
52
+ }
53
+ if (compiled === null)
54
+ return undefined;
55
+ const exports = new WebAssembly.Instance(compiled).exports;
56
+ instance = {
57
+ memory: exports.memory,
58
+ decode: exports.decode,
59
+ };
60
+ return instance;
61
+ }
62
+ function release() {
63
+ instance = undefined;
64
+ if (releaseTimer !== undefined)
65
+ clearTimeout(releaseTimer);
66
+ releaseTimer = undefined;
67
+ }
68
+ function releaseWhenIdle() {
69
+ if (releaseTimer !== undefined)
70
+ clearTimeout(releaseTimer);
71
+ releaseTimer = setTimeout(release, IDLE_RELEASE_MS);
72
+ // Node.js: don't keep the process alive just to free this.
73
+ releaseTimer.unref?.();
74
+ }
75
+ /**
76
+ * The records of the range-coded stream `buf[offset..]` (Section 8.7), or
77
+ * undefined when the fast path declined it or isn't available, and the
78
+ * caller should decode it in JavaScript. The caller has checked Section
79
+ * 8.7.5's bounds on `recordCount`, that `maxDepth` is at most 16 and
80
+ * `paletteCount` below 2^26.
81
+ */
82
+ export function decodeRangeCodedWasm(buf, offset, paletteCount, recordCount, maxDepth) {
83
+ if (!enabled)
84
+ return undefined;
85
+ const decoder = getInstance();
86
+ if (!decoder)
87
+ return undefined;
88
+ const width = paletteCount <= 255 ? 1 : paletteCount <= 65535 ? 2 : 4;
89
+ const streamBytes = buf.length - offset;
90
+ // Slack past the stream: a stream that runs past its end reads a few
91
+ // bytes there before the fast path declines it.
92
+ const out = Math.ceil((streamBytes + 64) / 16) * 16;
93
+ const indices = out + 4 * recordCount;
94
+ const depthsAt = indices + width * recordCount;
95
+ const scratch = Math.ceil((depthsAt + recordCount) / 16) * 16;
96
+ const needed = scratch + FIXED_SCRATCH_BYTES + 4 * (2 * 2 ** maxDepth + paletteCount + 1);
97
+ const { memory } = decoder;
98
+ if (memory.buffer.byteLength < needed) {
99
+ try {
100
+ memory.grow(Math.ceil((needed - memory.buffer.byteLength) / PAGE_BYTES));
101
+ }
102
+ catch {
103
+ return undefined; // out of memory: leave it to the JavaScript path
104
+ }
105
+ }
106
+ new Uint8Array(memory.buffer, 0, streamBytes).set(buf.subarray(offset));
107
+ let ok;
108
+ try {
109
+ ok = decoder.decode(streamBytes, recordCount, paletteCount, maxDepth, width, out, scratch);
110
+ }
111
+ catch {
112
+ // A stream that runs far past its end can read past the memory.
113
+ ok = 0;
114
+ }
115
+ releaseWhenIdle();
116
+ if (!ok)
117
+ return undefined;
118
+ const { buffer } = memory;
119
+ return {
120
+ codes: new Uint32Array(buffer, out, recordCount).slice(),
121
+ depths: new Uint8Array(buffer, depthsAt, recordCount).slice(),
122
+ paletteIndices: width === 1
123
+ ? new Uint8Array(buffer, indices, recordCount).slice()
124
+ : width === 2
125
+ ? new Uint16Array(buffer, indices, recordCount).slice()
126
+ : new Uint32Array(buffer, indices, recordCount).slice(),
127
+ };
128
+ }
129
+ //# sourceMappingURL=rangeDecoder.js.map
@@ -0,0 +1,2 @@
1
+ /** rangeDecoder.wat's compiled module (1381 bytes), base64. */
2
+ export declare const RANGE_DECODER_WASM = "AGFzbQEAAAABFwNgAX8Bf2ACf38Bf2AHf39/f39/fwF/AwQDAAECBQMBAAEGFQR/AUEAC38BQQALfwFBAAt/AUEACwcTAgZtZW1vcnkCAAZkZWNvZGUAAgqKCgOBAQEDfyAALwEAIQEjAEEMdiABbCECIwEgAkkEQCACJAAgACABQYAgIAFrQQR2ajsBAAUjASACayQBIwAgAmskACAAIAEgAUEEdms7AQBBASEDCwJAA0AjAEGAgIAITw0BIwFBCHQjAi0AAHIkASMCQQFqJAIjAEEIdCQADAALCyADCzEBAX8gAEEASARAQQAPCyAAQR9xIQIgAiABTQRAQQEPCyACIAFBAWpGBEBBAg8LQQML0ggBJH9BAC0AAARAQQAPC0EBLQAAQRh0QQItAABBEHRyQQMtAABBCHRBBC0AAHJyJAFBfyQAQQUkAiAAJAMgBiEHIAZBgARqIQggBkHADGohCSAGQYAVaiEKIAZBgBdqIQsgBkGAJ2ohDCAMQbAGaiENIA1BBCADdGohDiAOQQQgA3RqIQ8gBSABQQJ0aiERIBEgASAEbGohEEEAIRIDQCAGIBJqQYAQOwEAIBJBAmohEiASQYAnSQ0ACyANQf8BQQggA3T8CwBBACESA0AgDyASQQJ0aiASNgIAIBJBAWohEiASIAJNDQALIAxCADcDACAMQQA2AghBASETQX8hFQJAA0AgFCABTw0BIBNFBEBBAA8LIBNBAWshEyAMIBNBDGxqIRsgGygCACEXIBsoAgQhGCAbKAIIIRkgGAR/IA0gGUECdGooAgAFQX8LIRwgGQR/IA4gGEECdGooAgAFQX8LIR0gHCAXEAEhHiAXIANJBEAgByAXQQR0IB5BAnRqIB0gFxABakEBdGoQAARAQQEgAyAXa0EBa3QhGiAXQQFqIRcgGyAXNgIAIBsgGCAaajYCBCAbIBkgGmo2AgggGyAXNgIMIBsgGDYCECAbIBkgGmo2AhQgGyAXNgIYIBsgGCAaajYCHCAbIBk2AiAgGyAXNgIkIBsgGDYCKCAbIBk2AiwgE0EEaiETDAILC0F/IBxBBXYgHEEASBshH0F/IB1BBXYgHUEASBshICAXQQN0IB8gIEZBAnRqIBUgH0ZBAXRqIBUgIEZqQQJ0IB5qISFBfyEiIB9BAE4EQCAIICFBAXRqEAAEQCAfISILCyAiQQBIICBBAE4gICAfR3FxBEAgCSAhQQF0ahAABEAgICEiCwtBACEjICJBAEgEQEEgQQAgH0EAThtBwABBACAgQQBOG2ohJEEAISUCQANAIAogJCAlakEBdGoQAEUNASAlQQFqISUgJUEgRgRAQQAPCwwACwtBASEmICUhEgJAA0AgEkUNASASQQFrIRIgJkEBdCALICVBBXQgEmpBAXRqEABqISYMAAsLICZBAWshJwJAA0AgIyACSwRAQQAPCyAPICNBAnRqKAIAISggKCAfRyAoICBHcQRAICdFDQIgJ0EBayEnCyAjQQFqISMMAAsLICghIgUDQCAPICNBAnRqKAIAICJHBEAgI0EBaiEjDAELCwsgIwRAIA9BBGogDyAjQQJ0/AoAACAPICI2AgALQQEgAyAXa3QhKSAiQQV0IBdyISpBACESA0AgDSAZIBJqQQJ0aiAqNgIAIA4gGCASakECdGogKjYCACASQQFqIRIgEiApSQ0ACyAFIBRBAnRqIBY2AgAgECAUaiAXOgAAIARBAUYEQCARIBRqICI6AAAFIARBAkYEQCARIBRBAXRqICI7AQAFIBEgFEECdGogIjYCAAsLIBYgKSApbGohFiAiIRUgFEEBaiEUDAALCyMCIwNGCw==";
@@ -0,0 +1,4 @@
1
+ // Generated by `npm run build:wasm -w reader` from wasm/rangeDecoder.wat; do not edit.
2
+ /** rangeDecoder.wat's compiled module (1381 bytes), base64. */
3
+ export const RANGE_DECODER_WASM = "AGFzbQEAAAABFwNgAX8Bf2ACf38Bf2AHf39/f39/fwF/AwQDAAECBQMBAAEGFQR/AUEAC38BQQALfwFBAAt/AUEACwcTAgZtZW1vcnkCAAZkZWNvZGUAAgqKCgOBAQEDfyAALwEAIQEjAEEMdiABbCECIwEgAkkEQCACJAAgACABQYAgIAFrQQR2ajsBAAUjASACayQBIwAgAmskACAAIAEgAUEEdms7AQBBASEDCwJAA0AjAEGAgIAITw0BIwFBCHQjAi0AAHIkASMCQQFqJAIjAEEIdCQADAALCyADCzEBAX8gAEEASARAQQAPCyAAQR9xIQIgAiABTQRAQQEPCyACIAFBAWpGBEBBAg8LQQML0ggBJH9BAC0AAARAQQAPC0EBLQAAQRh0QQItAABBEHRyQQMtAABBCHRBBC0AAHJyJAFBfyQAQQUkAiAAJAMgBiEHIAZBgARqIQggBkHADGohCSAGQYAVaiEKIAZBgBdqIQsgBkGAJ2ohDCAMQbAGaiENIA1BBCADdGohDiAOQQQgA3RqIQ8gBSABQQJ0aiERIBEgASAEbGohEEEAIRIDQCAGIBJqQYAQOwEAIBJBAmohEiASQYAnSQ0ACyANQf8BQQggA3T8CwBBACESA0AgDyASQQJ0aiASNgIAIBJBAWohEiASIAJNDQALIAxCADcDACAMQQA2AghBASETQX8hFQJAA0AgFCABTw0BIBNFBEBBAA8LIBNBAWshEyAMIBNBDGxqIRsgGygCACEXIBsoAgQhGCAbKAIIIRkgGAR/IA0gGUECdGooAgAFQX8LIRwgGQR/IA4gGEECdGooAgAFQX8LIR0gHCAXEAEhHiAXIANJBEAgByAXQQR0IB5BAnRqIB0gFxABakEBdGoQAARAQQEgAyAXa0EBa3QhGiAXQQFqIRcgGyAXNgIAIBsgGCAaajYCBCAbIBkgGmo2AgggGyAXNgIMIBsgGDYCECAbIBkgGmo2AhQgGyAXNgIYIBsgGCAaajYCHCAbIBk2AiAgGyAXNgIkIBsgGDYCKCAbIBk2AiwgE0EEaiETDAILC0F/IBxBBXYgHEEASBshH0F/IB1BBXYgHUEASBshICAXQQN0IB8gIEZBAnRqIBUgH0ZBAXRqIBUgIEZqQQJ0IB5qISFBfyEiIB9BAE4EQCAIICFBAXRqEAAEQCAfISILCyAiQQBIICBBAE4gICAfR3FxBEAgCSAhQQF0ahAABEAgICEiCwtBACEjICJBAEgEQEEgQQAgH0EAThtBwABBACAgQQBOG2ohJEEAISUCQANAIAogJCAlakEBdGoQAEUNASAlQQFqISUgJUEgRgRAQQAPCwwACwtBASEmICUhEgJAA0AgEkUNASASQQFrIRIgJkEBdCALICVBBXQgEmpBAXRqEABqISYMAAsLICZBAWshJwJAA0AgIyACSwRAQQAPCyAPICNBAnRqKAIAISggKCAfRyAoICBHcQRAICdFDQIgJ0EBayEnCyAjQQFqISMMAAsLICghIgUDQCAPICNBAnRqKAIAICJHBEAgI0EBaiEjDAELCwsgIwRAIA9BBGogDyAjQQJ0/AoAACAPICI2AgALQQEgAyAXa3QhKSAiQQV0IBdyISpBACESA0AgDSAZIBJqQQJ0aiAqNgIAIA4gGCASakECdGogKjYCACASQQFqIRIgEiApSQ0ACyAFIBRBAnRqIBY2AgAgECAUaiAXOgAAIARBAUYEQCARIBRqICI6AAAFIARBAkYEQCARIBRBAXRqICI7AQAFIBEgFEECdGogIjYCAAsLIBYgKSApbGohFiAiIRUgFEEBaiEUDAALCyMCIwNGCw==";
4
+ //# sourceMappingURL=rangeDecoderWasm.js.map
package/dist/reader.js CHANGED
@@ -929,7 +929,11 @@ export class CGeoDataReader {
929
929
  }
930
930
  async decodeChunk(index) {
931
931
  const compressed = await this.source.read(this.directory.chunkOffsets[index], this.directory.chunkLengths[index]);
932
- return this.decodes.run(async () => this.checkPalette(index, parseChunk(await inflateBlock(this.inflater, compressed), this.header.maxDepth, this.header.version)));
932
+ return this.decodes.run(async () => this.checkPalette(index, parseChunk(
933
+ // Version 2 chunks are range coded, not zlib (Section 8.7).
934
+ this.header.version >= 2
935
+ ? compressed
936
+ : await inflateBlock(this.inflater, compressed), this.header.maxDepth, this.header.version)));
933
937
  }
934
938
  checkPalette(index, chunk) {
935
939
  // Section 11: "every palette entry [MUST be] less than N".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ferrybig/cgeodata-reader",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Reverse geocoding without a database: resolves a latitude/longitude to its country, region and city from OpenStreetMap boundaries, reading a compact CGEODATA file. No dependencies; Node.js and browsers.",
5
5
  "keywords": [
6
6
  "reverse-geocoding",