Skip to content

Commit 4c92ea4

Browse files
authored
[Experimental] Extent forwarding for predicates (#301)
1 parent d539e65 commit 4c92ea4

File tree

11 files changed

+272
-80
lines changed

11 files changed

+272
-80
lines changed

ext/GeometryOpsLibGEOSExt/simple_overrides.jl

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,47 +23,27 @@ function GO.symdifference(::GEOS, geom_a, geom_b; target=nothing, calc_extent =
2323
end
2424

2525
# ## DE-9IM boolean methods
26-
# ### Equals
27-
function GO.equals(::GEOS, geom_a, geom_b)
28-
return LG.equals(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
26+
# These are all the same so we loop over all names and eval them in
27+
for fn in (:equals, :disjoint, :touches, :crosses, :within, :contains, :overlaps, :covers, :coveredby, :intersects)
28+
@eval begin
29+
# The basic method for geometries
30+
function GO.$fn(::GEOS, geom_a, geom_b)
31+
return LG.$fn(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
32+
end
33+
# Extents and geometries
34+
function GO.$fn(alg::GEOS, geom_a::GO.Extents.Extent, geom_b)
35+
return GO.$fn(alg, GO.extent_to_polygon(geom_a), geom_b)
36+
end
37+
function GO.$fn(alg::GEOS, geom_a, geom_b::GO.Extents.Extent)
38+
return GO.$fn(alg, geom_a, GO.extent_to_polygon(geom_b))
39+
end
40+
# Pure extents - this should probably be some GEOSRect or something,
41+
# but for now this works
42+
function GO.$fn(alg::GEOS, geom_a::GO.Extents.Extent, geom_b::GO.Extents.Extent)
43+
return GO.$fn(alg, GO.extent_to_polygon(geom_a), GO.extent_to_polygon(geom_b))
44+
end
45+
end
2946
end
30-
# ### Disjoint
31-
function GO.disjoint(::GEOS, geom_a, geom_b)
32-
return LG.disjoint(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
33-
end
34-
# ### Touches
35-
function GO.touches(::GEOS, geom_a, geom_b)
36-
return LG.touches(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
37-
end
38-
# ### Crosses
39-
function GO.crosses(::GEOS, geom_a, geom_b)
40-
return LG.crosses(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
41-
end
42-
# ### Within
43-
function GO.within(::GEOS, geom_a, geom_b)
44-
return LG.within(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
45-
end
46-
# ### Contains
47-
function GO.contains(::GEOS, geom_a, geom_b)
48-
return LG.contains(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
49-
end
50-
# ### Overlaps
51-
function GO.overlaps(::GEOS, geom_a, geom_b)
52-
return LG.overlaps(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
53-
end
54-
# ### Covers
55-
function GO.covers(::GEOS, geom_a, geom_b)
56-
return LG.covers(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
57-
end
58-
# ### CoveredBy
59-
function GO.coveredby(::GEOS, geom_a, geom_b)
60-
return LG.coveredby(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
61-
end
62-
# ### Intersects
63-
function GO.intersects(::GEOS, geom_a, geom_b)
64-
return LG.intersects(GI.convert(LG, geom_a), GI.convert(LG, geom_b))
65-
end
66-
6747
# ## Convex hull
6848
function GO.convex_hull(::GEOS, geoms)
6949
chull = LG.convexhull(

src/methods/geom_relations/coveredby.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,16 @@ function _coveredby(
271271
end
272272
return true
273273
end
274+
275+
# Extent forwarding
276+
function _coveredby(t1::GI.AbstractGeometryTrait, g1, t2, e::Extents.Extent)
277+
return _coveredby(t1, g1, GI.PolygonTrait(), extent_to_polygon(e))
278+
end
279+
function _coveredby(t1, e1::Extents.Extent, t2, g2)
280+
return _coveredby(GI.PolygonTrait(), extent_to_polygon(e1), t2, g2)
281+
end
282+
function _coveredby(t1, e1::Extents.Extent, t2, e2::Extents.Extent)
283+
return Extents.coveredby(e1, e2)
284+
end
285+
286+

src/methods/geom_relations/crosses.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ crosses(::LineStringTrait, g1, ::LineStringTrait, g2)::Bool = line_crosses_line(
2626
crosses(::PolygonTrait, g1, ::MultiPointTrait, g2)::Bool = multipoint_crosses_poly(g2, g1)
2727
crosses(::PolygonTrait, g1, ::LineStringTrait, g2)::Bool = line_crosses_poly(g2, g1)
2828

29+
30+
31+
function crosses(t1::GI.AbstractGeometryTrait, g1, t2, e::Extents.Extent)
32+
return crosses(t1, g1, GI.PolygonTrait(), extent_to_polygon(e))
33+
end
34+
function crosses(t1, e1::Extents.Extent, t2, g2)
35+
return crosses(GI.PolygonTrait(), extent_to_polygon(e1), t2, g2)
36+
end
37+
function crosses(t1, e1::Extents.Extent, t2, e2::Extents.Extent)
38+
return false # two extents can never cross!
39+
# TODO that's not quite true, if one extent is a line...
40+
end
41+
42+
43+
44+
45+
46+
47+
2948
function multipoint_crosses_line(geom1, geom2)
3049
int_point = false
3150
ext_point = false
@@ -128,3 +147,5 @@ function _point_on_segment(point, (start, stop); exclude_boundary::Symbol=:none)
128147
end
129148
return false
130149
end
150+
151+

src/methods/geom_relations/disjoint.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,17 @@ function _disjoint(
255255
end
256256
return true
257257
end
258+
259+
260+
# Extent forwarding
261+
function _disjoint(t1::GI.AbstractGeometryTrait, g1, t2, e::Extents.Extent)
262+
return _disjoint(t1, g1, GI.PolygonTrait(), extent_to_polygon(e))
263+
end
264+
function _disjoint(t1, e1::Extents.Extent, t2, g2)
265+
return _disjoint(GI.PolygonTrait(), extent_to_polygon(e1), t2, g2)
266+
end
267+
function _disjoint(t1, e1::Extents.Extent, t2, e2::Extents.Extent)
268+
return Extents.disjoint(e1, e2)
269+
end
270+
271+

src/methods/geom_relations/overlaps.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,18 @@ function _overlaps(
227227
return seg_val == line_over && (!a_fully_within && !b_fully_within)
228228
end
229229

230+
# Extent forwarding
231+
232+
function _overlaps(t1::GI.AbstractGeometryTrait, g1, t2, e::Extents.Extent)
233+
return _overlaps(t1, g1, GI.PolygonTrait(), extent_to_polygon(e))
234+
end
235+
function _overlaps(t1, e1::Extents.Extent, t2, g2)
236+
return _overlaps(GI.PolygonTrait(), extent_to_polygon(e1), t2, g2)
237+
end
238+
function _overlaps(t1, e1::Extents.Extent, t2, e2::Extents.Extent)
239+
return Extents.overlaps(e1, e2)
240+
end
241+
230242
#= TODO: Once overlaps is swapped over to use the geom relations workflow, can
231243
delete these helpers. =#
232244

src/methods/geom_relations/touches.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,19 @@ function _touches(
293293
end
294294
end
295295
return has_touched
296-
end
296+
end
297+
298+
# Extent forwarding
299+
300+
301+
function _touches(t1::GI.AbstractGeometryTrait, g1, t2, e::Extents.Extent)
302+
return _touches(t1, g1, GI.PolygonTrait(), extent_to_polygon(e))
303+
end
304+
function _touches(t1, e1::Extents.Extent, t2::GI.AbstractGeometryTrait, g2)
305+
return _touches(GI.PolygonTrait(), extent_to_polygon(e1), t2, g2)
306+
end
307+
function _touches(t1, e1::Extents.Extent, t2, e2::Extents.Extent)
308+
return Extents.touches(e1, e2)
309+
end
310+
311+

src/methods/geom_relations/within.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,19 @@ function _within(
278278
end
279279
return true
280280
end
281+
282+
283+
# Extent forwarding
284+
285+
286+
function _within(t1::GI.AbstractGeometryTrait, g1, t2, e::Extents.Extent)
287+
return _within(t1, g1, GI.PolygonTrait(), extent_to_polygon(e))
288+
end
289+
function _within(t1, e1::Extents.Extent, t2, g2)
290+
return _within(GI.PolygonTrait(), extent_to_polygon(e1), t2, g2)
291+
end
292+
function _within(t1, e1::Extents.Extent, t2, e2::Extents.Extent)
293+
return Extents.within(e1, e2)
294+
end
295+
296+

src/utils/utils.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,40 @@ function lazy_edge_extents(geom)
233233
end
234234
for edge in eachedge(geom, Float64))
235235
end
236+
237+
238+
# Extent to polygon
239+
240+
"""
241+
extent_to_polygon(ext::Extents.Extent)
242+
243+
Convert an extent to a polygon.
244+
245+
# Examples
246+
247+
```jldoctest
248+
import GeometryOps as GO, Extents
249+
250+
ext = Extents.Extent(X=(1.0, 2.0), Y=(1.0, 2.0))
251+
GO.extent_to_polygon(ext)
252+
# output
253+
GeoInterface.Wrappers.Polygon{false, false}([GeoInterface.Wrappers.LinearRing([(1.0, 1.0), … (3) … , (1.0, 1.0)])])
254+
```
255+
"""
256+
function extent_to_polygon(ext::Extents.Extent{(:X, :Y)})
257+
x1, x2 = ext.X
258+
y1, y2 = ext.Y
259+
return GI.Polygon(StaticArrays.@SVector[GI.LinearRing(StaticArrays.@SVector[(x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1)])])
260+
end
261+
262+
263+
function extent_to_polygon(ext::Extents.Extent{(:Y, :X)})
264+
x1, x2 = ext.X
265+
y1, y2 = ext.Y
266+
return GI.Polygon(StaticArrays.@SVector[GI.LinearRing(StaticArrays.@SVector[(x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1)])])
267+
end
268+
269+
270+
271+
272+

test/helpers.jl

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module TestHelpers
22

3+
import GeometryOps as GO
4+
35
using Test, GeoInterface, ArchGDAL, GeometryBasics, LibGEOS
46

57
export @test_implementations, @testset_implementations
@@ -31,6 +33,45 @@ const TEST_MODULES = [GeoInterface, ArchGDAL, GeometryBasics, LibGEOS]
3133
function GeoInterface.convert(::Type{Vector{_ALL_GB_GEOM_TYPES}}, ::GeoInterface.GeometryCollectionTrait, geoms)
3234
return _ALL_GB_GEOM_TYPES[GeoInterface.convert(GeometryBasics, g) for g in GeoInterface.getgeom(geoms)]
3335
end
36+
37+
function GeoInterface.convert(
38+
::Type{GeometryBasics.LineString},
39+
type::GeoInterface.LineStringTrait,
40+
geom::GeoInterface.Wrappers.LinearRing{false, false, StaticArraysCore.SVector{N, Tuple{Float64, Float64}}, Nothing, Nothing} where N
41+
)
42+
return GeoInterface.convert(LineString, GeoInterface.LineStringTrait(), collect(geom.geom))
43+
end
44+
45+
function GeoInterface.convert(
46+
::Type{GeometryBasics.LineString},
47+
type::GeoInterface.LineStringTrait,
48+
geom::GeoInterface.Wrappers.LinearRing{false, false, StaticArraysCore.SVector{N, Tuple{Float64, Float64}}, Nothing, Nothing} where N
49+
)
50+
return LineString(Point2{Float64}.(collect(geom.geom)))
51+
end
52+
end
53+
54+
55+
@eval ArchGDAL begin
56+
function GeoInterface.convert(
57+
::Type{T},
58+
type::GeoInterface.PolygonTrait,
59+
geom,
60+
) where {T<:IGeometry}
61+
f = get(lookup_method, typeof(type), nothing)
62+
isnothing(f) && error(
63+
"Cannot convert an object of $(typeof(geom)) with the $(typeof(type)) trait (yet). Please report an issue.",
64+
)
65+
poly = createpolygon()
66+
foreach(GeoInterface.getring(geom)) do ring
67+
xs = GeoInterface.x.(GeoInterface.getpoint(ring)) |> collect
68+
ys = GeoInterface.y.(GeoInterface.getpoint(ring)) |> collect
69+
subgeom = unsafe_createlinearring(xs, ys)
70+
result = GDAL.ogr_g_addgeometrydirectly(poly, subgeom)
71+
@ogrerr result "Failed to add linearring."
72+
end
73+
return poly
74+
end
3475
end
3576

3677

@@ -52,7 +93,16 @@ function _test_implementations_inner(modules::Union{Expr,Vector}, code::Expr)
5293
for mod in modules1
5394
expr = Expr(:block)
5495
for (var, genkey) in pairs(vars)
55-
push!(expr.args, :($genkey = $GeoInterface.convert($mod, $var)))
96+
push!(expr.args, :($genkey = if $var isa $(GeoInterface.Extents.Extent)
97+
if $mod in ($GeoInterface, $LibGEOS)
98+
$var
99+
else
100+
$GeoInterface.convert($mod, $(GO.extent_to_polygon)($var))
101+
end
102+
else
103+
$GeoInterface.convert($mod, $var)
104+
end
105+
))
56106
end
57107
push!(expr.args, :(@test $code1))
58108
push!(tests.args, expr)

test/methods/geom_relations.jl

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import GeometryOps as GO
22
import GeoInterface as GI
33
import LibGEOS as LG
4+
using Extents
45
using ..TestHelpers
56

67
# Tests of DE-9IM Methods
@@ -65,6 +66,12 @@ mp1 = LG.MultiPolygon([p1])
6566
mp2 = LG.MultiPolygon([p6, p7])
6667
gc1 = LG.GeometryCollection([pt1, l5, p6])
6768

69+
ext1 = Extents.Extent(X=(0.0, 1.0), Y=(0.0, 1.0))
70+
ext2 = Extents.Extent(X=(0.5, 1.5), Y=(0.5, 1.5))
71+
ext3 = Extents.Extent(X=(2.0, 3.0), Y=(2.0, 3.0))
72+
ext4 = Extents.Extent(X=(1.0, 2.0), Y=(1.0, 2.0)) # Touches ext1
73+
74+
6875
test_pairs = [
6976
# Points and geometries
7077
(pt1, pt1, "pt1", "pt1", "Same point"),
@@ -86,7 +93,7 @@ test_pairs = [
8693
(pt6, p1, "pt6", "p1", "Point on hole edge"),
8794
(pt7, p1, "pt7", "p1", "Point inside of polygon hole"),
8895
(p1, pt5, "p1", "pt5", "Point inside of polygon (order swapped)"),
89-
# # Lines and geometries
96+
# Lines and geometries
9097
(l1, l1, "l1", "l1", "Same line"),
9198
(l2, l1, "l2", "l1", "L2 is one segment of l1"),
9299
(l3, l1, "l3", "l1", "L3 shares one segment with l1 and has one segment outside"),
@@ -155,7 +162,17 @@ test_pairs = [
155162
(mpt1, mpt3, "mpt1", "mpt3", "No shared points"),
156163
(ml1, ml2, "ml1", "ml2", "Lines in ml1 cross and touch ml2"),
157164
(mp1, mp2, "mp1", "mp2", "Polygons in mp1 are inside hole and overlap"),
158-
# (gc1, ml1, "gc1", "ml1", "Make sure collection works with multi-geom"),
165+
# Extent tests
166+
(ext1, ext1, "ext1", "ext1", "Same extent"),
167+
(ext1, ext2, "ext1", "ext2", "Overlapping extents"),
168+
(ext1, ext3, "ext1", "ext3", "Disjoint extents"),
169+
(ext1, ext4, "ext1", "ext4", "Touching extents"),
170+
(ext1, pt1, "ext1", "pt1", "Point inside extent"),
171+
(ext1, pt2, "ext1", "pt2", "Point outside extent"),
172+
(ext1, l1, "ext1", "l1", "Line inside extent"),
173+
(ext1, l2, "ext1", "l2", "Line crossing extent"),
174+
(ext1, p1, "ext1", "p1", "Polygon same as extent"),
175+
(ext1, p2, "ext1", "p2", "Polygon overlapping extent"),
159176
]
160177

161178
function test_geom_relation(GO_f, LG_f, f_name; swap_points=false)
@@ -170,13 +187,26 @@ function test_geom_relation(GO_f, LG_f, f_name; swap_points=false)
170187
end
171188
end
172189

173-
@testset "Contains" begin test_geom_relation(GO.contains, LG.contains, "contains"; swap_points = true) end
174-
@testset "Covered By" begin test_geom_relation(GO.coveredby, LG.coveredby, "coveredby") end
175-
@testset "Covers" begin test_geom_relation(GO.covers, LG.covers, "covers"; swap_points = true) end
176-
@testset "Disjoint" begin test_geom_relation(GO.disjoint, LG.disjoint, "disjoint")end
177-
@testset "Intersect" begin test_geom_relation(GO.intersects, LG.intersects, "intersects") end
178-
@testset "Touches" begin test_geom_relation(GO.touches, LG.touches, "touches") end
179-
@testset "Within" begin test_geom_relation(GO.within, LG.within, "within") end
190+
191+
function test_geom_relation(GO_f, alg::GO.Algorithm, f_name; swap_points=false)
192+
for (g1, g2, sg1, sg2, sdesc) in test_pairs
193+
@testset "$sg1 $sg2 $sdesc" begin
194+
if swap_points
195+
@test_implementations (GO_f($g2, $g1) == GO_f(alg, $g2, $g1))
196+
else
197+
@test_implementations GO_f($g1, $g2) == GO_f(alg, $g1, $g2)
198+
end
199+
end
200+
end
201+
end
202+
203+
@testset "Contains" begin test_geom_relation(GO.contains, GO.GEOS(), "contains"; swap_points = true) end
204+
@testset "Covered By" begin test_geom_relation(GO.coveredby, GO.GEOS(), "coveredby") end
205+
@testset "Covers" begin test_geom_relation(GO.covers, GO.GEOS(), "covers"; swap_points = true) end
206+
@testset "Disjoint" begin test_geom_relation(GO.disjoint, GO.GEOS(), "disjoint")end
207+
@testset "Intersect" begin test_geom_relation(GO.intersects, GO.GEOS(), "intersects") end
208+
@testset "Touches" begin test_geom_relation(GO.touches, GO.GEOS(), "touches") end
209+
@testset "Within" begin test_geom_relation(GO.within, GO.GEOS(), "within") end
180210

181211

182212
@testset "Overlaps" begin

0 commit comments

Comments
 (0)