diff options
author | Andrew Harvey <andrew@alantgeo.com.au> | 2021-05-25 10:36:36 +1000 |
---|---|---|
committer | Andrew Harvey <andrew@alantgeo.com.au> | 2021-05-25 10:36:36 +1000 |
commit | ff194d6ce52965cc3e963c0992bc0a38c6241d18 (patch) | |
tree | 9da202fd68b739b967bbe25d633ae37e48c7aa57 | |
parent | aaa6e040482b1afd5b2485ef91e49026f610c81c (diff) |
apply patch from https://github.com/pelias/polygon-lookup/pull/50 until a new release is cut
-rw-r--r-- | .gitlab-ci.yml | 5 | ||||
-rw-r--r-- | src/polygon-lookup-patch.js | 177 |
2 files changed, 182 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1faf86f..ded44aa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -84,6 +84,7 @@ prepare: policy: push script: - yarn install + - cp src/polygon-lookup-patch.js node_modules/polygon-lookup/index.js - mkdir -p dist - make data/vicmap.geojson when: manual @@ -98,6 +99,7 @@ build vicmap: policy: pull-push script: - yarn install + - cp src/polygon-lookup-patch.js node_modules/polygon-lookup/index.js - yarn run test - mkdir -p dist data - mkdir -p data/vicmap/ll_gda94/sde_shape/whole/VIC/VMADD/layer @@ -127,6 +129,7 @@ build osm: QT_QPA_PLATFORM: "offscreen" script: - yarn install + - cp src/polygon-lookup-patch.js node_modules/polygon-lookup/index.js - mkdir -p dist data - make dist/blocksByOSMAddr.fgb - make dist/blocksByOSMAddr.geojson @@ -147,6 +150,7 @@ conflate: policy: pull script: - yarn install + - cp src/polygon-lookup-patch.js node_modules/polygon-lookup/index.js - make dist/conflate - wc -l dist/conflate/*.geojson when: manual @@ -165,6 +169,7 @@ build compareSuburb: policy: pull script: - yarn install + - cp src/polygon-lookup-patch.js node_modules/polygon-lookup/index.js - mkdir -p dist data - mkdir -p data/vicmap/ll_gda94/sde_shape/whole/VIC/VMADD/layer - touch data/VICMAP_ADDRESS.zip diff --git a/src/polygon-lookup-patch.js b/src/polygon-lookup-patch.js new file mode 100644 index 0000000..1bc09ee --- /dev/null +++ b/src/polygon-lookup-patch.js @@ -0,0 +1,177 @@ +/** + * Exports a `PolygonLookup` constructor, which constructs a data-structure for + * quickly finding the polygon that a point intersects in a (potentially very + * large) set. + */ + +'use strict'; + +var Rbush = require( 'rbush' ); +var pointInPolygon = require( 'point-in-polygon' ); +var polygonUtils = require( './lib/polygon_utils' ); +var _ = require('lodash'); + +/** + * @property {rbush} rtree A spatial index for `this.polygons`. + * @property {object} polgons A GeoJSON feature collection. + * + * @param {object} [featureCollection] An optional GeoJSON feature collection + * to pass to `loadFeatureCollection()`. + */ +function PolygonLookup( featureCollection ){ + if( featureCollection !== undefined ){ + this.loadFeatureCollection( featureCollection ); + } +} + +// Calculate point in polygon intersection, accounting for any holes +function pointInPolygonWithHoles(point, polygons) { + var mainPolygon = polygons.geometry.coordinates[0]; + if( pointInPolygon( point, mainPolygon ) ){ + for( var subPolyInd = 1; subPolyInd < polygons.geometry.coordinates.length; subPolyInd++ ){ + if( pointInPolygon( point, polygons.geometry.coordinates[ subPolyInd ] ) ){ + return false; + } + } + return true; + } + return false; +} + + +/* + * Internal helper method to return a single matching polygon + */ +PolygonLookup.prototype.searchForOnePolygon = function searchForOnePolygon( x, y ) { + // find which bboxes contain the search point. their polygons _may_ intersect that point + var bboxes = this.rtree.search( { minX: x, minY: y, maxX: x, maxY: y } ); + + var point = [ x, y ]; + + // get the polygon for each possibly matching polygon based on the searched bboxes + var polygons = bboxes.map(function(bbox, index) { + return this.polygons[ bboxes[index].polyId ]; + // find the first polygon that actually intersects and return it + }, this); + + return _.find(polygons, function(polyObj) { + return pointInPolygonWithHoles(point, polyObj); + }); +}; + +/* + * Internal helper method to return multiple matching polygons, up to a given limit. + * A limit of -1 means unlimited + */ +PolygonLookup.prototype.searchForMultiplePolygons = function searchForMultiplePolygons( x, y, limit ) { + if (limit === -1) { + limit = Number.MAX_SAFE_INTEGER; + } + + var point = [ x, y ]; + var bboxes = this.rtree.search( { minX: x, minY: y, maxX: x, maxY: y } ); + + // get the polygon for each possibly matching polygon based on the searched bboxes + var polygons = bboxes.map(function(bbox, index) { + return this.polygons[ bboxes[index].polyId ]; + }, this); + + // keep track of matches to avoid extra expensive calculations if limit reached + var matchesFound = 0; + + // filter matching polygons, up to the limit + polygons = polygons.filter(function(polygon) { + // short circuit if limit reached + if (matchesFound >= limit) { + return false; + } + + var intersects = pointInPolygonWithHoles(point, polygon); + if (intersects) { + matchesFound++; + return true; + } + return false; + }); + + // return all matching polygons as a GeoJSON FeatureCollection + return { + type : 'FeatureCollection', + features : polygons, + }; +}; + +/** + * Find polygon(s) that a point intersects. Execute a bounding-box search to + * narrow down the candidate polygons to a small subset, and then perform + * additional point-in-polygon intersections to resolve any ambiguities. + * + * @param {number} x The x-coordinate of the point. + * @param {number} y The y-coordinate of the point. + * @param {number} [limit] Number of results to return (-1 to return all the results). + * @return {undefined|object} If one or more bounding box intersections are + * found and limit is undefined, return the first polygon that intersects (`x`, `y`); otherwise, + * `undefined`. If a limit is passed in, return intercecting polygons as a GeoJSON FeatureCollection. + */ +PolygonLookup.prototype.search = function search( x, y, limit ){ + if (limit === undefined) { + return this.searchForOnePolygon( x, y ); + } else { + return this.searchForMultiplePolygons( x, y, limit ); + } +}; + +/** + * Build a spatial index for a set of polygons, and store both the polygons and + * the index in this `PolygonLookup`. + * + * @param {object} collection A GeoJSON-formatted FeatureCollection. + */ +PolygonLookup.prototype.loadFeatureCollection = function loadFeatureCollection( collection ){ + var bboxes = []; + var polygons = []; + var polyId = 0; + + function indexPolygon( poly ){ + polygons.push(poly); + var bbox = polygonUtils.getBoundingBox( poly.geometry.coordinates[ 0 ] ); + bbox.polyId = polyId++; + bboxes.push(bbox); + } + + function indexFeature( poly ){ + if( poly.geometry && + poly.geometry.coordinates[ 0 ] !== undefined && + poly.geometry.coordinates[ 0 ].length > 0){ + switch( poly.geometry.type ){ + case 'Polygon': + indexPolygon( poly ); + break; + + case 'MultiPolygon': + var childPolys = poly.geometry.coordinates; + for( var ind = 0; ind < childPolys.length; ind++ ){ + var childPoly = { + type: 'Feature', + properties: poly.properties, + geometry: { + type: 'Polygon', + coordinates: childPolys[ ind ] + } + }; + if ('id' in poly) { + childPoly.id = poly.id; + } + indexPolygon( childPoly ); + } + break; + } + } + } + + collection.features.forEach( indexFeature ); + this.rtree = new Rbush().load( bboxes ); + this.polygons = polygons; +}; + +module.exports = PolygonLookup; |