@mailwoman/corpus 10.3.0 → 10.5.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 +17 -17
- package/data/PROVENANCE.md +287 -273
- package/data/address-source-register.json +545 -23
- package/data/license-decisions.json +120 -0
- package/data/licenses/PROVENANCE.md +5 -5
- package/data/licenses/cdla-permissive-2.0.md +6 -6
- package/data/licenses/geonames-publication.md +5 -5
- package/data/licenses/licence-ouverte-2.0.md +3 -3
- package/data/licenses/national-address-database.md +12 -12
- package/data/licenses/nlod-2.0.md +3 -3
- package/data/licenses/sirene-publication.md +13 -6
- package/data/licenses/whosonfirst-licenses.md +4 -4
- package/data/source-resolutions.json +46 -0
- package/data/training-manifests/v0.7.2-address-systems.effective.json +1169 -0
- package/data/training-manifests/v0.7.2-address-systems.json +550 -0
- package/lib/adapters/cordis/adapter.ts +9 -9
- package/lib/adapters/gleif/adapter.ts +7 -8
- package/lib/adapters/po-box/adapter.ts +1 -1
- package/lib/adapters/source-id.ts +1 -1
- package/lib/adapters.ts +4 -2
- package/lib/au/adapters/gnaf/adapter.ts +1 -1
- package/lib/be/adapters/brussels/adapter.ts +3 -3
- package/lib/be/adapters/vlaanderen/adapter.ts +1 -1
- package/lib/be/adapters/wallonie/adapter.ts +1 -1
- package/lib/build/align.ts +1 -1
- package/lib/build.ts +1 -1
- package/lib/cz/adapters/cuzk/adapter.ts +2 -2
- package/lib/cz/tools/fetch/cuzk.ts +6 -6
- package/lib/de/recipes/locale.ts +1 -1
- package/lib/dk/adapters/inspire/adapter.ts +1 -1
- package/lib/ee/tools/fetch/ads.ts +2 -2
- package/lib/es/adapters/bizkaia/adapter.ts +1 -1
- package/lib/es/adapters/navarra/adapter.ts +2 -2
- package/lib/es/cadastre.ts +2 -2
- package/lib/es/tools/fetch/bizkaia.ts +8 -8
- package/lib/es/tools/fetch/catastro.ts +6 -6
- package/lib/es/tools/fetch/navarra.ts +4 -4
- package/lib/fingerprints.ts +3 -3
- package/lib/fr/adapters/finess/adapter.ts +6 -5
- package/lib/fr/adapters/finess/voie-type.ts +1 -1
- package/lib/fr/tools/fetch/annuaire-education.ts +3 -3
- package/lib/gb/adapters/os-open/adapter.ts +453 -0
- package/lib/gb/constituent-country.ts +52 -0
- package/lib/gb/os-open/nearest-link.ts +169 -0
- package/lib/gb/os-open/open-names.ts +180 -0
- package/lib/gb/os-open/open-roads.ts +145 -0
- package/lib/gb/tools/fetch/os-open.ts +251 -0
- package/lib/inspire/address.ts +3 -3
- package/lib/international/recipes/locale.ts +41 -7
- package/lib/it/adapters/anac/adapter.ts +4 -3
- package/lib/jp/adapters/wof-admin/adapter.ts +2 -2
- package/lib/kr/adapters/juso/label-rows.ts +1 -1
- package/lib/kr/adapters/localdata/key-index.ts +2 -1
- package/lib/lu/adapters/bd-adresses/adapter.ts +1 -1
- package/lib/nl/adapters/kadaster/adapter.ts +2 -2
- package/lib/nl/tools/fetch/kadaster.ts +2 -2
- package/lib/parquet/shuffle.ts +2 -2
- package/lib/parquet/span-sql.ts +8 -6
- package/lib/parquet/streams.ts +1 -1
- package/lib/parquet/writers.ts +2 -2
- package/lib/pl/tools/fetch/emuia.ts +2 -2
- package/lib/recipes/scaffold.ts +2 -2
- package/lib/recipes/sources.ts +3 -3
- package/lib/recipes/sub/venue/render.ts +1 -1
- package/lib/recipes/sub/venue/sources.ts +4 -3
- package/lib/recipes/sub/venue.ts +4 -3
- package/lib/recipes/venue.ts +11 -11
- package/lib/registers.ts +7 -0
- package/lib/ro/adapters/retea-scolara/adapter.ts +2 -2
- package/lib/runner.ts +3 -3
- package/lib/sk/tools/fetch/inspire.ts +3 -3
- package/lib/source-register/effective-manifest.ts +14 -8
- package/lib/source-register/training-manifest.ts +30 -2
- package/lib/source-register/types.ts +2 -2
- package/lib/source-register.ts +1 -0
- package/lib/subvenue/harvest.ts +6 -5
- package/lib/subvenue/promotions.ts +3 -3
- package/lib/subvenue/surfaces.ts +1 -1
- package/lib/subvenue/table.ts +6 -6
- package/lib/surfaces/locale.ts +1 -1
- package/lib/synthesizers/anchor-absorption.ts +1 -1
- package/lib/synthesizers/boundary-stress.ts +3 -2
- package/lib/synthesizers/no-street.ts +1 -1
- package/lib/synthesizers/po-box.ts +1 -1
- package/lib/synthesizers/tail.ts +2 -2
- package/lib/test-kit/corpus-recipe.ts +1 -1
- package/lib/tools/align-canonical.ts +3 -2
- package/lib/tools/audit.ts +1 -1
- package/lib/tools/corpus-stats.ts +1 -1
- package/lib/tools/fetch/download/network.ts +1 -1
- package/lib/tools/fetch/geonames/dump.ts +1 -1
- package/lib/tools/fetch/gleif.ts +2 -2
- package/lib/tools/fetch/header.ts +1 -1
- package/lib/tools/fetch/inspire-addresses.ts +1 -1
- package/lib/tools/fetch/vlaanderen-ad.ts +1 -1
- package/lib/tools/fetch/wfs-harvest.ts +6 -6
- package/lib/tools/fetch/zip-archive.ts +2 -2
- package/lib/tools/fetch.ts +2 -0
- package/lib/tools/golden/expand.ts +1 -1
- package/lib/tools/golden/promote.ts +3 -3
- package/lib/tools/golden/relabel-street/directory.ts +2 -1
- package/lib/tools/lint/recipe-output/vocab.ts +2 -2
- package/lib/tools/lint/recipe-output.ts +1 -1
- package/lib/tools/migrate/source-names.ts +1 -1
- package/lib/tools/overlay/manifest.ts +62 -10
- package/lib/tools/overlay/split-slice.ts +2 -10
- package/lib/tools/postcode-triples.ts +2 -2
- package/lib/tools/sub/venue/head-nouns.ts +4 -3
- package/lib/types.ts +3 -3
- package/lib/us/adapters/state/tx-notaries/adapter.ts +1 -1
- package/lib/us/adapters/usgov/irs-bmf/adapter.ts +2 -2
- package/lib/us/adapters/usgov/nad/adapter.ts +1 -1
- package/lib/us/adapters/usgov/nppes/adapter.ts +2 -2
- package/lib/us/synthesizers/intersection.ts +4 -4
- package/lib/us/synthesizers/street.ts +3 -3
- package/lib/us/tools/fetch/nad.ts +2 -2
- package/lib/utils/split.ts +59 -6
- package/lib/utils/wof-json.ts +1 -1
- package/out/adapters/cordis/adapter.d.ts +7 -7
- package/out/adapters/cordis/adapter.js +9 -9
- package/out/adapters/cordis/adapter.js.map +1 -1
- package/out/adapters/gleif/adapter.d.ts +7 -8
- package/out/adapters/gleif/adapter.d.ts.map +1 -1
- package/out/adapters/gleif/adapter.js +7 -8
- package/out/adapters/gleif/adapter.js.map +1 -1
- package/out/adapters/po-box/adapter.d.ts +1 -1
- package/out/adapters/source-id.d.ts +1 -1
- package/out/adapters/source-id.js +1 -1
- package/out/adapters.d.ts +2 -2
- package/out/adapters.d.ts.map +1 -1
- package/out/adapters.js +4 -2
- package/out/adapters.js.map +1 -1
- package/out/au/adapters/gnaf/adapter.js +1 -1
- package/out/be/adapters/brussels/adapter.d.ts +2 -2
- package/out/be/adapters/brussels/adapter.js +3 -3
- package/out/be/adapters/vlaanderen/adapter.d.ts +1 -1
- package/out/be/adapters/wallonie/adapter.d.ts +1 -1
- package/out/be/adapters/wallonie/adapter.js +1 -1
- package/out/build/align.d.ts +1 -1
- package/out/build.js +1 -1
- package/out/build.js.map +1 -1
- package/out/cz/adapters/cuzk/adapter.d.ts +2 -2
- package/out/cz/adapters/cuzk/adapter.js +2 -2
- package/out/cz/tools/fetch/cuzk.d.ts +2 -2
- package/out/cz/tools/fetch/cuzk.js +4 -4
- package/out/cz/tools/fetch/cuzk.js.map +1 -1
- package/out/de/recipes/locale.d.ts +1 -1
- package/out/de/recipes/locale.js +1 -1
- package/out/dk/adapters/inspire/adapter.js +1 -1
- package/out/ee/tools/fetch/ads.d.ts +2 -2
- package/out/ee/tools/fetch/ads.js +1 -1
- package/out/es/adapters/bizkaia/adapter.js +1 -1
- package/out/es/adapters/navarra/adapter.d.ts +2 -2
- package/out/es/adapters/navarra/adapter.js +2 -2
- package/out/es/cadastre.d.ts +2 -2
- package/out/es/cadastre.js +2 -2
- package/out/es/tools/fetch/bizkaia.d.ts +5 -5
- package/out/es/tools/fetch/bizkaia.js +6 -6
- package/out/es/tools/fetch/bizkaia.js.map +1 -1
- package/out/es/tools/fetch/catastro.d.ts +4 -4
- package/out/es/tools/fetch/catastro.js +2 -2
- package/out/es/tools/fetch/navarra.d.ts +2 -2
- package/out/es/tools/fetch/navarra.js +2 -2
- package/out/fingerprints.d.ts +3 -3
- package/out/fingerprints.js +3 -3
- package/out/fr/adapters/finess/adapter.d.ts +6 -5
- package/out/fr/adapters/finess/adapter.d.ts.map +1 -1
- package/out/fr/adapters/finess/adapter.js +6 -5
- package/out/fr/adapters/finess/adapter.js.map +1 -1
- package/out/fr/adapters/finess/voie-type.d.ts +1 -1
- package/out/fr/adapters/finess/voie-type.js +1 -1
- package/out/fr/tools/fetch/annuaire-education.d.ts +3 -3
- package/out/fr/tools/fetch/annuaire-education.js +3 -3
- package/out/gb/adapters/os-open/adapter.d.ts +165 -0
- package/out/gb/adapters/os-open/adapter.d.ts.map +1 -0
- package/out/gb/adapters/os-open/adapter.js +298 -0
- package/out/gb/adapters/os-open/adapter.js.map +1 -0
- package/out/gb/constituent-country.d.ts +36 -0
- package/out/gb/constituent-country.d.ts.map +1 -0
- package/out/gb/constituent-country.js +30 -0
- package/out/gb/constituent-country.js.map +1 -0
- package/out/gb/os-open/nearest-link.d.ts +73 -0
- package/out/gb/os-open/nearest-link.d.ts.map +1 -0
- package/out/gb/os-open/nearest-link.js +133 -0
- package/out/gb/os-open/nearest-link.js.map +1 -0
- package/out/gb/os-open/open-names.d.ts +92 -0
- package/out/gb/os-open/open-names.d.ts.map +1 -0
- package/out/gb/os-open/open-names.js +107 -0
- package/out/gb/os-open/open-names.js.map +1 -0
- package/out/gb/os-open/open-roads.d.ts +80 -0
- package/out/gb/os-open/open-roads.d.ts.map +1 -0
- package/out/gb/os-open/open-roads.js +79 -0
- package/out/gb/os-open/open-roads.js.map +1 -0
- package/out/gb/tools/fetch/os-open.d.ts +91 -0
- package/out/gb/tools/fetch/os-open.d.ts.map +1 -0
- package/out/gb/tools/fetch/os-open.js +166 -0
- package/out/gb/tools/fetch/os-open.js.map +1 -0
- package/out/inspire/address.d.ts +3 -3
- package/out/inspire/address.js +3 -3
- package/out/international/recipes/locale.d.ts +6 -0
- package/out/international/recipes/locale.d.ts.map +1 -1
- package/out/international/recipes/locale.js +29 -7
- package/out/international/recipes/locale.js.map +1 -1
- package/out/it/adapters/anac/adapter.d.ts +1 -1
- package/out/it/adapters/anac/adapter.d.ts.map +1 -1
- package/out/it/adapters/anac/adapter.js +4 -3
- package/out/it/adapters/anac/adapter.js.map +1 -1
- package/out/jp/adapters/wof-admin/adapter.d.ts +2 -2
- package/out/jp/adapters/wof-admin/adapter.js +2 -2
- package/out/kr/adapters/juso/label-rows.d.ts +1 -1
- package/out/kr/adapters/localdata/key-index.d.ts +2 -1
- package/out/kr/adapters/localdata/key-index.d.ts.map +1 -1
- package/out/kr/adapters/localdata/key-index.js +2 -1
- package/out/kr/adapters/localdata/key-index.js.map +1 -1
- package/out/lu/adapters/bd-adresses/adapter.d.ts +1 -1
- package/out/lu/adapters/bd-adresses/adapter.js +1 -1
- package/out/nl/adapters/kadaster/adapter.js +2 -2
- package/out/nl/tools/fetch/kadaster.d.ts +2 -2
- package/out/nl/tools/fetch/kadaster.js +2 -2
- package/out/parquet/shuffle.d.ts +2 -2
- package/out/parquet/shuffle.js +2 -2
- package/out/parquet/span-sql.d.ts +8 -6
- package/out/parquet/span-sql.d.ts.map +1 -1
- package/out/parquet/span-sql.js +8 -6
- package/out/parquet/span-sql.js.map +1 -1
- package/out/parquet/streams.d.ts +1 -1
- package/out/parquet/writers.d.ts +2 -2
- package/out/parquet/writers.js +1 -1
- package/out/pl/tools/fetch/emuia.d.ts +2 -2
- package/out/recipes/scaffold.d.ts +2 -2
- package/out/recipes/sources.d.ts +2 -2
- package/out/recipes/sources.js +3 -3
- package/out/recipes/sources.js.map +1 -1
- package/out/recipes/sub/venue/render.d.ts +1 -1
- package/out/recipes/sub/venue/render.js +1 -1
- package/out/recipes/sub/venue/sources.d.ts +4 -3
- package/out/recipes/sub/venue/sources.d.ts.map +1 -1
- package/out/recipes/sub/venue/sources.js +4 -3
- package/out/recipes/sub/venue/sources.js.map +1 -1
- package/out/recipes/sub/venue.d.ts +3 -2
- package/out/recipes/sub/venue.d.ts.map +1 -1
- package/out/recipes/sub/venue.js +2 -2
- package/out/recipes/sub/venue.js.map +1 -1
- package/out/recipes/venue.d.ts +7 -6
- package/out/recipes/venue.d.ts.map +1 -1
- package/out/recipes/venue.js +11 -10
- package/out/recipes/venue.js.map +1 -1
- package/out/registers.d.ts +7 -0
- package/out/registers.d.ts.map +1 -1
- package/out/registers.js +7 -0
- package/out/registers.js.map +1 -1
- package/out/ro/adapters/retea-scolara/adapter.d.ts +2 -2
- package/out/ro/adapters/retea-scolara/adapter.js +2 -2
- package/out/runner.d.ts +3 -3
- package/out/runner.js +2 -2
- package/out/sk/tools/fetch/inspire.d.ts +3 -3
- package/out/sk/tools/fetch/inspire.js +1 -1
- package/out/source-register/effective-manifest.d.ts +11 -5
- package/out/source-register/effective-manifest.d.ts.map +1 -1
- package/out/source-register/effective-manifest.js +9 -4
- package/out/source-register/effective-manifest.js.map +1 -1
- package/out/source-register/training-manifest.d.ts +27 -0
- package/out/source-register/training-manifest.d.ts.map +1 -1
- package/out/source-register/training-manifest.js +13 -1
- package/out/source-register/training-manifest.js.map +1 -1
- package/out/source-register/types.d.ts +2 -2
- package/out/source-register.d.ts +1 -1
- package/out/source-register.d.ts.map +1 -1
- package/out/source-register.js +1 -1
- package/out/source-register.js.map +1 -1
- package/out/subvenue/harvest.d.ts +4 -3
- package/out/subvenue/harvest.d.ts.map +1 -1
- package/out/subvenue/harvest.js +6 -5
- package/out/subvenue/harvest.js.map +1 -1
- package/out/subvenue/promotions.d.ts +2 -2
- package/out/subvenue/promotions.js +3 -3
- package/out/subvenue/promotions.js.map +1 -1
- package/out/subvenue/surfaces.d.ts +1 -1
- package/out/subvenue/table.d.ts +6 -6
- package/out/subvenue/table.js +1 -1
- package/out/surfaces/locale.d.ts +1 -1
- package/out/surfaces/locale.js +1 -1
- package/out/synthesizers/boundary-stress.d.ts.map +1 -1
- package/out/synthesizers/boundary-stress.js +2 -1
- package/out/synthesizers/boundary-stress.js.map +1 -1
- package/out/synthesizers/tail.d.ts +2 -2
- package/out/synthesizers/tail.js +2 -2
- package/out/test-kit/corpus-recipe.d.ts +1 -1
- package/out/test-kit/corpus-recipe.js +1 -1
- package/out/tools/align-canonical.d.ts.map +1 -1
- package/out/tools/align-canonical.js +3 -2
- package/out/tools/align-canonical.js.map +1 -1
- package/out/tools/audit.js +1 -1
- package/out/tools/corpus-stats.d.ts +1 -1
- package/out/tools/fetch/download/network.d.ts +1 -1
- package/out/tools/fetch/download/network.js +1 -1
- package/out/tools/fetch/geonames/dump.d.ts +1 -1
- package/out/tools/fetch/geonames/dump.js +1 -1
- package/out/tools/fetch/gleif.d.ts +2 -2
- package/out/tools/fetch/gleif.js +1 -1
- package/out/tools/fetch/header.d.ts +1 -1
- package/out/tools/fetch/header.js +1 -1
- package/out/tools/fetch/inspire-addresses.d.ts +1 -1
- package/out/tools/fetch/vlaanderen-ad.d.ts +1 -1
- package/out/tools/fetch/wfs-harvest.d.ts +3 -3
- package/out/tools/fetch/wfs-harvest.js +4 -4
- package/out/tools/fetch/wfs-harvest.js.map +1 -1
- package/out/tools/fetch/zip-archive.d.ts +2 -2
- package/out/tools/fetch/zip-archive.js +2 -2
- package/out/tools/fetch.d.ts +2 -0
- package/out/tools/fetch.d.ts.map +1 -1
- package/out/tools/fetch.js +2 -0
- package/out/tools/fetch.js.map +1 -1
- package/out/tools/golden/expand.js +1 -1
- package/out/tools/golden/expand.js.map +1 -1
- package/out/tools/golden/promote.d.ts +1 -1
- package/out/tools/golden/promote.js +3 -3
- package/out/tools/golden/promote.js.map +1 -1
- package/out/tools/golden/relabel-street/directory.d.ts +2 -1
- package/out/tools/golden/relabel-street/directory.d.ts.map +1 -1
- package/out/tools/golden/relabel-street/directory.js.map +1 -1
- package/out/tools/lint/recipe-output/vocab.js +2 -2
- package/out/tools/lint/recipe-output.d.ts +1 -1
- package/out/tools/migrate/source-names.d.ts +1 -1
- package/out/tools/overlay/manifest.d.ts +15 -2
- package/out/tools/overlay/manifest.d.ts.map +1 -1
- package/out/tools/overlay/manifest.js +41 -10
- package/out/tools/overlay/manifest.js.map +1 -1
- package/out/tools/overlay/split-slice.d.ts.map +1 -1
- package/out/tools/overlay/split-slice.js +2 -9
- package/out/tools/overlay/split-slice.js.map +1 -1
- package/out/tools/postcode-triples.d.ts +1 -1
- package/out/tools/postcode-triples.js +2 -2
- package/out/tools/postcode-triples.js.map +1 -1
- package/out/tools/sub/venue/head-nouns.d.ts +1 -1
- package/out/tools/sub/venue/head-nouns.d.ts.map +1 -1
- package/out/tools/sub/venue/head-nouns.js +4 -3
- package/out/tools/sub/venue/head-nouns.js.map +1 -1
- package/out/types.d.ts +3 -3
- package/out/types.js +1 -1
- package/out/us/adapters/state/tx-notaries/adapter.d.ts +1 -1
- package/out/us/adapters/state/tx-notaries/adapter.js +1 -1
- package/out/us/adapters/usgov/irs-bmf/adapter.d.ts +1 -1
- package/out/us/adapters/usgov/irs-bmf/adapter.js +2 -2
- package/out/us/adapters/usgov/nad/adapter.d.ts +1 -1
- package/out/us/adapters/usgov/nad/adapter.js +1 -1
- package/out/us/adapters/usgov/nppes/adapter.d.ts +1 -1
- package/out/us/adapters/usgov/nppes/adapter.js +1 -1
- package/out/us/synthesizers/intersection.js +4 -4
- package/out/us/synthesizers/intersection.js.map +1 -1
- package/out/us/synthesizers/street.js +3 -3
- package/out/us/synthesizers/street.js.map +1 -1
- package/out/us/tools/fetch/nad.d.ts +2 -2
- package/out/us/tools/fetch/nad.js +2 -2
- package/out/utils/split.d.ts +21 -0
- package/out/utils/split.d.ts.map +1 -1
- package/out/utils/split.js +49 -5
- package/out/utils/split.js.map +1 -1
- package/out/utils/wof-json.d.ts +1 -1
- package/out/utils/wof-json.js +1 -1
- package/package.json +15 -14
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Reads the road rows of OS Open Names.
|
|
7
|
+
*
|
|
8
|
+
* The CSV product is 819 files, one per 10 km National Grid square, with no header row. OS ships
|
|
9
|
+
* the 34 column names in `Doc/OS_Open_Names_Header.csv`, and the first data row of each file opens
|
|
10
|
+
* with a byte-order mark, which this reader strips from the `ID` value.
|
|
11
|
+
*
|
|
12
|
+
* A road row's `ID` is the roadNameTOID that OS Open Roads links reference, such as
|
|
13
|
+
* `osgb4000000010927776`. Two `LOCAL_TYPE` values are roads: `Named Road`, and
|
|
14
|
+
* `Section Of Named Road` for a road that OS splits where it crosses a postcode district or a
|
|
15
|
+
* settlement.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { CSVSpliterator } from "spliterator"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The 34 columns of an OS Open Names CSV, in file order, as `Doc/OS_Open_Names_Header.csv` names them.
|
|
22
|
+
*/
|
|
23
|
+
export const OPEN_NAMES_COLUMNS = [
|
|
24
|
+
"ID",
|
|
25
|
+
"NAMES_URI",
|
|
26
|
+
"NAME1",
|
|
27
|
+
"NAME1_LANG",
|
|
28
|
+
"NAME2",
|
|
29
|
+
"NAME2_LANG",
|
|
30
|
+
"TYPE",
|
|
31
|
+
"LOCAL_TYPE",
|
|
32
|
+
"GEOMETRY_X",
|
|
33
|
+
"GEOMETRY_Y",
|
|
34
|
+
"MOST_DETAIL_VIEW_RES",
|
|
35
|
+
"LEAST_DETAIL_VIEW_RES",
|
|
36
|
+
"MBR_XMIN",
|
|
37
|
+
"MBR_YMIN",
|
|
38
|
+
"MBR_XMAX",
|
|
39
|
+
"MBR_YMAX",
|
|
40
|
+
"POSTCODE_DISTRICT",
|
|
41
|
+
"POSTCODE_DISTRICT_URI",
|
|
42
|
+
"POPULATED_PLACE",
|
|
43
|
+
"POPULATED_PLACE_URI",
|
|
44
|
+
"POPULATED_PLACE_TYPE",
|
|
45
|
+
"DISTRICT_BOROUGH",
|
|
46
|
+
"DISTRICT_BOROUGH_URI",
|
|
47
|
+
"DISTRICT_BOROUGH_TYPE",
|
|
48
|
+
"COUNTY_UNITARY",
|
|
49
|
+
"COUNTY_UNITARY_URI",
|
|
50
|
+
"COUNTY_UNITARY_TYPE",
|
|
51
|
+
"REGION",
|
|
52
|
+
"REGION_URI",
|
|
53
|
+
"COUNTRY",
|
|
54
|
+
"COUNTRY_URI",
|
|
55
|
+
"RELATED_SPATIAL_OBJECT",
|
|
56
|
+
"SAME_AS_DBPEDIA",
|
|
57
|
+
"SAME_AS_GEONAMES",
|
|
58
|
+
] as const
|
|
59
|
+
|
|
60
|
+
const column = Object.fromEntries(OPEN_NAMES_COLUMNS.map((name, index) => [name, index])) as Record<
|
|
61
|
+
(typeof OPEN_NAMES_COLUMNS)[number],
|
|
62
|
+
number
|
|
63
|
+
>
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The `LOCAL_TYPE` values that mark a road row.
|
|
67
|
+
*/
|
|
68
|
+
export const OPEN_NAMES_ROAD_TYPES: ReadonlySet<string> = new Set(["Named Road", "Section Of Named Road"])
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* One road row of OS Open Names, with the columns the corpus adapter reads.
|
|
72
|
+
*/
|
|
73
|
+
export interface OpenNamesRoad {
|
|
74
|
+
/**
|
|
75
|
+
* The roadNameTOID, such as `osgb4000000010927776`.
|
|
76
|
+
*/
|
|
77
|
+
id: string
|
|
78
|
+
/**
|
|
79
|
+
* The road's name, in the language OS lists first.
|
|
80
|
+
*/
|
|
81
|
+
name1: string
|
|
82
|
+
/**
|
|
83
|
+
* The road's other-language name, Welsh or Gaelic, or an empty string.
|
|
84
|
+
*/
|
|
85
|
+
name2: string
|
|
86
|
+
/**
|
|
87
|
+
* The outward code of the postcode district holding the road, such as `LS6`.
|
|
88
|
+
*/
|
|
89
|
+
postcodeDistrict: string
|
|
90
|
+
/**
|
|
91
|
+
* The settlement holding the road, or an empty string.
|
|
92
|
+
*
|
|
93
|
+
* A bilingual settlement is written `<NAME1> / <NAME2>` of that settlement,
|
|
94
|
+
* such as `Stornoway / Steòrnabhagh`.
|
|
95
|
+
*/
|
|
96
|
+
populatedPlace: string
|
|
97
|
+
districtBorough: string
|
|
98
|
+
countyUnitary: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Counts from a read, with a reason for each row that did not yield.
|
|
103
|
+
*/
|
|
104
|
+
export interface OpenNamesReadStats {
|
|
105
|
+
/**
|
|
106
|
+
* Rows read across every file.
|
|
107
|
+
*/
|
|
108
|
+
read: number
|
|
109
|
+
/**
|
|
110
|
+
* Road rows yielded.
|
|
111
|
+
*/
|
|
112
|
+
roads: number
|
|
113
|
+
/**
|
|
114
|
+
* Road rows skipped because their `ID` was not among the requested ids.
|
|
115
|
+
*/
|
|
116
|
+
unrequested: number
|
|
117
|
+
/**
|
|
118
|
+
* Rows with a column count other than 34.
|
|
119
|
+
*/
|
|
120
|
+
malformed: number
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A stats object with every count at zero.
|
|
125
|
+
*/
|
|
126
|
+
export function createOpenNamesReadStats(): OpenNamesReadStats {
|
|
127
|
+
return { read: 0, roads: 0, unrequested: 0, malformed: 0 }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Streams the road rows of one Open Names CSV.
|
|
132
|
+
*
|
|
133
|
+
* @param ids When given, only roads whose `ID` is in the set are yielded.
|
|
134
|
+
*/
|
|
135
|
+
export async function* readOpenNamesRoads(
|
|
136
|
+
csvPath: string,
|
|
137
|
+
stats: OpenNamesReadStats,
|
|
138
|
+
ids?: ReadonlySet<string>
|
|
139
|
+
): AsyncGenerator<OpenNamesRoad> {
|
|
140
|
+
for await (const row of CSVSpliterator.fromAsync<string[]>(csvPath, { header: false })) {
|
|
141
|
+
stats.read++
|
|
142
|
+
|
|
143
|
+
if (row.length !== OPEN_NAMES_COLUMNS.length) {
|
|
144
|
+
stats.malformed++
|
|
145
|
+
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!OPEN_NAMES_ROAD_TYPES.has(row[column.LOCAL_TYPE]!)) continue
|
|
150
|
+
|
|
151
|
+
const id = row[column.ID]!.replace(/^/u, "")
|
|
152
|
+
|
|
153
|
+
if (ids && !ids.has(id)) {
|
|
154
|
+
stats.unrequested++
|
|
155
|
+
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
stats.roads++
|
|
160
|
+
|
|
161
|
+
yield {
|
|
162
|
+
id,
|
|
163
|
+
name1: row[column.NAME1]!.trim(),
|
|
164
|
+
name2: row[column.NAME2]!.trim(),
|
|
165
|
+
postcodeDistrict: row[column.POSTCODE_DISTRICT]!.trim(),
|
|
166
|
+
populatedPlace: row[column.POPULATED_PLACE]!.trim(),
|
|
167
|
+
districtBorough: row[column.DISTRICT_BOROUGH]!.trim(),
|
|
168
|
+
countyUnitary: row[column.COUNTY_UNITARY]!.trim(),
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The settlement's first-listed name from a `POPULATED_PLACE` value.
|
|
175
|
+
*
|
|
176
|
+
* OS writes a bilingual settlement as `<NAME1> / <NAME2>`, and NAME1 is the form the adapter emits.
|
|
177
|
+
*/
|
|
178
|
+
export function populatedPlaceName1(populatedPlace: string): string {
|
|
179
|
+
return populatedPlace.split(" / ")[0]!.trim()
|
|
180
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Reads the named road links of an OS Open Roads GML tile.
|
|
7
|
+
*
|
|
8
|
+
* The GML product is one file per 100 km National Grid square. A tile holds `road:RoadLink`,
|
|
9
|
+
* `road:RoadNode` and `road:MotorwayJunction` features. Only the links hold a centreline and a
|
|
10
|
+
* name. A named link has a `road:name1` and a `road:roadNameTOID` whose `xlink:href` is `#` plus
|
|
11
|
+
* the roadNameTOID that OS Open Names uses as its `ID`. A link with only a road number carries a
|
|
12
|
+
* `road:roadNumberTOID` instead, and an unnamed link carries neither.
|
|
13
|
+
*
|
|
14
|
+
* The centreline is a `gml:LineString` in EPSG:27700, written as a `gml:posList` of easting and
|
|
15
|
+
* northing pairs. The reader honors the line's `srsDimension` so a 3D tile would still read its
|
|
16
|
+
* first two ordinates.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { openReadStream } from "@mailwoman/core/fs/streams"
|
|
20
|
+
import { elementAtPath, streamMarkupElements, textAtPath, type MarkupElement } from "@mailwoman/core/html/elements"
|
|
21
|
+
import type { PathBuilderLike } from "path-ts"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The feature element that carries a centreline.
|
|
25
|
+
*/
|
|
26
|
+
export const OPEN_ROADS_LINK_ELEMENT = "road:RoadLink"
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* One named road link.
|
|
30
|
+
*/
|
|
31
|
+
export interface OpenRoadsLink {
|
|
32
|
+
/**
|
|
33
|
+
* The link's `gml:id`.
|
|
34
|
+
*/
|
|
35
|
+
id: string
|
|
36
|
+
/**
|
|
37
|
+
* `road:name1` as the tile spells it.
|
|
38
|
+
*/
|
|
39
|
+
name1: string
|
|
40
|
+
/**
|
|
41
|
+
* The roadNameTOID, without the `#` of the reference, such as `osgb4000000010927776`.
|
|
42
|
+
*/
|
|
43
|
+
roadNameTOID: string
|
|
44
|
+
/**
|
|
45
|
+
* The centreline as flat `[easting0, northing0, easting1, northing1, …]` in EPSG:27700 meters.
|
|
46
|
+
*/
|
|
47
|
+
vertices: Float64Array
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Counts from a read.
|
|
52
|
+
*/
|
|
53
|
+
export interface OpenRoadsReadStats {
|
|
54
|
+
/**
|
|
55
|
+
* Road links read.
|
|
56
|
+
*/
|
|
57
|
+
links: number
|
|
58
|
+
/**
|
|
59
|
+
* Named links yielded.
|
|
60
|
+
*/
|
|
61
|
+
named: number
|
|
62
|
+
/**
|
|
63
|
+
* Links with no `road:name1` or no `road:roadNameTOID`.
|
|
64
|
+
*/
|
|
65
|
+
unnamed: number
|
|
66
|
+
/**
|
|
67
|
+
* Named links whose centreline held fewer than two vertices or a non-numeric ordinate.
|
|
68
|
+
*/
|
|
69
|
+
malformedGeometry: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A stats object with every count at zero.
|
|
74
|
+
*/
|
|
75
|
+
export function createOpenRoadsReadStats(): OpenRoadsReadStats {
|
|
76
|
+
return { links: 0, named: 0, unnamed: 0, malformedGeometry: 0 }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const LINE_PATH = ["net:centrelineGeometry", "gml:LineString"] as const
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The named link a `road:RoadLink` element describes, or a reason it describes none.
|
|
83
|
+
*/
|
|
84
|
+
export function readOpenRoadsLink(element: MarkupElement): OpenRoadsLink | "unnamed" | "malformed-geometry" {
|
|
85
|
+
const name1 = (textAtPath(element, "road:name1") ?? "").trim()
|
|
86
|
+
const href = elementAtPath(element, "road:roadNameTOID")?.attributes["xlink:href"] ?? ""
|
|
87
|
+
const roadNameTOID = href.replace(/^#/u, "").trim()
|
|
88
|
+
|
|
89
|
+
if (!name1 || !roadNameTOID) return "unnamed"
|
|
90
|
+
|
|
91
|
+
const dimension = Number(elementAtPath(element, ...LINE_PATH)?.attributes.srsDimension ?? "2") || 2
|
|
92
|
+
const posList = (textAtPath(element, ...LINE_PATH, "gml:posList") ?? "").trim()
|
|
93
|
+
const ordinates = posList ? posList.split(/\s+/u) : []
|
|
94
|
+
const vertexCount = Math.floor(ordinates.length / dimension)
|
|
95
|
+
|
|
96
|
+
if (vertexCount < 2 || ordinates.length % dimension !== 0) return "malformed-geometry"
|
|
97
|
+
|
|
98
|
+
const vertices = new Float64Array(vertexCount * 2)
|
|
99
|
+
|
|
100
|
+
for (let vertex = 0; vertex < vertexCount; vertex++) {
|
|
101
|
+
const easting = Number(ordinates[vertex * dimension])
|
|
102
|
+
const northing = Number(ordinates[vertex * dimension + 1])
|
|
103
|
+
|
|
104
|
+
if (!Number.isFinite(easting) || !Number.isFinite(northing)) return "malformed-geometry"
|
|
105
|
+
|
|
106
|
+
vertices[vertex * 2] = easting
|
|
107
|
+
vertices[vertex * 2 + 1] = northing
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { id: element.attributes["gml:id"] ?? "", name1, roadNameTOID, vertices }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Streams the named links of one Open Roads GML tile.
|
|
115
|
+
*/
|
|
116
|
+
export async function* readOpenRoadsLinks(
|
|
117
|
+
gmlPath: PathBuilderLike,
|
|
118
|
+
stats: OpenRoadsReadStats,
|
|
119
|
+
options: { signal?: AbortSignal } = {}
|
|
120
|
+
): AsyncGenerator<OpenRoadsLink> {
|
|
121
|
+
for await (const element of streamMarkupElements(openReadStream(gmlPath), OPEN_ROADS_LINK_ELEMENT, {
|
|
122
|
+
xml: true,
|
|
123
|
+
signal: options.signal,
|
|
124
|
+
})) {
|
|
125
|
+
stats.links++
|
|
126
|
+
|
|
127
|
+
const link = readOpenRoadsLink(element)
|
|
128
|
+
|
|
129
|
+
if (link === "unnamed") {
|
|
130
|
+
stats.unnamed++
|
|
131
|
+
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (link === "malformed-geometry") {
|
|
136
|
+
stats.malformedGeometry++
|
|
137
|
+
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
stats.named++
|
|
142
|
+
|
|
143
|
+
yield link
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Downloads the three OS OpenData products the `gb-os-open` adapter joins, from the OS Downloads API.
|
|
7
|
+
*
|
|
8
|
+
* The API serves OpenData without a key. `GET /products/<id>/downloads` lists one archive per
|
|
9
|
+
* format and area with its OS-published MD5, and each archive is checked against that MD5 after
|
|
10
|
+
* transfer. A present archive whose MD5 already matches is kept rather than downloaded again.
|
|
11
|
+
*
|
|
12
|
+
* Each archive is unpacked into the subdirectory the adapter reads, `code-point-open/`,
|
|
13
|
+
* `open-names/` or `open-roads/`, so the fetch root is the adapter's `inputPath`. The archive
|
|
14
|
+
* stays beside its members, because its digest is the value the address-source register records.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { APIClient } from "@mailwoman/core/api"
|
|
18
|
+
import { ByteFormatter } from "@mailwoman/core/fs/formatters"
|
|
19
|
+
import { makeDirectories } from "@mailwoman/core/fs/writers"
|
|
20
|
+
import { extractZipEntries } from "@mailwoman/core/fs/zip"
|
|
21
|
+
import { md5File, sha256File } from "@mailwoman/core/hash"
|
|
22
|
+
import { OS_DOWNLOADS_API_BASE, type OSDownload } from "@mailwoman/spatial/gb/os-downloads"
|
|
23
|
+
import { PathBuilder, type PathBuilderLike } from "path-ts"
|
|
24
|
+
|
|
25
|
+
import { GB_OS_OPEN_ADAPTER_ID, GB_OS_OPEN_PRODUCT_DIRECTORIES } from "#gb/adapters/os-open/adapter"
|
|
26
|
+
import type { BaseFetchOptions, FetchSummary, SourceCollectionManifest, SourceManifest } from "#tools/fetch/download"
|
|
27
|
+
import { downloadToFile, writeManifest } from "#tools/fetch/download"
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The license all three products are published under.
|
|
31
|
+
*/
|
|
32
|
+
export const GB_OS_OPEN_LICENSE_NAME = "Open Government Licence v3.0"
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The attribution OGL v3 requires, in the wording OS asks for on Code-Point Open,
|
|
36
|
+
* which also carries Royal Mail and ONS data.
|
|
37
|
+
*/
|
|
38
|
+
export const GB_OS_OPEN_ATTRIBUTION =
|
|
39
|
+
"Contains OS data © Crown copyright and database right. Contains Royal Mail data © Royal Mail copyright and database right. Contains National Statistics data © Crown copyright and database right."
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* One product the adapter reads: its OS Downloads API id, the archive format,
|
|
43
|
+
* and the subdirectory it unpacks into.
|
|
44
|
+
*/
|
|
45
|
+
export interface OSOpenProduct {
|
|
46
|
+
productID: string
|
|
47
|
+
format: string
|
|
48
|
+
directory: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The three products, in the order they are fetched.
|
|
53
|
+
*/
|
|
54
|
+
export const GB_OS_OPEN_PRODUCTS: readonly OSOpenProduct[] = [
|
|
55
|
+
{ productID: "CodePointOpen", format: "CSV", directory: GB_OS_OPEN_PRODUCT_DIRECTORIES.codePointOpen },
|
|
56
|
+
{ productID: "OpenNames", format: "CSV", directory: GB_OS_OPEN_PRODUCT_DIRECTORIES.openNames },
|
|
57
|
+
{ productID: "OpenRoads", format: "GML", directory: GB_OS_OPEN_PRODUCT_DIRECTORIES.openRoads },
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The manifest entry for one product's archive.
|
|
62
|
+
*/
|
|
63
|
+
export interface OSOpenFileManifest extends SourceManifest {
|
|
64
|
+
product_id: string
|
|
65
|
+
format: string
|
|
66
|
+
md5: string
|
|
67
|
+
members: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The archive of `product` among the listed downloads, the Great Britain one where an area is stated.
|
|
72
|
+
*
|
|
73
|
+
* @throws Naming the formats offered, when none matches.
|
|
74
|
+
*/
|
|
75
|
+
export function selectOSDownload(product: OSOpenProduct, downloads: readonly OSDownload[]): OSDownload {
|
|
76
|
+
const matches = downloads.filter((download) => download.format === product.format)
|
|
77
|
+
const chosen = matches.find((download) => download.area === "GB") ?? (matches.length === 1 ? matches[0] : undefined)
|
|
78
|
+
|
|
79
|
+
if (!chosen) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`${GB_OS_OPEN_ADAPTER_ID}: OS Downloads API lists no single ${product.format} archive for ${product.productID} ` +
|
|
82
|
+
`(listed: ${downloads.map((download) => `${download.format}/${download.area}`).join(", ") || "nothing"})`
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return chosen
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The archives the OS Downloads API lists for one product.
|
|
91
|
+
*/
|
|
92
|
+
export async function readOSDownloads(
|
|
93
|
+
client: Pick<APIClient, "fetch">,
|
|
94
|
+
product: OSOpenProduct,
|
|
95
|
+
options: { signal?: AbortSignal } = {}
|
|
96
|
+
): Promise<OSDownload[]> {
|
|
97
|
+
const { data } = await client.fetch<OSDownload[]>({
|
|
98
|
+
method: "GET",
|
|
99
|
+
url: `${OS_DOWNLOADS_API_BASE}/products/${product.productID}/downloads`,
|
|
100
|
+
timeout: 60_000,
|
|
101
|
+
signal: options.signal,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (!Array.isArray(data)) {
|
|
105
|
+
throw new TypeError(
|
|
106
|
+
`${GB_OS_OPEN_ADAPTER_ID}: OS Downloads API answered ${product.productID} with no download list`
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return data
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Downloads and unpacks one product.
|
|
115
|
+
*/
|
|
116
|
+
export async function downloadOSOpenProduct(
|
|
117
|
+
client: Pick<APIClient, "fetch">,
|
|
118
|
+
product: OSOpenProduct,
|
|
119
|
+
options: {
|
|
120
|
+
outputDir: PathBuilderLike
|
|
121
|
+
force?: boolean
|
|
122
|
+
retries?: number
|
|
123
|
+
retryDelayMs?: number
|
|
124
|
+
signal?: AbortSignal
|
|
125
|
+
report?: (line: string) => void
|
|
126
|
+
}
|
|
127
|
+
): Promise<{ entry: OSOpenFileManifest; downloaded: boolean }> {
|
|
128
|
+
const { report } = options
|
|
129
|
+
const download = selectOSDownload(product, await readOSDownloads(client, product, { signal: options.signal }))
|
|
130
|
+
const productDir = PathBuilder.from(options.outputDir)(product.directory)
|
|
131
|
+
const archivePath = productDir(download.fileName)
|
|
132
|
+
|
|
133
|
+
report?.(
|
|
134
|
+
`=== ${GB_OS_OPEN_ADAPTER_ID} / ${product.productID} ${download.fileName} (${ByteFormatter.formatIEC(download.size)})`
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
await makeDirectories(productDir)
|
|
138
|
+
|
|
139
|
+
const present = options.force ? null : await md5File(archivePath).catch(() => null)
|
|
140
|
+
const downloaded = present !== download.md5
|
|
141
|
+
|
|
142
|
+
if (downloaded) {
|
|
143
|
+
await downloadToFile({
|
|
144
|
+
url: download.url,
|
|
145
|
+
dest: archivePath,
|
|
146
|
+
retries: options.retries,
|
|
147
|
+
retryDelayMs: options.retryDelayMs,
|
|
148
|
+
report,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
const md5 = await md5File(archivePath)
|
|
152
|
+
|
|
153
|
+
if (md5 !== download.md5) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`${GB_OS_OPEN_ADAPTER_ID}: ${download.fileName} has md5 ${md5}, and OS publishes ${download.md5} for it`
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
report?.(` present, and its md5 matches the published ${download.md5}`)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const members = await extractZipEntries(archivePath, productDir, { skipExisting: true })
|
|
163
|
+
const sha256 = await sha256File(archivePath)
|
|
164
|
+
|
|
165
|
+
report?.(` ${members.length} members unpacked sha256=${sha256}`)
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
entry: {
|
|
169
|
+
source_url: download.url,
|
|
170
|
+
filename: download.fileName,
|
|
171
|
+
product_id: product.productID,
|
|
172
|
+
format: product.format,
|
|
173
|
+
bytes: download.size,
|
|
174
|
+
md5: download.md5,
|
|
175
|
+
sha256,
|
|
176
|
+
members: members.length,
|
|
177
|
+
downloaded_at: new Date().toISOString(),
|
|
178
|
+
},
|
|
179
|
+
downloaded,
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Per-invocation options for the registry entry.
|
|
185
|
+
*/
|
|
186
|
+
export interface FetchGBOSOpenOptions extends BaseFetchOptions {
|
|
187
|
+
force?: boolean
|
|
188
|
+
retries?: number
|
|
189
|
+
signal?: AbortSignal
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The registry entry: fetches all three products and writes `MANIFEST.json` at the adapter's input root.
|
|
194
|
+
*
|
|
195
|
+
* A product that fails is counted and the others are still taken.
|
|
196
|
+
*/
|
|
197
|
+
export async function fetchGBOSOpen(
|
|
198
|
+
options: FetchGBOSOpenOptions,
|
|
199
|
+
report?: (line: string) => void
|
|
200
|
+
): Promise<FetchSummary> {
|
|
201
|
+
const outputDir = options.outRoot(GB_OS_OPEN_ADAPTER_ID)
|
|
202
|
+
const files: OSOpenFileManifest[] = []
|
|
203
|
+
const failedCodes: string[] = []
|
|
204
|
+
|
|
205
|
+
let fetched = 0
|
|
206
|
+
let skipped = 0
|
|
207
|
+
|
|
208
|
+
await using client = new APIClient({ displayName: GB_OS_OPEN_ADAPTER_ID, minRequestIntervalMs: 250, retry: true })
|
|
209
|
+
|
|
210
|
+
for (const product of GB_OS_OPEN_PRODUCTS) {
|
|
211
|
+
if (options.signal?.aborted) break
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const { entry, downloaded } = await downloadOSOpenProduct(client, product, {
|
|
215
|
+
outputDir,
|
|
216
|
+
force: options.force,
|
|
217
|
+
retries: options.retries,
|
|
218
|
+
retryDelayMs: options.retryDelayMs,
|
|
219
|
+
signal: options.signal,
|
|
220
|
+
report,
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
files.push(entry)
|
|
224
|
+
|
|
225
|
+
if (downloaded) {
|
|
226
|
+
fetched += 1
|
|
227
|
+
} else {
|
|
228
|
+
skipped += 1
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
report?.(` ✗ ${product.productID}: ${error instanceof Error ? error.message : String(error)}`)
|
|
232
|
+
|
|
233
|
+
failedCodes.push(product.productID)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const manifest: SourceCollectionManifest = {
|
|
238
|
+
source: GB_OS_OPEN_ADAPTER_ID,
|
|
239
|
+
source_url: OS_DOWNLOADS_API_BASE,
|
|
240
|
+
license: GB_OS_OPEN_LICENSE_NAME,
|
|
241
|
+
attribution: GB_OS_OPEN_ATTRIBUTION,
|
|
242
|
+
downloaded_at: new Date().toISOString(),
|
|
243
|
+
files,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await writeManifest(outputDir("MANIFEST.json"), manifest)
|
|
247
|
+
|
|
248
|
+
report?.(` MANIFEST written to ${outputDir("MANIFEST.json")}`)
|
|
249
|
+
|
|
250
|
+
return { fetched, skipped, failed: failedCodes.length, failedCodes }
|
|
251
|
+
}
|
package/lib/inspire/address.ts
CHANGED
|
@@ -123,7 +123,7 @@ export function codelistValue(uri: string | null | undefined): string | null {
|
|
|
123
123
|
/**
|
|
124
124
|
* Whether a publisher marked this element void.
|
|
125
125
|
*
|
|
126
|
-
* INSPIRE writes `xsi:nil="true"
|
|
126
|
+
* INSPIRE writes `xsi:nil="true"`, in most cases with a `nilReason`.
|
|
127
127
|
* Brussels writes the nil marker and no reason on all 232,003 of its void `ad:validFrom`
|
|
128
128
|
* elements, and Czechia writes four `gn:pronunciation` elements nil with no reason.
|
|
129
129
|
*
|
|
@@ -355,7 +355,7 @@ const REFERENCE_ID_PARAMETERS = new Set(["id", "featureid", "resourceid"])
|
|
|
355
355
|
* `http://vocab.belgif.be/auth/refnis1995/1000#id` addresses the same term as that URI without it.
|
|
356
356
|
*
|
|
357
357
|
* Whether a key joins is not a property of its spelling, so this reads a key
|
|
358
|
-
* and the caller decides joinability against the features the publisher
|
|
358
|
+
* and the caller decides joinability against the features the publisher served.
|
|
359
359
|
* A check based on the URL's shape once reported 15 of Flanders' 20 references as belonging
|
|
360
360
|
* to another register, when every one of them addressed a feature of the same service.
|
|
361
361
|
*
|
|
@@ -392,7 +392,7 @@ export function componentJoinKey(href: string): string | null {
|
|
|
392
392
|
/**
|
|
393
393
|
* The spelling a `gn:GeographicalName` holds, reached from a feature that has one.
|
|
394
394
|
*
|
|
395
|
-
* Every
|
|
395
|
+
* Every INSPIRE feature that has a name nests the text at
|
|
396
396
|
* `…/gn:GeographicalName/gn:spelling/gn:SpellingOfName/gn:text`, under a per-feature
|
|
397
397
|
* prefix: an `ad:ThoroughfareName` adds `ad:name/ad:ThoroughfareNameValue/ad:name`,
|
|
398
398
|
* while an `ad:AdminUnitName` and an `ad:AddressAreaName` add only `ad:name`.
|
|
@@ -19,6 +19,7 @@ import type { PathBuilderLike } from "path-ts"
|
|
|
19
19
|
import { CSVSpliterator } from "spliterator"
|
|
20
20
|
|
|
21
21
|
import { stableSourceID } from "#adapters/source-id"
|
|
22
|
+
import { openConstituentCountryReader } from "#gb/constituent-country"
|
|
22
23
|
import type { CorpusRecipe } from "#recipes/scaffold"
|
|
23
24
|
import { defaultRecipeSource } from "#recipes/sources"
|
|
24
25
|
import { SourceRegister } from "#registers"
|
|
@@ -47,6 +48,12 @@ export interface LocalePart {
|
|
|
47
48
|
* The street combines `tipo_vial` with `nombre_via`.
|
|
48
49
|
*/
|
|
49
50
|
cnigRaw?: boolean
|
|
51
|
+
/**
|
|
52
|
+
* Opens a reader that answers the row's region from its postcode, replacing the region column.
|
|
53
|
+
*
|
|
54
|
+
* A GB part answers the constituent country this way, and its county column is not read.
|
|
55
|
+
*/
|
|
56
|
+
regionOfPostcode?: () => (postcode: string) => string | null
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
/**
|
|
@@ -158,7 +165,18 @@ const COUNTRY_SOURCES: Record<string, LocaleCountrySource> = {
|
|
|
158
165
|
source: defaultRecipeSource("synth-gb"),
|
|
159
166
|
register: SourceRegister.LandRegistryPricePaid,
|
|
160
167
|
corpusVersion: "0.9.9",
|
|
161
|
-
|
|
168
|
+
// The `REGION` column holds a county, so the constituent country is read from NSUL by postcode instead.
|
|
169
|
+
parts: [
|
|
170
|
+
{
|
|
171
|
+
path: dataRootPath("ppd", "2026-07-22", "gb-tuples.csv"),
|
|
172
|
+
districtAsLocality: true,
|
|
173
|
+
regionOfPostcode: () => {
|
|
174
|
+
const countryOf = openConstituentCountryReader()
|
|
175
|
+
|
|
176
|
+
return (postcode) => countryOf(postcode).name
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
],
|
|
162
180
|
},
|
|
163
181
|
}
|
|
164
182
|
|
|
@@ -224,6 +242,8 @@ export async function readTuples(part: LocalePart, rng: () => number): Promise<L
|
|
|
224
242
|
let header: string[] | null = null
|
|
225
243
|
let seen = 0
|
|
226
244
|
let dropped = 0
|
|
245
|
+
const regionOf = part.regionOfPostcode?.()
|
|
246
|
+
let regionUnknown = 0
|
|
227
247
|
|
|
228
248
|
try {
|
|
229
249
|
// The first row holds the column names.
|
|
@@ -304,12 +324,24 @@ export async function readTuples(part: LocalePart, rng: () => number): Promise<L
|
|
|
304
324
|
continue
|
|
305
325
|
}
|
|
306
326
|
|
|
327
|
+
const postcode = get(cells, cols.post)
|
|
328
|
+
|
|
329
|
+
let region = get(cells, cols.region) || part.region || ""
|
|
330
|
+
|
|
331
|
+
if (regionOf) {
|
|
332
|
+
region = (postcode && regionOf(postcode)) || ""
|
|
333
|
+
|
|
334
|
+
if (!region) {
|
|
335
|
+
regionUnknown++
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
307
339
|
const tuple: LocaleBaseTuple = {
|
|
308
340
|
house_number: get(cells, cols.num),
|
|
309
341
|
street,
|
|
310
342
|
locality,
|
|
311
|
-
region
|
|
312
|
-
postcode
|
|
343
|
+
region,
|
|
344
|
+
postcode,
|
|
313
345
|
...(dependent_locality ? { dependent_locality } : {}),
|
|
314
346
|
}
|
|
315
347
|
|
|
@@ -333,6 +365,10 @@ export async function readTuples(part: LocalePart, rng: () => number): Promise<L
|
|
|
333
365
|
|
|
334
366
|
console.error(` ${part.path ?? part.csv}: ${reservoir.length} sampled of ${seen} rows (${dropped} city-noise drops)`)
|
|
335
367
|
|
|
368
|
+
if (regionOf) {
|
|
369
|
+
console.error(` ${part.path ?? part.csv}: ${regionUnknown} rows have no region for their postcode`)
|
|
370
|
+
}
|
|
371
|
+
|
|
336
372
|
return reservoir
|
|
337
373
|
}
|
|
338
374
|
|
|
@@ -425,10 +461,8 @@ export const localeRecipe: CorpusRecipe = {
|
|
|
425
461
|
throw new Error(`--country-fraction must be in [0, 1], got ${countryFraction}`)
|
|
426
462
|
}
|
|
427
463
|
|
|
428
|
-
// `COUNTRY_SOURCES`
|
|
429
|
-
|
|
430
|
-
// rather than in twelve table entries.
|
|
431
|
-
const source = opts.sourceName ?? defaultRecipeSource(countrySource.source)
|
|
464
|
+
// Each `COUNTRY_SOURCES` entry already holds the current spelling from `defaultRecipeSource`.
|
|
465
|
+
const source = opts.sourceName ?? countrySource.source
|
|
432
466
|
const count = opts.count ?? 4000
|
|
433
467
|
// When unset, each part keeps its own setting.
|
|
434
468
|
// True also selects the ES pedanía parts.
|