@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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -2
  3. package/binary-format.md +2366 -0
  4. package/dist/cache.d.ts +24 -0
  5. package/dist/cache.js +60 -0
  6. package/dist/cellBounds.d.ts +19 -0
  7. package/dist/cellBounds.js +36 -0
  8. package/dist/chunk.d.ts +34 -0
  9. package/dist/chunk.js +269 -0
  10. package/dist/chunkDecoder.d.ts +39 -0
  11. package/dist/chunkDecoder.js +116 -0
  12. package/dist/chunkDecoderWasm.d.ts +2 -0
  13. package/dist/chunkDecoderWasm.js +4 -0
  14. package/dist/cli/buildLocateIndex.d.ts +2 -0
  15. package/dist/cli/buildLocateIndex.js +46 -0
  16. package/dist/cli/buildSearchIndex.d.ts +2 -0
  17. package/dist/cli/buildSearchIndex.js +90 -0
  18. package/dist/cli/chunk.d.ts +2 -0
  19. package/dist/cli/chunk.js +67 -0
  20. package/dist/cli/info.d.ts +2 -0
  21. package/dist/cli/info.js +87 -0
  22. package/dist/cli/list.d.ts +2 -0
  23. package/dist/cli/list.js +71 -0
  24. package/dist/cli/locate.d.ts +2 -0
  25. package/dist/cli/locate.js +64 -0
  26. package/dist/cli/lookup.d.ts +2 -0
  27. package/dist/cli/lookup.js +128 -0
  28. package/dist/cli/search.d.ts +2 -0
  29. package/dist/cli/search.js +119 -0
  30. package/dist/cli/shared.d.ts +50 -0
  31. package/dist/cli/shared.js +140 -0
  32. package/dist/cli/show.d.ts +2 -0
  33. package/dist/cli/show.js +38 -0
  34. package/dist/cli.d.ts +2 -0
  35. package/dist/cli.js +107 -0
  36. package/dist/container.d.ts +17 -0
  37. package/dist/container.js +68 -0
  38. package/dist/directory.d.ts +21 -0
  39. package/dist/directory.js +31 -0
  40. package/dist/errors.d.ts +52 -0
  41. package/dist/errors.js +56 -0
  42. package/dist/format.d.ts +21 -0
  43. package/dist/format.js +22 -0
  44. package/dist/header.d.ts +29 -0
  45. package/dist/header.js +69 -0
  46. package/dist/index.d.ts +20 -0
  47. package/dist/index.js +18 -0
  48. package/dist/indexes.d.ts +11 -0
  49. package/dist/indexes.js +12 -0
  50. package/dist/inflate.d.ts +21 -0
  51. package/dist/inflate.js +61 -0
  52. package/dist/locateIndex.d.ts +142 -0
  53. package/dist/locateIndex.js +561 -0
  54. package/dist/morton.d.ts +28 -0
  55. package/dist/morton.js +51 -0
  56. package/dist/node.d.ts +65 -0
  57. package/dist/node.js +137 -0
  58. package/dist/placeTable.d.ts +69 -0
  59. package/dist/placeTable.js +177 -0
  60. package/dist/reader.d.ts +534 -0
  61. package/dist/reader.js +1128 -0
  62. package/dist/relevance.d.ts +184 -0
  63. package/dist/relevance.js +266 -0
  64. package/dist/searchIndex.d.ts +148 -0
  65. package/dist/searchIndex.js +786 -0
  66. package/dist/source.d.ts +49 -0
  67. package/dist/source.js +50 -0
  68. package/dist/turns.d.ts +19 -0
  69. package/dist/turns.js +40 -0
  70. package/dist/varint.d.ts +52 -0
  71. package/dist/varint.js +137 -0
  72. package/package.json +76 -5
@@ -0,0 +1,534 @@
1
+ import { type BoundingBox } from "./cellBounds.ts";
2
+ import { type ChunkRecords } from "./chunk.ts";
3
+ import type { CGeoDataDirectory } from "./directory.ts";
4
+ import type { CGeoDataHeader } from "./header.ts";
5
+ import { type Inflater } from "./inflate.ts";
6
+ import { LocateIndex } from "./locateIndex.ts";
7
+ import { SearchIndex } from "./searchIndex.ts";
8
+ import { type SourceInput } from "./source.ts";
9
+ /** `lookup()`'s result: where a point resolves, without names. */
10
+ export interface PlaceIdResult {
11
+ /** The global id (Section 7.5) of the finest place resolved -- the city,
12
+ * else the region, else the country -- or null where nothing resolved.
13
+ * Two results with the same id have the same names, so a caller doing
14
+ * many lookups can key per-place work (a color, a label) on it instead
15
+ * of on the names. */
16
+ placeId: number | null;
17
+ /**
18
+ * The lat/lon rectangle this result is valid for -- every coordinate in
19
+ * this box resolves to the exact same result as the one just queried.
20
+ * Callers doing many nearby lookups (e.g. a tile renderer sampling a
21
+ * grid of pixels) can cache on this to skip repeat queries that would
22
+ * land in the same underlying cell. It's as tight as the cell that
23
+ * produced the result when one was matched; for "unknown" results it's
24
+ * the whole empty chunk when possible, otherwise the single finest-grain
25
+ * cell containing the query point (always correct, just not maximal --
26
+ * the actual empty region in a gap between records isn't generally a
27
+ * plain rectangle, so this doesn't try to find its full extent).
28
+ */
29
+ boundingBox: BoundingBox;
30
+ /** Index of the chunk the query point falls in (Section 6.1's
31
+ * `r * W + c`), whether or not that chunk holds any records. Useful for
32
+ * narrowing a rebuild to one chunk with the compressor's `--only-chunks`
33
+ * when a query resolves unexpectedly. */
34
+ chunkIndex: number;
35
+ }
36
+ /** `query()`'s result: `lookup()`'s, with the place and its ancestors
37
+ * named. A level that doesn't apply is null: `city` for a point resolved
38
+ * only to a region, all three where nothing resolved. */
39
+ export interface QueryResult extends PlaceIdResult {
40
+ /** The country the point lies in. */
41
+ country: PlaceRef | null;
42
+ /** The region (province, state, ...) the point lies in. */
43
+ region: PlaceRef | null;
44
+ /** The city -- or whatever finest division the country has; see the
45
+ * README -- the point lies in. */
46
+ city: PlaceRef | null;
47
+ }
48
+ /** The three tiers of the place hierarchy (Section 7.1). */
49
+ export type PlaceLevel = "country" | "region" | "city";
50
+ /** A place's global id (Section 7.5) and its name entry (Section 7.4). */
51
+ export interface PlaceRef {
52
+ /** The place's global id (Section 7.5): what `place()`, `placeIds()`
53
+ * and `locate()` take. Stable within one file, not between builds. */
54
+ id: number;
55
+ /**
56
+ * The place's name in its own language and script, as people there
57
+ * write it: "東京都", "Den Haag". Field 0 of its name entry, never
58
+ * empty.
59
+ */
60
+ localName: string;
61
+ /**
62
+ * The place's international (English) name -- "Tokyo", "The Hague" --
63
+ * or `localName` where it has none, which is also the case for a place
64
+ * whose names are the same. Field 1 of its name entry. Pick this one
65
+ * when a name is shown to everyone regardless of their language, or
66
+ * stored once for all of them.
67
+ */
68
+ internationalName: string;
69
+ /**
70
+ * Every name, in name-entry order (Section 7.4): `localName`, then the
71
+ * international name (`""` where it has none but alternatives follow),
72
+ * then alternative names and spellings ("'s-Gravenhage"). For search
73
+ * and for showing alternatives; to display one name, use `localName`
74
+ * or `internationalName` rather than indexing into this.
75
+ */
76
+ names: string[];
77
+ }
78
+ /** A place's level and the global ids of it and its ancestors, shaped
79
+ * like `Place` without the names. */
80
+ export interface PlaceIds {
81
+ /** The place's own level. */
82
+ level: PlaceLevel;
83
+ /** Id of its country (the place itself, for a country). */
84
+ country: number;
85
+ /** Id of its region (the place itself, for a region); null for a
86
+ * country. */
87
+ region: number | null;
88
+ /** The place's own id for a city; null otherwise. */
89
+ city: number | null;
90
+ }
91
+ /**
92
+ * One entry of the place hierarchy with its ancestors resolved. Shaped
93
+ * like QueryResult's country/region/city trio so the two are easy to
94
+ * relate: a country has `region` and `city` null, a region has `city`
95
+ * null and `region` pointing at itself, a city has all three set.
96
+ */
97
+ export interface Place extends PlaceRef {
98
+ /** The place's own level. */
99
+ level: PlaceLevel;
100
+ /** Its country (itself, for a country). */
101
+ country: PlaceRef;
102
+ /** Its region (itself, for a region); null for a country. */
103
+ region: PlaceRef | null;
104
+ /** Itself, for a city; null otherwise. */
105
+ city: PlaceRef | null;
106
+ }
107
+ /** Narrows `places()` and `search()`. Filters combine: `{ level: "city",
108
+ * country }` is that country's cities. */
109
+ export interface PlaceFilter {
110
+ /** Only places of this level. */
111
+ level?: PlaceLevel;
112
+ /** Only places inside this country (by id); the country itself included. */
113
+ country?: number;
114
+ /** Only places inside this region (by id); the region itself included. */
115
+ region?: number;
116
+ }
117
+ /** Options for `search()`. */
118
+ export interface SearchOptions extends PlaceFilter {
119
+ /** Stop after this many matches. */
120
+ limit?: number;
121
+ /**
122
+ * Rank matches by relevance instead of id order (Appendix B.5): an
123
+ * exact name match outranks a match on a whole word of the name, which
124
+ * outranks a plain substring match, each tier further split by whether
125
+ * the query's own spelling (not just its folded form) matched; among
126
+ * equal matches, a country outranks a city, which outranks a region. `limit` still applies,
127
+ * but to the top of the ranked list rather than the first N found in
128
+ * id order -- so with `sort` the whole match set has to be scored
129
+ * before anything is yielded, since the best match isn't necessarily
130
+ * the first one found.
131
+ */
132
+ sort?: boolean;
133
+ /**
134
+ * With `sort`, whether to break ties by area (Appendix B.5.1): among
135
+ * equally good matches, larger places first, so "berlin" puts Berlin,
136
+ * Germany ahead of the many small Berlins in the United States, while a
137
+ * province still follows its own same-named city. Areas come from a
138
+ * locate index (`useLocateIndex()`):
139
+ *
140
+ * - `"auto"` (the default): by area when a locate index is attached,
141
+ * otherwise by Appendix B.5's plain order. Unlike everything else an
142
+ * index does, attaching one therefore changes the order of results
143
+ * (never which places match).
144
+ * - `true`: by area; rejects without a locate index.
145
+ * - `false`: Appendix B.5's plain order, index or not.
146
+ */
147
+ areaTieBreak?: "auto" | boolean;
148
+ }
149
+ /** One record of a chunk (Section 8.2) with its cell and place resolved. */
150
+ export interface ChunkRecord {
151
+ /** The record's stored Morton code within its chunk (Section 8.1). */
152
+ code: number;
153
+ /** The record's depth in the chunk's quadtree (0 = the whole chunk). */
154
+ depth: number;
155
+ /** The cell the record covers. */
156
+ boundingBox: BoundingBox;
157
+ /** null for a "no place" record (palette index 0). */
158
+ place: Place | null;
159
+ }
160
+ /** How many places of each level a file has. */
161
+ export interface PlaceCounts {
162
+ /** Number of countries; their ids are `0 .. countries - 1`. */
163
+ countries: number;
164
+ /** Number of regions; their ids follow the countries'. */
165
+ regions: number;
166
+ /** Number of cities; their ids follow the regions'. */
167
+ cities: number;
168
+ }
169
+ /** `locate()`'s result: a point inside a place, and the cell it's the
170
+ * centre of. */
171
+ export interface Location {
172
+ /** Latitude of the centre of `boundingBox`. */
173
+ lat: number;
174
+ /** Longitude of the centre of `boundingBox`. */
175
+ lon: number;
176
+ /** The place's representative cell (Section C.3). */
177
+ boundingBox: BoundingBox;
178
+ /**
179
+ * Roughly how large the place is, everything under it included, in
180
+ * square degrees -- from its area byte (Section C.3), so within about
181
+ * 9% either way. Useful for zooming a map to it: a square of this area
182
+ * is `sqrt(area)` degrees on a side, which a Web Mercator map shows
183
+ * across one 256-pixel tile at zoom `log2(360 / sqrt(area))`.
184
+ */
185
+ area: number;
186
+ }
187
+ /**
188
+ * A sidecar index for `OpenOptions`: the index itself (an open one stays
189
+ * the caller's to close), its file as a source, or a function that finds
190
+ * it on first use and resolves to undefined when there is none.
191
+ */
192
+ export type IndexOption<I> = I | SourceInput | (() => Promise<I | SourceInput | undefined>);
193
+ /** Options for `CGeoDataReader.open()`. */
194
+ export interface OpenOptions {
195
+ /**
196
+ * Memory budget, in bytes, for decoded chunks and name blocks; least
197
+ * recently used ones are dropped past it. Default 64 MiB. A worldwide
198
+ * file decodes to about 1 GB in all, its largest chunk to about 3 MB,
199
+ * so a budget much below that makes lookups scattered across the
200
+ * world re-decode often. 0 disables caching, except for the one most
201
+ * recently used chunk.
202
+ */
203
+ cacheBytes?: number;
204
+ /** How compressed blocks are inflated. Default `streamInflater`, which
205
+ * works everywhere; on Node.js, `nodeInflater` from `./node` is
206
+ * several times faster. */
207
+ inflater?: Inflater;
208
+ /** A search index (Appendix B) for `search()`; see `useSearchIndex()`.
209
+ * Not read until the first search that can use it. */
210
+ searchIndex?: IndexOption<SearchIndex>;
211
+ /** A locate index (Appendix C) for `locate()`; see `useLocateIndex()`.
212
+ * Not read until the first `locate()`. */
213
+ locateIndex?: IndexOption<LocateIndex>;
214
+ }
215
+ /** `OpenOptions.cacheBytes` when not given. */
216
+ export declare const DEFAULT_CACHE_BYTES: number;
217
+ /**
218
+ * A CGEODATA file opened for reading: answers which country, region and
219
+ * city a point lies in (`query`, `lookup`), what a place is called and
220
+ * where it sits in the hierarchy (`place`, `placeIds`), which places
221
+ * match a name (`search`), and where a place is (`locate`).
222
+ *
223
+ * Opening reads only the header, directory and place counts; each lookup
224
+ * reads and decodes the one chunk its point lies in, which then stays
225
+ * cached (`OpenOptions.cacheBytes`).
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * // Node.js
230
+ * import { openFile } from "@ferrybig/cgeodata-reader/node";
231
+ * const reader = await openFile("world.bin");
232
+ *
233
+ * // Anywhere else
234
+ * import { CGeoDataReader } from "@ferrybig/cgeodata-reader";
235
+ * const reader = await CGeoDataReader.open(await (await fetch(url)).arrayBuffer());
236
+ *
237
+ * const { city, region, country } = await reader.query(52.0799, 4.3113);
238
+ * city?.internationalName; // "The Hague"
239
+ * ```
240
+ */
241
+ export declare class CGeoDataReader {
242
+ /** Decoded chunks (keyed by chunk index) and name blocks (keyed by
243
+ * `-1 - blockIndex`) under one byte budget. */
244
+ private readonly cache;
245
+ /** Loads in flight, under the same keys as `cache`, so concurrent
246
+ * lookups in one chunk decode it once. */
247
+ private readonly loading;
248
+ /** The chunk the last lookup used, held outside `cache`: lookups close
249
+ * together (a tile's samples) mostly land in the same chunk, and this
250
+ * answers those without touching the LRU's bookkeeping. */
251
+ private lastChunkIndex;
252
+ private lastChunk;
253
+ /** Decodes take turns, one per macrotask; see `TurnQueue`. */
254
+ private readonly decodes;
255
+ /** The full-precision cell of the point `gridCell()` last placed; read
256
+ * right after, in the same synchronous run. */
257
+ private gx;
258
+ private gy;
259
+ private readonly search_;
260
+ private readonly locate_;
261
+ /** Rank areas of the regions searches have matched so far (Appendix
262
+ * B.5.1), -1 where not yet known, for the area bytes they came from. */
263
+ private rankAreas;
264
+ private placeTableChecksumPromise;
265
+ private prefixChecksumPromise;
266
+ private readonly source;
267
+ private readonly inflater;
268
+ /** The file's header (Section 5), including its `attribution`. */
269
+ readonly header: CGeoDataHeader;
270
+ /** The file's directory (Section 6): where each chunk is. */
271
+ readonly directory: CGeoDataDirectory;
272
+ private readonly placeTable;
273
+ private constructor();
274
+ /**
275
+ * Opens a CGEODATA file: reads and validates its header, directory and
276
+ * the uncompressed part of its place table (Sections 5-7.2), and
277
+ * nothing else -- chunks and names are read on first use. The reader
278
+ * takes ownership of `input`, if it's a `ByteSource`: `close()` closes
279
+ * it, as does a failed open.
280
+ */
281
+ static open(input: SourceInput, options?: OpenOptions): Promise<CGeoDataReader>;
282
+ /** Closes the source, and any index this reader opened itself. */
283
+ close(): Promise<void>;
284
+ /** How many countries, regions and cities the file has. */
285
+ get placeCounts(): PlaceCounts;
286
+ /**
287
+ * The country, region and city a point lies in: Section 9's lookup
288
+ * procedure (`lookup()`) with the names resolved (`place()`). Rejects
289
+ * with a `RangeError` for a latitude outside [-90, 90) or a longitude
290
+ * outside [-180, 180).
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * const result = await reader.query(35.6762, 139.6503);
295
+ * result.city?.localName; // "杉並区"
296
+ * result.city?.internationalName; // "Suginami"
297
+ * result.country?.internationalName; // "Japan"
298
+ * ```
299
+ */
300
+ query(lat: number, lon: number): Promise<QueryResult>;
301
+ /**
302
+ * Section 9's lookup procedure: the place a point lies in, as an id, and
303
+ * the cell the answer holds for. Reads and decodes the point's chunk if
304
+ * it isn't cached; resolve the id with `place()` or `placeIds()`.
305
+ */
306
+ lookup(lat: number, lon: number): Promise<PlaceIdResult>;
307
+ /**
308
+ * `lookup()` answered from memory only: the same result when the
309
+ * point's chunk is already decoded, undefined when answering would
310
+ * need a read. For callers doing many lookups close together, which
311
+ * mostly hit the same few chunks:
312
+ *
313
+ * ```ts
314
+ * const result = reader.lookupCached(lat, lon) ?? (await reader.lookup(lat, lon));
315
+ * ```
316
+ *
317
+ * waits only for the lookups that actually read something.
318
+ */
319
+ lookupCached(lat: number, lon: number): PlaceIdResult | undefined;
320
+ /** The index of the chunk a point lies in (Section 6.1). */
321
+ chunkIndexAt(lat: number, lon: number): number;
322
+ /** Validates a point and places it on the full-precision grid
323
+ * (`this.gx`, `this.gy`), returning its chunk's index. */
324
+ private gridCell;
325
+ /** Section 9 steps 5-8 for the point `gridCell()` just placed, in its
326
+ * (decoded, or null for empty) chunk. */
327
+ private resolve;
328
+ /** The names of one place by global id, without resolving its ancestors.
329
+ * Cheaper than `place()` when only the names are needed. */
330
+ placeNames(id: number): Promise<string[]>;
331
+ /** FNV-1a 64-bit hash of the place table bytes, as defined by
332
+ * binary-format.md Section B.6. Used to bind a search index to its source. */
333
+ placeTableChecksum(): Promise<bigint>;
334
+ /** FNV-1a 64-bit hash of everything before the first chunk -- header,
335
+ * directory and place table -- as defined by binary-format.md Section
336
+ * C.4. Binds a locate index to its source: the chunk lengths it covers
337
+ * change whenever the chunks do. */
338
+ prefixChecksum(): Promise<bigint>;
339
+ /**
340
+ * Attaches a text search index (Appendix B, built by `buildSearchIndex`
341
+ * / `cgeodata build-search-index`) so `search()` only checks the places
342
+ * the index says could match instead of every place. Results are
343
+ * identical with or without it -- only the speed differs.
344
+ *
345
+ * Takes the index's file (as for `open()`; read on demand) or an open
346
+ * `SearchIndex` (which the caller keeps ownership of), or undefined to
347
+ * search without one. Replaces `OpenOptions.searchIndex`. Rejects with
348
+ * a `CGeoDataIndexMismatchError` if it was built from a different file.
349
+ */
350
+ useSearchIndex(index: SearchIndex | SourceInput | undefined): Promise<void>;
351
+ /** Whether `search()` will use an index. Reads (and validates) the one
352
+ * from `OpenOptions.searchIndex` if that hasn't happened yet. */
353
+ hasSearchIndex(): Promise<boolean>;
354
+ /** The attached search index, opening `OpenOptions.searchIndex` on
355
+ * first use. One that's there but unusable (stale, corrupt) rejects on
356
+ * every call rather than silently falling back to a linear scan. */
357
+ private activeSearchIndex;
358
+ private checkSearchIndex;
359
+ /**
360
+ * Attaches a locate index (Appendix C, built by `buildLocateIndex` /
361
+ * `cgeodata build-locate-index`) so `locate()` reads one entry instead
362
+ * of scanning every chunk. Results are identical with or without it --
363
+ * only the speed differs -- except that its areas also let a sorted
364
+ * `search()` rank equal matches by size (`SearchOptions.areaTieBreak`).
365
+ *
366
+ * Takes the index's file (as for `open()`; read on demand) or an open
367
+ * `LocateIndex` (which the caller keeps ownership of), or undefined to
368
+ * locate without one. Replaces `OpenOptions.locateIndex`. Rejects with
369
+ * a `CGeoDataIndexMismatchError` if it was built from a different file.
370
+ */
371
+ useLocateIndex(index: LocateIndex | SourceInput | undefined): Promise<void>;
372
+ /** Whether `locate()` will use an index. Reads (and validates) the one
373
+ * from `OpenOptions.locateIndex` if that hasn't happened yet. */
374
+ hasLocateIndex(): Promise<boolean>;
375
+ /** The attached locate index, opening `OpenOptions.locateIndex` on
376
+ * first use. One that's there but unusable (stale, corrupt) rejects on
377
+ * every call rather than silently falling back to a chunk scan. */
378
+ private activeLocateIndex;
379
+ private checkLocateIndex;
380
+ /** `useSearchIndex`/`useLocateIndex`, for either slot. */
381
+ private attach;
382
+ /** The slot's index, trying its pending one first. */
383
+ private activate;
384
+ private tryPending;
385
+ /** Resolves one place id (Section 7.5) to its names and ancestors. */
386
+ place(id: number): Promise<Place>;
387
+ /** `place()` when every name it needs is cached, else undefined. */
388
+ private loadedPlace;
389
+ /** `place()` loading each name block in turn, for when the cache is
390
+ * too small to hold them all at once. */
391
+ private placeFrom;
392
+ /** `place()` without the names: the place's level and the global ids
393
+ * of it and its ancestors, from the place table's counts alone, so no
394
+ * name block is read -- and so synchronous. */
395
+ placeIds(id: number): PlaceIds;
396
+ /** How many places sit directly under `id`: a country's regions, a
397
+ * region's cities, always 0 for a city. From the place table's counts
398
+ * alone, like `placeIds()`, so no name block is read and it is
399
+ * synchronous: `childCount(region) === 0` finds a region with no city
400
+ * mapped to it. */
401
+ childCount(id: number): number;
402
+ /**
403
+ * Walks the place hierarchy in id order (countries, then regions, then
404
+ * cities), optionally restricted to one level and/or one ancestor. Each
405
+ * level's ids for a given parent are contiguous (Section 7.5), so a
406
+ * filtered walk only touches the ids it yields.
407
+ */
408
+ places(filter?: PlaceFilter): AsyncIterableIterator<Place>;
409
+ /**
410
+ * Diacritic- and case-insensitive substring match against every name of
411
+ * every place (all levels unless filtered) -- Section B.5's "matches":
412
+ * the folded form (`foldDiacritics`) of a name contains the folded form
413
+ * of the query. Without a search index this is a plain linear scan over
414
+ * the name blocks; with one (`useSearchIndex()`), only the places the
415
+ * index can't rule out are compared. Same results either way.
416
+ *
417
+ * A text with commas also reads as a place and the places it lies in
418
+ * (Appendix B.13): "Paris, Texas" finds a place named "Paris, Texas"
419
+ * and every "Paris" whose region or country matches "Texas";
420
+ * "Springfield, Illinois, United States" narrows by both. Each part
421
+ * after a comma has to match a different ancestor, nearest first.
422
+ *
423
+ * @example
424
+ * ```ts
425
+ * for await (const place of reader.search("munchen", { sort: true, limit: 20 }))
426
+ * console.log(place.internationalName, place.country.internationalName);
427
+ * // Munich Germany
428
+ * // Grafing bei München Germany
429
+ * // ...
430
+ * ```
431
+ */
432
+ search(text: string, options?: SearchOptions): AsyncIterableIterator<Place>;
433
+ /** The `sort: true` path of `search()`: scores every match in relevance
434
+ * order (Appendix B.5, or B.5.1 when ranking by area; `relevance.ts`),
435
+ * then yields the best first. `limit` is applied after sorting, not
436
+ * during the scan -- the top match can turn up anywhere in id order. */
437
+ private searchRanked;
438
+ /** `text`'s readings (Appendix B.13) as needles, and the places a search
439
+ * index says could match any of them (ascending), or undefined when
440
+ * there's no index or it can't narrow one of the readings down. */
441
+ private prepareQuery;
442
+ /** Whether place `id` matches any reading of `query`. With `compare`,
443
+ * instead the best-ranked way it does (by that order), or undefined;
444
+ * `rankArea` is left at 0 for the caller to fill in. */
445
+ private matchQuery;
446
+ /** The names of place `id`'s ancestors, nearest first: a city's region
447
+ * and country, a region's country, nothing for a country. */
448
+ private ancestorNames;
449
+ /** `areaTieBreak` resolved to the area bytes to rank by, or undefined
450
+ * for Appendix B.5's plain order. */
451
+ private searchAreas;
452
+ /**
453
+ * The area byte (binary-format.md Section C.3) of every place, by id --
454
+ * the size of the place and everything under it, on a log scale, a
455
+ * quarter of a doubling per step -- read from the attached locate index
456
+ * (`useLocateIndex()`), or undefined without one. What
457
+ * `search({ sort: true })` breaks ties with (`SearchOptions.areaTieBreak`).
458
+ * Read once and then kept; the array is shared, so don't modify it.
459
+ */
460
+ placeAreas(): Promise<Uint8Array | undefined>;
461
+ /** Place `id`'s rank area (Appendix B.5.1, `regionRankArea`): its area
462
+ * byte, except for a region with a city of the same local name. That
463
+ * takes reading the region's city names, so it's worked out only for
464
+ * the regions a search actually matched, once each. */
465
+ private rankArea;
466
+ /** The ids (ascending) of `level` under the filter that `search()` has
467
+ * to check: the whole `idRange`, or, when a search index narrowed the
468
+ * query to `candidates` (ascending), just those within it. */
469
+ private searchScope;
470
+ /** The parsed records of one chunk (Section 8), or undefined when the
471
+ * chunk is empty. Cached like lookups, unless `cache` is false: a
472
+ * one-pass walk over every chunk should skip the cache, so it doesn't
473
+ * evict everything lookups had cached for chunks it only visits once. */
474
+ chunk(index: number, { cache }?: {
475
+ cache?: boolean;
476
+ }): Promise<ChunkRecords | undefined>;
477
+ /** The lat/lon extent of a chunk (Section 6.1). */
478
+ chunkBoundingBox(index: number): BoundingBox;
479
+ /**
480
+ * A representative lat/lon for a place id: the centre of the place's
481
+ * representative cell (binary-format.md Section C.3), a cell near the
482
+ * middle of the place's area -- its own cells plus those of every place
483
+ * under it -- that is guaranteed to lie in that area, so `query()` at
484
+ * the returned point resolves to the place or one of its descendants.
485
+ *
486
+ * @example
487
+ * ```ts
488
+ * const position = await reader.locate(place.id);
489
+ * if (position) map.setView([position.lat, position.lon]);
490
+ * ```
491
+ *
492
+ * Null if neither the place nor anything under it has any cells -- and
493
+ * that happens to real places, not only to broken files: a place
494
+ * smaller than the file's finest cell (a tiny island, an enclave, a
495
+ * boundary sliver) loses every cell to its neighbours. On a worldwide
496
+ * build that's about 0.15% of places, every one of them still found
497
+ * by `search()`; drop them from results that need a position rather
498
+ * than defaulting to a coordinate.
499
+ *
500
+ * With a locate index (`useLocateIndex()`) this reads one entry;
501
+ * without one it scans every chunk. Same result either way.
502
+ */
503
+ locate(id: number): Promise<Location | null>;
504
+ /**
505
+ * The ids of place `id` and of every place under it, as half-open
506
+ * ranges: `[id, id + 1]` first, then for a country its regions and
507
+ * their cities, for a region its cities. Each level's children are
508
+ * contiguous (Section 7.1), so this is at most three ranges.
509
+ */
510
+ subtreeIdRanges(id: number): Array<[number, number]>;
511
+ /** One chunk's records with their cells and places resolved -- what a
512
+ * lookup in that chunk can answer with, laid out for inspection. */
513
+ chunkRecords(index: number): AsyncIterableIterator<ChunkRecord>;
514
+ private recordBoundingBox;
515
+ /** The half-open global id range of `level` under the filter's
516
+ * narrowest ancestor, via the Section 7.5 prefix arrays. */
517
+ private idRange;
518
+ private checkPlaceId;
519
+ /** A chunk without reading anything: its records when decoded and
520
+ * cached, null when it's empty, undefined when it would need a read. */
521
+ private loadedChunk;
522
+ /** Reads and decodes a chunk, sharing one decode among concurrent
523
+ * callers. */
524
+ private loadChunk;
525
+ private decodeChunk;
526
+ private checkPalette;
527
+ /** A place's names when its name block is cached, else undefined. */
528
+ private loadedNames;
529
+ /** Reads and decodes a place's name block (sharing one read among
530
+ * concurrent callers), and returns its names. */
531
+ private loadNames;
532
+ private get nameBlockSize();
533
+ private entryOf;
534
+ }