TGGeometry.jl is a Julia wrapper around the tg
C library for planar geometric predicates. Specifically, it provides:
intersects(geom1, geom2)
contains(geom1, geom2)
touches(geom1, geom2)
disjoint(geom1, geom2)
equals(geom1, geom2)
covers(geom1, geom2)
coveredby(geom1, geom2)
within(geom1, geom2)
from the DE-9IM model.
It is fully GeoInterface.jl compatible, and is able to accept any combination of GeoInterface-compatible geometries as input (from GeoDataFrames, GeoJSON, ArchGDAL, GeometryOps, and many more packages!).
It also provides a GeoInterface.jl-compatible type, TGGeom
, which can be used to wrap the C-level tg_geom
objects returned by the tg
library - in case you want complete speed. You can convert any GeoInterface-compatible geometry to a TGGeom
using GeoInterface.convert(TGGeom, geom)
, or GeoInterface.convert(TGGeometry, geom)
. Similarly, you can convert a TGGeom
back to any GeoInterface-compatible geometry using the same function
Install via ]add TGGeometry
in the REPL, or Pkg.add("TGGeometry")
.
Since TGGeometry allows any GeoInterface-compatible geometry as input, let's start with some geometries from NaturalEarth.jl:
using NaturalEarth
all_countries = naturalearth("admin_0_countries", 10)
germany = all_countries.geometry[findfirst(==("Germany"), all_countries.NAME)]
belgium = all_countries.geometry[findfirst(==("Belgium"), all_countries.NAME)]
using TGGeometry
TGGeometry.intersects(germany, belgium) # true
TGGeometry.contains(germany, belgium) # false
TGGeometry.touches(germany, belgium) # true
TGGeometry.disjoint(germany, belgium) # false
You can use any data loader, like GeoJSON, GeoDataFrames, ArchGDAL, etc. You can also construct your own geometries via GeometryBasics, GeoInterface wrapper geometries, or accepted basic types like 2-tuple points.
berlin = (13.4050, 52.5200) # berlin (latitude, longitude)
TGGeometry.contains(germany, berlin) # true
TGGeometry is fast naturally, but you can make it even faster by "preparing" your geometries by converting them to TGGeom
s. This converts the geometries to opaque pointers to a tg
geometry - still fully GeoInterface-compatible though, but they have the acceleration benefits and don't have to be continually converted every time you call a predicate.
The way to convert is to call GeoInterface.convert(TGGeometry, geom)
. This will convert the geometry to a TGGeom
and return the new TGGeom
object.
Let's see the difference in speed, between using a TGGeom
and a GeoJSON polygon:
using Chairmarks, GeoInterface
gj_bench = @be TGGeometry.contains($germany, $berlin)
Benchmark: 2014 samples with 1 evaluation
min 21.375 μs (72 allocs: 49.891 KiB)
median 32.791 μs (72 allocs: 49.891 KiB)
mean 44.772 μs (72 allocs: 49.891 KiB, 0.64% gc time)
max 2.290 ms (72 allocs: 49.891 KiB, 96.10% gc time)
# Convert to TGGeom
germany_tg = GeoInterface.convert(TGGeometry, germany)
tg_bench = @be TGGeometry.contains($germany_tg, $berlin)
Benchmark: 2355 samples with 305 evaluations
min 78.279 ns (1 allocs: 16 bytes)
median 94.262 ns (1 allocs: 16 bytes)
mean 131.635 ns (1 allocs: 16 bytes, 0.04% gc time)
max 85.267 μs (1 allocs: 16 bytes, 99.75% gc time)
The prepared approach is about 340x faster!