geokit/point_in_polygon

Point-in-polygon test for a Geometry — the operation Turf.js calls booleanPointInPolygon.

The first ring of a Polygon is its exterior and every further ring is a hole, so a point inside a hole is outside the polygon. A MultiPolygon contains a point when any of its polygons does. Rings may be closed (first point repeated at the end) or open — an open ring is closed implicitly — and may run in either direction.

A point exactly on a ring — on an edge or a vertex, of the exterior or of a hole — is on the polygon’s boundary. contains counts the boundary as inside, like Turf’s default. locate reports it separately as OnBoundary, so Turf’s ignoreBoundary: true is locate(...) == Ok(Inside).

The test is planar ray casting, as in Turf: each edge is the straight segment between its two vertices on the flat lng/lat plane, not a great-circle arc. For polygons spanning many degrees an edge therefore does not follow the shortest path between its vertices. The antimeridian is not special-cased: a ring with vertices at lng 170 and lng -170 encloses the wide band through lng 0, and lng 180 and lng -180 are different points. Split a shape that crosses the antimeridian into a MultiPolygon at ±180°, as RFC 7946 §3.1.9 recommends.

Coordinates are only subtracted and multiplied, never divided. On whole-degree coordinates the answer is therefore exact, points on a ring included; elsewhere a point within rounding error of an edge may be reported on either side of it.

Types

Where a point lies relative to a polygon, as reported by locate.

pub type Location {
  Inside
  OnBoundary
  Outside
}

Constructors

  • Inside

    Inside the exterior ring and outside every hole, not touching any ring.

  • OnBoundary

    On an edge or a vertex of the exterior ring or of a hole.

  • Outside

    Outside the exterior ring, or inside a hole.

Errors returned by contains and locate.

pub type PointInPolygonError {
  NotAPolygon
}

Constructors

  • NotAPolygon

    The geometry was a Point, MultiPoint, or LineString, none of which encloses an area.

Values

pub fn contains(
  geometry geometry: geometry.Geometry,
  point point: latlng.LatLng,
) -> Result(Bool, PointInPolygonError)

Whether point lies inside geometry, a Polygon or MultiPolygon. A point on the boundary — an edge or a vertex of any ring — counts as inside, like Turf’s booleanPointInPolygon default. A point inside a hole is outside. Use locate to tell boundary points apart.

import geokit/geometry
import geokit/latlng
import geokit/point_in_polygon

let assert Ok(a) = latlng.new(lat: 0.0, lng: 0.0)
let assert Ok(b) = latlng.new(lat: 0.0, lng: 10.0)
let assert Ok(c) = latlng.new(lat: 10.0, lng: 10.0)
let assert Ok(d) = latlng.new(lat: 10.0, lng: 0.0)
let assert Ok(p) = latlng.new(lat: 5.0, lng: 5.0)
point_in_polygon.contains(
  geometry: geometry.Polygon([[a, b, c, d, a]]),
  point: p,
)
// == Ok(True)

Any other geometry returns NotAPolygon. A polygon with no rings, or whose rings are empty, contains no point.

pub fn locate(
  geometry geometry: geometry.Geometry,
  point point: latlng.LatLng,
) -> Result(Location, PointInPolygonError)

Where point lies relative to geometry, a Polygon or MultiPolygon: Inside, OnBoundary, or Outside.

import geokit/geometry
import geokit/latlng
import geokit/point_in_polygon

let assert Ok(a) = latlng.new(lat: 0.0, lng: 0.0)
let assert Ok(b) = latlng.new(lat: 0.0, lng: 10.0)
let assert Ok(c) = latlng.new(lat: 10.0, lng: 10.0)
let assert Ok(d) = latlng.new(lat: 10.0, lng: 0.0)
let assert Ok(on_edge) = latlng.new(lat: 0.0, lng: 5.0)
point_in_polygon.locate(
  geometry: geometry.Polygon([[a, b, c, d, a]]),
  point: on_edge,
)
// == Ok(point_in_polygon.OnBoundary)

For a MultiPolygon the answer is Inside when any member polygon has the point inside, otherwise OnBoundary when any member has it on its boundary, otherwise Outside. Any other geometry returns NotAPolygon.

✨ Search Document