-
Notifications
You must be signed in to change notification settings - Fork 6
Tree based acceleration for polygon clipping / boolean ops #274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
asinghvi17
wants to merge
18
commits into
main
Choose a base branch
from
as/trees
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
9168b81
Add AbstractTrees + constrain STR to performant versions
asinghvi17 f03e650
include LoopStateMachine and STI
asinghvi17 c424789
Implement natural indexing from `tg`
asinghvi17 182bb5c
Implement `foreach_pair_of_maybe_intersecting_edges_in_order`
asinghvi17 e10351e
Test all single accelerators in poly clipping tests
asinghvi17 2077adf
Move imports to main file
asinghvi17 17b1487
Add comments to naturalindexing and clean up
asinghvi17 c6fc596
Adapt to refactor of SpatialTreeInterface
asinghvi17 01c8def
add `isspatialtree` to NaturalIndexing
asinghvi17 f31a6da
add tests for NaturalIndex in SpatialTreeInterface tests
asinghvi17 646619e
Rename NaturalTree -> NaturalIndex and fix typos everywhere
asinghvi17 6b5e087
Fix minor stupidity in singlestr implementation
asinghvi17 aea8ed1
make it easier to tell where errors are happening
asinghvi17 23dedec
Fix doc build error
asinghvi17 ac75de8
fix multiple type declarations
asinghvi17 8663700
Merge remote-tracking branch 'origin/as/trees' into as/trees
asinghvi17 5600628
Merge branch 'main' into as/trees
asinghvi17 f4a1077
fix dual type declarations, again
asinghvi17 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
module NaturalIndexing | ||
|
||
import GeoInterface as GI | ||
import Extents | ||
|
||
using ..SpatialTreeInterface | ||
|
||
import ..GeometryOps as GO # TODO: only needed for NaturallyIndexedRing, remove when that is removed. | ||
|
||
export NaturalIndex, NaturallyIndexedRing, prepare_naturally | ||
|
||
""" | ||
NaturalLevel{E <: Extents.Extent} | ||
|
||
A level in the natural tree. Stored in a vector in [`NaturalIndex`](@ref). | ||
|
||
- `extents` is a vector of extents of the children of the node | ||
""" | ||
struct NaturalLevel{E <: Extents.Extent} | ||
extents::Vector{E} # child extents | ||
end | ||
|
||
Base.show(io::IO, level::NaturalLevel) = print(io, "NaturalLevel($(length(level.extents)) extents)") | ||
Base.show(io::IO, ::MIME"text/plain", level::NaturalLevel) = Base.show(io, level) | ||
|
||
""" | ||
NaturalIndex{E <: Extents.Extent} | ||
|
||
A natural tree index. Stored in a vector in [`NaturalIndex`](@ref). | ||
|
||
- `nodecapacity` is the "spread", number of children per node | ||
- `extent` is the extent of the tree | ||
- `levels` is a vector of [`NaturalLevel`](@ref)s | ||
""" | ||
struct NaturalIndex{E <: Extents.Extent} | ||
nodecapacity::Int # "spread", number of children per node | ||
extent::E | ||
levels::Vector{NaturalLevel{E}} | ||
end | ||
|
||
Extents.extent(idx::NaturalIndex) = idx.extent | ||
|
||
function Base.show(io::IO, ::MIME"text/plain", idx::NaturalIndex) | ||
println(io, "NaturalIndex with $(length(idx.levels)) levels and $(idx.nodecapacity) children per node") | ||
println(io, "extent: $(idx.extent)") | ||
end | ||
function Base.show(io::IO, idx::NaturalIndex) | ||
println(io, "NaturalIndex($(length(idx.levels)) levels, $(idx.extent))") | ||
end | ||
|
||
function NaturalIndex(geoms; nodecapacity = 32) | ||
# Get the extent type initially (coord order, coord type, etc.) | ||
# so that the construction is type stable. | ||
e1 = GI.extent(first(geoms)) | ||
E = typeof(e1) | ||
return NaturalIndex{E}(geoms; nodecapacity = nodecapacity) | ||
end | ||
function NaturalIndex(last_level_extents::Vector{E}; nodecapacity = 32) where E <: Extents.Extent | ||
# If we are passed a vector of extents - inflate immediately! | ||
return NaturalIndex{E}(last_level_extents; nodecapacity = nodecapacity) | ||
end | ||
|
||
function NaturalIndex{E}(geoms; nodecapacity = 32) where E <: Extents.Extent | ||
# If passed a vector of geometries, and we know the type of the extent, | ||
# then simply retrieve the extents so they can serve as the "last-level" | ||
# extents. | ||
# Finally, call the lowest level method that performs inflation. | ||
last_level_extents = GI.extent.(geoms) | ||
return NaturalIndex{E}(last_level_extents; nodecapacity = nodecapacity) | ||
end | ||
# This is the main constructor that performs inflation. | ||
function NaturalIndex{E}(last_level_extents::Vector{E}; nodecapacity = 32) where E <: Extents.Extent | ||
ngeoms = length(last_level_extents) | ||
last_level = NaturalLevel(last_level_extents) | ||
|
||
nlevels = _number_of_levels(nodecapacity, ngeoms) | ||
|
||
levels = Vector{NaturalLevel{E}}(undef, nlevels) | ||
levels[end] = last_level | ||
# Iterate backwards, from bottom to top level, | ||
# and build up the level extent vectors. | ||
for level_index in (nlevels-1):(-1):1 | ||
prev_level = levels[level_index+1] # this is always instantiated, since we are iterating backwards | ||
nrects = _number_of_keys(nodecapacity, nlevels - (level_index), ngeoms) | ||
extents = [ | ||
begin | ||
start = (rect_index - 1) * nodecapacity + 1 | ||
stop = min(start + nodecapacity - 1, length(prev_level.extents)) | ||
reduce(Extents.union, view(prev_level.extents, start:stop)) | ||
end | ||
for rect_index in 1:nrects | ||
] | ||
levels[level_index] = NaturalLevel(extents) | ||
end | ||
|
||
return NaturalIndex(nodecapacity, reduce(Extents.union, levels[1].extents), levels) | ||
|
||
end | ||
|
||
function _number_of_keys(nodecapacity::Int, level::Int, ngeoms::Int) | ||
return ceil(Int, ngeoms / (nodecapacity ^ (level))) | ||
end | ||
|
||
""" | ||
_number_of_levels(nodecapacity::Int, ngeoms::Int) | ||
|
||
Calculate the number of levels in a natural tree for a given number of geometries and node capacity. | ||
|
||
## How this works | ||
|
||
The number of keys in a level is given by `ngeoms / nodecapacity ^ level`. | ||
|
||
The number of levels is the smallest integer such that the number of keys in the last level is 1. | ||
So it goes - if that makes sense. | ||
""" | ||
function _number_of_levels(nodecapacity::Int, ngeoms::Int) | ||
level = 1 | ||
while _number_of_keys(nodecapacity, level, ngeoms) > 1 | ||
level += 1 | ||
end | ||
return level | ||
end | ||
|
||
|
||
# This is like a pointer to a node in the tree. | ||
""" | ||
NaturalIndexNode{E <: Extents.Extent} | ||
|
||
A reference to a node in the natural tree. Kind of like a tree cursor. | ||
|
||
- `parent_index` is a pointer to the parent index | ||
- `level` is the level of the node in the tree | ||
- `index` is the index of the node in the level | ||
- `extent` is the extent of the node | ||
""" | ||
struct NaturalIndexNode{E <: Extents.Extent} | ||
parent_index::NaturalIndex{E} | ||
level::Int | ||
index::Int | ||
extent::E | ||
end | ||
|
||
Extents.extent(node::NaturalIndexNode) = node.extent | ||
|
||
# What does SpatialTreeInterface require of trees? | ||
# - Parents completely cover their children | ||
# - `GI.extent(node)` returns `Extent` | ||
# - can mean that `Extents.extent(node)` returns the extent of the node | ||
# - `nchild(node)` returns the number of children of the node | ||
# - `getchild(node)` returns an iterator over all children of the node | ||
# - `getchild(node, i)` returns the i-th child of the node | ||
# - `isleaf(node)` returns a boolean indicating whether the node is a leaf | ||
# - `child_indices_extents(node)` returns an iterator over the indices and extents of the children of the node | ||
|
||
SpatialTreeInterface.isspatialtree(::Type{<: NaturalIndex}) = true | ||
SpatialTreeInterface.isspatialtree(::Type{<: NaturalIndexNode}) = true | ||
|
||
function SpatialTreeInterface.nchild(node::NaturalIndexNode) | ||
start_idx = (node.index - 1) * node.parent_index.nodecapacity + 1 | ||
stop_idx = min(start_idx + node.parent_index.nodecapacity - 1, length(node.parent_index.levels[node.level+1].extents)) | ||
return stop_idx - start_idx + 1 | ||
end | ||
|
||
function SpatialTreeInterface.getchild(node::NaturalIndexNode, i::Int) | ||
child_index = (node.index - 1) * node.parent_index.nodecapacity + i | ||
return NaturalIndexNode( | ||
node.parent_index, | ||
node.level + 1, # increment level by 1 | ||
child_index, # index of this particular child | ||
node.parent_index.levels[node.level+1].extents[child_index] # the extent of this child | ||
) | ||
end | ||
|
||
# Get all children of a node | ||
function SpatialTreeInterface.getchild(node::NaturalIndexNode) | ||
return (SpatialTreeInterface.getchild(node, i) for i in 1:SpatialTreeInterface.nchild(node)) | ||
end | ||
|
||
SpatialTreeInterface.isleaf(node::NaturalIndexNode) = node.level == length(node.parent_index.levels) - 1 | ||
|
||
function SpatialTreeInterface.child_indices_extents(node::NaturalIndexNode) | ||
start_idx = (node.index - 1) * node.parent_index.nodecapacity + 1 | ||
stop_idx = min(start_idx + node.parent_index.nodecapacity - 1, length(node.parent_index.levels[node.level+1].extents)) | ||
return ((i, node.parent_index.levels[node.level+1].extents[i]) for i in start_idx:stop_idx) | ||
end | ||
|
||
# implementation for "root node" / top level tree | ||
|
||
SpatialTreeInterface.isleaf(node::NaturalIndex) = length(node.levels) == 1 | ||
|
||
SpatialTreeInterface.nchild(node::NaturalIndex) = length(node.levels[1].extents) | ||
|
||
SpatialTreeInterface.getchild(node::NaturalIndex) = SpatialTreeInterface.getchild(NaturalIndexNode(node, 0, 1, node.extent)) | ||
SpatialTreeInterface.getchild(node::NaturalIndex, i) = SpatialTreeInterface.getchild(NaturalIndexNode(node, 0, 1, node.extent), i) | ||
|
||
SpatialTreeInterface.child_indices_extents(node::NaturalIndex) = (i_ext for i_ext in enumerate(node.levels[1].extents)) | ||
|
||
""" | ||
NaturallyIndexedRing(points; nodecapacity = 32) | ||
|
||
A linear ring that contains a natural index. | ||
|
||
!!! warning | ||
This will be removed in favour of prepared geometry - the idea here | ||
is just to test what interface works best to store things in. | ||
""" | ||
struct NaturallyIndexedRing | ||
points::Vector{Tuple{Float64, Float64}} | ||
index::NaturalIndex{Extents.Extent{(:X, :Y), NTuple{2, NTuple{2, Float64}}}} | ||
end | ||
|
||
asinghvi17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
function NaturallyIndexedRing(points::Vector{Tuple{Float64, Float64}}; nodecapacity = 32) | ||
index = NaturalIndex(GO.edge_extents(GI.LinearRing(points)); nodecapacity) | ||
return NaturallyIndexedRing(points, index) | ||
end | ||
NaturallyIndexedRing(ring::NaturallyIndexedRing) = ring | ||
|
||
function GI.convert(::Type{NaturallyIndexedRing}, ::GI.LinearRingTrait, geom) | ||
points = GO.tuples(geom).geom | ||
return NaturallyIndexedRing(points) | ||
end | ||
|
||
Base.show(io::IO, ::MIME"text/plain", ring::NaturallyIndexedRing) = Base.show(io, ring) | ||
Base.show(io::IO, ring::NaturallyIndexedRing) = print(io, "NaturallyIndexedRing($(length(ring.points)) points) with index $(sprint(show, ring.index))") | ||
|
||
GI.ncoord(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = 2 | ||
GI.is3d(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = false | ||
GI.ismeasured(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = false | ||
|
||
GI.ngeom(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = length(ring.points) | ||
GI.getgeom(::GI.LinearRingTrait, ring::NaturallyIndexedRing) = ring.points | ||
GI.getgeom(::GI.LinearRingTrait, ring::NaturallyIndexedRing, i::Int) = ring.points[i] | ||
|
||
Extents.extent(ring::NaturallyIndexedRing) = ring.index.extent | ||
|
||
GI.isgeometry(::Type{<: NaturallyIndexedRing}) = true | ||
GI.geomtrait(::NaturallyIndexedRing) = GI.LinearRingTrait() | ||
|
||
function prepare_naturally(geom) | ||
return GO.apply(GI.PolygonTrait(), geom) do poly | ||
return GI.Polygon([GI.convert(NaturallyIndexedRing, GI.LinearRingTrait(), ring) for ring in GI.getring(poly)]) | ||
end | ||
end | ||
|
||
end # module NaturalIndexing |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.