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.

4
  • Some details are missing... For example, If you have overlapping polygons, a point may belong to more than one polygon Commented Apr 24, 2021 at 7:34
  • @MichaelRovinsky thanks for the note, I updated my answer accordingly. There are no overlapping polygons. Commented Apr 24, 2021 at 9:01
  • Do you need to do it for more than one point e.g. a lot of points? Commented Apr 24, 2021 at 9:56
  • @RobinMackenzie no, just one point Commented Apr 24, 2021 at 10:40

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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.
Thanks @AndrewReid, agree this is an important point, and assume you mean the turf function ? Or is this referring to rbush as well ?
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.
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

1 Comment

i think this is the pragmatic solution to use. theoretically the problem is treated under the name "point location". 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 ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.