aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Harvey <andrew@alantgeo.com.au>2021-06-18 16:47:12 +1000
committerAndrew Harvey <andrew@alantgeo.com.au>2021-06-18 16:47:12 +1000
commit2db70ba5db5eebd292ac08a0d10228b118bdbf27 (patch)
tree6680637ae807e97d7bf691649829284962cce39f
parentd979aeb8e311ba3970c77a57b07ec1dea622b39f (diff)
convert overlapping points into a range
-rw-r--r--README.md12
-rwxr-xr-xbin/reduceOverlap.js141
2 files changed, 88 insertions, 65 deletions
diff --git a/README.md b/README.md
index b48a7b8..bcf4858 100644
--- a/README.md
+++ b/README.md
@@ -62,10 +62,6 @@ Next, drop address ranges where the range endpoints are separately mapped (see [
make dist/vicmap-osm-uniq-flats-withinrange.geojson
-Even if we are lacking the intermediate address points within the range, we still drop the range as software can interpolate intermediate addresses from the two endpoints.
-
-Also where one of the range endpoints is mapped with `addr:flats` and the range itself has no `addr:flats` then the range is removed (for example at _116 Anderson Street, South Yarra_).
-
### Omitted addresses
Source addresses are omitted:
@@ -82,7 +78,9 @@ Some addresses appear as both a range and individual points. For example one add
Where the endpoints of the range match existing non-range address points, and where the unit value is the same, and where the individual points have different geometries the range address is dropped in favour of the individual points.
-Where the individual points share the same geometry as each other, then the range is favoured and the individual points are dropped.
+Where the individual points share the same geometry as each other, then the range is favoured and the individual points are dropped, unless one of the range endpoints is mapped with `addr:flats` and the range itself has no `addr:flats` then the range is removed (for example at _116 Anderson Street, South Yarra_).
+
+Even if we are lacking the intermediate address points within the range, we still drop the range as software can interpolate intermediate addresses from the two endpoints.
### OSM schema
@@ -141,7 +139,7 @@ Source address data contains many address points overlapping or within a close p
![reduceDuplicates in action](img/reduceDuplicates_singleCluster.png)
-2. Where each of the housenumber, street (previously also including suburb, postcode, state) are the same for each of the strictly overlapping points, but only the unit value differs we attempt to reduce these to a single address point without `addr:unit` but instead using [`addr:flats`](https://wiki.openstreetmap.org/wiki/Key:addr:flats).
+2. Where each of the housenumber, street, suburb, postcode, state are the same for each of the strictly overlapping points, but only the unit value differs we attempt to reduce these to a single address point without `addr:unit` but instead using [`addr:flats`](https://wiki.openstreetmap.org/wiki/Key:addr:flats).
`addr:flats` is the documented tag for describing the unit numbers at an address.
@@ -157,6 +155,8 @@ Data consumers can still easily explode `addr:flats` out into overlapping nodes
Because OSM tag values are limited to 255 characters, if the constructed `addr:flats` exceeds this it is split across `addr:flats`, `addr:flats1`, etc. While not ideal I don't see any other option.
+3. Where all the overlapping points have no units and the same street, suburb, state, postcode but different housenumbers, the housenumbers are combined into a range.
+
### null values
Values `UNNAMED` and `NOT NAMED` appear as street name and locality names. These values are treated as null/empty values rather than proper names.
diff --git a/bin/reduceOverlap.js b/bin/reduceOverlap.js
index 7414241..49a06ad 100755
--- a/bin/reduceOverlap.js
+++ b/bin/reduceOverlap.js
@@ -80,18 +80,18 @@ const reduce = new Transform({
}
}
- var groupedFeatures = features[key]
+ var overlappingFeatures = features[key]
- if (groupedFeatures.length === 1) {
+ if (overlappingFeatures.length === 1) {
// only one feature with this geometry, nothing to reduce, output as is
- this.push(groupedFeatures[0])
+ this.push(overlappingFeatures[0])
} else {
// multiple features with the same geometry
// group by housenumber, street, suburb, state, postcode to reduce units into addr:flats
- // groupedFeatures is all the features at the same point
+ // overlappingFeatures is all the features at the same point
const featuresGroupByNonUnit = {}
- groupedFeatures.forEach(feature => {
+ overlappingFeatures.forEach(feature => {
const key = [
feature.properties['addr:housenumber'],
feature.properties['addr:street'],
@@ -107,71 +107,94 @@ const reduce = new Transform({
featuresGroupByNonUnit[key].push(feature)
})
- Object.values(featuresGroupByNonUnit).forEach(featureGroup => {
- if (featureGroup.length > 1) {
- const hasNonUnit = featureGroup.map(f => 'addr:unit' in f.properties).includes(false)
-
- if (hasNonUnit) {
- // all have same housenumber, street, suburb, state, postcode and there is a non-unit feature
- const nonUnitFeatures = featureGroup.filter(f => (!('addr:unit' in f.properties)))
- if (nonUnitFeatures.length > 1) {
- // multiple non-unit features, unsure how to reduce
- // TODO should these still be output to be picked up by ranges
- if (argv.debug) {
- featureGroup.forEach(feature => {
- debugStreams.multipleNonUnit.write(feature)
- })
- }
- } else {
- // a single non-unit feature exists
- const nonUnitFeature = cloneDeep(nonUnitFeatures[0])
-
- // place all the other addr:unit into addr:flats on the non-unit feature
- const allOtherUnits = featureGroup.filter(f => 'addr:unit' in f.properties).map(f => f.properties['addr:unit'])
-
- // if allOtherUnits.length is one then that means we have one address without a unit and one with a unit at the same point
- // in this case we just drop the non-unit address and keep the addr:unit one
- if (allOtherUnits.length === 1) {
+ const noUnits = !overlappingFeatures.filter(f => 'addr:unit' in f.properties).length
+ const sameNonHousenumber = overlappingFeatures.map(feature => [
+ feature.properties['addr:street'],
+ feature.properties['addr:suburb'],
+ feature.properties['addr:state'],
+ feature.properties['addr:postcode']
+ ].join('|'))
+ .every( (val, i, arr) => val === arr[0] ) // check if all values are the same
+
+ const firstNumber = noUnits && sameNonHousenumber ? overlappingFeatures.map(f => f.properties['addr:housenumber']).reduce((acc, cur) => {
+ return (cur < acc) ? cur : acc
+ }) : null
+
+ const lastNumber = noUnits && sameNonHousenumber ? overlappingFeatures.map(f => f.properties['addr:housenumber']).reduce((acc, cur) => {
+ return (cur > acc) ? cur : acc
+ }) : null
+
+ if (noUnits && sameNonHousenumber && firstNumber && lastNumber) {
+ const featureAsRange = overlappingFeatures[0]
+ featureAsRange.properties['addr:housenumber'] = `${firstNumber}-${lastNumber}`
+ this.push(featureAsRange)
+ } else {
+ Object.values(featuresGroupByNonUnit).forEach(featureGroup => {
+ if (featureGroup.length > 1) {
+ const hasNonUnit = featureGroup.map(f => 'addr:unit' in f.properties).includes(false)
+
+ if (hasNonUnit) {
+ // all have same housenumber, street, suburb, state, postcode and there is a non-unit feature
+ const nonUnitFeatures = featureGroup.filter(f => (!('addr:unit' in f.properties)))
+ if (nonUnitFeatures.length > 1) {
+ // multiple non-unit features, unsure how to reduce
+ // TODO should these still be output to be picked up by ranges
if (argv.debug) {
featureGroup.forEach(feature => {
- debugStreams.oneUnitOneNonUnit.write(feature)
+ debugStreams.multipleNonUnit.write(feature)
})
}
- this.push(featureGroup.filter(f => 'addr:unit' in f.properties)[0])
} else {
- const flats = unitsToRanges(allOtherUnits, argv.verbose && featureGroup)
- nonUnitFeature.properties['addr:flats'] = flats
- this.push(nonUnitFeature)
+ // a single non-unit feature exists
+ const nonUnitFeature = cloneDeep(nonUnitFeatures[0])
+
+ // place all the other addr:unit into addr:flats on the non-unit feature
+ const allOtherUnits = featureGroup.filter(f => 'addr:unit' in f.properties).map(f => f.properties['addr:unit'])
+
+ // if allOtherUnits.length is one then that means we have one address without a unit and one with a unit at the same point
+ // in this case we just drop the non-unit address and keep the addr:unit one
+ if (allOtherUnits.length === 1) {
+ if (argv.debug) {
+ featureGroup.forEach(feature => {
+ debugStreams.oneUnitOneNonUnit.write(feature)
+ })
+ }
+ this.push(featureGroup.filter(f => 'addr:unit' in f.properties)[0])
+ } else {
+ const flats = unitsToRanges(allOtherUnits, argv.verbose && featureGroup)
+ nonUnitFeature.properties['addr:flats'] = flats
+ this.push(nonUnitFeature)
+ }
}
- }
- } else {
- // all have same housenumber, street, suburb, state, postcode but no non-unit, ie. all with different unit values
- // combine all the addr:unit into addr:flats and then drop addr:unit
- const units = featureGroup.filter(f => 'addr:unit' in f.properties).map(f => f.properties['addr:unit'])
+ } else {
+ // all have same housenumber, street, suburb, state, postcode but no non-unit, ie. all with different unit values
+ // combine all the addr:unit into addr:flats and then drop addr:unit
+ const units = featureGroup.filter(f => 'addr:unit' in f.properties).map(f => f.properties['addr:unit'])
- if (units.length <= 1) {
- console.log(`all have same housenumber, street, suburb, state, postcode with no non-unit, but only found ${units.length} units`, units)
- process.exit(1)
- }
+ if (units.length <= 1) {
+ console.log(`all have same housenumber, street, suburb, state, postcode with no non-unit, but only found ${units.length} units`, units)
+ process.exit(1)
+ }
- const feature = cloneDeep(featureGroup[0])
- delete feature.properties['addr:unit']
+ const feature = cloneDeep(featureGroup[0])
+ delete feature.properties['addr:unit']
- const flats = unitsToRanges(units, argv.verbose && featureGroup)
- feature.properties['addr:flats'] = flats
+ const flats = unitsToRanges(units, argv.verbose && featureGroup)
+ feature.properties['addr:flats'] = flats
+ this.push(feature)
+ }
+ } else if (featureGroup.length === 1) {
+ // while other features share the same geometry, this one is unique in it's housenumber,street,suburb,state,postcode
+ // so output this feature, and we deal with the overlap at another stage
+ const feature = featureGroup[0]
this.push(feature)
+
+ if (argv.debug) {
+ debugStreams.sameGeometry.write(feature)
+ }
}
- } else if (featureGroup.length === 1) {
- // while other features share the same geometry, this one is unique in it's housenumber,street,suburb,state,postcode
- // so output this feature, and we deal with the overlap at another stage
- const feature = featureGroup[0]
- this.push(feature)
-
- if (argv.debug) {
- debugStreams.sameGeometry.write(feature)
- }
- }
- })
+ })
+ }
}
callback()