@mailwoman/resolver-wof-sqlite 10.7.0 → 10.8.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 +4 -4
- package/lib/address/point/spine-schema.ts +59 -0
- package/lib/ancestry/backfill.ts +3 -25
- package/lib/build/candidate.ts +22 -5
- package/lib/candidate/alias-bags.ts +32 -14
- package/lib/candidate/bare-admin-names.ts +54 -0
- package/lib/candidate/importance.ts +2 -2
- package/lib/candidate/lookup.ts +54 -26
- package/lib/candidate/own-name.ts +1 -5
- package/lib/candidate/scoring.ts +4 -4
- package/lib/capitals.ts +2 -2
- package/lib/coincident-roles.ts +3 -3
- package/lib/currency-backfill.ts +4 -4
- package/lib/fst/autocomplete.ts +1 -1
- package/lib/fst/binary-matcher.ts +379 -0
- package/lib/fst/builder.ts +1 -1
- package/lib/fst/deserialize-web.ts +14 -143
- package/lib/fst/matcher.ts +3 -3
- package/lib/fst/serialize.ts +1 -1
- package/lib/fst.ts +1 -0
- package/lib/geonames/aliases.ts +2 -2
- package/lib/interpolation.ts +3 -2
- package/lib/lookup.ts +6 -6
- package/lib/nsul/lookup.ts +2 -1
- package/lib/nsul/schema.ts +2 -8
- package/lib/place-importance-schema.ts +1 -1
- package/lib/poi/lookup.ts +14 -14
- package/lib/proximity-rerank.ts +2 -2
- package/lib/reverse.ts +8 -5
- package/lib/search-fetch.ts +3 -3
- package/lib/sqlite/convention-source.ts +1 -1
- package/lib/street/centroid.ts +2 -2
- package/lib/street/morphology/fst/builder.ts +3 -3
- package/lib/street/morphology/fst/loader.ts +3 -3
- package/lib/street/normalize.ts +59 -1
- package/lib/types.ts +8 -0
- package/lib/uprn/existence.ts +0 -1
- package/lib/uprn/lookup.ts +5 -5
- package/lib/uprn/schema.ts +3 -4
- package/lib/weights-overlay-linker.ts +4 -4
- package/out/address/point/interpolation.js +4 -4
- package/out/address/point/interpolation.js.map +1 -1
- package/out/address/point/spine-schema.d.ts +40 -0
- package/out/address/point/spine-schema.d.ts.map +1 -0
- package/out/address/point/spine-schema.js +40 -0
- package/out/address/point/spine-schema.js.map +1 -0
- package/out/ancestry/backfill.d.ts.map +1 -1
- package/out/ancestry/backfill.js +3 -22
- package/out/ancestry/backfill.js.map +1 -1
- package/out/build/candidate.d.ts.map +1 -1
- package/out/build/candidate.js +17 -5
- package/out/build/candidate.js.map +1 -1
- package/out/candidate/alias-bags.d.ts +8 -0
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +20 -11
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/candidate/bare-admin-names.d.ts +26 -0
- package/out/candidate/bare-admin-names.d.ts.map +1 -0
- package/out/candidate/bare-admin-names.js +38 -0
- package/out/candidate/bare-admin-names.js.map +1 -0
- package/out/candidate/importance.js +2 -2
- package/out/candidate/importance.js.map +1 -1
- package/out/candidate/lookup.d.ts.map +1 -1
- package/out/candidate/lookup.js +35 -19
- package/out/candidate/lookup.js.map +1 -1
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +1 -5
- package/out/candidate/own-name.js.map +1 -1
- package/out/candidate/scoring.js +4 -4
- package/out/candidate/scoring.js.map +1 -1
- package/out/capitals.js +2 -2
- package/out/capitals.js.map +1 -1
- package/out/coincident-roles.js +3 -3
- package/out/coincident-roles.js.map +1 -1
- package/out/currency-backfill.js +4 -4
- package/out/currency-backfill.js.map +1 -1
- package/out/env.d.ts +3 -0
- package/out/env.d.ts.map +1 -1
- package/out/fst/autocomplete.js +1 -1
- package/out/fst/autocomplete.js.map +1 -1
- package/out/fst/binary-matcher.d.ts +34 -0
- package/out/fst/binary-matcher.d.ts.map +1 -0
- package/out/fst/binary-matcher.js +294 -0
- package/out/fst/binary-matcher.js.map +1 -0
- package/out/fst/builder.js +1 -1
- package/out/fst/builder.js.map +1 -1
- package/out/fst/deserialize-web.d.ts +11 -6
- package/out/fst/deserialize-web.d.ts.map +1 -1
- package/out/fst/deserialize-web.js +12 -94
- package/out/fst/deserialize-web.js.map +1 -1
- package/out/fst/matcher.js +3 -3
- package/out/fst/matcher.js.map +1 -1
- package/out/fst/serialize.js +1 -1
- package/out/fst/serialize.js.map +1 -1
- package/out/fst.d.ts +1 -0
- package/out/fst.d.ts.map +1 -1
- package/out/fst.js +1 -0
- package/out/fst.js.map +1 -1
- package/out/geonames/aliases.js +2 -2
- package/out/geonames/aliases.js.map +1 -1
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +3 -2
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +2 -2
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +4 -4
- package/out/lookup.js.map +1 -1
- package/out/nsul/lookup.d.ts +1 -1
- package/out/nsul/lookup.d.ts.map +1 -1
- package/out/nsul/lookup.js +1 -1
- package/out/nsul/lookup.js.map +1 -1
- package/out/nsul/schema.d.ts +2 -5
- package/out/nsul/schema.d.ts.map +1 -1
- package/out/nsul/schema.js +2 -7
- package/out/nsul/schema.js.map +1 -1
- package/out/place-importance-schema.js +1 -1
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts +3 -3
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +9 -9
- package/out/poi/lookup.js.map +1 -1
- package/out/proximity-rerank.js +2 -2
- package/out/proximity-rerank.js.map +1 -1
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +7 -5
- package/out/reverse.js.map +1 -1
- package/out/search-fetch.js +3 -3
- package/out/search-fetch.js.map +1 -1
- package/out/sqlite/convention-source.js +1 -1
- package/out/sqlite/convention-source.js.map +1 -1
- package/out/street/centroid.js +2 -2
- package/out/street/centroid.js.map +1 -1
- package/out/street/morphology/fst/builder.d.ts +2 -2
- package/out/street/morphology/fst/builder.js +2 -2
- package/out/street/morphology/fst/builder.js.map +1 -1
- package/out/street/morphology/fst/loader.d.ts +2 -2
- package/out/street/morphology/fst/loader.js +2 -2
- package/out/street/morphology/fst/loader.js.map +1 -1
- package/out/street/normalize.d.ts +16 -0
- package/out/street/normalize.d.ts.map +1 -1
- package/out/street/normalize.js +51 -1
- package/out/street/normalize.js.map +1 -1
- package/out/types.d.ts +7 -0
- package/out/types.d.ts.map +1 -1
- package/out/uprn/existence.d.ts.map +1 -1
- package/out/uprn/existence.js.map +1 -1
- package/out/uprn/lookup.d.ts.map +1 -1
- package/out/uprn/lookup.js +3 -3
- package/out/uprn/lookup.js.map +1 -1
- package/out/uprn/schema.d.ts +1 -1
- package/out/uprn/schema.d.ts.map +1 -1
- package/out/uprn/schema.js +1 -1
- package/out/uprn/schema.js.map +1 -1
- package/out/weights-overlay-linker.js +4 -4
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +16 -13
package/lib/nsul/lookup.ts
CHANGED
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import type { UkCountryCode } from "@mailwoman/codex/gb/country"
|
|
28
|
+
import { compactPostcode } from "@mailwoman/postcode/display-form"
|
|
28
29
|
import { SQLiteLookup, type SQLiteLookupOptions } from "@mailwoman/sqlite/lookup"
|
|
29
30
|
|
|
30
|
-
import {
|
|
31
|
+
import type { NSULDatabase } from "#nsul/schema"
|
|
31
32
|
import { prepareAll, prepareGet } from "#sqlite-utils"
|
|
32
33
|
|
|
33
34
|
/**
|
package/lib/nsul/schema.ts
CHANGED
|
@@ -70,13 +70,6 @@ export interface NSULDatabase extends layerschemadatabase {
|
|
|
70
70
|
nsul_meta: NSULMetaTable
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
/**
|
|
74
|
-
* The compact form of a unit postcode, every space removed and upper-cased.
|
|
75
|
-
*/
|
|
76
|
-
export function compactPostcode(pcds: string): string {
|
|
77
|
-
return pcds.replaceAll(/\s+/g, "").toUpperCase()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
73
|
export async function createUPRNPostcodeTable(db: Kysely<NSULDatabase>): Promise<void> {
|
|
81
74
|
await db.schema
|
|
82
75
|
.createTable("uprn_postcode")
|
|
@@ -102,7 +95,8 @@ export async function createNSULMetaTable(db: Kysely<NSULDatabase>): Promise<voi
|
|
|
102
95
|
/**
|
|
103
96
|
* The `pcds_compact` index the `uprnsForPostcode` probe reads.
|
|
104
97
|
*
|
|
105
|
-
* There is no index on the spaced `pcds` because
|
|
98
|
+
* There is no index on the spaced `pcds` because `compactPostcode` from
|
|
99
|
+
* `@mailwoman/postcode/display-form` derives the compact column from it.
|
|
106
100
|
*/
|
|
107
101
|
export async function createNSULIndexes(db: Kysely<NSULDatabase>): Promise<void> {
|
|
108
102
|
await db.schema.createIndex("uprn_postcode_pcds_compact").on("uprn_postcode").column("pcds_compact").execute()
|
|
@@ -262,7 +262,7 @@ export function loadImportanceSplit<DB>(db: DatabaseClient<DB>): ImportanceSplit
|
|
|
262
262
|
for (const row of rows) {
|
|
263
263
|
const split = splitLegacyImportance(row.importance, population.get(row.id))
|
|
264
264
|
|
|
265
|
-
if (split.encyclopedic
|
|
265
|
+
if (!split.encyclopedic) {
|
|
266
266
|
legacyFallbackRows++
|
|
267
267
|
} else {
|
|
268
268
|
encyclopedic.set(row.id, split.encyclopedic)
|
package/lib/poi/lookup.ts
CHANGED
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
* `without rowid` B-tree `poi-schema.ts` builds. Category, brand and name searches share one
|
|
8
8
|
* artifact. Each path has a method that returns its details.
|
|
9
9
|
*
|
|
10
|
-
* `latLngToCell` and `gridDisk` come from
|
|
10
|
+
* `latLngToCell` and `gridDisk` come from `@sister.software/h3`. The 48-bit short-cell packing that turns a raw
|
|
11
11
|
* H3 cell into the integer `poi.h3_cell` stores is `@mailwoman/spatial`'s `shortCellToInt`, and
|
|
12
12
|
* that math is never reimplemented here.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { allRows } from "@mailwoman/core/utils"
|
|
16
|
-
import {
|
|
16
|
+
import { haversine, shortCellToInt } from "@mailwoman/spatial"
|
|
17
17
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
18
18
|
import { SQLiteLookup, type SQLiteLookupOptions } from "@mailwoman/sqlite/lookup"
|
|
19
|
-
import { gridDisk, latLngToCell } from "h3
|
|
19
|
+
import { gridDisk, latLngToCell } from "@sister.software/h3"
|
|
20
20
|
|
|
21
21
|
import type { POICategoryCodeTable, POIDatabase, POITable } from "#poi/schema"
|
|
22
22
|
/**
|
|
@@ -79,7 +79,7 @@ export interface POISearchQuery {
|
|
|
79
79
|
*
|
|
80
80
|
* Required for category/brand queries (k-ring expansion).
|
|
81
81
|
*/
|
|
82
|
-
center?: { latitude: number; longitude: number }
|
|
82
|
+
center?: { latitude: number; longitude: number } | null
|
|
83
83
|
/**
|
|
84
84
|
* Ring budget: the count of res-9 k-rings to expand before giving up.
|
|
85
85
|
*
|
|
@@ -88,7 +88,7 @@ export interface POISearchQuery {
|
|
|
88
88
|
* @defaultValue `16` ≈ ~5.4 km
|
|
89
89
|
*/
|
|
90
90
|
maxRings?: number
|
|
91
|
-
limit?: number
|
|
91
|
+
limit?: number | null
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export interface POISearchHit {
|
|
@@ -183,7 +183,7 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
|
|
|
183
183
|
const limit = Math.max(1, query.limit ?? DEFAULT_LIMIT)
|
|
184
184
|
|
|
185
185
|
if (query.name) {
|
|
186
|
-
return this.#searchByName(query.name, limit, query.center)
|
|
186
|
+
return this.#searchByName(query.name, limit, query.center ?? undefined)
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
if (query.categoryIDs?.length || query.brandWikidata) {
|
|
@@ -216,7 +216,7 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
|
|
|
216
216
|
|
|
217
217
|
return sortByDistance(rows, center)
|
|
218
218
|
.filter(
|
|
219
|
-
(row) =>
|
|
219
|
+
(row) => haversine(center.latitude, center.longitude, row.latitude, row.longitude) <= BRAND_MAX_DISTANCE_KM
|
|
220
220
|
)
|
|
221
221
|
.slice(0, limit)
|
|
222
222
|
.map((row) => toHit(row, this.#idToCategory, center))
|
|
@@ -235,7 +235,7 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
|
|
|
235
235
|
for (const id of query.categoryIDs ?? []) {
|
|
236
236
|
const resolved = this.#categoryToID.get(id)
|
|
237
237
|
|
|
238
|
-
if (resolved
|
|
238
|
+
if (resolved) {
|
|
239
239
|
categoryIDs.push(resolved)
|
|
240
240
|
}
|
|
241
241
|
}
|
|
@@ -244,7 +244,7 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
|
|
|
244
244
|
// This is a clean miss instead of a throw.
|
|
245
245
|
if (!categoryIDs.length) return []
|
|
246
246
|
|
|
247
|
-
const origin = latLngToCell(center.latitude, center.longitude, POI_H3_RESOLUTION)
|
|
247
|
+
const origin = latLngToCell(center.latitude, center.longitude, POI_H3_RESOLUTION)
|
|
248
248
|
const seenCells = new Set<string>()
|
|
249
249
|
let rows: POIRow[] = []
|
|
250
250
|
|
|
@@ -252,12 +252,12 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
|
|
|
252
252
|
for (let ring = 0; ring < maxRings; ring++) {
|
|
253
253
|
// gridDisk(origin, ring) returns the whole disk out to `ring`, so diffing against
|
|
254
254
|
// the already-probed set derives only this ring's new cells.
|
|
255
|
-
const diskCells = gridDisk(origin, ring)
|
|
255
|
+
const diskCells = gridDisk(origin, ring)
|
|
256
256
|
const newCells = diskCells.filter((cell) => !seenCells.has(cell))
|
|
257
257
|
|
|
258
258
|
for (const cell of newCells) {
|
|
259
259
|
seenCells.add(cell)
|
|
260
|
-
const shortCell = shortCellToInt(cell
|
|
260
|
+
const shortCell = shortCellToInt(cell)
|
|
261
261
|
|
|
262
262
|
for (const categoryID of categoryIDs) {
|
|
263
263
|
rows.push(...allRows<POIRow>(this.#categoryCellProbe, shortCell, categoryID, limit))
|
|
@@ -344,8 +344,8 @@ export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLooku
|
|
|
344
344
|
function sortByDistance(rows: POIRow[], center: { latitude: number; longitude: number }): POIRow[] {
|
|
345
345
|
return [...rows].toSorted(
|
|
346
346
|
(a, b) =>
|
|
347
|
-
|
|
348
|
-
|
|
347
|
+
haversine(center.latitude, center.longitude, a.latitude, a.longitude) -
|
|
348
|
+
haversine(center.latitude, center.longitude, b.latitude, b.longitude)
|
|
349
349
|
)
|
|
350
350
|
}
|
|
351
351
|
|
|
@@ -363,7 +363,7 @@ function toHit(
|
|
|
363
363
|
country: row.country,
|
|
364
364
|
confidence: row.confidence,
|
|
365
365
|
gersID: row.gers_id,
|
|
366
|
-
distanceM: center ?
|
|
366
|
+
distanceM: center ? haversine(center.latitude, center.longitude, row.latitude, row.longitude) * 1000 : null,
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|
package/lib/proximity-rerank.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* ordering silently discarded downstream.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { haversine } from "@mailwoman/spatial/geometries/point/distance"
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Full magnitude of the nearness term at distance 0, before decay.
|
|
@@ -92,7 +92,7 @@ export function combinedProminence(candidate: ProximityRerankable, bias: readonl
|
|
|
92
92
|
// A 0,0 pair interpreted as a real coordinate would receive an enormous score.
|
|
93
93
|
if (!(candidate.lat === 0 && candidate.lon === 0)) {
|
|
94
94
|
for (const b of bias) {
|
|
95
|
-
const d =
|
|
95
|
+
const d = haversine(b.lat, b.lon, candidate.lat, candidate.lon)
|
|
96
96
|
const term = (BIAS_BOOST * (b.weight ?? 1)) / (1 + d / PROX_SCALE_KM)
|
|
97
97
|
|
|
98
98
|
if (term > proxTerm) {
|
package/lib/reverse.ts
CHANGED
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
|
|
25
25
|
import { tryParsingJSON } from "@mailwoman/core/json"
|
|
26
26
|
import { allRows } from "@mailwoman/core/utils"
|
|
27
|
-
import {
|
|
27
|
+
import type { ParsedGeometry } from "@mailwoman/spatial"
|
|
28
|
+
import { bboxAround } from "@mailwoman/spatial/bbox"
|
|
29
|
+
import { haversine } from "@mailwoman/spatial/geometries/point/distance"
|
|
30
|
+
import { geometryContains } from "@mailwoman/spatial/geometries/polygon"
|
|
28
31
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
29
32
|
import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
30
33
|
import type { PathBuilderLike } from "path-ts"
|
|
@@ -311,7 +314,7 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
311
314
|
let bestKm = Infinity
|
|
312
315
|
|
|
313
316
|
for (const c of pointOnly) {
|
|
314
|
-
const km =
|
|
317
|
+
const km = haversine(lat, lon, c.lat, c.lon)
|
|
315
318
|
|
|
316
319
|
if (km < bestKm) {
|
|
317
320
|
bestKm = km
|
|
@@ -325,7 +328,7 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
325
328
|
// Approximate descent into finer tiers than the winner.
|
|
326
329
|
let current = winner
|
|
327
330
|
let currentConfirmed = winnerConfirmed
|
|
328
|
-
let currentDistanceKm = currentConfirmed ? null :
|
|
331
|
+
let currentDistanceKm = currentConfirmed ? null : haversine(lat, lon, current.lat, current.lon)
|
|
329
332
|
|
|
330
333
|
for (const tier of DESCENT_TIERS) {
|
|
331
334
|
if (placetypeDepth(tier) <= placetypeDepth(current.placetype)) continue
|
|
@@ -349,7 +352,7 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
349
352
|
}
|
|
350
353
|
|
|
351
354
|
if (contains === false) continue // known not-here, polygon rejected
|
|
352
|
-
const km =
|
|
355
|
+
const km = haversine(lat, lon, k.lat, k.lon)
|
|
353
356
|
|
|
354
357
|
if (km <= maxApproximateKm && km < bestKm) {
|
|
355
358
|
bestKm = km
|
|
@@ -494,7 +497,7 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
494
497
|
if (!this.#polygons) return null
|
|
495
498
|
const cached = this.#geometryCache.get(id)
|
|
496
499
|
|
|
497
|
-
if (cached
|
|
500
|
+
if (cached) return cached
|
|
498
501
|
|
|
499
502
|
if (this.#geometryCache.size >= WOFReverseGeocoder.#GEOMETRY_CACHE_CAP) {
|
|
500
503
|
this.#geometryCache.clear()
|
package/lib/search-fetch.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { allRows } from "@mailwoman/core/utils"
|
|
16
|
-
import { bboxAround } from "@mailwoman/spatial"
|
|
16
|
+
import { bboxAround } from "@mailwoman/spatial/bbox"
|
|
17
17
|
import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
|
|
18
18
|
|
|
19
19
|
import { PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE } from "#fts"
|
|
@@ -142,7 +142,7 @@ export function fetchSearchRows<DB>(options: {
|
|
|
142
142
|
params.push(query.country)
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
if (query.parentID
|
|
145
|
+
if (query.parentID) {
|
|
146
146
|
where.push(`(spr.parent_id = ? OR spr.id IN (SELECT id FROM ${sch}.ancestors WHERE ancestor_id = ?))`)
|
|
147
147
|
params.push(query.parentID, query.parentID)
|
|
148
148
|
}
|
|
@@ -152,7 +152,7 @@ export function fetchSearchRows<DB>(options: {
|
|
|
152
152
|
// A requested filter on a legacy extract is silently treated as no bbox filter
|
|
153
153
|
// so the query does not crash.
|
|
154
154
|
const extractHasBbox = hasBboxIndex.get(sch) === true
|
|
155
|
-
const useBboxJoin = (query.bbox || query.near?.maxDistanceKm
|
|
155
|
+
const useBboxJoin = (query.bbox || query.near?.maxDistanceKm) && extractHasBbox
|
|
156
156
|
let joinClause = `JOIN ${sch}.spr ON spr.id = place_search.wof_id`
|
|
157
157
|
|
|
158
158
|
if (useBboxJoin) {
|
|
@@ -35,7 +35,7 @@ export class SqliteConventionSource<DB> implements ConventionSource {
|
|
|
35
35
|
get(wofID: number): Convention | null {
|
|
36
36
|
const cached = this.#cache.get(wofID)
|
|
37
37
|
|
|
38
|
-
if (cached
|
|
38
|
+
if (cached) return cached
|
|
39
39
|
let value: Convention | null = null
|
|
40
40
|
|
|
41
41
|
try {
|
package/lib/street/centroid.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { StreetCentroidHit, StreetCentroidLookup } from "@mailwoman/core/resolver"
|
|
9
|
-
import {
|
|
9
|
+
import { haversine } from "@mailwoman/spatial/geometries/point/distance"
|
|
10
10
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
11
11
|
import type { PathBuilderLike } from "path-ts"
|
|
12
12
|
|
|
@@ -51,7 +51,7 @@ const AGG_SELECT =
|
|
|
51
51
|
* Half the bounding-box diagonal in meters, used as a coarse uncertainty radius for a street centroid.
|
|
52
52
|
*/
|
|
53
53
|
function extentRadiusM(minLat: number, maxLat: number, minLon: number, maxLon: number): number {
|
|
54
|
-
return Math.round(
|
|
54
|
+
return Math.round(haversine(minLat, minLon, maxLat, maxLon) * 500)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* Design rationale + the four-layer street-supplement architecture lives in
|
|
18
18
|
* `docs/articles/concepts/street-supplement-architecture.md`.
|
|
19
19
|
*
|
|
20
|
-
* Source: `
|
|
20
|
+
* Source: `dictionaries/data/libpostal/dictionaries/{locale}/street_types.txt`. Each line is pipe-delimited
|
|
21
21
|
* surface forms with the canonical form first: avenue|av|ave|aven|avenu|avn|avnu|avnue
|
|
22
22
|
*
|
|
23
23
|
* Output: an `FSTMatcher` ready to serialize via `serializeFST` to e.g.
|
|
@@ -61,7 +61,7 @@ export const REGION_ABBREVIATION_VARIANTS: ReadonlySet<string> = new Set(
|
|
|
61
61
|
|
|
62
62
|
export interface BuildStreetMorphologyFSTOpts {
|
|
63
63
|
/**
|
|
64
|
-
* Path to the `
|
|
64
|
+
* Path to the `dictionaries/data/libpostal/dictionaries` directory containing per-locale subfolders.
|
|
65
65
|
*/
|
|
66
66
|
dictionariesDir: PathBuilderLike
|
|
67
67
|
/**
|
|
@@ -213,7 +213,7 @@ export async function buildStreetMorphologyFST(
|
|
|
213
213
|
const node = nodes[stateID]!
|
|
214
214
|
let next = node.edges.get(t)
|
|
215
215
|
|
|
216
|
-
if (next
|
|
216
|
+
if (!next) {
|
|
217
217
|
next = nodes.length
|
|
218
218
|
nodes.push({ edges: new Map(), places: [] })
|
|
219
219
|
node.edges.set(t, next)
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 1. An explicit `artifactPath`, e.g. the weights-package sibling surfaced by
|
|
11
11
|
* `NeuralAddressClassifier.streetMorphologyPath`. When given, it is the only artifact probed.
|
|
12
12
|
* 2. Otherwise the staged sealed artifact at `$MAILWOMAN_DATA_ROOT/db/wof/fst-street-morphology.bin`.
|
|
13
|
-
* 3. A build-from-dictionaries fallback: `buildStreetMorphologyFST` over
|
|
13
|
+
* 3. A build-from-dictionaries fallback: `buildStreetMorphologyFST` over `@mailwoman/dictionaries`' libpostal
|
|
14
14
|
* `street_types.txt` files, kept so a missing artifact degrades to a per-process build instead
|
|
15
15
|
* of a crash.
|
|
16
16
|
*
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import { readLocalBuffer } from "@mailwoman/core/fs/readers"
|
|
23
23
|
import { pathExists } from "@mailwoman/core/fs/readers/stat"
|
|
24
|
-
import { resourceDictionaryPath } from "@mailwoman/
|
|
24
|
+
import { resourceDictionaryPath } from "@mailwoman/dictionaries/paths"
|
|
25
25
|
import type { PathBuilder, PathBuilderLike } from "path-ts"
|
|
26
26
|
|
|
27
27
|
import type { FSTMatcher } from "#fst/matcher"
|
|
@@ -54,7 +54,7 @@ export interface LoadStreetMorphologyFSTOpts {
|
|
|
54
54
|
/**
|
|
55
55
|
* Dictionaries dir for the build fallback.
|
|
56
56
|
*
|
|
57
|
-
* @defaultValue
|
|
57
|
+
* @defaultValue the libpostal dictionaries `@mailwoman/dictionaries` ships.
|
|
58
58
|
*/
|
|
59
59
|
dictionariesDir?: PathBuilderLike
|
|
60
60
|
/**
|
package/lib/street/normalize.ts
CHANGED
|
@@ -68,6 +68,16 @@ function fold(input: string): string {
|
|
|
68
68
|
.trim()
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* The name and version of the street-key normalization {@link normalizeStreetForKeyLocale} applies.
|
|
73
|
+
*
|
|
74
|
+
* An artifact manifest of a street-keyed role records this pair with its locale,
|
|
75
|
+
* and a reader refuses an artifact keyed by another version.
|
|
76
|
+
* Version `1` is the normalization in this module when versions began to be recorded, on 2026-10-29.
|
|
77
|
+
* A change to any key it produces is a new version.
|
|
78
|
+
*/
|
|
79
|
+
export const STREET_KEY_NORMALIZATION = { algorithm: "street-key", version: "1" } as const
|
|
80
|
+
|
|
71
81
|
/**
|
|
72
82
|
* Normalizes a US or English street name into an address-point key, folding directionals,
|
|
73
83
|
* spelled ordinals and the USPS street-type suffix.
|
|
@@ -90,7 +100,7 @@ export function normalizeStreetForKey(street: string): StreetKey {
|
|
|
90
100
|
AbbreviationToDirectional.get(raw.toUpperCase())?.toLowerCase().replace(" ", "")
|
|
91
101
|
|
|
92
102
|
const mergePair = (a?: string, b?: string) =>
|
|
93
|
-
a && b && /^(north|south)$/.test(a) && /^(east|west)$/.test(b) ? a + b :
|
|
103
|
+
a && b && /^(north|south)$/.test(a) && /^(east|west)$/.test(b) ? a + b : null
|
|
94
104
|
|
|
95
105
|
const leadPair = mergePair(tokens[0], tokens[1])
|
|
96
106
|
|
|
@@ -138,6 +148,23 @@ export function normalizeStreetForKey(street: string): StreetKey {
|
|
|
138
148
|
*/
|
|
139
149
|
export type StreetLocale = "us" | "en" | "es" | "fr" | "de" | "it" | "nl" | "pl" | "vn" | "id" | "zh"
|
|
140
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Every locale {@link normalizeStreetForKeyLocale} keys streets under.
|
|
153
|
+
*/
|
|
154
|
+
export const STREET_LOCALES = [
|
|
155
|
+
"us",
|
|
156
|
+
"en",
|
|
157
|
+
"es",
|
|
158
|
+
"fr",
|
|
159
|
+
"de",
|
|
160
|
+
"it",
|
|
161
|
+
"nl",
|
|
162
|
+
"pl",
|
|
163
|
+
"vn",
|
|
164
|
+
"id",
|
|
165
|
+
"zh",
|
|
166
|
+
] as const satisfies readonly StreetLocale[]
|
|
167
|
+
|
|
141
168
|
function foldHan(input: string): string {
|
|
142
169
|
return input.normalize("NFKC").replaceAll("臺", "台").replaceAll(/\s+/g, "").toLowerCase()
|
|
143
170
|
}
|
|
@@ -434,6 +461,14 @@ const CANONICAL_TYPE_WORDS: ReadonlySet<string> = new Set(
|
|
|
434
461
|
[...US_STREET_SUFFIX_LOOKUP.values()].map((v) => v.toLowerCase())
|
|
435
462
|
)
|
|
436
463
|
|
|
464
|
+
const ENGLISH_ORDINAL_RULES = new Intl.PluralRules("en-US", { type: "ordinal" })
|
|
465
|
+
|
|
466
|
+
const ENGLISH_ORDINAL_SUFFIX: Record<string, string> = { one: "st", two: "nd", few: "rd", other: "th" }
|
|
467
|
+
|
|
468
|
+
function ordinalOf(n: number): string {
|
|
469
|
+
return `${n}${ENGLISH_ORDINAL_SUFFIX[ENGLISH_ORDINAL_RULES.select(n)] ?? "th"}`
|
|
470
|
+
}
|
|
471
|
+
|
|
437
472
|
/**
|
|
438
473
|
* Lists the lookup keys to probe for a street span, most literal first, adding doubled-type
|
|
439
474
|
* and Saint/St-swap recovery forms for `us` and `en`.
|
|
@@ -463,6 +498,29 @@ export function streetKeyVariants(street: string, locale: StreetLocale = "us"):
|
|
|
463
498
|
}
|
|
464
499
|
}
|
|
465
500
|
|
|
501
|
+
// NAD writes a numbered street with its ordinal suffix ("48th St")
|
|
502
|
+
// and NYC Open Data writes the bare number ("48 ST").
|
|
503
|
+
// The build keys each row as written, so the reader probes both forms.
|
|
504
|
+
for (const variant of variants.slice()) {
|
|
505
|
+
const parts = variant.split(" ")
|
|
506
|
+
|
|
507
|
+
const toggled = parts
|
|
508
|
+
.map((token, i) => {
|
|
509
|
+
// Only a number followed by a street type is an ordinal ("48 street"), not "us route 9".
|
|
510
|
+
if (!CANONICAL_TYPE_WORDS.has(parts[i + 1] ?? "")) return token
|
|
511
|
+
const bare = /^(\d+)(?:st|nd|rd|th)$/.exec(token)?.[1]
|
|
512
|
+
|
|
513
|
+
if (bare) return bare
|
|
514
|
+
|
|
515
|
+
return /^\d+$/.test(token) ? ordinalOf(Number(token)) : token
|
|
516
|
+
})
|
|
517
|
+
.join(" ") as StreetKey
|
|
518
|
+
|
|
519
|
+
if (toggled !== variant && !variants.includes(toggled)) {
|
|
520
|
+
variants.push(toggled)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
466
524
|
const preSwap = variants.slice()
|
|
467
525
|
|
|
468
526
|
for (const variant of preSwap) {
|
package/lib/types.ts
CHANGED
|
@@ -155,6 +155,14 @@ export interface FindPlaceQuery {
|
|
|
155
155
|
*/
|
|
156
156
|
parentID?: number
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* A resolved parent place whose containment orders the rows and admits contained sub-locality
|
|
160
|
+
* rows the placetype filter excludes, read through the ancestors sidecar (#2804).
|
|
161
|
+
*
|
|
162
|
+
* It ranks rather than filters, so a query sets this or `parentID`.
|
|
163
|
+
*/
|
|
164
|
+
parentQualifierID?: number
|
|
165
|
+
|
|
158
166
|
/**
|
|
159
167
|
* The sibling postcode for a `locality` query.
|
|
160
168
|
*
|
package/lib/uprn/existence.ts
CHANGED
|
@@ -18,7 +18,6 @@ import { shortCellToParentInt } from "@mailwoman/spatial"
|
|
|
18
18
|
|
|
19
19
|
import type { UPRNLookup } from "#uprn/lookup"
|
|
20
20
|
import { UPRN_COVERAGE_H3_RESOLUTION, UPRN_H3_RESOLUTION, uprnH3Cell } from "#uprn/schema"
|
|
21
|
-
|
|
22
21
|
/**
|
|
23
22
|
* The identity fold, on purpose: this probe keys on a coordinate rather than a name,
|
|
24
23
|
* so there is no string folding for the builder and the probe to disagree about.
|
package/lib/uprn/lookup.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { allRows } from "@mailwoman/core/utils"
|
|
10
|
-
import {
|
|
10
|
+
import { haversine, shortCellToInt, type GeoCoordinate } from "@mailwoman/spatial"
|
|
11
11
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
12
|
-
import { gridDisk } from "h3
|
|
12
|
+
import { gridDisk } from "@sister.software/h3"
|
|
13
13
|
import type { PathBuilderLike } from "path-ts"
|
|
14
14
|
|
|
15
15
|
import type { UPRNDatabase } from "#uprn/schema"
|
|
@@ -128,13 +128,13 @@ export class UPRNLookup implements Disposable {
|
|
|
128
128
|
|
|
129
129
|
if (closestPossibleM > Math.min(radiusM, best?.distanceM ?? radiusM)) break
|
|
130
130
|
|
|
131
|
-
const diskCells = gridDisk(origin, ring)
|
|
131
|
+
const diskCells = gridDisk(origin, ring)
|
|
132
132
|
const newCells: number[] = []
|
|
133
133
|
|
|
134
134
|
for (const cell of diskCells) {
|
|
135
135
|
if (!seenCells.has(cell)) {
|
|
136
136
|
seenCells.add(cell)
|
|
137
|
-
newCells.push(shortCellToInt(cell
|
|
137
|
+
newCells.push(shortCellToInt(cell))
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -148,7 +148,7 @@ export class UPRNLookup implements Disposable {
|
|
|
148
148
|
)
|
|
149
149
|
|
|
150
150
|
for (const row of rows) {
|
|
151
|
-
const distanceM =
|
|
151
|
+
const distanceM = haversine(latitude, longitude, row.lat, row.lon) * 1000
|
|
152
152
|
|
|
153
153
|
if (distanceM <= radiusM && (!best || distanceM < best.distanceM)) {
|
|
154
154
|
best = { uprn: row.uprn, latitude: row.lat, longitude: row.lon, distanceM }
|
package/lib/uprn/schema.ts
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { layerschemadatabase } from "@mailwoman/core/layers"
|
|
10
|
-
import { shortCellToInt
|
|
11
|
-
import { latLngToCell } from "h3
|
|
10
|
+
import { shortCellToInt } from "@mailwoman/spatial"
|
|
11
|
+
import { latLngToCell, type H3Cell } from "@sister.software/h3"
|
|
12
12
|
import type { Kysely } from "kysely"
|
|
13
|
-
|
|
14
13
|
/**
|
|
15
14
|
* Resolution the `uprn` table's `h3_cell` column is keyed at, shared with the other layer spines.
|
|
16
15
|
*/
|
|
@@ -65,7 +64,7 @@ export interface UPRNDatabase extends layerschemadatabase {
|
|
|
65
64
|
* This is the one derivation the builder and its consumers share so their cells cannot disagree.
|
|
66
65
|
*/
|
|
67
66
|
export function uprnFullCell(latitude: number, longitude: number): H3Cell {
|
|
68
|
-
return latLngToCell(latitude, longitude, UPRN_H3_RESOLUTION)
|
|
67
|
+
return latLngToCell(latitude, longitude, UPRN_H3_RESOLUTION)
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
@@ -385,7 +385,7 @@ async function pairIndexIsFresh(
|
|
|
385
385
|
return false
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
if (minimumPlausibleBytes
|
|
388
|
+
if (minimumPlausibleBytes && (await statPath(dest)).size < minimumPlausibleBytes) {
|
|
389
389
|
console.log(
|
|
390
390
|
`rebuilding ${artifact} — ${(await statPath(dest)).size.toLocaleString()} bytes is implausibly small ` +
|
|
391
391
|
`for this recipe (wrong-source clobber)`
|
|
@@ -508,8 +508,8 @@ export async function buildPairIndexOverlay(overlay: PairIndexOverlay): Promise<
|
|
|
508
508
|
country,
|
|
509
509
|
"--delta",
|
|
510
510
|
String(delta),
|
|
511
|
-
...(transitionBeta
|
|
512
|
-
...(parentDelta
|
|
511
|
+
...(!transitionBeta ? [] : ["--transition-beta", String(transitionBeta)]),
|
|
512
|
+
...(!parentDelta ? [] : ["--parent-delta", String(parentDelta)]),
|
|
513
513
|
...extraArgs,
|
|
514
514
|
],
|
|
515
515
|
{ stdio: "inherit" }
|
|
@@ -727,7 +727,7 @@ async function linkBaseModelPair(destDir: PathBuilder, digestCard: string | null
|
|
|
727
727
|
const recipe = await readReleaseConfig()
|
|
728
728
|
|
|
729
729
|
const dataRoot = dataRootPath()
|
|
730
|
-
const digests = digestCard ? (await readWeightsCard(digestCard))?.files_md5 :
|
|
730
|
+
const digests = digestCard ? (await readWeightsCard(digestCard))?.files_md5 : null
|
|
731
731
|
|
|
732
732
|
const pair = [
|
|
733
733
|
{ label: "model", name: "model.onnx", override: $public.MAILWOMAN_DEV_MODEL, recipe: recipe.weights.model },
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*
|
|
29
29
|
* Standalone like the segment tier, wired through the ordered `spatialTiers` list.
|
|
30
30
|
*/
|
|
31
|
-
import {
|
|
31
|
+
import { haversine } from "@mailwoman/spatial/geometries/point/distance";
|
|
32
32
|
import { DatabaseClient } from "@mailwoman/sqlite/client";
|
|
33
33
|
import { hasTable, prepareAll } from "#sqlite-utils";
|
|
34
34
|
import { canonicalizeRouteKey, streetKeyVariants } from "#street/normalize";
|
|
@@ -137,7 +137,7 @@ function interpolateFromNeighbors(rows, n) {
|
|
|
137
137
|
}
|
|
138
138
|
if (below && above) {
|
|
139
139
|
const t = (n - below.n) / (above.n - below.n);
|
|
140
|
-
const spanM =
|
|
140
|
+
const spanM = haversine(below.lat, below.lon, above.lat, above.lon) * 1000;
|
|
141
141
|
return {
|
|
142
142
|
lat: below.lat + (above.lat - below.lat) * t,
|
|
143
143
|
lon: below.lon + (above.lon - below.lon) * t,
|
|
@@ -161,8 +161,8 @@ function interpolateFromNeighbors(rows, n) {
|
|
|
161
161
|
return null;
|
|
162
162
|
const lat = far.lat + (near.lat - far.lat) * t;
|
|
163
163
|
const lon = far.lon + (near.lon - far.lon) * t;
|
|
164
|
-
const pairM =
|
|
165
|
-
const overshootM =
|
|
164
|
+
const pairM = haversine(near.lat, near.lon, far.lat, far.lon) * 1000;
|
|
165
|
+
const overshootM = haversine(lat, lon, near.lat, near.lon) * 1000;
|
|
166
166
|
return {
|
|
167
167
|
lat,
|
|
168
168
|
lon,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../../lib/address/point/interpolation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../../lib/address/point/interpolation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,8CAA8C,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAIzD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAoB,MAAM,eAAe,CAAA;AACtE,OAAO,EAAE,oBAAoB,EAAiB,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1F;;;;;GAKG;AACH,MAAM,mBAAmB,GAAG,CAAC,CAAA;AAqB7B,MAAM,OAAO,wBAAwB;IAG3B,GAAG,CAAoB;IAChC;;;;;OAKG;IACM,UAAU,CAAiB;IAC3B,SAAS,CAAgC;IACzC,WAAW,CAAyF;IAE7G,YAAY,IAAuF;QAClG,MAAM,SAAS,GAAG,IAAI,eAAe,EAAE,CAAA;QAEvC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAA;QACzB,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,cAAc,CAAK,IAAI,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAClF,CAAC;aAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;QACnF,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAA;QAE9B,qEAAqE;QACrE,+FAA+F;QAC/F,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,WAAW,GAAG,UAAU,CAC5B,IAAI,CAAC,GAAG,EACR;;;;sCAIkC,CAClC,CAAA;QACF,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,CAAA;IACnC,CAAC;IAEQ,iBAAiB,GAAG,IAAI,CAAA;IAEjC,IAAI,CAAC,KAAyB;QAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;QAErC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAA;QACzC,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;QAE3B,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAA;QAEpF,gGAAgG;QAChG,IAAI,IAAI,GAAe,EAAE,CAAA;QAEzB,KAAK,MAAM,OAAO,IAAI,iBAAiB,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAA;YAE/C,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;YAE5D,IAAI,IAAI,CAAC,MAAM;gBAAE,MAAK;QACvB,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAEvE,OAAO,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAA;IAClD,CAAC;IAED,CAAC,MAAM,CAAC,OAAO,CAAC;QACf,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;IAClC,CAAC;CACD;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,IAAyB;IACjD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB,CAAA;IAEzC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAE5B,IAAI,KAAK,EAAE,CAAC;YACX,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAChB,CAAC;aAAM,CAAC;YACP,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;QACtB,CAAC;IACF,CAAC;IAED,OAAO,CAAC,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;SACvB,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACrB,CAAC;QACD,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM;QAC5D,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM;QAC5D,MAAM,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,MAAM;QACxB,OAAO,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,OAAO;KAC1B,CAAC,CAAC;SACF,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;AAChC,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAyB,EAAE,CAAS;IACrE,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IAErC,kDAAkD;IAClD,mCAAmC;IACnC,IAAI,KAAK,GAAwB,IAAI,CAAA;IACrC,IAAI,KAAK,GAAwB,IAAI,CAAA;IAErC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;YAClB,KAAK,GAAG,MAAM,CAAA;QACf,CAAC;aAAM,CAAC;YACP,KAAK,GAAG,MAAM,CAAA;YAEd,MAAK;QACN,CAAC;IACF,CAAC;IAED,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QAC7C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;QAE1E,OAAO;YACN,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YAC5C,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YAC5C,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,eAAe;YACvB,aAAa,EAAE,IAAI;YACnB,OAAO,EAAE,MAAM;YACf,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;YACnC,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,KAAK,CAAC,OAAO;SACtB,CAAA;IACF,CAAC;IAED,uFAAuF;IACvF,2DAA2D;IAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAE5D,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAChC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,CAAA;IACvE,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;IAExC,IAAI,CAAC,GAAG,mBAAmB;QAAE,OAAO,IAAI,CAAA;IAExC,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC9C,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;IACpE,MAAM,UAAU,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;IAEjE,OAAO;QACN,GAAG;QACH,GAAG;QACH,YAAY,EAAE,IAAI;QAClB,MAAM,EAAE,eAAe;QACvB,aAAa,EAAE,IAAI;QACnB,OAAO,EAAE,QAAQ;QACjB,wFAAwF;QACxF,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC;QAC5C,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,IAAI,CAAC,OAAO;KACrB,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software.
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file The shared rooftop schema with an H3 spine column and the layer-interface tables.
|
|
6
|
+
*
|
|
7
|
+
* The OpenStreetMap and G-NAF rooftop builders write this shape. Readers of existing address points
|
|
8
|
+
* project only the columns of `address_point` in `#address/point/schema`, so the H3 spine and the
|
|
9
|
+
* layer-interface tables leave their query path unchanged.
|
|
10
|
+
*/
|
|
11
|
+
import { type layerschemadatabase } from "@mailwoman/core/layers";
|
|
12
|
+
import type { Kysely } from "kysely";
|
|
13
|
+
import { type AddressPointTable } from "#address/point/schema";
|
|
14
|
+
/**
|
|
15
|
+
* Jitter-stable address/POI join cell.
|
|
16
|
+
* Matches @mailwoman/address-id.
|
|
17
|
+
*/
|
|
18
|
+
export declare const ADDRESS_POINT_H3_RESOLUTION = 9;
|
|
19
|
+
export interface SpineAddressPointTable extends AddressPointTable {
|
|
20
|
+
/**
|
|
21
|
+
* 48-bit shortened H3 cell stored as a safe SQLite integer.
|
|
22
|
+
*/
|
|
23
|
+
h3_cell: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SpineAddressPointDatabase extends layerschemadatabase {
|
|
26
|
+
address_point: SpineAddressPointTable;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Positional insert order: the shared address-point fields followed by the H3 spine.
|
|
30
|
+
*/
|
|
31
|
+
export declare const SPINE_ADDRESS_POINT_COLUMNS: readonly ["street_norm", "street_key", "number", "unit", "postcode", "locality_norm", "street_raw", "lat", "lon", "source", "release", "admin_code", "certified", "h3_cell"];
|
|
32
|
+
/**
|
|
33
|
+
* Create the legacy-compatible domain table plus the H3 spine and the layer-interface tables.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createSpineAddressPointTables(db: Kysely<SpineAddressPointDatabase>): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Build after the bulk load so the spine index is not updated on every streaming insert.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createSpineAddressPointIndexes(db: Kysely<SpineAddressPointDatabase>): Promise<void>;
|
|
40
|
+
//# sourceMappingURL=spine-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-schema.d.ts","sourceRoot":"","sources":["../../../lib/address/point/spine-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAsD,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AACrH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEpC,OAAO,EAAyB,KAAK,iBAAiB,EAA2B,MAAM,uBAAuB,CAAA;AAE9G;;;GAGG;AACH,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAChE;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACrE,aAAa,EAAE,sBAAsB,CAAA;CACrC;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,8KAAiD,CAAA;AAEzF;;GAEG;AACH,wBAAsB,6BAA6B,CAAC,EAAE,EAAE,MAAM,CAAC,yBAAyB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxG;AAED;;GAEG;AACH,wBAAsB,8BAA8B,CAAC,EAAE,EAAE,MAAM,CAAC,yBAAyB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzG"}
|