@mailwoman/resolver-wof-sqlite 9.3.0 → 9.4.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/lib/address/point-interpolation.ts +1 -2
- package/lib/address/point.ts +54 -6
- package/lib/ancestry/index.ts +1 -2
- package/lib/build-candidate.ts +19 -6
- package/lib/candidate/extract-fold.ts +89 -8
- package/lib/candidate/own-name.ts +1 -0
- package/lib/candidate-lookup.ts +10 -9
- package/lib/candidate-schema.ts +1 -1
- package/lib/candidate-scoring.ts +1 -1
- package/lib/coincident-roles.ts +2 -3
- package/lib/coverage-manifest-schema.ts +2 -1
- package/lib/fst/builder.ts +1 -1
- package/lib/fst/freshness.ts +0 -1
- package/lib/interpolation.ts +1 -1
- package/lib/lookup.ts +2 -2
- package/lib/place-importance-schema.ts +1 -9
- package/lib/poi/lookup.ts +1 -2
- package/lib/primary-preference.ts +14 -4
- package/lib/reverse.ts +1 -2
- package/lib/search-fetch.ts +1 -2
- package/lib/sqlite-utils.ts +0 -1
- package/lib/street/centroid.ts +1 -1
- package/lib/street/normalize.ts +42 -1
- package/lib/uprn/lookup.ts +1 -2
- package/lib/weights-overlay-linker.ts +1 -0
- package/out/address/point-interpolation.d.ts +1 -1
- package/out/address/point-interpolation.d.ts.map +1 -1
- package/out/address/point-interpolation.js.map +1 -1
- package/out/address/point.d.ts +3 -1
- package/out/address/point.d.ts.map +1 -1
- package/out/address/point.js +37 -6
- package/out/address/point.js.map +1 -1
- package/out/ancestry/index.d.ts.map +1 -1
- package/out/ancestry/index.js +1 -1
- package/out/ancestry/index.js.map +1 -1
- package/out/build-candidate.d.ts +5 -3
- package/out/build-candidate.d.ts.map +1 -1
- package/out/build-candidate.js +11 -3
- package/out/build-candidate.js.map +1 -1
- package/out/candidate/extract-fold.d.ts +11 -2
- package/out/candidate/extract-fold.d.ts.map +1 -1
- package/out/candidate/extract-fold.js +56 -7
- package/out/candidate/extract-fold.js.map +1 -1
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +1 -0
- package/out/candidate/own-name.js.map +1 -1
- package/out/candidate-lookup.d.ts +5 -5
- package/out/candidate-lookup.d.ts.map +1 -1
- package/out/candidate-lookup.js +8 -7
- package/out/candidate-lookup.js.map +1 -1
- package/out/candidate-schema.d.ts +1 -1
- package/out/candidate-schema.js +1 -1
- package/out/candidate-scoring.d.ts.map +1 -1
- package/out/candidate-scoring.js +1 -1
- package/out/candidate-scoring.js.map +1 -1
- package/out/coincident-roles.d.ts +1 -1
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +1 -1
- package/out/coincident-roles.js.map +1 -1
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +2 -1
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/fst/builder.d.ts.map +1 -1
- package/out/fst/builder.js +1 -1
- package/out/fst/builder.js.map +1 -1
- package/out/fst/freshness.d.ts +0 -1
- package/out/fst/freshness.d.ts.map +1 -1
- package/out/fst/freshness.js +0 -1
- package/out/fst/freshness.js.map +1 -1
- package/out/interpolation.d.ts +1 -1
- package/out/interpolation.d.ts.map +1 -1
- package/out/lookup.d.ts +1 -1
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +2 -2
- package/out/lookup.js.map +1 -1
- package/out/place-importance-schema.d.ts +0 -1
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +1 -2
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +1 -1
- package/out/poi/lookup.js.map +1 -1
- package/out/primary-preference.d.ts +2 -2
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +12 -4
- package/out/primary-preference.js.map +1 -1
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +1 -1
- package/out/reverse.js.map +1 -1
- package/out/search-fetch.d.ts.map +1 -1
- package/out/search-fetch.js +1 -1
- package/out/search-fetch.js.map +1 -1
- package/out/sqlite-utils.d.ts +0 -1
- package/out/sqlite-utils.d.ts.map +1 -1
- package/out/sqlite-utils.js +0 -3
- package/out/sqlite-utils.js.map +1 -1
- package/out/street/centroid.d.ts +1 -1
- package/out/street/centroid.d.ts.map +1 -1
- package/out/street/normalize.d.ts +13 -1
- package/out/street/normalize.d.ts.map +1 -1
- package/out/street/normalize.js +37 -0
- package/out/street/normalize.js.map +1 -1
- package/out/uprn/lookup.d.ts.map +1 -1
- package/out/uprn/lookup.js +1 -1
- package/out/uprn/lookup.js.map +1 -1
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +1 -0
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +9 -9
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* Standalone like the segment tier — core wiring rides the Phase 2 ordered `spatialTiers` list.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
import type { InterpolationLookup } from "@mailwoman/resolver"
|
|
36
|
+
import type { InterpolationLookup } from "@mailwoman/core/resolver"
|
|
37
37
|
import { haversineKm } from "@mailwoman/spatial"
|
|
38
38
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
39
39
|
|
|
@@ -41,7 +41,6 @@ import type { AddressPointDatabase } from "#address/point-schema"
|
|
|
41
41
|
import type { InterpolatedHit, InterpolationQuery, StreetInterpolator } from "#interpolation"
|
|
42
42
|
import { hasTable, prepareAll, type PreparedAll } from "#sqlite-utils"
|
|
43
43
|
import { canonicalizeRouteKey, type RouteKey, streetKeyVariants } from "#street/normalize"
|
|
44
|
-
|
|
45
44
|
/**
|
|
46
45
|
* Extrapolation cap for a single-sided bracket: at most one pair-span beyond the nearest known point (`t = 2`). Past
|
|
47
46
|
* it, the two-point line carries no evidence about the query number.
|
package/lib/address/point.ts
CHANGED
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
* scopes missed). Multiple hits return the first by rowid — unit siblings share the building coord.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { AddressPointHit, AddressPointLookup } from "@mailwoman/resolver"
|
|
18
|
+
import type { AddressPointHit, AddressPointLookup } from "@mailwoman/core/resolver"
|
|
19
19
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
20
20
|
|
|
21
21
|
import type { AddressPointDatabase, AddressPointTable } from "#address/point-schema"
|
|
22
22
|
import { hasTable, prepareGet, type PreparedGet } from "#sqlite-utils"
|
|
23
23
|
import {
|
|
24
|
+
normalizeHouseNumberForKey,
|
|
24
25
|
normalizeLocalityForKey,
|
|
26
|
+
normalizeLocalityForKeyLocale,
|
|
25
27
|
type NameKey,
|
|
26
28
|
type StreetKey,
|
|
27
29
|
streetKeyVariants,
|
|
@@ -53,6 +55,13 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
53
55
|
| PreparedGet<[postcode: string, locality: NameKey, street: StreetKey, number: string], AddressPointRow>
|
|
54
56
|
| undefined
|
|
55
57
|
readonly #byLocality: PreparedGet<[locality: NameKey, street: StreetKey, number: string], AddressPointRow> | undefined
|
|
58
|
+
/**
|
|
59
|
+
* The scope key matched by its TAIL — a `zh` query that names the 鄉鎮市區 without its 縣市. Narrowed by the (street,
|
|
60
|
+
* number) index first, so the LIKE walks the handful of rows that share the pair.
|
|
61
|
+
*/
|
|
62
|
+
readonly #byLocalityTail:
|
|
63
|
+
| PreparedGet<[street: StreetKey, number: string, tailPattern: string], AddressPointRow>
|
|
64
|
+
| undefined
|
|
56
65
|
readonly #byBbox:
|
|
57
66
|
| PreparedGet<
|
|
58
67
|
[street: StreetKey, number: string, minLat: number, maxLat: number, minLon: number, maxLon: number],
|
|
@@ -102,6 +111,12 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
102
111
|
`SELECT ${SELECT_COLS} FROM address_point
|
|
103
112
|
WHERE street_norm = ? AND number = ? AND lat BETWEEN ? AND ? AND lon BETWEEN ? AND ? LIMIT 1`
|
|
104
113
|
)
|
|
114
|
+
|
|
115
|
+
this.#byLocalityTail = prepareGet(
|
|
116
|
+
this.#db,
|
|
117
|
+
`SELECT ${SELECT_COLS} FROM address_point
|
|
118
|
+
WHERE street_norm = ? AND number = ? AND locality_norm LIKE ? LIMIT 1`
|
|
119
|
+
)
|
|
105
120
|
}
|
|
106
121
|
}
|
|
107
122
|
|
|
@@ -110,20 +125,33 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
110
125
|
number: string
|
|
111
126
|
postcode?: string
|
|
112
127
|
locality?: string
|
|
128
|
+
region?: string
|
|
129
|
+
subregion?: string
|
|
113
130
|
bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
|
|
114
131
|
}): AddressPointHit | null {
|
|
115
132
|
if (!this.#byPostcode || !this.#byLocality || !this.#byBbox) return null
|
|
116
|
-
const number = query.number
|
|
133
|
+
const number = normalizeHouseNumberForKey(query.number, this.#locale)
|
|
117
134
|
|
|
118
135
|
if (!number) return null
|
|
119
136
|
|
|
137
|
+
// A `zh` extract scopes a point by 縣市 + 鄉鎮市區, the pair the parse tags `region` + `subregion`; the Taiwanese
|
|
138
|
+
// register carries no postcode and the parse no `locality`. The pair becomes the locality key here, on the
|
|
139
|
+
// reader that was built with the same fold, and a Latin extract never sees the two fields. A line that names
|
|
140
|
+
// only the 鄉鎮市區 (`中和區中興街281號`, 14.7% of the TW board) matches the stored pair by its tail instead.
|
|
141
|
+
const scoped =
|
|
142
|
+
this.#locale === "zh" && !query.locality && query.subregion
|
|
143
|
+
? query.region
|
|
144
|
+
? { ...query, locality: `${query.region}${query.subregion}` }
|
|
145
|
+
: { ...query, localityTail: normalizeLocalityForKeyLocale(query.subregion, "zh") }
|
|
146
|
+
: query
|
|
147
|
+
|
|
120
148
|
// Key-variant ladder (see `streetKeyVariants`): the literal key first, then the doubled-type
|
|
121
149
|
// collapse and the saint↔st register swap — each variant runs the FULL number ladder below, and
|
|
122
150
|
// the first variant to answer wins, so an attested literal key is never second-guessed.
|
|
123
151
|
let row: AddressPointRow | undefined
|
|
124
152
|
|
|
125
153
|
for (const streetNorm of streetKeyVariants(query.street, streetLocaleForSurface(query.street, this.#locale))) {
|
|
126
|
-
row = this.#findForKey(streetNorm, number,
|
|
154
|
+
row = this.#findForKey(streetNorm, number, scoped)
|
|
127
155
|
|
|
128
156
|
if (row) break
|
|
129
157
|
}
|
|
@@ -152,6 +180,7 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
152
180
|
number: string
|
|
153
181
|
postcode?: string
|
|
154
182
|
locality?: string
|
|
183
|
+
localityTail?: NameKey
|
|
155
184
|
bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
|
|
156
185
|
}
|
|
157
186
|
) {
|
|
@@ -186,6 +215,19 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
186
215
|
}
|
|
187
216
|
}
|
|
188
217
|
|
|
218
|
+
// Sub-number fallback for the Taiwanese register: `14之12` is building 12 off number 14, `30附40` an attached
|
|
219
|
+
// number, stored as number `14` / `30` with the rest in `unit`. A query that kept the pair on the number span
|
|
220
|
+
// falls to the base number — the same adjacent-parcel approximation the letter-suffix rung makes, priced the
|
|
221
|
+
// same: on the 2,000-row served read the rows answered this way sit a median 256 m and at most 1.0 km from the
|
|
222
|
+
// building.
|
|
223
|
+
if (!row && this.#locale === "zh") {
|
|
224
|
+
const base = /^(\d+)(?:[之附]\d+)+$/u.exec(number)?.[1]
|
|
225
|
+
|
|
226
|
+
if (base) {
|
|
227
|
+
row = this.#probe(streetNorm, base, query)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
189
231
|
return row
|
|
190
232
|
}
|
|
191
233
|
|
|
@@ -199,6 +241,7 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
199
241
|
query: {
|
|
200
242
|
postcode?: string
|
|
201
243
|
locality?: string
|
|
244
|
+
localityTail?: NameKey
|
|
202
245
|
bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
|
|
203
246
|
}
|
|
204
247
|
): AddressPointRow | undefined {
|
|
@@ -226,6 +269,11 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
226
269
|
row = this.#byLocality!(this.#localityKey(query.locality), streetNorm, number)
|
|
227
270
|
}
|
|
228
271
|
|
|
272
|
+
if (!row && query.localityTail && this.#byLocalityTail) {
|
|
273
|
+
// A Han key carries no `%` or `_`, so the tail is a literal suffix pattern.
|
|
274
|
+
row = this.#byLocalityTail(streetNorm, number, `%${query.localityTail}`)
|
|
275
|
+
}
|
|
276
|
+
|
|
229
277
|
// Bbox fall-through (#247): the point carries no postcode/locality of its own, but its coordinate falls
|
|
230
278
|
// inside the resolved locality's box. Only reached when the scoped probes missed AND a bbox was supplied.
|
|
231
279
|
if (!row && query.bbox) {
|
|
@@ -248,9 +296,9 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
248
296
|
* 13e Arrondissement" and "Paris" both hit; a no-op for every other locale.
|
|
249
297
|
*/
|
|
250
298
|
#localityKey(locality: string): NameKey {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
299
|
+
if (this.#locale === "fr") return stripArrondissement(normalizeLocalityForKey(locality))
|
|
300
|
+
|
|
301
|
+
return normalizeLocalityForKeyLocale(locality, this.#locale)
|
|
254
302
|
}
|
|
255
303
|
|
|
256
304
|
/**
|
package/lib/ancestry/index.ts
CHANGED
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
* geocoding always does).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
18
19
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
19
20
|
|
|
20
|
-
import { allRows } from "#sqlite-utils"
|
|
21
|
-
|
|
22
21
|
/**
|
|
23
22
|
* WOF placetype → containment depth, coarsest = 1. Higher = finer. Placetypes we never resolve (continent, empire, …)
|
|
24
23
|
* map to 0 and sort last. NOT the same table as the FST's `PLACETYPE_ORDER` (fst-serialize.ts) — that one is a
|
package/lib/build-candidate.ts
CHANGED
|
@@ -97,9 +97,11 @@ export interface BuildCandidateOptions {
|
|
|
97
97
|
/**
|
|
98
98
|
* Optional LOCALITY extracts (`spr` rows with `placetype='locality'` + real coords, e.g. localities-nz-linz.db — the
|
|
99
99
|
* #1564 NZ suburb tier) — folded through the same extract loop as the postcode extracts, staged as `locality`
|
|
100
|
-
* candidate rows with
|
|
101
|
-
*
|
|
102
|
-
* `
|
|
100
|
+
* candidate rows with UNMEASURED population (`neg_rank 0`: a extract row ranks behind any populated namesake and wins
|
|
101
|
+
* only where its key is the answer). Each extract's `names` table folds as aliases, `is_primary = 0`, same as the
|
|
102
|
+
* delivery-city pass. An extract whose `ancestors` table names an admin region for a row gives that row the region's
|
|
103
|
+
* scope (`region_id`) plus closure rows for the region and the region's own chain above it; an extract without one
|
|
104
|
+
* stays unscoped, as before.
|
|
103
105
|
*/
|
|
104
106
|
localities?: string[]
|
|
105
107
|
/**
|
|
@@ -520,6 +522,8 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
520
522
|
}
|
|
521
523
|
|
|
522
524
|
let nLocality = 0
|
|
525
|
+
let nLocalityScoped = 0
|
|
526
|
+
let nLocalityAncestor = 0
|
|
523
527
|
|
|
524
528
|
for (const locDB of opts.localities ?? []) {
|
|
525
529
|
const folded = foldExtract({
|
|
@@ -529,14 +533,23 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
529
533
|
ccID,
|
|
530
534
|
ptID,
|
|
531
535
|
stageRow,
|
|
536
|
+
attrs,
|
|
532
537
|
progress,
|
|
533
538
|
})
|
|
534
539
|
|
|
535
540
|
nLocality += folded.primaries
|
|
541
|
+
nLocalityScoped += folded.scoped
|
|
542
|
+
nLocalityAncestor += folded.ancestorRows
|
|
536
543
|
}
|
|
537
544
|
|
|
538
545
|
if (nLocality > 0) {
|
|
539
|
-
progress(
|
|
546
|
+
progress(
|
|
547
|
+
"localities",
|
|
548
|
+
`${nLocality.toLocaleString()} extract localities folded` +
|
|
549
|
+
(nLocalityScoped
|
|
550
|
+
? `; ${nLocalityScoped.toLocaleString()} carry a region scope (${nLocalityAncestor.toLocaleString()} closure rows)`
|
|
551
|
+
: "")
|
|
552
|
+
)
|
|
540
553
|
}
|
|
541
554
|
|
|
542
555
|
// --- code dictionaries: typed batch inserts via kdb (a few hundred rows — Kysely is clean here) ---
|
|
@@ -610,8 +623,8 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
610
623
|
abbrevs: nAbbr,
|
|
611
624
|
postcodes: nPostcode,
|
|
612
625
|
postcodeAliases: nPostcodeAlias,
|
|
613
|
-
ancestorRows: sidecar.ancestorRows,
|
|
614
|
-
ancestorPlaces: sidecar.ancestorPlaces,
|
|
626
|
+
ancestorRows: sidecar.ancestorRows + nLocalityAncestor,
|
|
627
|
+
ancestorPlaces: sidecar.ancestorPlaces + nLocalityScoped,
|
|
615
628
|
intervalPlaces: sidecar.intervalPlaces,
|
|
616
629
|
...roles,
|
|
617
630
|
...(importance ? { importanceScored: importance.matched, importanceFiltered: importance.refused } : {}),
|
|
@@ -8,17 +8,51 @@
|
|
|
8
8
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
9
9
|
import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
10
10
|
|
|
11
|
+
import { CANDIDATE_ANCESTOR_COLUMNS, CANDIDATE_ANCESTOR_TABLE } from "#candidate-ancestors-schema"
|
|
11
12
|
import type { CandidateDatabase } from "#candidate-schema"
|
|
12
13
|
import type { PlaceAttrs, StageRow } from "#candidate/place-attrs"
|
|
13
14
|
import type { WOFDatabase } from "#schema"
|
|
14
15
|
import { normalizeLocalityForKey } from "#street/normalize"
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The extract's own region-tier ancestry, when it carries any: extract id → the admin region's id. A locality extract
|
|
19
|
+
* derived from a national register (the Taiwanese 鄉鎮市區 from the civil-affairs address points) knows which 縣市 each row
|
|
20
|
+
* belongs to, and writes that as an `ancestors` row against the admin region's WOF id. Without it a folded row has no
|
|
21
|
+
* region scope, so a `parentID`-scoped probe misses it and the cascade widens to whatever namesake carries the key.
|
|
22
|
+
*
|
|
23
|
+
* Only a region the admin build staged (`attrs`) counts: an id the admin does not know cannot be denormalized into a
|
|
24
|
+
* closure row, and a scope nothing can match would be a silent zero rather than an absence.
|
|
25
|
+
*/
|
|
26
|
+
function extractRegionAncestry(pc: DatabaseClient<WOFDatabase>, attrs: Map<number, PlaceAttrs>): Map<number, number> {
|
|
27
|
+
const regionOf = new Map<number, number>()
|
|
28
|
+
|
|
29
|
+
if (!tableExists(pc, "ancestors")) {
|
|
30
|
+
return regionOf
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const r of pc
|
|
34
|
+
.prepare(
|
|
35
|
+
"SELECT id, MIN(ancestor_id) AS ancestor_id FROM ancestors WHERE ancestor_placetype = 'region' GROUP BY id"
|
|
36
|
+
)
|
|
37
|
+
.iterate()) {
|
|
38
|
+
const rid = Number(r.ancestor_id)
|
|
39
|
+
|
|
40
|
+
if (attrs.has(rid)) {
|
|
41
|
+
regionOf.set(Number(r.id), rid)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return regionOf
|
|
46
|
+
}
|
|
47
|
+
|
|
16
48
|
/**
|
|
17
49
|
* Fold ONE extract (`spr` rows at `extractPlacetype` carrying real coordinates) in, then pass 4b: the alias names
|
|
18
50
|
* hanging off that same extract's `names` table.
|
|
19
51
|
*
|
|
20
52
|
* Self-contained by construction — it shares only the staging statement and the code dictionaries with the admin passes
|
|
21
|
-
* above it, and nothing downstream reads anything it produces except the
|
|
53
|
+
* above it, and nothing downstream reads anything it produces except the counters it returns. The one thing it writes
|
|
54
|
+
* beyond the staging table is a depth-1 closure row for a row whose extract names its region (see
|
|
55
|
+
* {@link extractRegionAncestry}), so the pick's stamped ancestors agree with the scope it was found under.
|
|
22
56
|
*/
|
|
23
57
|
export function foldExtract(ctx: {
|
|
24
58
|
/**
|
|
@@ -30,8 +64,13 @@ export function foldExtract(ctx: {
|
|
|
30
64
|
ccID: (code: string | null) => number
|
|
31
65
|
ptID: (pt: string | null) => number
|
|
32
66
|
stageRow: StageRow
|
|
67
|
+
/**
|
|
68
|
+
* The admin places pass 1 staged — what an extract's region ancestry is resolved against. Absent, no extract row
|
|
69
|
+
* takes a region scope (the postcode extracts, which carry none).
|
|
70
|
+
*/
|
|
71
|
+
attrs?: Map<number, PlaceAttrs>
|
|
33
72
|
progress: (phase: string, message: string) => void
|
|
34
|
-
}): { primaries: number; aliases: number } {
|
|
73
|
+
}): { primaries: number; aliases: number; scoped: number; ancestorRows: number } {
|
|
35
74
|
const { out, extractPath, extractPlacetype, ccID, ptID, stageRow, progress } = ctx
|
|
36
75
|
|
|
37
76
|
progress(extractPlacetype === "postalcode" ? "postcodes" : "localities", `reading ${extractPath}`)
|
|
@@ -41,8 +80,24 @@ export function foldExtract(ctx: {
|
|
|
41
80
|
// Per-extract, not the admin `attrs` map: pass 1 only ever sees the admin DB, so the alias pass
|
|
42
81
|
// below has nothing to join against unless this primary loop records what it staged.
|
|
43
82
|
const pcAttrs = new Map<number, PlaceAttrs>()
|
|
83
|
+
const regionOf = ctx.attrs ? extractRegionAncestry(pc, ctx.attrs) : new Map<number, number>()
|
|
84
|
+
|
|
85
|
+
const insAncestor = out.prepare(
|
|
86
|
+
`INSERT OR IGNORE INTO ${CANDIDATE_ANCESTOR_TABLE} VALUES (${CANDIDATE_ANCESTOR_COLUMNS.map(() => "?").join(", ")})`
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// The region's own closure rows, as the sidecar pass wrote them. A scoped row inherits the whole chain above its
|
|
90
|
+
// region — macroregion, country — not the region alone: the widened-scope refusal reads a pick's stamped ancestors
|
|
91
|
+
// against the parent the walk resolved, and a 縣市 that resolves to its macroregion record (`桃園市`, which WOF names
|
|
92
|
+
// only at that tier) would otherwise contradict a lineage that stops at the region.
|
|
93
|
+
const regionChain = out.prepare(
|
|
94
|
+
`SELECT depth, parent_spr_id, parent_placetype_id, parent_name, parent_name_key FROM ${CANDIDATE_ANCESTOR_TABLE} WHERE spr_id = ? ORDER BY depth`
|
|
95
|
+
)
|
|
96
|
+
|
|
44
97
|
let primaries = 0
|
|
45
98
|
let aliases = 0
|
|
99
|
+
let scoped = 0
|
|
100
|
+
let ancestorRows = 0
|
|
46
101
|
|
|
47
102
|
out.exec("BEGIN")
|
|
48
103
|
|
|
@@ -60,12 +115,15 @@ export function foldExtract(ctx: {
|
|
|
60
115
|
|
|
61
116
|
const lat = r.latitude as number
|
|
62
117
|
const lon = r.longitude as number
|
|
118
|
+
const id = Number(r.id)
|
|
119
|
+
const rid = regionOf.get(id) ?? 0
|
|
63
120
|
|
|
64
|
-
// region_id 0 (a postcode is unique by
|
|
65
|
-
// (no population). bbox = the
|
|
121
|
+
// region_id from the extract's own ancestry when it names one, else 0 (a postcode is unique by
|
|
122
|
+
// name+country — no same-name disambiguation); neg_rank 0 (no population). bbox = the row's own
|
|
123
|
+
// min/max (falls back to the centroid point).
|
|
66
124
|
const a: PlaceAttrs = {
|
|
67
125
|
cid: ccID(r.country as string | null),
|
|
68
|
-
rid
|
|
126
|
+
rid,
|
|
69
127
|
ptid: pcPtid,
|
|
70
128
|
name,
|
|
71
129
|
lat,
|
|
@@ -83,8 +141,31 @@ export function foldExtract(ctx: {
|
|
|
83
141
|
imp: null,
|
|
84
142
|
}
|
|
85
143
|
|
|
86
|
-
pcAttrs.set(
|
|
87
|
-
stageRow(key, a,
|
|
144
|
+
pcAttrs.set(id, a)
|
|
145
|
+
stageRow(key, a, id, 1)
|
|
146
|
+
|
|
147
|
+
if (rid !== 0) {
|
|
148
|
+
const region = ctx.attrs!.get(rid)!
|
|
149
|
+
|
|
150
|
+
insAncestor.run(id, 1, rid, region.ptid, region.name, region.pkey)
|
|
151
|
+
|
|
152
|
+
scoped++
|
|
153
|
+
|
|
154
|
+
ancestorRows++
|
|
155
|
+
|
|
156
|
+
for (const link of regionChain.iterate(rid)) {
|
|
157
|
+
insAncestor.run(
|
|
158
|
+
id,
|
|
159
|
+
Number(link.depth) + 1,
|
|
160
|
+
Number(link.parent_spr_id),
|
|
161
|
+
Number(link.parent_placetype_id),
|
|
162
|
+
String(link.parent_name ?? ""),
|
|
163
|
+
String(link.parent_name_key ?? "")
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
ancestorRows++
|
|
167
|
+
}
|
|
168
|
+
}
|
|
88
169
|
|
|
89
170
|
primaries++
|
|
90
171
|
}
|
|
@@ -134,5 +215,5 @@ export function foldExtract(ctx: {
|
|
|
134
215
|
progress("postcode-aliases", `${extractPath} has no \`names\` table — no delivery-city aliases to fold`)
|
|
135
216
|
}
|
|
136
217
|
|
|
137
|
-
return { primaries, aliases }
|
|
218
|
+
return { primaries, aliases, scoped, ancestorRows }
|
|
138
219
|
}
|
|
@@ -127,6 +127,7 @@ export function romanizeNameKey(key: string): string | null {
|
|
|
127
127
|
* Edit similarity between a holder's primary name key and one of its alias keys, both romanized and
|
|
128
128
|
* abbreviation-expanded. `null` when either side's script is uncovered.
|
|
129
129
|
*/
|
|
130
|
+
// repo-health-ignore export-name-affix -- romanizes and expands abbreviations first; `nameSimilarity` compares as given.
|
|
130
131
|
export function ownNameSimilarity(primaryKey: string, aliasKey: string): number | null {
|
|
131
132
|
const primary = romanizeNameKey(primaryKey)
|
|
132
133
|
const alias = romanizeNameKey(aliasKey)
|
package/lib/candidate-lookup.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
6
|
* Node-side {@link PlaceLookup} over the byte-range CANDIDATE table (`build-candidate.ts`) — the
|
|
7
|
-
* SAME gazetteer the browser
|
|
8
|
-
* `
|
|
9
|
-
* instead of sql.js-httpvfs. This is what makes the server/CLI resolver match the
|
|
10
|
-
* lookup surface, one artifact, one ranking.
|
|
7
|
+
* SAME gazetteer the browser runtime resolves against ({@link WOFCandidateTableLookup} in
|
|
8
|
+
* `packages/resolver-wof-wasm/lib/httpvfs/resolver.ts`), but reading a LOCAL `candidate.db` via
|
|
9
|
+
* `node:sqlite` instead of sql.js-httpvfs. This is what makes the server/CLI resolver match the
|
|
10
|
+
* browser: one lookup surface, one artifact, one ranking.
|
|
11
11
|
*
|
|
12
12
|
* The query is a single contiguous probe on the `WITHOUT ROWID` B-tree keyed `(name_key,
|
|
13
13
|
* country_id, region_id, placetype_id, neg_rank, spr_id)`. `name_key` is the SHARED
|
|
@@ -23,13 +23,15 @@
|
|
|
23
23
|
* `bbox` field on {@link FindPlaceQuery}).
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { jaroWinkler, levenshteinSimilarity } from "@mailwoman/match/comparators"
|
|
27
26
|
import {
|
|
28
27
|
expandPlacetypeFilter,
|
|
29
|
-
partitionByContainment,
|
|
30
28
|
type Ancestor,
|
|
31
29
|
type GazetteerArtifactCoverage,
|
|
32
|
-
|
|
30
|
+
referentialFromPopulation,
|
|
31
|
+
} from "@mailwoman/core/resolver"
|
|
32
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
33
|
+
import { jaroWinkler, levenshteinSimilarity } from "@mailwoman/match/comparators"
|
|
34
|
+
import { partitionByContainment } from "@mailwoman/resolver"
|
|
33
35
|
import { haversineKm } from "@mailwoman/spatial"
|
|
34
36
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
35
37
|
import type { PathBuilderLike } from "path-ts"
|
|
@@ -44,12 +46,11 @@ import {
|
|
|
44
46
|
import { CANDIDATE_FTS_TABLE } from "#candidate-fts"
|
|
45
47
|
import type { CandidateDatabase, CandidateTable, CountryCodeTable, PlacetypeCodeTable } from "#candidate-schema"
|
|
46
48
|
import { readGazetteerCoverageManifest } from "#coverage-manifest-schema"
|
|
47
|
-
import { referentialFromPopulation } from "#place-importance-schema"
|
|
48
49
|
import { POSTAL_CITY_CANDIDATE_TABLE, type PostalCityCandidateTable } from "#postal/city-candidate-schema"
|
|
49
50
|
import { rankByPrimaryPreference, type RankedRow, RERANK_FETCH } from "#primary-preference"
|
|
50
51
|
import { applyProximityRerank } from "#proximity-rerank"
|
|
51
52
|
import { REGION_CLASS_PLACETYPES, regionQualifierProbeKeys } from "#region-keys"
|
|
52
|
-
import {
|
|
53
|
+
import { hasColumn, hasTable } from "#sqlite-utils"
|
|
53
54
|
import { type NameKey, normalizeLocalityForKey, stripLocalityQualifier } from "#street/normalize"
|
|
54
55
|
import type { FindPlaceQuery, PlaceCandidate, PlaceLookup, WOFPlacetype } from "#types"
|
|
55
56
|
|
package/lib/candidate-schema.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Typed schema for the byte-range CANDIDATE gazetteer (`candidate.db`) — the single source of truth
|
|
7
7
|
* for the columns shared by the BUILDER ({@link buildCandidateTable}) and the READERS (the Node
|
|
8
|
-
* {@link WOFCandidateTableLookup} + the browser
|
|
8
|
+
* {@link WOFCandidateTableLookup} + the browser `@mailwoman/resolver-wof-wasm/httpvfs/resolver`). Before this module each
|
|
9
9
|
* side hand-wrote the column list; a rename in one place broke the other at runtime. Now the
|
|
10
10
|
* contract is a Kysely `Database` interface (`new DatabaseClient<CandidateDatabase>(...)` for
|
|
11
11
|
* typed inserts) plus the table DDL as strings — so a column change is a compile error on every
|
package/lib/candidate-scoring.ts
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* ordered — the weighted-sum score and the exact-match tiering that ranks over it.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { compareReferential, referentialFromPopulation } from "@mailwoman/core/resolver"
|
|
10
11
|
import { haversineKm } from "@mailwoman/spatial"
|
|
11
12
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
12
13
|
|
|
13
14
|
import { exactMatchIDs, officialNameIDs } from "#exact-match"
|
|
14
15
|
import { foldQueryText } from "#fts/index"
|
|
15
|
-
import { compareReferential, referentialFromPopulation } from "#place-importance-schema"
|
|
16
16
|
import { populationBoostTerm, type RankingWeights } from "#ranking-weights"
|
|
17
17
|
import type { RawSearchRow } from "#search-fetch"
|
|
18
18
|
import type { FindPlaceQuery, PlaceCandidate, WOFPlacetype } from "#types"
|
package/lib/coincident-roles.ts
CHANGED
|
@@ -38,14 +38,13 @@
|
|
|
38
38
|
* be wired as a post-step of the main `scripts/build-unified-wof.ts`.
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
import type { CoincidentLocality } from "@mailwoman/resolver"
|
|
41
|
+
import type { CoincidentLocality } from "@mailwoman/core/resolver"
|
|
42
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
42
43
|
import { haversineKm } from "@mailwoman/spatial"
|
|
43
44
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
44
45
|
import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
45
46
|
|
|
46
47
|
import type { WOFDatabase } from "#schema"
|
|
47
|
-
import { allRows } from "#sqlite-utils"
|
|
48
|
-
|
|
49
48
|
/**
|
|
50
49
|
* Table of places that hold more than one admin role — a locality that is also its county seat. Written by the
|
|
51
50
|
* gazetteer build, read by the resolver when a coincident locality has to be chosen.
|
|
@@ -29,10 +29,11 @@ import {
|
|
|
29
29
|
type CountryCoverageFact,
|
|
30
30
|
type GazetteerArtifactCoverage,
|
|
31
31
|
} from "@mailwoman/core/resolver"
|
|
32
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
32
33
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
33
34
|
import { sql, type Kysely } from "kysely"
|
|
34
35
|
|
|
35
|
-
import {
|
|
36
|
+
import { hasTable } from "#sqlite-utils"
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* One country's hard-filter coverage measurement — the storage form of {@link CountryCoverageFact}.
|
package/lib/fst/builder.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* PlaceEntry at terminals → return FSTMatcher.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { allRows, getRow } from "@mailwoman/core/utils"
|
|
14
15
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
15
16
|
import { resolvePath } from "path-ts"
|
|
16
17
|
|
|
@@ -20,7 +21,6 @@ import { FSTMatcher, normalizeTokens } from "#fst/matcher"
|
|
|
20
21
|
import type { BuildFSTOpts, BuildFSTResult, FSTProvenance, PlaceEntry, PlacetypeID } from "#fst/types"
|
|
21
22
|
import { loadImportanceSplit } from "#place-importance-schema"
|
|
22
23
|
import type { WOFDatabase } from "#schema"
|
|
23
|
-
import { allRows, getRow } from "#sqlite-utils"
|
|
24
24
|
|
|
25
25
|
const DEFAULT_PLACETYPES: PlacetypeID[] = [
|
|
26
26
|
"country",
|
package/lib/fst/freshness.ts
CHANGED
|
@@ -146,7 +146,6 @@ export async function peekFSTStampFields(path: string): Promise<FSTStampFields |
|
|
|
146
146
|
* Re-exported from `@mailwoman/core/hash`. This package's own test imports it from here, and the sidecar convention
|
|
147
147
|
* below is what it is for.
|
|
148
148
|
*/
|
|
149
|
-
export { md5File } from "@mailwoman/core/hash"
|
|
150
149
|
|
|
151
150
|
/**
|
|
152
151
|
* The source identity an FST build should stamp, or a check should compare against.
|
package/lib/interpolation.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { parseJSONStrict } from "@mailwoman/core/json"
|
|
30
|
-
import type { InterpolationLookup } from "@mailwoman/resolver"
|
|
30
|
+
import type { InterpolationLookup } from "@mailwoman/core/resolver"
|
|
31
31
|
import { clampFraction, haversineKm, pointAlong } from "@mailwoman/spatial"
|
|
32
32
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
33
33
|
|
package/lib/lookup.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* See `docs/plan/phases/PHASE_4_2_wof_sqlite.md` for the design rationale.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { expandPlacetypeFilter, type Ancestor, type CoincidentLocality } from "@mailwoman/resolver"
|
|
13
|
+
import { expandPlacetypeFilter, type Ancestor, type CoincidentLocality } from "@mailwoman/core/resolver"
|
|
14
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
14
15
|
import { haversineKm } from "@mailwoman/spatial"
|
|
15
16
|
import type { SQLInputValue } from "@mailwoman/sqlite/client"
|
|
16
17
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
@@ -51,7 +52,6 @@ import { DEFAULT_WEIGHTS, populationScaleTerm, type RankingWeights } from "#rank
|
|
|
51
52
|
import type { WOFDatabase } from "#schema"
|
|
52
53
|
import { fetchSearchRows, type RawSearchRow } from "#search-fetch"
|
|
53
54
|
import { SqliteConventionSource } from "#sqlite-convention-source"
|
|
54
|
-
import { allRows } from "#sqlite-utils"
|
|
55
55
|
import type { FindPlaceQuery, PlaceCandidate, PlaceLookup, WOFPlacetype } from "#types"
|
|
56
56
|
|
|
57
57
|
export interface WOFSQLitePlaceLookupOpts {
|
|
@@ -32,11 +32,10 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import { referentialFromPopulation } from "@mailwoman/core/resolver"
|
|
35
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
35
36
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
36
37
|
import type { Kysely } from "kysely"
|
|
37
38
|
|
|
38
|
-
import { allRows } from "#sqlite-utils"
|
|
39
|
-
|
|
40
39
|
//#region Schema
|
|
41
40
|
|
|
42
41
|
/**
|
|
@@ -106,13 +105,6 @@ export async function createPlaceImportanceTable(db: Kysely<PlaceImportanceDatab
|
|
|
106
105
|
* (backend-agnostic — it cannot import this package) reads the same number. Re-exported HERE so the schema module stays
|
|
107
106
|
* the one-stop read for the table: the column and the function that fills it are one hop apart.
|
|
108
107
|
*/
|
|
109
|
-
export {
|
|
110
|
-
compareReferential,
|
|
111
|
-
REFERENTIAL_LOG2_SCALE,
|
|
112
|
-
REFERENTIAL_POPULATION_DIVISOR,
|
|
113
|
-
REFERENTIAL_SATURATION_POPULATION,
|
|
114
|
-
referentialFromPopulation,
|
|
115
|
-
} from "@mailwoman/core/resolver"
|
|
116
108
|
|
|
117
109
|
//#endregion
|
|
118
110
|
|
package/lib/poi/lookup.ts
CHANGED
|
@@ -25,13 +25,12 @@
|
|
|
25
25
|
* it).
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
28
29
|
import { haversineKm, shortCellToInt, type H3Cell } from "@mailwoman/spatial"
|
|
29
30
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
30
31
|
import { gridDisk, latLngToCell } from "h3-js"
|
|
31
32
|
|
|
32
33
|
import type { POICategoryCodeTable, POIDatabase, POITable } from "#poi/schema"
|
|
33
|
-
import { allRows } from "#sqlite-utils"
|
|
34
|
-
|
|
35
34
|
/**
|
|
36
35
|
* Resolution the `poi` table's `h3_cell` column is keyed at — matches the builder (spec §3.4).
|
|
37
36
|
*/
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Bounded cross-country PRIMARY-NAME preference over a population-ordered candidate row set — the
|
|
7
7
|
* `is_primary` ranking signal, PURE and platform-free so the Node candidate lookup
|
|
8
|
-
* (`candidate-lookup.ts`) and the browser twin (`
|
|
9
|
-
* SAME function rather than two copies that drift (the #861 server↔
|
|
8
|
+
* (`candidate-lookup.ts`) and the browser twin (`packages/resolver-wof-wasm/lib/httpvfs/resolver.ts`) rank with
|
|
9
|
+
* the SAME function rather than two copies that drift (the #861 server↔browser parity contract).
|
|
10
10
|
*
|
|
11
11
|
* Only type imports and arithmetic live here: anything with a `node:` import stays out.
|
|
12
12
|
*/
|
|
@@ -207,17 +207,27 @@ export function rankByPrimaryPreference<R extends PrimaryPreferenceRow>(
|
|
|
207
207
|
? 1
|
|
208
208
|
: 0
|
|
209
209
|
|
|
210
|
+
// 1 for a PRIMARY-name row whose prominence is unmeasured (population 0), 0 for everything else. Two unmeasured
|
|
211
|
+
// rows tie on `neg_rank`, and that tie used to fall through to scan order — the B-tree's `spr_id`, which is no
|
|
212
|
+
// evidence about either place. A row that IS named X ranks ahead of one merely also-known-as X when nothing else
|
|
213
|
+
// separates them: Taiwan's 溪州鄉 township row (its own name, unmeasured) over the village 溪洲 30 km away that
|
|
214
|
+
// carries `溪州鄉` as an alias (unmeasured), which the lower WOF id had been winning. Populated ties are untouched —
|
|
215
|
+
// a populated alias ("NYC") never reaches this term, and two populated rows keep the order they had.
|
|
216
|
+
const unmeasuredPrimary = (r: R): number =>
|
|
217
|
+
r.is_primary === 1 && typeof r.population === "number" && r.population === 0 ? 1 : 0
|
|
218
|
+
|
|
210
219
|
return (
|
|
211
220
|
rows
|
|
212
221
|
.map((r, i) => ({ row: annotate(r), i }))
|
|
213
|
-
// Effective rank ASC; ties keep population order, then the seat preference DESC, then
|
|
214
|
-
// index (stable).
|
|
222
|
+
// Effective rank ASC; ties keep population order, then the seat preference DESC, then the primary name
|
|
223
|
+
// among unmeasured rows, then original index (stable).
|
|
215
224
|
// oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array; toSorted would double-allocate on a hot path
|
|
216
225
|
.sort(
|
|
217
226
|
(a, b) =>
|
|
218
227
|
a.row.effectiveNegRank - b.row.effectiveNegRank ||
|
|
219
228
|
a.row.neg_rank - b.row.neg_rank ||
|
|
220
229
|
seatPreference(b.row) - seatPreference(a.row) ||
|
|
230
|
+
unmeasuredPrimary(b.row) - unmeasuredPrimary(a.row) ||
|
|
221
231
|
a.i - b.i
|
|
222
232
|
)
|
|
223
233
|
.slice(0, limit)
|
package/lib/reverse.ts
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { tryParsingJSON } from "@mailwoman/core/json"
|
|
32
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
32
33
|
import { bboxAround, geometryContains, haversineKm, type ParsedGeometry } from "@mailwoman/spatial"
|
|
33
34
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
34
35
|
import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
@@ -36,9 +37,7 @@ import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
|
36
37
|
import { ancestorLineage, placetypeDepth } from "#ancestry/index"
|
|
37
38
|
import { PLACE_BBOX_TABLE } from "#fts/index"
|
|
38
39
|
import type { WOFDatabase } from "#schema"
|
|
39
|
-
import { allRows } from "#sqlite-utils"
|
|
40
40
|
import type { PlaceCandidate, WOFPlacetype } from "#types"
|
|
41
|
-
|
|
42
41
|
/**
|
|
43
42
|
* Largest absolute latitude in WGS-84 degrees.
|
|
44
43
|
*/
|