Skip to content

Commit

Permalink
feat(gbif): enumerated values are read from the API
Browse files Browse the repository at this point in the history
* πŸ“– docstrings

* Additional docstrings

* πŸ“– RandomSelection docstring

* 🧹 format some stuff

* πŸ“– update the pseudo absences docstrings

* chore: update the documentation for the layer/dataset interface

* yesh

* release(gbif): 0.5.0

* feat(gbif): enum types are read from the API

* compat(fauxcurrences): use ^0.4 for GBIF

* compat: drop minor release version

* compat: restore to original compat

* doc(gbif): enumerations from the API

* doc(gbif): fix the documented method in man page

* bug: wrong compat entries for top level package
  • Loading branch information
tpoisot committed Apr 23, 2023
1 parent 39a871e commit 5571028
Show file tree
Hide file tree
Showing 15 changed files with 147 additions and 109 deletions.
2 changes: 1 addition & 1 deletion GBIF/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "GBIF"
uuid = "ee291a33-5a6c-5552-a3c8-0f29a1181037"
authors = ["TimothΓ©e Poisot <timothee.poisot@umontreal.ca>"]
version = "0.4.2"
version = "0.4.3"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
131 changes: 50 additions & 81 deletions GBIF/src/GBIF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,90 +6,59 @@ using Dates
using Tables

const gbifurl = "http://api.gbif.org/v1/"

"""
enumerablevalues()
Returns an *array* of values that can be enumerated by the GBIF API.
"""
function enumerablevalues()
endpoint = GBIF.gbifurl * "enumeration/basic"
req = HTTP.get(endpoint)
if isequal(200)(req.status)
response = JSON.parse(String(req.body))
return convert(Vector{String}, response)
end
return nothing
end

# We load the keys that can be enumerated when we load the package
const gbifenumkeys = enumerablevalues()

"""
enumeratedvalues(enumerable::String)
For a given enumerable value (given as a string as reported by the output of the `enumerablevalues` function), this function will return an array of possible values.
"""
function enumeratedvalues(enumerable::String)
if enumerable in GBIF.gbifenumkeys
endpoint = GBIF.gbifurl * "enumeration/basic/$(enumerable)"
req = HTTP.get(endpoint)
if isequal(200)(req.status)
response = JSON.parse(String(req.body))
return convert(Vector{String}, response)
else
throw(ErrorException("Unable to enumerate the value $(enumerable)"))
end
else
throw(
ArgumentError(
"$(enumerable) is not an enumerable entity for GBIF -- see GBIF.enumerablevalues()",
),
)
end
end

const gbifenums = Dict(
"basisOfRecord" => [
"FOSSIL_SPECIMEN",
"HUMAN_OBSERVATION",
"LITERATURE",
"LIVING_SPECIMEN",
"MACHINE_OBSERVATION",
"MATERIAL_SAMPLE",
"OBSERVATION",
"PRESERVED_SPECIMEN",
"UNKNOWN",
],
"occurrenceStatus" => [
"PRESENT", "ABSENT",
],
"continent" => [
"AFRICA",
"ANTARCTICA",
"ASIA",
"EUROPE",
"NORTH_AMERICA",
"OCEANIA",
"SOUTH_AMERICA",
],
"establishmentMeans" => [
"INTRODUCED",
"INVASIVE",
"MANAGED",
"NATIVE",
"NATURALISED",
"UNCERTAIN",
],
"issue" => [
"BASIS_OF_RECORD_INVALID",
"CONTINENT_COUNTRY_MISMATCH",
"CONTINENT_DERIVED_FROM_COORDINATES",
"CONTINENT_INVALID",
"COORDINATE_INVALID",
"COORDINATE_OUT_OF_RANGE",
"COORDINATE_PRECISION_INVALID",
"COORDINATE_REPROJECTED",
"COORDINATE_REPROJECTION_FAILED",
"COORDINATE_REPROJECTION_SUSPICIOUS",
"COORDINATE_ROUNDED",
"COORDINATE_UNCERTAINTY_METERS_INVALID",
"COUNTRY_COORDINATE_MISMATCH",
"COUNTRY_DERIVED_FROM_COORDINATES",
"COUNTRY_INVALID",
"COUNTRY_MISMATCH",
"DEPTH_MIN_MAX_SWAPPED",
"DEPTH_NON_NUMERIC",
"DEPTH_NOT_METRIC",
"DEPTH_UNLIKELY",
"ELEVATION_MIN_MAX_SWAPPED",
"ELEVATION_NON_NUMERIC",
"ELEVATION_NOT_METRIC",
"ELEVATION_UNLIKELY",
"GEODETIC_DATUM_ASSUMED_WGS84",
"GEODETIC_DATUM_INVALID",
"IDENTIFIED_DATE_INVALID",
"IDENTIFIED_DATE_UNLIKELY",
"INDIVIDUAL_COUNT_INVALID",
"INTERPRETATION_ERROR",
"MODIFIED_DATE_INVALID",
"MODIFIED_DATE_UNLIKELY",
"MULTIMEDIA_DATE_INVALID",
"MULTIMEDIA_URI_INVALID",
"PRESUMED_NEGATED_LATITUDE",
"PRESUMED_NEGATED_LONGITUDE",
"PRESUMED_SWAPPED_COORDINATE",
"RECORDED_DATE_INVALID",
"RECORDED_DATE_MISMATCH",
"RECORDED_DATE_UNLIKELY",
"REFERENCES_URI_INVALID",
"TAXON_MATCH_FUZZY",
"TAXON_MATCH_HIGHERRANK",
"TAXON_MATCH_NONE",
"TYPE_STATUS_INVALID",
"ZERO_COORDINATE",
],
"basisOfRecord" => enumeratedvalues("BasisOfRecord"),
"occurrenceStatus" => enumeratedvalues("OccurrenceStatus"),
"continent" => enumeratedvalues("Continent"),
"country" => enumeratedvalues("Country"),
"establishmentMeans" => enumeratedvalues("EstablishmentMeans"),
"issue" => enumeratedvalues("OccurrenceIssue"),
"mediaType" => enumeratedvalues("MediaType"),
)

# Load the main functions

include("query.jl")

include("types/GBIFTaxon.jl")
Expand Down
2 changes: 1 addition & 1 deletion GBIF/src/occurrence.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ is "ABSENT"; therefore, for the majority of uses, your query will *at least* con
`"occurrenceStatus" => "PRESENT"`.
"""
function occurrences(query::Pair...)
retrieved, offset, of_max = _internal_occurrences_getter(query...)
retrieved, _, of_max = _internal_occurrences_getter(query...)
if !isnothing(retrieved)
store = Vector{GBIFRecord}(undef, of_max)
store[1:length(retrieved)] = retrieved
Expand Down
4 changes: 3 additions & 1 deletion GBIF/src/query.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""
**Checks that the queries for occurrences searches are well formatted**
validate_occurrence_query(query::Pair)
Checks that the queries for occurrences searches are well formatted.
This is used internally.
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ GBIF = "0.4"
GDAL = "1.5"
MakieCore = "0.6"
Reexport = "1.2"
SimpleSDMDatasets = "0.1"
SimpleSDMLayers = "0.9"
SimpleSDMDatasets = "0.1"
StatsBase = "0.33"
julia = "1.8"
6 changes: 6 additions & 0 deletions SimpleSDMDatasets/src/types/datasets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ given by `RasterProvider`s, and the same dataset can have multiple providers.
abstract type RasterDataset end

# List of datasets
# TODO write documentation to also mention what they can be used for, e.g. all DEM can
# provide Elevation
struct BioClim <: RasterDataset end

struct Elevation <: RasterDataset end

struct MinimumTemperature <: RasterDataset end
struct MaximumTemperature <: RasterDataset end
struct AverageTemperature <: RasterDataset end
struct Precipitation <: RasterDataset end
struct SolarRadiation <: RasterDataset end
struct WindSpeed <: RasterDataset end
struct WaterVaporPressure <: RasterDataset end

struct LandCover <: RasterDataset end

struct HabitatHeterogeneity <: RasterDataset end
struct Topography <: RasterDataset end
5 changes: 5 additions & 0 deletions SimpleSDMDatasets/src/types/futures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ CMIP6Model = Union{
UKESM1_0_LL,
}

"""
CMIP5Scenario
These scenarios are part of CMIP5. They can be `RCP26` to `RCP85` (with `RCPXX` the scenario).
"""
CMIP5Scenario = Union{RCP26, RCP45, RCP60, RCP85}
CMIP5Model = Union{
ACCESS1_0,
Expand Down
10 changes: 9 additions & 1 deletion SimpleSDMDatasets/src/types/providers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ For example, WorldClim2 and CHELSA2 are `RasterProvider`s.
"""
abstract type RasterProvider end

"""
WorldClim2
This provider offers access to the version 2 of the WorldClim data, accessible from `http://www.worldclim.com/version2`.
"""
struct WorldClim2 <: RasterProvider end

struct EarthEnv <: RasterProvider end

struct CHELSA1 <: RasterProvider end
struct CHELSA2 <: RasterProvider end

struct CHELSA2 <: RasterProvider end
2 changes: 1 addition & 1 deletion dev.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,4 @@ xlims!(ax, -0.2, 1.2)
ylims!(ax, -0.2, 1.2)
hidedecorations!(ax)

current_figure()
current_figure()
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ makedocs(;
"Interface to GBIF data" => "manual/GBIF/index.md",
"GBIF data representation" => "manual/GBIF/types.md",
"GBIF data retrieval" => "manual/GBIF/data.md",
"Enumerated query parameters" => "manual/GBIF/enums.md",
],
"SimpleSDMDatasets.jl" => [
"Interface to raster data" => "manual/SimpleSDMDatasets/index.md",
Expand Down
12 changes: 12 additions & 0 deletions docs/src/manual/GBIF/enums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Enumerated values

The GBIF API has a number of controlled vocabularies to perform queries (also called "enumerations"). In order to keep the API and the package in sync, when the package is loaded, we query the API to see what values are enumerable, and what values are acceptable for each of these categories.

```@docs
GBIF.enumerablevalues
GBIF.enumeratedvalues
```

These functions are *not* exported, and are only called once per session to populate
a dictionary with the accepted values. Note that at the moment, the only enumerated values
that we store are the one accepted as search argument by the occurrence search endpoint.
45 changes: 27 additions & 18 deletions recipes.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using SimpleSDMLayers

@shorthands bivariate
@shorthands bivariatelegend
@shorthands trivariate
Expand All @@ -6,7 +8,7 @@
"""
test 1
"""
@recipe function plot(layer::T) where {T<:SimpleSDMLayer}
@recipe function plot(layer::T) where {T <: SimpleSDMLayer}
seriestype --> :heatmap
if get(plotattributes, :seriestype, :heatmap) in [:heatmap, :contour]
eltype(layer) <: AbstractFloat || throw(
Expand All @@ -32,20 +34,20 @@ test 2
@recipe function plot(
l1::FT,
l2::ST;
classes::Int=3,
p0=colorant"#e8e8e8ff",
p1=colorant"#64acbeff",
p2=colorant"#c85a5aff",
quantiles=true,
) where {FT<:SimpleSDMLayer,ST<:SimpleSDMLayer}
classes::Int = 3,
p0 = colorant"#e8e8e8ff",
p1 = colorant"#64acbeff",
p2 = colorant"#c85a5aff",
quantiles = true,
) where {FT <: SimpleSDMLayer, ST <: SimpleSDMLayer}
eltype(l1) <: Number || throw(
ArgumentError(
"Plotting is only supported for layers with number values ($(eltype(l1)))"
"Plotting is only supported for layers with number values ($(eltype(l1)))",
),
)
eltype(l2) <: Number || throw(
ArgumentError(
"Plotting is only supported for layers with number values ($(eltype(l2)))"
"Plotting is only supported for layers with number values ($(eltype(l2)))",
),
)
seriestype --> :scatter
Expand Down Expand Up @@ -130,21 +132,21 @@ end
test 2
"""
@recipe function plot(
x::FT, y::ST, z::TT; quantiles=true, simplex=false, red="", green="", blue=""
) where {FT<:SimpleSDMLayer,ST<:SimpleSDMLayer,TT<:SimpleSDMLayer}
x::FT, y::ST, z::TT; quantiles = true, simplex = false, red = "", green = "", blue = "",
) where {FT <: SimpleSDMLayer, ST <: SimpleSDMLayer, TT <: SimpleSDMLayer}
eltype(x) <: Number || throw(
ArgumentError(
"Plotting is only supported for layers with number values ($(eltype(x)))"
"Plotting is only supported for layers with number values ($(eltype(x)))",
),
)
eltype(y) <: Number || throw(
ArgumentError(
"Plotting is only supported for layers with number values ($(eltype(y)))"
"Plotting is only supported for layers with number values ($(eltype(y)))",
),
)
eltype(z) <: Number || throw(
ArgumentError(
"Plotting is only supported for layers with number values ($(eltype(z)))"
"Plotting is only supported for layers with number values ($(eltype(z)))",
),
)
SimpleSDMLayers._layers_are_compatible(x, y)
Expand Down Expand Up @@ -206,26 +208,33 @@ test 2
y0 = (row_number - 1) * h
push!(shapes_x, [x0, x0 + a / 2, x0 + a])
push!(shapes_y, [y0, y0 + h, y0])
push!(shapes_colors, rgb_from_xy(x0 + a / 2, y0 + h / 2; simplex=simplex))
push!(shapes_colors, rgb_from_xy(x0 + a / 2, y0 + h / 2; simplex = simplex))
if row_number > 1
push!(shapes_x, [x0, x0 + a / 2, x0 + a])
push!(shapes_y, [y0, y0 - h, y0])
push!(shapes_colors, rgb_from_xy(x0 + a / 2, y0 - h / 2; simplex=simplex))
push!(
shapes_colors,
rgb_from_xy(x0 + a / 2, y0 - h / 2; simplex = simplex),
)
end
end
end
_fs = get(plotattributes, :annotationfontsize, 14)
@series begin
seriestype := :shape
annotations := [(-0.05, 0.0, (red, _fs, :left, 60.0)), (1.0, -0.05, (green, _fs, :right, 0.0)), (0.5+0.05, sqrt(3)/2, (blue, _fs, :left, -60.0))]
annotations := [
(-0.05, 0.0, (red, _fs, :left, 60.0)),
(1.0, -0.05, (green, _fs, :right, 0.0)),
(0.5 + 0.05, sqrt(3) / 2, (blue, _fs, :left, -60.0)),
]
markersize := 0
seriescolor := hcat(shapes_colors...)
shapes_x, shapes_y
end
end
end

function rgb_from_xy(x, y; simplex=false)
function rgb_from_xy(x, y; simplex = false)
b = 2y / sqrt(3)
g = (2x - b) / 2
r = 1 - (b + g)
Expand Down
2 changes: 0 additions & 2 deletions src/SpeciesDistributionToolkit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,4 @@ include("pseudoabsences.jl")
export WithinRadius, SurfaceRangeEnvelope, RandomSelection
export pseudoabsencemask



end # module SpeciesDistributionToolkit
Loading

2 comments on commit 5571028

@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 subdir="GBIF"

@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/82149

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 GBIF-v0.4.3 -m "<description of version>" 5571028566f956d3a18d903c098826bd2f236f9b
git push origin GBIF-v0.4.3

Please sign in to comment.