Skip to content

Commit d355d7f

Browse files
author
Max Freudenberg
committed
initial commit
1 parent c08788a commit d355d7f

6 files changed

Lines changed: 183 additions & 13 deletions

File tree

Project.toml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
name = "STRtree"
1+
name = "GISTRtree"
22
uuid = "746ee33f-1797-42c2-866d-db2fce69d14d"
33
authors = ["Max Freudenberg <maximilian.freudenberg@uni-goettingen.de> and contributors"]
4-
version = "1.0.0-DEV"
4+
version = "0.1.0"
5+
6+
[deps]
7+
Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
8+
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
59

610
[compat]
7-
julia = "1.8"
11+
julia = "1.6"
12+
Extents = "0.1.0"
13+
GeoInterface = "1"
814

915
[extras]
1016
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1-
# STRtree
1+
# GISTRtree
22

3-
[![Build Status](https://github.com/maxfreu/STRtree.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/maxfreu/STRtree.jl/actions/workflows/CI.yml?query=branch%3Amain)
3+
[![Build Status](https://github.com/maxfreu/GISTRtree.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/maxfreu/GISTRtree.jl/actions/workflows/CI.yml?query=branch%3Amain)
4+
5+
An STR tree implementation for GeoInterface compatible geometries.
6+
7+
Usage:
8+
9+
```julia
10+
using GISTRtree
11+
using Extents
12+
13+
tree = STRtree(geometries)
14+
query_result = query(tree, Extent(X=(0, 100.5), Y=(0, 1.5)))
15+
# or
16+
query_result = query(tree, query_geometry)
17+
```
18+
19+
Contributions are welcome! :)

src/GISTRtree.jl

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
module GISTRtree
2+
3+
using Extents
4+
import GeoInterface as GI
5+
6+
7+
"""
8+
STRtree(geoms; nodecapacity=10)
9+
10+
Construct an STRtree from a collection of geometries with the given node capacity.
11+
"""
12+
struct STRtree{T}
13+
rootnode::T
14+
function STRtree(geoms; nodecapacity=10)
15+
rootnode = build_root_node(geoms, nodecapacity=nodecapacity)
16+
return new{typeof(rootnode)}(rootnode)
17+
end
18+
end
19+
20+
21+
struct STRNode{E,T}
22+
extent::E
23+
children::T
24+
end
25+
26+
27+
struct STRLeafNode{E}
28+
extents::E
29+
indices::Vector{Int}
30+
end
31+
32+
33+
GI.extent(n::STRNode) = n.extent
34+
GI.extent(n::STRLeafNode) = foldl(Extents.union, n.extents)
35+
36+
37+
function leafnodes(geoms; nodecapacity=10)
38+
extents_indices = [(GI.extent(geoms[i]), i) for i in eachindex(geoms)]
39+
perm = sortperm(extents_indices; by=(v -> ((v[1][1][1] + v[1][1][2]) / 2))) # [extent/index][dim][min/max] sort by x
40+
sorted_extents = extents_indices[perm]
41+
r = length(sorted_extents)
42+
P = ceil(Int, r / nodecapacity)
43+
S = ceil(Int, sqrt(P))
44+
x_splits = Iterators.partition(sorted_extents, S * nodecapacity)
45+
46+
nodes = STRLeafNode{Vector{typeof(extents_indices[1][1])}}[]
47+
for x_split in x_splits
48+
perm = sortperm(x_split; by=(v -> ((v[1][2][1] + v[1][2][2]) / 2))) # [extent/index][dim][min/max] sort by y
49+
sorted_split = x_split[perm]
50+
y_splits = Iterators.partition(sorted_split, nodecapacity)
51+
for y_split in y_splits
52+
push!(nodes, STRLeafNode(getindex.(y_split,1), getindex.(y_split,2)))
53+
end
54+
end
55+
return nodes
56+
end
57+
58+
59+
# a bit of duplication...
60+
function parentnodes(nodes; nodecapacity=10)
61+
extents_indices = [(GI.extent(node), node) for node in nodes]
62+
perm = sortperm(extents_indices; by=(v -> ((v[1][1][1] + v[1][1][2]) / 2))) # [extent/node][dim][min/max] sort by x
63+
sorted_extents = extents_indices[perm]
64+
r = length(sorted_extents)
65+
P = ceil(Int, r / nodecapacity)
66+
S = ceil(Int, sqrt(P))
67+
x_splits = Iterators.partition(sorted_extents, S * nodecapacity)
68+
69+
T = typeof(extents_indices[1][1])
70+
N = Vector{typeof(extents_indices[1][2])}
71+
nodes = STRNode{T, N}[]
72+
for x_split in x_splits
73+
perm = sortperm(x_split; by=(v -> ((v[1][2][1] + v[1][2][2]) / 2))) # [extent/index][dim][min/max] sort by y
74+
sorted_split = x_split[perm]
75+
y_splits = Iterators.partition(sorted_split, nodecapacity)
76+
for y_split in y_splits
77+
push!(nodes, STRNode(foldl(Extents.union, getindex.(y_split,1)), getindex.(y_split,2)))
78+
end
79+
end
80+
return nodes
81+
end
82+
83+
84+
"""recursively build root node from geometries and node capacity"""
85+
function build_root_node(geoms; nodecapacity=10)
86+
nodes = leafnodes(geoms, nodecapacity=nodecapacity)
87+
while length(nodes) > 1
88+
nodes = parentnodes(nodes, nodecapacity=nodecapacity)
89+
end
90+
return nodes[1]
91+
end
92+
93+
94+
"""
95+
query(tree::STRtree, extent::Extent)
96+
query(tree::STRtree, geom)
97+
98+
Query the tree for geometries whose extent intersects with the given extent or the extent of the given geometry.
99+
Returns a vector of indices of the geometries that can be used to index into the original collection of geometries under the assumption that the collection has not been modified since the tree was built.
100+
"""
101+
function query end
102+
103+
function query(tree::STRtree, extent::Extent)
104+
query_result = Int[]
105+
query!(query_result, tree.rootnode, extent)
106+
return unique(sort!(query_result))
107+
end
108+
109+
query(tree::STRtree, geom) = query(tree, GI.extent(geom))
110+
111+
"""recursively query the nodes until a leaf node is reached"""
112+
function query!(query_result::Vector{Int}, node::STRNode, extent::Extent)
113+
if Extents.intersects(node.extent, extent)
114+
for child in node.children
115+
query!(query_result, child, extent)
116+
end
117+
end
118+
return query_result
119+
end
120+
121+
"""when leaf node is reached, push indices of geometries to query result"""
122+
function query!(query_result::Vector{Int}, node::STRLeafNode, extent::Extent)
123+
for i in eachindex(node.extents)
124+
if Extents.intersects(node.extents[i], extent)
125+
push!(query_result, node.indices[i])
126+
end
127+
end
128+
end
129+
130+
131+
export STRtree, query
132+
133+
end

src/STRtree.jl

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/Project.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[deps]
2+
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
3+
Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
4+
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
5+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

test/runtests.jl

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1-
using STRtree
1+
using GISTRtree
22
using Test
3+
using Extents
4+
import ArchGDAL as AG
5+
import GeoInterface as GI
36

4-
@testset "STRtree.jl" begin
5-
# Write your tests here.
7+
8+
@testset "GISTRtree.jl" begin
9+
x = 1:100
10+
y = 1:100
11+
points = AG.createpoint.(x, y')
12+
# polygons = AG.buffer.(points, 0.1)
13+
tree = STRtree(points)
14+
@test tree.rootnode isa GISTRtree.STRNode
15+
@test tree.rootnode.children[1] isa GISTRtree.STRNode
16+
17+
query_result = query(tree, Extent(X=(0, 100.5), Y=(0, 1.5)))
18+
@test query_result isa Vector{Int}
19+
@test length(query_result) == 100
20+
@test points[query_result] == points[:,1]
621
end

0 commit comments

Comments
 (0)