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