-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🩹 SimpleSDMLayers handling of clip with coordinates on cell boundaries (
#153) * 🩹 SimpleSDMLayers version bump * ✅ test for clipping with natural coordinates (#152) * 🐛 no use of stride in clip / expand keyword * 🐛 Update the stitching code to get the right bounding box (#155) * 🐛 Stitch returns the wrong bounding box in the end Fixes #154 * 🟩 test the stitch patch * 🟩 some more tests for the right coordinate * 🟩 removed a test where the layer type changes * ➖ drop dependencies for SimpleSDMLayers test * 🟩 tests with the expand keyword * GBIF throws 404 (not 410) for deleted resources (#156) * 🤢 GBIF throws 404 (not 410) for deleted resources * 🐛 throws an error when the occurrence is not available * ✅ 404 error on single occurrence is handled * 🟥 remove the subsetting test (handled by clip) * 🐛 fix clipping with natural coordinates * 🐛 fix approximation in displayed coordinate * 🐛 tests when clipping exactly on the limit * ✅ add test comparing clip with gdalwarp * 🐛 missing end for test module --------- Co-authored-by: Gabriel Dansereau <gabriel.dansereau@umontreal.ca>
- Loading branch information
1 parent
f5e475e
commit bbaae1d
Showing
9 changed files
with
210 additions
and
68 deletions.
There are no files selected for viewing
This file contains 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 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 |
---|---|---|
@@ -1,62 +1,142 @@ | ||
""" | ||
clip(layer::T, p1::Point, p2::Point) where {T <: SimpleSDMLayer} | ||
clip(layer::T, p1::Point, p2::Point; expand=Symbol[]) where {T <: SimpleSDMLayer} | ||
Return a raster by defining a bounding box through two points. The order of the | ||
points (in terms of bottom/top/left/right) is not really important, as the | ||
correct coordinates will be extracted. | ||
**This method is the one that other versions of `clip` ultimately end up | ||
calling**. | ||
This operation takes an additional keyword argument `expand`, which is a vector | ||
of `Symbol`s, the symbols being any combination of `:left`, `:right`, `:bottom`, | ||
and `:top`. This argument specifies, in the case where the coordinate to clip is | ||
at the limit between two cells in a raster, whether the outer neighboring | ||
row/column should be included. This defaults to `expand=Symbol[]`, *i.e.* the | ||
outer neighboring cell will *not* be included. | ||
""" | ||
function clip(layer::T, p1::Point, p2::Point) where {T <: SimpleSDMLayer} | ||
latextr = extrema([p1[2], p2[2]]) | ||
lonextr = extrema([p1[1], p2[1]]) | ||
pmin = _point_to_cartesian(layer, Point(minimum(lonextr), minimum(latextr)); side=:bottomleft) | ||
pmax = _point_to_cartesian(layer, Point(maximum(lonextr), maximum(latextr)); side=:topright) | ||
R = T <: SimpleSDMResponse ? SimpleSDMResponse : SimpleSDMPredictor | ||
left = longitudes(layer)[last(pmin.I)]-stride(layer, 1) | ||
right = longitudes(layer)[last(pmax.I)]+stride(layer, 1) | ||
bottom = latitudes(layer)[first(pmin.I)]-stride(layer, 2) | ||
top = latitudes(layer)[first(pmax.I)]+stride(layer, 2) | ||
return R( | ||
layer.grid[pmin:pmax], | ||
left = isapprox(left, round(left)) ? round(left) : left, | ||
right = isapprox(right, round(right)) ? round(right) : right, | ||
bottom = isapprox(bottom, round(bottom)) ? round(bottom) : bottom, | ||
top = isapprox(top, round(top)) ? round(top) : top | ||
function clip( | ||
layer::T, | ||
p1::Point, | ||
p2::Point; | ||
expand = Symbol[], | ||
) where {T <: SimpleSDMLayer} | ||
# Find the new bounding box for the clipped layer | ||
bottom, top = extrema([p1[2], p2[2]]) | ||
left, right = extrema([p1[1], p2[1]]) | ||
|
||
# Get the boundaries between pixels | ||
lon_boundaries = LinRange(layer.left, layer.right, size(layer, 2) + 1) | ||
lat_boundaries = LinRange(layer.bottom, layer.top, size(layer, 1) + 1) | ||
|
||
# Left to cell coordinate | ||
left_cell = findlast(isapprox.(lon_boundaries, left) .|| lon_boundaries .<= left) | ||
if left == lon_boundaries[left_cell] | ||
if :left in expand | ||
left_cell = max(1, left_cell - 1) | ||
end | ||
end | ||
|
||
# Right to cell coordinate | ||
right_cell = findfirst(isapprox.(lon_boundaries, right) .||lon_boundaries .>= right) | ||
if right == lon_boundaries[right_cell] | ||
if :right in expand | ||
right_cell = min(size(layer, 2) + 1, right_cell + 1) | ||
end | ||
end | ||
|
||
# Bottom to cell coordinate | ||
bottom_cell = findlast(isapprox.(lat_boundaries, bottom) .|| lat_boundaries .<= bottom) | ||
if bottom == lat_boundaries[bottom_cell] | ||
if :bottom in expand | ||
bottom_cell = max(1, bottom_cell - 1) | ||
end | ||
end | ||
|
||
# Top to cell coordinate | ||
top_cell = findfirst(isapprox.(lat_boundaries, top) .|| lat_boundaries .>= top) | ||
if top == lat_boundaries[top_cell] | ||
if :top in expand | ||
top_cell = min(size(layer, 1) + 1, top_cell + 1) | ||
end | ||
end | ||
|
||
# New grid | ||
newgrid = layer.grid[bottom_cell:(top_cell - 1), left_cell:(right_cell - 1)] | ||
|
||
# New return type | ||
RT = typeof(layer) <: SimpleSDMResponse ? SimpleSDMResponse : SimpleSDMPredictor | ||
|
||
# Return with the correct bounding box | ||
return RT( | ||
newgrid; | ||
left = isapprox(left, lon_boundaries[left_cell]) ? left : lon_boundaries[left_cell], | ||
right = isapprox(right, lon_boundaries[right_cell]) ? right : lon_boundaries[right_cell], | ||
bottom = isapprox(bottom, lat_boundaries[bottom_cell]) ? bottom : lat_boundaries[bottom_cell], | ||
top = isapprox(top, lat_boundaries[top_cell]) ? top : lat_boundaries[top_cell], | ||
) | ||
end | ||
|
||
""" | ||
clip(layer::T; left=nothing, right=nothing, top=nothing, bottom=nothing) where {T <: SimpleSDMLayer} | ||
clip(layer::T; left=nothing, right=nothing, top=nothing, bottom=nothing, kwargs...) where {T <: SimpleSDMLayer} | ||
Clips a raster by giving the (optional) limites `left`, `right`, `bottom`, and `top`. | ||
Clips a raster by giving the (optional) limits `left`, `right`, `bottom`, and | ||
`top`. | ||
This operation takes an additional keyword argument `expand`, which is a vector | ||
of `Symbol`s, the symbols being any combination of `:left`, `:right`, `:bottom`, | ||
and `:top`. This argument specifies, in the case where the coordinate to clip is | ||
at the limit between two cells in a raster, whether the outer neighboring | ||
row/column should be included. This defaults to `expand=Symbol[]`, *i.e.* the | ||
outer neighboring cell will *not* be included. | ||
""" | ||
function clip(layer::T; left=nothing, right=nothing, top=nothing, bottom=nothing) where {T <: SimpleSDMLayer} | ||
function clip( | ||
layer::T; | ||
left = nothing, | ||
right = nothing, | ||
top = nothing, | ||
bottom = nothing, | ||
kwargs..., | ||
) where {T <: SimpleSDMLayer} | ||
p1 = Point( | ||
isnothing(left) ? layer.left : left, | ||
isnothing(bottom) ? layer.bottom : bottom | ||
isnothing(bottom) ? layer.bottom : bottom, | ||
) | ||
p2 = Point( | ||
isnothing(right) ? layer.right : right, | ||
isnothing(top) ? layer.top : top | ||
isnothing(top) ? layer.top : top, | ||
) | ||
return clip(layer, p1, p2) | ||
return clip(layer, p1, p2; kwargs...) | ||
end | ||
|
||
""" | ||
clip(origin::T1, destination::T2) where {T1 <: SimpleSDMLayer, T2 <: SimpleSDMLayer} | ||
clip(origin::T1, destination::T2; kwargs...) where {T1 <: SimpleSDMLayer, T2 <: SimpleSDMLayer} | ||
Clips a layer by another layer, *i.e.* subsets the first layer so that it has | ||
the dimensions of the second layer. This operation applies a very small | ||
displacement to the limits (`5eps()`) to ensure that if the coordinate to cut at | ||
falls exactly on a cell boundary, the correct cell will be used in the return | ||
layer. | ||
the dimensions of the second layer. | ||
This operation takes an additional keyword argument `expand`, which is a vector | ||
of `Symbol`s, the symbols being any combination of `:left`, `:right`, `:bottom`, | ||
and `:top`. This argument specifies, in the case where the coordinate to clip is | ||
at the limit between two cells in a raster, whether the outer neighboring | ||
row/column should be included. This defaults to `expand=Symbol[]`, *i.e.* the | ||
outer neighboring cell will *not* be included. | ||
""" | ||
function clip(origin::T1, destination::T2) where {T1 <: SimpleSDMLayer, T2 <: SimpleSDMLayer} | ||
_d = 5eps() | ||
function clip( | ||
origin::T1, | ||
destination::T2; | ||
kwargs..., | ||
) where {T1 <: SimpleSDMLayer, T2 <: SimpleSDMLayer} | ||
err = false | ||
destination.right > origin.right && (err = true) | ||
destination.left < origin.left && (err = true) | ||
destination.bottom < origin.bottom && (err = true) | ||
destination.top > origin.top && (err = true) | ||
err && throw(ArgumentError("The two layers are not compatible")) | ||
return clip(origin, Point(destination.left+_d, destination.bottom+_d), Point(destination.right-_d, destination.top-_d)) | ||
return clip( | ||
origin, | ||
Point(destination.left, destination.top), | ||
Point(destination.right, destination.bottom); | ||
kwargs..., | ||
) | ||
end |
This file contains 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 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 |
---|---|---|
@@ -1,9 +1,3 @@ | ||
[deps] | ||
GBIF = "ee291a33-5a6c-5552-a3c8-0f29a1181037" | ||
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" | ||
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" | ||
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" | ||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" | ||
|
||
[compat] | ||
GBIF = "0.2, 0.3" |
This file contains 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 was deleted.
Oops, something went wrong.
This file contains 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 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,43 @@ | ||
module TestThatStitchIsNotInAMood | ||
|
||
# The stitching sometimes gave the wrong bounding box after using `map`, so this | ||
# checks this doesn't happen anymore | ||
|
||
using SpeciesDistributionToolkit | ||
using Test | ||
|
||
# Get some data | ||
dataprovider = RasterData(EarthEnv, LandCover) | ||
spatial_extent = (left = -80.00, bottom = 43.19, right = -70.94, top = 46.93) | ||
trees = sum([ | ||
SimpleSDMPredictor(dataprovider; layer = i, full = true, spatial_extent...) for | ||
i in 1:4 | ||
]) | ||
|
||
for tilesize in [(3, 4), (8, 8), (4, 3), (9, 5), (2, 2), (12, 1), (3, 6)] | ||
|
||
# make the tiles | ||
tiles = tile(trees, tilesize) | ||
@test eltype(tiles) == typeof(trees) | ||
|
||
# stitch the tiles as they are | ||
tiled_trees = stitch(tiles) | ||
@test typeof(tiled_trees) == typeof(trees) | ||
@test size(tiled_trees) == size(trees) | ||
@test tiled_trees.grid == trees.grid | ||
@test tiled_trees.left == trees.left | ||
@test tiled_trees.right == trees.right | ||
@test tiled_trees.bottom == trees.bottom | ||
@test tiled_trees.top == trees.top | ||
|
||
# stitch the tiles after a transform | ||
more_trees = stitch(map(x -> 2x, tiles)) | ||
@test size(more_trees) == size(trees) | ||
@test more_trees.left == trees.left | ||
@test more_trees.right == trees.right | ||
@test more_trees.bottom == trees.bottom | ||
@test more_trees.top == trees.top | ||
|
||
end | ||
|
||
end |
This file contains 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
bbaae1d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JuliaRegistrator register subdir=SimpleSDMLayers
bbaae1d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Registration pull request created: JuliaRegistries/General/78219
After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.
This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via: