Skip to content

Commit e08a202

Browse files
committed
RTree: optimize spatial queries
if node is known to be inside the region of intersects/contained query, don't tests its subtrees as they are automatically intersecting/contained too
1 parent 4fdb4dd commit e08a202

File tree

2 files changed

+46
-27
lines changed

2 files changed

+46
-27
lines changed

src/abstract.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ Specifies the kind of spatial data query.
4444
"""
4545
@enum QueryKind QueryContainedIn QueryIntersectsWith # TODO QueryPoint QueryNearestNeighbours
4646

47+
"""
48+
Specifies the result of spatial data query.
49+
"""
50+
@enum QueryMatch::Int QueryNoMatch=0 QueryMatchPartial=1 QueryMatchComplete=2
51+
4752
"""
4853
Base abstract class for implementing spatial queries in `N`-dimensional space.
4954
"""

src/rtree/query.jl

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ function _isempty(node::Node, region::Region{T,N}) where {T,N}
5454
return true
5555
end
5656

57-
# the RTreeIterator/RTreeRegionQueryIterator state
58-
# FIXME can mark whether the MBR of the node satisfies the query, so all
59-
# its subnodes and data elements need not to be checked
57+
# the RTreeIterator state
6058
struct RTreeIteratorState{T,N,V}
6159
leaf::Leaf{T,N,V} # current leaf node
6260
indices::Vector{Int} # indices of the nodes (in their parents) in the current subtree
@@ -112,22 +110,32 @@ struct RTreeRegionQueryIterator{T,N,V,Q,TT,R} <: SpatialQueryIterator{T,N,V,Q}
112110
end
113111
end
114112

113+
# the RTreeRegionQueryIterator state
114+
struct RTreeQueryIteratorState{T,N,V}
115+
leaf::Leaf{T,N,V} # current leaf node
116+
indices::Vector{Int} # indices of the nodes (in their parents) in the current subtree
117+
needtests::BitVector # whether children MBRs should be tested or not (because they automatically satisfy query)
118+
end
119+
120+
# get the current data element pointed by `RTreeIteratorState`
121+
Base.get(state::RTreeQueryIteratorState) = @inbounds(state.leaf[state.indices[1]])
122+
115123
function Base.iterate(iter::RTreeRegionQueryIterator)
124+
isempty(iter.tree) && return nothing
116125
# 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)))
126+
root_match = should_visit(iter.tree.root, iter)
127+
root_match == QueryNoMatch && return nothing
128+
return _iterate(iter, iter.tree.root, fill(1, height(iter.tree)),
129+
root_match == QueryMatchComplete ? falses(height(iter.tree)) : trues(height(iter.tree)))
122130
end
123131

124132
function Base.iterate(iter::RTreeRegionQueryIterator,
125-
state::RTreeIteratorState)
126-
@inbounds ix = state.indices[1] = _nextchild(state.leaf, state.indices[1] + 1, iter)
133+
state::RTreeQueryIteratorState)
134+
@inbounds ix = state.indices[1] = _nextchild(state.leaf, state.indices[1] + 1, state.needtests[1], iter)[1]
127135
if ix <= length(state.leaf) # fast branch: next data element in the same leaf
128136
return get(state), state
129137
else
130-
return _iterate(iter, state.leaf, state.indices)
138+
return _iterate(iter, state.leaf, state.indices, state.needtests)
131139
end
132140
end
133141

@@ -149,40 +157,45 @@ intersects_with(tree::RTree{T,N}, region::Region{T,N}) where {T,N} =
149157

150158
# whether the R-tree node/data element should be visited (i.e. its children examined)
151159
# by the region iterator
152-
should_visit(node::Node, iter::RTreeRegionQueryIterator) =
153-
intersects(iter.region, mbr(node)) # FIXME update for NotContainedIn etc
160+
function should_visit(node::Node, iter::RTreeRegionQueryIterator)
161+
kind = querykind(iter)
162+
if kind == QueryContainedIn || kind == QueryIntersectsWith
163+
contains(iter.region, mbr(node)) && return QueryMatchComplete # node (and all its children) fully contained in query region
164+
intersects(iter.region, mbr(node)) && return QueryMatchPartial # node (and maybe its children) intersects query region
165+
return QueryNoMatch
166+
else
167+
throw(ArgumentError("Unknown spatial query kind: $kind"))
168+
end
169+
end
154170

155171
should_visit(el::Any, iter::RTreeRegionQueryIterator) =
156172
((querykind(iter) == QueryContainedIn) && contains(iter.region, mbr(el))) ||
157-
((querykind(iter) == QueryIntersectsWith) && intersects(iter.region, mbr(el)))
173+
((querykind(iter) == QueryIntersectsWith) && intersects(iter.region, mbr(el))) ?
174+
QueryMatchComplete : QueryNoMatch
158175
# FIXME update for NotContainedIn etc
159176

160177
# get the index of the first child of `node` starting from `pos` (including)
161178
# 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)
179+
@inline function _nextchild(node::Node, pos::Integer, needtests::Bool, iter::RTreeRegionQueryIterator)
180+
res = needtests ? QueryNoMatch : QueryMatchComplete # if tests not needed, all node subtrees are considered fully matching
181+
while pos <= length(node) && needtests &&
182+
((res = should_visit(@inbounds(node[pos]), iter)) == QueryNoMatch)
167183
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
171184
end
172-
return pos
185+
return pos, res
173186
end
174187

175188
# do depth-first search starting from the `node` subtree and return the
176189
# `RTreeIteratorState` for the first leaf that satisfies `iter` query or
177190
# `nothing` if no such leaf in the R-tree.
178191
# 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
192+
function _iterate(iter::RTreeRegionQueryIterator, node::Node,
193+
indices::AbstractVector{Int}, needtests::AbstractVector{Bool})
181194
#@debug"_iterate(): enter lev=$(level(node)) indices=$indices"
182195
@assert length(indices) == height(iter.tree)
183196
ix = @inbounds(indices[level(node) + 1])
184197
while true
185-
ix_new = _nextchild(node, ix, iter)
198+
ix_new, queryres = _nextchild(node, ix, needtests[level(node)+1], iter)
186199
#@debug "node=$(Int(Base.pointer_from_objref(node))) lev=$(level(node)) ix_new=$ix_new"
187200
if ix_new > length(node) # all node subtrees visited, go up one level
188201
while ix_new > length(node)
@@ -201,11 +214,12 @@ function _iterate(iter::RTreeRegionQueryIterator, nod::Node, indices::AbstractVe
201214
if node isa Branch
202215
# go down into the first child
203216
indices[level(node)] = ix = 1
217+
needtests[level(node)] = queryres != QueryMatchComplete
204218
node = node[ix_new]
205219
#@debug "_iterate(): down lev=$(level(node)) indices=$indices"
206220
else # Leaf
207221
#@debug "_iterate(): return lev=$(level(node)) indices=$indices"
208-
state = RTreeIteratorState(node, indices)
222+
state = RTreeQueryIteratorState(node, indices, needtests)
209223
return get(state), state
210224
end
211225
end

0 commit comments

Comments
 (0)