@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/binary-format.md
ADDED
|
@@ -0,0 +1,2366 @@
|
|
|
1
|
+
# CGEODATA: A Compact Binary Format for Reverse Geocoding
|
|
2
|
+
|
|
3
|
+
Version 1 (draft)
|
|
4
|
+
|
|
5
|
+
## Abstract
|
|
6
|
+
|
|
7
|
+
This document specifies CGEODATA, a binary file format that maps a WGS84
|
|
8
|
+
coordinate to the country, region, and city that contain it, and that
|
|
9
|
+
carries a searchable list of those places. The format stores a quadtree
|
|
10
|
+
partition of the world as its leaf cells in Morton order, split into
|
|
11
|
+
independently compressed chunks so that a lookup reads only a small part of the file.
|
|
12
|
+
The place list is stored as delta-coded hierarchy counts plus
|
|
13
|
+
keyframe-compressed name blocks, so that names can be recovered by index
|
|
14
|
+
without decompressing the whole list.
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
1. [Introduction](#1-introduction)
|
|
19
|
+
2. [Conventions and Terminology](#2-conventions-and-terminology)
|
|
20
|
+
3. [Primitive Types](#3-primitive-types)
|
|
21
|
+
4. [File Layout](#4-file-layout)
|
|
22
|
+
5. [Header](#5-header)
|
|
23
|
+
6. [Directory](#6-directory)
|
|
24
|
+
7. [Place Table](#7-place-table)
|
|
25
|
+
8. [Chunks](#8-chunks)
|
|
26
|
+
9. [Lookup Procedure](#9-lookup-procedure)
|
|
27
|
+
10. [Encoder Considerations](#10-encoder-considerations)
|
|
28
|
+
11. [Robustness and Security Considerations](#11-robustness-and-security-considerations)
|
|
29
|
+
12. [Open Issues](#12-open-issues)
|
|
30
|
+
- [Appendix A. Worked Example](#appendix-a-worked-example)
|
|
31
|
+
- [Appendix B. Text Search Index](#appendix-b-text-search-index)
|
|
32
|
+
- [Appendix C. Place Location Index](#appendix-c-place-location-index)
|
|
33
|
+
- [Appendix D. References](#appendix-d-references)
|
|
34
|
+
|
|
35
|
+
## 1. Introduction
|
|
36
|
+
|
|
37
|
+
### 1.1. Motivation
|
|
38
|
+
|
|
39
|
+
Reverse geocoding for photo management (see the project's [README](https://github.com/ferrybig/loc-to-city#readme))
|
|
40
|
+
is commonly implemented by storing one labelled point per place and
|
|
41
|
+
returning the nearest label. Cities are not points, and near any border
|
|
42
|
+
the nearest label is frequently wrong. Storing full polygons is accurate
|
|
43
|
+
but large and requires polygon arithmetic at query time.
|
|
44
|
+
|
|
45
|
+
CGEODATA stores the result of point-in-polygon classification instead of
|
|
46
|
+
the polygons: the world is recursively subdivided into cells until each
|
|
47
|
+
cell is attributed to a single place, and the resulting leaf cells are
|
|
48
|
+
stored in Morton order. A lookup is then one binary search.
|
|
49
|
+
|
|
50
|
+
### 1.2. Design Goals
|
|
51
|
+
|
|
52
|
+
The format is designed with the following goals, in rough priority order:
|
|
53
|
+
|
|
54
|
+
1. **Small on disk.** The place names and the spatial index together
|
|
55
|
+
should be far smaller than a worldwide nearest-point table.
|
|
56
|
+
2. **Partial reads.** A reader MUST be able to answer a lookup by reading
|
|
57
|
+
and decompressing only a small, bounded part of the file, without
|
|
58
|
+
loading the entire file into memory. The file is expected to be large
|
|
59
|
+
at worldwide scale.
|
|
60
|
+
3. **Fast lookups** after a modest one-time load of the directory.
|
|
61
|
+
4. **Adequate accuracy for geotagging.** The target cell resolution is on
|
|
62
|
+
the order of a few hundred metres; the format itself supports finer
|
|
63
|
+
resolution at the cost of size.
|
|
64
|
+
5. **Searchable place names.** The place list supports listing and text
|
|
65
|
+
search over countries, regions, and cities, and resolving a place to
|
|
66
|
+
its parents. An optional sidecar index
|
|
67
|
+
([Appendix B](#appendix-b-text-search-index)) speeds up text search
|
|
68
|
+
without changing the file, and another
|
|
69
|
+
([Appendix C](#appendix-c-place-location-index)) gives each place a
|
|
70
|
+
representative coordinate.
|
|
71
|
+
6. **Implementable with no dependencies in JavaScript** (Node.js and
|
|
72
|
+
browsers) as well as other languages: only zlib, integer arithmetic,
|
|
73
|
+
and binary search are required.
|
|
74
|
+
|
|
75
|
+
### 1.3. Non-Goals
|
|
76
|
+
|
|
77
|
+
- The format does not store polygon geometry and cannot answer
|
|
78
|
+
distance-to-border or area queries.
|
|
79
|
+
- The file itself does not store a representative coordinate per place.
|
|
80
|
+
One is defined by the chunks ([Section C.3](#c3-representative-cell))
|
|
81
|
+
and can be precomputed into an optional sidecar file
|
|
82
|
+
([Appendix C](#appendix-c-place-location-index)).
|
|
83
|
+
- The format does not attempt exact answers at borders; cells that remain
|
|
84
|
+
ambiguous at the maximum depth are attributed to one place by the
|
|
85
|
+
encoder.
|
|
86
|
+
|
|
87
|
+
## 2. Conventions and Terminology
|
|
88
|
+
|
|
89
|
+
### 2.1. Requirements Language
|
|
90
|
+
|
|
91
|
+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
|
92
|
+
"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
|
|
93
|
+
"OPTIONAL" in this document are to be interpreted as described in BCP 14
|
|
94
|
+
[RFC2119] [RFC8174] when, and only when, they appear in all capitals, as
|
|
95
|
+
shown here.
|
|
96
|
+
|
|
97
|
+
### 2.2. Terms
|
|
98
|
+
|
|
99
|
+
- **Byte**: an octet, 8 bits.
|
|
100
|
+
- **Writer**, **encoder**: software that produces a CGEODATA file.
|
|
101
|
+
- **Reader**, **decoder**: software that consumes a CGEODATA file.
|
|
102
|
+
- **Place**: a country, region, or city. Every place has an
|
|
103
|
+
**identifier** (ID), which is its zero-based position in the place
|
|
104
|
+
table ([Section 7](#7-place-table)).
|
|
105
|
+
- **Chunk grid**: the fixed rectangular grid of `W × H` chunks that
|
|
106
|
+
covers the world ([Section 6.1](#61-chunk-grid)).
|
|
107
|
+
- **Chunk**: one cell of the chunk grid, together with its stored
|
|
108
|
+
spatial index ([Section 8](#8-chunks)). The stored form is one
|
|
109
|
+
independently compressed block.
|
|
110
|
+
- **Cell**: a rectangular area produced by quadtree subdivision of a
|
|
111
|
+
chunk. A cell at depth `d` is `2^d` times smaller than the chunk in each
|
|
112
|
+
direction.
|
|
113
|
+
- **Record**: one leaf cell stored in a chunk, attributing that cell to
|
|
114
|
+
one place (or to no place).
|
|
115
|
+
- **Morton code** (also Z-order code): the integer obtained by
|
|
116
|
+
interleaving the bits of a cell's row and column, so that quadtree
|
|
117
|
+
order becomes numeric order ([Section 8.1](#81-cell-grid-and-morton-codes)).
|
|
118
|
+
- **Unknown**: the lookup result for a coordinate that no record
|
|
119
|
+
attributes to a place.
|
|
120
|
+
|
|
121
|
+
### 2.3. Coordinate System
|
|
122
|
+
|
|
123
|
+
All coordinates are WGS84 latitude and longitude in decimal degrees. A
|
|
124
|
+
valid latitude satisfies `-90 <= lat < 90`. A valid longitude satisfies
|
|
125
|
+
`-180 <= lon < 180`. Readers MUST reject coordinates outside these ranges.
|
|
126
|
+
Readers do not normalise longitude; a caller that wishes to accept
|
|
127
|
+
`lon = 180` or values outside the range MUST wrap them itself before the
|
|
128
|
+
lookup.
|
|
129
|
+
|
|
130
|
+
All intervals in this document are half-open: `[a, b)` includes `a` and
|
|
131
|
+
excludes `b`. The world grid is equirectangular: cells are equal in
|
|
132
|
+
degrees, not in metres.
|
|
133
|
+
|
|
134
|
+
## 3. Primitive Types
|
|
135
|
+
|
|
136
|
+
### 3.1. Fixed-Width Integers
|
|
137
|
+
|
|
138
|
+
`u8` is one byte. `u16`, `u24`, `u32`, and `u64` are unsigned integers of
|
|
139
|
+
2, 3, 4, and 8 bytes, stored least significant byte first
|
|
140
|
+
(little-endian). `u64` is used only for checksums; a reader never needs
|
|
141
|
+
its value as a number and can compare it byte by byte, which sidesteps
|
|
142
|
+
the 53-bit integer limit of JavaScript.
|
|
143
|
+
|
|
144
|
+
### 3.2. Variable-Length Integers (varint)
|
|
145
|
+
|
|
146
|
+
A `varint` is an unsigned integer encoded as in unsigned LEB128 (the
|
|
147
|
+
"base 128 varint" of Protocol Buffers):
|
|
148
|
+
|
|
149
|
+
- Each byte carries 7 bits of the value in its low bits.
|
|
150
|
+
- The high bit (`0x80`) is a continuation flag: `1` means another byte
|
|
151
|
+
follows, `0` marks the last byte.
|
|
152
|
+
- Groups are stored least significant first. The decoder accumulates
|
|
153
|
+
`byte & 0x7F` shifted left by `7 × i` for the `i`-th byte, and stops
|
|
154
|
+
after the first byte whose high bit is clear.
|
|
155
|
+
|
|
156
|
+
Every varint in this format MUST be less than `2^53`, so that it can be
|
|
157
|
+
represented exactly as an IEEE 754 double. An encoding therefore never
|
|
158
|
+
exceeds 8 bytes; readers MUST reject a varint longer than 8 bytes.
|
|
159
|
+
Writers MUST use the shortest encoding (no trailing `0x80 0x00`
|
|
160
|
+
padding). Readers MAY reject non-minimal encodings.
|
|
161
|
+
|
|
162
|
+
Examples: `0` is `00`; `127` is `7f`; `128` is `80 01`; `300` is
|
|
163
|
+
`ac 02`; `8192` is `80 40`.
|
|
164
|
+
|
|
165
|
+
### 3.3. Delta-Coded Lists
|
|
166
|
+
|
|
167
|
+
Several lists in this format are stored delta-coded: each stored varint is
|
|
168
|
+
the difference between an element and the element before it, with an
|
|
169
|
+
implicit previous element of `0` before the first. A delta-coded list is
|
|
170
|
+
therefore always non-decreasing; where this document says "strictly
|
|
171
|
+
increasing", every stored delta after the first MUST be at least `1`.
|
|
172
|
+
|
|
173
|
+
### 3.4. Strings
|
|
174
|
+
|
|
175
|
+
Strings are UTF-8 [RFC3629]. The header's attribution string is length
|
|
176
|
+
prefixed. Place names are `NUL` (`0x00`) terminated inside their
|
|
177
|
+
compressed block and MUST NOT contain `NUL`.
|
|
178
|
+
|
|
179
|
+
### 3.5. Compressed Blocks
|
|
180
|
+
|
|
181
|
+
A compressed block is one complete zlib stream [RFC1950] containing
|
|
182
|
+
DEFLATE data [RFC1951]. Every compressed block in this format is
|
|
183
|
+
self-contained: it can be inflated without any other block. Readers
|
|
184
|
+
locate a block by the byte lengths recorded in the file, not by scanning
|
|
185
|
+
for stream boundaries.
|
|
186
|
+
|
|
187
|
+
## 4. File Layout
|
|
188
|
+
|
|
189
|
+
A CGEODATA file consists of four consecutive sections, with no padding or
|
|
190
|
+
alignment between or within them:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
+---------------------------+
|
|
194
|
+
| Header (Sec. 5) | fixed fields, uncompressed
|
|
195
|
+
+---------------------------+
|
|
196
|
+
| Directory (Sec. 6) | place table length + chunk length table
|
|
197
|
+
+---------------------------+
|
|
198
|
+
| Place table (Sec. 7) | hierarchy counts + compressed name blocks
|
|
199
|
+
+---------------------------+
|
|
200
|
+
| Chunk 0 (Sec. 8) | one compressed block, or absent if empty
|
|
201
|
+
| Chunk 1 |
|
|
202
|
+
| ... |
|
|
203
|
+
| Chunk W*H - 1 |
|
|
204
|
+
+---------------------------+
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The header and directory are small and are expected to be read once and
|
|
208
|
+
kept in memory. The place table's uncompressed prefix is also small. The
|
|
209
|
+
compressed name blocks and the chunks are read on demand.
|
|
210
|
+
|
|
211
|
+
The file has no trailer. The end of the last chunk is the end of the
|
|
212
|
+
file.
|
|
213
|
+
|
|
214
|
+
Derived sidecar files, such as the text search index of
|
|
215
|
+
[Appendix B](#appendix-b-text-search-index) and the place location index
|
|
216
|
+
of [Appendix C](#appendix-c-place-location-index), are separate files that
|
|
217
|
+
share the signature and the version field's kind mechanism
|
|
218
|
+
([Section 5](#5-header)). They are never embedded in a CGEODATA file,
|
|
219
|
+
and a CGEODATA file never refers to them.
|
|
220
|
+
|
|
221
|
+
## 5. Header
|
|
222
|
+
|
|
223
|
+
| Field | Type | Description |
|
|
224
|
+
|----------------------|------------|-------------|
|
|
225
|
+
| `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. |
|
|
227
|
+
| `attribution_length` | varint | Byte length of `attribution`. |
|
|
228
|
+
| `attribution` | bytes | UTF-8 text, MAY contain Markdown, intended for display to humans, for example `[OpenStreetMap](https://openstreetmap.org/copyright)`. MAY be empty. |
|
|
229
|
+
| `width_exp` | u8 | The chunk grid has `W = 2^width_exp` columns. |
|
|
230
|
+
| `height_exp` | u8 | The chunk grid has `H = 2^height_exp` rows. |
|
|
231
|
+
| `max_depth` | u8 | The maximum quadtree depth `D` inside a chunk. Every Morton code in the file is `2 × D` bits wide. |
|
|
232
|
+
| `keyframe_exp` | u8 | Name block size. `0` means the whole name list is a single block. Any other value `k` means each block holds `I = 2^k` names. See [Section 7.3](#73-name-blocks). |
|
|
233
|
+
|
|
234
|
+
The `version` field carries two numbers: its low 4 bits are the format
|
|
235
|
+
version within a kind, and the remaining high bits are the **kind**,
|
|
236
|
+
which distinguishes the different files that share the `CGEODATA`
|
|
237
|
+
signature. The kinds are:
|
|
238
|
+
|
|
239
|
+
| Kind | Value of `version` | File |
|
|
240
|
+
|------|--------------------|------|
|
|
241
|
+
| `0` | `0` to `15` | A CGEODATA file as described by Sections 4 to 9. |
|
|
242
|
+
| `1` | `16` to `31` | A text search index derived from a kind `0` file ([Appendix B](#appendix-b-text-search-index)). |
|
|
243
|
+
| `2` | `32` to `47` | A place location index derived from a kind `0` file ([Appendix C](#appendix-c-place-location-index)). |
|
|
244
|
+
|
|
245
|
+
Other kinds are reserved. Kinds `1` and up are sidecar files: each is
|
|
246
|
+
derived from one kind `0` file, is regenerated rather than edited, and
|
|
247
|
+
has its own layout after the `version` field. Whether a file is a
|
|
248
|
+
sidecar is fixed by its kind, not by its name or location.
|
|
249
|
+
|
|
250
|
+
Readers MUST reject a file whose signature does not match, whose kind
|
|
251
|
+
is not the kind they expect, or whose version they do not implement.
|
|
252
|
+
Version `0` of any kind is a pre-release version: files written under
|
|
253
|
+
this document carry no compatibility promise once the version number
|
|
254
|
+
changes.
|
|
255
|
+
|
|
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.
|
|
260
|
+
|
|
261
|
+
Constraints:
|
|
262
|
+
|
|
263
|
+
- `max_depth` MUST be at most `26`, so that a Morton code (`2 × D` bits)
|
|
264
|
+
fits in 52 bits and is an exact integer in JavaScript.
|
|
265
|
+
- `width_exp` and `height_exp` MUST each be at most `15`. Larger values
|
|
266
|
+
are reserved.
|
|
267
|
+
|
|
268
|
+
Choosing the grid sizes is discussed in
|
|
269
|
+
[Section 10](#10-encoder-considerations).
|
|
270
|
+
|
|
271
|
+
## 6. Directory
|
|
272
|
+
|
|
273
|
+
### 6.1. Chunk Grid
|
|
274
|
+
|
|
275
|
+
The chunk grid divides the world into `W` columns and `H` rows of equal
|
|
276
|
+
angular size. Column `c` (`0 <= c < W`) covers longitudes
|
|
277
|
+
`[-180 + c × 360 / W, -180 + (c + 1) × 360 / W)`. Row `r` (`0 <= r < H`)
|
|
278
|
+
covers latitudes `[-90 + r × 180 / H, -90 + (r + 1) × 180 / H)`. Row `0`
|
|
279
|
+
is the southernmost row; the origin of the grid is the south-west corner
|
|
280
|
+
of the world at `(lat -90, lon -180)`.
|
|
281
|
+
|
|
282
|
+
Chunks are numbered in row-major order: chunk index `i = r × W + c`.
|
|
283
|
+
This is the order of both the chunk length table and the stored chunks.
|
|
284
|
+
|
|
285
|
+
For example, with `width_exp = 6` (`W = 64`) each column is `5.625°`
|
|
286
|
+
wide: column `0` covers `[-180, -174.375)`, column `1` covers
|
|
287
|
+
`[-174.375, -168.75)`, and so on.
|
|
288
|
+
|
|
289
|
+
### 6.2. Layout
|
|
290
|
+
|
|
291
|
+
Immediately after the header:
|
|
292
|
+
|
|
293
|
+
| Field | Type | Description |
|
|
294
|
+
|----------------------|-------------------|-------------|
|
|
295
|
+
| `place_table_length` | varint | Byte length of the entire place table ([Section 7](#7-place-table)), including its uncompressed prefix. |
|
|
296
|
+
| `chunk_lengths` | `W × H` varints | Entry `i` is the byte length of the compressed block for chunk `i`. `0` means chunk `i` is empty: no bytes are stored for it, and every coordinate inside it resolves to unknown. |
|
|
297
|
+
|
|
298
|
+
Entries are plain lengths, not deltas and not offsets.
|
|
299
|
+
|
|
300
|
+
### 6.3. Locating a Chunk
|
|
301
|
+
|
|
302
|
+
Let `chunks_start` be the file offset of the first byte after the place
|
|
303
|
+
table, that is, the offset of the end of `chunk_lengths` plus
|
|
304
|
+
`place_table_length`. Then the compressed block for chunk `i` occupies
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
offset(i) = chunks_start + sum(chunk_lengths[0 .. i-1])
|
|
308
|
+
length(i) = chunk_lengths[i]
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Readers SHOULD compute the prefix sums once when loading the directory,
|
|
312
|
+
so that locating a chunk is an array index. The directory alone is
|
|
313
|
+
enough to locate any chunk; no chunk needs to be decompressed to find
|
|
314
|
+
another.
|
|
315
|
+
|
|
316
|
+
The last chunk's length is recorded like any other. Because each chunk
|
|
317
|
+
is a self-contained zlib stream, a reader could inflate it without
|
|
318
|
+
knowing its length, but the recorded length lets a reader bound the read
|
|
319
|
+
and verify that the file is complete.
|
|
320
|
+
|
|
321
|
+
## 7. Place Table
|
|
322
|
+
|
|
323
|
+
The place table is the list of all countries, regions, and cities the
|
|
324
|
+
file knows about, their names, and the parent of each region and city.
|
|
325
|
+
It is used to turn the identifier found by a spatial lookup into names,
|
|
326
|
+
and to support text search and listing.
|
|
327
|
+
|
|
328
|
+
### 7.1. Identifiers and Hierarchy
|
|
329
|
+
|
|
330
|
+
The hierarchy has exactly three levels: countries contain regions, and
|
|
331
|
+
regions contain cities. Places are numbered in one shared identifier
|
|
332
|
+
space:
|
|
333
|
+
|
|
334
|
+
- IDs `0 .. C-1` are the `C` countries.
|
|
335
|
+
- IDs `C .. C+R-1` are the `R` regions, grouped by country: all regions
|
|
336
|
+
of country `0` first, then all regions of country `1`, and so on.
|
|
337
|
+
- IDs `C+R .. N-1` are the cities, grouped by region in the same way.
|
|
338
|
+
`N = C + R + (number of cities)` is the total number of places.
|
|
339
|
+
|
|
340
|
+
An identifier denotes exactly one place; a value used for a country is
|
|
341
|
+
never also used for a region or city. Chunk palettes
|
|
342
|
+
([Section 8.3](#83-palette)) MAY reference any place, at any level: a
|
|
343
|
+
cell whose most specific known place is a region resolves to that
|
|
344
|
+
region.
|
|
345
|
+
|
|
346
|
+
The grouping order is what makes the hierarchy cheap to store: instead
|
|
347
|
+
of a parent pointer per place, the table stores only how many children
|
|
348
|
+
each parent has, and readers recover parents from prefix sums. A useful
|
|
349
|
+
side effect is that a search that stops after a limited number of hits
|
|
350
|
+
naturally reports countries before regions before cities.
|
|
351
|
+
|
|
352
|
+
### 7.2. Layout
|
|
353
|
+
|
|
354
|
+
| Field | Type | Description |
|
|
355
|
+
|---------------------|----------------------|-------------|
|
|
356
|
+
| `country_count` | varint | `C`, the number of countries. |
|
|
357
|
+
| `region_counts` | `C` varints | Entry `j` is the number of regions belonging to country `j`. `R` is the sum of all entries. |
|
|
358
|
+
| `city_counts` | `R` varints | Entry `j` is the number of cities belonging to region `j` (region index, not ID). The number of cities is the sum of all entries. |
|
|
359
|
+
| `block_lengths` | `K - 1` varints | Entry `b` is the byte length of name block `b`, for `0 <= b < K-1`. `K` is defined in Section 7.3. |
|
|
360
|
+
| `name_blocks` | `K` compressed blocks | The name list, in ID order, split into blocks as described in Section 7.3. |
|
|
361
|
+
|
|
362
|
+
The four count fields are uncompressed and are expected to be read once
|
|
363
|
+
and kept in memory. A country's region count or a region's city count
|
|
364
|
+
MAY be `0`.
|
|
365
|
+
|
|
366
|
+
### 7.3. Name Blocks
|
|
367
|
+
|
|
368
|
+
The name list is the concatenation, in ID order, of every place's name
|
|
369
|
+
entry ([Section 7.4](#74-name-entries)), each followed by a `NUL` byte.
|
|
370
|
+
It is split into `K` blocks of `I` consecutive entries each (the last
|
|
371
|
+
block MAY be shorter), where
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
I = 2^keyframe_exp (if keyframe_exp != 0)
|
|
375
|
+
K = ceil(N / I) (if keyframe_exp != 0)
|
|
376
|
+
K = 1 (if keyframe_exp == 0)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
A file with no places (`N = 0`) has `K = 0` and no name blocks.
|
|
380
|
+
|
|
381
|
+
Block `b` holds the entries with IDs `b × I .. min((b + 1) × I, N) - 1`.
|
|
382
|
+
A block boundary MAY fall anywhere, including between a country and a
|
|
383
|
+
region. Each block is compressed separately as one compressed block
|
|
384
|
+
([Section 3.5](#35-compressed-blocks)).
|
|
385
|
+
|
|
386
|
+
Block `b` starts at
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
names_start + sum(block_lengths[0 .. b-1])
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
where `names_start` is the offset of the first byte after
|
|
393
|
+
`block_lengths`. The length of the last block is not stored; it extends
|
|
394
|
+
to the end of the place table, so its length is `place_table_length`
|
|
395
|
+
minus the number of bytes consumed by everything before it.
|
|
396
|
+
|
|
397
|
+
The blocks act as keyframes for random access: to recover the name of
|
|
398
|
+
place `id`, a reader inflates only block `floor(id / I)` and skips
|
|
399
|
+
`id mod I` `NUL`-terminated entries. A smaller `I` gives cheaper random
|
|
400
|
+
access; a larger `I` gives better compression, since zlib finds more
|
|
401
|
+
repetition in a larger block, and a shorter `block_lengths` list.
|
|
402
|
+
|
|
403
|
+
`keyframe_exp = 7` (blocks of 128 names) is RECOMMENDED. Measured on the
|
|
404
|
+
worldwide file (about 319,000 places), with a reader that opens the file
|
|
405
|
+
afresh for every operation so that no name block is cached, which is
|
|
406
|
+
the situation of a reader that is queried only occasionally:
|
|
407
|
+
|
|
408
|
+
| `keyframe_exp` | Place table | Resolving one place | Scanning all names |
|
|
409
|
+
|----------------|-------------|---------------------|--------------------|
|
|
410
|
+
| `13` | 1.64 MB | 4.9 ms | 134 ms |
|
|
411
|
+
| `11` | 1.70 MB | 1.7 ms | 142 ms |
|
|
412
|
+
| `9` | 1.82 MB | 0.9 ms | 141 ms |
|
|
413
|
+
| `7` | 2.05 MB | 0.7 ms | 152 ms |
|
|
414
|
+
| `5` | 2.53 MB | 1.4 ms | 210 ms |
|
|
415
|
+
|
|
416
|
+
"Resolving one place" is a place and its parents' names, as every
|
|
417
|
+
lookup ([Section 9](#9-lookup-procedure)) that finds a place needs.
|
|
418
|
+
Name access is random in nearly every use: a lookup needs up to three
|
|
419
|
+
names from anywhere in the table, and a search confirms candidates
|
|
420
|
+
spread across it ([Section B.9](#b9-query-procedure)), so every
|
|
421
|
+
operation other than a full scan pays for one block per distinct name
|
|
422
|
+
it touches, and a smaller block is directly cheaper. Below `7` the
|
|
423
|
+
fixed costs take over: a reader parses `K - 1` block lengths when it
|
|
424
|
+
opens the file, and each block carries its own zlib framing, so both
|
|
425
|
+
opening the file and a full scan get slower. The place table grows by
|
|
426
|
+
a quarter from `13` to `7`, which is small beside the chunks at any
|
|
427
|
+
useful `max_depth`.
|
|
428
|
+
|
|
429
|
+
### 7.4. Name Entries
|
|
430
|
+
|
|
431
|
+
A name entry is one or more names separated by `|` (`0x7C`), interpreted
|
|
432
|
+
by position:
|
|
433
|
+
|
|
434
|
+
1. The first field is the local name, as the place is called in its own
|
|
435
|
+
language, for example `Nederland`. It MUST NOT be empty.
|
|
436
|
+
2. The second field is the international (English) name, for example
|
|
437
|
+
`The Netherlands`. It MAY be empty or absent, meaning the
|
|
438
|
+
international name is the same as the local name.
|
|
439
|
+
3. Any further fields are alternative names or spellings, useful for
|
|
440
|
+
search, for example the historical or transliterated names.
|
|
441
|
+
|
|
442
|
+
Writers SHOULD omit trailing empty fields, so a place with a single name
|
|
443
|
+
is stored as `Amsterdam`, not `Amsterdam|`. Fields MUST NOT contain `|`
|
|
444
|
+
or `NUL`; there is no escape mechanism. A reader that displays a place
|
|
445
|
+
SHOULD show the local name, and a search SHOULD match against every
|
|
446
|
+
field.
|
|
447
|
+
|
|
448
|
+
Examples:
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
Nederland|The Netherlands
|
|
452
|
+
Den Haag|The Hague|'s-Gravenhage
|
|
453
|
+
Amsterdam
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### 7.5. Resolving an Identifier
|
|
457
|
+
|
|
458
|
+
Given the counts of Section 7.2, a reader classifies `id` as follows:
|
|
459
|
+
|
|
460
|
+
- If `id < C`, it is country `id`.
|
|
461
|
+
- Otherwise, if `id < C + R`, it is the region with region index
|
|
462
|
+
`j = id - C`. Its country is the largest `c` such that
|
|
463
|
+
`sum(region_counts[0 .. c-1]) <= j`.
|
|
464
|
+
- Otherwise it is the city with city index `j = id - C - R`. Its region
|
|
465
|
+
index is the largest `r` such that `sum(city_counts[0 .. r-1]) <= j`,
|
|
466
|
+
and its country follows from the region as above.
|
|
467
|
+
|
|
468
|
+
Readers SHOULD precompute the two prefix-sum arrays at load time, after
|
|
469
|
+
which both steps are a binary search. Readers MUST reject an `id` that
|
|
470
|
+
is not less than `N`.
|
|
471
|
+
|
|
472
|
+
## 8. Chunks
|
|
473
|
+
|
|
474
|
+
Each non-empty chunk stores the spatial index for its area as a single
|
|
475
|
+
compressed block ([Section 3.5](#35-compressed-blocks)). This section
|
|
476
|
+
describes the inflated content.
|
|
477
|
+
|
|
478
|
+
### 8.1. Cell Grid and Morton Codes
|
|
479
|
+
|
|
480
|
+
Inside a chunk, a quadtree of depth `D = max_depth` subdivides the chunk
|
|
481
|
+
into `2^D × 2^D` cells of equal angular size. A cell is addressed by its
|
|
482
|
+
column `x` and row `y`, each in `[0, 2^D)`, counted from the chunk's
|
|
483
|
+
south-west corner: `x` increases eastward and `y` increases northward.
|
|
484
|
+
|
|
485
|
+
The full-precision Morton code of cell `(x, y)` is the `2 × D`-bit
|
|
486
|
+
integer obtained by interleaving the bits of `y` and `x`, most
|
|
487
|
+
significant first, with the `y` bit placed above the `x` bit in every
|
|
488
|
+
pair:
|
|
489
|
+
|
|
490
|
+
```
|
|
491
|
+
code = 0
|
|
492
|
+
for i from D-1 down to 0:
|
|
493
|
+
code = (code << 1) | ((y >> i) & 1)
|
|
494
|
+
code = (code << 1) | ((x >> i) & 1)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Equivalently, reading the code two bits at a time from the top selects a
|
|
498
|
+
quadrant at each level: `00` south-west, `01` south-east, `10`
|
|
499
|
+
north-west, `11` north-east.
|
|
500
|
+
|
|
501
|
+
A cell at depth `d < D` is the union of the `4^(D-d)` full-precision
|
|
502
|
+
cells whose codes share its top `2 × d` bits. Its **stored code** is the
|
|
503
|
+
smallest of those codes: the `2 × d` path bits followed by
|
|
504
|
+
`2 × (D - d)` zero bits. Consequently the stored code of a cell is the
|
|
505
|
+
minimum full-precision code of any point inside it, which is the
|
|
506
|
+
invariant the lookup in [Section 9](#9-lookup-procedure) depends on.
|
|
507
|
+
|
|
508
|
+
Codes are chunk-local. Cell `(0, 0)` is the chunk's own south-west
|
|
509
|
+
corner, not the world's. This keeps codes small.
|
|
510
|
+
|
|
511
|
+
For `D = 2` the sixteen full-precision codes are laid out as follows
|
|
512
|
+
(north is up):
|
|
513
|
+
|
|
514
|
+
```
|
|
515
|
+
y=3 | 10 11 14 15
|
|
516
|
+
y=2 | 8 9 12 13
|
|
517
|
+
y=1 | 2 3 6 7
|
|
518
|
+
y=0 | 0 1 4 5
|
|
519
|
+
+----------------
|
|
520
|
+
x=0 x=1 x=2 x=3
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### 8.2. Layout
|
|
524
|
+
|
|
525
|
+
| Field | Type | Description |
|
|
526
|
+
|-------------------|----------------------------|-------------|
|
|
527
|
+
| `palette_count` | varint | `P`, the number of palette entries. |
|
|
528
|
+
| `palette` | `P` varints, delta-coded | Place IDs referenced by this chunk, strictly increasing ([Section 8.3](#83-palette)). |
|
|
529
|
+
| `record_count` | varint | `M`, the number of records. |
|
|
530
|
+
| `depths` | `M` u8 | Depth `d` of each record, in Morton order. |
|
|
531
|
+
| `palette_indices` | `M` × `S` bytes | Palette index of each record, in Morton order, as unsigned little-endian integers of `S` bytes. |
|
|
532
|
+
|
|
533
|
+
The two per-record arrays are stored one after the other rather than
|
|
534
|
+
interleaved per record. Grouping same-typed values together lets zlib
|
|
535
|
+
find far more repetition; on the prototype data this columnar layout
|
|
536
|
+
plus delta coding reduced the index about twelvefold compared with
|
|
537
|
+
fixed 8-byte records.
|
|
538
|
+
|
|
539
|
+
A record's Morton code is not stored: the records tile the chunk in
|
|
540
|
+
Morton order, so each one starts where the one before it ends
|
|
541
|
+
([Section 8.4](#84-records)). Both arrays are fixed width, so the length
|
|
542
|
+
of the block follows from `P` and `M`.
|
|
543
|
+
|
|
544
|
+
The inflated block MUST contain exactly these fields and nothing else:
|
|
545
|
+
after `palette_indices` the block ends.
|
|
546
|
+
|
|
547
|
+
### 8.3. Palette
|
|
548
|
+
|
|
549
|
+
The palette lists every place ID referenced by the chunk's records, in
|
|
550
|
+
strictly increasing order, delta-coded ([Section 3.3](#33-delta-coded-lists)).
|
|
551
|
+
The first stored value is the first ID itself (its delta from `0`), so an
|
|
552
|
+
ID of `0`, the first country, can be referenced.
|
|
553
|
+
|
|
554
|
+
Records refer to the palette by a **local index** in `[0, P]`:
|
|
555
|
+
|
|
556
|
+
- Index `0` means "no place". A record with index `0` resolves to
|
|
557
|
+
unknown, exactly as if no record covered the cell.
|
|
558
|
+
- Index `k` for `1 <= k <= P` refers to `palette[k - 1]`.
|
|
559
|
+
|
|
560
|
+
The index width `S` is derived from `P`:
|
|
561
|
+
|
|
562
|
+
| `P` | `S` |
|
|
563
|
+
|-------------------------|---------|
|
|
564
|
+
| `P <= 255` | 1 byte |
|
|
565
|
+
| `256 <= P <= 65535` | 2 bytes |
|
|
566
|
+
| `65536 <= P <= 2^24-1` | 3 bytes |
|
|
567
|
+
| otherwise | 4 bytes |
|
|
568
|
+
|
|
569
|
+
Most chunks are expected to reference a few dozen places at most and to
|
|
570
|
+
use 1-byte indices.
|
|
571
|
+
|
|
572
|
+
Every palette entry MUST be a valid place ID, that is, less than `N`.
|
|
573
|
+
|
|
574
|
+
### 8.4. Records
|
|
575
|
+
|
|
576
|
+
Record `j` is the triple `(codes[j], depths[j], palette_indices[j])`.
|
|
577
|
+
It states that the cell at depth `depths[j]` whose stored code is
|
|
578
|
+
`codes[j]` is attributed to the place selected by `palette_indices[j]`.
|
|
579
|
+
|
|
580
|
+
A cell denoted by `(code, d)` covers the full-precision code range
|
|
581
|
+
`[code, code + 4^(D-d))`. The records cover the chunk from its first
|
|
582
|
+
full-precision cell on, without gaps, so each code is where the record
|
|
583
|
+
before it ends:
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
codes[0] = 0
|
|
587
|
+
codes[j+1] = codes[j] + 4^(D - depths[j])
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
An empty area between two places is covered by records with palette
|
|
591
|
+
index `0`. The records MAY end before the chunk does: the cells from
|
|
592
|
+
`codes[M-1] + 4^(D - depths[M-1])` up to `4^D` have no record and resolve
|
|
593
|
+
to unknown. A chunk with no place at all need not be stored
|
|
594
|
+
([Section 6.2](#62-layout)).
|
|
595
|
+
|
|
596
|
+
### 8.5. Invariants
|
|
597
|
+
|
|
598
|
+
A chunk MUST satisfy all of the following. Readers MAY verify them at
|
|
599
|
+
load time and MUST NOT rely on undefined behaviour if they do not.
|
|
600
|
+
|
|
601
|
+
1. `0 <= depths[j] <= D` for every record.
|
|
602
|
+
2. The low `2 × (D - depths[j])` bits of `codes[j]` are zero (the code
|
|
603
|
+
is a stored code as defined in Section 8.1).
|
|
604
|
+
3. The records end within the chunk:
|
|
605
|
+
`codes[M-1] + 4^(D - depths[M-1]) <= 4^D`.
|
|
606
|
+
4. Every palette index is at most `P`.
|
|
607
|
+
|
|
608
|
+
Codes are then strictly increasing, every code is less than `4^D`, and
|
|
609
|
+
cells do not overlap, by construction.
|
|
610
|
+
|
|
611
|
+
### 8.6. Version 0 Chunks
|
|
612
|
+
|
|
613
|
+
In a version `0` file ([Section 5](#5-header)), a chunk stores every
|
|
614
|
+
record's code, between `record_count` and `depths`:
|
|
615
|
+
|
|
616
|
+
| Field | Type | Description |
|
|
617
|
+
|-------------------|----------------------------|-------------|
|
|
618
|
+
| `palette_count` | varint | As in Section 8.2. |
|
|
619
|
+
| `palette` | `P` varints, delta-coded | As in Section 8.2. |
|
|
620
|
+
| `record_count` | varint | As in Section 8.2. |
|
|
621
|
+
| `codes_length` | varint | Byte length of `codes`. |
|
|
622
|
+
| `codes` | `M` varints, delta-coded | Stored Morton codes, strictly increasing. |
|
|
623
|
+
| `depths` | `M` u8 | As in Section 8.2. |
|
|
624
|
+
| `palette_indices` | `M` × `S` bytes | As in Section 8.2. |
|
|
625
|
+
|
|
626
|
+
The records need not tile the chunk: a writer could leave out any empty
|
|
627
|
+
cell, not only those after the last record, and a coordinate in such a
|
|
628
|
+
gap resolves to unknown. Invariants 1, 2 and 4 of
|
|
629
|
+
[Section 8.5](#85-invariants) apply, and invariant 3 is replaced by:
|
|
630
|
+
|
|
631
|
+
- Codes are strictly increasing, `codes[j] < codes[j+1]`, and cells do
|
|
632
|
+
not overlap, `codes[j] + 4^(D - depths[j]) <= codes[j+1]`.
|
|
633
|
+
- Every code is less than `4^D`.
|
|
634
|
+
|
|
635
|
+
Version `1` dropped the codes because they were nearly all redundant.
|
|
636
|
+
A writer's records hardly ever leave a gap, and where none is left, a
|
|
637
|
+
code delta is the size of the record before it, `4^(D - depths[j])`.
|
|
638
|
+
zlib, which sees the codes and the depths as separate columns, did not
|
|
639
|
+
find that out. In the worldwide file, 1.66 million gaps among 106.4
|
|
640
|
+
million records took 3.77 million index-`0` records (3.5% more records)
|
|
641
|
+
to fill, and the chunks went from 62.2 MB to 45.6 MB (-26.7%), with the
|
|
642
|
+
same cell for every place.
|
|
643
|
+
|
|
644
|
+
## 9. Lookup Procedure
|
|
645
|
+
|
|
646
|
+
To resolve a coordinate `(lat, lon)` to a place ID, a reader performs the
|
|
647
|
+
following steps. `W`, `H`, and `D` come from the header.
|
|
648
|
+
|
|
649
|
+
1. **Validate.** If `lat` or `lon` is outside the ranges of
|
|
650
|
+
[Section 2.3](#23-coordinate-system), fail.
|
|
651
|
+
2. **Compute global cell coordinates.** Let `NX = W × 2^D` and
|
|
652
|
+
`NY = H × 2^D` be the total number of full-precision cells across the
|
|
653
|
+
world in each direction.
|
|
654
|
+
|
|
655
|
+
```
|
|
656
|
+
gx = floor((lon + 180) / 360 × NX)
|
|
657
|
+
gy = floor((lat + 90) / 180 × NY)
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Because floating-point rounding can push a value just below the upper
|
|
661
|
+
bound onto it, readers MUST clamp `gx` to `[0, NX-1]` and `gy` to
|
|
662
|
+
`[0, NY-1]`.
|
|
663
|
+
3. **Split into chunk and local cell.** `c = gx >> D`, `r = gy >> D`,
|
|
664
|
+
`x = gx & (2^D - 1)`, `y = gy & (2^D - 1)`, and the chunk index is
|
|
665
|
+
`i = r × W + c`. Computing `gx` and `gy` once and splitting them, rather
|
|
666
|
+
than computing the chunk and the local cell with separate divisions,
|
|
667
|
+
avoids inconsistent rounding between the two.
|
|
668
|
+
4. **Fetch the chunk.** If `chunk_lengths[i]` is `0`, the result is
|
|
669
|
+
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.
|
|
673
|
+
5. **Compute the query code** `q` from `(x, y)` with the procedure of
|
|
674
|
+
[Section 8.1](#81-cell-grid-and-morton-codes).
|
|
675
|
+
6. **Binary search.** Find the largest `j` such that `codes[j] <= q`
|
|
676
|
+
(`bisect_right(codes, q) - 1` in Python terms). If no such `j` exists,
|
|
677
|
+
the result is unknown.
|
|
678
|
+
7. **Verify containment.** Let `shift = 2 × (D - depths[j])`. If
|
|
679
|
+
`(q >> shift) != (codes[j] >> shift)`, the point lies after the last
|
|
680
|
+
record (or, in a version `0` file, in a gap between records) and the
|
|
681
|
+
result is unknown.
|
|
682
|
+
8. **Resolve the palette index.** Let `k = palette_indices[j]`. If `k` is
|
|
683
|
+
`0`, the result is unknown. Otherwise the result is `palette[k - 1]`.
|
|
684
|
+
|
|
685
|
+
Step 6 is correct because of the invariants of
|
|
686
|
+
[Section 8.5](#85-invariants): every stored code is the minimum code of
|
|
687
|
+
its cell, cells do not overlap, and codes are sorted, so the cell
|
|
688
|
+
containing `q`, if any, is the one with the largest stored code not
|
|
689
|
+
exceeding `q`. Step 7 is required because the records may end before the
|
|
690
|
+
chunk does.
|
|
691
|
+
|
|
692
|
+
To present the result, the reader resolves the ID to its parents
|
|
693
|
+
([Section 7.5](#75-resolving-an-identifier)) and recovers the names from
|
|
694
|
+
the appropriate name blocks ([Section 7.3](#73-name-blocks)).
|
|
695
|
+
|
|
696
|
+
## 10. Encoder Considerations
|
|
697
|
+
|
|
698
|
+
This section is informative.
|
|
699
|
+
|
|
700
|
+
**Chunk grid size.** More chunks mean that each lookup inflates a smaller
|
|
701
|
+
block and that codes are shorter, at the cost of a larger directory and
|
|
702
|
+
some per-chunk overhead (zlib framing and a palette per chunk). Chunks
|
|
703
|
+
over open ocean cost only their `0` length entry. `width_exp = height_exp + 1`
|
|
704
|
+
gives chunks that are square in degrees; any other ratio gives
|
|
705
|
+
rectangular chunks and cells, which is permitted.
|
|
706
|
+
|
|
707
|
+
**Maximum depth.** `D` together with the chunk grid fixes the finest
|
|
708
|
+
resolution: a full-precision cell is `360 / (W × 2^D)` degrees of
|
|
709
|
+
longitude by `180 / (H × 2^D)` degrees of latitude. The prototype found
|
|
710
|
+
that forcing every ambiguous cell down to a 10 m floor produced far more
|
|
711
|
+
records than the size budget allowed, while stopping at roughly 300 m
|
|
712
|
+
and attributing the remaining ambiguous cells to the candidate with the
|
|
713
|
+
largest share of the cell's area gave good results for geotagging.
|
|
714
|
+
|
|
715
|
+
A deep `D` makes lookups slower, not just files smaller: a chunk is one
|
|
716
|
+
zlib block, which a lookup inflates whole, and it spans up to `4^D`
|
|
717
|
+
cells -- already over four billion at `D = 16`. Finer cells should come
|
|
718
|
+
from a bigger chunk grid instead. The reference reader therefore
|
|
719
|
+
rejects files with `D` above `16`, where every Morton code still fits in
|
|
720
|
+
an unsigned 32-bit integer (`4^16 = 2^32`); a writer SHOULD stay at or
|
|
721
|
+
below it. With a `128 × 128` chunk grid, `D = 16` is a cell about 5 m
|
|
722
|
+
wide at the equator. Deeper files are valid, but only suit transfer or
|
|
723
|
+
archiving, where a decoder can stream through each chunk.
|
|
724
|
+
|
|
725
|
+
**Building records.** The intended construction is recursive: starting
|
|
726
|
+
from the chunk as a depth-`0` cell, stop and emit a record when no place
|
|
727
|
+
touches the cell (with palette index `0`), or when exactly one place
|
|
728
|
+
fully contains it; otherwise subdivide, and at depth `D` pick one
|
|
729
|
+
candidate. When several places fully contain a cell (nested
|
|
730
|
+
administrative levels), prefer the smallest.
|
|
731
|
+
|
|
732
|
+
**Empty cells.** An empty area between two records is best written as
|
|
733
|
+
the fewest index-`0` records that cover it: each the largest cell that
|
|
734
|
+
starts where the one before it ends and does not reach past the area.
|
|
735
|
+
Four empty siblings make one empty parent, as they would in the
|
|
736
|
+
recursive construction. Empty cells after the last record need no
|
|
737
|
+
record at all.
|
|
738
|
+
|
|
739
|
+
**Keyframe interval.** See [Section 7.3](#73-name-blocks).
|
|
740
|
+
|
|
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.
|
|
747
|
+
|
|
748
|
+
## 11. Robustness and Security Considerations
|
|
749
|
+
|
|
750
|
+
A CGEODATA file may come from an untrusted source. Readers MUST NOT
|
|
751
|
+
assume that a file is well formed.
|
|
752
|
+
|
|
753
|
+
- Readers MUST verify the signature, kind, and version before
|
|
754
|
+
interpreting any other field.
|
|
755
|
+
- Readers MUST check that `place_table_length`, every chunk offset, and
|
|
756
|
+
every chunk length lie within the file before reading.
|
|
757
|
+
- Readers MUST bound the inflated size of every compressed block. A
|
|
758
|
+
chunk's inflated size is at most a small multiple of what its counts
|
|
759
|
+
imply; a reader SHOULD inflate with an explicit output limit and reject
|
|
760
|
+
a block that exceeds it, to defend against decompression bombs.
|
|
761
|
+
- Readers MUST check that the inflated chunk contains exactly the bytes
|
|
762
|
+
its counts require, that every depth is at most `D`, that the records
|
|
763
|
+
end within the chunk, that every palette index is at most `P`, and
|
|
764
|
+
that every palette entry is less than `N`. Readers MAY additionally
|
|
765
|
+
verify the alignment invariant of [Section 8.5](#85-invariants), and
|
|
766
|
+
in a version `0` file the ordering and non-overlap invariants of
|
|
767
|
+
[Section 8.6](#86-version-0-chunks).
|
|
768
|
+
- Readers MUST treat the attribution text and place names as untrusted
|
|
769
|
+
text. Where they are rendered as Markdown or HTML, they MUST be
|
|
770
|
+
sanitised.
|
|
771
|
+
- Varints MUST be rejected when longer than 8 bytes, so that a malformed
|
|
772
|
+
file cannot cause unbounded loops or values beyond `2^53`.
|
|
773
|
+
|
|
774
|
+
## 12. Open Issues
|
|
775
|
+
|
|
776
|
+
The following are known gaps or unresolved decisions in version `1`.
|
|
777
|
+
|
|
778
|
+
1. **Latitude scaling.** Because the grid is equirectangular, cells
|
|
779
|
+
shrink in east-west extent towards the poles. A per-row scaling
|
|
780
|
+
factor, or fewer columns at high latitudes, would keep cells closer to
|
|
781
|
+
square on the ground. Not addressed in this version.
|
|
782
|
+
2. **Representative points are derived, not stored.** A place search
|
|
783
|
+
that returns a location for each hit (as Immich's `searchPlaces`
|
|
784
|
+
does) needs a representative point per place. The place table has no
|
|
785
|
+
such field; the point is derived from the chunks
|
|
786
|
+
([Section C.3](#c3-representative-cell)) and cached in a sidecar file
|
|
787
|
+
of kind `2`. It is therefore limited to what the cells show: it cannot
|
|
788
|
+
be an administrative centre or a label position from the source data.
|
|
789
|
+
A future version could let the encoder supply one.
|
|
790
|
+
3. **No exact-fallback marker.** The prototype's approach C reserves a
|
|
791
|
+
sentinel for cells that should be resolved by exact polygon
|
|
792
|
+
arithmetic. This format only supports build-time attribution
|
|
793
|
+
(approach D). A reserved palette index could be added later.
|
|
794
|
+
4. **Fixed three-level hierarchy.** Countries whose cities sit directly
|
|
795
|
+
under the country need a placeholder region.
|
|
796
|
+
5. **Global versus per-chunk depth.** `D` is global. A per-chunk depth
|
|
797
|
+
would let sparse areas stop at coarser cells; whether that is worth
|
|
798
|
+
the complexity has not been measured.
|
|
799
|
+
6. **Name escaping.** `|` cannot appear in a name. No real place name
|
|
800
|
+
affected by this is known, but there is no escape mechanism.
|
|
801
|
+
|
|
802
|
+
## Appendix A. Worked Example
|
|
803
|
+
|
|
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
|
|
806
|
+
a `2 × 2` chunk grid, a quadtree depth of `2`, and name blocks of `4`
|
|
807
|
+
entries.
|
|
808
|
+
|
|
809
|
+
### A.1. Header (17 bytes)
|
|
810
|
+
|
|
811
|
+
```
|
|
812
|
+
43 47 45 4f 44 41 54 41 signature "CGEODATA"
|
|
813
|
+
01 version 1
|
|
814
|
+
03 4f 53 4d attribution_length 3, attribution "OSM"
|
|
815
|
+
01 width_exp 1 -> W = 2
|
|
816
|
+
01 height_exp 1 -> H = 2
|
|
817
|
+
02 max_depth 2 -> D = 2, codes are 4 bits
|
|
818
|
+
02 keyframe_exp 2 -> I = 4 names per block
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### A.2. Directory (5 bytes)
|
|
822
|
+
|
|
823
|
+
```
|
|
824
|
+
6d place_table_length 109
|
|
825
|
+
00 00 00 1a chunk_lengths: chunk 0, 1, 2 empty; chunk 3 is 26 bytes
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
Chunk `3` is row `1`, column `1`: latitudes `[0, 90)`, longitudes
|
|
829
|
+
`[0, 180)`. Its block starts at offset `17 + 5 + 109 = 131` and ends at
|
|
830
|
+
`157`, the end of the file.
|
|
831
|
+
|
|
832
|
+
### A.3. Place Table (109 bytes)
|
|
833
|
+
|
|
834
|
+
Uncompressed prefix (6 bytes):
|
|
835
|
+
|
|
836
|
+
```
|
|
837
|
+
02 country_count C = 2
|
|
838
|
+
01 01 region_counts: country 0 has 1 region, country 1 has 1 -> R = 2
|
|
839
|
+
02 01 city_counts: region 0 has 2 cities, region 1 has 1 -> 3 cities, N = 7
|
|
840
|
+
43 block_lengths: block 0 is 67 bytes (K = ceil(7/4) = 2, so K-1 = 1 entry)
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
The identifier space is:
|
|
844
|
+
|
|
845
|
+
| ID | Kind | Parent | Name entry |
|
|
846
|
+
|----|---------|--------|----------------------------|
|
|
847
|
+
| 0 | country | | `Nederland\|The Netherlands` |
|
|
848
|
+
| 1 | country | | `België\|Belgium` |
|
|
849
|
+
| 2 | region | 0 | `Noord-Holland` |
|
|
850
|
+
| 3 | region | 1 | `Antwerpen\|Antwerp` |
|
|
851
|
+
| 4 | city | 2 | `Amsterdam` |
|
|
852
|
+
| 5 | city | 2 | `Zaandam` |
|
|
853
|
+
| 6 | city | 3 | `Antwerpen\|Antwerp` |
|
|
854
|
+
|
|
855
|
+
Block `0` (IDs 0–3) inflates to
|
|
856
|
+
`Nederland|The Netherlands\0België|Belgium\0Noord-Holland\0Antwerpen|Antwerp\0`
|
|
857
|
+
and is stored as these 67 bytes:
|
|
858
|
+
|
|
859
|
+
```
|
|
860
|
+
78 da f3 4b 4d 49 2d ca 49 cc 4b a9 09 c9 48 55 f0 4b 2d c9 80 70 8b 19
|
|
861
|
+
9c 52 73 d2 33 0f af ae 01 d3 a5 b9 0c 7e f9 f9 45 29 ba 1e f9 39 20 69
|
|
862
|
+
06 c7 bc 92 f2 d4 a2 82 d4 bc 1a 28 8b 01 00 2e 70 1c 57
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Block `1` (IDs 4–6) inflates to `Amsterdam\0Zaandam\0Antwerpen|Antwerp\0`
|
|
866
|
+
and is stored as the remaining `109 - 6 - 67 = 36` bytes:
|
|
867
|
+
|
|
868
|
+
```
|
|
869
|
+
78 da 73 cc 2d 2e 49 2d 4a 49 cc 65 88 4a 4c cc 03 d1 8e 79 25 e5 a9 45
|
|
870
|
+
05 a9 79 35 50 16 03 00 f7 a7 0d 6c
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
To find the name of ID `5`, a reader inflates block `floor(5 / 4) = 1`
|
|
874
|
+
and skips `5 mod 4 = 1` entry, arriving at `Zaandam`.
|
|
875
|
+
|
|
876
|
+
### A.4. Chunk 3 (26 bytes compressed)
|
|
877
|
+
|
|
878
|
+
Stored bytes:
|
|
879
|
+
|
|
880
|
+
```
|
|
881
|
+
78 da 63 62 62 66 67 64 62 62 62 62 64 62 64 62 60 60 60 64 02 00 01 8b
|
|
882
|
+
00 21
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
Inflated content (18 bytes):
|
|
886
|
+
|
|
887
|
+
```
|
|
888
|
+
02 palette_count P = 2 -> S = 1
|
|
889
|
+
02 03 palette deltas 2, 3 -> IDs 2 (Noord-Holland), 5 (Zaandam)
|
|
890
|
+
07 record_count M = 7
|
|
891
|
+
01 02 02 02 02 01 02 depths
|
|
892
|
+
01 02 00 00 00 01 02 palette_indices -> ID 2, ID 5, none, none, none, ID 2, ID 5
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
Each record starts where the one before it ends
|
|
896
|
+
([Section 8.4](#84-records)). A depth-1 record spans `4^(2-1) = 4`
|
|
897
|
+
codes and a depth-2 record `1`, so the codes are:
|
|
898
|
+
|
|
899
|
+
| Record | Depth | Code | Palette index |
|
|
900
|
+
|--------|-------|-------------------|---------------|
|
|
901
|
+
| 0 | 1 | `0` | `1`, ID 2 |
|
|
902
|
+
| 1 | 2 | `0 + 4 = 4` | `2`, ID 5 |
|
|
903
|
+
| 2 | 2 | `4 + 1 = 5` | `0`, none |
|
|
904
|
+
| 3 | 2 | `5 + 1 = 6` | `0`, none |
|
|
905
|
+
| 4 | 2 | `6 + 1 = 7` | `0`, none |
|
|
906
|
+
| 5 | 1 | `7 + 1 = 8` | `1`, ID 2 |
|
|
907
|
+
| 6 | 2 | `8 + 4 = 12` | `2`, ID 5 |
|
|
908
|
+
|
|
909
|
+
The last record ends at `12 + 1 = 13`. The seven records cover the
|
|
910
|
+
chunk's `4 × 4` cell grid as follows (north is up; `A` = Noord-Holland,
|
|
911
|
+
`B` = Zaandam, `-` = index 0, `.` = no record):
|
|
912
|
+
|
|
913
|
+
```
|
|
914
|
+
y=3 | A A . .
|
|
915
|
+
y=2 | A A B .
|
|
916
|
+
y=1 | A A - -
|
|
917
|
+
y=0 | A A B -
|
|
918
|
+
+-----------------
|
|
919
|
+
x=0 x=1 x=2 x=3
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
Record `0` is `(code 0, depth 1)`, the depth-1 cell covering codes
|
|
923
|
+
`0..3`. Record `5` is `(code 8, depth 1)`, covering codes `8..11`. The
|
|
924
|
+
other five are full-precision cells. Codes `5` to `7` are empty and
|
|
925
|
+
covered by index-`0` records; codes `13` to `15`, after the last record,
|
|
926
|
+
have no record. Records `3` and `4` could not be one depth-1 record:
|
|
927
|
+
codes `6` and `7` are half of the depth-1 cell `4..7`, whose other half
|
|
928
|
+
holds Zaandam and record `2`.
|
|
929
|
+
|
|
930
|
+
### A.5. Lookups
|
|
931
|
+
|
|
932
|
+
`NX = NY = 2 × 2^2 = 8`.
|
|
933
|
+
|
|
934
|
+
**`(lat 20, lon 100)`.** `gx = floor(280/360 × 8) = 6`,
|
|
935
|
+
`gy = floor(110/180 × 8) = 4`. Chunk `c = 1, r = 1`, index `3`; local
|
|
936
|
+
cell `x = 2, y = 0`, code `q = 4`. The largest stored code `<= 4` is
|
|
937
|
+
record `1` (`code 4, depth 2`). `shift = 0`, and `4 == 4`, so the point
|
|
938
|
+
is inside. Palette index `2` selects ID `5`: **Zaandam**, in
|
|
939
|
+
Noord-Holland, Nederland.
|
|
940
|
+
|
|
941
|
+
**`(lat 40, lon 30)`.** `gx = floor(210/360 × 8) = 4`,
|
|
942
|
+
`gy = floor(130/180 × 8) = 5`. Chunk `3`; local cell `x = 0, y = 1`,
|
|
943
|
+
`q = 2`. The largest stored code `<= 2` is record `0` (`code 0,
|
|
944
|
+
depth 1`). `shift = 2 × (2 - 1) = 2`, and `2 >> 2 == 0 >> 2`, so the
|
|
945
|
+
point is inside that depth-1 cell. Palette index `1` selects ID `2`:
|
|
946
|
+
**Noord-Holland** (a region-level result).
|
|
947
|
+
|
|
948
|
+
**`(lat 30, lon 100)`.** `gx = 6`, `gy = floor(120/180 × 8) = 5`;
|
|
949
|
+
chunk `3`, local `x = 2, y = 1`, `q = 6`. The largest code `<= 6` is
|
|
950
|
+
record `3` (`code 6, depth 2`), which matches exactly, but its palette
|
|
951
|
+
index is `0`: **unknown**.
|
|
952
|
+
|
|
953
|
+
**`(lat 20, lon 170)`.** `gx = 7`, `gy = 4`; local `x = 3, y = 0`,
|
|
954
|
+
`q = 5`. Record `2` matches exactly, and its palette index is `0` too:
|
|
955
|
+
**unknown**.
|
|
956
|
+
|
|
957
|
+
**`(lat 60, lon 170)`.** `gx = 7`, `gy = floor(150/180 × 8) = 6`;
|
|
958
|
+
chunk `3`, local `x = 3, y = 2`, `q = 13`. The largest code `<= 13` is
|
|
959
|
+
record `6` (`code 12, depth 2`). `shift = 0` and `13 != 12`, so the
|
|
960
|
+
point lies after the last record: **unknown**.
|
|
961
|
+
|
|
962
|
+
**`(lat -10, lon 10)`.** `gx = 4`, `gy = floor(80/180 × 8) = 3`; chunk
|
|
963
|
+
`c = 1, r = 0`, index `1`. Its length is `0`: **unknown**, with no
|
|
964
|
+
decompression performed.
|
|
965
|
+
|
|
966
|
+
## Appendix B. Text Search Index
|
|
967
|
+
|
|
968
|
+
This appendix specifies an OPTIONAL sidecar file that speeds up substring
|
|
969
|
+
search over place names. It is a separate file, derived entirely from the
|
|
970
|
+
place table ([Section 7](#7-place-table)) of one CGEODATA file, and it
|
|
971
|
+
carries file kind `1` ([Section 5](#5-header)).
|
|
972
|
+
|
|
973
|
+
### B.1. Purpose and Properties
|
|
974
|
+
|
|
975
|
+
A text search without an index reads every name in the place table and
|
|
976
|
+
compares it with the query: a linear scan over every name block, which
|
|
977
|
+
at worldwide scale means inflating the names of over 300,000 places. The
|
|
978
|
+
index described here lets a reader avoid most of that, in two ways:
|
|
979
|
+
|
|
980
|
+
- **Trigram lists** ([Section B.3](#b3-trigrams-and-lists)) record, for
|
|
981
|
+
every run of three symbols, which places contain it in one of their
|
|
982
|
+
names. A query of three or more symbols reads only the lists of its own
|
|
983
|
+
trigrams, and only the places present in all of them can match.
|
|
984
|
+
- **A short-query table** ([Section B.6](#b6-short-query-table)) answers
|
|
985
|
+
queries of one or two characters, which have no trigram and match a
|
|
986
|
+
large part of the table, by storing the first places of their
|
|
987
|
+
relevance-ordered result directly.
|
|
988
|
+
|
|
989
|
+
The index has the following properties:
|
|
990
|
+
|
|
991
|
+
- **It covers every place.** Countries, regions, and cities are all
|
|
992
|
+
indexed, so a query never has to scan any part of the place table
|
|
993
|
+
linearly while the index applies.
|
|
994
|
+
- **The trigram lists only over-approximate.** Every step that maps
|
|
995
|
+
names to lists is many-to-one, so the places they leave over are a
|
|
996
|
+
superset of the true matches ([Section B.9](#b9-query-procedure)). A
|
|
997
|
+
reader confirms every candidate by comparing its names, exactly as it
|
|
998
|
+
would without the index, and MUST produce the same results with and
|
|
999
|
+
without it.
|
|
1000
|
+
- **It is a pure function of its source.** Two writers given the
|
|
1001
|
+
same source, the same parameters, and the same relevance rule
|
|
1002
|
+
([Section B.7](#b7-file-layout)) MUST produce byte-identical files.
|
|
1003
|
+
The file carries no timestamp and nothing else that depends on
|
|
1004
|
+
the writer, and nothing in it is compressed, since deflate output is
|
|
1005
|
+
not guaranteed to be identical between zlib implementations. It is
|
|
1006
|
+
bound to its source by a checksum ([Section B.8](#b8-source-checksum)):
|
|
1007
|
+
over the source's place table, or for a short-query table in area
|
|
1008
|
+
order, over everything up to the first chunk.
|
|
1009
|
+
- **It is read in parts.** The header records the length of every
|
|
1010
|
+
section, so a reader reads only the header up front, and afterwards
|
|
1011
|
+
only what a query needs: the list directory and a few lists for a long
|
|
1012
|
+
query, or the table directory and one table block for a short one.
|
|
1013
|
+
- **It is disposable.** Because it is derived, it can be rebuilt from
|
|
1014
|
+
its source at any time.
|
|
1015
|
+
|
|
1016
|
+
### B.2. Symbol Folding
|
|
1017
|
+
|
|
1018
|
+
Names and queries are reduced in two stages, both used elsewhere in this
|
|
1019
|
+
appendix: first to their **folded form**, a plain string that is
|
|
1020
|
+
diacritic- and case-insensitive; then, for trigram purposes only, further
|
|
1021
|
+
to a sequence of **symbols**, non-negative integers that also collapse
|
|
1022
|
+
every ASCII digit together and every other ASCII punctuation/space
|
|
1023
|
+
character together. The folded form is what [Section B.5](#b5-relevance-order)
|
|
1024
|
+
and [Section B.6](#b6-short-query-table) compare and rank with; the
|
|
1025
|
+
symbol sequence is what trigrams ([Section B.3](#b3-trigrams-and-lists))
|
|
1026
|
+
are taken from. Keeping them distinct matters: the symbol sequence's
|
|
1027
|
+
digit/punctuation bucketing is the right call for keeping the number of
|
|
1028
|
+
trigram lists bounded, but it would make `5th` match `9th` as a
|
|
1029
|
+
substring, which is too loose for deciding whether, or how well, text
|
|
1030
|
+
actually matches a query.
|
|
1031
|
+
|
|
1032
|
+
The folded form is produced by:
|
|
1033
|
+
|
|
1034
|
+
1. Decompose the text into Unicode Normalization Form D [UAX15].
|
|
1035
|
+
2. Remove every code point in the Combining Diacritical Marks block,
|
|
1036
|
+
`U+0300` to `U+036F` inclusive.
|
|
1037
|
+
3. Convert the result to lowercase with the Unicode default case
|
|
1038
|
+
conversion (the locale-independent `toLowercase()` of [UNICODE]
|
|
1039
|
+
Section 3.13).
|
|
1040
|
+
|
|
1041
|
+
Step 1 followed by step 2 turns `é` into `e`. Step 3 turns `E` into `e`.
|
|
1042
|
+
`ß`, `ø`, and `ł`, which have no decomposition, and every letter of a
|
|
1043
|
+
non-Latin script, pass through unchanged apart from case.
|
|
1044
|
+
|
|
1045
|
+
The symbol sequence continues from the folded form with a fourth step,
|
|
1046
|
+
mapping each remaining code point `c` to one symbol:
|
|
1047
|
+
|
|
1048
|
+
| Code points | Symbol |
|
|
1049
|
+
|---------------------------------|--------|
|
|
1050
|
+
| `a` to `z` (`U+0061`–`U+007A`) | `0` to `25` in alphabetical order |
|
|
1051
|
+
| `0` to `9` (`U+0030`–`U+0039`) | `27`, the digit symbol |
|
|
1052
|
+
| any other code point below `U+0080` | `26`, the separator symbol |
|
|
1053
|
+
| any code point from `U+0080` up | `c` itself |
|
|
1054
|
+
|
|
1055
|
+
Step 4 puts all ASCII digits in one bucket and all ASCII spaces and
|
|
1056
|
+
punctuation in another, and keeps every other character distinct: `ß`,
|
|
1057
|
+
`ø`, and `ł` and every letter of a non-Latin script are symbols of their
|
|
1058
|
+
own, same as in the folded form. Symbols `0` to `27` are the **Latin
|
|
1059
|
+
symbols**; every other symbol is at least `128`.
|
|
1060
|
+
|
|
1061
|
+
Both reductions are lossy, but one code point never becomes more than one
|
|
1062
|
+
folded character, and one folded character never becomes more than one
|
|
1063
|
+
symbol, so any text that contains another as a substring contains it as a
|
|
1064
|
+
substring after folding, and after taking symbols, too
|
|
1065
|
+
([Section B.9](#b9-query-procedure)). Step 4's bucketing means the symbol
|
|
1066
|
+
sequence is a *further*, coarser folding of the folded form -- so a
|
|
1067
|
+
comparison against the folded form alone (Sections B.5, B.6) is stricter
|
|
1068
|
+
than, and therefore safe to confirm trigram-index candidates with, a
|
|
1069
|
+
comparison against the symbol sequence ([Section B.9](#b9-query-procedure)
|
|
1070
|
+
explains why that direction is the one that matters). Neither stage uses
|
|
1071
|
+
any Unicode character property beyond normalization and case conversion,
|
|
1072
|
+
so that a writer and a reader built against different versions of the
|
|
1073
|
+
Unicode Character Database fold the same text the same way. Writers and
|
|
1074
|
+
readers MUST use exactly this procedure for both the folded form and the
|
|
1075
|
+
symbol sequence.
|
|
1076
|
+
|
|
1077
|
+
### B.3. Trigrams and Lists
|
|
1078
|
+
|
|
1079
|
+
A **trigram** is a run of three consecutive symbols. The trigram set of
|
|
1080
|
+
a place is the union, over every field of its name entry
|
|
1081
|
+
([Section 7.4](#74-name-entries)), of the trigrams of that field's
|
|
1082
|
+
symbol sequence. Each field is folded separately; no trigram spans the
|
|
1083
|
+
`|` between two fields. A field shorter than three symbols contributes
|
|
1084
|
+
nothing.
|
|
1085
|
+
|
|
1086
|
+
The index has one list per possible **list index**. There are
|
|
1087
|
+
|
|
1088
|
+
```
|
|
1089
|
+
L = 28^3 + 2^hash_exp = 21952 + 2^hash_exp
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
list indices. A trigram `(s0, s1, s2)` whose symbols are all Latin has
|
|
1093
|
+
the list index
|
|
1094
|
+
|
|
1095
|
+
```
|
|
1096
|
+
l = s0 × 784 + s1 × 28 + s2 (0 <= l < 21952)
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
so every Latin trigram has a list of its own. Any other trigram is
|
|
1100
|
+
hashed into one of `2^hash_exp` shared lists: let `bytes` be the nine
|
|
1101
|
+
bytes formed by writing `s0`, `s1`, and `s2` each as a `u24`
|
|
1102
|
+
([Section 3.1](#31-fixed-width-integers)), let `h` be the 32-bit
|
|
1103
|
+
FNV-1a hash [FNV] of `bytes`, and fold `h` to `hash_exp` bits:
|
|
1104
|
+
|
|
1105
|
+
```
|
|
1106
|
+
h = 0x811c9dc5
|
|
1107
|
+
for each byte b of bytes, in order:
|
|
1108
|
+
h = h xor b
|
|
1109
|
+
h = (h × 0x01000193) mod 2^32
|
|
1110
|
+
l = 21952 + (((h >> hash_exp) xor h) mod 2^hash_exp)
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
Symbols are code points or smaller, so each fits in a `u24`. Test
|
|
1114
|
+
vectors for the 32-bit hash: the empty input hashes to `811c9dc5`, the
|
|
1115
|
+
single byte `a` to `e40c292c`, and the ASCII text `foobar` to
|
|
1116
|
+
`bf9cf968`. Some trigram list indices with `hash_exp = 15`:
|
|
1117
|
+
|
|
1118
|
+
| Text | Trigram | `bytes` | `h` | `l` |
|
|
1119
|
+
|----------|----------------------------|------------------------------|------------|---------|
|
|
1120
|
+
| `łód` | `(0x142, 14, 3)` | `42 01 00 0e 00 00 03 00 00` | `0702848f` | `24650` |
|
|
1121
|
+
| `東京都` | `(0x6771, 0x4eac, 0x90fd)` | `71 67 00 ac 4e 00 fd 90 00` | `574bcbf2` | `47909` |
|
|
1122
|
+
| `straße` | `(0, 0xdf, 4)`, from `aße` | `00 00 00 df 00 00 04 00 00` | `e5f8c954` | `22629` |
|
|
1123
|
+
|
|
1124
|
+
List `l` holds every place whose trigram set contains a trigram with
|
|
1125
|
+
list index `l`. Two hashed trigrams may share a list; that only admits
|
|
1126
|
+
more candidates, never fewer.
|
|
1127
|
+
|
|
1128
|
+
### B.4. List Encoding
|
|
1129
|
+
|
|
1130
|
+
Each list is a strictly increasing set of place IDs, stored in one of
|
|
1131
|
+
two encodings:
|
|
1132
|
+
|
|
1133
|
+
- **ID list** (encoding `0`): the IDs as a delta-coded list
|
|
1134
|
+
([Section 3.3](#33-delta-coded-lists)), with no count. The list ends
|
|
1135
|
+
where its stored length ends.
|
|
1136
|
+
- **Bitmap** (encoding `1`): `ceil(N / 8)` bytes, one bit per place of
|
|
1137
|
+
the source, where `N` is the number of places. The bit of place `id`
|
|
1138
|
+
is in byte `id >> 3`, at bit position `id & 7`, least significant bit
|
|
1139
|
+
first. Bits for positions `N` and beyond in the last byte MUST be `0`.
|
|
1140
|
+
|
|
1141
|
+
Writers MUST use the ID list when its encoded length is at most
|
|
1142
|
+
`ceil(N / 8)`, and the bitmap otherwise. The choice is then fixed by the
|
|
1143
|
+
set, which keeps the file a pure function of its source. A list that
|
|
1144
|
+
would be empty is not stored at all ([Section B.7](#b7-file-layout)).
|
|
1145
|
+
|
|
1146
|
+
### B.5. Relevance Order
|
|
1147
|
+
|
|
1148
|
+
A reader that returns search results ordered by relevance, rather than
|
|
1149
|
+
by ID, SHOULD use this order or the area order of
|
|
1150
|
+
[Section B.5.1](#b51-area-order), and the short-query table
|
|
1151
|
+
([Section B.6](#b6-short-query-table)) MUST be built with the one its
|
|
1152
|
+
`relevance_rule` names ([Section B.7](#b7-file-layout)).
|
|
1153
|
+
|
|
1154
|
+
A place **matches** a query when the folded form
|
|
1155
|
+
([Section B.2](#b2-symbol-folding)) of at least one field of its name
|
|
1156
|
+
entry contains the folded form of the query as a substring. Folding
|
|
1157
|
+
before comparing means a query need not be typed with the right
|
|
1158
|
+
diacritics: `zurich` matches a field spelled `Zürich`.
|
|
1159
|
+
|
|
1160
|
+
The **tier** of a field that contains the query, against the folded
|
|
1161
|
+
form of both, is:
|
|
1162
|
+
|
|
1163
|
+
- `40` when the field's folded form equals the query's;
|
|
1164
|
+
- otherwise `20` when the query's **tokens** occur in the field's tokens
|
|
1165
|
+
as a contiguous run, where the tokens of a text are its folded form
|
|
1166
|
+
split at every code point whose General Category is not a letter (`L*`)
|
|
1167
|
+
or a number (`N*`), with empty tokens dropped;
|
|
1168
|
+
- otherwise `10`.
|
|
1169
|
+
|
|
1170
|
+
A field that does not contain the query folded has tier `0` and, with
|
|
1171
|
+
it, **quality** `0`: nothing below distinguishes a non-match. A field
|
|
1172
|
+
that does contain it gets `1` added to its folded tier -- an **exact
|
|
1173
|
+
spelling bonus** -- when the *unfolded* comparison (plain lowercase, no
|
|
1174
|
+
decomposition or diacritic stripping) reaches that same tier against the
|
|
1175
|
+
*unfolded* query, i.e. when folding was not actually needed to reach it.
|
|
1176
|
+
The unfolded comparison can never reach a *better* tier than the folded
|
|
1177
|
+
one -- folding only ever adds matches -- so "reaches at least the same
|
|
1178
|
+
tier" is exactly "the query's own spelling, as given, was already
|
|
1179
|
+
enough". Unlike the folded tier itself, and unlike
|
|
1180
|
+
[Section B.9](#b9-query-procedure)'s confirmation step, which compares
|
|
1181
|
+
folded forms, the bonus is evaluated with no normalization beyond
|
|
1182
|
+
lowercasing: it decides only how well a field matches, never whether it
|
|
1183
|
+
matches at all, so it can be stricter than the comparison the index's
|
|
1184
|
+
guarantee rests on without ever losing a match. A field's **quality** is
|
|
1185
|
+
its folded tier plus this bonus, so quality is one of `0`, `10`, `11`,
|
|
1186
|
+
`20`, `21`, `40`, `41`.
|
|
1187
|
+
|
|
1188
|
+
The quality of a place is the highest quality of its fields, and its
|
|
1189
|
+
**score** is its quality plus a level bonus: `3` for a country, `2` for
|
|
1190
|
+
a city, `1` for a region. Results are ordered by descending score,
|
|
1191
|
+
places with equal scores by descending context quality
|
|
1192
|
+
([Section B.13](#b13-qualified-queries)), which is `0` for a query
|
|
1193
|
+
without contexts, and then by ascending ID.
|
|
1194
|
+
|
|
1195
|
+
The level bonus only ever breaks a tie between two places of equal
|
|
1196
|
+
quality: its spread (`3 - 1 = 2`) is kept well under the gap between any
|
|
1197
|
+
two quality values that differ in folded tier (`11` to `20` is `9`, `21`
|
|
1198
|
+
to `40` is `19`), so a worse tier can never outscore a better one no
|
|
1199
|
+
matter which levels are involved, and the exact spelling bonus can never
|
|
1200
|
+
promote a field past the next tier either. A country outranks a
|
|
1201
|
+
same-quality city or region specifically because a query that exactly
|
|
1202
|
+
names a country is a rarer, more deliberate kind of search than one
|
|
1203
|
+
that merely happens to match one of the many more numerous cities or
|
|
1204
|
+
regions sharing the text -- a plain reversal of city and region was
|
|
1205
|
+
deliberately not used instead, since it would equally rank, say, a
|
|
1206
|
+
*province* named Antwerpen above the *city* of Antwerp on an identical
|
|
1207
|
+
exact match, which is backwards for the far more common case of
|
|
1208
|
+
searching a city's own name; a city therefore still outranks a region of
|
|
1209
|
+
equal quality, exactly as if countries did not exist in the formula.
|
|
1210
|
+
|
|
1211
|
+
For example, the query `amsterdam` gives the city `Amsterdam` quality
|
|
1212
|
+
`41` (folded tier `40`, an exact match that needed no folding) and score
|
|
1213
|
+
`43`, and the city `Nieuw-Amsterdam` quality `21` (folded tier `20`; its
|
|
1214
|
+
tokens are `nieuw`, `amsterdam`) and score `23`. The query `belgie`
|
|
1215
|
+
(typed without the diaeresis) gives the country `België` folded tier
|
|
1216
|
+
`40` (an exact match once both are folded to `belgie`) but quality `40`,
|
|
1217
|
+
not `41`: the unfolded comparison, `belgië` against `belgie`, does not
|
|
1218
|
+
reach an exact match (or even a substring match) on its own, so the
|
|
1219
|
+
spelling bonus does not apply; querying `belgië` instead reaches quality
|
|
1220
|
+
`41`. The query ` amsterdam` with a leading space matches neither: token
|
|
1221
|
+
matching only ranks places that already match.
|
|
1222
|
+
|
|
1223
|
+
Only the ranking depends on Unicode character properties, never whether
|
|
1224
|
+
a place matches, so a reader and writer that disagree about a character's
|
|
1225
|
+
category can disagree about order but not about the set of results.
|
|
1226
|
+
|
|
1227
|
+
#### B.5.1. Area Order
|
|
1228
|
+
|
|
1229
|
+
Among places of equal score, the order above falls back to IDs, which
|
|
1230
|
+
carry no meaning: the query `berlin` would list a dozen towns in the
|
|
1231
|
+
United States before the city-state of Berlin. A reader that knows the
|
|
1232
|
+
area of every place -- the area bytes of
|
|
1233
|
+
[Section C.3](#c3-representative-cell), which a place location index
|
|
1234
|
+
stores -- MAY instead order results by area, so that among equally good
|
|
1235
|
+
matches a large place comes first. Whether to is the application's
|
|
1236
|
+
choice, and a reader SHOULD let it make it. A reader MUST NOT use this
|
|
1237
|
+
order without the areas, since a reader that has them and one that does
|
|
1238
|
+
not would then disagree.
|
|
1239
|
+
|
|
1240
|
+
The **rank area** of a country or a city is its area byte. The rank
|
|
1241
|
+
area of a region is also its area byte, unless one or more of its cities
|
|
1242
|
+
has a local name (field `0`, [Section 7.4](#74-name-entries)) whose
|
|
1243
|
+
folded form ([Section B.2](#b2-symbol-folding)) equals that of the
|
|
1244
|
+
region's own local name: then it is the largest area byte among those
|
|
1245
|
+
cities. A province would otherwise come before its own capital of the
|
|
1246
|
+
same name (the province of Utrecht before the city of Utrecht) for no
|
|
1247
|
+
reason but its size, which Section B.5's level bonus exists to prevent;
|
|
1248
|
+
with this rule it comes right after it, while a city-state with no city
|
|
1249
|
+
of its own name (Berlin, Vienna) still ranks by its own area.
|
|
1250
|
+
|
|
1251
|
+
In area order, places are compared on the following keys in turn, each
|
|
1252
|
+
one only deciding between places equal on all keys before it:
|
|
1253
|
+
|
|
1254
|
+
1. the folded tier of their quality ([Section B.5](#b5-relevance-order),
|
|
1255
|
+
quality without the exact spelling bonus), higher first;
|
|
1256
|
+
2. their context quality ([Section B.13](#b13-qualified-queries)),
|
|
1257
|
+
higher first;
|
|
1258
|
+
3. countries before regions and cities;
|
|
1259
|
+
4. their rank area, higher first;
|
|
1260
|
+
5. the exact spelling bonus, present first;
|
|
1261
|
+
6. cities before regions;
|
|
1262
|
+
7. their ID, lower first.
|
|
1263
|
+
|
|
1264
|
+
The exact spelling bonus comes after the area, unlike in Section B.5's
|
|
1265
|
+
score, or the query `zurich` would rank a village spelled `Zurich` ahead
|
|
1266
|
+
of `Zürich` whatever their sizes. Countries keep their precedence for
|
|
1267
|
+
the reason Section B.5 gives.
|
|
1268
|
+
|
|
1269
|
+
For example, the query `dam` against the file of
|
|
1270
|
+
[Appendix A](#appendix-a-worked-example) matches `Amsterdam` and
|
|
1271
|
+
`Zaandam`, both with quality `11` and score `13`. Section B.5 orders them
|
|
1272
|
+
by ID, `Amsterdam` first. In area order `Zaandam`, with area byte `5`
|
|
1273
|
+
([Section C.8](#c8-worked-example)), comes first, and `Amsterdam`, which
|
|
1274
|
+
has no cells and so area byte `0`, second.
|
|
1275
|
+
|
|
1276
|
+
Area order matches the same places as Section B.5 and only orders them
|
|
1277
|
+
differently.
|
|
1278
|
+
|
|
1279
|
+
### B.6. Short-Query Table
|
|
1280
|
+
|
|
1281
|
+
A query of one or two characters has no trigram, and matches so many
|
|
1282
|
+
places that comparing every name is the only way to rank them. The
|
|
1283
|
+
short-query table instead stores, for every such query that has any
|
|
1284
|
+
match, the first `top_count` places in relevance order
|
|
1285
|
+
([Section B.5](#b5-relevance-order)).
|
|
1286
|
+
|
|
1287
|
+
The table's **keys** are every string of one or two consecutive code
|
|
1288
|
+
points that occurs in the folded form ([Section B.2](#b2-symbol-folding))
|
|
1289
|
+
of some field of some name entry. The table MUST contain every key and
|
|
1290
|
+
no other. The **entry** of a key is the list of the places that match
|
|
1291
|
+
it, in the relevance order the file's `relevance_rule` names
|
|
1292
|
+
([Section B.5](#b5-relevance-order) or
|
|
1293
|
+
[Section B.5.1](#b51-area-order), scoring the key itself as the query),
|
|
1294
|
+
truncated to its first `top_count` places. A
|
|
1295
|
+
key always has at least one match, so an entry is never empty; it has
|
|
1296
|
+
fewer than `top_count` places only when fewer places match. A query
|
|
1297
|
+
whose folded form is one or two code points long and is not a key
|
|
1298
|
+
matches no place at all.
|
|
1299
|
+
|
|
1300
|
+
Because a key is already in folded form, scoring it as a query can never
|
|
1301
|
+
need Section B.5's exact spelling bonus to be anything other than
|
|
1302
|
+
whatever the key's own, already-folded spelling gives it -- a key never
|
|
1303
|
+
carries a diacritic of its own to fold away. A *query* that does,
|
|
1304
|
+
though, may need a different bonus than its key would: `e` and `é` both
|
|
1305
|
+
fold to the key `e`, but a field spelled with an actual `é` reaches the
|
|
1306
|
+
exact spelling bonus against the query `é` and not against the query
|
|
1307
|
+
`e`, which the table cannot represent with one entry per key. A reader
|
|
1308
|
+
therefore MUST NOT answer a query from this table unless the query's own
|
|
1309
|
+
folded form already equals its unfolded (plain lowercase) form, i.e. the
|
|
1310
|
+
query itself has no diacritic to fold -- see [Section B.9](#b9-query-procedure)'s
|
|
1311
|
+
step 1.
|
|
1312
|
+
|
|
1313
|
+
Entries are sorted by the UTF-8 bytes of their key, compared bytewise
|
|
1314
|
+
(which equals comparing code points), and grouped into **table blocks**
|
|
1315
|
+
of `2^top_block_exp` consecutive entries; the last block MAY be shorter.
|
|
1316
|
+
A table block is the concatenation of its entries, each stored as:
|
|
1317
|
+
|
|
1318
|
+
| Field | Type | Description |
|
|
1319
|
+
|---------|---------------|-------------|
|
|
1320
|
+
| `key` | bytes, `NUL` | The key in UTF-8, followed by a `NUL` byte. |
|
|
1321
|
+
| `count` | varint | The number of places in the entry, from `1` to `top_count`. |
|
|
1322
|
+
| `ids` | `count` varints | The places, in relevance order. Not delta-coded: relevance order is not ID order. |
|
|
1323
|
+
|
|
1324
|
+
The **table directory** lists, for each block, the key of its first
|
|
1325
|
+
entry, so that a reader finds the block of a key by binary search over
|
|
1326
|
+
the directory and then reads that block alone:
|
|
1327
|
+
|
|
1328
|
+
| Field | Type | Description |
|
|
1329
|
+
|-----------------|---------------|-------------|
|
|
1330
|
+
| `key_count` | varint | `Q`, the number of keys. There are `ceil(Q / 2^top_block_exp)` blocks. |
|
|
1331
|
+
| for each block: `first_key` | bytes, `NUL` | The key of the block's first entry, in UTF-8, followed by `NUL`. |
|
|
1332
|
+
| for each block: `block_length` | varint | The byte length of the block. |
|
|
1333
|
+
|
|
1334
|
+
Blocks are stored consecutively in block order, so block `b` starts at
|
|
1335
|
+
the sum of the lengths of blocks `0` to `b - 1`.
|
|
1336
|
+
|
|
1337
|
+
### B.7. File Layout
|
|
1338
|
+
|
|
1339
|
+
The file is a header followed by four sections, with no padding and no
|
|
1340
|
+
trailer.
|
|
1341
|
+
|
|
1342
|
+
| Field | Type | Description |
|
|
1343
|
+
|-------------------------|---------|-------------|
|
|
1344
|
+
| `signature` | 8 bytes | The ASCII bytes `CGEODATA`, as in [Section 5](#5-header). |
|
|
1345
|
+
| `version` | varint | Kind and version as defined in Section 5. This appendix specifies kind `1`, version `3`, so the value is `19` (`13`). Version `2` had no `relevance_rule` field, its short-query table always in Section B.5's order; a reader MUST reject it. Version `1` built the short-query table from the unfolded lowercase form and a different level bonus ordering (city highest, not country); a version-1 file is not just an older layout but a different, incompatible relevance order, and nothing in its bytes records which rule built it, since `source_checksum` covers only the place table, not the relevance rule -- a reader MUST reject it rather than answer queries with it. |
|
|
1346
|
+
| `source_checksum` | u64 | Checksum of the source file's place table ([Section B.8](#b8-source-checksum)). |
|
|
1347
|
+
| `place_count` | varint | `N`, the number of places in the source. |
|
|
1348
|
+
| `hash_exp` | u8 | The number of hashed lists is `2^hash_exp` ([Section B.3](#b3-trigrams-and-lists)). MUST be from `1` to `24`. |
|
|
1349
|
+
| `top_count` | u8 | The number of places per short-query entry ([Section B.6](#b6-short-query-table)). `0` means the file has no short-query table. |
|
|
1350
|
+
| `top_block_exp` | u8 | Each table block holds `2^top_block_exp` entries. MUST be at most `24`. |
|
|
1351
|
+
| `relevance_rule` | u8 | The order of the short-query table ([Section B.6](#b6-short-query-table)): `0` for [Section B.5](#b5-relevance-order)'s, `1` for the area order of [Section B.5.1](#b51-area-order). Other values are reserved. Also decides what `source_checksum` covers ([Section B.8](#b8-source-checksum)). |
|
|
1352
|
+
| `list_directory_length` | varint | Byte length of `list_directory`. |
|
|
1353
|
+
| `lists_length` | varint | Byte length of `lists`. |
|
|
1354
|
+
| `table_directory_length`| varint | Byte length of `table_directory`. |
|
|
1355
|
+
| `list_directory` | bytes | See below. |
|
|
1356
|
+
| `lists` | bytes | The stored lists, in increasing list index, each in its encoding ([Section B.4](#b4-list-encoding)). |
|
|
1357
|
+
| `table_directory` | bytes | The table directory of Section B.6. Empty when `top_count` is `0`. |
|
|
1358
|
+
| `table_blocks` | bytes | The table blocks of Section B.6, in block order. They run to the end of the file. Empty when `top_count` is `0`. |
|
|
1359
|
+
|
|
1360
|
+
The list directory records which lists are stored and how long each
|
|
1361
|
+
one is:
|
|
1362
|
+
|
|
1363
|
+
| Field | Type | Description |
|
|
1364
|
+
|------------|-----------------------|-------------|
|
|
1365
|
+
| `presence` | `ceil(L / 8)` bytes | Bit `l` is `1` if list `l` is stored, that is, if it is not empty. Bit `l` is in byte `l >> 3`, at bit position `l & 7`, least significant bit first. Bits for `L` and beyond in the last byte MUST be `0`. |
|
|
1366
|
+
| `entries` | one varint per stored list | In increasing list index: `length × 2 + encoding`, where `length` is the byte length of the stored list and `encoding` is `0` for an ID list and `1` for a bitmap. |
|
|
1367
|
+
|
|
1368
|
+
List `l`, if stored, starts at the sum of the lengths of the stored
|
|
1369
|
+
lists with a smaller index. A reader computes all offsets with one pass
|
|
1370
|
+
over `entries`. A list that is not stored is empty: no place has a
|
|
1371
|
+
trigram with that list index.
|
|
1372
|
+
|
|
1373
|
+
Version `0` of kind `1` (value `16`) was an earlier bigram layout of
|
|
1374
|
+
this index. Readers MUST reject it.
|
|
1375
|
+
|
|
1376
|
+
### B.8. Source Checksum
|
|
1377
|
+
|
|
1378
|
+
`source_checksum` binds an index to the place table it was built from.
|
|
1379
|
+
It is the 64-bit FNV-1a hash [FNV] of exactly the `place_table_length`
|
|
1380
|
+
bytes of the source file's place table ([Section 7.2](#72-layout)),
|
|
1381
|
+
starting at the first byte after `chunk_lengths`
|
|
1382
|
+
([Section 6.2](#62-layout)):
|
|
1383
|
+
|
|
1384
|
+
```
|
|
1385
|
+
h = 0xcbf29ce484222325
|
|
1386
|
+
for each byte b of the place table, in file order:
|
|
1387
|
+
h = h xor b
|
|
1388
|
+
h = (h × 0x100000001b3) mod 2^64
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
The result is stored as a `u64`, least significant byte first. Test
|
|
1392
|
+
vectors: the empty input hashes to `cbf29ce484222325`, the single byte
|
|
1393
|
+
`a` to `af63dc4c8601ec8c`, and the ASCII text `foobar` to
|
|
1394
|
+
`85944171f73967e8`.
|
|
1395
|
+
|
|
1396
|
+
Only the place table is covered, not the whole source file, for two
|
|
1397
|
+
reasons. The index depends on nothing else, so a source rebuilt with
|
|
1398
|
+
different chunk parameters, or with some chunks left empty, keeps the
|
|
1399
|
+
same index valid. And verifying the checksum then reads only the place
|
|
1400
|
+
table, which stays small (about 2 MiB for the worldwide file at the
|
|
1401
|
+
recommended `keyframe_exp` of [Section 7.3](#73-name-blocks)), rather
|
|
1402
|
+
than every chunk.
|
|
1403
|
+
|
|
1404
|
+
A short-query table in area order (`relevance_rule = 1`) depends on the
|
|
1405
|
+
areas of [Section C.3](#c3-representative-cell) as well, which depend on
|
|
1406
|
+
the chunks. For such an index, `source_checksum` is instead the checksum
|
|
1407
|
+
of [Section C.4](#c4-file-layout): of the source file from its first
|
|
1408
|
+
byte up to the first chunk, which covers the chunk lengths.
|
|
1409
|
+
|
|
1410
|
+
Readers SHOULD compute the checksum of the source's place table (or
|
|
1411
|
+
prefix) when they load an index, and a reader that does so MUST reject
|
|
1412
|
+
an index whose `source_checksum` differs. A reader MAY skip the check where the
|
|
1413
|
+
build pipeline guarantees the pairing, but it then has no protection
|
|
1414
|
+
against a stale index, which silently misses places whose names were
|
|
1415
|
+
added or changed after the index was built. The checksum detects
|
|
1416
|
+
accidental mismatches; it is not a defence against a deliberately
|
|
1417
|
+
crafted index ([Section B.10](#b10-robustness)).
|
|
1418
|
+
|
|
1419
|
+
FNV-1a was chosen over a cryptographic hash because it is a dozen lines
|
|
1420
|
+
of integer arithmetic in any language and needs no library. In
|
|
1421
|
+
JavaScript, where 64-bit integers are not native, the multiplication is
|
|
1422
|
+
convenient to implement on two 32-bit halves: because the prime is
|
|
1423
|
+
`2^40 + 0x1b3`, `h × prime` is `(h << 40) + h × 0x1b3`, and `h << 40`
|
|
1424
|
+
only ever contributes `low32(h) << 8` to the high half.
|
|
1425
|
+
|
|
1426
|
+
### B.9. Query Procedure
|
|
1427
|
+
|
|
1428
|
+
To search for a query text:
|
|
1429
|
+
|
|
1430
|
+
1. **Short queries.** Let `q` be the folded form of the query
|
|
1431
|
+
([Section B.2](#b2-symbol-folding)). If `q` is one or two code points
|
|
1432
|
+
long, the search asks for results in the relevance order that
|
|
1433
|
+
`relevance_rule` names, with a limit of at most `top_count` and no
|
|
1434
|
+
restriction by level or parent, `top_count` is not `0`, the query
|
|
1435
|
+
has no comma ([Section B.13](#b13-qualified-queries)), and `q`
|
|
1436
|
+
already equals the plain lowercase form of the query (i.e. the query
|
|
1437
|
+
has no diacritic of its own to fold --
|
|
1438
|
+
[Section B.6](#b6-short-query-table) explains why this matters), then
|
|
1439
|
+
look `q` up in the short-query table: find the last block whose
|
|
1440
|
+
`first_key` is at most `q`, read that block, and scan it for `q`. The
|
|
1441
|
+
result is the first `limit` places of its entry, or nothing if `q` is
|
|
1442
|
+
not a key. Stop.
|
|
1443
|
+
2. **Fold the query** further, with step 4 of
|
|
1444
|
+
[Section B.2](#b2-symbol-folding), into a symbol sequence
|
|
1445
|
+
`s_0 .. s_{m-1}`. If `m < 3`, the query has no trigram and the lists
|
|
1446
|
+
cannot help: compare every name without the index, and stop.
|
|
1447
|
+
3. **Collect the distinct list indices** of the query's trigrams
|
|
1448
|
+
([Section B.3](#b3-trigrams-and-lists)). If any of them is not
|
|
1449
|
+
stored, no place can match: the result is empty.
|
|
1450
|
+
4. **Intersect the lists.** The candidates are the places present in
|
|
1451
|
+
every one of them. A reader SHOULD start from the shortest stored
|
|
1452
|
+
list and MAY stop as soon as the intersection is empty. An ID list
|
|
1453
|
+
is intersected by merging two sorted sequences; a bitmap by testing
|
|
1454
|
+
one bit per remaining candidate.
|
|
1455
|
+
5. **Confirm each candidate** by comparing its names with the query, as
|
|
1456
|
+
the reader would without the index, and report the confirmed ones.
|
|
1457
|
+
Candidates come out in increasing ID order, which is the order of a
|
|
1458
|
+
search without relevance ranking.
|
|
1459
|
+
|
|
1460
|
+
A qualified query ([Section B.13](#b13-qualified-queries)) takes steps 2
|
|
1461
|
+
to 4 once for the head of each of its readings: the candidates of the
|
|
1462
|
+
query are the union of the candidates of its heads, and if any head has
|
|
1463
|
+
no trigram, every name is compared.
|
|
1464
|
+
|
|
1465
|
+
The lists never lose a match, under the following condition. Say a
|
|
1466
|
+
reader matches a name against a query when the name's symbol sequence
|
|
1467
|
+
contains the query's symbol sequence as a (contiguous) subsequence --
|
|
1468
|
+
the comparison the trigram lists are actually built from. Then a
|
|
1469
|
+
matching name contains every trigram of the query's symbol sequence, so
|
|
1470
|
+
the place is present in every list of step 4 and is examined in step 5.
|
|
1471
|
+
A reader whose comparison is stricter than this inherits the guarantee,
|
|
1472
|
+
since everything it accepts is also accepted by the symbol-sequence
|
|
1473
|
+
comparison. The folded-form substring match of
|
|
1474
|
+
[Section B.5](#b5-relevance-order) is one such stricter comparison: step
|
|
1475
|
+
4 of Section B.2 maps each folded-form code point to one symbol, so a
|
|
1476
|
+
folded-form substring relationship survives into the symbol sequence
|
|
1477
|
+
unchanged, the same argument Section B.2 makes for why neither folding
|
|
1478
|
+
stage ever loses a match the other would find. A reader whose comparison
|
|
1479
|
+
is looser, for example one that tolerates a missing or transposed
|
|
1480
|
+
character, or that matches across the `|` between fields, does not
|
|
1481
|
+
inherit the guarantee, and MUST NOT use the index for such queries. In
|
|
1482
|
+
particular, a reader that ranks by tokens MUST only rank places that
|
|
1483
|
+
match as a folded substring, as Section B.5 requires; ranking a place
|
|
1484
|
+
whose tokens match but whose names do not contain the query as a folded
|
|
1485
|
+
substring would make results depend on the index. The exotic cases in
|
|
1486
|
+
which lowercasing and decomposition do not commute are not known to
|
|
1487
|
+
affect any place name.
|
|
1488
|
+
|
|
1489
|
+
The short-query table of step 1 is exact rather than an
|
|
1490
|
+
over-approximation: its entries are the answer. A reader SHOULD still
|
|
1491
|
+
confirm that each place of an entry matches `q`, which costs at most
|
|
1492
|
+
`top_count` name lookups, and drop any that do not.
|
|
1493
|
+
|
|
1494
|
+
### B.10. Robustness
|
|
1495
|
+
|
|
1496
|
+
A search index file may come from an untrusted source, or may be stale.
|
|
1497
|
+
Readers MUST NOT assume that it is well formed or that it matches its
|
|
1498
|
+
source.
|
|
1499
|
+
|
|
1500
|
+
- Readers MUST verify the signature, kind, and version before
|
|
1501
|
+
interpreting any other field, and MUST reject `hash_exp` outside `1`
|
|
1502
|
+
to `24`, `top_block_exp` above `24`, and a `relevance_rule` other than
|
|
1503
|
+
`0` or `1`.
|
|
1504
|
+
- Readers MUST check that the header's section lengths add up to at
|
|
1505
|
+
most the file length, and that `list_directory_length` equals
|
|
1506
|
+
`ceil(L / 8)` plus the length of `entries`, where the number of
|
|
1507
|
+
entries is the number of `1` bits in `presence`.
|
|
1508
|
+
- Readers MUST check that the lengths in `entries` add up to
|
|
1509
|
+
`lists_length`, that every bitmap is exactly `ceil(N / 8)` bytes, and
|
|
1510
|
+
that every block length in the table directory lies within
|
|
1511
|
+
`table_blocks`.
|
|
1512
|
+
- Readers MUST check that `place_count` equals `N`, the number of places
|
|
1513
|
+
in the source, and MUST reject any place ID in a list or table entry
|
|
1514
|
+
that is not less than `N`, and any ID list that is not strictly
|
|
1515
|
+
increasing.
|
|
1516
|
+
- Readers MUST confirm every trigram candidate by comparing its names.
|
|
1517
|
+
Given this, a malformed or mismatched list can cause missed results
|
|
1518
|
+
and wasted work, but never a wrong result. A malformed short-query
|
|
1519
|
+
table can reorder or omit results; confirming its places, as
|
|
1520
|
+
Section B.9 recommends, keeps it from adding a wrong one.
|
|
1521
|
+
- Nothing in the file is compressed, so there is no decompression to
|
|
1522
|
+
bound. The allocations a query needs are bounded by the lengths of the
|
|
1523
|
+
sections and lists it reads, which are bounded by the file length.
|
|
1524
|
+
|
|
1525
|
+
### B.11. Choosing Parameters
|
|
1526
|
+
|
|
1527
|
+
This section is informative. The measurements were taken on the
|
|
1528
|
+
worldwide file (239 countries, 5,469 regions, 313,732 cities), with a
|
|
1529
|
+
reader that opens the source afresh for every query, as a search box
|
|
1530
|
+
queried only occasionally would: nothing is cached except by the
|
|
1531
|
+
operating system. Each query asks for the top five places in relevance
|
|
1532
|
+
order and resolves them to their parents.
|
|
1533
|
+
|
|
1534
|
+
**Hashed lists.** With `hash_exp = 15` the file holds 32,768 hashed
|
|
1535
|
+
lists beside the 21,952 Latin ones; the presence bitmap is 6.7 KiB, and
|
|
1536
|
+
a query of a script other than Latin leaves about as few candidates as
|
|
1537
|
+
a Latin one of the same length. With the 28-symbol folding of the
|
|
1538
|
+
earlier bigram version, which mapped every non-Latin letter to the
|
|
1539
|
+
separator, a three-letter Cyrillic, Arabic, or Chinese query left every
|
|
1540
|
+
place with a non-Latin name, over 36,000 cities, as candidates.
|
|
1541
|
+
`hash_exp = 15` is RECOMMENDED.
|
|
1542
|
+
|
|
1543
|
+
**Size.** The worldwide index is 4.2 MiB: 3.8 MiB of lists (36,317
|
|
1544
|
+
stored), a 49 KiB list directory, and 362 KiB of short-query table.
|
|
1545
|
+
Every trigram list turned out smaller as an ID list than as a bitmap:
|
|
1546
|
+
an ID list costs about a byte per place, so a bitmap only wins for a
|
|
1547
|
+
trigram shared by more than about one place in eight, which the
|
|
1548
|
+
worldwide data does not have, but a small file (such as the example
|
|
1549
|
+
below) does. The index is built in under three seconds.
|
|
1550
|
+
|
|
1551
|
+
**Speed.** How long a query takes depends far more on the source's
|
|
1552
|
+
`keyframe_exp` ([Section 7.3](#73-name-blocks)) than on the index,
|
|
1553
|
+
because confirming a candidate means inflating the name block it is in.
|
|
1554
|
+
Mean times for random substrings of real city names, with and without
|
|
1555
|
+
the index; every query opens the source and the index afresh, including
|
|
1556
|
+
verifying the checksum (about 10 ms of each indexed query):
|
|
1557
|
+
|
|
1558
|
+
| `keyframe_exp` | Index | 1 character | 2 characters | 3 characters | 5 characters | 8 characters | 95th percentile |
|
|
1559
|
+
|----------------|-------|-------------|--------------|--------------|--------------|--------------|-----------------|
|
|
1560
|
+
| `13` | yes | 15 ms | 13 ms | 67 ms | 24 ms | 15 ms | 88 ms |
|
|
1561
|
+
| `13` | no | 155 ms | 127 ms | 124 ms | 123 ms | 122 ms | 169 ms |
|
|
1562
|
+
| `7` | yes | 11 ms | 10 ms | 26 ms | 13 ms | 12 ms | 34 ms |
|
|
1563
|
+
| `7` | no | 181 ms | 141 ms | 138 ms | 136 ms | 135 ms | 200 ms |
|
|
1564
|
+
|
|
1565
|
+
The one- and two-character rows are answered by the short-query table.
|
|
1566
|
+
Intersecting the lists takes well under a millisecond in every case; the
|
|
1567
|
+
rest of a three-character query is confirming its candidates, of which
|
|
1568
|
+
there are thousands for the commonest trigrams.
|
|
1569
|
+
|
|
1570
|
+
**Short-query table.** With `top_count = 5` the worldwide table has
|
|
1571
|
+
25,497 keys and takes 365 KiB. It replaces the whole-table
|
|
1572
|
+
scan that one- and two-character queries otherwise need, for the
|
|
1573
|
+
common case of a search box that shows the top few matches.
|
|
1574
|
+
`top_count = 5` and `top_block_exp = 6` (blocks of 64 keys, roughly
|
|
1575
|
+
1 KiB each) are RECOMMENDED. A reader asked for more results, or for
|
|
1576
|
+
results restricted by level or parent, falls back to the scan for these
|
|
1577
|
+
queries.
|
|
1578
|
+
|
|
1579
|
+
**Why trigrams.** Bigrams (the earlier version of this index) are
|
|
1580
|
+
common enough that a query leaves several times more candidates than
|
|
1581
|
+
with trigrams, and with bigrams pooled into groups of places to keep
|
|
1582
|
+
the file small, more again: for Latin queries of three or more
|
|
1583
|
+
characters with on average 530 true matches among the cities, the
|
|
1584
|
+
earlier layout left 3,500 cities as candidates, where trigrams leave
|
|
1585
|
+
650. Longer n-grams would narrow queries of four or more characters
|
|
1586
|
+
further, but these are already fast, and would serve no query of
|
|
1587
|
+
three.
|
|
1588
|
+
|
|
1589
|
+
### B.12. Worked Example
|
|
1590
|
+
|
|
1591
|
+
This example indexes the source file of
|
|
1592
|
+
[Appendix A](#appendix-a-worked-example) with `hash_exp = 4`,
|
|
1593
|
+
`top_count = 2`, and `top_block_exp = 4`: small parameters, so that
|
|
1594
|
+
the example is short but still has several table blocks. Its
|
|
1595
|
+
short-query table is in Section B.5's order (`relevance_rule = 0`). The
|
|
1596
|
+
seven places and their name fields are:
|
|
1597
|
+
|
|
1598
|
+
| ID | Level | Fields |
|
|
1599
|
+
|----|---------|-------------------------------|
|
|
1600
|
+
| 0 | country | `Nederland`, `The Netherlands` |
|
|
1601
|
+
| 1 | country | `België`, `Belgium` |
|
|
1602
|
+
| 2 | region | `Noord-Holland` |
|
|
1603
|
+
| 3 | region | `Antwerpen`, `Antwerp` |
|
|
1604
|
+
| 4 | city | `Amsterdam` |
|
|
1605
|
+
| 5 | city | `Zaandam` |
|
|
1606
|
+
| 6 | city | `Antwerpen`, `Antwerp` |
|
|
1607
|
+
|
|
1608
|
+
`L = 21952 + 2^4 = 21968`, so `presence` is `2746` bytes. `N = 7`, so a
|
|
1609
|
+
bitmap is `1` byte, and any list of two or more places is stored as a
|
|
1610
|
+
bitmap, since its ID list would take two bytes or more. The file is
|
|
1611
|
+
`3202` bytes: a 26-byte header, a `2793`-byte list directory, `47` bytes
|
|
1612
|
+
of lists, a `16`-byte table directory, and `320` bytes of table blocks.
|
|
1613
|
+
|
|
1614
|
+
#### B.12.1. Header (26 bytes)
|
|
1615
|
+
|
|
1616
|
+
```
|
|
1617
|
+
43 47 45 4f 44 41 54 41 signature "CGEODATA"
|
|
1618
|
+
13 version 19 -> kind 1 (text search index), version 3
|
|
1619
|
+
9f e6 00 b9 37 18 dc 77 source_checksum 0x77dc1837b900e69f
|
|
1620
|
+
07 place_count 7
|
|
1621
|
+
04 hash_exp 4 -> 16 hashed lists, L = 21968
|
|
1622
|
+
02 top_count 2
|
|
1623
|
+
04 top_block_exp 4 -> 16 entries per table block
|
|
1624
|
+
00 relevance_rule 0 -> Section B.5's order
|
|
1625
|
+
e9 15 list_directory_length 2793
|
|
1626
|
+
2f lists_length 47
|
|
1627
|
+
10 table_directory_length 16
|
|
1628
|
+
```
|
|
1629
|
+
|
|
1630
|
+
The checksum is the FNV-1a 64 hash of the 109 bytes of the place table
|
|
1631
|
+
in [Section A.3](#a3-place-table-109-bytes), that is the bytes at file
|
|
1632
|
+
offsets `22` to `130` of the Appendix A file, from the `02` of
|
|
1633
|
+
`country_count` to the `6c` that ends block `1`.
|
|
1634
|
+
|
|
1635
|
+
#### B.12.2. Lists
|
|
1636
|
+
|
|
1637
|
+
Every name folds to Latin symbols only (`België` becomes `belgie`), so
|
|
1638
|
+
no hashed list is used. The 47 stored lists, in list index order, are
|
|
1639
|
+
below; `-` stands for the separator symbol `26`. "Bit" is the `presence`
|
|
1640
|
+
byte and bit that mark the list as stored, "Entry" is its varint in
|
|
1641
|
+
`entries`, and "Stored" is the list itself.
|
|
1642
|
+
|
|
1643
|
+
| Trigram | `l` | Bit | Entry | Stored | Places |
|
|
1644
|
+
|---------|-------|--------------|-------|-------------|--------|
|
|
1645
|
+
| `aan` | 13 | 1: `20` | `02` | IDs `05` | 5 |
|
|
1646
|
+
| `ams` | 354 | 44: `04` | `02` | IDs `04` | 4 |
|
|
1647
|
+
| `and` | 367 | 45: `80` | `03` | bitmap `25` | 0, 2, 5 |
|
|
1648
|
+
| `ant` | 383 | 47: `80` | `03` | bitmap `48` | 3, 6 |
|
|
1649
|
+
| `bel` | 907 | 113: `08` | `02` | IDs `01` | 1 |
|
|
1650
|
+
| `dam` | 2364 | 295: `10` | `03` | bitmap `30` | 4, 5 |
|
|
1651
|
+
| `der` | 2481 | 310: `02` | `02` | IDs `00` | 0 |
|
|
1652
|
+
| `d-h` | 3087 | 385: `80` | `02` | IDs `02` | 2 |
|
|
1653
|
+
| `ede` | 3224 | 403: `01` | `02` | IDs `00` | 0 |
|
|
1654
|
+
| `elg` | 3450 | 431: `04` | `02` | IDs `01` | 1 |
|
|
1655
|
+
| `erd` | 3615 | 451: `80` | `02` | IDs `04` | 4 |
|
|
1656
|
+
| `erl` | 3623 | 452: `80` | `02` | IDs `00` | 0 |
|
|
1657
|
+
| `erp` | 3627 | 453: `08` | `03` | bitmap `48` | 3, 6 |
|
|
1658
|
+
| `eth` | 3675 | 459: `08` | `02` | IDs `00` | 0 |
|
|
1659
|
+
| `e-n` | 3877 | 484: `20` | `02` | IDs `00` | 0 |
|
|
1660
|
+
| `gie` | 4932 | 616: `10` | `02` | IDs `01` | 1 |
|
|
1661
|
+
| `giu` | 4948 | 618: `10` | `02` | IDs `01` | 1 |
|
|
1662
|
+
| `her` | 5617 | 702: `02` | `02` | IDs `00` | 0 |
|
|
1663
|
+
| `he-` | 5626 | 703: `04` | `02` | IDs `00` | 0 |
|
|
1664
|
+
| `hol` | 5891 | 736: `08` | `02` | IDs `02` | 2 |
|
|
1665
|
+
| `ium` | 6844 | 855: `10` | `02` | IDs `01` | 1 |
|
|
1666
|
+
| `lan` | 8637 | 1079: `20` | `03` | bitmap `05` | 0, 2 |
|
|
1667
|
+
| `lgi` | 8800 | 1100: `01` | `02` | IDs `01` | 1 |
|
|
1668
|
+
| `lla` | 8932 | 1116: `10` | `02` | IDs `02` | 2 |
|
|
1669
|
+
| `mst` | 9931 | 1241: `08` | `02` | IDs `04` | 4 |
|
|
1670
|
+
| `nda` | 10276 | 1284: `10` | `02` | IDs `05` | 5 |
|
|
1671
|
+
| `nds` | 10294 | 1286: `40` | `02` | IDs `00` | 0 |
|
|
1672
|
+
| `ned` | 10307 | 1288: `08` | `02` | IDs `00` | 0 |
|
|
1673
|
+
| `net` | 10323 | 1290: `08` | `02` | IDs `00` | 0 |
|
|
1674
|
+
| `noo` | 10598 | 1324: `40` | `02` | IDs `02` | 2 |
|
|
1675
|
+
| `ntw` | 10746 | 1343: `04` | `03` | bitmap `48` | 3, 6 |
|
|
1676
|
+
| `oll` | 11295 | 1411: `80` | `02` | IDs `02` | 2 |
|
|
1677
|
+
| `oor` | 11385 | 1423: `02` | `02` | IDs `02` | 2 |
|
|
1678
|
+
| `ord` | 11455 | 1431: `80` | `02` | IDs `02` | 2 |
|
|
1679
|
+
| `pen` | 11885 | 1485: `20` | `03` | bitmap `48` | 3, 6 |
|
|
1680
|
+
| `rda` | 13412 | 1676: `10` | `02` | IDs `04` | 4 |
|
|
1681
|
+
| `rd-` | 13438 | 1679: `40` | `02` | IDs `02` | 2 |
|
|
1682
|
+
| `rla` | 13636 | 1704: `10` | `02` | IDs `00` | 0 |
|
|
1683
|
+
| `rpe` | 13752 | 1719: `01` | `03` | bitmap `48` | 3, 6 |
|
|
1684
|
+
| `ste` | 14648 | 1831: `01` | `02` | IDs `04` | 4 |
|
|
1685
|
+
| `ter` | 15025 | 1878: `02` | `02` | IDs `04` | 4 |
|
|
1686
|
+
| `the` | 15096 | 1887: `01` | `02` | IDs `00` | 0 |
|
|
1687
|
+
| `twe` | 15516 | 1939: `10` | `03` | bitmap `48` | 3, 6 |
|
|
1688
|
+
| `wer` | 17377 | 2172: `02` | `03` | bitmap `48` | 3, 6 |
|
|
1689
|
+
| `zaa` | 19600 | 2450: `01` | `02` | IDs `05` | 5 |
|
|
1690
|
+
| `-ho` | 20594 | 2574: `04` | `02` | IDs `02` | 2 |
|
|
1691
|
+
| `-ne` | 20752 | 2594: `01` | `02` | IDs `00` | 0 |
|
|
1692
|
+
|
|
1693
|
+
All other `presence` bytes are `00`. The entries and the lists are the
|
|
1694
|
+
"Entry" and "Stored" columns concatenated: `entries` is 47 bytes, one
|
|
1695
|
+
varint per list, and `lists` is 47 bytes, since every stored list here
|
|
1696
|
+
is one byte long. The ID list of a single place is its ID, since the
|
|
1697
|
+
first delta is taken from `0`. The place table's `The Netherlands` and
|
|
1698
|
+
`Noord-Holland` contribute the separator trigrams `e-n`, `he-`, `-ne`,
|
|
1699
|
+
`d-h`, `rd-`, and `-ho`.
|
|
1700
|
+
|
|
1701
|
+
#### B.12.3. Short-Query Table
|
|
1702
|
+
|
|
1703
|
+
The names' folded forms contain 63 distinct strings of one or two code
|
|
1704
|
+
points, so the table has four blocks: three of 16 entries and one of
|
|
1705
|
+
15. The table directory (16 bytes) is:
|
|
1706
|
+
|
|
1707
|
+
```
|
|
1708
|
+
3f key_count 63 -> 4 blocks
|
|
1709
|
+
20 00 50 block 0: first_key " ", block_length 80
|
|
1710
|
+
65 20 00 51 block 1: first_key "e ", block_length 81
|
|
1711
|
+
6c 67 00 52 block 2: first_key "lg", block_length 82
|
|
1712
|
+
72 64 00 4d block 3: first_key "rd", block_length 77
|
|
1713
|
+
```
|
|
1714
|
+
|
|
1715
|
+
The entries, key followed by its places in relevance order:
|
|
1716
|
+
|
|
1717
|
+
```
|
|
1718
|
+
block 0 " " 0 " n" 0 "-" 2 "-h" 2 "a" 0 4 "aa" 5
|
|
1719
|
+
"am" 4 5 "an" 0 5 "b" 1 "be" 1 "d" 0 4 "d-" 2
|
|
1720
|
+
"da" 4 5 "de" 0 "ds" 0 "e" 0 1
|
|
1721
|
+
block 1 "e " 0 "ed" 0 "el" 1 "en" 6 3 "er" 0 4 "et" 0
|
|
1722
|
+
"g" 1 "gi" 1 "h" 0 2 "he" 0 "ho" 2 "i" 1
|
|
1723
|
+
"ie" 1 "iu" 1 "l" 0 1 "la" 0 2
|
|
1724
|
+
block 2 "lg" 1 "ll" 2 "m" 1 4 "ms" 4 "n" 0 5 "nd" 0 5
|
|
1725
|
+
"ne" 0 "no" 2 "nt" 6 3 "o" 2 "ol" 2 "oo" 2
|
|
1726
|
+
"or" 2 "p" 6 3 "pe" 6 3 "r" 0 4
|
|
1727
|
+
block 3 "rd" 4 2 "rl" 0 "rp" 6 3 "s" 0 4 "st" 4 "t" 0 4
|
|
1728
|
+
"te" 4 "th" 0 "tw" 6 3 "u" 1 "um" 1 "w" 6 3
|
|
1729
|
+
"we" 6 3 "z" 5 "za" 5
|
|
1730
|
+
```
|
|
1731
|
+
|
|
1732
|
+
Block 0 is stored as these 80 bytes:
|
|
1733
|
+
|
|
1734
|
+
```
|
|
1735
|
+
20 00 01 00 20 6e 00 01 00 2d 00 01 02 2d 68 00 01 02 61 00 02 00 04
|
|
1736
|
+
61 61 00 01 05 61 6d 00 02 04 05 61 6e 00 02 00 05 62 00 01 01
|
|
1737
|
+
62 65 00 01 01 64 00 02 00 04 64 2d 00 01 02 64 61 00 02 04 05
|
|
1738
|
+
64 65 00 01 00 64 73 00 01 00 65 00 02 00 01
|
|
1739
|
+
```
|
|
1740
|
+
|
|
1741
|
+
Some entries worked through Section B.5 (scores aren't stored, only the
|
|
1742
|
+
ID order they produce, but are given here to show why):
|
|
1743
|
+
|
|
1744
|
+
- `en`: the region `Antwerpen` (ID 3) and the city `Antwerpen` (ID 6)
|
|
1745
|
+
both reach folded tier `10` (a substring match) and need no folding
|
|
1746
|
+
either, so both get the exact spelling bonus, quality `11`; the
|
|
1747
|
+
city's level bonus (`2`) still beats the region's (`1`), giving scores
|
|
1748
|
+
`13` and `12` -- the entry is `6 3`, not in ID order. (Country beating
|
|
1749
|
+
city and region at equal quality is the point of this version's level
|
|
1750
|
+
bonus; city still beats region here exactly as version 1's did, since
|
|
1751
|
+
neither place in this example is a country.)
|
|
1752
|
+
- `h`: the country `Nederland` / `The Netherlands` (ID 0, matching via
|
|
1753
|
+
the `The Netherlands` field) and the region `Noord-Holland` (ID 2)
|
|
1754
|
+
both reach quality `11` the same way `en` does; the country's level
|
|
1755
|
+
bonus (`3`) beats the region's (`1`), giving scores `14` and `12` --
|
|
1756
|
+
the entry is `0 2`. Under version 1's level bonus (city `4`, region
|
|
1757
|
+
`2`, country `1`) this came out the other way, `2 0` -- a region used
|
|
1758
|
+
to outrank a country of equal quality, which this version's bonus
|
|
1759
|
+
ordering specifically fixes.
|
|
1760
|
+
- `ie` and `iu` are both keys of the country `België` / `Belgium` (ID
|
|
1761
|
+
`1`) alone, reached in opposite ways. `ie` exists only in `België`'s
|
|
1762
|
+
folded form (`belgië` does not contain the literal substring `ie`,
|
|
1763
|
+
since its sixth letter is `ë` not `e`, and `belgium` does not contain
|
|
1764
|
+
it either), so it reaches quality `10`, no exact spelling bonus, score
|
|
1765
|
+
`13`; `iu` is a literal substring of `belgium` with no folding
|
|
1766
|
+
involved at all, quality `11`, score `14`. Both entries hold only ID
|
|
1767
|
+
`1`, so the difference isn't visible in the stored bytes, only in how
|
|
1768
|
+
each key was reached.
|
|
1769
|
+
- `nl` is not a key, so a search for `nl` matches nothing, without
|
|
1770
|
+
reading any block beyond the one that would hold it.
|
|
1771
|
+
|
|
1772
|
+
#### B.12.4. Queries
|
|
1773
|
+
|
|
1774
|
+
**`dam`.** Folds to symbols `3, 0, 12`: the one trigram `dam`, list
|
|
1775
|
+
`2364`, a bitmap `30`, places `4` and `5`. Both `Amsterdam` and
|
|
1776
|
+
`Zaandam` contain `dam`, so both are confirmed.
|
|
1777
|
+
|
|
1778
|
+
**`dams`.** Trigrams `dam` (list `2364`, places `4`, `5`) and `ams`
|
|
1779
|
+
(list `354`, place `4`). The intersection is place `4`, `Amsterdam`,
|
|
1780
|
+
whose names do not contain `dams`: it is rejected and the search
|
|
1781
|
+
reports nothing. The lists passed it because a trigram set discards
|
|
1782
|
+
where the trigrams occur: `Amsterdam` has both, just not overlapping as
|
|
1783
|
+
in `dams`.
|
|
1784
|
+
|
|
1785
|
+
**`antwerp`.** Trigrams `ant`, `ntw`, `twe`, `wer`, `erp`, all with
|
|
1786
|
+
bitmap `48` (places `3` and `6`). Both are confirmed, and in relevance
|
|
1787
|
+
order the city (quality `41`, an exact match of the field `Antwerp`
|
|
1788
|
+
needing no folding, score `41 + 2 = 43`) comes before the region
|
|
1789
|
+
(quality `41`, score `41 + 1 = 42`).
|
|
1790
|
+
|
|
1791
|
+
**`belgie`, sorted.** Typed without the diaeresis. Trigrams `bel`,
|
|
1792
|
+
`elg`, `lgi`, `gie`, all list `België`'s country alone (places `1`'s
|
|
1793
|
+
lists -- see B.12.2). `België`'s folded form, `belgie`, equals the
|
|
1794
|
+
query exactly: folded tier `40`. The *unfolded* comparison, `belgië`
|
|
1795
|
+
against `belgie`, does not reach even a substring match (`belgië`
|
|
1796
|
+
does not contain `belgie` literally -- nor does `Belgium`, whose sixth
|
|
1797
|
+
letter is `u`, not `e`), so the exact spelling bonus does not apply:
|
|
1798
|
+
quality stays `40`, score `43`. A reader with no folding at all (version
|
|
1799
|
+
1, or plain case-insensitive matching) would have found nothing here,
|
|
1800
|
+
since neither field contains `belgie` literally. Querying `belgië`
|
|
1801
|
+
instead (typed correctly) matches `België`'s own field without any
|
|
1802
|
+
folding needed, reaching quality `41`, score `44` -- a query a
|
|
1803
|
+
non-folding reader would also have found, since it's byte-identical to
|
|
1804
|
+
the stored field after lowercasing alone. This reader answers both
|
|
1805
|
+
queries, by folding for the first and plain matching for the second,
|
|
1806
|
+
and ranks the correctly-spelled one slightly higher.
|
|
1807
|
+
|
|
1808
|
+
**`Zaan-dam`.** Folds to `z a a n - d a m`. The trigrams `an-`
|
|
1809
|
+
(list `390`), `n-d` (`10923`), and `-da` (`20468`) are not stored, so
|
|
1810
|
+
nothing can match and no list needs to be read at all: step 3 of
|
|
1811
|
+
Section B.9 ends the search. This is correct, since no name contains
|
|
1812
|
+
`zaan-dam`.
|
|
1813
|
+
|
|
1814
|
+
**`a`, sorted, limit 2.** One code point, and its folded form equals its
|
|
1815
|
+
plain lowercase form (step 1 of Section B.9 applies, per Section B.6's
|
|
1816
|
+
restriction): `a` is in block 0 (the last block whose first key, `" "`,
|
|
1817
|
+
is at most `a`), and its entry is `0 4`, the country `Nederland` (field
|
|
1818
|
+
`Nederland` matches `a` at quality `11`, score `11 + 3 = 14`) ahead of
|
|
1819
|
+
the city `Amsterdam` (quality `11`, score `11 + 2 = 13` -- `Zaandam`
|
|
1820
|
+
ties it but loses the ID tie-break). Under version 1's level bonus this
|
|
1821
|
+
entry was `4 5`, `Amsterdam` and `Zaandam`, since a country was worth
|
|
1822
|
+
less than either city. A search for `a` in ID order, or with a limit
|
|
1823
|
+
above 2, instead compares every name.
|
|
1824
|
+
|
|
1825
|
+
Built in area order instead (`relevance_rule = 1`, from the area bytes
|
|
1826
|
+
of [Section C.8](#c8-worked-example)), the entry of `a` is `0 2`: the
|
|
1827
|
+
country still comes first, then `Noord-Holland`, whose area byte `14`
|
|
1828
|
+
is larger than `Zaandam`'s `5`, while `Amsterdam` has none. That
|
|
1829
|
+
index's `source_checksum` is the one of Section C.8.1,
|
|
1830
|
+
`0xfc0348f51391f7a1`. A reader that ranks by Section B.5's order cannot
|
|
1831
|
+
use that table, and compares every name instead.
|
|
1832
|
+
|
|
1833
|
+
**`ë`, sorted, limit 1.** Its folded form, `e`, differs from its plain
|
|
1834
|
+
lowercase form, `ë` itself, so step 1 does NOT apply here even though
|
|
1835
|
+
`e` is a key (Section B.6) -- comparing against the key `e`'s own
|
|
1836
|
+
precomputed entry (`0 1`, from B.12.3 above) would silently drop the
|
|
1837
|
+
exact spelling bonus `België`'s own `belgië` field earns against the
|
|
1838
|
+
*literal* query `ë`: `belgië` contains `ë`, reaching folded tier `10`
|
|
1839
|
+
*and* the bonus (the raw comparison also reaches tier `10`, since
|
|
1840
|
+
`belgië` literally contains `ë`), quality `11`, score `11 + 3 = 14`.
|
|
1841
|
+
`Nederland` / `The Netherlands` reaches only folded tier `10` with no
|
|
1842
|
+
bonus (neither field contains a literal `ë`), quality `10`, score
|
|
1843
|
+
`10 + 3 = 13`. Scored this way, `België` (`14`) outranks `Nederland`
|
|
1844
|
+
(`13`) outright -- the key `e`'s own precomputed entry, built by
|
|
1845
|
+
scoring `e` (not `ë`) as the query, has them the other way around
|
|
1846
|
+
(`0 1`, `Nederland` first) precisely because `e` itself, being
|
|
1847
|
+
diacritic-free, never earns `België`'s field the bonus the way the
|
|
1848
|
+
literal query `ë` does. Trusting the key's entry here would report
|
|
1849
|
+
`Nederland` as the top match for `ë`, the wrong answer. The query
|
|
1850
|
+
instead falls through to step 2: it folds to one symbol (one short of a
|
|
1851
|
+
trigram), so the lists cannot help either, and every name is compared
|
|
1852
|
+
directly, correctly placing `België` first.
|
|
1853
|
+
|
|
1854
|
+
### B.13. Qualified Queries
|
|
1855
|
+
|
|
1856
|
+
A search box is often typed into as `Paris, Texas`: a place, then a
|
|
1857
|
+
place it lies in. A reader MAY treat a query that contains a comma
|
|
1858
|
+
(`,`, U+002C) as a **qualified query**, which has several
|
|
1859
|
+
**readings**:
|
|
1860
|
+
|
|
1861
|
+
- the whole query as given, with no contexts, so that a place whose own
|
|
1862
|
+
name contains a comma, such as `Stormont, Dundas and Glengarry
|
|
1863
|
+
Counties`, is still found by typing its name;
|
|
1864
|
+
- for each comma in the query, a reading whose **head** is the text
|
|
1865
|
+
before that comma and whose **contexts** are the parts after it,
|
|
1866
|
+
divided at the remaining commas. White space (Unicode `White_Space`)
|
|
1867
|
+
is removed from both ends of the head and of every context, and
|
|
1868
|
+
contexts that are then empty are dropped, so that `Paris,`, typed on
|
|
1869
|
+
the way to `Paris, Texas`, reads as `Paris`. A reading whose head is
|
|
1870
|
+
empty is dropped, as is one identical to an earlier reading.
|
|
1871
|
+
|
|
1872
|
+
A place matches a reading when its name entry matches the head
|
|
1873
|
+
([Section B.5](#b5-relevance-order)) and every context matches the name
|
|
1874
|
+
entry of one of the place's ancestors
|
|
1875
|
+
([Section 7.1](#71-identifiers-and-hierarchy)), each context a further
|
|
1876
|
+
ancestor than the one before it, nearest first: for a city, the first
|
|
1877
|
+
context may match its region or its country, and a second only its
|
|
1878
|
+
country. A reading with more contexts than the place has ancestors does
|
|
1879
|
+
not match it. A place matches a qualified query when it matches any of
|
|
1880
|
+
its readings.
|
|
1881
|
+
|
|
1882
|
+
For ranking, a place's quality is that of the head against its own name
|
|
1883
|
+
entry, and its **context quality** is the sum of the qualities of the
|
|
1884
|
+
contexts against the ancestors they match, under the assignment of
|
|
1885
|
+
contexts to ancestors that gives the highest sum. Among the readings a
|
|
1886
|
+
place matches, the one that ranks first in the order being used counts.
|
|
1887
|
+
|
|
1888
|
+
Every head is matched as a query of its own, so the trigram lists never
|
|
1889
|
+
lose a match of a reading, and a reader uses them head by head
|
|
1890
|
+
([Section B.9](#b9-query-procedure)). The short-query table is not used
|
|
1891
|
+
for a query with a comma.
|
|
1892
|
+
|
|
1893
|
+
For example, against the file of
|
|
1894
|
+
[Appendix A](#appendix-a-worked-example), `zaandam, noord` has the
|
|
1895
|
+
readings `zaandam, noord` (no contexts), which matches nothing, and
|
|
1896
|
+
`zaandam` with the context `noord`, which matches `Zaandam`, whose
|
|
1897
|
+
region `Noord-Holland` contains `noord`. `antwerp, antwerpen` matches
|
|
1898
|
+
the city `Antwerpen`, whose region is `Antwerpen`, but not the region
|
|
1899
|
+
`Antwerpen`, whose only ancestor is `België`. `zaandam, nederland,
|
|
1900
|
+
noord` matches nothing, since the contexts are nearest first.
|
|
1901
|
+
|
|
1902
|
+
## Appendix C. Place Location Index
|
|
1903
|
+
|
|
1904
|
+
This appendix defines a **representative cell** for every place, a
|
|
1905
|
+
cell near the middle of the place's area whose centre serves as the
|
|
1906
|
+
place's coordinate, and an **area byte**, the size of that area on a
|
|
1907
|
+
logarithmic scale, and specifies an OPTIONAL sidecar file that stores
|
|
1908
|
+
both. The file is derived entirely from one CGEODATA file and carries
|
|
1909
|
+
file kind `2` ([Section 5](#5-header)).
|
|
1910
|
+
|
|
1911
|
+
### C.1. Purpose and Properties
|
|
1912
|
+
|
|
1913
|
+
A place search that shows its results on a map, or hands them to an
|
|
1914
|
+
application that wants a coordinate per result, needs a point for each
|
|
1915
|
+
place. The file stores areas, not points, so the point has to be
|
|
1916
|
+
derived from the chunks: [Section C.3](#c3-representative-cell) defines
|
|
1917
|
+
it. Deriving it for one place means decoding every chunk that might
|
|
1918
|
+
hold one of the place's cells, which at worldwide scale is every chunk,
|
|
1919
|
+
about 1.5 seconds. The index described here stores the result for every
|
|
1920
|
+
place, so that finding it costs one read of a few bytes. The area tells
|
|
1921
|
+
an application how far to zoom a map in on the place, and lets a search
|
|
1922
|
+
rank large places before small ones of the same name
|
|
1923
|
+
([Section B.5.1](#b51-area-order)); computing it is as costly as the
|
|
1924
|
+
cell.
|
|
1925
|
+
|
|
1926
|
+
The index has the following properties:
|
|
1927
|
+
|
|
1928
|
+
- **It is a cache, not a data source.** Every entry is the
|
|
1929
|
+
representative cell, and every area byte the area byte, of
|
|
1930
|
+
[Section C.3](#c3-representative-cell), which a reader can compute
|
|
1931
|
+
from the source file alone. A reader MUST produce the same result with
|
|
1932
|
+
and without the index. The one exception is search ranking: a reader
|
|
1933
|
+
that ranks by area ([Section B.5.1](#b51-area-order)) only while an
|
|
1934
|
+
index supplies the areas orders its search results differently
|
|
1935
|
+
without one, though it finds the same places.
|
|
1936
|
+
- **It is a pure function of its source.** Two writers given the same
|
|
1937
|
+
source file MUST produce byte-identical files. The file carries no
|
|
1938
|
+
timestamp and no parameter chosen by the writer. It is bound to its
|
|
1939
|
+
source by a checksum ([Section C.4](#c4-file-layout)).
|
|
1940
|
+
- **It is uncompressed and fixed-width.** Entry `id` is at a computed
|
|
1941
|
+
offset, so a lookup reads exactly one entry and a reader needs to keep
|
|
1942
|
+
nothing but the header in memory between lookups.
|
|
1943
|
+
- **It is disposable.** It can be rebuilt from its source at any time,
|
|
1944
|
+
and a reader that lacks it computes the same cells from the chunks.
|
|
1945
|
+
|
|
1946
|
+
### C.2. Entries
|
|
1947
|
+
|
|
1948
|
+
An **entry** names one cell of the source file, or no cell at all. It
|
|
1949
|
+
is an unsigned integer. Let `B = width_exp + height_exp`, so that a
|
|
1950
|
+
chunk index fits in `B` bits. The entry for the cell of depth `d` whose
|
|
1951
|
+
chunk index is `i` ([Section 6.1](#61-chunk-grid)) and whose stored code
|
|
1952
|
+
is `code` ([Section 8.1](#81-cell-grid-and-morton-codes)) is
|
|
1953
|
+
|
|
1954
|
+
```
|
|
1955
|
+
entry = 2^(B + 2d) + i × 4^d + (code >> 2(D - d))
|
|
1956
|
+
```
|
|
1957
|
+
|
|
1958
|
+
that is, a single `1` bit (the **marker**), followed by the `B` bits of
|
|
1959
|
+
the chunk index, followed by the cell's `2d`-bit path from the top of
|
|
1960
|
+
the chunk's quadtree. The entry `0` has no marker and means that the
|
|
1961
|
+
place has no cells. A reader decodes a non-zero entry as follows:
|
|
1962
|
+
|
|
1963
|
+
1. Let `t` be the position of the most significant `1` bit, so that
|
|
1964
|
+
`2^t <= entry < 2^(t+1)`.
|
|
1965
|
+
2. `t - B` MUST be even, and between `0` and `2D` inclusive. Otherwise
|
|
1966
|
+
the entry is invalid. The depth is `d = (t - B) / 2`.
|
|
1967
|
+
3. Let `rest = entry - 2^t`. The chunk index is `i = rest >> 2d`, and
|
|
1968
|
+
the stored code is `(rest mod 4^d) << 2(D - d)`.
|
|
1969
|
+
|
|
1970
|
+
Since `rest < 2^(B + 2d)`, the chunk index is always less than
|
|
1971
|
+
`W × H`. The largest entry, for a cell of depth `D`, has
|
|
1972
|
+
`B + 2D + 1` bits, so every entry of a file fits in
|
|
1973
|
+
|
|
1974
|
+
```
|
|
1975
|
+
entry_width = ceil((width_exp + height_exp + 2 × max_depth + 1) / 8)
|
|
1976
|
+
```
|
|
1977
|
+
|
|
1978
|
+
bytes, which is what the index uses for all of them. For a `64 × 64`
|
|
1979
|
+
chunk grid this is 4 bytes up to `max_depth = 9`, 5 bytes up to
|
|
1980
|
+
`max_depth = 13`, and 6 bytes up to `max_depth = 17`. A reader whose
|
|
1981
|
+
integers are exact only up to `2^53`, such as one in JavaScript, can
|
|
1982
|
+
handle entries of up to 6 bytes and MAY reject an index with wider
|
|
1983
|
+
ones. The marker is what lets one width serve cells of
|
|
1984
|
+
every depth, and lets `0` stand for no cell without reserving any
|
|
1985
|
+
valid cell's value.
|
|
1986
|
+
|
|
1987
|
+
Writers following this version store only full-precision cells
|
|
1988
|
+
(`d = D`), as [Section C.3](#c3-representative-cell) requires. Readers
|
|
1989
|
+
MUST nevertheless accept an entry of any depth, so that a later version
|
|
1990
|
+
can name coarser cells without a new layout.
|
|
1991
|
+
|
|
1992
|
+
### C.3. Representative Cell
|
|
1993
|
+
|
|
1994
|
+
The **area** of a place is the set of records, across all chunks,
|
|
1995
|
+
attributed to that place or to a place under it: for a city, the
|
|
1996
|
+
records whose palette entry is the city; for a region, those of the
|
|
1997
|
+
region and of its cities; for a country, those of the country, its
|
|
1998
|
+
regions, and their cities ([Section 7.1](#71-identifiers-and-hierarchy)).
|
|
1999
|
+
Records with palette index `0` belong to no area. A place whose area is
|
|
2000
|
+
empty has no representative cell, and its entry is `0`.
|
|
2001
|
+
|
|
2002
|
+
Each record of the area is placed on the global grid of full-precision
|
|
2003
|
+
cells used by the lookup procedure ([Section 9](#9-lookup-procedure),
|
|
2004
|
+
step 2). For a record with stored code `code` and depth `d` in chunk
|
|
2005
|
+
`i = r × W + c`, let `(x, y)` be the cell whose full-precision code is
|
|
2006
|
+
`code`, the south-west corner cell of the record. Then the record
|
|
2007
|
+
covers the `s × s` square of global cells with south-west corner
|
|
2008
|
+
`(gx, gy)`, where
|
|
2009
|
+
|
|
2010
|
+
```
|
|
2011
|
+
gx = c × 2^D + x gy = r × 2^D + y s = 2^(D - d)
|
|
2012
|
+
```
|
|
2013
|
+
|
|
2014
|
+
and it has **weight** `s × s`, its area in full-precision cells. The
|
|
2015
|
+
representative cell is computed from these squares in four steps.
|
|
2016
|
+
|
|
2017
|
+
1. **Centres.** The centre of each square, in units of half a cell, is
|
|
2018
|
+
`u = 2 × gx + s` horizontally and `v = 2 × gy + s` vertically. Both
|
|
2019
|
+
are integers.
|
|
2020
|
+
2. **Weighted median per axis.** Let `T` be the total weight of the
|
|
2021
|
+
area. For the `u` axis, let `lo` be the smallest centre `u` for which
|
|
2022
|
+
the weight of all squares with centre at most `u` is at least `T / 2`.
|
|
2023
|
+
If that weight is exactly `T / 2`, let `hi` be the smallest centre
|
|
2024
|
+
greater than `lo`, which exists because the remaining weight is
|
|
2025
|
+
positive. Otherwise let `hi = lo`. The median lies at `(lo + hi) / 2`
|
|
2026
|
+
half-cells, in the full-precision cell column
|
|
2027
|
+
|
|
2028
|
+
```
|
|
2029
|
+
fx = floor((lo + hi) / 4)
|
|
2030
|
+
```
|
|
2031
|
+
|
|
2032
|
+
The same procedure on the `v` centres gives the row `fy`. With equal
|
|
2033
|
+
weights this is the familiar median: the middle value of an odd
|
|
2034
|
+
count, and the midpoint of the two middle values of an even one.
|
|
2035
|
+
3. **Nearest cell of the area.** For every square, its cell nearest to
|
|
2036
|
+
`(fx, fy)` is `(clamp(fx, gx, gx + s - 1), clamp(fy, gy, gy + s - 1))`,
|
|
2037
|
+
at offset `(dx, dy)` from `(fx, fy)`. Its squared distance is
|
|
2038
|
+
|
|
2039
|
+
```
|
|
2040
|
+
(a × dx)^2 + (b × dy)^2 where g = min(2 × H, W), a = 2 × H / g, b = W / g
|
|
2041
|
+
```
|
|
2042
|
+
|
|
2043
|
+
which is proportional to the squared distance in degrees, a
|
|
2044
|
+
full-precision cell being `360 / (W × 2^D)` degrees wide and
|
|
2045
|
+
`180 / (H × 2^D)` high. The representative cell is the nearest of
|
|
2046
|
+
these cells. Among equally near cells, it is the one in the lower
|
|
2047
|
+
row, and among those, the one in the lower column. When `(fx, fy)`
|
|
2048
|
+
itself lies in the area, it is the only cell at distance `0`, and so
|
|
2049
|
+
it is the representative cell.
|
|
2050
|
+
4. **Entry.** The representative cell `(px, py)` is the full-precision
|
|
2051
|
+
cell `x = px mod 2^D`, `y = py mod 2^D` of chunk
|
|
2052
|
+
`i = (py >> D) × W + (px >> D)`, and its entry is formed as in
|
|
2053
|
+
[Section C.2](#c2-entries) with `d = D`.
|
|
2054
|
+
|
|
2055
|
+
The representative cell always lies within one of the area's records,
|
|
2056
|
+
so a lookup ([Section 9](#9-lookup-procedure)) of any point in it, in
|
|
2057
|
+
particular its centre, resolves to the place or to a place under it.
|
|
2058
|
+
The result depends only on the set of records in the area, not on the
|
|
2059
|
+
order in which they are visited.
|
|
2060
|
+
|
|
2061
|
+
The **area byte** of a place is `0` when its area is empty. Otherwise it
|
|
2062
|
+
is the number of binary digits of `T^4`, where `T` is the total weight
|
|
2063
|
+
of step 2, but at most `255`:
|
|
2064
|
+
|
|
2065
|
+
```
|
|
2066
|
+
area_byte = min(255, floor(log2(T^4)) + 1)
|
|
2067
|
+
```
|
|
2068
|
+
|
|
2069
|
+
Each step is a quarter of a doubling: an area of `T` cells has area byte
|
|
2070
|
+
`v` when `2^((v - 1) / 4) <= T < 2^(v / 4)`, so a reader can recover the
|
|
2071
|
+
area to within about 9%. Implementations MUST compute it exactly, for
|
|
2072
|
+
example by counting the bits of `T^4` in integer arithmetic, since a
|
|
2073
|
+
floating-point logarithm can round the wrong way for an area just below
|
|
2074
|
+
a power of two. A `128 × 128` chunk grid with `max_depth = 10` has
|
|
2075
|
+
`2^34` full-precision cells, area byte `137`; the cap of `255` is only
|
|
2076
|
+
reached by an area of `2^63.5` cells or more.
|
|
2077
|
+
|
|
2078
|
+
Every quantity above is an integer, and implementations MUST compute
|
|
2079
|
+
them exactly. The largest is a squared distance, which is less than
|
|
2080
|
+
`(a × W × 2^D)^2 + (b × H × 2^D)^2`; an implementation whose integers
|
|
2081
|
+
are exact only up to `2^53`, such as one in JavaScript, SHOULD check
|
|
2082
|
+
this bound before computing any representative cell. It holds for a
|
|
2083
|
+
`64 × 64` chunk grid up to `max_depth = 19`.
|
|
2084
|
+
|
|
2085
|
+
Informally: the median locates the middle of the place along each axis
|
|
2086
|
+
independently, weighting every cell by its area. Unlike the mean, it is
|
|
2087
|
+
not dragged away by a distant exclave or island. Unlike the centre of
|
|
2088
|
+
the bounding box, it follows where the area actually is. When the
|
|
2089
|
+
median point falls outside the place, as it can for a crescent-shaped
|
|
2090
|
+
area or one made of several islands, step 3 moves it to the nearest
|
|
2091
|
+
cell that is inside. Two known imperfections follow from working on
|
|
2092
|
+
the grid: weights are areas in degrees, not on the ground, so cells at
|
|
2093
|
+
high latitude count for more than their true size, in the area byte as
|
|
2094
|
+
much as in the median; and the horizontal
|
|
2095
|
+
axis does not wrap, so the median of a place that straddles the 180th
|
|
2096
|
+
meridian lands on one side of it rather than in the middle.
|
|
2097
|
+
|
|
2098
|
+
### C.4. File Layout
|
|
2099
|
+
|
|
2100
|
+
The file is one header followed by the entries and the area bytes,
|
|
2101
|
+
with no padding and no trailer.
|
|
2102
|
+
|
|
2103
|
+
| Field | Type | Description |
|
|
2104
|
+
|-------------------|------------------------------|-------------|
|
|
2105
|
+
| `signature` | 8 bytes | The ASCII bytes `CGEODATA`, as in [Section 5](#5-header). |
|
|
2106
|
+
| `version` | varint | Kind and version as defined in Section 5. This appendix specifies kind `2`, version `1`, so the value is `33` (`21`). Version `0` had no `areas`; readers MUST reject it. |
|
|
2107
|
+
| `source_checksum` | u64 | Checksum of the start of the source file, defined below. |
|
|
2108
|
+
| `entry_width` | u8 | The width in bytes of every entry. MUST equal the value given in [Section C.2](#c2-entries) for the source's header. |
|
|
2109
|
+
| `place_count` | varint | `N`, the number of places in the source ([Section 7.1](#71-identifiers-and-hierarchy)). |
|
|
2110
|
+
| `entries` | `N × entry_width` bytes | The entry of place `0`, then of place `1`, and so on, each an unsigned little-endian integer of `entry_width` bytes. |
|
|
2111
|
+
| `areas` | `N` bytes | The area byte ([Section C.3](#c3-representative-cell)) of place `0`, then of place `1`, and so on. |
|
|
2112
|
+
|
|
2113
|
+
The file length MUST be exactly the header length plus
|
|
2114
|
+
`N × (entry_width + 1)`. The entry of place `id` starts at byte
|
|
2115
|
+
`header_length + id × entry_width`, and its area byte is at
|
|
2116
|
+
`header_length + N × entry_width + id`. The area bytes follow the
|
|
2117
|
+
entries rather than being part of them so that a reader ranking a
|
|
2118
|
+
search by area reads all of them in one read.
|
|
2119
|
+
|
|
2120
|
+
`source_checksum` is the 64-bit FNV-1a hash
|
|
2121
|
+
([Section B.8](#b8-source-checksum)) of the first bytes of the source
|
|
2122
|
+
file up to, but not including, the first chunk: the header, the
|
|
2123
|
+
directory, and the place table, that is file offsets `0` to
|
|
2124
|
+
`chunks_start - 1` ([Section 6.3](#63-locating-a-chunk)). It is stored
|
|
2125
|
+
least significant byte first.
|
|
2126
|
+
|
|
2127
|
+
The index depends on the chunks as well as on the place table, so the
|
|
2128
|
+
checksum of [Section B.8](#b8-source-checksum), which covers only the
|
|
2129
|
+
place table, would not do. Hashing the chunks themselves would make
|
|
2130
|
+
loading an index read the whole source file. The directory is a
|
|
2131
|
+
compromise: it holds the length of every compressed chunk, which
|
|
2132
|
+
changes whenever the chunk's content does, except in the unlikely case
|
|
2133
|
+
that a rebuilt chunk compresses to exactly its old length. Writers that
|
|
2134
|
+
rebuild a source SHOULD rebuild its index at the same time rather than
|
|
2135
|
+
rely on the checksum to catch the difference.
|
|
2136
|
+
|
|
2137
|
+
Readers SHOULD compute the checksum when they load an index, and a
|
|
2138
|
+
reader that does so MUST reject an index whose `source_checksum`
|
|
2139
|
+
differs from it. A reader that rejects an index falls back to computing
|
|
2140
|
+
representative cells from the chunks.
|
|
2141
|
+
|
|
2142
|
+
### C.5. Lookup Procedure
|
|
2143
|
+
|
|
2144
|
+
To find the representative coordinate of place `id` with an index:
|
|
2145
|
+
|
|
2146
|
+
1. **Validate.** `id` MUST be less than `N`.
|
|
2147
|
+
2. **Read the entry.** Read `entry_width` bytes at
|
|
2148
|
+
`header_length + id × entry_width` and interpret them as an unsigned
|
|
2149
|
+
little-endian integer.
|
|
2150
|
+
3. **Decode** it as in [Section C.2](#c2-entries). An entry of `0`
|
|
2151
|
+
means the place has no representative cell, and no coordinate.
|
|
2152
|
+
4. **Compute the coordinate.** The cell's extent follows from its chunk
|
|
2153
|
+
([Section 6.1](#61-chunk-grid)) and its path within the chunk
|
|
2154
|
+
([Section 8.1](#81-cell-grid-and-morton-codes)), as for any record.
|
|
2155
|
+
The place's coordinate is the centre of that extent.
|
|
2156
|
+
5. **Read the area**, if wanted: the byte at
|
|
2157
|
+
`header_length + N × entry_width + id`.
|
|
2158
|
+
|
|
2159
|
+
Without an index, a reader computes the same cell and area byte from the
|
|
2160
|
+
chunks by [Section C.3](#c3-representative-cell) and continues at
|
|
2161
|
+
step 4.
|
|
2162
|
+
|
|
2163
|
+
A place of area byte `v` covers about `2^((v - 0.5) / 4)` full-precision
|
|
2164
|
+
cells, each `360 / (W × 2^D)` degrees wide and `180 / (H × 2^D)` high.
|
|
2165
|
+
A square of the same area in degrees is a rough guide to how far to zoom
|
|
2166
|
+
a map in on the place: at zoom `z`, a Web Mercator map shows
|
|
2167
|
+
`360 / 2^z` degrees of longitude across one 256-pixel tile.
|
|
2168
|
+
|
|
2169
|
+
The extent of the cell tells a reader how precisely to present the
|
|
2170
|
+
coordinate: a full-precision cell of a worldwide file with
|
|
2171
|
+
`max_depth = 8` is about `0.02°` wide, so three decimals are
|
|
2172
|
+
meaningful and more are not.
|
|
2173
|
+
|
|
2174
|
+
### C.6. Robustness
|
|
2175
|
+
|
|
2176
|
+
A locate index may come from an untrusted source, or may be stale.
|
|
2177
|
+
Readers MUST NOT assume that it is well formed or that it matches its
|
|
2178
|
+
source.
|
|
2179
|
+
|
|
2180
|
+
- Readers MUST verify the signature, kind, and version before
|
|
2181
|
+
interpreting any other field.
|
|
2182
|
+
- Readers MUST check that `entry_width` equals the value
|
|
2183
|
+
[Section C.2](#c2-entries) gives for the source, that `place_count`
|
|
2184
|
+
equals `N`, and that the file length is exactly the header length
|
|
2185
|
+
plus `N × (entry_width + 1)`, before reading any entry.
|
|
2186
|
+
- Every byte is a valid area byte, so there is nothing to check. A
|
|
2187
|
+
wrong one only misorders search results ranked by area or misjudges a
|
|
2188
|
+
zoom level.
|
|
2189
|
+
- Readers MUST reject an entry whose marker is not at a valid position
|
|
2190
|
+
([Section C.2](#c2-entries), step 2). The decoding cannot produce a
|
|
2191
|
+
chunk index outside the grid, so no further bounds check is needed
|
|
2192
|
+
before computing the cell's extent.
|
|
2193
|
+
- Unlike a search index, a locate index contributes its result directly:
|
|
2194
|
+
a malformed or mismatched index yields a wrong coordinate rather than
|
|
2195
|
+
a missed one. A reader that cannot trust the pairing MAY confirm a
|
|
2196
|
+
result by looking up its coordinate and checking that it resolves to
|
|
2197
|
+
the place or to a place under it, at the cost of decoding one chunk.
|
|
2198
|
+
|
|
2199
|
+
### C.7. Design Notes
|
|
2200
|
+
|
|
2201
|
+
This section is informative.
|
|
2202
|
+
|
|
2203
|
+
**Measurements.** On a worldwide file of 319,440 places with a
|
|
2204
|
+
`64 × 64` chunk grid and `max_depth = 8` (9.5 MiB), entries are 4 bytes
|
|
2205
|
+
and the index (of version `0`, without area bytes) is 1.2 MiB; the area
|
|
2206
|
+
bytes add one byte per place, 0.3 MiB. Building it decodes the chunks two or three
|
|
2207
|
+
times, since places are processed a batch of countries at a time to
|
|
2208
|
+
bound memory, and takes about 11 seconds. Without the index, one representative cell
|
|
2209
|
+
costs about 1.6 seconds, nearly all of it spent decoding chunks. With
|
|
2210
|
+
it, a lookup takes well under a millisecond, even with the file evicted
|
|
2211
|
+
from the operating system's cache, and a reader that is idle between
|
|
2212
|
+
lookups holds nothing but an open file. An older worldwide build at
|
|
2213
|
+
`max_depth = 11` (67 MiB, 255,555 places) needs 5-byte entries, for an
|
|
2214
|
+
index of 1.2 MiB built in 2.5 minutes. A `128 × 128` grid with
|
|
2215
|
+
`max_depth = 10` (71 MiB, 259,598 places) also needs 5-byte entries;
|
|
2216
|
+
with its area bytes the index is 1.5 MiB, built in about 90 seconds,
|
|
2217
|
+
of which totalling the areas takes a few.
|
|
2218
|
+
|
|
2219
|
+
**Alternatives measured.** Storing only the chunk of each place's cell
|
|
2220
|
+
(2 bytes per place for a `64 × 64` grid) and decoding that chunk at
|
|
2221
|
+
lookup time cost 4 to 6 ms per lookup, and about 50 ms for a burst of
|
|
2222
|
+
ten, the typical page of search results; the time goes into decoding
|
|
2223
|
+
the chunk, not reading it. With `max_depth = 11`, where chunks are
|
|
2224
|
+
about ten times larger, it would cost tens of milliseconds per lookup.
|
|
2225
|
+
Compressing the entries in blocks of 256 saved 30% of the size and
|
|
2226
|
+
doubled the lookup time, since the cells of neighbouring places are
|
|
2227
|
+
close but their low-order bits are not. Storing a coarser cell to save
|
|
2228
|
+
bytes and refining it from the chunk at lookup time has the costs of
|
|
2229
|
+
the chunk-only design for every place whose cell is deeper than the
|
|
2230
|
+
coarse cell. Fitting 4-byte entries at `max_depth = 11` means coarsening
|
|
2231
|
+
every cell deeper than depth 9, which was 16% of places, with chunk
|
|
2232
|
+
decodes of 40 to 140 ms each.
|
|
2233
|
+
|
|
2234
|
+
**Why the whole area.** The records attributed directly to a region or
|
|
2235
|
+
country are only the parts not covered by any of its children: often
|
|
2236
|
+
nothing at all, and otherwise leftover strips such as water or
|
|
2237
|
+
unincorporated land. On the worldwide file, 41% of regions have no
|
|
2238
|
+
records of their own. Including the records of every place under a
|
|
2239
|
+
place makes the coordinate of a region or country the middle of the
|
|
2240
|
+
region or country, as a user expects.
|
|
2241
|
+
|
|
2242
|
+
### C.8. Worked Example
|
|
2243
|
+
|
|
2244
|
+
This example indexes the source file of
|
|
2245
|
+
[Appendix A](#appendix-a-worked-example). There, `B = 1 + 1 = 2` and
|
|
2246
|
+
`D = 2`, so entries have at most `2 + 4 + 1 = 7` bits and
|
|
2247
|
+
`entry_width = 1`. The file is `19 + 7 = 26` bytes.
|
|
2248
|
+
|
|
2249
|
+
#### C.8.1. File (33 bytes)
|
|
2250
|
+
|
|
2251
|
+
```
|
|
2252
|
+
43 47 45 4f 44 41 54 41 signature "CGEODATA"
|
|
2253
|
+
21 version 33 -> kind 2 (place location index), version 1
|
|
2254
|
+
a1 f7 91 13 f5 48 03 fc source_checksum 0xfc0348f51391f7a1
|
|
2255
|
+
01 entry_width 1
|
|
2256
|
+
07 place_count 7
|
|
2257
|
+
73 00 73 00 00 74 00 entries of places 0 to 6
|
|
2258
|
+
0e 00 0e 00 00 05 00 areas of places 0 to 6
|
|
2259
|
+
```
|
|
2260
|
+
|
|
2261
|
+
The checksum is the FNV-1a 64 hash of the first 131 bytes of the
|
|
2262
|
+
Appendix A file: the 17-byte header, the 5-byte directory, and the
|
|
2263
|
+
109-byte place table, up to the first byte of chunk `3` at offset
|
|
2264
|
+
`131`.
|
|
2265
|
+
|
|
2266
|
+
#### C.8.2. Areas
|
|
2267
|
+
|
|
2268
|
+
All records are in chunk `3` (`c = 1`, `r = 1`), so `gx = 4 + x` and
|
|
2269
|
+
`gy = 4 + y`. The four records with a place are:
|
|
2270
|
+
|
|
2271
|
+
| Record | Code, depth | Place | `(gx, gy)` | `s` | Weight | `u` | `v` |
|
|
2272
|
+
|--------|-------------|------------------|------------|-----|--------|------|------|
|
|
2273
|
+
| 0 | 0, 1 | 2, Noord-Holland | (4, 4) | 2 | 4 | 10 | 10 |
|
|
2274
|
+
| 1 | 4, 2 | 5, Zaandam | (6, 4) | 1 | 1 | 13 | 9 |
|
|
2275
|
+
| 5 | 8, 1 | 2, Noord-Holland | (4, 6) | 2 | 4 | 10 | 14 |
|
|
2276
|
+
| 6 | 12, 2 | 5, Zaandam | (6, 6) | 1 | 1 | 13 | 13 |
|
|
2277
|
+
|
|
2278
|
+
Records `2` to `4` have palette index `0`. The area of Zaandam (`5`) is
|
|
2279
|
+
records `1` and `6`. The areas of Noord-Holland (`2`) and of Nederland
|
|
2280
|
+
(`0`), which contains it, are all four records. Amsterdam, België, and both
|
|
2281
|
+
Antwerpens have no records, so their entries are `00`.
|
|
2282
|
+
|
|
2283
|
+
#### C.8.3. Zaandam
|
|
2284
|
+
|
|
2285
|
+
`T = 2`. Both `u` centres are `13`, which already holds more than half
|
|
2286
|
+
the weight, so `lo = hi = 13` and `fx = floor(26 / 4) = 6`. The `v`
|
|
2287
|
+
centres are `9` and `13`, one weight each. The weight at or below `9`
|
|
2288
|
+
is exactly `T / 2`, so `lo = 9`, `hi = 13`, and
|
|
2289
|
+
`fy = floor(22 / 4) = 5`: the median falls midway between Zaandam's two
|
|
2290
|
+
cells.
|
|
2291
|
+
|
|
2292
|
+
Cell `(6, 5)` is local cell `(2, 1)` of chunk `3`, code `6`, which is
|
|
2293
|
+
record `3`, with no place. The nearest cells of the area are record
|
|
2294
|
+
`1`'s `(6, 4)` and record `6`'s `(6, 6)`, both one row away. With
|
|
2295
|
+
`g = min(4, 2) = 2`, `a = 2` and `b = 1`, both are at squared distance
|
|
2296
|
+
`1`. The tie goes to
|
|
2297
|
+
the lower row, `(6, 4)`: local cell `(2, 0)` of chunk `3`, code `4`.
|
|
2298
|
+
The entry is `2^(2 + 4) + 3 × 16 + 4 = 116`, `74`.
|
|
2299
|
+
|
|
2300
|
+
Its centre is `lat -90 + 4.5 × 22.5 = 11.25`,
|
|
2301
|
+
`lon -180 + 6.5 × 45 = 112.5`, which is the cell of the first lookup in
|
|
2302
|
+
[Section A.5](#a5-lookups), Zaandam.
|
|
2303
|
+
|
|
2304
|
+
#### C.8.4. Noord-Holland and Nederland
|
|
2305
|
+
|
|
2306
|
+
`T = 4 + 1 + 4 + 1 = 10`. The `u` centres are `10` with weight `8`
|
|
2307
|
+
and `13` with weight `2`: `10` holds more than half, so
|
|
2308
|
+
`fx = floor(20 / 4) = 5`. The `v` centres are `9` (weight `1`), `10`
|
|
2309
|
+
(`4`), `13` (`1`), and `14` (`4`). The weight at or below `10` is `5`,
|
|
2310
|
+
exactly half, so `lo = 10`, `hi = 13`, and `fy = floor(23 / 4) = 5`.
|
|
2311
|
+
|
|
2312
|
+
Cell `(5, 5)` is local cell `(1, 1)`, inside record `0`'s square, so it
|
|
2313
|
+
is the representative cell. Its code at depth `2` is `3`, and the
|
|
2314
|
+
entry is `64 + 48 + 3 = 115`, `73`. Its centre is `lat 33.75`,
|
|
2315
|
+
`lon 67.5`, where a lookup finds record `0`: Noord-Holland, which is
|
|
2316
|
+
the place itself for ID `2` and a place under it for ID `0`.
|
|
2317
|
+
|
|
2318
|
+
#### C.8.5. Decoding
|
|
2319
|
+
|
|
2320
|
+
Entry `74` is `116`. Its most significant bit is `t = 6`, and
|
|
2321
|
+
`t - B = 4` is even and at most `2D = 4`, so `d = 2`.
|
|
2322
|
+
`rest = 116 - 64 = 52`, the chunk index is `52 >> 4 = 3`, and the
|
|
2323
|
+
stored code is `52 mod 16 = 4`. An entry of `75` (`117`) would decode
|
|
2324
|
+
the same way to code `5`, a cell with no place: a well-formed entry is
|
|
2325
|
+
not necessarily a correct one, which is what the checksum is for. An
|
|
2326
|
+
entry of `20` (`32`) would be rejected, since `t - B = 3` is odd.
|
|
2327
|
+
|
|
2328
|
+
#### C.8.6. Area Bytes
|
|
2329
|
+
|
|
2330
|
+
Zaandam's area has `T = 2` (C.8.3). `T^4 = 16`, `10000` in binary, has
|
|
2331
|
+
five digits, so its area byte is `05`: `2^(4/4) <= 2 < 2^(5/4)`.
|
|
2332
|
+
Noord-Holland's and Nederland's have `T = 10` (C.8.4). `T^4 = 10000` lies
|
|
2333
|
+
between `2^13 = 8192` and `2^14 = 16384`, so it has fourteen binary
|
|
2334
|
+
digits and their area byte is `0e`. The four places without records
|
|
2335
|
+
have area byte `00`.
|
|
2336
|
+
|
|
2337
|
+
A full-precision cell of this file is `45°` wide and `22.5°` high,
|
|
2338
|
+
`1012.5` square degrees. Area byte `05` stands for about
|
|
2339
|
+
`2^(4.5 / 4) × 1012.5 ≈ 2209` square degrees, against Zaandam's true
|
|
2340
|
+
`2025`.
|
|
2341
|
+
|
|
2342
|
+
## Appendix D. References
|
|
2343
|
+
|
|
2344
|
+
- [RFC1950] Deutsch, P. and J-L. Gailly, "ZLIB Compressed Data Format
|
|
2345
|
+
Specification version 3.3", May 1996.
|
|
2346
|
+
- [RFC1951] Deutsch, P., "DEFLATE Compressed Data Format Specification
|
|
2347
|
+
version 1.3", May 1996.
|
|
2348
|
+
- [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
|
2349
|
+
Requirement Levels", BCP 14, March 1997.
|
|
2350
|
+
- [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO 10646",
|
|
2351
|
+
STD 63, November 2003.
|
|
2352
|
+
- [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119
|
|
2353
|
+
Key Words", BCP 14, May 2017.
|
|
2354
|
+
- Protocol Buffers, "Encoding: Base 128 Varints",
|
|
2355
|
+
<https://protobuf.dev/programming-guides/encoding/#varints>.
|
|
2356
|
+
- Morton, G. M., "A computer oriented geodetic data base and a new
|
|
2357
|
+
technique in file sequencing", IBM, 1966.
|
|
2358
|
+
- [UAX15] Whistler, K., "Unicode Normalization Forms", Unicode Standard
|
|
2359
|
+
Annex #15, <https://unicode.org/reports/tr15/>.
|
|
2360
|
+
- [UNICODE] The Unicode Consortium, "The Unicode Standard", Section
|
|
2361
|
+
3.13 "Default Case Algorithms", <https://www.unicode.org/versions/latest/>.
|
|
2362
|
+
- [FNV] Eastlake, D., Fowler, G., Noll, L., Vo, K-P., and T. Hansen,
|
|
2363
|
+
"The FNV Non-Cryptographic Hash Algorithm",
|
|
2364
|
+
draft-eastlake-fnv, <https://datatracker.ietf.org/doc/draft-eastlake-fnv/>.
|
|
2365
|
+
- [history.md](https://github.com/ferrybig/loc-to-city/blob/master/history.md): the prototype and measurements this format
|
|
2366
|
+
is derived from.
|