Skip to content

Commit

Permalink
apply patch from pelias/polygon-lookup#50 until a new release is cut
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewharvey committed May 25, 2021
1 parent aaa6e04 commit ff194d6
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
177 changes: 177 additions & 0 deletions src/polygon-lookup-patch.js
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit ff194d6

Please sign in to comment.