Skip to content

Commit 004ac6a

Browse files
committed
Implement natural indexing from tg
1 parent 8809343 commit 004ac6a

File tree

2 files changed

+236
-0
lines changed

2 files changed

+236
-0
lines changed

src/GeometryOps.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ include("not_implemented_yet.jl") # functions that are not implemented yet, but
4141
# Include utility modules first!
4242
include("utils/LoopStateMachine/LoopStateMachine.jl") # Utils for functions that can tell the loop they run in to do something via the return value
4343
include("utils/SpatialTreeInterface/SpatialTreeInterface.jl") # Utils for spatial trees
44+
include("utils/NaturalIndexing.jl") # Utils for natural indexing
4445
include("utils/utils.jl") # More general utility functions
4546

4647
include("methods/angles.jl")

src/utils/NaturalIndexing.jl

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
module NaturalIndexing
2+
3+
import GeoInterface as GI
4+
import Extents
5+
6+
using ..SpatialTreeInterface
7+
8+
"""
9+
NaturalLevel{E <: Extents.Extent}
10+
11+
A level in the natural tree. Stored in a vector in [`NaturalIndex`](@ref).
12+
13+
- `extents` is a vector of extents of the children of the node
14+
"""
15+
struct NaturalLevel{E <: Extents.Extent}
16+
extents::Vector{E} # child extents
17+
end
18+
19+
Base.show(io::IO, level::NaturalLevel) = print(io, "NaturalLevel($(length(level.extents)) extents)")
20+
Base.show(io::IO, ::MIME"text/plain", level::NaturalLevel) = Base.show(io, level)
21+
22+
"""
23+
NaturalIndex{E <: Extents.Extent}
24+
25+
A natural tree index. Stored in a vector in [`NaturalIndex`](@ref).
26+
27+
- `nodecapacity` is the "spread", number of children per node
28+
- `extent` is the extent of the tree
29+
- `levels` is a vector of [`NaturalLevel`](@ref)s
30+
"""
31+
struct NaturalIndex{E <: Extents.Extent}
32+
nodecapacity::Int # "spread", number of children per node
33+
extent::E
34+
levels::Vector{NaturalLevel{E}}
35+
end
36+
37+
GI.extent(idx::NaturalIndex) = idx.extent
38+
Extents.extent(idx::NaturalIndex) = idx.extent
39+
40+
function Base.show(io::IO, ::MIME"text/plain", idx::NaturalIndex)
41+
println(io, "NaturalIndex with $(length(idx.levels)) levels and $(idx.nodecapacity) children per node")
42+
println(io, "extent: $(idx.extent)")
43+
end
44+
45+
function Base.show(io::IO, idx::NaturalIndex)
46+
println(io, "NaturalIndex($(length(idx.levels)) levels, $(idx.extent))")
47+
end
48+
49+
function NaturalIndex(geoms; nodecapacity = 32)
50+
e1 = GI.extent(first(geoms))
51+
E = typeof(e1)
52+
return NaturalIndex{E}(geoms; nodecapacity = nodecapacity)
53+
end
54+
55+
function NaturalIndex{E}(geoms; nodecapacity = 32) where E <: Extents.Extent
56+
last_level_extents = GI.extent.(geoms)
57+
return NaturalIndex{E}(last_level_extents; nodecapacity = nodecapacity)
58+
end
59+
60+
function NaturalIndex(last_level_extents::Vector{E}; nodecapacity = 32) where E <: Extents.Extent
61+
return NaturalIndex{E}(last_level_extents; nodecapacity = nodecapacity)
62+
end
63+
64+
function NaturalIndex{E}(last_level_extents::Vector{E}; nodecapacity = 32) where E <: Extents.Extent
65+
ngeoms = length(last_level_extents)
66+
last_level = NaturalLevel(last_level_extents)
67+
68+
nlevels = _number_of_levels(nodecapacity, ngeoms)
69+
70+
levels = Vector{NaturalLevel{E}}(undef, nlevels)
71+
levels[end] = last_level
72+
73+
for level_index in (nlevels-1):(-1):1
74+
prev_level = levels[level_index+1] # this is always instantiated
75+
nrects = _number_of_keys(nodecapacity, nlevels - (level_index), ngeoms)
76+
# @show level_index nrects
77+
extents = [
78+
begin
79+
start = (rect_index - 1) * nodecapacity + 1
80+
stop = min(start + nodecapacity - 1, length(prev_level.extents))
81+
reduce(Extents.union, view(prev_level.extents, start:stop))
82+
end
83+
for rect_index in 1:nrects
84+
]
85+
levels[level_index] = NaturalLevel(extents)
86+
end
87+
88+
return NaturalIndex(nodecapacity, reduce(Extents.union, levels[1].extents), levels)
89+
90+
end
91+
92+
function _number_of_keys(nodecapacity::Int, level::Int, ngeoms::Int)
93+
return ceil(Int, ngeoms / (nodecapacity ^ (level)))
94+
end
95+
96+
"""
97+
_number_of_levels(nodecapacity::Int, ngeoms::Int)
98+
99+
Calculate the number of levels in a natural tree for a given number of geometries and node capacity.
100+
101+
## How this works
102+
103+
The number of keys in a level is given by `ngeoms / nodecapacity ^ level`.
104+
105+
The number of levels is the smallest integer such that the number of keys in the last level is 1.
106+
So it goes - if that makes sense.
107+
"""
108+
function _number_of_levels(nodecapacity::Int, ngeoms::Int)
109+
level = 1
110+
while _number_of_keys(nodecapacity, level, ngeoms) > 1
111+
level += 1
112+
end
113+
return level
114+
end
115+
116+
117+
# This is like a pointer to a node in the tree.
118+
"""
119+
NaturalTreeNode{E <: Extents.Extent}
120+
121+
A reference to a node in the natural tree. Kind of like a tree cursor.
122+
123+
- `parent_index` is a pointer to the parent index
124+
- `level` is the level of the node in the tree
125+
- `index` is the index of the node in the level
126+
- `extent` is the extent of the node
127+
"""
128+
struct NaturalTreeNode{E <: Extents.Extent}
129+
parent_index::NaturalIndex{E}
130+
level::Int
131+
index::Int
132+
extent::E
133+
end
134+
135+
Extents.extent(node::NaturalTreeNode) = node.extent
136+
137+
# What does SpatialTreeInterface require of trees?
138+
# - Parents completely cover their children
139+
# - `GI.extent(node)` returns `Extent`
140+
# - can mean that `Extents.extent(node)` returns the extent of the node
141+
# - `nchild(node)` returns the number of children of the node
142+
# - `getchild(node)` returns an iterator over all children of the node
143+
# - `getchild(node, i)` returns the i-th child of the node
144+
# - `isleaf(node)` returns a boolean indicating whether the node is a leaf
145+
# - `child_indices_extents(node)` returns an iterator over the indices and extents of the children of the node
146+
147+
function SpatialTreeInterface.nchild(node::NaturalTreeNode)
148+
start_idx = (node.index - 1) * node.parent_index.nodecapacity + 1
149+
stop_idx = min(start_idx + node.parent_index.nodecapacity - 1, length(node.parent_index.levels[node.level+1].extents))
150+
return stop_idx - start_idx + 1
151+
end
152+
153+
function SpatialTreeInterface.getchild(node::NaturalTreeNode, i::Int)
154+
child_index = (node.index - 1) * node.parent_index.nodecapacity + i
155+
return NaturalTreeNode(
156+
node.parent_index,
157+
node.level + 1, # increment level by 1
158+
child_index, # index of this particular child
159+
node.parent_index.levels[node.level+1].extents[child_index] # the extent of this child
160+
)
161+
end
162+
163+
# Get all children of a node
164+
function SpatialTreeInterface.getchild(node::NaturalTreeNode)
165+
return (SpatialTreeInterface.getchild(node, i) for i in 1:SpatialTreeInterface.nchild(node))
166+
end
167+
168+
SpatialTreeInterface.isleaf(node::NaturalTreeNode) = node.level == length(node.parent_index.levels) - 1
169+
170+
function SpatialTreeInterface.child_indices_extents(node::NaturalTreeNode)
171+
start_idx = (node.index - 1) * node.parent_index.nodecapacity + 1
172+
stop_idx = min(start_idx + node.parent_index.nodecapacity - 1, length(node.parent_index.levels[node.level+1].extents))
173+
return ((i, node.parent_index.levels[node.level+1].extents[i]) for i in start_idx:stop_idx)
174+
end
175+
176+
# implementation for "root node" / top level tree
177+
178+
SpatialTreeInterface.isleaf(node::NaturalIndex) = length(node.levels) == 1
179+
180+
SpatialTreeInterface.nchild(node::NaturalIndex) = length(node.levels[1].extents)
181+
182+
SpatialTreeInterface.getchild(node::NaturalIndex) = SpatialTreeInterface.getchild(NaturalTreeNode(node, 0, 1, node.extent))
183+
SpatialTreeInterface.getchild(node::NaturalIndex, i) = SpatialTreeInterface.getchild(NaturalTreeNode(node, 0, 1, node.extent), i)
184+
185+
SpatialTreeInterface.child_indices_extents(node::NaturalIndex) = (i_ext for i_ext in enumerate(node.levels[1].extents))
186+
187+
struct NaturallyIndexedRing
188+
points::Vector{Tuple{Float64, Float64}}
189+
index::NaturalIndex{Extents.Extent{(:X, :Y), NTuple{2, NTuple{2, Float64}}}}
190+
end
191+
192+
function NaturallyIndexedRing(points::Vector{Tuple{Float64, Float64}}; nodecapacity = 32)
193+
index = NaturalIndex(GO.edge_extents(GI.LinearRing(points)); nodecapacity)
194+
return NaturallyIndexedRing(points, index)
195+
end
196+
197+
NaturallyIndexedRing(ring::NaturallyIndexedRing) = ring
198+
199+
function GI.convert(::Type{NaturallyIndexedRing}, ::GI.LinearRingTrait, geom)
200+
points = GO.tuples(geom).geom
201+
return NaturallyIndexedRing(points)
202+
end
203+
204+
Base.show(io::IO, ::MIME"text/plain", ring::NaturallyIndexedRing) = Base.show(io, ring)
205+
206+
Base.show(io::IO, ring::NaturallyIndexedRing) = print(io, "NaturallyIndexedRing($(length(ring.points)) points) with index $(sprint(show, ring.index))")
207+
208+
GI.ncoord(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = 2
209+
GI.is3d(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = false
210+
GI.ismeasured(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = false
211+
212+
GI.npoint(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = length(ring.points)
213+
GI.getpoint(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = ring.points[i]
214+
GI.getpoint(::GI.LinearRingTrait, ring::NaturallyIndexedRing, i::Int) = ring.points[i]
215+
216+
GI.ngeom(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = length(ring.points)
217+
GI.getgeom(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = ring.points
218+
GI.getgeom(::GI.LinearRingTrait, ring::NaturallyIndexedRing, i::Int) = ring.points[i]
219+
220+
Extents.extent(ring::NaturallyIndexedRing) = ring.index.extent
221+
222+
GI.isgeometry(::NaturallyIndexedRing) = true
223+
GI.geomtrait(::NaturallyIndexedRing) = GI.LinearRingTrait()
224+
225+
function prepare_naturally(geom)
226+
return GO.apply(GI.PolygonTrait(), geom) do poly
227+
return GI.Polygon([GI.convert(NaturallyIndexedRing, GI.LinearRingTrait(), ring) for ring in GI.getring(poly)])
228+
end
229+
end
230+
231+
export NaturalTree, NaturallyIndexedRing, prepare_naturally
232+
233+
end # module NaturalIndexing
234+
235+
using .NaturalIndexing

0 commit comments

Comments
 (0)