Skip to content

Commit ba8cd05

Browse files
maxfreuvisr
andauthored
implement STRtree (#115)
Co-authored-by: Martijn Visser <[email protected]>
1 parent 8f6844b commit ba8cd05

File tree

4 files changed

+94
-1
lines changed

4 files changed

+94
-1
lines changed

src/LibGEOS.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ export Point,
9090
getYMax,
9191
minimumRotatedRectangle,
9292
getGeometry,
93-
getGeometries
93+
getGeometries,
94+
STRtree,
95+
query
9496

9597
include("libgeos_api.jl")
9698

@@ -190,5 +192,6 @@ include("geos_functions.jl")
190192
include("geos_types.jl")
191193
include("geos_operations.jl")
192194
include("geo_interface.jl")
195+
include("strtree.jl")
193196
include("deprecated.jl")
194197
end

src/strtree.jl

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
struct STRtree{T}
2+
ptr::Ptr{GEOSSTRtree} # void pointer to the tree
3+
items::T # any geometry for which an envelope can be derived
4+
end
5+
6+
"""
7+
STRtree(items; nodecapacity=10, context::GEOSContext=_context)
8+
9+
Create a Sort Tile Recursive tree for fast intersection queries of objects for which an
10+
envelope is defined. The tree cannot be changed after its creation.
11+
12+
## Arguments
13+
- `items`: The items to store in the tree.
14+
- `nodecapacity`: The maximum number of items that can be stored per tree node (default 10).
15+
- `context`: The GEOS context in which the tree should be created in. Defaults to the global
16+
LibGEOS context.
17+
"""
18+
function STRtree(items; nodecapacity = 10, context::GEOSContext = _context)
19+
tree = LibGEOS.GEOSSTRtree_create_r(context.ptr, nodecapacity)
20+
for item in items
21+
envptr = envelope(item).ptr
22+
GEOSSTRtree_insert_r(context.ptr, tree, envptr, pointer_from_objref(item))
23+
end
24+
return STRtree(tree, items)
25+
end
26+
27+
"""
28+
query(tree::STRtree, geometry; context::GEOSContext=_context)
29+
30+
Returns the objects within `tree`, whose envelope intersects the envelope of `geometry`.
31+
32+
## Arguments
33+
- `tree`: The STRtree to query
34+
- `geometry`: The LibGEOS geometry (e.g. Polygon) to run the query for
35+
- `context`: The GEOS context. Defaults to the global LibGEOS context.
36+
"""
37+
function query(
38+
tree::STRtree{T},
39+
geometry::Geometry;
40+
context::GEOSContext = _context,
41+
) where {T}
42+
matches = eltype(T)[]
43+
44+
# called for each overlapping item in the tree, used to push the item to matches
45+
function callback(item, userdata)::Ptr{Cvoid}
46+
item_ = unsafe_pointer_to_objref(item)
47+
userdata_ = unsafe_pointer_to_objref(userdata)
48+
push!(userdata_, item_)
49+
return C_NULL
50+
end
51+
52+
cfun_callback = @cfunction($callback, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}))
53+
GEOSSTRtree_query_r(
54+
context.ptr,
55+
tree.ptr,
56+
geometry.ptr,
57+
cfun_callback,
58+
pointer_from_objref(matches),
59+
)
60+
return matches
61+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ end
1818
include("test_geo_interface.jl")
1919
include("test_regressions.jl")
2020
include("test_invalid_geometry.jl")
21+
include("test_strtree.jl")
2122
end

test/test_strtree.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@testset "STRtree" begin
2+
# front page examples
3+
p1 = readgeom("POLYGON((0 0,1 0,1 1,0 0))")
4+
p2 = readgeom("POLYGON((0 0,1 0,1 1,0 1,0 0))")
5+
p3 = readgeom("POLYGON((2 0,3 0,3 1,2 1,2 0))")
6+
7+
# query polygon
8+
p4 = readgeom("POLYGON((0.5 0.5, 1.5 0.5, 1.5 1.5, 0.5 1.5, 0.5 0.5))")
9+
10+
tree = STRtree((p1, p2, p3))
11+
res = query(tree, p4)
12+
@test res isa Vector{Polygon}
13+
@test res == [p1, p2]
14+
15+
# heterogeneous tree with linestring and polygon
16+
ls = readgeom("LINESTRING(0 0,1 0,1 1,0 0)")
17+
tree = STRtree((p1, p2, p3, ls))
18+
res = query(tree, p4)
19+
@test res isa Vector{GeoInterface.AbstractGeometry}
20+
@test res == [p1, p2, ls]
21+
22+
# Let's accidentally "drop" p2. Since it is in the tree it won't be finalized.
23+
p2 = nothing
24+
p5 = readgeom("POLYGON((0 0,1 0,1 1,0 1,0 0))") # equal to p2
25+
res = query(tree, p4)
26+
@test equals(res[1], p1)
27+
@test equals(res[2], p5)
28+
end

0 commit comments

Comments
 (0)