
Lightweight geofencing and fast point-in-polygon tests with a spatial grid index that prunes candidates, enabling efficient "which zones contain this point" queries across many polygons.
Lightweight geofencing and point-in-polygon for the JVM and the browser, written in Kotlin.
Answer "which of these zones contains this point?" fast, over many polygons — without pulling in a whole GIS stack. The "I just need geofencing, not GeoTools" case.
Status: early / work in progress. Geometry types, ray-casting point-in-polygon, a
Geofencespatial index (containing(point)), supporting geometry (area, centroid, haversine), and a JMH benchmark are in, with tests. A live browser demo is next. Not yet published to Maven Central.
Lots of services need one geospatial answer — is this GPS point inside a charging zone, a delivery area, a catchment? — and reach for a full GIS library (JTS, GeoTools) to get it. geo4k is the small alternative: a hand-written point-in-polygon test plus a spatial index so "which of many zones" stays fast, Kotlin-idiomatic, dependency-free, and compiled to both the JVM and the browser.
val zone = Polygon(listOf(Point(0.0, 0.0), Point(4.0, 0.0), Point(4.0, 4.0), Point(0.0, 4.0)))
val inside: Boolean = Point(2.0, 2.0) in zone // trueIndex many zones and ask which contain a point — the spatial grid prunes to a handful of candidates before the exact test, so it stays fast as the zone count grows:
val fence = Geofence.of(
Zone("congestion", Polygon(/* … */)),
Zone("ulez", Polygon(/* … */)),
)
val here: List<Zone> = fence.containing(Point(-0.12, 51.51)) // [congestion, ulez]geo4k works in the plane (Cartesian): map longitude to x, latitude to y. Edges are straight
lines, not geodesics, and there is no projection / CRS handling. That is accurate enough for city-scale
geofencing — the target case — and is exactly what keeps the library small and dependency-free. If you
need geodesic edges, datums or reprojection you need a real GIS stack, and that is the line geo4k does
not cross.
./gradlew :benchmark:jmh runs the JMH benchmark: the Geofence
index versus a naive scan of every zone, for the same "which zones contain this point?" query, as the
zone count grows.
| zones | indexed | naive scan | speed-up |
|---|---|---|---|
| 100 | ~8 ns | ~250 ns | ~30× |
| 1,000 | ~97 ns | ~4,550 ns | ~47× |
| 10,000 | ~530 ns | ~118,000 ns | ~220× |
The naive scan grows linearly with the zone count; the indexed query stays far flatter (grid pruning), so the gap widens as zones grow. (JMH average time, 2 forks × 5 iterations; machine-dependent — run it yourself.)
./gradlew buildRequires a JDK; the Gradle wrapper fetches the rest.
MIT.
Lightweight geofencing and point-in-polygon for the JVM and the browser, written in Kotlin.
Answer "which of these zones contains this point?" fast, over many polygons — without pulling in a whole GIS stack. The "I just need geofencing, not GeoTools" case.
Status: early / work in progress. Geometry types, ray-casting point-in-polygon, a
Geofencespatial index (containing(point)), supporting geometry (area, centroid, haversine), and a JMH benchmark are in, with tests. A live browser demo is next. Not yet published to Maven Central.
Lots of services need one geospatial answer — is this GPS point inside a charging zone, a delivery area, a catchment? — and reach for a full GIS library (JTS, GeoTools) to get it. geo4k is the small alternative: a hand-written point-in-polygon test plus a spatial index so "which of many zones" stays fast, Kotlin-idiomatic, dependency-free, and compiled to both the JVM and the browser.
val zone = Polygon(listOf(Point(0.0, 0.0), Point(4.0, 0.0), Point(4.0, 4.0), Point(0.0, 4.0)))
val inside: Boolean = Point(2.0, 2.0) in zone // trueIndex many zones and ask which contain a point — the spatial grid prunes to a handful of candidates before the exact test, so it stays fast as the zone count grows:
val fence = Geofence.of(
Zone("congestion", Polygon(/* … */)),
Zone("ulez", Polygon(/* … */)),
)
val here: List<Zone> = fence.containing(Point(-0.12, 51.51)) // [congestion, ulez]geo4k works in the plane (Cartesian): map longitude to x, latitude to y. Edges are straight
lines, not geodesics, and there is no projection / CRS handling. That is accurate enough for city-scale
geofencing — the target case — and is exactly what keeps the library small and dependency-free. If you
need geodesic edges, datums or reprojection you need a real GIS stack, and that is the line geo4k does
not cross.
./gradlew :benchmark:jmh runs the JMH benchmark: the Geofence
index versus a naive scan of every zone, for the same "which zones contain this point?" query, as the
zone count grows.
| zones | indexed | naive scan | speed-up |
|---|---|---|---|
| 100 | ~8 ns | ~250 ns | ~30× |
| 1,000 | ~97 ns | ~4,550 ns | ~47× |
| 10,000 | ~530 ns | ~118,000 ns | ~220× |
The naive scan grows linearly with the zone count; the indexed query stays far flatter (grid pruning), so the gap widens as zones grow. (JMH average time, 2 forks × 5 iterations; machine-dependent — run it yourself.)
./gradlew buildRequires a JDK; the Gradle wrapper fetches the rest.
MIT.