Spatial relationship predicates for orb geometry types.
This package implements the standard OGC/DE-9IM spatial predicates for determining topological relationships between geometries.
- Implements the complete OGC/DE-9IM predicate set (
Within,Contains,Covers,CoveredBy,Intersects,Disjoint,Touches,Crosses,Overlaps). - Supports every
orbgeometry type (includingorb.Collectionandorb.Bound) for any combination of A/B inputs. - Validated against thousands of official JTS Topology Suite XML test cases that live under
testdata/jts. - Ships with extensive Go unit tests that describe the tricky edge cases you typically run into when working with GIS data.
- Includes comprehensive benchmarks for performance testing across all geometry types and sizes.
- Optimized with bounding box early-exit and efficient algorithms for real-world performance.
go get github.com/tingold/orb-predicates| Predicate | Description |
|---|---|
Within |
Geometry A is completely inside geometry B |
Contains |
Geometry A completely contains geometry B |
Covers |
No point in B is outside of A (boundary contact allowed) |
CoveredBy |
No point in A is outside of B |
Intersects |
Geometries share at least one point in common |
Disjoint |
Geometries have no points in common |
Touches |
Geometries touch at boundaries only, interiors don't intersect |
Crosses |
Geometries have some but not all interior points in common |
Overlaps |
Geometries share some but not all points, with same dimension |
All predicates support the following orb geometry types:
orb.Pointorb.MultiPointorb.LineStringorb.MultiLineStringorb.Ringorb.Polygonorb.MultiPolygonorb.Collectionorb.Bound
package main
import (
"fmt"
"github.com/paulmach/orb"
predicates "github.com/tingold/orb-predicates"
)
func main() {
// Define a polygon
poly := orb.Polygon{
orb.Ring{
orb.Point{0, 0},
orb.Point{10, 0},
orb.Point{10, 10},
orb.Point{0, 10},
orb.Point{0, 0},
},
}
// Points to test
pointInside := orb.Point{5, 5}
pointOutside := orb.Point{15, 15}
pointOnEdge := orb.Point{5, 0}
// Within / Contains
fmt.Println(predicates.Within(pointInside, poly)) // true
fmt.Println(predicates.Contains(poly, pointInside)) // true
// Intersects / Disjoint
fmt.Println(predicates.Intersects(pointOnEdge, poly)) // true
fmt.Println(predicates.Disjoint(pointOutside, poly)) // true
// Touches
fmt.Println(predicates.Touches(pointOnEdge, poly)) // true
// Line crossing polygon
line := orb.LineString{
orb.Point{-5, 5},
orb.Point{15, 5},
}
fmt.Println(predicates.Crosses(line, poly)) // true
}// Polygon-Polygon relationships
smallSquare := orb.Polygon{
orb.Ring{
orb.Point{2, 2},
orb.Point{4, 2},
orb.Point{4, 4},
orb.Point{2, 4},
orb.Point{2, 2},
},
}
largeSquare := orb.Polygon{
orb.Ring{
orb.Point{0, 0},
orb.Point{10, 0},
orb.Point{10, 10},
orb.Point{0, 10},
orb.Point{0, 0},
},
}
fmt.Println(predicates.Within(smallSquare, largeSquare)) // true
fmt.Println(predicates.Contains(largeSquare, smallSquare)) // true
fmt.Println(predicates.Covers(largeSquare, smallSquare)) // true
// Overlapping polygons
overlapping := orb.Polygon{
orb.Ring{
orb.Point{5, 5},
orb.Point{15, 5},
orb.Point{15, 15},
orb.Point{5, 15},
orb.Point{5, 5},
},
}
fmt.Println(predicates.Overlaps(largeSquare, overlapping)) // true
fmt.Println(predicates.Intersects(largeSquare, overlapping)) // trueRun the complete unit test suite:
go test ./...The package includes comprehensive benchmarks for all predicates across different geometry sizes and combinations.
# Run all benchmarks
go test -bench=. -benchmem
# Run specific predicate benchmarks
go test -bench=Within -benchmem
go test -bench=Intersects -benchmem
# Run benchmarks with longer duration for more accurate results
go test -bench=. -benchmem -benchtime=1sThe comprehensive benchmark suite includes over 80 benchmarks covering:
- Point vs Polygon: Small (5 vertices), medium (50), large (500), and very large (2000) polygons with points inside, outside, and on boundaries
- LineString vs Polygon: Lines inside, crossing, and outside polygons of various sizes
- Polygon vs Polygon: Contained, overlapping, disjoint, and touching polygons
- MultiPoint operations: Small (10), medium (100), and large (500) multipoints against polygons
- MultiPolygon operations: Point containment and polygon intersection
- LineString vs LineString: Small (10), large (100), and very large (500) linestrings, both intersecting and disjoint
- Ring operations: Ring-to-ring intersection and containment
- Collection operations: Mixed geometry collections against polygons
- Bound operations: Point, polygon, and linestring operations with bounds
- Worst-case scenarios: Points on boundaries, nearly collinear segments, and degenerate polygons
- Helper functions: Low-level geometric operations including segment intersection, point-on-segment checks, and bounding box overlap
| Operation | Small Geometry | Large Geometry (500 vertices) |
|---|---|---|
| Point in Polygon | ~250 ns | ~25 µs |
| Polygon Contains Polygon | ~1.2 µs | ~130 µs |
| Polygon Intersects (disjoint) | ~200 ns | ~10 µs |
| LineString in Polygon | ~3.7 µs | ~400 µs |
Performance scales approximately linearly with vertex count for most operations.
The package includes several optimizations for improved performance:
- Bounding box early-exit: All predicates check bounding box overlap first to quickly reject disjoint geometries
- Optimized helper functions: Internal functions like
lineStringsIntersect,ringsIntersect,ringBoundariesIntersect, andlineStringIntersectsRinginclude bounding box rejection for fast early-exit on disjoint geometries - Smart sampling for MultiPolygon containment: Uses efficient vertex-proximity checking (50 samples + targeted checks near polygon vertices) instead of dense sampling (up to 10,000 samples) when checking if linestrings are within multipolygons, providing significant performance improvements while maintaining accuracy
- Efficient bounds overlap checking: Dedicated helper functions (
ringBoundsOverlap,lineStringBoundsOverlap,lineStringRingBoundsOverlap) for optimized bounding box checks
TestJTSPredicates replays the official JTS Topology Suite XML fixtures located in testdata/jts and verifies that Intersects, Contains, Within, Covers, CoveredBy, Crosses, Overlaps, Touches, and Disjoint all mirror JTS behaviour.
go test ./... -run JTSPredicates -vNeed a quick overview of what those XML files contain? TestJTSSummary prints counts for each predicate so you can tell whether a new fixture increases coverage:
go test ./... -run JTSSummary -vThe XML files are copied from the JTS repository (see testdata/jts) and can be extended by dropping additional fixtures into that directory.
bound := orb.Bound{
Min: orb.Point{0, 0},
Max: orb.Point{10, 10},
}
point := orb.Point{5, 5}
fmt.Println(predicates.Covers(bound, point)) // true- Within: The interior of A must intersect the interior of B. Boundary-only contact is not sufficient.
- Covers: Every point of B must be in A (interior or boundary). A geometry entirely on the boundary of another is covered but not within.
- Intersects: Any shared point counts.
- Touches: Only boundary contact; interiors must not intersect.
- Crosses: For geometries of different dimensions (e.g., line crossing polygon) or lines crossing lines where the intersection is a point.
- Overlaps: For geometries of the same dimension that share area/length but neither contains the other.
See LICENSE file.