geometry

package
v0.4.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package geometry provides 2D geometry primitives (Point, LineString, Polygon, MultiPoint) with WKB and WKT encoding, a coordinate reference system model, and common spatial operations (area, distance, centroid, convex hull, intersection tests, polygon boolean ops).

All coordinates are 2D. XY order (X = longitude/easting, Y = latitude/northing) matches the WKB, WKT, and GeoJSON specifications. Angles are always in degrees at the API surface; internal trig uses radians.

Index

Constants

View Source
const DefaultBufferSegments = 32

DefaultBufferSegments is the number of straight segments used to approximate a full circle in Buffer operations when the caller doesn't specify one.

View Source
const DefaultClipTolerance = 1e-10

DefaultClipTolerance is the relative tolerance used by the boolean-op engine when the caller doesn't set one. 1e-10 gives ~13 significant figures — enough for coastal-scale UTM coordinates (~1e6 m).

View Source
const DefaultHilbertOrder = 16

DefaultHilbertOrder is the resolution used when SortByHilbert or downstream helpers omit an explicit order. 16 bits per axis = 65,536 × 65,536 cells, which gives sub-parts-per-million spatial discrimination on any planet-scale bounding box (~30 meters on a WGS84 world-extent bbox) — enough that within-row-group locality dominates any residual quantization error.

View Source
const EarthRadiusKM = 6371.0088

EarthRadiusKM is the mean Earth radius used by haversine calculations.

View Source
const RTreeNodeSize = 16

RTreeNodeSize is the maximum number of children in an R-tree node. Values around 16 balance memory density and traversal cost.

Variables

View Source
var (
	WGS84          = CRS{EPSG: 4326, Name: "WGS 84", Projected: false}
	PseudoMercator = CRS{EPSG: 3857, Name: "WGS 84 / Pseudo-Mercator", Projected: true}
)

Known CRSes. The set is intentionally small; add as needed.

View Source
var (
	ErrShortWKB          = errors.New("geometry: WKB too short")
	ErrInvalidByteOrder  = errors.New("geometry: invalid WKB byte order")
	ErrUnsupportedWKB    = errors.New("geometry: unsupported WKB geometry type")
	ErrTypeMismatch      = errors.New("geometry: WKB type does not match target")
	ErrEmptyGeometry     = errors.New("geometry: geometry has no points")
	ErrInvalidWKT        = errors.New("geometry: invalid WKT")
	ErrUnknownCRS        = errors.New("geometry: unknown CRS")
	ErrInvalidUnit       = errors.New("geometry: invalid distance unit")
	ErrCRSMismatch       = errors.New("geometry: operation requires geometries in the same CRS")
	ErrProjectionMissing = errors.New("geometry: reprojection between these CRSes is not implemented")
)
View Source
var ErrAntimeridianCrossing = errors.New("geometry: geometry crosses the antimeridian")

ErrAntimeridianCrossing indicates that an operation received a geographic-CRS geometry whose vertices span the ±180° meridian and cannot be interpreted correctly under gobi's cartesian primitives without pre-splitting. Callers should either

  • route through SplitAtAntimeridian, or
  • reproject to a projected CRS that avoids the discontinuity.
View Source
var ErrAntipodalPoints = errors.New("geometry: cannot interpolate a unique great circle through antipodal points")

ErrAntipodalPoints is returned when SampleGeodesic is asked to interpolate between two points that are exact antipodes: the great circle through them isn't unique, so there's no canonical arc to sample. Callers can dodge this by nudging one endpoint slightly or splitting the path through an intermediate waypoint.

View Source
var ErrCircleFit = errors.New("geometry: cannot fit circle to input")

ErrCircleFit is returned by FitCircle when the input cannot resolve to a unique circle (e.g. fewer than 3 points, or points collinear within numerical tolerance).

View Source
var ErrEllipseFromFoci = errors.New("geometry: cannot construct ellipse from foci")

ErrEllipseFromFoci is returned by EllipseFromFoci when the given major-axis length is too small to accommodate the two foci (majorAxis < |f1 - f2|), which is geometrically impossible.

View Source
var ErrGeodesicRequiresGeographic = errors.New(
	"geometry: geodesic ops require a geographic CRS (X = longitude, Y = latitude in degrees)")

ErrGeodesicRequiresGeographic is returned by SampleGeodesic / DensifyGeodesic when the input isn't in a geographic CRS. "Great-circle" only makes sense on lon/lat coordinates; projected- CRS callers wanting "insert intermediate vertices in a straight line" should linearly interpolate themselves.

View Source
var ErrGeographicCRS = errors.New("geometry: boolean ops require a projected CRS")

ErrGeographicCRS is returned by boolean-op functions when either input is in a geographic (non-projected) CRS. The current engine is planar-only; geographic inputs must be reprojected to a projected CRS first (see EstimateUTMCRS / ToCRS).

Functions

func Area

func Area(g Geometry, u Unit) (float64, error)

Area returns the planar (XY) area of g in u². Non-polygonal geometries return 0.

func BoundsCompatible added in v0.4.1

func BoundsCompatible(pred Predicate, ab, bb Bounds) bool

BoundsCompatible reports whether the two bounds are compatible under pred's necessary-condition. Wrong answers here would falsely reject valid predicate matches, so err on the side of "compatible" for any predicate we don't specifically handle.

Used by TestPrepared internally and by the per-row bbox-reject fast paths in Series predicate ops (which read row bboxes via BoundsFromWKB and can short-circuit false without a full ParseWKB when this returns false).

PredIntersects / PredTouches / PredCrosses / PredOverlaps: bboxes must overlap. PredContains: a's bbox must cover b's bbox. PredWithin: b's bbox must cover a's bbox.

func BoundsMinDistance added in v0.4.1

func BoundsMinDistance(a, b Bounds) float64

BoundsMinDistance returns the minimum Euclidean distance between two axis-aligned bounding rectangles. Zero when they overlap or touch. Empty bounds → +Inf.

Used by WithinDistance's short-circuit; also exposed publicly so per-row `Series.GeomDWithin` callers can reject far rows via `BoundsFromWKB` + this helper without a full ParseWKB.

func CentroidAndBoundsFromWKB added in v0.4.1

func CentroidAndBoundsFromWKB(data []byte) (Point, Bounds, error)

CentroidAndBoundsFromWKB is the fused-scan variant: computes the centroid AND the 2D bounding box in a single byte-stream pass. The centroid semantics match CentroidFromWKB; the bounds semantics match BoundsFromWKB. Callers who need both (e.g. the fused HilbertSortWithCovering write path) save a full second byte-scan.

func Contains

func Contains(a, b Geometry) bool

Contains reports whether a fully contains b.

func ConvexHullFromXY added in v0.4.1

func ConvexHullFromXY(xs, ys []float64) (hullXs, hullYs []float64)

ConvexHullFromXY runs Andrew's monotone-chain algorithm on parallel Xs / Ys slabs and returns the convex hull as a fresh pair of slabs in counter-clockwise order, with the closing vertex (a repeat of the first) appended so the output is a closed ring.

Design vs the AoS Graham scan

The AoS Polygon.ConvexHull in polygon.go runs Graham scan: find a pivot, sort every other vertex by polar angle from the pivot via `sort.Slice` on []Point (which boxes the closure function-value and moves 40-byte Point structs during partition), then stack-scan retaining CCW turns. Two nested allocations (the closure box + the sorted []Point copy) plus point-struct swaps in-place.

Andrew's monotone chain replaces the polar-angle sort with an index sort by (x, y) lex — the resulting order is the same linear structure for the two half-hulls. Two O(n) stack scans over an index permutation (lower hull, then upper hull) produce the CCW output. Sort is on `[]int` indices via `slices.SortFunc` (8-byte swaps + typed callback — no closure boxing that `sort.Slice` would incur); the hull-scan reads coordinates directly from the input slabs so the inner-loop arithmetic operates on cache-friendly float64 arrays.

Semantics

  • Fewer than 3 unique points return a copy of the input slabs (no closing vertex appended — matches the AoS shape which also returns the input unchanged when Exterior() has <3 points).
  • Duplicate points are tolerated. Since the (x,y) lex order is a total order over distinct points, sort stability is irrelevant — equal keys mean the same point, and the CCW scan drops the extra via strict `<= 0` cross-product rejection just like collinear points on the hull edge (matches the AoS behavior).
  • Output vertex count includes the closing repeat, so a hull with k unique vertices has k+1 entries.

func Crosses added in v0.3.0

func Crosses(a, b Geometry) bool

Crosses reports whether a and b have some interior points in common but not all — typically LineString vs Polygon or LineString vs LineString. Matches shapely's a.crosses(b).

func CrossesAntimeridian added in v0.3.0

func CrossesAntimeridian(g Geometry) bool

CrossesAntimeridian reports whether any adjacent vertex pair in g has |Δlon| > 180°, which under WGS84 interpretation means the edge between them wraps around the ±180° meridian. Returns false for projected-CRS inputs (the concept doesn't apply) and for empty geometries. Assumes X = longitude, Y = latitude in degrees, matching WKB convention.

func Euclidean

func Euclidean(from, to Point, u Unit) (float64, error)

Euclidean returns the planar distance between two Points, in the requested unit. The input coordinates are assumed to already be in meters (projected CRS); Z / CRS are ignored.

Breaking change in v0.2.16: previously took four float64 args (x1, y1, x2, y2). The new signature aligns with Haversine + HaversineBatch + Point.Distance. Migration: replace `Euclidean(a.X, a.Y, b.X, b.Y, u)` with `Euclidean(a, b, u)`.

func GeomDistance added in v0.3.0

func GeomDistance(a, b Geometry, u Unit) (float64, error)

GeomDistance returns the minimum planar (Euclidean) distance between any two points in a and b. Returns 0 when they intersect. Uses point-to-segment distance across all vertex-vs-edge pairs from both sides — O(V_a·E_b + V_b·E_a) in the general case.

Coordinates are treated as planar meters; for geographic (WGS84 lon/lat) inputs the result is Euclidean on degrees, which is meaningless. Project to a suitable CRS first (see Point.Distance / Haversine for lon/lat point pairs).

func Haversine

func Haversine(from, to Point, u Unit) (float64, error)

Haversine returns the great-circle distance between two lon/lat Points on a sphere of Earth radius, in the requested unit. Point X = longitude, Y = latitude (WKB convention); Z / CRS are ignored.

Breaking change in v0.2.16: previously took four float64 args (lon1, lat1, lon2, lat2). The new signature aligns with HaversineBatch and Point.Distance so callers holding geometry types can pass them directly. Migration: replace `Haversine(a.X, a.Y, b.X, b.Y, u)` with `Haversine(a, b, u)`.

func HaversineBatch added in v0.2.16

func HaversineBatch(from, to []Point, u Unit) ([]float64, error)

HaversineBatch returns per-pair great-circle distances between from[i] and to[i] in the requested unit. Semantically equivalent to calling Haversine(from[i], to[i], u) in a loop, but bulk-optimized:

  • The unit conversion factor, Earth-radius constant, and degree-to-radian scale are hoisted outside the inner loop (one metersPerUnit call for the whole batch).
  • Per-row math runs in a fixed-count vars body — Go's inliner keeps it tight vs. the per-call scalar Haversine which pays a function-call boundary + defer + err-check per row.

Both input slices must be the same length; a mismatch returns ErrColumnLenMismatch. Empty slices are legal and return an empty (non-nil) result.

CRS on each Point is ignored — Haversine is a lon/lat sphere computation that expects Y=latitude, X=longitude in degrees. Points in a projected CRS give nonsensical distances; converting via ToCRS(WGS84) before calling is the caller's responsibility.

Return type is a flat []float64 — same shape a downstream SIMD kernel or arrow builder wants. Nulls in the input aren't signaled (Point isn't a nullable type); to skip-and-preserve row positions, either pre-filter or pass sentinel points and mask the output.

func HilbertIndex added in v0.3.5

func HilbertIndex(x, y float64, bounds Bounds, order int) uint64

HilbertIndex returns the 1D position of (x, y) along a space-filling Hilbert curve of the given order, computed after normalizing (x, y) into a `2^order × 2^order` integer grid derived from bounds.

The Hilbert curve preserves 2D locality: points close in (x, y) tend to be close in HilbertIndex. Sorting rows by their centroid's HilbertIndex before writing a GeoParquet file makes per-row-group bboxes small, which is what the v0.3.4 row-group pushdown machinery actually needs to prune usefully on real data.

Contract:

  • bounds must be a valid non-empty rectangle. Empty bounds return 0 (all points "index to origin"), which sorts to random-adjacent order — a no-op rather than a crash.
  • order in [1, 31]. Values outside clamp to DefaultHilbertOrder. Order 16 (default) is enough for continental datasets; order 24+ approaches meter-scale on a WGS84 world extent.
  • Points outside bounds are clamped to the boundary before quantization — an off-by-a-hair polygon still gets a stable index.

The 2D Hilbert construction is the standard iterative quadrant-rotation algorithm (see e.g. Wikipedia's "Hilbert curve" article, xy2d). No lookup tables — hot loop is O(order) integer ops, ~30 ns/call at order=16.

func Intersects

func Intersects(a, b Geometry) bool

Intersects reports whether a and b share any point. The relation is symmetric.

func IsEmpty added in v0.3.0

func IsEmpty(g Geometry) bool

IsEmpty reports whether g has no coordinates. Matches shapely's .is_empty semantics: a Point with the default (0,0) is NOT empty (shapely's convention), but a Polygon with no rings, a LineString with fewer than 2 points, or a MultiPolygon with no components IS.

func IsValid added in v0.3.0

func IsValid(g Geometry) bool

IsValid reports whether g satisfies the OGC Simple Features validity rules gobi checks:

  • LineString: >= 2 points, no consecutive duplicate vertices.
  • Polygon: every ring closed (or auto-closable), >= 3 unique vertices per ring, no ring self-intersection.
  • Multi types: all components valid.
  • Point / MultiPoint: always valid.

This is deliberately a subset of GEOS's IsValid — full OGC validity includes checks like "holes lie inside exterior" and "rings don't touch except at points" which require more machinery than gobi currently exposes. Returns false for structurally-broken input; callers can rely on IsValid == true meaning "safe to pass to Boolean / Buffer without triggering degenerate-input paths."

func Length

func Length(g Geometry, u Unit) (float64, error)

Length returns the planar (XY) length of g in u. Non-linear geometries (Point, MultiPoint, Polygon) return 0. Polygons don't return perimeter here — use Polygon.Perimeter for that.

func MetersPerUnit added in v0.2.16

func MetersPerUnit(u Unit) (float64, error)

MetersPerUnit returns the number of meters in one of the given unit. Exported so callers building their own bulk distance kernels can hoist the scale factor outside a hot loop instead of paying a per-call `metersPerUnit` lookup.

func MultiPolygonRingViewsFromWKB added in v0.4.1

func MultiPolygonRingViewsFromWKB(data []byte) ([][]PointsView, error)

MultiPolygonRingViewsFromWKB parses a MultiPolygon or MultiPolygonZ WKB directly into a [][]PointsView with one []PointsView per sub-polygon. Same layout as MultiPolygon.PolygonRingViews() but no []Point intermediate.

func Overlaps added in v0.3.0

func Overlaps(a, b Geometry) bool

Overlaps reports whether a and b share interior points but neither contains the other, and both are of the same dimension (both areas or both lines). Matches shapely's a.overlaps(b).

func PIPFromWKB added in v0.4.1

func PIPFromWKB(data []byte, tx, ty float64) (bool, error)

PIPFromWKB reports whether (tx, ty) lies inside the polygon encoded in data. Semantics match Polygon.Contains(pt) for Polygon inputs (test point against exterior, exclude holes) and MultiPolygon "point in any constituent polygon" for MultiPolygon inputs. Non-polygon type codes (Point, LineString, etc.) return (false, nil) — matches the AoS shape where Polygon.Contains isn't defined on those types.

This is Slice 4's WKB-facing entry point. Walks the byte stream once with an inline even-odd crossing test — no []Point or Polygon allocation, no `closedRing` copy for unclosed rings. Zero allocation per call.

Same "points on the boundary have undefined containment" caveat as the AoS pointInRing kernel. Callers requiring boundary-inclusive semantics should pair this with a separate on-boundary scan, matching the pointInPolygon → pointOnPolygonBoundary shape in the AoS path.

func PIPInclusiveFromWKB added in v0.4.1

func PIPInclusiveFromWKB(data []byte, tx, ty float64) (bool, error)

PIPInclusiveFromWKB reports whether (tx, ty) lies inside the polygon encoded in data OR on its boundary. Semantics match the AoS `pointInPolygon(pt, poly)` exactly, i.e. boundary-inclusive containment. This is the entry point SJoin's Points × Polygons refine needs — the strict-interior `PIPFromWKB` has documented undefined behavior on boundaries, which would silently change SJoin semantics for grid-aligned data.

Single WKB pass: extends the ring scan to also do a point-on-segment test per segment (collinearity + within-bbox). Returns true on first boundary hit; otherwise finalizes with the crossing-count parity. Zero allocation on well-formed input.

Non-polygon type codes (Point, LineString, etc.) return (false, nil) — matches PIPFromWKB.

func PIPPolygonFromRings added in v0.4.1

func PIPPolygonFromRings(rings []PointsView, tx, ty float64) bool

PIPPolygonFromRings tests point-in-polygon over a full polygon represented as an ordered slice of PointsViews: rings[0] is the exterior ring; rings[1:] are holes. Returns true iff (tx, ty) lies inside the exterior and outside every hole.

Semantics match Polygon.Contains on the AoS representation. Zero allocation. Callers holding a Polygon can obtain the RingViews once via `polygon.RingViews()` (Slice 1 amortization) and reuse the slice across many candidate points — the common pattern in spatial-join refine loops.

Empty rings input returns false.

func PIPRingFromXY added in v0.4.1

func PIPRingFromXY(xs, ys []float64, tx, ty float64) bool

PIPRingFromXY reports whether the point (tx, ty) lies inside the polygon ring defined by parallel Xs / Ys slices. Even-odd crossing rule; matches the semantics of pointInRing on the AoS `[]Point` representation.

Handles both closed rings (last point equals first) and unclosed rings — the crossing test walks segments (i, i+1) for i in [0, n-1) and (if the ring isn't already closed) implicitly the (n-1, 0) closing segment via the modulo-wrap loop shape.

Points on the boundary have undefined containment, same as the AoS pointInRing kernel. Callers requiring boundary-inclusive semantics should combine with a separate on-boundary check (matches pointInPolygon → pointOnPolygonBoundary in the AoS path).

Empty ring (fewer than 3 points) or mismatched-length slices return false. Zero-allocation on every input.

func PROJJSONFor added in v0.3.0

func PROJJSONFor(epsg int32) map[string]any

PROJJSONFor returns the canonical PROJJSON object for the given EPSG code, or nil if we don't have one on file. Callers include this in GeoParquet's "geo" metadata so downstream readers (geopandas via pyproj) can round-trip the CRS.

func PlanarAreaFromWKB added in v0.4.1

func PlanarAreaFromWKB(data []byte) (float64, error)

PlanarAreaFromWKB returns the absolute planar (XY) area of the WKB geometry in coord² without materializing intermediate []Point / Polygon structs. Polygon area is exterior − holes; MultiPolygon sums per-polygon area; GeometryCollection recurses. Non-areal types (Point, LineString, MultiPoint, MultiLineString) contribute 0.

Semantics match `PlanarRingArea` composed via `Polygon.Area` on a projected CRS — i.e. the shoelace formula applied ring-by- ring with unclosed rings treated as if the first vertex were virtually appended. Unit conversion is left to the caller (multiply by `1 / (perM*perM)` for a projected CRS whose linear unit is meters). Geographic CRSes must fall back to the AoS spherical-excess path.

Byte-order / type-code handling mirrors ParseWKB; malformed input returns an error. Zero-allocation on well-formed input.

func PlanarLengthFromWKB added in v0.4.1

func PlanarLengthFromWKB(data []byte) (float64, error)

PlanarLengthFromWKB returns the planar (XY) length of the WKB geometry in coordinate units without materializing any intermediate []Point / LineString structs. Sums Euclidean segment lengths for LineString / MultiLineString; recurses into GeometryCollections. All other type codes (Point, MultiPoint, Polygon, MultiPolygon) contribute 0 — matches the AoS shape of `geometry.Length` where non-linear geometries return 0.

This is the SoA sibling of BoundsFromWKB / CentroidFromWKB for callers that only need the planar linear extent — e.g. a Series GeomLength column over a projected CRS. Returns coordinate-unit length; Unit conversion is left to the caller (multiply by `1 / metersPerUnit(u)` for a projected CRS whose linear unit is meters). Geographic CRSes must fall back to the AoS Haversine path — the WKB blob doesn't carry CRS context.

Byte-order / type-code handling mirrors ParseWKB; malformed input returns an error. Zero-allocation on well-formed input.

func PlanarMinDistanceFromWKB added in v0.4.1

func PlanarMinDistanceFromWKB(a, b []byte) (float64, error)

PlanarMinDistanceFromWKB returns the min planar Euclidean distance between two WKB-encoded geometries. Parses both to slab-form representations directly (no `[]Point` intermediate) and runs the Slice-11 min-distance nested loop with a single `math.Sqrt` at the end.

Allocation shape

The parse skips `[]Point` materialization but does `append` onto per-role slabs (pointXs/pointYs, polylines). For most workloads this amortizes to a small constant number of `growslice` calls; callers that need strict zero-alloc must pre-scan geometry counts and pass hinted slabs — not a supported entry point today.

Non-intersection assumption

This function is a fast path for the (bbox-disjoint → definitely non-intersecting) case that `Series.GeomDistance` uses when filtering rows against a fixed `other`. It does NOT run a segment-segment intersects check; intersecting geometries can produce a nonzero distance (matching the vertex-to-segment approach that `planarMinDistance` returns for non-intersecting inputs). Callers that need `Intersects → 0` semantics must verify bboxes are disjoint or fall through to the AoS `GeomDistance(a, b, u)` path.

Returns math.Inf(+1) when both inputs are empty (no vertex or segment to compare against). Malformed WKB returns an error.

func PlanarRingArea added in v0.3.4

func PlanarRingArea(ring []Point) float64

PlanarRingArea returns the planar (unsigned) area of a ring via the shoelace formula. Independent of CRS — callers that need the spherical-area interpretation for geographic coordinates should use Polygon.Area with a Unit instead. Exposed so downstream packages (predicate stats, buffer heuristics) can compute planar areas without re-implementing the shoelace loop.

Rings that aren't explicitly closed (first vertex != last vertex) are treated as if the caller had appended the first vertex again.

func PointToPolylineMinDistanceSq added in v0.4.1

func PointToPolylineMinDistanceSq(px, py float64, xs, ys []float64, closed bool) float64

PointToPolylineMinDistanceSq returns the minimum squared Euclidean distance from (px, py) to any point on the polyline held in parallel Xs / Ys slabs. When closed=true the closing segment (last, first) is also considered — matching the ring closure that Polygon.Segments enumerates.

Empty polyline returns math.Inf(1) (no segments to compare against). Single-point polyline returns squared distance from (px, py) to that single vertex.

Zero-alloc; single pass over the polyline slabs.

func PointToSegmentDistanceSqXY added in v0.4.1

func PointToSegmentDistanceSqXY(px, py, ax, ay, bx, by float64) float64

PointToSegmentDistanceSqXY returns the squared Euclidean distance from (px, py) to the closed line segment ((ax, ay), (bx, by)). Handles a == b (zero-length segment) as squared point-to-point distance.

Why squared

Min-distance loops repeatedly compare distances and only need the sqrt on the final answer. The classic AoS pointToSegmentDistance in distance_geom.go calls math.Hypot per segment; on a polygon×polygon distance with ~100 vertices each, that's 10k sqrts per per-row call. Squared form defers to a single sqrt at the outermost call — see planarMinDistance's slab-form rewrite (Slice 11).

The formula is the standard projection-onto-line-segment:

t = ((p-a) · (b-a)) / |b-a|²   clamped to [0, 1]
f = a + t·(b-a)                 nearest point on segment
d² = (p.x - f.x)² + (p.y - f.y)²

When |b-a|² == 0 the segment is a single point; return squared distance from p to a directly.

func RegisterCRS

func RegisterCRS(c CRS)

RegisterCRS adds a CRS to the runtime registry. Overwrites any prior entry for the same EPSG code.

func SimplifyDPFromXY added in v0.4.1

func SimplifyDPFromXY(xs, ys []float64, tolerance float64) (outXs, outYs []float64)

SimplifyDPFromXY runs iterative Douglas-Peucker on parallel Xs / Ys slabs, returning a fresh pair of slabs containing the retained coordinates. Endpoints are always preserved. tolerance ≤ 0 or n < 3 returns a copy of the input coordinates.

Design vs. the AoS douglasPeucker

The AoS `douglasPeucker([]Point, float64)` in simplify.go is recursive; at every split it slices the []Point twice and stitches the two halves with a fresh append allocation. Frequent splits on real-world polylines (coastlines, admin boundaries) mean O(log n) heap allocations per polyline alongside the O(n) `[]Point` walks the recursion drives.

This SoA rewrite is iterative on an explicit (lo, hi) stack plus a keep-bitmap:

  • One []bool allocation of length n.
  • One stack allocation (typically log₂(n) frames — 20-ish for a coastline-scale ring).
  • Two final output []float64 allocations sized to the exact retained count.

Total: 3-4 allocations regardless of split count, vs the AoS recursion's O(log n) per-split appends.

The perpendicular-distance kernel avoids the sqrt+div on non-splitting segments: instead of computing `d = |cross| / segLen` per point and comparing against `tolerance`, it tracks `argmax(cross²)` across the sub-array and compares once against `tolerance² * segLen²`. Saves one sqrt per split when no interior point exceeds tolerance (the common case near the leaves of the DP tree).

Determinism vs. AoS

Split order matches the AoS recursion (left before right) so the produced vertex indices are identical for well-formed input. Tie-breaking on argmax also matches (both use strict `>` which picks the first occurrence of the max).

func String

func String(g Geometry) string

String returns g's WKT representation, prefixed by its CRS if set.

func Test

func Test(pred Predicate, a, b Geometry) bool

Test evaluates pred on the ordered pair (a, b).

func TestPointPrepared added in v0.4.2

func TestPointPrepared(pred Predicate, x, y float64, prep PreparedGeometry) bool

TestPointPrepared evaluates pred on (Point{x, y}, prep.G) without the interface-boxing allocation that Prepare(Point{...}) + TestPrepared would incur. The atomic query underlying TestPointsPrepared — the batch API is essentially a loop over this with a held R-tree scratch.

Fast-path shapes (same as TestPrepared, non-swapped ordering):

  • Point × Polygon: pred ∈ {Intersects, Within}
  • Point × MultiPolygon: pred ∈ {Intersects, Within}

Every other pred / prep shape falls through to per-call Test — no panic, no error, just AoS semantics.

Concurrent-safe against the prep (atomic lazy slots).

func TestPointsPrepared added in v0.4.2

func TestPointsPrepared(pred Predicate, xs, ys []float64, prep PreparedGeometry, out []bool)

TestPointsPrepared evaluates pred for each (Point{xs[i], ys[i]}, prep.G) pair, writing results into out. Semantics match a per- index Test(pred, Point{X: xs[i], Y: ys[i]}, prep.G).

The batch amortizes prep-side work across every point:

  • Ring views / R-tree / bounds are materialized once by Prepare and shared across the entire call.
  • No per-point interface-boxing of the point into a Geometry (single-point TestPrepared costs one alloc per Prepare(Point{...})).
  • MultiPolygon R-tree Search scratch is held for the whole batch, not Get/Put'd per point.
  • Bbox reject is inlined into the loop with no dispatch overhead.

Fast-path shapes (same as TestPrepared, non-swapped ordering):

  • Point × Polygon: pred ∈ {Intersects, Within}
  • Point × MultiPolygon: pred ∈ {Intersects, Within}

Every other pred (Contains, Disjoint, ...) or prep shape falls through to per-point Test — correctness preserved, no batch speedup.

len(xs) must equal len(ys); len(out) must be ≥ len(xs). Panics on mismatch (matches compute-package convention).

Concurrent-safe against the prep — atomic lazy slots let multiple goroutines batch-query the same prep. The out buffer obviously must not be shared across goroutines.

func TestPrepared added in v0.4.1

func TestPrepared(pred Predicate, a, b PreparedGeometry) bool

TestPrepared evaluates pred on the ordered pair (a, b) using precomputed SoA views when the pair shape has a fast path. Semantically equivalent to Test(pred, a.G, b.G).

Nil geometry on either side returns false for every predicate (matches Test). Non-fast-path shapes fall through to Test — the only cost of using TestPrepared over Test on those shapes is the bounds-reject double-check + the type-switch overhead (single-digit nanoseconds per call).

func Touches added in v0.3.0

func Touches(a, b Geometry) bool

Touches reports whether a and b share at least one boundary point but no interior points. Matches shapely's a.touches(b). Empty geometries return false.

func TypeString added in v0.3.0

func TypeString(g Geometry) string

TypeString returns the OGC-style name for g's concrete type ("Point", "MultiPolygon", etc.). Matches shapely's .geom_type output.

func UTMEpsgFor

func UTMEpsgFor(lon, lat float64) int32

UTMEpsgFor returns the EPSG code of the UTM CRS covering (lon, lat) on the WGS84 datum. Latitude sign selects hemisphere.

func UTMZoneFor

func UTMZoneFor(lon float64) int

UTMZoneFor returns the UTM zone number [1..60] whose central meridian nearest longitude lon.

func WKB

func WKB(g Geometry) []byte

WKB returns the Well-Known Binary encoding of g.

func WKBTypeCode added in v0.4.1

func WKBTypeCode(data []byte) (typ uint32, hasZ bool, err error)

WKBTypeCode returns the OGC type code (and whether Z is present) of the WKB blob without decoding any coordinates. Zero-alloc. Callers dispatching per-type fast paths (SoA scanners vs AoS fallback based on shape) use this to peek before committing to a full ParseWKB.

Returned codes match the WKB spec: 1=Point, 2=LineString, 3=Polygon, 4=MultiPoint, 5=MultiLineString, 6=MultiPolygon, 7=GeometryCollection. hasZ=true when the code is the 1001-1007 variant.

func Within

func Within(a, b Geometry) bool

Within is Contains(b, a).

func WithinDistance added in v0.3.5

func WithinDistance(a, b Geometry, d float64) bool

WithinDistance reports whether any two points in a and b are at most d coordinate units apart. Equivalent to `GeomDistance(a, b) <= d` but with a bbox-distance short-circuit that lets far-apart pairs return false without walking edges.

d must be non-negative; d = 0 is equivalent to Intersects(a, b). Nil operands or NaN d return false. Coordinates are treated as planar — for lon/lat inputs, project to a suitable CRS first (Haversine + a per-row loop covers the geographic case).

The bbox short-circuit computes the minimum distance between the two bounding rectangles: if that's already > d, no interior point pair could be closer. This is what makes DWithin's row- group pushdown pay off — a row whose bbox is far from an AOI bbox never gets its WKB decoded.

Types

type BoolOp added in v0.3.0

type BoolOp uint8

BoolOp names a binary boolean operation on polygons.

const (
	// OpIntersection returns the area contained by both operands.
	OpIntersection BoolOp = iota
	// OpUnion returns the area contained by either operand.
	OpUnion
	// OpDifference returns subject minus clipping (subject \ clipping).
	OpDifference
	// OpSymDifference returns the area in exactly one of the operands.
	OpSymDifference
)

func (BoolOp) String added in v0.3.0

func (op BoolOp) String() string

type Bounds

type Bounds struct {
	MinX, MinY, MaxX, MaxY float64
}

Bounds is an axis-aligned 2D bounding box: (MinX, MinY, MaxX, MaxY).

func BoundsFromWKB added in v0.4.1

func BoundsFromWKB(data []byte) (Bounds, error)

BoundsFromWKB computes the axis-aligned 2D bounding box of a WKB geometry without materializing any intermediate Point / Polygon / etc. structs. Walks the byte stream once, tracking running min/max on X and Y.

This is Slice 2's SoA fast path for bbox-only callers — the parquetio bbox-covering-column write path, GeoParquet metadata bounds compute, and any Filter/predicate hot path that only needs the bbox of each input geometry. Skips the O(n) `[]Point` allocation that ParseWKB does even though the caller throws the geometry away immediately after `.Bounds()`.

Semantics match `ParseWKB(data).Bounds()` exactly:

  • Empty geometries (empty LineString / Polygon / GeometryCollection) return EmptyBounds().
  • Z coordinates are ignored — matches the 2D Bounds type.
  • MultiPoint / MultiLineString / MultiPolygon / GeometryCollection recursively include every sub-geometry's coordinates. Nested GeometryCollections are rejected inside a GeometryCollection (matching ParseWKB).
  • Byte-order and type-code handling mirrors ParseWKB; unsupported type codes return ErrUnsupportedWKB.

The scanner is per-call zero-allocation on well-formed input. Malformed input returns an error without leaking partial state.

func BoundsFromXY added in v0.4.1

func BoundsFromXY(xs, ys []float64) Bounds

BoundsFromXY computes the axis-aligned bounding box of the points held in parallel Xs / Ys slices. Assumes len(xs) == len(ys); a mismatch is caller error and produces bounds derived from the shorter slice.

This is the SoA-form kernel that spatial-predicate hot paths call once they've materialized (or been handed) a PointsView. Kept as a free function so callers holding raw `[]float64` slabs — e.g. from a WKB parse-into-slab entry point in a future slice, or from arrow-backed coordinate columns — can call it directly without constructing a PointsView.

Portable Go body — no SIMD wire-in. Slice 6a shipped compute.BoundsF64 with a SIMD variant that wins 2.4× on Apple silicon (deep-OOO cores) but LOSES 48% on Ampere Neoverse (throughput-tuned server cores where vector Min/Max costs more per lane than scalar compare-branch). Rather than pick a default that regresses on one class of production hardware, keep the scalar body here and let advanced callers who know their target arch call compute.BoundsF64 explicitly. See the Slice 6 recap in .vscode/CLAUDE.md for the full Apple-vs-Ampere measurement.

func EmptyBounds

func EmptyBounds() Bounds

EmptyBounds returns a bounds value that will always be extended by the first point passed to Extend.

func (Bounds) Contains

func (b Bounds) Contains(x, y float64) bool

Contains reports whether the bounds contain (x, y). The upper edges are inclusive.

func (Bounds) Empty

func (b Bounds) Empty() bool

Empty reports whether the bounds are the inverted-sentinel returned by EmptyBounds — i.e. no point has ever been added to them.

Empty is NOT the same as "zero-area rectangle." A Point's Bounds is {x, y, x, y} (min == max on both axes), which is not Empty — it's a legitimate zero-area rectangle carrying the point's location. Only the inverted sentinel signals "unset." Use IsZero to test for the zero-value Bounds{} shape, which callers using bounds as a reference-frame parameter often want to treat as "derive from data."

func (Bounds) Extend

func (b Bounds) Extend(x, y float64) Bounds

Extend returns a Bounds enlarged to include (x, y).

func (Bounds) Intersects

func (b Bounds) Intersects(o Bounds) bool

Intersects reports whether the two bounds overlap.

func (Bounds) IsZero added in v0.3.5

func (b Bounds) IsZero() bool

IsZero reports whether the bounds are the Go zero-value Bounds{} (all four fields == 0). Distinct from Empty (which catches the inverted-sentinel EmptyBounds()). Callers that accept bounds as an optional reference frame (see HilbertSortOptions.Bounds) treat IsZero as "unspecified — derive from data" so users don't need to know about the sentinel form.

func (Bounds) Union

func (b Bounds) Union(o Bounds) Bounds

Union returns the smallest Bounds containing both b and o.

type BufferOptions

type BufferOptions struct {
	// Segments controls the number of straight edges used to approximate a
	// full circle in BufferRound style. Higher = smoother, larger output.
	// 0 falls back to DefaultBufferSegments. Ignored when Style is
	// BufferSquare.
	Segments int
	// Style picks between rounded and square outputs. See BufferStyle.
	Style BufferStyle
}

BufferOptions configures Buffer behavior.

type BufferStyle added in v0.3.0

type BufferStyle uint8

BufferStyle picks the shape of Buffer's caps and joins.

const (
	// BufferRound emits rounded caps (semicircles) and rounded convex
	// joins. Matches shapely's cap_style=round + join_style=round.
	// Default (zero value).
	BufferRound BufferStyle = iota
	// BufferSquare emits square outputs: Point becomes an
	// axis-aligned square of half-width = distance; LineString gets
	// flat caps extended by distance and mitre joins; Polygon gets
	// mitre (sharp) joins. Matches shapely's cap_style=square +
	// join_style=mitre. Faster than BufferRound and produces fewer
	// vertices, but strongly-concave inputs may need clamping.
	BufferSquare
)

type CRS

type CRS struct {
	EPSG      int32
	Name      string
	Projected bool
}

CRS identifies a coordinate reference system. Only EPSG code is authoritative for equality — Name is a human-readable label.

func LookupCRS

func LookupCRS(epsg int32) (CRS, error)

LookupCRS returns the CRS for the given EPSG code, or ErrUnknownCRS.

func (CRS) Equal

func (c CRS) Equal(o CRS) bool

Equal reports whether two CRSes refer to the same system.

func (CRS) String

func (c CRS) String() string

func (CRS) Zero

func (c CRS) Zero() bool

Zero reports whether the CRS is the zero value.

type Circle added in v0.3.1

type Circle struct {
	Center Point
	Radius float64
}

Circle is a first-class circular shape defined by its center and radius. Deliberately NOT a Geometry: OGC SFA / WKB has no encoding for circles, so callers who need to serialize should lower via Boundary or BoundaryLine first.

Radius units follow Center.CRSValue: meters for UTM / PseudoMercator, degrees for WGS84. Callers wanting a Circle "in meters" from a geographic-CRS point cluster should reproject the input via ToCRS before calling FitCircle.

func FitCircle added in v0.3.1

func FitCircle(points []Point, opts CircleFitOptions) (Circle, []float64, error)

FitCircle fits a Circle to the input points via least squares and returns per-point signed geometric residuals — residuals[i] is (|points[i] - center| - radius), positive when the point is outside the fit circle, negative when inside. Requires at least 3 points. Returns ErrCircleFit when the points are collinear or the system is otherwise degenerate.

The fit is 2D in the coordinate plane of the input Points; Z is ignored. Output Circle's CRS is inherited from the first point.

func (Circle) Area added in v0.3.1

func (c Circle) Area() float64

Area returns πr².

func (Circle) Boundary added in v0.3.1

func (c Circle) Boundary(nVertices int) Polygon

Boundary returns a closed Polygon approximating the circle with nVertices segments (nVertices < 4 falls back to DefaultBufferSegments for consistency with Point.Buffer). The resulting polygon has exactly nVertices+1 points (the last equals the first) and is wound CCW.

func (Circle) BoundaryLine added in v0.3.1

func (c Circle) BoundaryLine(nVertices int) LineString

BoundaryLine returns an open LineString along the circle's circumference (n vertices, NOT closed — the last point is not a repeat of the first). Useful when callers want to represent an arc without polygon closure semantics. nVertices < 4 falls back to DefaultBufferSegments.

func (Circle) Circumference added in v0.3.1

func (c Circle) Circumference() float64

Circumference returns 2πr.

func (Circle) Contains added in v0.3.1

func (c Circle) Contains(p Point) bool

Contains reports whether p lies inside (or on the boundary of) the circle. Uses the Euclidean distance from p to the center. Only the 2D X/Y coordinates are considered.

func (Circle) Distance added in v0.3.1

func (c Circle) Distance(p Point) float64

Distance returns the SIGNED distance from p to the circle's boundary in the coordinate plane's units: negative when p is inside the circle, positive when outside, zero on the boundary. This matches the "level set" convention (φ(x) = |x - center| - r).

type CircleFitMethod added in v0.3.1

type CircleFitMethod int

CircleFitMethod selects the least-squares algorithm.

const (
	// FitTaubin (default): geometrically-weighted algebraic fit
	// (Taubin 1991 / Chernov). Closed-form via one Newton step on a
	// cubic; unbiased when the point cloud spans only a partial arc.
	// Preferred for GIS-style fits where points may cluster on one
	// side of the true circle.
	FitTaubin CircleFitMethod = iota
	// FitKasa: plain algebraic fit (Kasa 1976). Faster (linear
	// solve, no root-find) but biased toward smaller radii on
	// partial-arc inputs. Use when speed dominates and inputs are
	// known to span most of the circumference.
	FitKasa
)

type CircleFitOptions added in v0.3.1

type CircleFitOptions struct {
	Method CircleFitMethod
}

CircleFitOptions tunes FitCircle behavior. The zero value picks sensible defaults.

type ClipOptions added in v0.3.0

type ClipOptions struct {
	// Tolerance controls when two coordinates are treated as coincident.
	// Comparisons scale by max(|x|, |y|), so this is a relative tolerance.
	// Zero picks DefaultClipTolerance.
	Tolerance float64
}

ClipOptions tunes the boolean-op engine. The zero value picks sensible defaults; callers rarely need to override.

type Ellipse added in v0.3.3

type Ellipse struct {
	Center   Point
	SemiA    float64 // "first" semi-axis (along +X in ellipse-local frame)
	SemiB    float64 // "second" semi-axis (along +Y in ellipse-local frame)
	Rotation float64 // CCW radians from +X to ellipse-local +X
}

Ellipse is an axis-aligned-then-rotated elliptical shape. Like Circle it is deliberately NOT a Geometry — OGC SFA / WKB has no ellipse encoding, so callers who need to serialize should lower via Boundary(n) → Polygon or BoundaryLine(n) → LineString.

Rotation is measured in radians, counter-clockwise from the +X axis. When Rotation == 0, SemiA runs along +X and SemiB along +Y. Units follow Center.CRSValue (meters for UTM, degrees for WGS84); for physically-meaningful ellipses on the ground, reproject the input via ToCRS to a projected CRS first.

func EllipseFromFoci added in v0.3.3

func EllipseFromFoci(f1, f2 Point, majorAxis float64) (Ellipse, error)

EllipseFromFoci constructs the unique ellipse with the two given foci and a specified major-axis length. Requires majorAxis >= |f1 - f2| (otherwise no ellipse satisfies the definition). SemiA is placed along the f1→f2 direction; SemiB perpendicular.

Both foci must share (or leave unset) the same CRS; the output Ellipse inherits it from f1.

func NewEllipse added in v0.3.3

func NewEllipse(center Point, semiA, semiB, rotation float64) Ellipse

NewEllipse returns an Ellipse with the given center, semi-axes, and rotation. Semi-axis order is not enforced: callers may pass SemiA < SemiB (rotation still applies).

func (Ellipse) Area added in v0.3.3

func (e Ellipse) Area() float64

Area returns π·a·b — exact.

func (Ellipse) Boundary added in v0.3.3

func (e Ellipse) Boundary(nVertices int) Polygon

Boundary returns a closed CCW Polygon approximating the ellipse with nVertices segments. Each vertex is the parametric point (a cos t, b sin t) transformed by Rotation and translated to Center. Points nVertices < 4 fall back to DefaultBufferSegments (matching Circle.Boundary and Point.Buffer for consistency).

func (Ellipse) BoundaryLine added in v0.3.3

func (e Ellipse) BoundaryLine(nVertices int) LineString

BoundaryLine returns an open LineString along the ellipse's circumference (nVertices points, NOT closed — the last point is not a repeat of the first). Useful for representing an arc without polygon closure semantics.

func (Ellipse) Bounds added in v0.3.3

func (e Ellipse) Bounds() Bounds

Bounds returns the axis-aligned bounding box of the rotated ellipse. Derived from the parametric extremum:

x_max = √(a²cos²θ + b²sin²θ)
y_max = √(a²sin²θ + b²cos²θ)

where θ is the rotation. The box is centered on e.Center and has full width 2·x_max, full height 2·y_max.

func (Ellipse) Circumference added in v0.3.3

func (e Ellipse) Circumference() float64

Circumference returns an approximation via Ramanujan's second formula:

C ≈ π(a+b) · (1 + 3h / (10 + √(4 - 3h)))
where h = ((a-b)/(a+b))²

Accurate to ~1e-9 for eccentricity < 0.9 (i.e. axis ratio > 0.44), degrading to ~1e-4 near extreme eccentricity. No closed-form exact solution exists for the ellipse's perimeter (it's an incomplete elliptic integral of the second kind).

func (Ellipse) Contains added in v0.3.3

func (e Ellipse) Contains(p Point) bool

Contains reports whether p lies inside or on the boundary of the ellipse. Transforms p to the ellipse-local frame (translate to center, rotate by -Rotation) and evaluates (x'/a)² + (y'/b)² ≤ 1. Only the 2D X/Y coordinates are considered.

type Geometry

type Geometry interface {
	// Type returns the concrete geometry type.
	Type() Type
	// CRS returns the coordinate reference system.
	CRS() CRS
	// Bounds returns the axis-aligned bounding box (minX, minY, maxX, maxY).
	Bounds() Bounds
	// Is3D reports whether the geometry carries Z (XYZ). Bounds and 2D
	// operations ignore Z whether or not the geometry is 3D.
	Is3D() bool
	// Centroid returns the geometry's centroid using its type-specific
	// definition. For Point this is the identity; for lines, polygons,
	// and collections see the concrete implementations.
	Centroid() Point
	// WKT returns the Well-Known Text representation.
	WKT() string
	// AppendWKB appends the little-endian WKB encoding to buf and returns
	// the resulting slice.
	AppendWKB(buf []byte) []byte
}

Geometry is the common interface for all geometry primitives.

func Boolean added in v0.3.0

func Boolean(a, b Geometry, op BoolOp, opts ClipOptions) (Geometry, error)

Boolean applies op to a and b using the given options. Boolean is the shared entry point that Clip/Union/Difference/SymDifference route through.

func Buffer

func Buffer(g Geometry, distance float64, opts BufferOptions) (Geometry, error)

Buffer returns a polygon (or MultiPolygon-shaped result) approximating the set of points within distance of g. The Style option in opts picks between rounded (default) and square caps/joins.

Only positive distances are supported. Passing distance <= 0 returns (nil, error). Buffering Polygon inputs assumes the input's exterior ring is convex or nearly so; strongly concave inputs may produce self- intersecting output rings (see file-level comment).

func Clip added in v0.3.0

func Clip(subject, mask Geometry) (Geometry, error)

Clip returns the geometric intersection of subject and mask. Both must be Polygon or MultiPolygon in the same projected CRS. Non-polygonal inputs return an error. An empty result is a Polygon with no rings.

func Difference added in v0.3.0

func Difference(a, b Geometry) (Geometry, error)

Difference returns a minus b (points in a that are not in b). See Clip for input rules.

func Dissolve added in v0.3.0

func Dissolve(geoms []Geometry) (Geometry, error)

Dissolve merges every polygon in geoms into their union. Inputs may be Polygon or MultiPolygon; other geometry types return an error. All inputs must agree on CRS (an unset CRS is treated as a wildcard).

The implementation clusters inputs by bbox connectivity using an R-tree, then unites each cluster via a spatially-sorted divide-and-conquer merge (like shapely's unary_union). On fully-disjoint inputs this is near-linear; on heavily-overlapping inputs it's O(n log n) sweeps.

func ParseWKB

func ParseWKB(data []byte) (Geometry, error)

ParseWKB decodes a Well-Known Binary geometry. The resulting geometry has no CRS set; callers must supply one from context (e.g. a GeoParquet schema).

func ParseWKT

func ParseWKT(s string) (Geometry, error)

ParseWKT parses a Well-Known Text geometry. Whitespace and case around the type keyword are tolerated; a " Z" qualifier after the keyword (e.g. "POINT Z (1 2 3)") switches the parser into 3D mode. Coordinates must be numeric.

func Project

func Project(g Geometry, target CRS) (Geometry, error)

Project reprojects g from its current CRS to target. If the source or target CRS is unset, WGS84 is assumed. Supported source/target pairs:

  • WGS84 (EPSG:4326) ↔ Web Mercator (EPSG:3857)
  • WGS84 (EPSG:4326) ↔ UTM zone (EPSG:32601–32660 / 32701–32760)

Other pairs (Mercator ↔ UTM, UTM ↔ UTM) are routed through WGS84 internally.

func Simplify

func Simplify(g Geometry, tolerance float64) (Geometry, error)

Simplify returns a copy of g with vertices removed until every discarded vertex lies within tolerance (planar distance) of the retained polyline. Uses the Douglas-Peucker algorithm.

tolerance is measured in the CRS's linear unit (degrees for WGS84, meters for a projected CRS). Passing tolerance <= 0 returns g unchanged.

Point and MultiPoint pass through untouched; the algorithm doesn't apply to them. GeometryCollection recurses into each component.

func SplitAtAntimeridian added in v0.3.0

func SplitAtAntimeridian(g Geometry) (Geometry, error)

SplitAtAntimeridian returns g split into components that each lie on one side of the ±180° meridian. Antimeridian-crossing edges get a synthetic vertex inserted at (±180, interpolated-lat) on each side. Non-crossing inputs pass through unchanged.

Supported types: LineString → MultiLineString, MultiLineString → MultiLineString, Polygon → MultiPolygon (assuming the exterior ring crosses an even number of times and holes don't cross), MultiPolygon → MultiPolygon. Point / MultiPoint pass through unchanged. GeometryCollection recurses into components.

Returns an error only if the input CRS is projected (where the operation is meaningless) — in that case the geometry is returned unchanged.

func SymDifference added in v0.3.0

func SymDifference(a, b Geometry) (Geometry, error)

SymDifference returns the symmetric difference of a and b (points in exactly one of them). See Clip for input rules.

func Union added in v0.3.0

func Union(a, b Geometry) (Geometry, error)

Union returns the geometric union of a and b. See Clip for input rules.

type GeometryCollection

type GeometryCollection struct {
	Geometries []Geometry
	CRSValue   CRS
	HasZ       bool
}

GeometryCollection is a heterogeneous set of geometries. Per the OGC spec and PostGIS's practical guarantee, a GeometryCollection may not directly contain another GeometryCollection. Setting HasZ marks the collection as 3D; each contained geometry is expected to share that dimensionality.

func NewGeometryCollection

func NewGeometryCollection(gs []Geometry, crs CRS) GeometryCollection

NewGeometryCollection returns a 2D GeometryCollection wrapping the given geometries.

func NewGeometryCollectionZ

func NewGeometryCollectionZ(gs []Geometry, crs CRS) GeometryCollection

NewGeometryCollectionZ returns a 3D GeometryCollection.

func (GeometryCollection) AppendWKB

func (c GeometryCollection) AppendWKB(buf []byte) []byte

func (GeometryCollection) Bounds

func (c GeometryCollection) Bounds() Bounds

func (GeometryCollection) CRS

func (c GeometryCollection) CRS() CRS

func (GeometryCollection) Centroid

func (c GeometryCollection) Centroid() Point

Centroid returns the midpoint of the collection's overall bounding box. A meaningful centroid for a heterogeneous collection is not universally defined; the bounds midpoint is a stable, cheap approximation.

func (GeometryCollection) EstimateUTMCRS

func (c GeometryCollection) EstimateUTMCRS() (CRS, error)

EstimateUTMCRS returns the CRS of the UTM zone covering the collection's bounds midpoint.

func (GeometryCollection) Is3D

func (c GeometryCollection) Is3D() bool

func (GeometryCollection) Type

func (c GeometryCollection) Type() Type

func (GeometryCollection) WKT

func (c GeometryCollection) WKT() string

type LineString

type LineString struct {
	Points   []Point
	CRSValue CRS
	HasZ     bool
}

LineString is an ordered sequence of two or more points. Set HasZ to true to encode Z values in WKB/WKT output.

func DensifyGeodesic added in v0.3.2

func DensifyGeodesic(l LineString, stepMeters float64) (LineString, error)

DensifyGeodesic replaces every segment of l with its great-circle densification at ≤ stepMeters spacing. Endpoints of each original segment are always preserved. Requires l to be in a geographic CRS (see SampleGeodesic).

stepMeters is measured on a sphere of Earth radius (matching the existing Haversine implementation). Values <= 0 return the input unchanged.

func NewLineString

func NewLineString(pts []Point, crs CRS) LineString

NewLineString returns a 2D LineString sharing the underlying slice.

func NewLineStringZ

func NewLineStringZ(pts []Point, crs CRS) LineString

NewLineStringZ returns a 3D LineString.

func (LineString) AppendWKB

func (l LineString) AppendWKB(buf []byte) []byte

func (LineString) Bounds

func (l LineString) Bounds() Bounds

Bounds returns the XY bounding box. Z is ignored.

func (LineString) Buffer

func (l LineString) Buffer(distance float64, segments int) Polygon

Buffer returns a polygon approximating the set of points within distance of the linestring, using rounded joins at internal vertices and rounded caps at the endpoints. Requires the linestring to have at least 2 points.

func (LineString) CRS

func (l LineString) CRS() CRS

func (LineString) Centroid

func (l LineString) Centroid() Point

Centroid returns the length-weighted midpoint of the linestring in planar XY. Each segment contributes its midpoint weighted by its planar length; the result carries l's CRS.

func (LineString) Clip added in v0.3.12

func (l LineString) Clip(p Polygon) []LineString

Clip returns the sub-LineStrings of l that lie inside p (inclusive of the polygon boundary). Fragments preserve order along l: fragment i occurs before fragment i+1 when walking l from its first to last vertex.

Contract:

  • l fully inside p -> []LineString{l}
  • l fully outside p -> nil
  • l touches p at a single vertex -> nil (zero-length overlap dropped)
  • l coincident with a p edge -> the coincident sub-segment is returned as one fragment (convex path only; the concave/holed path treats boundary-coincident sub-segments as unspecified — a follow-up will tighten this)

Coordinate system: planar (WGS84 lon/lat treated as x/y). Callers that need geodesic accuracy should densify l first. Antimeridian handling is the caller's responsibility (see SplitAtAntimeridian).

func (LineString) EstimateUTMCRS

func (l LineString) EstimateUTMCRS() (CRS, error)

EstimateUTMCRS returns the CRS of the UTM zone covering the linestring's bounds midpoint. If the linestring is in a projected CRS, the midpoint is inverse-projected to WGS84 first.

func (LineString) Is3D

func (l LineString) Is3D() bool

func (LineString) Length

func (l LineString) Length(u Unit) (float64, error)

Length returns the total planar (XY) length of the linestring in the requested unit. Z is ignored. Geographic CRSes use haversine; projected CRSes use planar distance.

func (LineString) Simplify

func (l LineString) Simplify(tolerance float64) LineString

Simplify returns a copy of l with vertices removed via Douglas-Peucker at the given planar tolerance. Endpoints are always preserved. If the line has fewer than 3 points it is returned unchanged (a 2-point line is already the simplest possible representation).

func (LineString) SplitBy added in v0.3.12

func (l LineString) SplitBy(p Polygon) (inside, outside []LineString)

SplitBy returns the fragments of l that lie inside p and the fragments that lie outside p, in a single pass. Both slices preserve order along l. Equivalent to Clip(p) plus a complementary clip, but shares the per-segment edge-intersection work.

func (LineString) ToCRS

func (l LineString) ToCRS(target CRS) (LineString, error)

ToCRS reprojects the linestring into target.

func (LineString) Type

func (l LineString) Type() Type

func (LineString) View added in v0.4.1

func (l LineString) View() PointsView

View materializes a PointsView from a LineString's ordered points. Empty input yields a non-nil empty view.

func (LineString) WKT

func (l LineString) WKT() string

type MultiLineString

type MultiLineString struct {
	Lines    []LineString
	CRSValue CRS
	HasZ     bool
}

MultiLineString is a collection of LineStrings.

func NewMultiLineString

func NewMultiLineString(lines []LineString, crs CRS) MultiLineString

NewMultiLineString returns a 2D MultiLineString wrapping lines.

func NewMultiLineStringZ

func NewMultiLineStringZ(lines []LineString, crs CRS) MultiLineString

NewMultiLineStringZ returns a 3D MultiLineString.

func (MultiLineString) AppendWKB

func (m MultiLineString) AppendWKB(buf []byte) []byte

func (MultiLineString) Bounds

func (m MultiLineString) Bounds() Bounds

func (MultiLineString) CRS

func (m MultiLineString) CRS() CRS

func (MultiLineString) Centroid

func (m MultiLineString) Centroid() Point

Centroid returns the length-weighted centroid of the multi-linestring, where each component contributes its centroid weighted by its planar length. Empty geometry returns the zero Point.

func (MultiLineString) Clip added in v0.3.12

func (m MultiLineString) Clip(p Polygon) []LineString

Clip returns the sub-LineStrings of m that lie inside p (inclusive of the polygon boundary). Fragments preserve two orderings: fragments from component i appear before fragments from component i+1, and within each component they appear in walk order. See LineString.Clip for the per-component edge-case contract.

func (MultiLineString) EstimateUTMCRS

func (m MultiLineString) EstimateUTMCRS() (CRS, error)

EstimateUTMCRS returns the CRS of the UTM zone covering the multiline's bounds midpoint.

func (MultiLineString) Is3D

func (m MultiLineString) Is3D() bool

func (MultiLineString) Length

func (m MultiLineString) Length(u Unit) (float64, error)

Length sums the length of every line in the requested unit (XY only).

func (MultiLineString) LineViews added in v0.4.1

func (m MultiLineString) LineViews() []PointsView

LineViews materializes one PointsView per LineString in a MultiLineString. Each view carries the multi's CRS + HasZ.

func (MultiLineString) SplitBy added in v0.3.12

func (m MultiLineString) SplitBy(p Polygon) (inside, outside []LineString)

SplitBy returns the fragments of m that lie inside p and the fragments that lie outside p. Both slices preserve component-and-walk order: component i's fragments appear before component i+1's, and within a component fragments appear in walk order.

func (MultiLineString) Type

func (m MultiLineString) Type() Type

func (MultiLineString) WKT

func (m MultiLineString) WKT() string

type MultiPoint

type MultiPoint struct {
	Points   []Point
	CRSValue CRS
	HasZ     bool
}

MultiPoint is an unordered collection of points. Set HasZ to true to encode Z on WKB/WKT output.

func NewMultiPoint

func NewMultiPoint(pts []Point, crs CRS) MultiPoint

func NewMultiPointZ

func NewMultiPointZ(pts []Point, crs CRS) MultiPoint

func (MultiPoint) AppendWKB

func (m MultiPoint) AppendWKB(buf []byte) []byte

func (MultiPoint) Bounds

func (m MultiPoint) Bounds() Bounds

func (MultiPoint) CRS

func (m MultiPoint) CRS() CRS

func (MultiPoint) Centroid

func (m MultiPoint) Centroid() Point

Centroid returns the arithmetic mean of the multipoint's points.

func (MultiPoint) EstimateUTMCRS

func (m MultiPoint) EstimateUTMCRS() (CRS, error)

EstimateUTMCRS returns the CRS of the UTM zone covering the multipoint's bounds midpoint.

func (MultiPoint) Is3D

func (m MultiPoint) Is3D() bool

func (MultiPoint) Type

func (m MultiPoint) Type() Type

func (MultiPoint) View added in v0.4.1

func (m MultiPoint) View() PointsView

View materializes a PointsView from a MultiPoint's points. Order follows MultiPoint.Points.

func (MultiPoint) WKT

func (m MultiPoint) WKT() string

type MultiPolygon

type MultiPolygon struct {
	Polygons []Polygon
	CRSValue CRS
	HasZ     bool
}

MultiPolygon is a collection of Polygons.

func NewMultiPolygon

func NewMultiPolygon(polys []Polygon, crs CRS) MultiPolygon

NewMultiPolygon returns a 2D MultiPolygon wrapping polys.

func NewMultiPolygonZ

func NewMultiPolygonZ(polys []Polygon, crs CRS) MultiPolygon

NewMultiPolygonZ returns a 3D MultiPolygon.

func (MultiPolygon) AppendWKB

func (m MultiPolygon) AppendWKB(buf []byte) []byte

func (MultiPolygon) Area

func (m MultiPolygon) Area(u Unit) (float64, error)

Area sums the planar (XY) area of every component polygon in u².

func (MultiPolygon) Bounds

func (m MultiPolygon) Bounds() Bounds

func (MultiPolygon) CRS

func (m MultiPolygon) CRS() CRS

func (MultiPolygon) Centroid

func (m MultiPolygon) Centroid() Point

Centroid returns the area-weighted centroid of the multipolygon. Each component contributes its own centroid weighted by its planar area (or spherical area for geographic CRSes). Empty geometry returns the zero Point.

func (MultiPolygon) Contains

func (m MultiPolygon) Contains(pt Point) bool

Contains reports whether pt lies inside any polygon in the collection.

func (MultiPolygon) EstimateUTMCRS

func (m MultiPolygon) EstimateUTMCRS() (CRS, error)

EstimateUTMCRS returns the CRS of the UTM zone covering the multipolygon's bounds midpoint.

func (MultiPolygon) Is3D

func (m MultiPolygon) Is3D() bool

func (MultiPolygon) PolygonRingViews added in v0.4.1

func (m MultiPolygon) PolygonRingViews() [][]PointsView

PolygonRingViews materializes one []PointsView per Polygon in a MultiPolygon. Outer slice index selects the polygon; inner slice mirrors that polygon's ring layout (exterior first, then holes).

func (MultiPolygon) Type

func (m MultiPolygon) Type() Type

func (MultiPolygon) WKT

func (m MultiPolygon) WKT() string

type Point

type Point struct {
	X, Y, Z  float64
	CRSValue CRS
	HasZ     bool
}

Point is a 2D or optionally 3D (XYZ) point. Z is populated only when HasZ is true; otherwise it is ignored by encoders and decoders.

func AntimeridianCrossings added in v0.3.0

func AntimeridianCrossings(g Geometry) []Point

AntimeridianCrossings returns the (lon=±180, lat) points at which g's boundary crosses the antimeridian, in the order encountered while walking edges. Each physical crossing produces ONE entry (at lon matching the edge's starting side); callers building split output should mirror to the opposite side themselves.

func Centroid

func Centroid(g Geometry) Point

Centroid returns the centroid of g using each concrete type's own definition. Kept as a package-level helper for API symmetry with Area and Length; internally this is just interface dispatch on g.Centroid().

func CentroidFromWKB added in v0.4.1

func CentroidFromWKB(data []byte) (Point, error)

CentroidFromWKB extracts the geometry's centroid from a WKB blob without materializing intermediate `[]Point` / `Polygon` / etc. structs. Walks the byte stream once, running per-type centroid accumulators against the raw coordinate pairs.

This is Slice 3's SoA fast path for the `SortByHilbert` write path. The two-pass `SortByHilbertWith` and the fused `HilbertSortWithCovering` both currently parse every row's WKB into a full geometry, call `.Centroid()`, and discard the geometry — exactly the shape BoundsFromWKB (Slice 2) already targets on the bbox side.

Semantics vs g.Centroid()

The returned Point matches `ParseWKB(data).Centroid()` on Point, LineString, Polygon, MultiPoint, and MultiLineString. Divergences:

  • MultiPolygon centroid uses bbox-center. The AoS `MultiPolygon.Centroid()` weights each sub-polygon by its geodesic area (`Area(UnitMeters)`), which requires CRS context this scanner doesn't carry from the WKB alone. bbox-center is locality-preserving and CRS-independent — enough for spatial-sort use cases (Hilbert-index inputs) which don't care about geodesic accuracy.

  • GeometryCollection centroid uses bbox-center. This matches the AoS implementation exactly (see collection.go — `GeometryCollection.Centroid()` already returns bbox-center).

The returned Point's CRS is unset — the WKB blob doesn't carry CRS. Callers embedding CRS via a schema/annotation must set it themselves.

Zero-allocation on well-formed input.

func CircleIntersectionPoints added in v0.3.3

func CircleIntersectionPoints(c1, c2 Circle) []Point

CircleIntersectionPoints returns the points at which the boundaries of c1 and c2 cross:

  • two points for two circles that properly overlap
  • one point when the circles are externally or internally tangent
  • zero points when the circles are disjoint, one properly contains the other, or the circles are concentric (in which case the "intersection" is either empty or the whole circle, neither of which is representable as a finite point set)

Result points inherit CRS from c1.Center.

func NewPoint

func NewPoint(x, y float64, crs CRS) Point

NewPoint returns a 2D Point with the given coordinates and CRS. Passing the zero CRS leaves the CRS unset (interpreted as WGS84 by most operations).

func NewPointZ

func NewPointZ(x, y, z float64, crs CRS) Point

NewPointZ returns a 3D Point (X, Y, Z) with the given CRS.

func SampleGeodesic added in v0.3.2

func SampleGeodesic(a, b Point, n int) ([]Point, error)

SampleGeodesic returns n points along the great-circle arc from a to b (inclusive of both endpoints). n must be >= 2. Both points must be in a geographic CRS with X = longitude and Y = latitude in degrees. Uses standard spherical linear interpolation (slerp) on unit sphere vectors — no ellipsoidal-Earth corrections.

Output points carry the input's CRS (or WGS84 if the CRS is unset) and lon in [-180, 180].

func (Point) AppendWKB

func (p Point) AppendWKB(buf []byte) []byte

func (Point) Bounds

func (p Point) Bounds() Bounds

Bounds returns the XY bounding box. Z, if present, is ignored (the Bounds type is deliberately 2D).

func (Point) Buffer

func (p Point) Buffer(distance float64, segments int) Polygon

Buffer returns a circular polygon of radius distance centered on p, approximated with the given number of edge segments. segments < 4 falls back to DefaultBufferSegments.

func (Point) CRS

func (p Point) CRS() CRS

func (Point) Centroid

func (p Point) Centroid() Point

Centroid returns p itself — a Point is its own centroid. Present so Point satisfies the Geometry interface's Centroid method and the top-level geometry.Centroid dispatch collapses to interface dispatch.

func (Point) Distance

func (p Point) Distance(o Point, u Unit) (float64, error)

Distance returns the planar (XY) distance from p to o in the requested unit. Z is ignored. For geographic CRSes Haversine is used; for projected CRSes Euclidean.

func (Point) Distance3D

func (p Point) Distance3D(o Point, u Unit) (float64, error)

Distance3D returns the 3D Euclidean distance from p to o, treating Z as coplanar with X/Y (i.e. as if measured in the same unit as the CRS's linear unit). Requires both points to be in the same projected CRS and to have HasZ set on both sides; otherwise returns ErrCRSMismatch or an error noting the missing dimension.

func (Point) Equal

func (p Point) Equal(o Point) bool

Equal reports whether p and o are equal in coordinates, CRS, and dimensionality. Z is compared only if both points are 3D.

func (Point) EstimateUTMCRS

func (p Point) EstimateUTMCRS() (CRS, error)

EstimateUTMCRS returns the CRS of the UTM zone covering p (on the WGS84 datum). If p is in a projected CRS, it is first inverse-projected to WGS84 to pick the zone.

func (Point) Is3D

func (p Point) Is3D() bool

func (Point) ToCRS

func (p Point) ToCRS(target CRS) (Point, error)

ToCRS reprojects p into target. Z is carried through unchanged.

func (Point) Type

func (p Point) Type() Type

func (Point) WKT

func (p Point) WKT() string

type PointsView added in v0.4.1

type PointsView struct {
	// Xs and Ys are parallel arrays of the same length holding the
	// point coordinates. Never nil for a materialized view — an
	// empty geometry produces an empty (zero-length, non-nil) slice.
	Xs, Ys []float64
	// Zs holds Z coordinates when HasZ is true. Nil otherwise —
	// callers that don't check HasZ but iterate Zs get an obvious
	// panic rather than reading zeros as valid altitudes.
	Zs []float64
	// HasZ mirrors the source geometry's Is3D() at view time.
	HasZ bool
	// CRS is the coordinate reference system for the whole view.
	// In the AoS `[]Point` representation this is duplicated per
	// point; SoA carries it once at the collection level.
	CRS CRS
}

PointsView is a struct-of-arrays view over a sequence of 2D or 3D points sharing a single CRS. Callers who need SoA-friendly access (bbox compute, Hilbert index, SIMD-eligible loops) call `.View()` on the source geometry to materialize a fresh view, then work on the parallel `Xs` / `Ys` slices directly.

Amortization

Materialization is O(n) per `.View()` call — a fresh allocation of parallel float64 slabs plus a struct-to-slabs copy loop. Do NOT call `.View()` for a single operation; the fresh conversion costs more than the AoS-form equivalent. Benchmarks on LineString.Bounds (n=1M): AoS = 1.55 ms, SoA on held view = 1.10 ms (−29%), SoA with fresh view = 3.25 ms (+109%). Rule of thumb: break-even is roughly two SoA operations per view.

The default `.Bounds()` / `.Contains()` / etc. methods on LineString / Polygon / MultiPolygon deliberately keep the AoS path so single-op callers don't regress. Downstream slices (WKB parse-into-view, Hilbert index, spatial predicates) offer entry points that skip the AoS intermediate entirely on the read-heavy paths where amortization actually holds.

func LineStringViewFromWKB added in v0.4.1

func LineStringViewFromWKB(data []byte) (PointsView, error)

LineStringViewFromWKB parses a LineString or LineStringZ WKB directly into a PointsView, skipping the intermediate []Point slice that ParseWKB(data).View() would allocate. Type codes other than LineString / LineStringZ return ErrTypeMismatch.

This is the WKB-facing sibling of LineString.View() for callers materializing SoA views from bytes (Hilbert index over stored WKB, spatial-predicate refine loops, streamed-parquet consumers). Two allocations: the Xs and Ys slabs sized to the vertex count. One extra Zs slab when the WKB carries Z.

CRS is left unset on the returned view — the WKB blob doesn't carry CRS. Callers embedding CRS via schema/annotation must attach it themselves.

func PolygonRingViewsFromWKB added in v0.4.1

func PolygonRingViewsFromWKB(data []byte) ([]PointsView, error)

PolygonRingViewsFromWKB parses a Polygon or PolygonZ WKB directly into a []PointsView with one view per ring (exterior at index 0, then holes). Skips the ParseWKB intermediate [][]Point allocation.

This is the primary Slice-10 entry point for PreparedGeometry callers — collapses ParseWKB + Polygon.RingViews() into a single byte-stream pass. Wired into PrepareFromWKB.

func (PointsView) Bounds added in v0.4.1

func (v PointsView) Bounds() Bounds

Bounds returns the 2D axis-aligned bounding box of the view. Delegates to BoundsFromXY on Xs and Ys — Z, if present, is not part of the 2D bounds (matches the Bounds type's XY-only shape).

func (PointsView) ConvexHull added in v0.4.1

func (v PointsView) ConvexHull() PointsView

ConvexHull materializes the convex hull of v as a fresh PointsView with the same CRS/HasZ. Z coordinates (when v.HasZ) are copied for retained indices — the hull decision uses XY only, matching the Polygon.ConvexHull shape.

This is the amortized-view entry point for callers holding a materialized PointsView (via LineString.View(), Polygon.RingViews(), etc.) — no AoS []Point round-trip.

func (PointsView) Len added in v0.4.1

func (v PointsView) Len() int

Len returns the number of points in the view.

func (PointsView) SimplifyDP added in v0.4.1

func (v PointsView) SimplifyDP(tolerance float64) PointsView

SimplifyDP applies Douglas-Peucker to the coordinates held by v, returning a new PointsView with the same CRS and HasZ. Z coordinates (when v.HasZ) are copied for retained indices — the split decisions use XY only, matching the AoS shape.

This is the amortized-view entry point: callers holding a materialized PointsView (via LineString.View() or Polygon.RingViews()) can simplify without going through the AoS []Point round-trip.

type Polygon

type Polygon struct {
	Rings    [][]Point
	CRSValue CRS
	HasZ     bool
}

Polygon is a planar surface defined by one exterior linear ring and zero or more interior rings (holes). Rings are lists of points; the first and last point of each ring must coincide (Polygon operations close rings implicitly if they don't).

func ConvexHull added in v0.3.0

func ConvexHull(g Geometry) Polygon

ConvexHull returns the convex hull of every vertex in g as a Polygon. Handles all concrete geometry types by extracting vertices and running a Graham scan over the union. Returns an empty Polygon if g has fewer than 3 unique vertices.

func Envelope added in v0.3.0

func Envelope(g Geometry) Polygon

Envelope returns the axis-aligned bounding-box polygon of g. Matches geopandas's GeoSeries.envelope: 5-vertex closed ring (MinXY, MaxX/MinY, MaxXY, MinX/MaxY, close). Empty input returns a zero-ring Polygon.

func LensPolygon added in v0.3.3

func LensPolygon(c1, c2 Circle, arcSegments int) Polygon

LensPolygon returns the intersection region of c1 and c2 as a Polygon, sampled analytically (no sweep-line involved). arcSegments is the number of samples PER arc — the returned ring has ~2· arcSegments vertices. Values < 4 fall back to DefaultBufferSegments / 2.

Special cases:

  • Disjoint circles → empty Polygon (Rings == nil).
  • One circle properly contains the other → the smaller circle's Boundary (the lens IS the smaller circle).
  • Concentric circles with equal radius → the shared circle's Boundary.
  • Tangent circles → empty Polygon (a lens with zero area).

Output is wound CCW (as gobi's convention prefers for exterior rings) and inherits CRS from c1.Center.

func NewPolygon

func NewPolygon(rings [][]Point, crs CRS) Polygon

NewPolygon returns a 2D Polygon with the given rings. The first ring is the exterior boundary; any subsequent rings are holes.

func NewPolygonZ

func NewPolygonZ(rings [][]Point, crs CRS) Polygon

NewPolygonZ returns a 3D Polygon.

func SimplePolygon

func SimplePolygon(exterior []Point, crs CRS) Polygon

SimplePolygon returns a Polygon with a single exterior ring.

func (Polygon) AppendWKB

func (p Polygon) AppendWKB(buf []byte) []byte

func (Polygon) Area

func (p Polygon) Area(u Unit) (float64, error)

Area returns the polygon area. For geographic CRSes the area is computed on a sphere of Earth radius and returned in u² (u ∈ km/mi/nmi/m/ft). For projected CRSes the area is planar (signed absolute value) in the CRS's linear unit², converted to u².

func (Polygon) Bounds

func (p Polygon) Bounds() Bounds

func (Polygon) Buffer

func (p Polygon) Buffer(distance float64, segments int) Polygon

Buffer returns a polygon approximating the set of points within distance of p's exterior ring. Holes are shrunk (their offset ring moves inward); if a hole's offset would collapse or self-intersect it is dropped.

This is a positive (outward) buffer only. For convex or nearly-convex inputs the output is topologically correct. For strongly concave inputs the offset exterior ring may self-intersect — cleaning that up requires a polygon-union pass, which this package doesn't yet provide.

func (Polygon) CRS

func (p Polygon) CRS() CRS

func (Polygon) Centroid

func (p Polygon) Centroid() Point

Centroid returns the area-weighted centroid of the exterior ring, using the planar (shoelace) formula. For small geographic polygons the result is a close approximation.

func (Polygon) Contains

func (p Polygon) Contains(pt Point) bool

Contains reports whether pt lies inside p (exterior ring, minus any holes). The check uses the winding-parity (crossing) rule. Points on the boundary have undefined containment.

func (Polygon) ConvexHull

func (p Polygon) ConvexHull() Polygon

ConvexHull returns the convex hull of the polygon's exterior points. Collinear points on hull edges are dropped.

Slice-12 SoA rewrite: extracts the exterior into parallel Xs / Ys slabs and runs Andrew's monotone chain (ConvexHullFromXY), replacing the AoS Graham scan's polar-angle sort on []Point. Sorting indices (8-byte swaps) instead of Point structs (40-byte swaps) with coord reads from cache-friendly float64 slabs cuts wall time meaningfully on n≥1K exteriors.

The result's starting vertex may differ from the pre-Slice-12 Graham output (Andrew starts at leftmost-then-lowest-Y; Graham started at lowest-Y-then-lowest-X) — the vertex SET and CCW ordering are the same.

func (Polygon) EstimateUTMCRS

func (p Polygon) EstimateUTMCRS() (CRS, error)

EstimateUTMCRS returns the CRS of the UTM zone covering the polygon's centroid. If the polygon is in a projected CRS, the centroid is inverse-projected to WGS84 first.

func (Polygon) Exterior

func (p Polygon) Exterior() []Point

Exterior returns the exterior ring, or nil if the polygon has no rings.

func (Polygon) Is3D

func (p Polygon) Is3D() bool

func (Polygon) IsConvex added in v0.3.0

func (p Polygon) IsConvex() bool

IsConvex reports whether p is a single-ring polygon whose vertices wind consistently (all left-turns or all right-turns). Polygons with holes always return false (a hole makes the polygon non-convex). Zero-area rings return false.

func (Polygon) Perimeter

func (p Polygon) Perimeter(u Unit) (float64, error)

Perimeter returns the length of the polygon's exterior ring in u.

func (Polygon) RingViews added in v0.4.1

func (p Polygon) RingViews() []PointsView

RingViews materializes one PointsView per ring in a Polygon. Rings[0] is the exterior; subsequent rings are holes. Each view carries the polygon's CRS + HasZ. Empty Polygon returns nil.

func (Polygon) Simplify

func (p Polygon) Simplify(tolerance float64) Polygon

Simplify applies Douglas-Peucker to each ring of the polygon. Rings that collapse to fewer than 4 points (three unique vertices plus the closing vertex) are kept as-is to preserve topological validity of the polygon.

func (Polygon) ToCRS

func (p Polygon) ToCRS(target CRS) (Polygon, error)

ToCRS reprojects the polygon into target.

func (Polygon) Type

func (p Polygon) Type() Type

func (Polygon) WKT

func (p Polygon) WKT() string

type Predicate

type Predicate uint8

Predicate names a binary spatial predicate.

const (
	// PredIntersects: a and b share any point.
	PredIntersects Predicate = iota
	// PredContains: a fully contains b (every point of b lies in a).
	PredContains
	// PredWithin: a lies fully within b (equivalent to Contains(b, a)).
	PredWithin
	// PredTouches: a and b share boundary points but no interior
	// points. Matches DE-9IM T*F**F*** / F*T**F*** / F**T*F***.
	PredTouches
	// PredCrosses: a and b share some but not all interior points and
	// the dimension of the intersection is less than max(dim(a), dim(b)).
	// Typical shape: LineString × Polygon or LineString × LineString
	// with mixed dimensions.
	PredCrosses
	// PredOverlaps: a and b share some interior points, neither
	// contains the other, and both are of the same dimension.
	PredOverlaps
	// PredDisjoint: a and b share no point. Exactly the negation of
	// PredIntersects; kept as its own value so downstream code
	// dispatches on n.pred alone rather than an "intersects + invert"
	// side-channel (which is easy to trip on when adding new logic).
	PredDisjoint
)

func (Predicate) String

func (p Predicate) String() string

type PreparedGeometry added in v0.4.1

type PreparedGeometry struct {
	// G is the source geometry. Retained so the fallback Test(G, other)
	// path stays available for shapes without a fast path.
	G Geometry

	// Bounds is g.Bounds() cached at Prepare time. Used for the
	// cheap bbox-reject step in TestPrepared without a repeat
	// Bounds() call per predicate evaluation.
	Bounds Bounds
	// contains filtered or unexported fields
}

PreparedGeometry wraps a Geometry with precomputed indexes + cached bounds for accelerated repeated predicate evaluation. The primary use case is spatial joins where each right-side polygon is tested against many left-side candidate points — caching the indexes once amortizes the per-call setup tax across every predicate call.

When to use

Prepare + TestPrepared wins on hot loops where the amortization ratio — number of predicate calls per prepared geometry — exceeds the break-even point measured on the target shape. The amortized-view microbench in pip_bench_test.go shows a 3× win on a 64-vertex polygon with 100 candidate points held-view; the break-even is around 5-10 candidates per polygon for that shape.

Cost model

  • Polygon: Prepare materializes ring PointsViews upfront (2 allocs per ring). Query cost is one PIP walk per call.
  • MultiPolygon: Prepare caches one Bounds per sub-polygon (single small slab) and — when N ≥ 16 — builds an R-tree over those bboxes. Per-sub-polygon ring views are populated LAZILY on first hit (atomic publish; concurrent readers race benignly). Query cost is O(log N + k) via the tree, or O(N) bbox-compares + k PIPs via the linear scan.

The lazy MultiPolygon path is what makes Prepare(landMP) with hundreds of small islands cheap: only bbox slabs are allocated upfront (24 bytes × N), and only sub-polygons that a query actually touches ever pay their ring-view cost. The pre-review implementation materialized every ring of every sub-poly at Prepare time, which was a strict regression on many-small-poly shapes because the upfront work dwarfed the per-query savings.

Fast paths

The SoA fast paths kick in for the following pair shapes, covering the dominant spatial-join workloads:

  • Point × Polygon (both orderings) — PredContains, PredIntersects, PredWithin. Uses PIPPolygonFromRings on the polygon's cached ring views. Boundary points fall through to the AoS path via pointOnPolygonBoundary so results match Test() exactly.
  • Point × MultiPolygon (both orderings) — same predicates. Tree-indexed (N ≥ 16) or linear-with-bbox-reject (N < 16), then per-candidate PIP with lazy ring materialization.

Every other shape (Point×Point, LineString×Polygon, Polygon×Polygon, etc.) transparently falls through to Test(a.G, b.G) so callers can pass a PreparedGeometry into TestPrepared without worrying about which shapes are optimized. Adding a new fast path only requires a case in TestPrepared and matching correctness tests — no changes to callers.

gobi's built-in SJoin

SJoin does NOT use PreparedGeometry today: its R-tree pre-filter on the RIGHT frame drives the candidate ratio to ~1 point per right-polygon on non-overlapping workloads, well below the amortization break-even. Callers with denser workloads (overlapping polygons, spatial-index-free refine loops, per-polygon many-candidate tests) SHOULD use PreparedGeometry directly.

func Prepare added in v0.4.1

func Prepare(g Geometry) PreparedGeometry

Prepare returns a PreparedGeometry for g.

Polygon: materializes ring views upfront. MultiPolygon: caches per-sub-polygon bounds; builds an R-tree index when N ≥ 16; leaves per-sub-polygon ring views nil for lazy on-demand materialization. Every other geometry type: bounds only. The fast-path table doesn't have entries for non-polygon geometries yet, so materializing views up front would be pure overhead.

func PrepareFromWKB added in v0.4.1

func PrepareFromWKB(data []byte) (PreparedGeometry, error)

PrepareFromWKB builds a PreparedGeometry from a WKB blob using the byte-stream direct-parse (Slice 10). Single WKB walk: the slabs are populated first, then the AoS Polygon / MultiPolygon needed for TestPrepared's non-fast-path fall-through is materialized from the slabs (a cheap float64→Point copy, not a second WKB parse). Non-polygon geometry types fall through to `Prepare(ParseWKB(data))` — they don't have cached slabs today, so there's no direct-parse win to capture.

Bounds are derived from the slabs (BoundsFromXY per ring) rather than a third byte-stream pass. Matches g.Bounds() exactly.

type RTree

type RTree struct {
	// contains filtered or unexported fields
}

RTree is a static, bulk-loaded Sort-Tile-Recursive R-tree over 2D bounding boxes. Once built with NewRTree the tree is immutable and safe for concurrent readers.

Internal layout: struct-of-arrays. Node bboxes live in four parallel []float64s (nodeMinX/Y/MaxX/MaxY); item bboxes get the same treatment. The query hot paths (Search, NearestOne) read tightly-packed contiguous slices instead of walking through a []struct where each 48-byte node dragged in 16 unused padding bytes per cache line.

func NewRTree

func NewRTree(bounds []Bounds) *RTree

NewRTree builds an R-tree over the given bounding boxes. Item IDs returned by queries are indexes into bounds.

func (*RTree) Bounds

func (t *RTree) Bounds() Bounds

Bounds returns the R-tree's overall bounding box.

func (*RTree) Len

func (t *RTree) Len() int

Len returns the number of items indexed.

func (*RTree) Nearest

func (t *RTree) Nearest(x, y float64, k int) []int32

Nearest returns the k item IDs whose bounding boxes are closest (by squared Euclidean point-to-bbox distance) to (x, y), in ascending distance order. Fewer than k IDs are returned if the tree is smaller.

For the k=1 case, prefer NearestOne — same semantics but skips the priority queue for a zero-allocation depth-first descent.

func (*RTree) NearestOne added in v0.2.17

func (t *RTree) NearestOne(x, y float64) (id int32, ok bool)

NearestOne returns the ID of the item whose bounding box is closest to (x, y) by squared Euclidean point-to-bbox distance. ok=false when the tree is empty. Semantically equivalent to Nearest(x, y, 1)[0] but with zero allocations — depth-first descent with a running best-so-far distance + bbox pruning replaces the general k>1 path's priority queue.

Callers doing a single-nearest lookup at high frequency (e.g. snap-to-graph, per-point classification) should prefer this over Nearest(x, y, 1). At 1M+ calls per request the alloc + boxing savings dominate the CPU profile.

func (*RTree) Search

func (t *RTree) Search(q Bounds) []int32

Search returns the IDs of every item whose bounding box intersects q. Allocates a fresh result slice per call.

func (*RTree) SearchInto

func (t *RTree) SearchInto(buf []int32, q Bounds) []int32

SearchInto appends every item ID whose bounding box intersects q to buf (after truncating buf to zero length) and returns the resulting slice. This lets callers reuse a scratch buffer across queries to avoid a fresh allocation each time.

type Type

type Type uint8

Type identifies a geometry kind.

const (
	TypeUnknown Type = iota
	TypePoint
	TypeLineString
	TypePolygon
	TypeMultiPoint
	TypeMultiLineString
	TypeMultiPolygon
	TypeGeometryCollection
)

func (Type) String

func (t Type) String() string

type Unit

type Unit string

Unit represents a linear distance unit.

const (
	UnitMeters        Unit = "m"
	UnitKilometers    Unit = "km"
	UnitMiles         Unit = "mi"
	UnitFeet          Unit = "ft"
	UnitNauticalMiles Unit = "nmi"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL