Skip to content
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

Read the GBIF enums from the API endpoints #170

Merged
merged 16 commits into from
Apr 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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