3

Given a defined (lat, lon) geo-point I'm trying to find in which polygon this point lies in. I suppose iterating over all the polygons is not efficient. Is there available any function or library for NodeJS that does this?

const polygon = getPolygonFromPoint(FeatureCollection, x, y);

There are no overlapping polygons, actually I'm using this to detect in which district of a certain country a defined GPS coordinates point lies in.

Robin Mackenzie
  • 18,801
  • 7
  • 38
  • 56
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109

2 Answers2

4

For a simple point in polygon test you can check turf which has a booleanPointInPolygon. Turf works in node but you should check for differences between v5 and v6+ around how to use npm accordingly. Points should be long/ lat (not lat/ long) and the polygon can be easily pulled out of the feature geometry of your feature collection.

For a more complex use case where you have many points and many polygons within which to locate them you should consider using rbush.

Note that the rbush library constructs an r-tree out of the bounding boxes of the polygons and not the polygons themselves, so use of an r-tree is just a way to vastly reduce the number of polygons you need to test with booleanPointInPolygon.

Example code for rbush:

const RBush = require("rbush");
const turfBbox = require("@turf/bbox").default;

const geo = {} // your feature collection...
const maxEntriesPerNode = 50; // check the doco
const tree = new RBush(maxEntriesPerNode);
const bbox2Object = (keys, bbox) => ["minX", "minY", "maxX", "maxY"].reduce((o, k, i) => ({...o, [k]: bbox[i]}), {})

// create rtree from feature collection
geo.features.forEach(feature => {
  const leaf = bbox2Object(bboxKeys, turfBbox(feature)); // use bbox of feature
  leaf["id"] = feature.properties.SOME_ID; // add some custom properties
  tree.insert(leaf);
});

// test a random point from your data
const [x, y] = [123, 456]; // should be long, lat
const test = tree.search({minX: x, minY: y, maxX: x, maxY: y});
// test should have an array of leaves per the tree.insert above

You can then perform the booleanPointInPolygon test on this reduced set of polygons.

Robin Mackenzie
  • 18,801
  • 7
  • 38
  • 56
  • 1
    Just a note: these approaches consider coordinates in Cartesian space, while D3 considers lat long pairs to be 3 dimensional points on a sphere - as a consequence D3 uses great circles to connect vertices of a polygon, which means that a point that is geographically within a polygon might be missed by the above methods, but drawn inside the polygon by D3. D3 does contain the method d3.geoContains which avoids this and any issues with antimeridians, though for the majority of cases, treating coordinates as Cartesian will result in accurate results. – Andrew Reid Apr 29 '21 at 17:05
  • Thanks @AndrewReid, agree this is an important point, and assume you mean the turf function ? Or is this referring to rbush as well ? – Robin Mackenzie Apr 30 '21 at 09:04
  • Both turf and rbush work in Cartesian coordinate space rather than a geographic coordinate space, both should return the same result, which may be geographically incorrect. The likelihood of incorrect results will be dependent on the length of polygon segments, the span of longitude crossed, and the distance from the poles. D3's geographic functions are relatively unique in using spherical math including for path rendering. – Andrew Reid Apr 30 '21 at 19:44
2

I implemented that with the library polygon-lookup

const PolygonLookup = require('polygon-lookup')
const featureCollection = {
    type: 'FeatureCollection',
    features: [{
        type: 'Feature',
        properties: { id: 'bar' },
        geometry: {
            type: 'Polygon',
            coordinates: [ [ [ 0, 1 ], [ 2, 1 ], [ 3, 4 ], [ 1, 5 ] ] ]
        }
    }]
}
var lookup = new PolygonLookup(featureCollection)
var poly = lookup.search(1, 2)
console.log(poly.properties.id) // bar
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
  • 1
    i think this is the pragmatic solution to use. theoretically the problem is treated under the name "point location". https://en.wikipedia.org/wiki/Point_location The slab decomposition approach is the origin of persistent data structures (tarjan et al). slab decomposition is log(n) - full polygons test included while the rtree appraoch is log(n) for bounding boxes + k * point in polygon test. it assumes non overlapping. interesting topic ;) – citykid Jun 25 '21 at 12:13