@mailwoman/resolver-wof-sqlite 7.2.0 → 7.3.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 (57) hide show
  1. package/address-point-interpolation.ts +207 -0
  2. package/address-point-schema.ts +107 -0
  3. package/address-point.ts +122 -0
  4. package/ancestry-backfill.ts +205 -0
  5. package/ancestry.ts +70 -0
  6. package/build-candidate.ts +351 -0
  7. package/build-slim.ts +394 -0
  8. package/candidate-fts.ts +43 -0
  9. package/candidate-lookup.ts +382 -0
  10. package/candidate-schema.ts +166 -0
  11. package/coincident-roles.ts +240 -0
  12. package/convention.ts +152 -0
  13. package/fst-autocomplete.ts +187 -0
  14. package/fst-builder.ts +291 -0
  15. package/fst-deserialize-web.ts +164 -0
  16. package/fst-matcher.ts +150 -0
  17. package/fst-serialize.ts +311 -0
  18. package/fst-types.ts +78 -0
  19. package/fts.ts +318 -0
  20. package/geo.ts +140 -0
  21. package/geonames-aliases.ts +317 -0
  22. package/geonames-postal.ts +150 -0
  23. package/index.ts +117 -0
  24. package/interpolation.ts +232 -0
  25. package/lookup.ts +1498 -0
  26. package/out/poi-lookup.d.ts +14 -2
  27. package/out/poi-lookup.d.ts.map +1 -1
  28. package/out/poi-lookup.js +55 -21
  29. package/out/poi-lookup.js.map +1 -1
  30. package/out/poi-schema.d.ts +9 -0
  31. package/out/poi-schema.d.ts.map +1 -1
  32. package/out/poi-schema.js +16 -0
  33. package/out/poi-schema.js.map +1 -1
  34. package/out/reverse.d.ts +8 -1
  35. package/out/reverse.d.ts.map +1 -1
  36. package/out/reverse.js +10 -1
  37. package/out/reverse.js.map +1 -1
  38. package/package.json +168 -82
  39. package/poi-lookup.ts +375 -0
  40. package/poi-schema.ts +164 -0
  41. package/postal-city-alias-lookup.ts +89 -0
  42. package/postal-city-alias-schema.ts +75 -0
  43. package/postal-city-candidate-schema.ts +81 -0
  44. package/postcode-point-lookup.ts +64 -0
  45. package/reverse.ts +439 -0
  46. package/schema.ts +176 -0
  47. package/sharding.ts +235 -0
  48. package/sqlite-convention-source.ts +61 -0
  49. package/sqlite-utils.ts +25 -0
  50. package/street-centroid-schema.ts +124 -0
  51. package/street-centroid.ts +124 -0
  52. package/street-morphology-fst-builder.ts +230 -0
  53. package/street-name-lookup.ts +101 -0
  54. package/street-normalize.ts +302 -0
  55. package/street-segment-schema.ts +104 -0
  56. package/types.ts +164 -0
  57. package/unified-schema.ts +171 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Binary serialization for the FST gazetteer. Format:
7
+ *
8
+ * HEADER (32 bytes) magic [u8; 4] "FST\0" version u16 1 flags u16 0 (reserved) stateCount u32
9
+ * edgeCount u32 total edges across all states placeCount u32 total place entries across all
10
+ * states stringCount u32 unique strings in string table stringBytes u32 total bytes of string
11
+ * data _reserved u32
12
+ *
13
+ * STRING TABLE offsets [u32; stringCount + 1] byte offset into data (last = sentinel) data [u8;
14
+ * stringBytes] concatenated UTF-8
15
+ *
16
+ * STATE TABLE [stateCount × 12 bytes] edgeStart u32 index into edge table placeStart u32 index into
17
+ * place table edgeCount u16 placeCount u16
18
+ *
19
+ * EDGE TABLE [edgeCount × 8 bytes] stringIdx u32 index into string table targetState u32
20
+ *
21
+ * PLACE TABLE [placeCount × 56 bytes] wofID u32 placetypeIdx u8 index into PLACETYPE_ORDER chainLen
22
+ * u8 0..8 _pad u16 nameIdx u32 index into string table importance f32 Wikipedia importance [0,1]
23
+ * (V2); was population u32 (V1) lat f32 lon f32 chain [u32; 8] parent chain (unused slots = 0)
24
+ */
25
+
26
+ import type { FSTNode } from "./fst-matcher.ts"
27
+ import { FSTMatcher } from "./fst-matcher.ts"
28
+ import type { FSTProvenance, PlaceEntry, PlacetypeID } from "./fst-types.ts"
29
+
30
+ const MAGIC = Buffer.from("FST\0", "ascii")
31
+ const VERSION = 4
32
+ const HEADER_SIZE = 32
33
+ const STATE_ENTRY_SIZE = 16
34
+ const EDGE_ENTRY_SIZE = 8
35
+ const PLACE_ENTRY_SIZE = 56
36
+ const MAX_CHAIN_LEN = 8
37
+
38
+ const PLACETYPE_ORDER: readonly PlacetypeID[] = [
39
+ "country",
40
+ "region",
41
+ "county",
42
+ "locality",
43
+ "localadmin",
44
+ "borough",
45
+ "neighbourhood",
46
+ "postalcode",
47
+ "campus",
48
+ "dependency",
49
+ "street_affix",
50
+ ]
51
+
52
+ const placetypeToIdx = new Map<string, number>()
53
+
54
+ for (let i = 0; i < PLACETYPE_ORDER.length; i++) {
55
+ placetypeToIdx.set(PLACETYPE_ORDER[i]!, i)
56
+ }
57
+
58
+ export function serializeFST(matcher: FSTMatcher, provenance?: FSTProvenance): Buffer {
59
+ const nodes = matcher.toNodes() as FSTNode[]
60
+
61
+ // --- String interning ---
62
+ const stringMap = new Map<string, number>()
63
+ const strings: string[] = []
64
+
65
+ function intern(s: string): number {
66
+ let idx = stringMap.get(s)
67
+
68
+ if (idx === undefined) {
69
+ idx = strings.length
70
+ strings.push(s)
71
+ stringMap.set(s, idx)
72
+ }
73
+
74
+ return idx
75
+ }
76
+
77
+ for (const node of nodes) {
78
+ for (const token of node.edges.keys()) {
79
+ intern(token)
80
+ }
81
+
82
+ for (const place of node.places) {
83
+ intern(place.name)
84
+ }
85
+ }
86
+
87
+ const encodedStrings = strings.map((s) => Buffer.from(s, "utf8"))
88
+ const stringBytes = encodedStrings.reduce((sum, b) => sum + b.length, 0)
89
+
90
+ // --- Counts ---
91
+ let totalEdges = 0
92
+ let totalPlaces = 0
93
+
94
+ for (const node of nodes) {
95
+ totalEdges += node.edges.size
96
+ totalPlaces += node.places.length
97
+ }
98
+
99
+ // --- Allocate ---
100
+ const stringTableSize = (strings.length + 1) * 4 + stringBytes
101
+ const stateTableSize = nodes.length * STATE_ENTRY_SIZE
102
+ const edgeTableSize = totalEdges * EDGE_ENTRY_SIZE
103
+ const placeTableSize = totalPlaces * PLACE_ENTRY_SIZE
104
+ const provenanceJson = provenance ? Buffer.from(JSON.stringify(provenance), "utf8") : null
105
+ const provenanceSize = provenanceJson ? 4 + provenanceJson.length : 0
106
+ const binarySize = HEADER_SIZE + stringTableSize + stateTableSize + edgeTableSize + placeTableSize
107
+ const totalSize = binarySize + provenanceSize
108
+ const buf = Buffer.alloc(totalSize)
109
+ let pos = 0
110
+
111
+ // --- Header ---
112
+ MAGIC.copy(buf, pos)
113
+ pos += 4
114
+ buf.writeUInt16LE(VERSION, pos)
115
+ pos += 2
116
+ buf.writeUInt16LE(0, pos)
117
+ pos += 2
118
+ buf.writeUInt32LE(nodes.length, pos)
119
+ pos += 4
120
+ buf.writeUInt32LE(totalEdges, pos)
121
+ pos += 4
122
+ buf.writeUInt32LE(totalPlaces, pos)
123
+ pos += 4
124
+ buf.writeUInt32LE(strings.length, pos)
125
+ pos += 4
126
+ buf.writeUInt32LE(stringBytes, pos)
127
+ pos += 4
128
+ buf.writeUInt32LE(provenanceJson ? binarySize : 0, pos)
129
+ pos += 4
130
+
131
+ // --- String table ---
132
+ let strOffset = 0
133
+
134
+ for (let i = 0; i < encodedStrings.length; i++) {
135
+ buf.writeUInt32LE(strOffset, pos)
136
+ pos += 4
137
+ strOffset += encodedStrings[i]!.length
138
+ }
139
+ buf.writeUInt32LE(strOffset, pos)
140
+ pos += 4
141
+
142
+ // sentinel
143
+
144
+ for (const encoded of encodedStrings) {
145
+ encoded.copy(buf, pos)
146
+ pos += encoded.length
147
+ }
148
+
149
+ // --- State, edge, and place tables ---
150
+ const stateTableStart = pos
151
+ const edgeTableStart = stateTableStart + stateTableSize
152
+ const placeTableStart = edgeTableStart + edgeTableSize
153
+
154
+ let edgeIdx = 0
155
+ let placeIdx = 0
156
+
157
+ for (let si = 0; si < nodes.length; si++) {
158
+ const node = nodes[si]!
159
+ const sp = stateTableStart + si * STATE_ENTRY_SIZE
160
+
161
+ buf.writeUInt32LE(edgeIdx, sp)
162
+ buf.writeUInt32LE(placeIdx, sp + 4)
163
+ buf.writeUInt32LE(node.edges.size, sp + 8)
164
+ buf.writeUInt32LE(node.places.length, sp + 12)
165
+
166
+ for (const [token, target] of node.edges) {
167
+ const ep = edgeTableStart + edgeIdx * EDGE_ENTRY_SIZE
168
+ buf.writeUInt32LE(intern(token), ep)
169
+ buf.writeUInt32LE(target, ep + 4)
170
+ edgeIdx++
171
+ }
172
+
173
+ for (const place of node.places) {
174
+ const pp = placeTableStart + placeIdx * PLACE_ENTRY_SIZE
175
+ // Filter out WOF sentinel parent IDs (negative values like -1, -4).
176
+ const validChain = place.parentChain.filter((id) => id > 0)
177
+ const chainLen = Math.min(validChain.length, MAX_CHAIN_LEN)
178
+ buf.writeUInt32LE(place.wofID, pp)
179
+ buf.writeUInt8(placetypeToIdx.get(place.placetype) ?? 0, pp + 4)
180
+ buf.writeUInt8(chainLen, pp + 5)
181
+ buf.writeUInt16LE(0, pp + 6) // pad
182
+ buf.writeUInt32LE(intern(place.name), pp + 8)
183
+ buf.writeFloatLE(place.importance, pp + 12)
184
+ buf.writeFloatLE(place.lat, pp + 16)
185
+ buf.writeFloatLE(place.lon, pp + 20)
186
+
187
+ for (let ci = 0; ci < MAX_CHAIN_LEN; ci++) {
188
+ buf.writeUInt32LE(ci < chainLen ? validChain[ci]! : 0, pp + 24 + ci * 4)
189
+ }
190
+ placeIdx++
191
+ }
192
+ }
193
+
194
+ if (provenanceJson) {
195
+ const trailerStart = binarySize
196
+ buf.writeUInt32LE(provenanceJson.length, trailerStart)
197
+ provenanceJson.copy(buf, trailerStart + 4)
198
+ }
199
+
200
+ return buf
201
+ }
202
+
203
+ export function deserializeFST(buf: Buffer): FSTMatcher {
204
+ // --- Header ---
205
+ if (buf.length < HEADER_SIZE) throw new Error("FST buffer too small for header")
206
+
207
+ if (!buf.subarray(0, 4).equals(MAGIC)) throw new Error("FST magic mismatch")
208
+ const version = buf.readUInt16LE(4)
209
+
210
+ if (version < 1 || version > VERSION) throw new Error(`FST version ${version} unsupported (expected 1..${VERSION})`)
211
+ const isV2 = version >= 2
212
+
213
+ const stateCount = buf.readUInt32LE(8)
214
+ const edgeCount = buf.readUInt32LE(12)
215
+ const _placeCount = buf.readUInt32LE(16)
216
+ const stringCount = buf.readUInt32LE(20)
217
+ const stringBytes = buf.readUInt32LE(24)
218
+
219
+ let pos = HEADER_SIZE
220
+
221
+ // --- String table ---
222
+ const strOffsets = new Uint32Array(stringCount + 1)
223
+
224
+ for (let i = 0; i <= stringCount; i++) {
225
+ strOffsets[i] = buf.readUInt32LE(pos)
226
+ pos += 4
227
+ }
228
+ const strDataStart = pos
229
+ const strings: string[] = new Array(stringCount)
230
+
231
+ for (let i = 0; i < stringCount; i++) {
232
+ const start = strDataStart + strOffsets[i]!
233
+ const end = strDataStart + strOffsets[i + 1]!
234
+ strings[i] = buf.toString("utf8", start, end)
235
+ }
236
+ pos += stringBytes
237
+
238
+ // --- State table ---
239
+ const stateEntrySize = version >= 4 ? 16 : 12
240
+ const stateTableStart = pos
241
+ const edgeTableStart = stateTableStart + stateCount * stateEntrySize
242
+ const placeTableStart = edgeTableStart + edgeCount * EDGE_ENTRY_SIZE
243
+
244
+ const nodes: FSTNode[] = new Array(stateCount)
245
+
246
+ for (let si = 0; si < stateCount; si++) {
247
+ const sp = stateTableStart + si * stateEntrySize
248
+ const edgeStart = buf.readUInt32LE(sp)
249
+ const placeStart = buf.readUInt32LE(sp + 4)
250
+ const edgeCountForState = version >= 4 ? buf.readUInt32LE(sp + 8) : buf.readUInt16LE(sp + 8)
251
+ const placeCountForState = version >= 4 ? buf.readUInt32LE(sp + 12) : buf.readUInt16LE(sp + 10)
252
+
253
+ const edges = new Map<string, number>()
254
+
255
+ for (let ei = 0; ei < edgeCountForState; ei++) {
256
+ const ep = edgeTableStart + (edgeStart + ei) * EDGE_ENTRY_SIZE
257
+ const stringIdx = buf.readUInt32LE(ep)
258
+ const target = buf.readUInt32LE(ep + 4)
259
+ edges.set(strings[stringIdx]!, target)
260
+ }
261
+
262
+ const places: PlaceEntry[] = new Array(placeCountForState)
263
+
264
+ for (let pi = 0; pi < placeCountForState; pi++) {
265
+ const pp = placeTableStart + (placeStart + pi) * PLACE_ENTRY_SIZE
266
+ const chainLen = buf.readUInt8(pp + 5)
267
+ const parentChain: number[] = []
268
+
269
+ for (let ci = 0; ci < chainLen; ci++) {
270
+ parentChain.push(buf.readUInt32LE(pp + 24 + ci * 4))
271
+ }
272
+ const rawImportance = isV2
273
+ ? buf.readFloatLE(pp + 12)
274
+ : Math.min(1.0, Math.log2(1 + buf.readUInt32LE(pp + 12) / 1000) / 14)
275
+ places[pi] = {
276
+ wofID: buf.readUInt32LE(pp),
277
+ placetype: PLACETYPE_ORDER[buf.readUInt8(pp + 4)] ?? "locality",
278
+ name: strings[buf.readUInt32LE(pp + 8)]!,
279
+ importance: rawImportance,
280
+ lat: buf.readFloatLE(pp + 16),
281
+ lon: buf.readFloatLE(pp + 20),
282
+ parentChain,
283
+ }
284
+ }
285
+
286
+ nodes[si] = { edges, places }
287
+ }
288
+
289
+ return FSTMatcher.fromNodes(nodes)
290
+ }
291
+
292
+ export function readFSTProvenance(buf: Buffer): FSTProvenance | undefined {
293
+ if (buf.length < HEADER_SIZE) return undefined
294
+
295
+ if (!buf.subarray(0, 4).equals(MAGIC)) return undefined
296
+ const version = buf.readUInt16LE(4)
297
+
298
+ if (version < 3) return undefined
299
+ const provenanceOffset = buf.readUInt32LE(28)
300
+
301
+ if (provenanceOffset === 0 || provenanceOffset >= buf.length) return undefined
302
+
303
+ try {
304
+ const jsonLen = buf.readUInt32LE(provenanceOffset)
305
+ const jsonStr = buf.toString("utf8", provenanceOffset + 4, provenanceOffset + 4 + jsonLen)
306
+
307
+ return JSON.parse(jsonStr) as FSTProvenance
308
+ } catch {
309
+ return undefined
310
+ }
311
+ }
package/fst-types.ts ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Types for the FST gazetteer language model. The FST maps token sequences (place names) to
7
+ * (placetype, wof_id, parent_chain) entries — pre-computing the valid interpretations for each
8
+ * prefix of every place name in the gazetteer.
9
+ */
10
+
11
+ export interface PlaceEntry {
12
+ wofID: number
13
+ placetype: PlacetypeID
14
+ name: string
15
+ parentChain: number[]
16
+ importance: number
17
+ lat: number
18
+ lon: number
19
+ }
20
+
21
+ export type PlacetypeID =
22
+ | "country"
23
+ | "region"
24
+ | "county"
25
+ | "locality"
26
+ | "localadmin"
27
+ | "borough"
28
+ | "neighbourhood"
29
+ | "postalcode"
30
+ | "campus"
31
+ | "dependency"
32
+ | "street_affix"
33
+
34
+ export interface FSTMatchResult {
35
+ stateID: number
36
+ accepted: boolean
37
+ depth: number
38
+ }
39
+
40
+ export interface FSTContinuation {
41
+ token: string
42
+ targetState: number
43
+ acceptingCount: number
44
+ }
45
+
46
+ export interface FSTQueryResult {
47
+ path: string[]
48
+ stateID: number
49
+ accepting: PlaceEntry[]
50
+ continuations: FSTContinuation[]
51
+ }
52
+
53
+ export interface FSTProvenance {
54
+ builtAt: string
55
+ countries: string[]
56
+ stateCount: number
57
+ placeCount: number
58
+ edgeCount: number
59
+ nameInsertions: number
60
+ importanceMatches: number
61
+ sourceDB?: string
62
+ modelCardVersion?: string
63
+ }
64
+
65
+ export interface BuildFSTOpts {
66
+ dbPath: string
67
+ countries?: string[]
68
+ placetypes?: PlacetypeID[]
69
+ languages?: string[]
70
+ onProgress?: (phase: string, detail?: string) => void
71
+ }
72
+
73
+ export interface BuildFSTResult {
74
+ stateCount: number
75
+ placeCount: number
76
+ edgeCount: number
77
+ tokenCount: number
78
+ }