Skip to content

Commit 46c6b02

Browse files
authored
Add a TGGeometry extension for fast inexact geometric predicates (#271)
- Add a TGGeometry extension - Centralize the definition of extension algorithms GEOS, PROJ, and TG (GDAL can be later) TGGeometry.jl is a Julia wrapper around the [tg](https://github.com/tidwall/tg) C library for planar geometric predicates. It doesn't use exact computation, but the indexing ideas there are extremely efficient and I thought it would be nice to at least have it accessible. It's ridiculously fast - 100 ns for point in polygon if you convert the geometry to a TGGeometry first. Accessible by `predicate(GO.TG(), geom1, geom2)` for all GI/Simple Features planar geometry. This also refactors GEOS to work under the algorithm interface as well as laying the foundation for a future hypothetical PROJ algorithm, that we could use for reproject or segmentize. > [!NOTE] > I'm deliberately not testing TG along with GO in the operations since I don't want to deal with our tests, that require exact predicates, returning the wrong values from the inexact `tg`. However, I should and will add some tests elsewhere. This PR will also be a good proving ground for the new extension interface should there be one.
1 parent b3f2cdc commit 46c6b02

File tree

14 files changed

+797
-59
lines changed

14 files changed

+797
-59
lines changed

GeometryOpsCore/src/types/algorithm.jl

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,80 @@ MyIndependentAlgorithm(m::Manifold; kw1 = 1, kw2 = "hello") = MyIndependentAlgor
4242

4343
export Algorithm, AutoAlgorithm, ManifoldIndependentAlgorithm, SingleManifoldAlgorithm, NoAlgorithm
4444

45+
"""
46+
abstract type Algorithm{M <: Manifold}
47+
48+
The abstract supertype for all GeometryOps algorithms.
49+
These define how to perform a particular [`Operation`](@ref).
50+
51+
An algorithm may be associated with one or many [`Manifold`](@ref)s.
52+
It may either have the manifold as a field, or have it as a static parameter
53+
(e.g. `struct GEOS <: Algorithm{Planar}`).
54+
55+
## Interface
56+
57+
All `Algorithm`s must implement the following methods:
58+
59+
- `rebuild(alg, manifold::Manifold)` Rebuild algorithm `alg` with a new manifold
60+
as passed in the second argument. This may error and throw a [`WrongManifoldException`](@ref)
61+
if the manifold is not compatible with that algorithm.
62+
- `manifold(alg::Algorithm)` Return the manifold associated with the algorithm.
63+
- `best_manifold(alg::Algorithm, input)`: Return the best manifold for that algorithm, in the absence of
64+
any other context. WARNING: this may change in future and is not stable!
65+
66+
The actual implementation is left to the implementation of that particular [`Operation`](@ref).
67+
68+
## Notable subtypes
69+
70+
- [`AutoAlgorithm`](@ref): Tells the [`Operation`](@ref) receiving
71+
it to automatically select the best algorithm for its input data.
72+
- [`ManifoldIndependentAlgorithm`](@ref): An abstract supertype for an algorithm that works on any manifold.
73+
The manifold must be stored in the algorithm for a `ManifoldIndependentAlgorithm`, and accessed via `manifold(alg)`.
74+
- [`SingleManifoldAlgorithm`](@ref): An abstract supertype for an algorithm that only works on a
75+
single manifold, specified in its type parameter. `SingleManifoldAlgorithm{Planar}` is a special case
76+
that does not have to store its manifold, since that doesn't contain any information. All other
77+
`SingleManifoldAlgorithm`s must store their manifold, since they do contain information.
78+
- [`NoAlgorithm`](@ref): A type that indicates no algorithm is to be used, essentially the equivalent
79+
of `nothing`.
80+
"""
4581
abstract type Algorithm{M <: Manifold} end
4682

83+
"""
84+
manifold(alg::Algorithm)::Manifold
85+
86+
Return the manifold associated with the algorithm.
87+
88+
May be any subtype of [`Manifold`](@ref).
89+
"""
90+
function manifold end
91+
92+
# The below definition is a special case, since [`Planar`](@ref) has no contents, being a
93+
# singleton struct.
94+
# If that changes in the future, then this method must be deleted.
95+
manifold(::Algorithm{<: Planar}) = Planar()
96+
97+
"""
98+
best_manifold(alg::Algorithm, input)::Manifold
99+
100+
Return the best [`Manifold`](@ref) for the algorithm `alg` based on the given `input`.
101+
102+
May be any subtype of [`Manifold`](@ref).
103+
"""
104+
function best_manifold end
105+
106+
# ## Implementation of basic algorithm types
107+
108+
# ### `AutoAlgorithm`
109+
110+
"""
111+
AutoAlgorithm{T, M <: Manifold}(manifold::M, x::T)
112+
113+
Indicates that the [`Operation`](@ref) should automatically select the best algorithm for
114+
its input data, based on the passed in manifold (may be an [`AutoManifold`](@ref)) and data
115+
`x`.
116+
117+
The actual implementation is left to the implementation of that particular [`Operation`](@ref).
118+
"""
47119
struct AutoAlgorithm{T, M <: Manifold} <: Algorithm{M}
48120
manifold::M
49121
x::T
@@ -52,18 +124,33 @@ end
52124
AutoAlgorithm(m::Manifold; kwargs...) = AutoAlgorithm(m, kwargs)
53125
AutoAlgorithm(; kwargs...) = AutoAlgorithm(AutoManifold(), kwargs)
54126

127+
manifold(a::AutoAlgorithm) = a.manifold
128+
rebuild(a::AutoAlgorithm, m::Manifold) = AutoAlgorithm(m, a.x)
129+
130+
131+
# ### `ManifoldIndependentAlgorithm`
132+
133+
"""
134+
abstract type ManifoldIndependentAlgorithm{M <: Manifold} <: Algorithm{M}
135+
136+
The abstract supertype for a manifold-independent algorithm, i.e., one which may work on any manifold.
55137
138+
The manifold is stored in the algorithm for a `ManifoldIndependentAlgorithm`, and accessed via `manifold(alg)`.
139+
"""
56140
abstract type ManifoldIndependentAlgorithm{M <: Manifold} <: Algorithm{M} end
57141

58-
abstract type SingleManifoldAlgorithm{M <: Manifold} <: Algorithm{M} end
59142

60-
struct NoAlgorithm{M <: Manifold} <: Algorithm{M}
61-
m::M
62-
end
143+
# ### `SingleManifoldAlgorithm`
63144

64-
NoAlgorithm() = NoAlgorithm(Planar()) # TODO: add a NoManifold or AutoManifold type?
65-
# Maybe AutoManifold
66-
# and then we have DD.format like materialization
145+
"""
146+
abstract type SingleManifoldAlgorithm{M <: Manifold} <: Algorithm{M}
147+
148+
The abstract supertype for a single-manifold algorithm, i.e., one which is known to only work
149+
on a single manifold.
150+
151+
The manifold may be accessed via `manifold(alg)`.
152+
"""
153+
abstract type SingleManifoldAlgorithm{M <: Manifold} <: Algorithm{M} end
67154

68155
function (Alg::Type{<: SingleManifoldAlgorithm{M}})(m::M; kwargs...) where {M}
69156
# successful - the algorithm is designed for this manifold
@@ -76,3 +163,24 @@ function (Alg::Type{<: SingleManifoldAlgorithm{M}})(m::Manifold; kwargs...) wher
76163
# throw a WrongManifoldException and be done with it
77164
throw(WrongManifoldException{typeof(m), M, Alg}())
78165
end
166+
167+
168+
# ### `NoAlgorithm`
169+
170+
"""
171+
NoAlgorithm(manifold)
172+
173+
A type that indicates no algorithm is to be used, essentially the equivalent
174+
of `nothing`.
175+
176+
Stores a manifold within itself.
177+
"""
178+
struct NoAlgorithm{M <: Manifold} <: Algorithm{M}
179+
m::M
180+
end
181+
182+
NoAlgorithm() = NoAlgorithm(Planar()) # TODO: add a NoManifold or AutoManifold type?
183+
184+
manifold(a::NoAlgorithm) = a.m
185+
# Maybe AutoManifold
186+
# and then we have DD.format like materialization

Project.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
88
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
99
DelaunayTriangulation = "927a84f5-c5f4-47a5-9785-b46e178433df"
1010
ExactPredicates = "429591f6-91af-11e9-00e2-59fbe8cec110"
11+
Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
1112
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"
1213
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
1314
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
@@ -21,17 +22,20 @@ Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
2122
FlexiJoins = "e37f2e79-19fa-4eb7-8510-b63b51fe0a37"
2223
LibGEOS = "a90b1aa1-3769-5649-ba7e-abc5a9d163eb"
2324
Proj = "c94c279d-25a6-4763-9509-64d165bea63e"
25+
TGGeometry = "d7e755d2-3c95-4bcf-9b3c-79ab1a78647b"
2426

2527
[extensions]
2628
GeometryOpsFlexiJoinsExt = "FlexiJoins"
2729
GeometryOpsLibGEOSExt = "LibGEOS"
2830
GeometryOpsProjExt = "Proj"
31+
GeometryOpsTGGeometryExt = "TGGeometry"
2932

3033
[compat]
3134
CoordinateTransformations = "0.5, 0.6"
3235
DataAPI = "1"
3336
DelaunayTriangulation = "1.0.4"
3437
ExactPredicates = "2.2.8"
38+
Extents = "0.1.5"
3539
FlexiJoins = "0.1.30"
3640
GeoFormatTypes = "0.4"
3741
GeoInterface = "1.2"
@@ -42,8 +46,9 @@ LinearAlgebra = "1"
4246
Proj = "1"
4347
SortTileRecursiveTree = "0.1"
4448
Statistics = "1"
49+
TGGeometry = "0.1"
4550
Tables = "1"
46-
julia = "1.9"
51+
julia = "1.10"
4752

4853
[extras]
4954
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
@@ -53,7 +58,6 @@ DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0"
5358
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
5459
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
5560
FlexiJoins = "e37f2e79-19fa-4eb7-8510-b63b51fe0a37"
56-
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"
5761
GeoJSON = "61d90e0f-e114-555e-ac52-39dfb47a3ef9"
5862
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
5963
LibGEOS = "a90b1aa1-3769-5649-ba7e-abc5a9d163eb"
@@ -64,7 +68,8 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
6468
Rasters = "a3a2b9e3-a471-40c9-b274-f788e487c689"
6569
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
6670
Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4"
71+
TGGeometry = "d7e755d2-3c95-4bcf-9b3c-79ab1a78647b"
6772
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
6873

6974
[targets]
70-
test = ["ArchGDAL", "CoordinateTransformations", "DataFrames", "Distributions", "DimensionalData", "Downloads", "FlexiJoins", "GeoFormatTypes", "GeoJSON", "Proj", "JLD2", "LibGEOS", "Random", "Rasters", "NaturalEarth", "OffsetArrays", "SafeTestsets", "Shapefile", "Test"]
75+
test = ["ArchGDAL", "CoordinateTransformations", "DataFrames", "Distributions", "DimensionalData", "Downloads", "FlexiJoins", "GeoJSON", "Proj", "JLD2", "LibGEOS", "Random", "Rasters", "NaturalEarth", "OffsetArrays", "SafeTestsets", "Shapefile", "TGGeometry", "Test"]

benchmarks/benchmark_plots.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ function plot_trials(
7373
legend_orientation = :horizontal,
7474
legend_halign = 1.0,
7575
legend_valign = -0.25,
76+
legend_kws = (;),
7677
)
7778

7879
xs, ys, labels = [], [], []
@@ -118,7 +119,8 @@ function plot_trials(
118119
tellheight = legend_position isa Union{Tuple, Makie.Automatic} && (legend_position isa Makie.Automatic || length(legend_position) != 3) && legend_orientation == :horizontal,
119120
halign = legend_halign,
120121
valign = legend_valign,
121-
orientation = legend_orientation
122+
orientation = legend_orientation,
123+
legend_kws...,
122124
)
123125
ax.xtickcolor[] = ax.xgridcolor[]
124126
ax

0 commit comments

Comments
 (0)