@mailwoman/resolver-wof-sqlite 10.7.0 → 11.0.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 (189) hide show
  1. package/lib/address/point/interpolation.ts +4 -4
  2. package/lib/address/point/spine-schema.ts +59 -0
  3. package/lib/address/point.ts +44 -1
  4. package/lib/ancestry/backfill.ts +3 -25
  5. package/lib/build/candidate.ts +22 -5
  6. package/lib/build/slim.ts +0 -19
  7. package/lib/candidate/alias-bags.ts +32 -14
  8. package/lib/candidate/bare-admin-names.ts +54 -0
  9. package/lib/candidate/importance.ts +2 -2
  10. package/lib/candidate/lookup.ts +63 -30
  11. package/lib/candidate/own-name.ts +1 -5
  12. package/lib/candidate/scoring.ts +4 -4
  13. package/lib/capitals.ts +2 -2
  14. package/lib/currency-backfill.ts +4 -4
  15. package/lib/fst/autocomplete.ts +1 -1
  16. package/lib/fst/binary-matcher.ts +379 -0
  17. package/lib/fst/builder.ts +1 -1
  18. package/lib/fst/deserialize-web.ts +14 -143
  19. package/lib/fst/matcher.ts +3 -3
  20. package/lib/fst/serialize.ts +1 -1
  21. package/lib/fst.ts +1 -0
  22. package/lib/geonames/aliases.ts +2 -2
  23. package/lib/index.ts +0 -1
  24. package/lib/interpolation.ts +3 -2
  25. package/lib/landmark/rows.ts +91 -0
  26. package/lib/landmark.ts +94 -0
  27. package/lib/lookup.ts +7 -27
  28. package/lib/nsul/lookup.ts +2 -1
  29. package/lib/nsul/schema.ts +2 -8
  30. package/lib/place-importance-schema.ts +1 -1
  31. package/lib/poi/lookup.ts +14 -14
  32. package/lib/primary-preference.ts +13 -20
  33. package/lib/proximity-rerank.ts +2 -2
  34. package/lib/reverse.ts +8 -5
  35. package/lib/schema.ts +0 -18
  36. package/lib/search-fetch.ts +3 -3
  37. package/lib/sqlite/convention-source.ts +1 -1
  38. package/lib/street/centroid.ts +2 -2
  39. package/lib/street/morphology/fst/builder.ts +3 -3
  40. package/lib/street/morphology/fst/loader.ts +3 -3
  41. package/lib/street/normalize.ts +150 -1
  42. package/lib/types.ts +15 -2
  43. package/lib/uprn/existence.ts +0 -1
  44. package/lib/uprn/lookup.ts +5 -5
  45. package/lib/uprn/schema.ts +3 -4
  46. package/lib/weights-overlay-linker.ts +4 -4
  47. package/out/address/point/interpolation.js +4 -4
  48. package/out/address/point/interpolation.js.map +1 -1
  49. package/out/address/point/spine-schema.d.ts +40 -0
  50. package/out/address/point/spine-schema.d.ts.map +1 -0
  51. package/out/address/point/spine-schema.js +40 -0
  52. package/out/address/point/spine-schema.js.map +1 -0
  53. package/out/address/point.d.ts.map +1 -1
  54. package/out/address/point.js +36 -1
  55. package/out/address/point.js.map +1 -1
  56. package/out/ancestry/backfill.d.ts.map +1 -1
  57. package/out/ancestry/backfill.js +3 -22
  58. package/out/ancestry/backfill.js.map +1 -1
  59. package/out/build/candidate.d.ts.map +1 -1
  60. package/out/build/candidate.js +17 -5
  61. package/out/build/candidate.js.map +1 -1
  62. package/out/build/slim.d.ts +1 -1
  63. package/out/build/slim.d.ts.map +1 -1
  64. package/out/build/slim.js +0 -12
  65. package/out/build/slim.js.map +1 -1
  66. package/out/candidate/alias-bags.d.ts +8 -0
  67. package/out/candidate/alias-bags.d.ts.map +1 -1
  68. package/out/candidate/alias-bags.js +20 -11
  69. package/out/candidate/alias-bags.js.map +1 -1
  70. package/out/candidate/bare-admin-names.d.ts +26 -0
  71. package/out/candidate/bare-admin-names.d.ts.map +1 -0
  72. package/out/candidate/bare-admin-names.js +38 -0
  73. package/out/candidate/bare-admin-names.js.map +1 -0
  74. package/out/candidate/importance.js +2 -2
  75. package/out/candidate/importance.js.map +1 -1
  76. package/out/candidate/lookup.d.ts.map +1 -1
  77. package/out/candidate/lookup.js +43 -23
  78. package/out/candidate/lookup.js.map +1 -1
  79. package/out/candidate/own-name.d.ts.map +1 -1
  80. package/out/candidate/own-name.js +1 -5
  81. package/out/candidate/own-name.js.map +1 -1
  82. package/out/candidate/scoring.js +4 -4
  83. package/out/candidate/scoring.js.map +1 -1
  84. package/out/capitals.js +2 -2
  85. package/out/capitals.js.map +1 -1
  86. package/out/currency-backfill.js +4 -4
  87. package/out/currency-backfill.js.map +1 -1
  88. package/out/env.d.ts +3 -0
  89. package/out/env.d.ts.map +1 -1
  90. package/out/fst/autocomplete.js +1 -1
  91. package/out/fst/autocomplete.js.map +1 -1
  92. package/out/fst/binary-matcher.d.ts +34 -0
  93. package/out/fst/binary-matcher.d.ts.map +1 -0
  94. package/out/fst/binary-matcher.js +294 -0
  95. package/out/fst/binary-matcher.js.map +1 -0
  96. package/out/fst/builder.js +1 -1
  97. package/out/fst/builder.js.map +1 -1
  98. package/out/fst/deserialize-web.d.ts +11 -6
  99. package/out/fst/deserialize-web.d.ts.map +1 -1
  100. package/out/fst/deserialize-web.js +12 -94
  101. package/out/fst/deserialize-web.js.map +1 -1
  102. package/out/fst/matcher.js +3 -3
  103. package/out/fst/matcher.js.map +1 -1
  104. package/out/fst/serialize.js +1 -1
  105. package/out/fst/serialize.js.map +1 -1
  106. package/out/fst.d.ts +1 -0
  107. package/out/fst.d.ts.map +1 -1
  108. package/out/fst.js +1 -0
  109. package/out/fst.js.map +1 -1
  110. package/out/geonames/aliases.js +2 -2
  111. package/out/geonames/aliases.js.map +1 -1
  112. package/out/index.d.ts +1 -1
  113. package/out/index.d.ts.map +1 -1
  114. package/out/index.js.map +1 -1
  115. package/out/interpolation.d.ts.map +1 -1
  116. package/out/interpolation.js +3 -2
  117. package/out/interpolation.js.map +1 -1
  118. package/out/landmark/rows.d.ts +63 -0
  119. package/out/landmark/rows.d.ts.map +1 -0
  120. package/out/landmark/rows.js +48 -0
  121. package/out/landmark/rows.js.map +1 -0
  122. package/out/landmark.d.ts +40 -0
  123. package/out/landmark.d.ts.map +1 -0
  124. package/out/landmark.js +78 -0
  125. package/out/landmark.js.map +1 -0
  126. package/out/lookup.d.ts +3 -7
  127. package/out/lookup.d.ts.map +1 -1
  128. package/out/lookup.js +4 -21
  129. package/out/lookup.js.map +1 -1
  130. package/out/nsul/lookup.d.ts +1 -1
  131. package/out/nsul/lookup.d.ts.map +1 -1
  132. package/out/nsul/lookup.js +1 -1
  133. package/out/nsul/lookup.js.map +1 -1
  134. package/out/nsul/schema.d.ts +2 -5
  135. package/out/nsul/schema.d.ts.map +1 -1
  136. package/out/nsul/schema.js +2 -7
  137. package/out/nsul/schema.js.map +1 -1
  138. package/out/place-importance-schema.js +1 -1
  139. package/out/place-importance-schema.js.map +1 -1
  140. package/out/poi/lookup.d.ts +3 -3
  141. package/out/poi/lookup.d.ts.map +1 -1
  142. package/out/poi/lookup.js +9 -9
  143. package/out/poi/lookup.js.map +1 -1
  144. package/out/primary-preference.d.ts +10 -10
  145. package/out/primary-preference.d.ts.map +1 -1
  146. package/out/primary-preference.js +8 -9
  147. package/out/primary-preference.js.map +1 -1
  148. package/out/proximity-rerank.js +2 -2
  149. package/out/proximity-rerank.js.map +1 -1
  150. package/out/reverse.d.ts.map +1 -1
  151. package/out/reverse.js +7 -5
  152. package/out/reverse.js.map +1 -1
  153. package/out/schema.d.ts +0 -17
  154. package/out/schema.d.ts.map +1 -1
  155. package/out/search-fetch.js +3 -3
  156. package/out/search-fetch.js.map +1 -1
  157. package/out/sqlite/convention-source.js +1 -1
  158. package/out/sqlite/convention-source.js.map +1 -1
  159. package/out/street/centroid.js +2 -2
  160. package/out/street/centroid.js.map +1 -1
  161. package/out/street/morphology/fst/builder.d.ts +2 -2
  162. package/out/street/morphology/fst/builder.js +2 -2
  163. package/out/street/morphology/fst/builder.js.map +1 -1
  164. package/out/street/morphology/fst/loader.d.ts +2 -2
  165. package/out/street/morphology/fst/loader.js +2 -2
  166. package/out/street/morphology/fst/loader.js.map +1 -1
  167. package/out/street/normalize.d.ts +23 -0
  168. package/out/street/normalize.d.ts.map +1 -1
  169. package/out/street/normalize.js +126 -1
  170. package/out/street/normalize.js.map +1 -1
  171. package/out/types.d.ts +13 -2
  172. package/out/types.d.ts.map +1 -1
  173. package/out/uprn/existence.d.ts.map +1 -1
  174. package/out/uprn/existence.js.map +1 -1
  175. package/out/uprn/lookup.d.ts.map +1 -1
  176. package/out/uprn/lookup.js +3 -3
  177. package/out/uprn/lookup.js.map +1 -1
  178. package/out/uprn/schema.d.ts +1 -1
  179. package/out/uprn/schema.d.ts.map +1 -1
  180. package/out/uprn/schema.js +1 -1
  181. package/out/uprn/schema.js.map +1 -1
  182. package/out/weights-overlay-linker.js +4 -4
  183. package/out/weights-overlay-linker.js.map +1 -1
  184. package/package.json +16 -13
  185. package/lib/coincident-roles.ts +0 -347
  186. package/out/coincident-roles.d.ts +0 -121
  187. package/out/coincident-roles.d.ts.map +0 -1
  188. package/out/coincident-roles.js +0 -222
  189. package/out/coincident-roles.js.map +0 -1
@@ -10,7 +10,7 @@ import { type Ancestor, type GazetteerArtifactCoverage, referentialFromPopulatio
10
10
  import { allRows } from "@mailwoman/core/utils"
11
11
  import { jaroWinkler, levenshteinSimilarity } from "@mailwoman/match/comparators"
12
12
  import { partitionByContainment } from "@mailwoman/resolver"
13
- import { haversineKm } from "@mailwoman/spatial"
13
+ import { haversine } from "@mailwoman/spatial/geometries/point/distance"
14
14
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
15
15
  import { SQLiteLookup, type SQLiteLookupOptions } from "@mailwoman/sqlite/lookup"
16
16
 
@@ -67,6 +67,18 @@ type CandidateRow = Pick<
67
67
  // Optional because older artifacts may not include `importance`.
68
68
  Partial<Pick<CandidateTable, "importance">>
69
69
 
70
+ /**
71
+ * What a containment re-rank needs beside the rows: the keys to inject contained
72
+ * misses from and the shape filters the initial probe applied.
73
+ */
74
+ interface ContainmentOptions {
75
+ nameKey: NameKey
76
+ strippedKey: NameKey
77
+ shapeFilters: string[]
78
+ shapeParams: Array<string | number>
79
+ limit: number
80
+ }
81
+
70
82
  const FUZZY_FETCH = 40
71
83
 
72
84
  const WORD_FUZZY_MIN = 0.85
@@ -177,7 +189,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
177
189
 
178
190
  const bandIDs = [...REGION_CLASS_PLACETYPES, "country"]
179
191
  .map((placetype) => this.#placetypeToID.get(placetype))
180
- .filter((id): id is number => id !== undefined)
192
+ .filter((id) => typeof id === "number")
181
193
 
182
194
  if (bandIDs.length) {
183
195
  this.#qualifierProbe = this.database.prepare(
@@ -219,7 +231,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
219
231
 
220
232
  const cached = this.#intervalCache.get(sprID)
221
233
 
222
- if (cached !== undefined) return cached
234
+ if (cached) return cached
223
235
 
224
236
  const row = this.#intervalProbe.get(sprID) as { pre: number; post: number } | undefined
225
237
  const label = row ? { pre: Number(row.pre), post: Number(row.post) } : null
@@ -260,23 +272,30 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
260
272
  }
261
273
 
262
274
  /**
263
- * Apply admin-containment re-rank: stamp containment, inject contained misses,
264
- * then partition contained-first.
275
+ * Apply admin-containment re-rank for a parsed region qualifier: resolve the
276
+ * qualifier's region rows, then {@linkcode #applyContainment}.
265
277
  */
266
278
  #applyAdminContainment(
267
279
  rows: Array<RankedRow<CandidateRow>>,
268
280
  qualifier: string,
269
281
  country: string | null,
270
- opts: {
271
- nameKey: NameKey
272
- strippedKey: NameKey
273
- shapeFilters: string[]
274
- shapeParams: Array<string | number>
275
- limit: number
276
- }
282
+ opts: ContainmentOptions
277
283
  ): Array<RankedRow<CandidateRow>> {
278
- const qualifierIDs = this.#qualifierRegionIDs(qualifier, country)
284
+ return this.#applyContainment(rows, this.#qualifierRegionIDs(qualifier, country), opts)
285
+ }
279
286
 
287
+ /**
288
+ * Stamp containment in the qualifier rows, inject contained misses, then partition contained-first.
289
+ *
290
+ * The qualifier rows are a parsed region's rows or one resolved parent place (#2804).
291
+ * The injection reads the same name key under the shape filters and under the sub-locality placetypes,
292
+ * so a dependent locality the locality band excludes re-enters the list when its parent contains it.
293
+ */
294
+ #applyContainment(
295
+ rows: Array<RankedRow<CandidateRow>>,
296
+ qualifierIDs: ReadonlySet<number>,
297
+ opts: ContainmentOptions
298
+ ): Array<RankedRow<CandidateRow>> {
280
299
  if (!qualifierIDs.size) {
281
300
  for (const row of rows) {
282
301
  row.containedByQualifier = false
@@ -326,7 +345,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
326
345
  // Also inject contained neighborhood-band namesakes (primary-keyed only).
327
346
  const bandIDs = ["neighbourhood", "macrohood", "microhood"]
328
347
  .map((placetype) => this.#placetypeToID.get(placetype))
329
- .filter((id): id is number => id !== undefined)
348
+ .filter((id) => typeof id === "number")
330
349
 
331
350
  if (bandIDs.length) {
332
351
  const bandSQL =
@@ -375,7 +394,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
375
394
  #postcodeAnchor(postcode: string, country?: string): { lat: number; lon: number } | null {
376
395
  const placetypeID = this.#placetypeToID.get("postalcode")
377
396
 
378
- if (placetypeID === undefined) return null
397
+ if (typeof placetypeID !== "number") return null
379
398
 
380
399
  const conds = ["name_key = ?", "placetype_id = ?"]
381
400
  const params: Array<string | number> = [postcode.replaceAll(/\s+/g, ""), placetypeID]
@@ -383,7 +402,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
383
402
  if (country) {
384
403
  const countryID = this.#countryToID.get(country.toUpperCase())
385
404
 
386
- if (countryID === undefined) return null // Unknown country in this artifact.
405
+ if (typeof countryID !== "number") return null // Unknown country in this artifact.
387
406
  conds.push("country_id = ?")
388
407
  params.push(countryID)
389
408
  }
@@ -457,7 +476,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
457
476
  if (query.country) {
458
477
  const cid = this.#countryToID.get(query.country.toUpperCase())
459
478
 
460
- if (cid === undefined) return [] // Unknown country in this artifact.
479
+ if (typeof cid !== "number") return [] // Unknown country in this artifact.
461
480
  filters.push("country_id = ?")
462
481
  filterParams.push(cid)
463
482
  }
@@ -468,7 +487,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
468
487
 
469
488
  const ids = expandPlacetypeFilter(want as readonly string[])
470
489
  .map((t) => this.#placetypeToID.get(t))
471
- .filter((v): v is number => v !== undefined)
490
+ .filter((v) => typeof v === "number")
472
491
 
473
492
  if (!ids.length) return []
474
493
  shapeFilters.push(`placetype_id IN (${ids.map(() => "?").join(",")})`)
@@ -534,16 +553,19 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
534
553
  }
535
554
  }
536
555
 
537
- // Typo-tolerant fallback for true name misses only, skipping postcodes and respecting the fuzzy-country scope.
556
+ // Typo-tolerant fallback for true name misses only, skipping postcodes.
557
+ // With no stated country, locale hint or parent to search within,
558
+ // a correction keeps its first letter.
559
+ // `Aucklnad` reaches Auckland, and `Anal Island` stays away from `Capual Island` in the Philippines.
538
560
  const fuzzyCountryID =
539
561
  !query.country && query.fuzzyCountry ? this.#countryToID.get(query.fuzzyCountry.toUpperCase()) : undefined
540
562
 
541
- const fuzzyScopedOut = !query.country && !!query.fuzzyCountry && typeof fuzzyCountryID !== "number"
563
+ const hasCountryEvidence = !!query.country || regionID !== null || typeof fuzzyCountryID === "number"
542
564
 
543
565
  if (
544
566
  !rows.length &&
545
567
  !wantsPostcode &&
546
- !fuzzyScopedOut &&
568
+ !(query.fuzzyCountry && !query.country && typeof fuzzyCountryID !== "number") &&
547
569
  this.#ftsProbe &&
548
570
  this.#nameKeyExistsProbe &&
549
571
  !this.#nameKeyExistsProbe.get(nameKey)
@@ -555,7 +577,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
555
577
 
556
578
  const ranked = hits
557
579
  .map((h) => ({ nk: String(h.name_key), s: wordFuzzySimilarity(nameKey, String(h.name_key)) }))
558
- .filter((h) => h.s >= WORD_FUZZY_MIN)
580
+ .filter((h) => h.s >= WORD_FUZZY_MIN && (hasCountryEvidence || h.nk[0] === nameKey[0]))
559
581
  // oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array. toSorted would double-allocate on a hot path
560
582
  .sort((a, b) => b.s - a.s)
561
583
 
@@ -599,7 +621,7 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
599
621
  const outsideThreshold: RankedRow<CandidateRow>[] = []
600
622
 
601
623
  for (const row of rows) {
602
- const distanceKm = haversineKm(anchor.lat, anchor.lon, Number(row.latitude), Number(row.longitude))
624
+ const distanceKm = haversine(anchor.lat, anchor.lon, Number(row.latitude), Number(row.longitude))
603
625
 
604
626
  if (distanceKm <= POSTCODE_CONTAINMENT_THRESHOLD_KM) {
605
627
  insideThreshold.push({ row, distanceKm })
@@ -617,14 +639,23 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
617
639
  }
618
640
 
619
641
  // Admin-containment re-rank runs last.
642
+ const containmentOptions: ContainmentOptions = {
643
+ nameKey,
644
+ strippedKey: normalizeLocalityForKey(stripLocalityQualifier(text)),
645
+ shapeFilters,
646
+ shapeParams,
647
+ limit,
648
+ }
649
+
620
650
  if (query.regionQualifier?.trim() && this.#qualifierProbe && this.#wantsLocality(query.placetype)) {
621
- rows = this.#applyAdminContainment(rows, query.regionQualifier.trim(), query.country ?? null, {
622
- nameKey,
623
- strippedKey: normalizeLocalityForKey(stripLocalityQualifier(text)),
624
- shapeFilters,
625
- shapeParams,
626
- limit,
627
- })
651
+ rows = this.#applyAdminContainment(rows, query.regionQualifier.trim(), query.country ?? null, containmentOptions)
652
+ } else if (
653
+ typeof query.parentQualifierID === "number" &&
654
+ this.#ancestorsProbe &&
655
+ this.#wantsLocality(query.placetype)
656
+ ) {
657
+ // A resolved parent qualifies by id, so no name probe runs (#2804).
658
+ rows = this.#applyContainment(rows, new Set([query.parentQualifierID]), containmentOptions)
628
659
  }
629
660
 
630
661
  const candidates = rows.map((row): PlaceCandidate => {
@@ -656,6 +687,8 @@ export class WOFCandidateTableLookup extends SQLiteLookup<CandidateDatabase> imp
656
687
 
657
688
  variantAliasExempted: row.variantExempted ? true : null,
658
689
 
690
+ matchedAlias: row.is_primary === 0 ? true : null,
691
+
659
692
  population: hasPopulation ? row.population : null,
660
693
  referential: hasPopulation ? referentialFromPopulation(row.population) : null,
661
694
  encyclopedic: null,
@@ -114,11 +114,7 @@ export function romanizeNameKey(key: string): string | null {
114
114
  out += CYRILLIC_TO_LATIN[ch] ?? ch
115
115
  }
116
116
 
117
- out = out
118
- .normalize("NFD")
119
- .replaceAll(/[̀-ͯ]/g, "")
120
- .replaceAll(/\s+/g, " ")
121
- .trim()
117
+ out = out.normalize("NFD").replaceAll(/[̀-ͯ]/g, "").replaceAll(/\s+/g, " ").trim()
122
118
 
123
119
  return /^[a-z0-9 ]*$/.test(out) ? out : null
124
120
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { compareReferential, referentialFromPopulation } from "@mailwoman/core/resolver"
8
- import { haversineKm } from "@mailwoman/spatial"
8
+ import { haversine } from "@mailwoman/spatial/geometries/point/distance"
9
9
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
10
10
 
11
11
  import { exactMatchIDs, officialNameIDs } from "#exact-match"
@@ -47,7 +47,7 @@ export function candidateFromSearchRow(
47
47
  score += weights.countryMatchBoost
48
48
  }
49
49
 
50
- if (query.parentID !== undefined) {
50
+ if (query.parentID) {
51
51
  score += row.parent_id === query.parentID ? weights.directChildBoost : weights.descendantBoost
52
52
  }
53
53
 
@@ -74,7 +74,7 @@ export function candidateFromSearchRow(
74
74
  let scoreTerm = 0
75
75
 
76
76
  for (const h of hints) {
77
- const d = haversineKm(h.lat, h.lon, row.lat, row.lon)
77
+ const d = haversine(h.lat, h.lon, row.lat, row.lon)
78
78
  const decay = h.weight / (1 + d / weights.proximityScaleKm)
79
79
  const prom = decay * weights.biasBoost
80
80
 
@@ -184,7 +184,7 @@ export function rankCandidates<DB>(
184
184
  .map((c) => c.id as number),
185
185
  query.text
186
186
  )
187
- : undefined
187
+ : null
188
188
 
189
189
  const kind = (c: PlaceCandidate): number => {
190
190
  if (!exactIDs.has(c.id as number)) return 0
package/lib/capitals.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * radius a centroid-drift allowance rather than a catchment.
16
16
  */
17
17
 
18
- import { haversineKm } from "@mailwoman/spatial"
18
+ import { haversine } from "@mailwoman/spatial/geometries/point/distance"
19
19
 
20
20
  import { normalizeLocalityForKey } from "#street/normalize"
21
21
 
@@ -129,7 +129,7 @@ export class CapitalIndex {
129
129
  if (
130
130
  point.level > best &&
131
131
  point.keys.has(key) &&
132
- haversineKm(latitude, longitude, point.latitude, point.longitude) <= CAPITAL_MATCH_RADIUS_KM
132
+ haversine(latitude, longitude, point.latitude, point.longitude) <= CAPITAL_MATCH_RADIUS_KM
133
133
  ) {
134
134
  best = point.level
135
135
  }
@@ -7,8 +7,8 @@
7
7
  */
8
8
 
9
9
  import { pathExists } from "@mailwoman/core/fs/readers/stat"
10
- import { isStrictlyFiner } from "@mailwoman/core/resources/whosonfirst"
11
- import { haversineKm } from "@mailwoman/spatial"
10
+ import { isStrictlyFiner } from "@mailwoman/gazetteer/whosonfirst"
11
+ import { haversine } from "@mailwoman/spatial/geometries/point/distance"
12
12
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
13
13
  import { type PathBuilderLike, resolvePath } from "path-ts"
14
14
  import { TSVSpliterator } from "spliterator"
@@ -208,7 +208,7 @@ export async function resurrectCurrencyHoles(ctx: {
208
208
  // A live row blocks unless it is strictly finer than the dead one.
209
209
  // An unranked placetype also blocks because the failure mode is inventing a place.
210
210
  const liveNear = liveStmt.all(cc, name).some((row) => {
211
- if (haversineKm(dLat, dLon, Number(row.latitude), Number(row.longitude)) > CURRENCY_BACKFILL_RADIUS_KM) {
211
+ if (haversine(dLat, dLon, Number(row.latitude), Number(row.longitude)) > CURRENCY_BACKFILL_RADIUS_KM) {
212
212
  return false
213
213
  }
214
214
 
@@ -222,7 +222,7 @@ export async function resurrectCurrencyHoles(ctx: {
222
222
  }
223
223
 
224
224
  const near = (attestors.get(pkey) ?? []).filter(
225
- (g) => haversineKm(dLat, dLon, g.lat, g.lon) <= CURRENCY_BACKFILL_RADIUS_KM
225
+ (g) => haversine(dLat, dLon, g.lat, g.lon) <= CURRENCY_BACKFILL_RADIUS_KM
226
226
  )
227
227
 
228
228
  if (!near.length) {
@@ -135,7 +135,7 @@ class FSTReader implements AncestrieReaderLike<PlaceEntry> {
135
135
 
136
136
  entriesAt(stateID: number, limit?: number): AncestrieRecord<PlaceEntry>[] {
137
137
  const places = this.#fst.accepting(stateID)
138
- const selected = limit === undefined ? places : topByReferential(places, limit)
138
+ const selected = typeof limit !== "number" ? places : topByReferential(places, limit)
139
139
 
140
140
  return selected.map((entry) => {
141
141
  this.#chains.set(entry.wofID, entry.parentChain)