Skip to content

Commit 4fdb4dd

Browse files
committed
RTree: contained_in()/intersects_with() iteration
1 parent 22b4d0c commit 4fdb4dd

5 files changed

Lines changed: 138 additions & 49 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ and deletions).
3333
* `insert!(tree, item)`, `delete!(tree, item)` for element-wise insertion and deletion
3434
* bulk-loading of data using Overlap-minimizing Top-down (OMT) approach (`load!(tree, data)`)
3535
* `subtract!(tree, reg)` for removing data within specified region `reg`
36-
* **TODO: spatial queries**
36+
* `contained_in(tree, reg)` and `intersects_with(tree, reg)` spatial queries
3737

3838
## Simple Spatial Index
3939

src/SpatialIndexing.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module SpatialIndexing
22

33
export SpatialIndex, SpatialIndexException, SpatialElem,
4-
RTree, SimpleSpatialIndex
4+
RTree, SimpleSpatialIndex,
5+
contained_in, intersects_with
56

67
include("regions.jl")
78
include("pool.jl")

src/abstract.jl

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,12 @@ end
1414
__spatial_keyerror(eltype::Type, br::Region, id::Any = nothing) =
1515
idtrait(eltype) === HasNoID ? throw(KeyError(br)) : throw(KeyError((br, id)))
1616

17-
@enum QueryType::Int QueryContainedIn QueryIntersectsWith QueryPoint QueryNearestNeighbours
18-
1917
"""
2018
Base abstract class for spatial indexing of elements of type `V`
2119
in `N`-dimensional space with dimensions of type `T`.
2220
"""
2321
abstract type SpatialIndex{T<:Number, N, V} end
2422

25-
"""
26-
Base abstract class for implementing spatial queries in `N`-dimensional space.
27-
"""
28-
abstract type SpatialQueryIterator{T<:Number,N,Q} end
29-
3023
# basic SpatialIndex API
3124
Base.eltype(::Type{<:SpatialIndex{<:Any, <:Any, V}}) where V = V
3225
Base.eltype(si::SpatialIndex) = eltype(typeof(si))
@@ -46,6 +39,25 @@ Base.IteratorEltype(::Type{<:SpatialIndex}) = Base.HasEltype()
4639
Base.IteratorSize(::Type{<:SpatialIndex}) = Base.HasLength()
4740
# concrete SpatialIndex types should implement iterate()
4841

42+
"""
43+
Specifies the kind of spatial data query.
44+
"""
45+
@enum QueryKind QueryContainedIn QueryIntersectsWith # TODO QueryPoint QueryNearestNeighbours
46+
47+
"""
48+
Base abstract class for implementing spatial queries in `N`-dimensional space.
49+
"""
50+
abstract type SpatialQueryIterator{T<:Number,N,V,Q} end
51+
52+
Base.IteratorEltype(::Type{<:SpatialQueryIterator}) = Base.HasEltype()
53+
Base.IteratorSize(::Type{<:SpatialQueryIterator}) = Base.SizeUnknown()
54+
55+
Base.eltype(::Type{<:SpatialQueryIterator{<:Any,<:Any,V}}) where V = V
56+
Base.eltype(iter::SpatialQueryIterator) = eltype(typeof(iter))
57+
58+
querykind(::Type{<:SpatialQueryIterator{<:Any,<:Any,<:Any,Q}}) where Q = Q
59+
querykind(iter::SpatialQueryIterator) = querykind(typeof(iter))
60+
4961
# arbitrary spatial elements support
5062

5163
# Type trait for supporting id() method

src/rtree/query.jl

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -101,59 +101,113 @@ function Base.iterate(tree::RTree, state::RTreeIteratorState)
101101
return get(new_state), new_state
102102
end
103103

104-
#=
105-
TODO
106-
107-
struct RTreeRegionQueryIterator{T,N,K,TT,R} <: SpatialQuery{T,N,K}
104+
# iterates R-tree data elements matching `Q` query w.r.t `region`
105+
struct RTreeRegionQueryIterator{T,N,V,Q,TT,R} <: SpatialQueryIterator{T,N,V,Q}
108106
tree::TT
109107
region::R
110108

111109
function RTreeRegionQueryIterator{T,N}(kind::QueryKind, tree::TT, region::R) where
112-
{T, N, TT <: RTree{T,N}, R <: Region{T,N}}
113-
new{T,N,kind,TT,R}(tree, region)
110+
{T, N, V, TT <: RTree{T,N,V}, R <: Region{T,N}}
111+
new{T,N,V,kind,TT,R}(tree, region)
114112
end
115113
end
116114

117-
contained_in(tree::RTree{T,N}, region::Region{T,N}) where {T,N} =
118-
RTreeRegionQueryIterator{T,N}(QueryContainedIn, tree, region)
119-
120-
intersects_with(tree::RTree{T,N}, region::Region{T,N}) where {T,N} =
121-
RTreeRegionQueryIterator{T,N}(QueryIntersectsWith, tree, region)
115+
function Base.iterate(iter::RTreeRegionQueryIterator)
116+
# no data or doesn't intersect at all
117+
if (isempty(iter.tree) || !should_visit(iter.tree.root, iter))
118+
#@debug "iterate(): empty iter=$iter root_visit=$(should_visit(iter.tree.root, iter))"
119+
return nothing
120+
end
121+
return _iterate(iter, iter.tree.root, fill(1, height(iter.tree)))
122+
end
122123

123-
querytype(iter::RTreeRegionQueryIterator{<:Any,<:Any,K}) where K = K
124+
function Base.iterate(iter::RTreeRegionQueryIterator,
125+
state::RTreeIteratorState)
126+
@inbounds ix = state.indices[1] = _nextchild(state.leaf, state.indices[1] + 1, iter)
127+
if ix <= length(state.leaf) # fast branch: next data element in the same leaf
128+
return get(state), state
129+
else
130+
return _iterate(iter, state.leaf, state.indices)
131+
end
132+
end
124133

125-
isok(iter::TreeRegionQueryIterator{T,N}, node::Any) where {T,N} =
126-
intersects(iter.region, mbr(node))
134+
"""
135+
contained_in(index::SpatialIndex, region::Region)
127136
128-
isok(iter::RTreeRegionQueryIterator{<:Any,<:Any,QueryContainedIn}, el::Any) =
129-
contains(iter.region, mbr(node))
137+
Get iterator for `index` elements contained in `region`.
138+
"""
139+
contained_in(tree::RTree{T,N}, region::Region{T,N}) where {T,N} =
140+
RTreeRegionQueryIterator{T,N}(QueryContainedIn, tree, region)
130141

131-
struct RTreeRegionQueryIteratorState{T,N,Y}
132-
subtree::Vector{Node{T,N}}
133-
end
142+
"""
143+
intersects_with(index::SpatialIndex, region::Region)
134144
135-
function next(iter::RTreeRegionQueryIterator{T,N},
136-
state::RTreeRegionQueryIteratorState{T, N})
137-
isempty(state.subtree) && return nothing
138-
end
145+
Get iterator for `index` elements intersecting with `region`.
146+
"""
147+
intersects_with(tree::RTree{T,N}, region::Region{T,N}) where {T,N} =
148+
RTreeRegionQueryIterator{T,N}(QueryIntersectsWith, tree, region)
139149

140-
function Base.iterate(iter::RTreeRegionQueryIterator)
141-
# no root or doesn't intersect at all
142-
iter.tree.root !== nothing || !isok(iter, iter.tree.root) || return nothing
143-
subtree = push!(Vector{Node{T,N}}(), tree.root) # start with the root
144-
return iterate(iter, RTreeRegionQueryIteratorState(subtree))
150+
# whether the R-tree node/data element should be visited (i.e. its children examined)
151+
# by the region iterator
152+
should_visit(node::Node, iter::RTreeRegionQueryIterator) =
153+
intersects(iter.region, mbr(node)) # FIXME update for NotContainedIn etc
154+
155+
should_visit(el::Any, iter::RTreeRegionQueryIterator) =
156+
((querykind(iter) == QueryContainedIn) && contains(iter.region, mbr(el))) ||
157+
((querykind(iter) == QueryIntersectsWith) && intersects(iter.region, mbr(el)))
158+
# FIXME update for NotContainedIn etc
159+
160+
# get the index of the first child of `node` starting from `pos` (including)
161+
# that satifies `iter` query (or length(node) + 1 if not found)
162+
@inline function _nextchild(node::Node, pos::Integer, iter::RTreeRegionQueryIterator)
163+
if level(node) == 0 && length(iter.tree) > 100 && pos <= length(node)
164+
#@debug "_nextchild(): lev=$(level(node)) len=$(length(node)) pos=$pos should_visit=$(should_visit(@inbounds(node[pos]), iter)) rect=$(mbr(node[pos]))"
165+
end
166+
while pos <= length(node) && !should_visit(@inbounds(node[pos]), iter)
167+
pos += 1
168+
if level(node) == 0 && length(iter.tree) > 100 && pos <= length(node)
169+
#@debug "_nextchild(): lev=$(level(node)) len=$(length(node)) pos=$pos should_visit=$(should_visit(@inbounds(node[pos]), iter)) rect=$(mbr(node[pos]))"
170+
end
171+
end
172+
return pos
145173
end
146174

147-
function Base.iterate(iter::RTreeRegionQueryIterator{T,N},
148-
state::RTreeRegionQueryIteratorState{T,N})
149-
isempty(state.subtree) && return nothing
150-
node = pop!(state.subtree)
151-
152-
node = next(iter, state)
153-
if node === nothing
154-
return nothing
155-
else
156-
return (node, state)
157-
end
175+
# do depth-first search starting from the `node` subtree and return the
176+
# `RTreeIteratorState` for the first leaf that satisfies `iter` query or
177+
# `nothing` if no such leaf in the R-tree.
178+
# The method modifies `indicies` array and uses it for the returned iteration state
179+
function _iterate(iter::RTreeRegionQueryIterator, nod::Node, indices::AbstractVector{Int})
180+
node = nod
181+
#@debug"_iterate(): enter lev=$(level(node)) indices=$indices"
182+
@assert length(indices) == height(iter.tree)
183+
ix = @inbounds(indices[level(node) + 1])
184+
while true
185+
ix_new = _nextchild(node, ix, iter)
186+
#@debug "node=$(Int(Base.pointer_from_objref(node))) lev=$(level(node)) ix_new=$ix_new"
187+
if ix_new > length(node) # all node subtrees visited, go up one level
188+
while ix_new > length(node)
189+
if !hasparent(node)
190+
#@debug "_iterate(): finished lev=$(level(node)) indices=$indices ix_new=$ix_new"
191+
return nothing # returned to root, iteration finished
192+
end
193+
#@debug "_iterate(): up lev=$(level(node)) indices=$indices ix_new=$ix_new"
194+
node = parent(node)
195+
@inbounds ix_new = indices[level(node) + 1] += 1 # next subtree
196+
end
197+
ix = ix_new
198+
#@debug "_iterate(): next subtree lev=$(level(node)) indices=$indices ix_new=$ix_new"
199+
else # subtree found
200+
ix_new > ix && @inbounds(indices[level(node) + 1] = ix_new)
201+
if node isa Branch
202+
# go down into the first child
203+
indices[level(node)] = ix = 1
204+
node = node[ix_new]
205+
#@debug "_iterate(): down lev=$(level(node)) indices=$indices"
206+
else # Leaf
207+
#@debug "_iterate(): return lev=$(level(node)) indices=$indices"
208+
state = RTreeIteratorState(node, indices)
209+
return get(state), state
210+
end
211+
end
212+
end
158213
end
159-
=#

test/rtree.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ end
3434
@test_throws KeyError delete!(tree, SI.empty(SI.regiontype(tree)), 1)
3535
@test SI.check(tree)
3636
@test iterate(tree) === nothing
37+
@test iterate(contained_in(tree, SI.empty(SI.mbrtype(tree)))) === nothing
38+
@test iterate(intersects_with(tree, SI.empty(SI.mbrtype(tree)))) === nothing
39+
@test iterate(contained_in(tree, cmbr)) === nothing
40+
@test iterate(intersects_with(tree, cmbr)) === nothing
3741
@test collect(tree) == eltype(tree)[]
42+
@test collect(contained_in(tree, cmbr)) == eltype(tree)[]
43+
@test collect(intersects_with(tree, cmbr)) == eltype(tree)[]
3844
@test typeof(collect(tree)) === Vector{eltype(tree)}
3945

4046
@test insert!(tree, ambr, 1, 2) === tree
@@ -72,6 +78,12 @@ end
7278
@test insert!(tree, cmbr, 3, 3) === tree
7379
@test length(tree) == 3
7480
@test SI.check(tree)
81+
@test length(collect(contained_in(tree, SI.Rect((0.0, 0.0), (1.0, 1.0))))) == 3
82+
@test length(collect(intersects_with(tree, SI.Rect((0.0, 0.0), (1.0, 1.0))))) == 3
83+
@test length(collect(contained_in(tree, SI.Rect((0.0, 0.0), (0.6, 0.6))))) == 2
84+
@test length(collect(intersects_with(tree, SI.Rect((0.0, 0.0), (0.6, 0.6))))) == 2
85+
@test length(collect(contained_in(tree, SI.Rect((0.0, 0.0), (0.55, 0.55))))) == 1 # a only
86+
@test length(collect(intersects_with(tree, SI.Rect((0.0, 0.0), (0.55, 0.55))))) == 2 # a and c
7587
end
7688

7789
@testset "RTree{Int,3,String,Nothing}(variant=$tree_var) (no id)" for tree_var in tree_vars
@@ -163,6 +175,16 @@ end
163175
all_elems = collect(tree)
164176
@test length(all_elems) == length(tree)
165177
@test eltype(all_elems) === eltype(tree)
178+
179+
bound_mbr = SI.Rect((-40.0, -20.0, -20.0), (30.0, 50.0, 40.0))
180+
181+
in_elems = collect(contained_in(tree, bound_mbr))
182+
@test eltype(in_elems) === eltype(tree)
183+
@test length(in_elems) == sum(br -> in(br, bound_mbr), mbrs)
184+
185+
isect_elems = collect(intersects_with(tree, bound_mbr))
186+
@test eltype(isect_elems) === eltype(tree)
187+
@test length(isect_elems) == sum(br -> SI.intersects(br, bound_mbr), mbrs)
166188
end
167189
end
168190

0 commit comments

Comments
 (0)