|
| 1 | +abstract type PseudoAbsenceGenerator end |
| 2 | + |
| 3 | +struct WithinRadius <: PseudoAbsenceGenerator |
| 4 | +end |
| 5 | + |
| 6 | +struct SurfaceRangeEnvelope <: PseudoAbsenceGenerator |
| 7 | +end |
| 8 | + |
| 9 | +struct RandomSelection <: PseudoAbsenceGenerator |
| 10 | +end |
| 11 | + |
| 12 | +function _random_point(ref, d; R = 6371.0) |
| 13 | + # Convert the coordinates from degrees to radians |
| 14 | + λ, φ = deg2rad.(ref) |
| 15 | + # Get the angular distance |
| 16 | + δ = d / R |
| 17 | + # Pick a random bearing (angle w.r.t. true North) |
| 18 | + α = deg2rad(rand() * 360.0) |
| 19 | + # Get the new latitude |
| 20 | + φ2 = asin(sin(φ) * cos(δ) + cos(φ) * sin(δ) * cos(α)) |
| 21 | + # Get the new longitude |
| 22 | + λ2 = λ + atan(sin(α) * sin(δ) * cos(φ), cos(δ) - sin(φ) * sin(φ2)) |
| 23 | + # Return the coordinates in degree |
| 24 | + return rad2deg.((λ2, φ2)) |
| 25 | +end |
| 26 | + |
| 27 | +function _invalid_pseudoabsence(pt, msk) |
| 28 | + isnothing(msk[pt...]) && return true |
| 29 | + msk[pt...] && return true |
| 30 | + return false |
| 31 | +end |
| 32 | + |
| 33 | +function _layer_works_for_pseudoabsence(layer::T) where {T <: SimpleSDMLayer} |
| 34 | + @assert SimpleSDMLayers._inner_type(layer) <: Bool |
| 35 | + n_occ = sum(layer) |
| 36 | + return iszero(n_occ) && throw(ArgumentError("The presences layer is empty")) |
| 37 | +end |
| 38 | + |
| 39 | +function _return_point_as_grid(pt, layer) |
| 40 | + cart = SimpleSDMLayers._point_to_cartesian(layer, Point(pt...)) |
| 41 | + rtpt = Point((longitudes(layer)[cart[2]], latitudes(layer)[cart[1]])) |
| 42 | + return rtpt |
| 43 | +end |
| 44 | + |
| 45 | +""" |
| 46 | + pseudoabsence |
| 47 | +
|
| 48 | +This method generates a possible pseudo-absence location around known observations in a Boolean layer. The observations are generated in a radius expressed in *kilometers* around the known presences. To generate many background points, call this method multiple times. |
| 49 | +""" |
| 50 | +function pseudoabsence( |
| 51 | + ::Type{WithinRadius}, |
| 52 | + presences::T; |
| 53 | + distance::Number = 100.0, |
| 54 | +) where {T <: SimpleSDMLayer} |
| 55 | + _layer_works_for_pseudoabsence(presences) |
| 56 | + k = rand(keys(presences)) |
| 57 | + newpt = _random_point(k, sqrt(rand()) * distance) |
| 58 | + while _invalid_pseudoabsence(newpt, presences) |
| 59 | + newpt = _random_point(k, sqrt(rand()) * distance) |
| 60 | + end |
| 61 | + return _return_point_as_grid(newpt, presences) |
| 62 | +end |
| 63 | + |
| 64 | +function pseudoabsence( |
| 65 | + ::Type{SurfaceRangeEnvelope}, |
| 66 | + presences::T, |
| 67 | +) where {T <: SimpleSDMLayer} |
| 68 | + _layer_works_for_pseudoabsence(presences) |
| 69 | + bbox = SpeciesDistributionToolkit.boundingbox(replace(presences, false => nothing)) |
| 70 | + londist = Distributions.Uniform(bbox.left, bbox.right) |
| 71 | + latdist = Distributions.Uniform(bbox.bottom, bbox.top) |
| 72 | + newpt = (rand(londist), rand(latdist)) |
| 73 | + while _invalid_pseudoabsence(newpt, presences) |
| 74 | + newpt = (rand(londist), rand(latdist)) |
| 75 | + end |
| 76 | + return _return_point_as_grid(newpt, presences) |
| 77 | +end |
| 78 | + |
| 79 | +function pseudoabsence( |
| 80 | + ::Type{RandomSelection}, |
| 81 | + presences::T, |
| 82 | +) where {T <: SimpleSDMLayer} |
| 83 | + _layer_works_for_pseudoabsence(presences) |
| 84 | + bbox = SpeciesDistributionToolkit.boundingbox(presences) |
| 85 | + londist = Distributions.Uniform(bbox.left, bbox.right) |
| 86 | + latdist = Distributions.Uniform(bbox.bottom, bbox.top) |
| 87 | + newpt = (rand(londist), rand(latdist)) |
| 88 | + while _invalid_pseudoabsence(newpt, presences) |
| 89 | + newpt = (rand(londist), rand(latdist)) |
| 90 | + end |
| 91 | + return _return_point_as_grid(newpt, presences) |
| 92 | +end |
0 commit comments