@mailwoman/resolver-wof-sqlite 10.0.0 → 10.2.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/README.md +9 -8
- package/lib/address/point/interpolation.ts +45 -39
- package/lib/address/point/schema.ts +17 -40
- package/lib/address/point.ts +316 -0
- package/lib/{address/index.ts → address.ts} +1 -1
- package/lib/ancestry/backfill.ts +20 -86
- package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
- package/lib/build/candidate.ts +122 -174
- package/lib/build/population-corrections.ts +64 -0
- package/lib/build/slim.ts +44 -114
- package/lib/candidate/alias-bags.ts +16 -17
- package/lib/candidate/ancestors/schema.ts +77 -61
- package/lib/candidate/ancestors/sidecar.ts +15 -22
- package/lib/candidate/country-display-names.ts +6 -20
- package/lib/candidate/extract-fold.ts +10 -55
- package/lib/candidate/fts.ts +5 -23
- package/lib/candidate/importance.ts +65 -68
- package/lib/candidate/lookup.ts +124 -415
- package/lib/candidate/name-roles.ts +15 -33
- package/lib/candidate/own-name.ts +35 -32
- package/lib/candidate/place-attrs.ts +20 -13
- package/lib/candidate/schema.ts +29 -65
- package/lib/candidate/scoring.ts +54 -93
- package/lib/capital-schema.ts +21 -17
- package/lib/capitals.ts +29 -27
- package/lib/coincident-roles.ts +85 -55
- package/lib/convention/schema.ts +15 -16
- package/lib/convention.ts +144 -0
- package/lib/coverage-manifest-schema.ts +40 -45
- package/lib/currency-backfill.ts +34 -79
- package/lib/env.ts +1 -1
- package/lib/exact-match.ts +32 -21
- package/lib/extracts.ts +97 -74
- package/lib/fst/autocomplete.ts +54 -37
- package/lib/fst/builder.ts +37 -41
- package/lib/fst/deserialize-web.ts +16 -17
- package/lib/fst/format.ts +41 -23
- package/lib/fst/freshness.ts +43 -120
- package/lib/fst/matcher.ts +7 -5
- package/lib/fst/serialize.ts +54 -74
- package/lib/fst/types.ts +78 -75
- package/lib/{fst/index.ts → fst.ts} +1 -0
- package/lib/fts/query.ts +5 -36
- package/lib/fts.ts +280 -0
- package/lib/geonames/aliases.ts +73 -144
- package/lib/geonames/postal.ts +162 -71
- package/lib/index.ts +9 -8
- package/lib/interpolation.ts +48 -100
- package/lib/lookup.ts +155 -270
- package/lib/name-score.ts +9 -7
- package/lib/nsul/lookup.ts +37 -54
- package/lib/nsul/schema.ts +16 -43
- package/lib/paths.ts +159 -0
- package/lib/place-importance-schema.ts +62 -169
- package/lib/poi/lookup.ts +104 -121
- package/lib/poi/schema.ts +42 -26
- package/lib/polygon-schema.ts +10 -6
- package/lib/postal/city/alias/lookup.ts +22 -40
- package/lib/postal/city/alias/schema.ts +14 -10
- package/lib/postal/city/candidate-schema.ts +26 -20
- package/lib/postcode-point-lookup.ts +8 -27
- package/lib/primary-preference.ts +46 -126
- package/lib/proximity-rerank.ts +44 -33
- package/lib/ranking-weights.ts +105 -78
- package/lib/region-keys.ts +70 -42
- package/lib/reverse.ts +149 -86
- package/lib/schema.ts +73 -50
- package/lib/search-fetch.ts +90 -68
- package/lib/sqlite/convention-source.ts +15 -23
- package/lib/sqlite/utils.ts +19 -22
- package/lib/street/centroid/schema.ts +37 -42
- package/lib/street/{centroid/index.ts → centroid.ts} +16 -25
- package/lib/street/morphology/fst/builder.ts +60 -42
- package/lib/street/morphology/fst/loader.ts +40 -32
- package/lib/street/name-lookup.ts +45 -41
- package/lib/street/normalize.ts +88 -219
- package/lib/street/segment-schema.ts +116 -43
- package/lib/{street/index.ts → street.ts} +1 -1
- package/lib/types.ts +109 -150
- package/lib/unified-schema.ts +29 -29
- package/lib/uprn/existence.ts +30 -19
- package/lib/uprn/lookup.ts +29 -68
- package/lib/uprn/schema.ts +18 -40
- package/lib/weights-overlay-linker.ts +236 -252
- package/out/address/point/interpolation.d.ts +20 -23
- package/out/address/point/interpolation.d.ts.map +1 -1
- package/out/address/point/interpolation.js +42 -39
- package/out/address/point/interpolation.js.map +1 -1
- package/out/address/point/schema.d.ts +12 -35
- package/out/address/point/schema.d.ts.map +1 -1
- package/out/address/point/schema.js +3 -19
- package/out/address/point/schema.js.map +1 -1
- package/out/address/point.d.ts +57 -0
- package/out/address/point.d.ts.map +1 -0
- package/out/address/point.js +206 -0
- package/out/address/point.js.map +1 -0
- package/out/{address/index.js → address.d.ts} +2 -2
- package/out/address.d.ts.map +1 -0
- package/out/{address/index.d.ts → address.js} +2 -2
- package/out/address.js.map +1 -0
- package/out/ancestry/backfill.d.ts +11 -64
- package/out/ancestry/backfill.d.ts.map +1 -1
- package/out/ancestry/backfill.js +14 -74
- package/out/ancestry/backfill.js.map +1 -1
- package/out/ancestry.d.ts +49 -0
- package/out/ancestry.d.ts.map +1 -0
- package/out/ancestry.js +59 -0
- package/out/ancestry.js.map +1 -0
- package/out/build/candidate.d.ts +82 -112
- package/out/build/candidate.d.ts.map +1 -1
- package/out/build/candidate.js +17 -100
- package/out/build/candidate.js.map +1 -1
- package/out/build/population-corrections.d.ts +52 -0
- package/out/build/population-corrections.d.ts.map +1 -0
- package/out/build/population-corrections.js +40 -0
- package/out/build/population-corrections.js.map +1 -0
- package/out/build/slim.d.ts +8 -44
- package/out/build/slim.d.ts.map +1 -1
- package/out/build/slim.js +33 -94
- package/out/build/slim.js.map +1 -1
- package/out/candidate/alias-bags.d.ts +6 -6
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +16 -17
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/candidate/ancestors/schema.d.ts +73 -57
- package/out/candidate/ancestors/schema.d.ts.map +1 -1
- package/out/candidate/ancestors/schema.js +56 -48
- package/out/candidate/ancestors/schema.js.map +1 -1
- package/out/candidate/ancestors/sidecar.d.ts +3 -8
- package/out/candidate/ancestors/sidecar.d.ts.map +1 -1
- package/out/candidate/ancestors/sidecar.js +15 -22
- package/out/candidate/ancestors/sidecar.js.map +1 -1
- package/out/candidate/country-display-names.d.ts +5 -15
- package/out/candidate/country-display-names.d.ts.map +1 -1
- package/out/candidate/country-display-names.js +6 -20
- package/out/candidate/country-display-names.js.map +1 -1
- package/out/candidate/extract-fold.d.ts +7 -15
- package/out/candidate/extract-fold.d.ts.map +1 -1
- package/out/candidate/extract-fold.js +5 -47
- package/out/candidate/extract-fold.js.map +1 -1
- package/out/candidate/fts.d.ts +5 -23
- package/out/candidate/fts.d.ts.map +1 -1
- package/out/candidate/fts.js +5 -23
- package/out/candidate/fts.js.map +1 -1
- package/out/candidate/importance.d.ts +60 -65
- package/out/candidate/importance.d.ts.map +1 -1
- package/out/candidate/importance.js +56 -63
- package/out/candidate/importance.js.map +1 -1
- package/out/candidate/lookup.d.ts +18 -55
- package/out/candidate/lookup.d.ts.map +1 -1
- package/out/candidate/lookup.js +82 -385
- package/out/candidate/lookup.js.map +1 -1
- package/out/candidate/name-roles.d.ts +9 -21
- package/out/candidate/name-roles.d.ts.map +1 -1
- package/out/candidate/name-roles.js +15 -33
- package/out/candidate/name-roles.js.map +1 -1
- package/out/candidate/own-name.d.ts +23 -25
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +35 -32
- package/out/candidate/own-name.js.map +1 -1
- package/out/candidate/place-attrs.d.ts +20 -13
- package/out/candidate/place-attrs.d.ts.map +1 -1
- package/out/candidate/place-attrs.js +4 -5
- package/out/candidate/place-attrs.js.map +1 -1
- package/out/candidate/schema.d.ts +27 -62
- package/out/candidate/schema.d.ts.map +1 -1
- package/out/candidate/schema.js +10 -22
- package/out/candidate/schema.js.map +1 -1
- package/out/candidate/scoring.d.ts +7 -8
- package/out/candidate/scoring.d.ts.map +1 -1
- package/out/candidate/scoring.js +52 -89
- package/out/candidate/scoring.js.map +1 -1
- package/out/capital-schema.d.ts +21 -17
- package/out/capital-schema.d.ts.map +1 -1
- package/out/capital-schema.js +17 -15
- package/out/capital-schema.js.map +1 -1
- package/out/capitals.d.ts +27 -25
- package/out/capitals.d.ts.map +1 -1
- package/out/capitals.js +23 -21
- package/out/capitals.js.map +1 -1
- package/out/coincident-roles.d.ts +60 -43
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +72 -50
- package/out/coincident-roles.js.map +1 -1
- package/out/convention/schema.d.ts +14 -14
- package/out/convention/schema.d.ts.map +1 -1
- package/out/convention/schema.js +6 -4
- package/out/convention/schema.js.map +1 -1
- package/out/convention.d.ts +97 -0
- package/out/convention.d.ts.map +1 -0
- package/out/convention.js +81 -0
- package/out/convention.js.map +1 -0
- package/out/coverage-manifest-schema.d.ts +34 -39
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +25 -34
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/currency-backfill.d.ts +16 -38
- package/out/currency-backfill.d.ts.map +1 -1
- package/out/currency-backfill.js +19 -46
- package/out/currency-backfill.js.map +1 -1
- package/out/env.d.ts +1 -1
- package/out/env.js +1 -1
- package/out/exact-match.d.ts +19 -10
- package/out/exact-match.d.ts.map +1 -1
- package/out/exact-match.js +32 -21
- package/out/exact-match.js.map +1 -1
- package/out/extracts.d.ts +66 -55
- package/out/extracts.d.ts.map +1 -1
- package/out/extracts.js +63 -55
- package/out/extracts.js.map +1 -1
- package/out/fst/autocomplete.d.ts +29 -21
- package/out/fst/autocomplete.d.ts.map +1 -1
- package/out/fst/autocomplete.js +39 -28
- package/out/fst/autocomplete.js.map +1 -1
- package/out/fst/builder.d.ts.map +1 -1
- package/out/fst/builder.js +37 -41
- package/out/fst/builder.js.map +1 -1
- package/out/fst/deserialize-web.d.ts +4 -4
- package/out/fst/deserialize-web.d.ts.map +1 -1
- package/out/fst/deserialize-web.js +15 -16
- package/out/fst/deserialize-web.js.map +1 -1
- package/out/fst/format.d.ts +41 -23
- package/out/fst/format.d.ts.map +1 -1
- package/out/fst/format.js +41 -23
- package/out/fst/format.js.map +1 -1
- package/out/fst/freshness.d.ts +19 -81
- package/out/fst/freshness.d.ts.map +1 -1
- package/out/fst/freshness.js +31 -94
- package/out/fst/freshness.js.map +1 -1
- package/out/fst/matcher.d.ts +5 -4
- package/out/fst/matcher.d.ts.map +1 -1
- package/out/fst/matcher.js +2 -2
- package/out/fst/matcher.js.map +1 -1
- package/out/fst/serialize.d.ts +28 -28
- package/out/fst/serialize.d.ts.map +1 -1
- package/out/fst/serialize.js +53 -71
- package/out/fst/serialize.js.map +1 -1
- package/out/fst/types.d.ts +78 -74
- package/out/fst/types.d.ts.map +1 -1
- package/out/fst/types.js +1 -4
- package/out/fst/types.js.map +1 -1
- package/out/{fst/index.js → fst.d.ts} +2 -1
- package/out/fst.d.ts.map +1 -0
- package/out/{fst/index.d.ts → fst.js} +2 -1
- package/out/fst.js.map +1 -0
- package/out/fts/query.d.ts +2 -27
- package/out/fts/query.d.ts.map +1 -1
- package/out/fts/query.js +5 -36
- package/out/fts/query.js.map +1 -1
- package/out/fts.d.ts +126 -0
- package/out/fts.d.ts.map +1 -0
- package/out/fts.js +188 -0
- package/out/fts.js.map +1 -0
- package/out/geonames/aliases.d.ts +26 -62
- package/out/geonames/aliases.d.ts.map +1 -1
- package/out/geonames/aliases.js +45 -117
- package/out/geonames/aliases.js.map +1 -1
- package/out/geonames/postal.d.ts +84 -44
- package/out/geonames/postal.d.ts.map +1 -1
- package/out/geonames/postal.js +115 -73
- package/out/geonames/postal.js.map +1 -1
- package/out/{geonames/index.js → geonames.d.ts} +1 -1
- package/out/geonames.d.ts.map +1 -0
- package/out/{geonames/index.d.ts → geonames.js} +1 -1
- package/out/geonames.js.map +1 -0
- package/out/index.d.ts +9 -8
- package/out/index.d.ts.map +1 -1
- package/out/index.js +6 -5
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +18 -55
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +27 -69
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +23 -51
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +136 -234
- package/out/lookup.js.map +1 -1
- package/out/name-score.d.ts +2 -2
- package/out/name-score.d.ts.map +1 -1
- package/out/name-score.js +9 -7
- package/out/name-score.js.map +1 -1
- package/out/nsul/lookup.d.ts +33 -34
- package/out/nsul/lookup.d.ts.map +1 -1
- package/out/nsul/lookup.js +26 -40
- package/out/nsul/lookup.js.map +1 -1
- package/out/nsul/schema.d.ts +16 -43
- package/out/nsul/schema.d.ts.map +1 -1
- package/out/nsul/schema.js +9 -35
- package/out/nsul/schema.js.map +1 -1
- package/out/{nsul/index.d.ts → nsul.d.ts} +1 -1
- package/out/nsul.d.ts.map +1 -0
- package/out/{nsul/index.js → nsul.js} +1 -1
- package/out/nsul.js.map +1 -0
- package/out/paths.d.ts +124 -0
- package/out/paths.d.ts.map +1 -0
- package/out/paths.js +104 -0
- package/out/paths.js.map +1 -0
- package/out/place-importance-schema.d.ts +51 -143
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +28 -143
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts +40 -47
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +69 -96
- package/out/poi/lookup.js.map +1 -1
- package/out/poi/schema.d.ts +41 -25
- package/out/poi/schema.d.ts.map +1 -1
- package/out/poi/schema.js +25 -17
- package/out/poi/schema.js.map +1 -1
- package/out/{poi/index.js → poi.d.ts} +1 -1
- package/out/poi.d.ts.map +1 -0
- package/out/{poi/index.d.ts → poi.js} +1 -1
- package/out/poi.js.map +1 -0
- package/out/polygon-schema.d.ts +10 -6
- package/out/polygon-schema.d.ts.map +1 -1
- package/out/polygon-schema.js +2 -2
- package/out/polygon-schema.js.map +1 -1
- package/out/postal/city/alias/lookup.d.ts +20 -23
- package/out/postal/city/alias/lookup.d.ts.map +1 -1
- package/out/postal/city/alias/lookup.js +17 -30
- package/out/postal/city/alias/lookup.js.map +1 -1
- package/out/postal/city/alias/schema.d.ts +14 -10
- package/out/postal/city/alias/schema.d.ts.map +1 -1
- package/out/postal/city/alias/schema.js +10 -8
- package/out/postal/city/alias/schema.js.map +1 -1
- package/out/postal/city/candidate-schema.d.ts +25 -19
- package/out/postal/city/candidate-schema.d.ts.map +1 -1
- package/out/postal/city/candidate-schema.js +21 -17
- package/out/postal/city/candidate-schema.js.map +1 -1
- package/out/{postal/index.d.ts → postal.d.ts} +1 -1
- package/out/postal.d.ts.map +1 -0
- package/out/{postal/index.js → postal.js} +1 -1
- package/out/postal.js.map +1 -0
- package/out/postcode-point-lookup.d.ts +6 -24
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/postcode-point-lookup.js +5 -17
- package/out/postcode-point-lookup.js.map +1 -1
- package/out/primary-preference.d.ts +38 -92
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +15 -99
- package/out/primary-preference.js.map +1 -1
- package/out/proximity-rerank.d.ts +41 -30
- package/out/proximity-rerank.d.ts.map +1 -1
- package/out/proximity-rerank.js +38 -29
- package/out/proximity-rerank.js.map +1 -1
- package/out/ranking-weights.d.ts +98 -64
- package/out/ranking-weights.d.ts.map +1 -1
- package/out/ranking-weights.js +26 -25
- package/out/ranking-weights.js.map +1 -1
- package/out/region-keys.d.ts +50 -31
- package/out/region-keys.d.ts.map +1 -1
- package/out/region-keys.js +68 -40
- package/out/region-keys.js.map +1 -1
- package/out/reverse.d.ts +78 -50
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +98 -61
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +72 -49
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +4 -5
- package/out/schema.js.map +1 -1
- package/out/search-fetch.d.ts +16 -12
- package/out/search-fetch.d.ts.map +1 -1
- package/out/search-fetch.js +83 -63
- package/out/search-fetch.js.map +1 -1
- package/out/sqlite/convention-source.d.ts +12 -16
- package/out/sqlite/convention-source.d.ts.map +1 -1
- package/out/sqlite/convention-source.js +13 -22
- package/out/sqlite/convention-source.js.map +1 -1
- package/out/sqlite/utils.d.ts +19 -19
- package/out/sqlite/utils.d.ts.map +1 -1
- package/out/sqlite/utils.js +14 -15
- package/out/sqlite/utils.js.map +1 -1
- package/out/street/centroid/schema.d.ts +37 -42
- package/out/street/centroid/schema.d.ts.map +1 -1
- package/out/street/centroid/schema.js +9 -21
- package/out/street/centroid/schema.js.map +1 -1
- package/out/street/centroid.d.ts +30 -0
- package/out/street/centroid.d.ts.map +1 -0
- package/out/street/{centroid/index.js → centroid.js} +13 -24
- package/out/street/centroid.js.map +1 -0
- package/out/street/morphology/fst/builder.d.ts +20 -10
- package/out/street/morphology/fst/builder.d.ts.map +1 -1
- package/out/street/morphology/fst/builder.js +44 -34
- package/out/street/morphology/fst/builder.js.map +1 -1
- package/out/street/morphology/fst/loader.d.ts +33 -26
- package/out/street/morphology/fst/loader.d.ts.map +1 -1
- package/out/street/morphology/fst/loader.js +23 -22
- package/out/street/morphology/fst/loader.js.map +1 -1
- package/out/street/name-lookup.d.ts +24 -18
- package/out/street/name-lookup.d.ts.map +1 -1
- package/out/street/name-lookup.js +34 -35
- package/out/street/name-lookup.js.map +1 -1
- package/out/street/normalize.d.ts +35 -136
- package/out/street/normalize.d.ts.map +1 -1
- package/out/street/normalize.js +77 -194
- package/out/street/normalize.js.map +1 -1
- package/out/street/segment-schema.d.ts +79 -39
- package/out/street/segment-schema.d.ts.map +1 -1
- package/out/street/segment-schema.js +71 -21
- package/out/street/segment-schema.js.map +1 -1
- package/out/{street/index.js → street.d.ts} +2 -2
- package/out/street.d.ts.map +1 -0
- package/out/{street/index.d.ts → street.js} +2 -2
- package/out/street.js.map +1 -0
- package/out/types.d.ts +84 -149
- package/out/types.d.ts.map +1 -1
- package/out/types.js +0 -6
- package/out/types.js.map +1 -1
- package/out/unified-schema.d.ts +8 -12
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +29 -29
- package/out/unified-schema.js.map +1 -1
- package/out/uprn/existence.d.ts +28 -17
- package/out/uprn/existence.d.ts.map +1 -1
- package/out/uprn/existence.js +26 -16
- package/out/uprn/existence.js.map +1 -1
- package/out/uprn/lookup.d.ts +14 -44
- package/out/uprn/lookup.d.ts.map +1 -1
- package/out/uprn/lookup.js +21 -57
- package/out/uprn/lookup.js.map +1 -1
- package/out/uprn/schema.d.ts +18 -40
- package/out/uprn/schema.d.ts.map +1 -1
- package/out/uprn/schema.js +10 -32
- package/out/uprn/schema.js.map +1 -1
- package/out/{uprn/index.d.ts → uprn.d.ts} +1 -1
- package/out/uprn.d.ts.map +1 -0
- package/out/{uprn/index.js → uprn.js} +1 -1
- package/out/uprn.js.map +1 -0
- package/out/weights-overlay-linker.d.ts +138 -144
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +105 -159
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +91 -596
- package/lib/address/point/index.ts +0 -328
- package/lib/convention/index.ts +0 -154
- package/lib/fts/index.ts +0 -361
- package/out/address/index.d.ts.map +0 -1
- package/out/address/index.js.map +0 -1
- package/out/address/point/index.d.ts +0 -53
- package/out/address/point/index.d.ts.map +0 -1
- package/out/address/point/index.js +0 -222
- package/out/address/point/index.js.map +0 -1
- package/out/ancestry/index.d.ts +0 -45
- package/out/ancestry/index.d.ts.map +0 -1
- package/out/ancestry/index.js +0 -55
- package/out/ancestry/index.js.map +0 -1
- package/out/convention/index.d.ts +0 -106
- package/out/convention/index.d.ts.map +0 -1
- package/out/convention/index.js +0 -93
- package/out/convention/index.js.map +0 -1
- package/out/fst/index.d.ts.map +0 -1
- package/out/fst/index.js.map +0 -1
- package/out/fts/index.d.ts +0 -184
- package/out/fts/index.d.ts.map +0 -1
- package/out/fts/index.js +0 -277
- package/out/fts/index.js.map +0 -1
- package/out/geonames/index.d.ts.map +0 -1
- package/out/geonames/index.js.map +0 -1
- package/out/nsul/index.d.ts.map +0 -1
- package/out/nsul/index.js.map +0 -1
- package/out/poi/index.d.ts.map +0 -1
- package/out/poi/index.js.map +0 -1
- package/out/postal/index.d.ts.map +0 -1
- package/out/postal/index.js.map +0 -1
- package/out/street/centroid/index.d.ts +0 -40
- package/out/street/centroid/index.d.ts.map +0 -1
- package/out/street/centroid/index.js.map +0 -1
- package/out/street/index.d.ts.map +0 -1
- package/out/street/index.js.map +0 -1
- package/out/uprn/index.d.ts.map +0 -1
- package/out/uprn/index.js.map +0 -1
- /package/lib/{geonames/index.ts → geonames.ts} +0 -0
- /package/lib/{nsul/index.ts → nsul.ts} +0 -0
- /package/lib/{poi/index.ts → poi.ts} +0 -0
- /package/lib/{postal/index.ts → postal.ts} +0 -0
- /package/lib/{uprn/index.ts → uprn.ts} +0 -0
package/lib/poi/lookup.ts
CHANGED
|
@@ -3,59 +3,47 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Node reader for `poi.db` (spec §3.4)
|
|
7
|
-
* `
|
|
8
|
-
* artifact
|
|
6
|
+
* Node reader for `poi.db` (spec §3.4), the res-9 k-ring reader over the clustered `poi`
|
|
7
|
+
* `without rowid` B-tree `poi-schema.ts` builds. Category, brand and name searches share one
|
|
8
|
+
* artifact. Each path has a method that returns its details.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* haversine distance from `center` after every ring.
|
|
14
|
-
* - **Brand**: NOT a k-ring walk. Brand rows are globally sparse (~0.31% of poi.db; median nearest
|
|
15
|
-
* tagged instance ~110 km), so ring expansion could never reach them. Instead a single brand-wide
|
|
16
|
-
* indexed fetch on `brand_wikidata` (the partial `poi_brand_wikidata` index) pulls EVERY row for
|
|
17
|
-
* the QID — category unconstrained — then haversine-sorts from `center` and takes the nearest
|
|
18
|
-
* `limit`, bounded by a `BRAND_MAX_DISTANCE_KM` sanity radius.
|
|
19
|
-
* - **Name**: FTS5 `MATCH` against the `poi_search` virtual table, hydrated back to full rows by
|
|
20
|
-
* `name_key`. No center required; if one is given, hits are still distance-sorted.
|
|
21
|
-
*
|
|
22
|
-
* `latLngToCell`/`gridDisk` come from `h3-js`; the 48-bit short-cell packing that turns a raw H3
|
|
23
|
-
* cell into the integer `poi.h3_cell` stores is `@mailwoman/spatial`'s `shortCellToInt` — that math
|
|
24
|
-
* is NEVER reimplemented here (see AGENTS.md on `@mailwoman/spatial` being the one true home for
|
|
25
|
-
* it).
|
|
10
|
+
* `latLngToCell` and `gridDisk` come from `h3-js`. The 48-bit short-cell packing that turns a raw
|
|
11
|
+
* H3 cell into the integer `poi.h3_cell` stores is `@mailwoman/spatial`'s `shortCellToInt`, and
|
|
12
|
+
* that math is never reimplemented here.
|
|
26
13
|
*/
|
|
27
14
|
|
|
28
15
|
import { allRows } from "@mailwoman/core/utils"
|
|
29
16
|
import { haversineKm, shortCellToInt, type H3Cell } from "@mailwoman/spatial"
|
|
30
|
-
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
17
|
+
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
18
|
+
import { SQLiteLookup, type SQLiteLookupOptions } from "@mailwoman/sqlite/lookup"
|
|
31
19
|
import { gridDisk, latLngToCell } from "h3-js"
|
|
32
20
|
|
|
33
21
|
import type { POICategoryCodeTable, POIDatabase, POITable } from "#poi/schema"
|
|
34
22
|
/**
|
|
35
|
-
* Resolution the `poi` table's `h3_cell` column is keyed at
|
|
23
|
+
* Resolution the `poi` table's `h3_cell` column is keyed at, matching the builder (spec §3.4).
|
|
36
24
|
*/
|
|
37
25
|
export const POI_H3_RESOLUTION = 9
|
|
38
26
|
|
|
39
27
|
/**
|
|
40
|
-
* Ring budget default: 16 res-9 k-rings
|
|
41
|
-
*
|
|
28
|
+
* Ring budget default: 16 res-9 k-rings, about 5.4 km at the corner and 4.3 km worst-case.
|
|
29
|
+
*
|
|
30
|
+
* Category path only, since the brand path ignores rings entirely.
|
|
31
|
+
* Dense categories never reach this ceiling, because the loop breaks once a ring accumulates `limit` rows.
|
|
42
32
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* unaffected: the loop breaks the ring it accumulates `limit` rows (cafe@Paris fills 20 by ring 2), so this ceiling
|
|
47
|
-
* never enters their probe budget. Only sparse-but-present categories that never reach `limit` scan the fuller budget —
|
|
48
|
-
* a cold, one-shot `mailwoman poi` path, not per-keystroke. 16 (not the bare threshold 14) leaves ~2 rings of margin so
|
|
49
|
-
* the radius is stable against small db rebuilds, while staying ~4x tighter than the board's 25 km "roughly right
|
|
50
|
-
* place" window (no wrong-city false positives). The browser reader passes its own smaller `maxRings` and is
|
|
51
|
-
* untouched.
|
|
33
|
+
* Sparse-but-present categories that never reach `limit` scan the fuller budget.
|
|
34
|
+
* The extra rings keep the radius stable across small DB rebuilds.
|
|
35
|
+
* The browser reader passes its own smaller `maxRings`.
|
|
52
36
|
*/
|
|
53
37
|
const DEFAULT_MAX_RINGS = 16
|
|
54
38
|
|
|
55
39
|
/**
|
|
56
|
-
* Brand sanity radius (km)
|
|
57
|
-
*
|
|
58
|
-
*
|
|
40
|
+
* Brand sanity radius (km).
|
|
41
|
+
*
|
|
42
|
+
* The brand-wide fetch returns the global nearest at any distance, so this drops
|
|
43
|
+
* hits far enough away to be certainly on another continent.
|
|
44
|
+
* "Applebee's near Marseille" comes back empty instead of with a 5,700 km hit.
|
|
45
|
+
*
|
|
46
|
+
* This is a product bound, since the index already makes the fetch cheap regardless of distance.
|
|
59
47
|
*/
|
|
60
48
|
const BRAND_MAX_DISTANCE_KM = 500
|
|
61
49
|
|
|
@@ -66,16 +54,18 @@ const DEFAULT_LIMIT = 20
|
|
|
66
54
|
|
|
67
55
|
export interface POISearchQuery {
|
|
68
56
|
/**
|
|
69
|
-
* Poi-taxonomy category
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* leaf per cell and unions the rows
|
|
76
|
-
*
|
|
57
|
+
* Poi-taxonomy category ids (string side of the dictionary) to search.
|
|
58
|
+
*
|
|
59
|
+
* A single category is a one-element list.
|
|
60
|
+
* A canonical Overture `taxonomy.primary` category passes every leaf it rolls up into,
|
|
61
|
+
* as in `supermarket` → `grocery_store`, `organic_grocery_store`, ….
|
|
62
|
+
*
|
|
63
|
+
* The k-ring walk probes every resolvable leaf per cell and unions the rows.
|
|
64
|
+
* Unknown leaves are skipped.
|
|
65
|
+
*
|
|
66
|
+
* Ignored when `brandWikidata` is set, since brand wins.
|
|
77
67
|
*/
|
|
78
|
-
categoryIDs?: string[]
|
|
68
|
+
categoryIDs?: readonly string[]
|
|
79
69
|
/**
|
|
80
70
|
* Wikidata QID for brand-exact search.
|
|
81
71
|
*/
|
|
@@ -85,12 +75,17 @@ export interface POISearchQuery {
|
|
|
85
75
|
*/
|
|
86
76
|
name?: string
|
|
87
77
|
/**
|
|
88
|
-
* Search center.
|
|
78
|
+
* Search center.
|
|
79
|
+
*
|
|
80
|
+
* Required for category/brand queries (k-ring expansion).
|
|
89
81
|
*/
|
|
90
82
|
center?: { latitude: number; longitude: number }
|
|
91
83
|
/**
|
|
92
|
-
* Ring budget: how many res-9 k-rings to expand before giving up
|
|
93
|
-
*
|
|
84
|
+
* Ring budget: how many res-9 k-rings to expand before giving up.
|
|
85
|
+
*
|
|
86
|
+
* Counts ring 0, so k reaches `maxRings - 1`.
|
|
87
|
+
*
|
|
88
|
+
* @defaultValue `16` ≈ ~5.4 km
|
|
94
89
|
*/
|
|
95
90
|
maxRings?: number
|
|
96
91
|
limit?: number
|
|
@@ -105,25 +100,25 @@ export interface POISearchHit {
|
|
|
105
100
|
country: string
|
|
106
101
|
confidence: number
|
|
107
102
|
/**
|
|
108
|
-
* Overture
|
|
103
|
+
* Overture gers id.
|
|
104
|
+
*
|
|
105
|
+
* Nullable metadata, never a key.
|
|
106
|
+
* See `POITable.gers_id`.
|
|
109
107
|
*/
|
|
110
108
|
gersID: string | null
|
|
111
|
-
distanceM
|
|
109
|
+
distanceM: number | null
|
|
112
110
|
}
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
*/
|
|
122
|
-
database?: DatabaseClient<DB>
|
|
123
|
-
}
|
|
112
|
+
/**
|
|
113
|
+
* Where a {@link POILookup} reads from.
|
|
114
|
+
*
|
|
115
|
+
* The source is a `poi.db` built by the POI builder, opened read-only,
|
|
116
|
+
* or a connection the caller already holds.
|
|
117
|
+
*/
|
|
118
|
+
export type POILookupOpts<DB extends POIDatabase = POIDatabase> = SQLiteLookupOptions<DB>
|
|
124
119
|
|
|
125
120
|
/**
|
|
126
|
-
* The `poi` columns every search mode hydrates
|
|
121
|
+
* The `poi` columns every search mode hydrates, a typed projection of the shared {@link POITable}.
|
|
127
122
|
*/
|
|
128
123
|
type POIRow = Pick<
|
|
129
124
|
POITable,
|
|
@@ -141,13 +136,7 @@ type POIRow = Pick<
|
|
|
141
136
|
/**
|
|
142
137
|
* Node reader over `poi.db`.
|
|
143
138
|
*/
|
|
144
|
-
export class POILookup<DB extends POIDatabase = POIDatabase>
|
|
145
|
-
#db: DatabaseClient<DB>
|
|
146
|
-
/**
|
|
147
|
-
* Resources this instance opened. A connection handed in by a caller is NOT in here, so disposal cannot reach it —
|
|
148
|
-
* ownership is membership rather than a flag a later branch has to check.
|
|
149
|
-
*/
|
|
150
|
-
readonly #resources = new DisposableStack()
|
|
139
|
+
export class POILookup<DB extends POIDatabase = POIDatabase> extends SQLiteLookup<DB> {
|
|
151
140
|
readonly #categoryToID = new Map<string, number>()
|
|
152
141
|
readonly #idToCategory = new Map<number, string>()
|
|
153
142
|
|
|
@@ -156,39 +145,36 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
156
145
|
*/
|
|
157
146
|
readonly #categoryCellProbe: ReturnType<DatabaseClient["prepare"]>
|
|
158
147
|
/**
|
|
159
|
-
* `brand_wikidata` →
|
|
148
|
+
* `brand_wikidata` → all of a brand's rows globally (partial-index range-scan).
|
|
149
|
+
* The sort happens in JS.
|
|
160
150
|
*/
|
|
161
151
|
readonly #brandProbe: ReturnType<DatabaseClient["prepare"]>
|
|
162
152
|
/**
|
|
163
|
-
* FTS5 `
|
|
153
|
+
* FTS5 `match` over `poi_search`, returning candidate `name_key`s to hydrate.
|
|
164
154
|
*/
|
|
165
155
|
readonly #nameFTSProbe: ReturnType<DatabaseClient["prepare"]>
|
|
166
156
|
|
|
167
157
|
constructor(opts: POILookupOpts<DB>) {
|
|
168
|
-
|
|
169
|
-
this.#db = opts.database
|
|
170
|
-
} else if (opts.databasePath) {
|
|
171
|
-
this.#db = this.#resources.use(new DatabaseClient<DB>(opts.databasePath, { readOnly: true }))
|
|
172
|
-
} else {
|
|
173
|
-
throw new Error("POILookup needs `databasePath` or `database`")
|
|
174
|
-
}
|
|
158
|
+
super(opts)
|
|
175
159
|
|
|
176
|
-
// The category dictionary is tiny (poi-taxonomy's category count)
|
|
177
|
-
// construction
|
|
178
|
-
for (const r of allRows<POICategoryCodeTable>(
|
|
160
|
+
// The category dictionary is tiny (poi-taxonomy's category count), so load it once
|
|
161
|
+
// at construction and keep `search` from round-tripping to it.
|
|
162
|
+
for (const r of allRows<POICategoryCodeTable>(
|
|
163
|
+
this.database.prepare("SELECT id, category FROM poi_category_codes")
|
|
164
|
+
)) {
|
|
179
165
|
this.#categoryToID.set(String(r.category), Number(r.id))
|
|
180
166
|
this.#idToCategory.set(Number(r.id), String(r.category))
|
|
181
167
|
}
|
|
182
168
|
|
|
183
169
|
const columns = "name, category_id, brand_wikidata, latitude, longitude, country, confidence, name_key, gers_id"
|
|
184
170
|
|
|
185
|
-
this.#categoryCellProbe = this
|
|
171
|
+
this.#categoryCellProbe = this.database.prepare(
|
|
186
172
|
`SELECT ${columns} FROM poi WHERE h3_cell = ? AND category_id = ? ORDER BY neg_rank ASC LIMIT ?`
|
|
187
173
|
)
|
|
188
174
|
|
|
189
|
-
this.#brandProbe = this
|
|
175
|
+
this.#brandProbe = this.database.prepare(`SELECT ${columns} FROM poi WHERE brand_wikidata = ?`)
|
|
190
176
|
|
|
191
|
-
this.#nameFTSProbe = this
|
|
177
|
+
this.#nameFTSProbe = this.database.prepare(
|
|
192
178
|
"SELECT name_key FROM poi_search WHERE poi_search MATCH ? ORDER BY bm25(poi_search) LIMIT ?"
|
|
193
179
|
)
|
|
194
180
|
}
|
|
@@ -200,13 +186,11 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
200
186
|
return this.#searchByName(query.name, limit, query.center)
|
|
201
187
|
}
|
|
202
188
|
|
|
203
|
-
if (query.
|
|
189
|
+
if (query.categoryIDs?.length || query.brandWikidata) {
|
|
204
190
|
if (!query.center) {
|
|
205
191
|
throw new Error("POILookup.search: category/brand search requires a `center`")
|
|
206
192
|
}
|
|
207
193
|
|
|
208
|
-
// brandWikidata wins over categoryID(s) when both are set — see POISearchQuery.categoryID. The brand path is
|
|
209
|
-
// a brand-wide indexed fetch, NOT a k-ring walk (brand rows are too sparse for ring expansion to reach).
|
|
210
194
|
if (query.brandWikidata) {
|
|
211
195
|
return this.#searchBrand(query.brandWikidata, query.center, limit)
|
|
212
196
|
}
|
|
@@ -218,10 +202,14 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
218
202
|
}
|
|
219
203
|
|
|
220
204
|
/**
|
|
221
|
-
* Brand path: a single brand-wide indexed fetch
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
205
|
+
* Brand path: a single brand-wide indexed fetch and no k-ring.
|
|
206
|
+
*
|
|
207
|
+
* The partial `poi_brand_wikidata` index turns the QID fetch into a range-scan
|
|
208
|
+
* instead of a 13.68M full scan.
|
|
209
|
+
* Rows are sorted by haversine distance from `center`.
|
|
210
|
+
*
|
|
211
|
+
* The lookup drops rows past {@link BRAND_MAX_DISTANCE_KM}.
|
|
212
|
+
* It returns the nearest `limit` rows.
|
|
225
213
|
*/
|
|
226
214
|
#searchBrand(brandWikidata: string, center: { latitude: number; longitude: number }, limit: number): POISearchHit[] {
|
|
227
215
|
const rows = allRows<POIRow>(this.#brandProbe, brandWikidata)
|
|
@@ -242,11 +230,9 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
242
230
|
const maxRings = query.maxRings ?? DEFAULT_MAX_RINGS
|
|
243
231
|
const categoryIDs: number[] = []
|
|
244
232
|
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
for (const id of seedIDs) {
|
|
233
|
+
// Each id resolves through the dictionary.
|
|
234
|
+
// Unresolved ids are dropped to cover Overture taxonomy drift and identity ids with no rows.
|
|
235
|
+
for (const id of query.categoryIDs ?? []) {
|
|
250
236
|
const resolved = this.#categoryToID.get(id)
|
|
251
237
|
|
|
252
238
|
if (resolved !== undefined) {
|
|
@@ -254,7 +240,8 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
254
240
|
}
|
|
255
241
|
}
|
|
256
242
|
|
|
257
|
-
// No resolvable leaf
|
|
243
|
+
// No resolvable leaf can have rows, since every id is unknown to the dictionary.
|
|
244
|
+
// This is a clean miss instead of a throw.
|
|
258
245
|
if (!categoryIDs.length) return []
|
|
259
246
|
|
|
260
247
|
const origin = latLngToCell(center.latitude, center.longitude, POI_H3_RESOLUTION) as H3Cell
|
|
@@ -263,8 +250,8 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
263
250
|
|
|
264
251
|
// `ring` starts at 0 (the origin cell itself), so this loop's k reaches `maxRings - 1`.
|
|
265
252
|
for (let ring = 0; ring < maxRings; ring++) {
|
|
266
|
-
// gridDisk(origin, ring) returns the
|
|
267
|
-
// already
|
|
253
|
+
// gridDisk(origin, ring) returns the whole disk out to `ring`, so diffing against
|
|
254
|
+
// the already-probed set derives just this ring's new cells.
|
|
268
255
|
const diskCells = gridDisk(origin, ring) as string[]
|
|
269
256
|
const newCells = diskCells.filter((cell) => !seenCells.has(cell))
|
|
270
257
|
|
|
@@ -272,8 +259,6 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
272
259
|
seenCells.add(cell)
|
|
273
260
|
const shortCell = shortCellToInt(cell as H3Cell)
|
|
274
261
|
|
|
275
|
-
// Fan-out: probe every resolved Overture leaf for this canonical category, unioning the rows. The
|
|
276
|
-
// post-ring distance sort + `slice(0, limit)` below dedupes the pool down to the nearest `limit`.
|
|
277
262
|
for (const categoryID of categoryIDs) {
|
|
278
263
|
rows.push(...allRows<POIRow>(this.#categoryCellProbe, shortCell, categoryID, limit))
|
|
279
264
|
}
|
|
@@ -288,11 +273,13 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
288
273
|
}
|
|
289
274
|
|
|
290
275
|
/**
|
|
291
|
-
* Name path: FTS5
|
|
276
|
+
* Name path: FTS5 match → hydrate by name_key.
|
|
277
|
+
*
|
|
278
|
+
* No center is required.
|
|
279
|
+
* Hits are distance-sorted when one is given.
|
|
292
280
|
*
|
|
293
|
-
* Hydration is
|
|
294
|
-
*
|
|
295
|
-
* (poi-schema.ts) + this batching.
|
|
281
|
+
* Hydration is one batched `where name_key IN (...)` query over the unique `name_key`s
|
|
282
|
+
* of the FTS hits, since a per-hit probe would be up to `limit` full table scans.
|
|
296
283
|
*/
|
|
297
284
|
#searchByName(name: string, limit: number, center?: { latitude: number; longitude: number }): POISearchHit[] {
|
|
298
285
|
const matchQuery = sanitizePOINameQuery(name)
|
|
@@ -339,21 +326,19 @@ export class POILookup<DB extends POIDatabase = POIDatabase> implements Disposab
|
|
|
339
326
|
}
|
|
340
327
|
|
|
341
328
|
/**
|
|
342
|
-
* Batched hydration for the FTS name path: `
|
|
343
|
-
* one
|
|
344
|
-
*
|
|
329
|
+
* Batched hydration for the FTS name path: `where name_key IN (?, ?, …)`,
|
|
330
|
+
* one query for the whole batch instead of one probe per FTS hit.
|
|
331
|
+
*
|
|
332
|
+
* This is a cold path (name search only) with variable arity per call,
|
|
333
|
+
* so the statement is prepared fresh each time rather than cached.
|
|
345
334
|
*/
|
|
346
335
|
#hydrateByNameKeys(nameKeys: string[]): POIRow[] {
|
|
347
336
|
const columns = "name, category_id, brand_wikidata, latitude, longitude, country, confidence, name_key, gers_id"
|
|
348
337
|
const placeholders = nameKeys.map(() => "?").join(", ")
|
|
349
|
-
const stmt = this
|
|
338
|
+
const stmt = this.database.prepare(`SELECT ${columns} FROM poi WHERE name_key IN (${placeholders})`)
|
|
350
339
|
|
|
351
340
|
return allRows<POIRow>(stmt, ...nameKeys)
|
|
352
341
|
}
|
|
353
|
-
|
|
354
|
-
[Symbol.dispose](): void {
|
|
355
|
-
this.#resources[Symbol.dispose]()
|
|
356
|
-
}
|
|
357
342
|
}
|
|
358
343
|
|
|
359
344
|
function sortByDistance(rows: POIRow[], center: { latitude: number; longitude: number }): POIRow[] {
|
|
@@ -378,27 +363,25 @@ function toHit(
|
|
|
378
363
|
country: row.country,
|
|
379
364
|
confidence: row.confidence,
|
|
380
365
|
gersID: row.gers_id,
|
|
381
|
-
|
|
382
|
-
? { distanceM: haversineKm(center.latitude, center.longitude, row.latitude, row.longitude) * 1000 }
|
|
383
|
-
: {}),
|
|
366
|
+
distanceM: center ? haversineKm(center.latitude, center.longitude, row.latitude, row.longitude) * 1000 : null,
|
|
384
367
|
}
|
|
385
368
|
}
|
|
386
369
|
|
|
387
370
|
/**
|
|
388
|
-
* Sanitize free text into an FTS5-safe
|
|
389
|
-
* phrase delimiters, `*` prefix wildcards, `:` column-filter separators),
|
|
390
|
-
* token (
|
|
371
|
+
* Sanitize free text into an FTS5-safe `match` query: strip the characters FTS5 would otherwise
|
|
372
|
+
* read as syntax (`"` phrase delimiters, `*` prefix wildcards, `:` column-filter separators),
|
|
373
|
+
* then phrase-quote each whitespace-separated token (and-joined).
|
|
391
374
|
*
|
|
392
|
-
* `
|
|
393
|
-
* module-private there
|
|
394
|
-
* discipline locally rather than
|
|
375
|
+
* `lookup.ts` already has this discipline in `sanitizeFTSQuery`, but that function is
|
|
376
|
+
* module-private there and is not re-exported from `fts.ts` or the package's `index.ts`.
|
|
377
|
+
* This replicates the discipline locally rather than reach across the module boundary for a private helper.
|
|
395
378
|
*/
|
|
396
379
|
function sanitizePOINameQuery(text: string): string {
|
|
397
380
|
return text
|
|
398
381
|
.replaceAll(/["*:]/g, "")
|
|
399
382
|
.trim()
|
|
400
383
|
.split(/\s+/u)
|
|
401
|
-
.filter((token) => token.length
|
|
384
|
+
.filter((token) => token.length)
|
|
402
385
|
.map((token) => `"${token.replaceAll('"', '""')}"`)
|
|
403
386
|
.join(" ")
|
|
404
387
|
}
|
package/lib/poi/schema.ts
CHANGED
|
@@ -3,23 +3,25 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Typed schema for poi.db
|
|
7
|
-
*
|
|
8
|
-
* contiguous key range
|
|
9
|
-
* gazetteer
|
|
10
|
-
* `poi_category_codes` dictionary
|
|
11
|
-
* embeds the layer-
|
|
12
|
-
* manifest
|
|
6
|
+
* Typed schema for poi.db, the first spatial layer (spec §3.4). One clustered `without rowid`
|
|
7
|
+
* B-tree uses `(h3_cell, category_id, neg_rank, rowid_key)` as its key. All rows near a res-9 cell
|
|
8
|
+
* occupy a contiguous key range. This matches byte-range and httpvfs access and follows the
|
|
9
|
+
* candidate gazetteer's layout. Rows include denormalized name, brand and coordinates.
|
|
10
|
+
* The `poi_category_codes` dictionary stores category ids as small integers. POI-taxonomy category
|
|
11
|
+
* ids remain strings. The database embeds the layer-interface tables from `@mailwoman/core/layers`.
|
|
12
|
+
* The builder writes a manifest with tier `shipped` and spine `h3` res 9. It also writes per-res-6-cell coverage.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type {
|
|
15
|
+
import type { layerschemadatabase } from "@mailwoman/core/layers"
|
|
16
16
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
17
17
|
import { sql, type Kysely } from "kysely"
|
|
18
18
|
|
|
19
19
|
import type { NameKey } from "#street/normalize"
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* One POI row.
|
|
22
|
+
* One POI row.
|
|
23
|
+
*
|
|
24
|
+
* Clustered PK: h3_cell → category_id → neg_rank → rowid_key.
|
|
23
25
|
*/
|
|
24
26
|
export interface POITable {
|
|
25
27
|
/**
|
|
@@ -40,10 +42,12 @@ export interface POITable {
|
|
|
40
42
|
rowid_key: number
|
|
41
43
|
name: string | null
|
|
42
44
|
/**
|
|
43
|
-
* Probe key for exact name lookups, minted by {@link normalizeLocalityForKey} at build
|
|
45
|
+
* Probe key for exact name lookups, minted by {@link normalizeLocalityForKey} at build and at query.
|
|
46
|
+
*
|
|
47
|
+
* Branded because a `toLowerCase()` approximation of the fold is still a `string`.
|
|
48
|
+
* That approximation binds to the parameter and returns fewer rows.
|
|
44
49
|
*
|
|
45
|
-
*
|
|
46
|
-
* fewer rows, and the shortfall reads as a coverage gap in the data rather than a defect in the probe.
|
|
50
|
+
* The shortfall looks like a data coverage gap instead of a probe defect.
|
|
47
51
|
*/
|
|
48
52
|
name_key: NameKey | null
|
|
49
53
|
brand_wikidata: string | null
|
|
@@ -58,14 +62,18 @@ export interface POITable {
|
|
|
58
62
|
*/
|
|
59
63
|
confidence: number
|
|
60
64
|
/**
|
|
61
|
-
*
|
|
65
|
+
* Gers id.
|
|
66
|
+
* Nullable metadata, never a key.
|
|
62
67
|
*/
|
|
63
68
|
gers_id: string | null
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
72
|
+
* The staging mirror.
|
|
73
|
+
* Every column is nullable except the coords.
|
|
74
|
+
*
|
|
75
|
+
* The loader fills this table positionally.
|
|
76
|
+
* The materialize select enforces completeness.
|
|
69
77
|
*/
|
|
70
78
|
export interface POIStageTable {
|
|
71
79
|
h3_cell: number | null
|
|
@@ -90,14 +98,14 @@ export interface POICategoryCodeTable {
|
|
|
90
98
|
category: string
|
|
91
99
|
}
|
|
92
100
|
|
|
93
|
-
export interface POIDatabase extends
|
|
101
|
+
export interface POIDatabase extends layerschemadatabase {
|
|
94
102
|
poi: POITable
|
|
95
103
|
poi_stage: POIStageTable
|
|
96
104
|
poi_category_codes: POICategoryCodeTable
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
/**
|
|
100
|
-
* Clustered-key-order column list shared by builder + `
|
|
108
|
+
* Clustered-key-order column list shared by builder + `insert into poi select … from poi_stage`.
|
|
101
109
|
*/
|
|
102
110
|
export const POI_COLUMNS = [
|
|
103
111
|
"h3_cell",
|
|
@@ -154,25 +162,31 @@ export async function createPOITable(db: Kysely<POIDatabase>): Promise<void> {
|
|
|
154
162
|
.addColumn("confidence", "real", (c) => c.notNull())
|
|
155
163
|
.addColumn("gers_id", "text")
|
|
156
164
|
.addPrimaryKeyConstraint("poi_pk", ["h3_cell", "category_id", "neg_rank", "rowid_key"])
|
|
157
|
-
// `
|
|
165
|
+
// `without rowid` has no first-class builder. The raw modifier is the idiomatic fallback.
|
|
158
166
|
.modifyEnd(sql`without rowid`)
|
|
159
167
|
.execute()
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
/**
|
|
163
|
-
* Secondary index for the FTS-hydration path.
|
|
171
|
+
* Secondary index for the FTS-hydration path.
|
|
172
|
+
*
|
|
173
|
+
* Builders call this after the bulk materialize (index-after-load).
|
|
164
174
|
*/
|
|
165
175
|
export async function createPOINameKeyIndex(db: Kysely<POIDatabase>): Promise<void> {
|
|
166
176
|
await db.schema.createIndex("poi_name_key").on("poi").column("name_key").execute()
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
/**
|
|
170
|
-
* Secondary index for the
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
180
|
+
* Secondary index for the brand path, a brand-wide fetch by `brand_wikidata` with no `h3_cell` prefix.
|
|
181
|
+
*
|
|
182
|
+
* Brand rows are globally sparse, so the k-ring walk can never reach them and the reader
|
|
183
|
+
* instead fetches all of a brand's rows and distance-sorts.
|
|
184
|
+
* Without this index that fetch is a full-table scan, while with it the fetch is a range-scan.
|
|
185
|
+
*
|
|
186
|
+
* The partial index (`where brand_wikidata is not NULL`) keeps the mostly unbranded rows out of the B-tree.
|
|
187
|
+
*
|
|
188
|
+
* Builders call this after the bulk materialize (index-after-load),
|
|
189
|
+
* same phase as {@link createPOINameKeyIndex}.
|
|
176
190
|
*/
|
|
177
191
|
export async function createPOIBrandIndex(db: Kysely<POIDatabase>): Promise<void> {
|
|
178
192
|
await db.schema
|
|
@@ -189,7 +203,9 @@ export async function createPOIBrandIndex(db: Kysely<POIDatabase>): Promise<void
|
|
|
189
203
|
export const POI_FTS_TABLE = "poi_search"
|
|
190
204
|
|
|
191
205
|
/**
|
|
192
|
-
* FTS5 stays raw SQL by project rule (Kysely can't express virtual tables).
|
|
206
|
+
* FTS5 stays raw SQL by project rule (Kysely can't express virtual tables).
|
|
207
|
+
*
|
|
208
|
+
* Content-keyed by name_key.
|
|
193
209
|
*/
|
|
194
210
|
export function createPOISearchFTS<DB>(db: DatabaseClient<DB>): void {
|
|
195
211
|
db.exec(
|
package/lib/polygon-schema.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
6
|
* Typed schema for the polygon sidecar (`wof-polygons.db`) — the one table `WOFReverseGeocoder` probes for a
|
|
7
|
-
* place's geometry. The interface is the read/write
|
|
7
|
+
* place's geometry. The interface is the read/write interface and {@link createPolygonsTable} creates the table, so a
|
|
8
8
|
* column added to one is a compile error against the other.
|
|
9
9
|
*
|
|
10
|
-
* The sidecar is
|
|
10
|
+
* The sidecar is optional to the reverse geocoder: without it every result falls back to a centroid, so the reader
|
|
11
11
|
* checks for the table's presence rather than assuming it.
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -19,8 +19,10 @@ import type { Kysely } from "kysely"
|
|
|
19
19
|
export interface PolygonsTable {
|
|
20
20
|
id: number
|
|
21
21
|
/**
|
|
22
|
-
* The GeoJSON geometry, JSON-encoded.
|
|
23
|
-
*
|
|
22
|
+
* The GeoJSON geometry, JSON-encoded.
|
|
23
|
+
*
|
|
24
|
+
* A row that fails to parse reads as no-polygon, never as an error.
|
|
25
|
+
* A malformed geometry must not fail the whole reverse query.
|
|
24
26
|
*/
|
|
25
27
|
geom: string
|
|
26
28
|
}
|
|
@@ -30,8 +32,10 @@ export interface PolygonDatabase {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
|
-
* The
|
|
34
|
-
*
|
|
35
|
+
* The subset of a Kysely handle the polygon DDL touches.
|
|
36
|
+
*
|
|
37
|
+
* Kysely is invariant in its schema parameter, so naming only `schema` lets a
|
|
38
|
+
* builder holding a wider handle pass it without a cast.
|
|
35
39
|
*/
|
|
36
40
|
export type PolygonSchemaHandle = Pick<Kysely<PolygonDatabase>, "schema">
|
|
37
41
|
|