aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Harvey <andrew@alantgeo.com.au>2021-05-02 21:32:02 +1000
committerAndrew Harvey <andrew@alantgeo.com.au>2021-05-02 21:32:02 +1000
commitf8e36dd46eb331dd26d09a3659abd1ee94264016 (patch)
tree4d7bfe8717103f7a9941bc6bc6ec6a72aff088cf
initial commit
-rw-r--r--.gitlab-ci.yml21
-rw-r--r--Makefile22
-rw-r--r--filterOSM.js10
-rw-r--r--package.json14
-rw-r--r--toOSM.js170
-rwxr-xr-xvicmap2osm.js55
-rw-r--r--yarn.lock112
7 files changed, 404 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..b7aae12
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,21 @@
+image: "debian:buster-slim"
+
+stages:
+ - build
+
+build:
+ stage: build
+ script:
+ - apt-get update && apt-get install -y curl gnupg gdal-bin unzip wget
+ - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
+ - echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
+ - apt-get update && apt-get install -y yarn
+ - yarn install
+ - mkdir -p dist
+ - make downloadVicmap unzip
+ - make data/vicmap.geojson
+ - make dist/vicmap-osm.geojson
+ artifacts:
+ name: "build"
+ paths:
+ - dist
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..66de284
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+# download VicMap source data
+# the URL here usually gets manually updated weekly though no guarantees
+# it's a mirror of the upstream VICMAP data with split shp files reduced to a single shp file
+downloadVicmap:
+ mkdir -p data
+ wget --directory-prefix=data https://www.alantgeo.com.au/share/VICMAP_ADDRESS.zip
+
+unzip: data/VICMAP_ADDRESS.zip
+ mkdir -p data/vicmap
+ unzip -d data/vicmap $<
+
+data/vicmap.geojson: data/vicmap/ll_gda94/sde_shape/whole/VIC/VMADD/layer/address.shp
+ ogr2ogr -f GeoJSONSeq $@ $<
+
+dist/vicmap-osm.geojson: data/vicmap.geojson
+ ./vicmap2osm.js $< $@
+
+data/vicmap.fgb: data/vicmap/ll_gda94/sde_shape/whole/VIC/VMADD/layer/address.shp
+ ogr2ogr -f FlatGeobuf $@ $<
+
+dist/vicmap-osm.fgb: dist/vicmap-osm.geojson
+ ogr2ogr -f FlatGeobuf $@ $<
diff --git a/filterOSM.js b/filterOSM.js
new file mode 100644
index 0000000..93db4a7
--- /dev/null
+++ b/filterOSM.js
@@ -0,0 +1,10 @@
+module.exports = (feature) => {
+
+ // skip any addresses without a housenumber
+ // eg PFI 53396626 has no housenumber
+ if (!('addr:housenumber' in feature.properties)) {
+ return false
+ }
+
+ return true
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..5a1b265
--- /dev/null
+++ b/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "vicmap-addresses2osm",
+ "version": "1.0.0",
+ "description": "Transform upstream VicMap Address data into OSM schema",
+ "main": "index.js",
+ "author": "Andrew Harvey <andrew@alantgeo.com.au>",
+ "license": "MIT",
+ "dependencies": {
+ "capital-case": "^1.0.4",
+ "ndjson": "^2.0.0",
+ "readable-stream": "^3.6.0",
+ "title-case": "^3.0.3"
+ }
+}
diff --git a/toOSM.js b/toOSM.js
new file mode 100644
index 0000000..dd993d0
--- /dev/null
+++ b/toOSM.js
@@ -0,0 +1,170 @@
+const { titleCase } = require('title-case')
+const { capitalCase } = require('capital-case')
+
+const buildingUnitType = {
+ ANT: 'ANTENNA',
+ APT: 'APARTMENT',
+ ATM: 'ATM',
+ BBOX: 'BATHING BOX',
+ BERT: 'BERTH',
+ BLDG: 'BUILDING',
+ BTSD: 'BOATSHED',
+ CARP: 'CARPARK',
+ CARS: 'CARSPACE',
+ CARW: 'CARWASH',
+ CHAL: 'CHALET',
+ CLUB: 'CLUB',
+ CTGE: 'COTTAGE',
+ CTYD: 'COURTYARD',
+ DUPL: 'DUPLEX',
+ FCTY: 'FACTORY',
+ FLAT: 'FLAT',
+ GATE: 'GARAGE',
+ GRGE: 'GATE',
+ HALL: 'HALL',
+ HELI: 'HELIPORT',
+ HNGR: 'HANGAR',
+ HOST: 'HOSTEL',
+ HSE: 'HOUSE',
+ KSK: 'KIOSK',
+ LOT: 'LOT',
+ MBTH: 'MAISONETTE',
+ OFFC: 'OFFICE',
+ PSWY: 'PASSAGEWAY',
+ PTHS: 'PENTHOUSE',
+ REST: 'RESTAURANT',
+ RESV: 'RESERVE',
+ ROOM: 'ROOM',
+ RPTN: 'RECPETION',
+ SAPT: 'STUDIO APARTMENT',
+ SE: 'SUITE',
+ SHCS: 'SHOWCASE',
+ SHED: 'SHED',
+ SHOP: 'SHOP',
+ SHRM: 'SHOWROOM',
+ SIGN: 'SIGN',
+ SITE: 'SITE',
+ STLL: 'STALL',
+ STOR: 'STORE',
+ STR: 'STRATA UNIT',
+ STU: 'STUDIO',
+ SUBS: 'SUBSTATION',
+ TNCY: 'TENANCY',
+ TNHS: 'TOWNHOUSE',
+ TWR: 'TOWER',
+ UNIT: 'UNIT',
+ VLLA: 'VILLA',
+ VLT: 'VAULT',
+ WHSE: 'WAREHOUSE',
+ WKSH: 'WORKSHOP'
+}
+
+// likely these are not proper names, so we will ignore them
+const emptyNames = [
+ 'UNNAMED',
+ 'NOT NAMED'
+]
+
+module.exports = (sourceFeature) => {
+
+ const outputFeature = Object.assign({}, sourceFeature)
+ const sourceProperties = sourceFeature.properties
+ const outputProperties = {}
+
+ // Building sub address type (eg UNIT OFFICE SHOP)
+ //
+ // bld_unit_*
+ const bld_unit_1 = [
+ sourceProperties.BUNIT_PRE1,
+ sourceProperties.BUNIT_ID1 || null, // 0 is used for an empty value in the source data, so convert 0 to null
+ sourceProperties.BUNIT_SUF1
+ ].join('') || null
+
+ const bld_unit_2 = [
+ sourceProperties.BUNIT_PRE2,
+ sourceProperties.BUNIT_ID2 || null, // 0 is used for an empty value in the source data, so convert 0 to null
+ sourceProperties.BUNIT_SUF2
+ ].join('') || null
+
+ // if both 1 and 2 defined, then use a range 1-2 otherwise just select the one which was defined
+ let bld_unit = null
+ if (sourceProperties.HSA_FLAG === 'Y') {
+ bld_unit = sourceProperties.HSAUNITID
+ } else {
+ if (bld_unit_1 && bld_unit_2) {
+ bld_unit = `${bld_unit_1}-${bld_unit_2}`
+ } else if (bld_unit_1) {
+ bld_unit = bld_unit_1
+ } else if (bld_unit_2) {
+ bld_unit = bld_unit_2
+ }
+ }
+
+ if (bld_unit) {
+ outputProperties['addr:unit'] = bld_unit
+ }
+
+ if (sourceProperties.BLGUNTTYP && sourceProperties.BLGUNTTYP in buildingUnitType) {
+ outputProperties['addr:unit:type'] = buildingUnitType[sourceProperties.BLGUNTTYP]
+ }
+
+ if (sourceProperties.BUILDING) {
+ outputProperties['addr:housename'] = sourceProperties.BUILDING
+ }
+
+ // house_*
+ const house_1 = [
+ sourceProperties.HSE_PREF1,
+ sourceProperties.HSE_NUM1 || null, // 0 is used for an empty value in the source data, so convert 0 to null
+ sourceProperties.HSE_SUF1
+ ].join('')
+
+ const house_2 = [
+ sourceProperties.HSE_PREF2,
+ sourceProperties.HSE_NUM2 || null, // 0 is used for an empty value in the source data, so convert 0 to null
+ sourceProperties.HSE_SUF2
+ ].join('')
+
+ let housenumber = null
+ if (house_1 && house_2) {
+ housenumber = `${house_1}-${house_2}`
+ } else if (house_1) {
+ housenumber = house_1
+ } else if (house_2) {
+ housenumber = house_2
+ }
+
+ if (housenumber) {
+ outputProperties['addr:housenumber'] = housenumber
+ }
+
+ // display numbers used predominately in the City of Melbourne CBD by large properties. Primarily to simplify an assigned number range.
+ // so should map the assigned address or the signposted address?
+
+ // every record has at least ROAD_NAME populated
+ if (sourceProperties.ROAD_NAME && !emptyNames.includes(sourceProperties.ROAD_NAME)) {
+ outputProperties['addr:street'] = capitalCase([
+ sourceProperties.ROAD_NAME,
+ sourceProperties.ROAD_TYPE,
+ sourceProperties.RD_SUF
+ ].join(' '))
+ }
+
+ // every record has LOCALITY populated, however some values should be empty
+ if (sourceProperties.LOCALITY && !emptyNames.includes(sourceProperties.LOCALITY)) {
+ outputProperties['addr:suburb'] = capitalCase(sourceProperties.LOCALITY)
+ }
+
+ // every record has STATE populated
+ if (sourceProperties.STATE) {
+ outputProperties['addr:state'] = sourceProperties.STATE
+ }
+
+ // some records have no POSTCODE populated
+ if (sourceProperties.POSTCODE) {
+ outputProperties['addr:postcode'] = sourceProperties.POSTCODE
+ }
+
+ outputFeature.properties = outputProperties
+ return outputFeature
+}
diff --git a/vicmap2osm.js b/vicmap2osm.js
new file mode 100755
index 0000000..e579d98
--- /dev/null
+++ b/vicmap2osm.js
@@ -0,0 +1,55 @@
+#!/usr/bin/env node
+
+const fs = require('fs')
+const { Transform, pipeline } = require('readable-stream')
+const ndjson = require('ndjson')
+const toOSM = require('./toOSM.js')
+const filterOSM = require('./filterOSM.js')
+
+const args = process.argv.slice(2)
+
+if (args.length < 2) {
+ console.error("Usage: ./vicmap2osm.js input.geojson output.geojson")
+ process.exit(1)
+}
+
+const inputFile = args[0]
+const outputFile = args[1]
+
+if (!fs.existsSync(inputFile)) {
+ console.error(`${inputFile} not found`)
+ process.exit(1)
+}
+
+const transform = new Transform({
+ readableObjectMode: true,
+ writableObjectMode: true,
+ transform(feature, encoding, callback) {
+ // convert source Feature into a Feature per the OSM schema
+ const osm = toOSM(feature)
+
+ // some addresses we skip importing into OSM
+ if (filterOSM(osm)) {
+ this.push(osm)
+ }
+
+ callback()
+ }
+})
+
+// stream in source ndjson, transfom and stream out
+pipeline(
+ fs.createReadStream(inputFile),
+ ndjson.parse(),
+ transform,
+ ndjson.stringify(),
+ fs.createWriteStream(outputFile),
+ (err) => {
+ if (err) {
+ console.log(err)
+ process.exit(1)
+ } else {
+ process.exit(0)
+ }
+ }
+)
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..bb7a821
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,112 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+capital-case@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669"
+ integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+ upper-case-first "^2.0.2"
+
+inherits@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+json-stringify-safe@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+ integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+
+lower-case@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
+ integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
+ dependencies:
+ tslib "^2.0.3"
+
+minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
+ndjson@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ndjson/-/ndjson-2.0.0.tgz#320ac86f6fe53f5681897349b86ac6f43bfa3a19"
+ integrity sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==
+ dependencies:
+ json-stringify-safe "^5.0.1"
+ minimist "^1.2.5"
+ readable-stream "^3.6.0"
+ split2 "^3.0.0"
+ through2 "^4.0.0"
+
+no-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
+ integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
+ dependencies:
+ lower-case "^2.0.2"
+ tslib "^2.0.3"
+
+readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+ integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+safe-buffer@~5.2.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+split2@^3.0.0:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f"
+ integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==
+ dependencies:
+ readable-stream "^3.0.0"
+
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+through2@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764"
+ integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==
+ dependencies:
+ readable-stream "3"
+
+title-case@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"
+ integrity sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==
+ dependencies:
+ tslib "^2.0.3"
+
+tslib@^2.0.3:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
+ integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
+
+upper-case-first@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324"
+ integrity sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==
+ dependencies:
+ tslib "^2.0.3"
+
+util-deprecate@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=