Skip to content
This repository has been archived by the owner on Dec 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #50 from EcoJulia/tpoisot/issue48
Browse files Browse the repository at this point in the history
Various quality of life improvements
  • Loading branch information
tpoisot authored Feb 10, 2021
2 parents 1a41008 + aa83d19 commit ed1eef5
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "SimpleSDMLayers"
uuid = "2c645270-77db-11e9-22c3-0f302a89c64c"
authors = ["Timothée Poisot <[email protected]>", "Gabriel Dansereau <[email protected]>"]
version = "0.3.5"
version = "0.3.6"

[deps]
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
Expand Down
1 change: 1 addition & 0 deletions docs/src/man/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ slidingwindow
latitudes
longitudes
clip
mask
```
1 change: 1 addition & 0 deletions docs/src/man/overloads.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ directly, and also allow to set and get values using the geographic coordinates
```@docs
convert
copy
collect
eltype
size
stride
Expand Down
3 changes: 2 additions & 1 deletion src/SimpleSDMLayers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export landcover

include(joinpath("operations", "coarsen.jl"))
include(joinpath("operations", "sliding.jl"))
export coarsen, slidingwindow
include(joinpath("operations", "mask.jl"))
export coarsen, slidingwindow, mask

include(joinpath("recipes", "recipes.jl"))

Expand Down
2 changes: 1 addition & 1 deletion src/datasets/geotiff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function geotiff(
end

buffer = convert(Matrix{Union{Nothing,eltype(buffer)}}, rotl90(buffer))
buffer[findall(buffer .== minimum(buffer))] .= nothing
replace!(buffer, minimum(buffer) => nothing)

return LT(buffer, left_pos-0.5lon_stride, right_pos+0.5lon_stride, bottom_pos-0.5lat_stride, top_pos+0.5lat_stride)

Expand Down
62 changes: 62 additions & 0 deletions src/lib/overloads.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import Base: similar
import Base: copy
import Base: eltype
import Base: convert
import Base: collect
import Base.Broadcast: broadcast
import Base: hcat
import Base: vcat

"""
Base.convert(::Type{SimpleSDMResponse}, layer::T) where {T <: SimpleSDMPredictor}
Expand Down Expand Up @@ -245,6 +248,21 @@ function Base.similar(layer::T) where {T <: SimpleSDMLayer}
return SimpleSDMResponse(emptygrid, layer.left, layer.right, layer.bottom, layer.top)
end

"""
Base.similar(::Type{TC}, l::T) where {TC <: Any, T <: SimpleSDMLayer}
Returns a `SimpleSDMResponse` of the same dimensions as the original layer, with
`nothing` in the same positions. The rest of the values are replaced by the
output of `zero(TC)`, which implies that there must be a way to get a zero for
the type. If not, the same result can always be achieved through the use of
`copy`, manual update, and `convert`.
"""
function Base.similar(::Type{TC}, layer::T) where {TC <: Any, T <: SimpleSDMLayer}
emptygrid = convert(Matrix{Union{Nothing,TC}}, zeros(TC, size(layer)))
emptygrid[findall(isnothing, layer.grid)] .= nothing
return SimpleSDMResponse(emptygrid, layer.left, layer.right, layer.bottom, layer.top)
end

"""
Base.copy(l::T) where {T <: SimpleSDMLayer}
Expand Down Expand Up @@ -273,3 +291,47 @@ function Base.Broadcast.broadcast(f, L::LT) where {LT <: SimpleSDMLayer}
RT = LT <: SimpleSDMResponse ? SimpleSDMResponse : SimpleSDMPredictor
return RT(convert(Matrix{Union{internal_types...}}, N.grid), N)
end

"""
Base.collect(l::T) where {T <: SimpleSDMLayer}
Returns the non-`nothing` values of a layer.
"""
function Base.collect(l::T) where {T <: SimpleSDMLayer}
v = filter(!isnothing, l.grid)
return convert(Vector{eltype(l)}, v)
end

"""
Base.vcat(l1::T, l2::T) where {T <: SimpleSDMLayers}
Adds the second layer *under* the first one, assuming the strides and left/right
coordinates match. This will automatically re-order the layers if the second is
above the first.
"""
function Base.vcat(l1::T, l2::T) where {T <: SimpleSDMLayer}
(l1.left == l2.left) || throw(ArgumentError("The two layers passed to vcat must have the same left coordinate"))
(l1.right == l2.right) || throw(ArgumentError("The two layers passed to vcat must have the same right coordinate"))
all(stride(l1) .≈ stride(l2)) || throw(ArgumentError("The two layers passed to vcat must have the same stride"))
(l2.top == l1.bottom) && return vcat(l2, l1)
new_grid = vcat(l1.grid, l2.grid)
RT = T <: SimpleSDMPredictor ? SimpleSDMPredictor : SimpleSDMResponse
return RT(new_grid, l1.left, l1.right, l1.top, l2.bottom)
end

"""
Base.hcat(l1::T, l2::T) where {T <: SimpleSDMLayers}
Adds the second layer *to the right of* the first one, assuming the strides and
left/right coordinates match. This will automatically re-order the layers if the
second is to the left the first.
"""
function Base.hcat(l1::T, l2::T) where {T <: SimpleSDMLayer}
(l1.top == l2.top) || throw(ArgumentError("The two layers passed to hcat must have the same top coordinate"))
(l1.bottom == l2.bottom) || throw(ArgumentError("The two layers passed to hcat must have the same bottom coordinate"))
all(stride(l1) .≈ stride(l2)) || throw(ArgumentError("The two layers passed to hcat must have the same stride"))
(l2.right == l1.left) && return hcat(l2, l1)
new_grid = hcat(l1.grid, l2.grid)
RT = T <: SimpleSDMPredictor ? SimpleSDMPredictor : SimpleSDMResponse
return RT(new_grid, l1.left, l2.right, l1.top, l1.bottom)
end
29 changes: 29 additions & 0 deletions src/operations/mask.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
_inner_type(l::SimpleSDMResponse{T}) where {T <: Any} = T
_inner_type(l::SimpleSDMPredictor{T}) where {T <: Any} = T

"""
mask!(l1::T1, l2::T2) where {T1 <: SimpleSDMLayer, T2 <: SimpleSDMLayer}
Changes the second layer so that the positions for which the first layer is zero
(of the appropriate type) or `nothing` are set to `nothing`. This is mostly
useful in cases where you have a `Bool` layer.
"""
function mask!(l1::T1, l2::T2) where {T1 <: SimpleSDMLayer, T2 <: SimpleSDMLayer}
_itype = _inner_type(l1)
dropfunc = (x) -> isnothing(x) || (x == zero(_itype))
todrop = findall(dropfunc, l1.grid)
l2.grid[todrop] .= nothing
end

"""
mask(l1::T1, l2::T2) where {T1 <: SimpleSDMLayer, T2 <: SimpleSDMLayer}
Returns a copy of the second layer in which the positions for which the first
layer is zero (of the appropriate type) or `nothing` are set to `nothing`. This
is mostly useful in cases where you have a `Bool` layer.
"""
function mask(l1::T1, l2::T2) where {T1 <: SimpleSDMLayer, T2 <: SimpleSDMLayer}
l3 = copy(l2)
mask!(l1, l3)
return l3
end
20 changes: 10 additions & 10 deletions src/recipes/recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ test 1
seriestype --> :heatmap
if get(plotattributes, :seriestype, :heatmap) in [:heatmap, :contour]
aspect_ratio --> 1
xlims --> (minimum(longitudes(layer)),maximum(longitudes(layer)))
ylims --> (minimum(latitudes(layer)),maximum(latitudes(layer)))
xlims --> extrema(longitudes(layer))
ylims --> extrema(latitudes(layer))
lg = copy(layer.grid)
lg[lg.==nothing] .= NaN
replace!(lg, nothing => NaN)
longitudes(layer), latitudes(layer), lg
elseif get(plotattributes, :seriestype, :histogram) in [:histogram, :density]
filter(!isnothing, layer.grid)
collect(layer)
end
end

"""
test 2
"""
@recipe function plot(l1::FT, l2::ST) where {FT <: SimpleSDMLayer, ST <: SimpleSDMLayer}
seriestype --> :scatter
if get(plotattributes, :seriestype, :scatter) in [:scatter, :histogram2d]
SimpleSDMLayers._layers_are_compatible(l1, l2)
valid_i = filter(i -> !(isnothing(l1[i])|isnothing(l2[i])), eachindex(l1.grid))
l1.grid[valid_i], l2.grid[valid_i]
end
seriestype --> :scatter
if get(plotattributes, :seriestype, :scatter) in [:scatter, :histogram2d]
SimpleSDMLayers._layers_are_compatible(l1, l2)
valid_i = findall(.!isnothing.(l1.grid) .& .!isnothing.(l2.grid))
l1.grid[valid_i], l2.grid[valid_i]
end
end
21 changes: 21 additions & 0 deletions test/overloads.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,25 @@ Y[0.1,0.1] = 4.0
@test Z[0.1,0.1] != Y[0.1,0.1]
@test Z[0.1,0.1] == 0.2

Z = convert(SimpleSDMPredictor, Y)
V = collect(Z)
@test typeof(V) == Vector{eltype(Z)}

# hcat / vcat
l1 = worldclim(1, left=0.0, right=10.0, bottom=0.0, top=10.0)
l2 = worldclim(1, left=0.0, right=10.0, bottom=10.0, top=20.0)
l3 = worldclim(1, left=10.0, right=20.0, bottom=0.0, top=10.0)
l4 = worldclim(1, left=10.0, right=20.0, bottom=10.0, top=20.0)

ml1 = hcat(l1, l3)
vl1 = vcat(l1, l2)
ml2 = hcat(l2, l4)
vl2 = vcat(l3, l4)

@test all(vcat(ml1, ml2).grid == hcat(vl1, vl2).grid)

# typed similar
c2 = similar(Bool, l1)
@test eltype(c2) == Union{Nothing,Bool}

end

2 comments on commit ed1eef5

@tpoisot
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

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/29767

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:

git tag -a v0.3.6 -m "<description of version>" ed1eef5d0673ab81ab8576695c410a18f3551512
git push origin v0.3.6

Please sign in to comment.