diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..54923f0 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,26 @@ +indent = 4 +margin = 92 +always_for_in = true +whitespace_typedefs = true +whitespace_ops_in_indices = true +remove_extra_newlines = true +import_to_using = false +pipe_to_function_call = false +short_to_long_function_def = false +long_to_short_function_def = false +always_use_return = true +whitespace_in_kwargs = true +annotate_untyped_fileds_with_any = true +format_docstrings = true +conditional_to_if = true +normalize_line_endings = "unix" +trailing_comma = true +join_lines_based_on_source = true +indent_submodule = true +separate_kwargs_with_semicolon = true +surround_whereop_typeparameters = true +overwrite = true +verbose = true +format_markdown = true +align_struct_field = true +align_pair_arrow = true \ No newline at end of file diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d3acb46..7fe71e3 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,6 +17,9 @@ jobs: - '1.3' - '1.4' - '1.5' + - '1.6' + - '1.7' + - '1.8' os: - ubuntu-latest - macOS-latest diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index e9c9a90..cb02b8d 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -9,7 +9,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - julia-version: [1.4.0] + julia-version: [1.8.2] julia-arch: [x86] os: [ubuntu-latest] steps: diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index e63bc95..3c067a3 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - version: '1.5' + version: '1.8' - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy diff --git a/src/GBIF.jl b/src/GBIF.jl index 4ad819e..fd8feab 100644 --- a/src/GBIF.jl +++ b/src/GBIF.jl @@ -7,88 +7,91 @@ using Requires const gbifurl = "http://api.gbif.org/v1/" const gbifenums = Dict( - "basisOfRecord" => [ - "FOSSIL_SPECIMEN", - "HUMAN_OBSERVATION", - "LITERATURE", - "LIVING_SPECIMEN", - "MACHINE_OBSERVATION", - "MATERIAL_SAMPLE", - "OBSERVATION", - "PRESERVED_SPECIMEN", - "UNKNOWN" - ], - "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" => [ + "FOSSIL_SPECIMEN", + "HUMAN_OBSERVATION", + "LITERATURE", + "LIVING_SPECIMEN", + "MACHINE_OBSERVATION", + "MATERIAL_SAMPLE", + "OBSERVATION", + "PRESERVED_SPECIMEN", + "UNKNOWN", + ], + "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", + ], +) # Load the main functions include("query.jl") +include("exceptions.jl") +export GBIFException, NoTaxonMatchedAtRank, OtherTaxonMatchingException, BadHTMLResponseCode + include("types/GBIFTaxon.jl") export GBIFTaxon @@ -108,7 +111,9 @@ export occurrences! # Extends with DataFrames functionalities function __init__() - @require DataFrames="a93c6f00-e57d-5684-b7b6-d8193f3e46c0" include("requires/dataframes.jl") + @require DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" include( + "requires/dataframes.jl", + ) end end # module diff --git a/src/exceptions.jl b/src/exceptions.jl new file mode 100644 index 0000000..fce37d4 --- /dev/null +++ b/src/exceptions.jl @@ -0,0 +1,25 @@ +import Base: showerror + +abstract type GBIFException <: Exception end + +struct NoTaxonMatchedAtRank <: GBIFException + taxon::String + rank::String +end + +struct OtherTaxonMatchingException <: GBIFException + taxon::String + rank::String +end + +struct BadHTMLResponseCode <: GBIFException + taxon::String + rank::String + errorcode::Integer +end + +function Base.showerror(io::IO, err::NoTaxonMatchedAtRank) + msg = "No match in the GBIF taxonomy for $(err.tax) at rank $(err.rank)\n" + msg *= "→ you can append `strict=false` to your query to have non-strict matching" + return print(io, msg) +end \ No newline at end of file diff --git a/src/occurrence.jl b/src/occurrence.jl index 73fb3c6..ddbe396 100644 --- a/src/occurrence.jl +++ b/src/occurrence.jl @@ -1,59 +1,58 @@ format_querypair_stem(stem::Any) = replace(string(stem), " " => "%20") -function format_querypair_stem(stem::Tuple{T,T}) where {T <: Number} - m, M = stem - if (M <= m ) - throw(ArgumentError("Range queries must be formatted as min,max")) - end - return replace(string(m)*","*string(M), " " => "%20") +function format_querypair_stem(stem::Tuple{T, T}) where {T <: Number} + m, M = stem + if (M <= m) + throw(ArgumentError("Range queries must be formatted as min,max")) + end + return replace(string(m) * "," * string(M), " " => "%20") end - function pairs_to_querystring(query::Pair...) - if length(query) == 0 - return "" - else - # We start from an empty query string - querystring = "" - for (i, pair) in enumerate(query) - # We build it pairwise for every - delim = i == 1 ? "" : "&" # We use & to delimitate the queries - # Let's be extra cautious and encode the spaces correctly - root = replace(string(pair.first), " " => "%20") - stem = format_querypair_stem(pair.second) - # Then we can graft the pair string onto the query string - pairstring = "$(delim)$(root)=$(stem)" - querystring *= pairstring - end - return querystring - end + if length(query) == 0 + return "" + else + # We start from an empty query string + querystring = "" + for (i, pair) in enumerate(query) + # We build it pairwise for every + delim = i == 1 ? "" : "&" # We use & to delimitate the queries + # Let's be extra cautious and encode the spaces correctly + root = replace(string(pair.first), " " => "%20") + stem = format_querypair_stem(pair.second) + # Then we can graft the pair string onto the query string + pairstring = "$(delim)$(root)=$(stem)" + querystring *= pairstring + end + return querystring + end end """ **Return an interpreted occurrence given its key** - occurrence(key::Union{String, Integer}) + occurrence(key::Union{String, Integer}) The key can be given as a string or as an integer. """ function occurrence(key::Union{String, Integer}) - occ_url = gbifurl * "occurrence/" * string(key) - occ_key_req = HTTP.get(occ_url) - result = JSON.parse(String(occ_key_req.body)) - return GBIFRecord(result) + occ_url = gbifurl * "occurrence/" * string(key) + occ_key_req = HTTP.get(occ_url) + result = JSON.parse(String(occ_key_req.body)) + return GBIFRecord(result) end function _internal_occurrences_getter(query::Pair...) - validate_occurrence_query.(query) - occ_s_url = gbifurl * "occurrence/search" - occ_s_req = HTTP.get(occ_s_url; query=pairs_to_querystring(query...)) - if occ_s_req.status == 200 - body = JSON.parse(String(occ_s_req.body)) - of_max = body["count"] > 100000 ? 100000 : body["count"] - this_rec = GBIFRecord.(body["results"]) - return (this_rec, body["offset"], of_max) - end - return (nothing, nothing, nothing) + validate_occurrence_query.(query) + occ_s_url = gbifurl * "occurrence/search" + occ_s_req = HTTP.get(occ_s_url; query = pairs_to_querystring(query...)) + if occ_s_req.status == 200 + body = JSON.parse(String(occ_s_req.body)) + of_max = body["count"] > 100000 ? 100000 : body["count"] + this_rec = GBIFRecord.(body["results"]) + return (this_rec, body["offset"], of_max) + end + return (nothing, nothing, nothing) end """ @@ -70,17 +69,17 @@ The arguments accepted as queries are the one documented on the [GBIF API](https://www.gbif.org/developer/occurrence) website. """ function occurrences(query::Pair...) - retrieved, offset, of_max = _internal_occurrences_getter(query...) - if !isnothing(retrieved) - store = Vector{GBIFRecord}(undef, of_max) - store[1:length(retrieved)] = retrieved - return GBIFRecords( - vcat(query...), - store - ) - else - @error "Nothing retrieved" - end + retrieved, offset, of_max = _internal_occurrences_getter(query...) + if !isnothing(retrieved) + store = Vector{GBIFRecord}(undef, of_max) + store[1:length(retrieved)] = retrieved + return GBIFRecords( + vcat(query...), + store, + ) + else + @error "Nothing retrieved" + end end """ @@ -94,8 +93,8 @@ default page size GBIF uses). Future occurrences can be queried with `next!` or `complete!`. """ function occurrences(t::GBIFTaxon, query::Pair...) - levels = [:kingdom, :phylum, :class, :order, :family, :genus, :species] - level = levels[findlast(l -> getfield(t, l) !== missing, levels)] - taxon_query = String(level)*"Key" => getfield(t, level).second - return occurrences(taxon_query, query...) + levels = [:kingdom, :phylum, :class, :order, :family, :genus, :species] + level = levels[findlast(l -> getfield(t, l) !== missing, levels)] + taxon_query = String(level) * "Key" => getfield(t, level).second + return occurrences(taxon_query, query...) end diff --git a/src/paging.jl b/src/paging.jl index 6cc2761..ae291b9 100644 --- a/src/paging.jl +++ b/src/paging.jl @@ -1,21 +1,21 @@ function _internal_offset_update!(o::GBIFRecords) - offset = length(o) - offset_index = findfirst((p) -> string(p.first) == "offset", o.query) - if isnothing(offset_index) - push!(o.query, "offset" => offset) - else - deleteat!(o.query, offset_index) - push!(o.query, "offset" => offset) - end + offset = length(o) + offset_index = findfirst((p) -> string(p.first) == "offset", o.query) + if isnothing(offset_index) + push!(o.query, "offset" => offset) + else + deleteat!(o.query, offset_index) + push!(o.query, "offset" => offset) + end end function _internal_limit_update!(o::GBIFRecords) - limit_index = findfirst((p) -> string(p.first) == "limit", o.query) - limit = isnothing(limit_index) ? 20 : o.query[limit_index].second - if (length(o) + limit) > size(o) - isnothing(limit_index) || deleteat!(o.query, limit_index) - push!(o.query, "limit" => size(o) - length(o)) - end + limit_index = findfirst((p) -> string(p.first) == "limit", o.query) + limit = isnothing(limit_index) ? 20 : o.query[limit_index].second + if (length(o) + limit) > size(o) + isnothing(limit_index) || deleteat!(o.query, limit_index) + push!(o.query, "limit" => size(o) - length(o)) + end end """ @@ -31,19 +31,19 @@ ensure that the previous and the new occurrences have the same status, but only for records that have already been retrieved. """ function occurrences!(o::GBIFRecords) - if length(o) == size(o) - @info "All occurences for this query have been returned" - else - if isnothing(o.query) - o.query = Dict{String,Any}() + if length(o) == size(o) + @info "All occurences for this query have been returned" + else + if isnothing(o.query) + o.query = Dict{String, Any}() + end + _internal_offset_update!(o) + _internal_limit_update!(o) + offset = length(o) + # Retrieve and add + retrieved, _, of_max = _internal_occurrences_getter(o.query...) + start = length(o) + 1 + stop = start + length(retrieved) - 1 + o.occurrences[start:stop] = retrieved end - _internal_offset_update!(o) - _internal_limit_update!(o) - offset = length(o) - # Retrieve and add - retrieved, _, of_max = _internal_occurrences_getter(o.query...) - start = length(o)+1 - stop = start + length(retrieved) - 1 - o.occurrences[start:stop] = retrieved - end end diff --git a/src/query.jl b/src/query.jl index 4052d79..4787a62 100644 --- a/src/query.jl +++ b/src/query.jl @@ -13,35 +13,34 @@ results when they are returned. """ function validate_occurrence_query(query::Pair) - # List of fields from GBIF - allowed_fields = ["q", "basisOfRecord", "catalogNumber", "collectionCode", - "continent", "country", "datasetKey", "decimalLatitude", "decimalLongitude", - "depth", "elevation", "eventDate", "geometry", "hasCoordinate", - "hasGeospatialIssue", "institutionCode", "issue", "lastInterpreted", - "mediaType", "month", "occurrenceId", "organismId", "protocol", "license", - "publishingCountry", "publishingOrg", "crawlId", "recordedBy", "recordNumber", - "scientificName", "locality", "stateProvince", "waterBody", "taxonKey", - "kingdomKey", "phylumKey", "classKey", "orderKey", "familyKey", "genusKey", - "subGenusKey", "speciesKey", "year", "establishmentMeans", "repatriated", - "typeStatus", "facet", "facetMincount", "facetMultiselect", "limit", "offset"] - - if !(query.first ∈ allowed_fields) - @error "The $(query.first) parameter is not allowed by the GBIF API" - end - - # Country must be a two-letters country code - if query.first == "country" - if length(query.second) != 2 - @error "$(query.second) is not a two letter country code" + # List of fields from GBIF + allowed_fields = ["q", "basisOfRecord", "catalogNumber", "collectionCode", + "continent", "country", "datasetKey", "decimalLatitude", "decimalLongitude", + "depth", "elevation", "eventDate", "geometry", "hasCoordinate", + "hasGeospatialIssue", "institutionCode", "issue", "lastInterpreted", + "mediaType", "month", "occurrenceId", "organismId", "protocol", "license", + "publishingCountry", "publishingOrg", "crawlId", "recordedBy", "recordNumber", + "scientificName", "locality", "stateProvince", "waterBody", "taxonKey", + "kingdomKey", "phylumKey", "classKey", "orderKey", "familyKey", "genusKey", + "subGenusKey", "speciesKey", "year", "establishmentMeans", "repatriated", + "typeStatus", "facet", "facetMincount", "facetMultiselect", "limit", "offset"] + + if !(query.first ∈ allowed_fields) + @error "The $(query.first) parameter is not allowed by the GBIF API" end - end - # Latitude and longitudes - # TODO lat -90/90 lon -180/180, can be "min,max" + # Country must be a two-letters country code + if query.first == "country" + if length(query.second) != 2 + @error "$(query.second) is not a two letter country code" + end + end - # ENUMs - if query.first ∈ keys(gbifenums) - @assert query.second ∈ gbifenums[query.first] - end + # Latitude and longitudes + # TODO lat -90/90 lon -180/180, can be "min,max" + # ENUMs + if query.first ∈ keys(gbifenums) + @assert query.second ∈ gbifenums[query.first] + end end diff --git a/src/taxon.jl b/src/taxon.jl index edd78be..0e05419 100644 --- a/src/taxon.jl +++ b/src/taxon.jl @@ -8,15 +8,15 @@ reference taxonomy. Optional arguments are -- `rank::Union{Symbol,Nothing}=:SPECIES` -- the rank of the taxon you want. This - is part of a controlled vocabulary, and can only be one of `:DOMAIN`, - `:CLASS`, `:CULTIVAR`, `:FAMILY`, `:FORM`, `:GENUS`, `:INFORMAL`, `:ORDER`, - `:PHYLUM,`, `:SECTION`, `:SUBCLASS`, `:VARIETY`, `:TRIBE`, `:KINGDOM`, - `:SUBFAMILY`, `:SUBFORM`, `:SUBGENUS`, `:SUBKINGDOM`, `:SUBORDER`, - `:SUBPHYLUM`, `:SUBSECTION`, `:SUBSPECIES`, `:SUBTRIBE`, `:SUBVARIETY`, - `:SUPERCLASS`, `:SUPERFAMILY`, `:SUPERORDER`, and `:SPECIES` + - `rank::Union{Symbol,Nothing}=:SPECIES` -- the rank of the taxon you want. This + is part of a controlled vocabulary, and can only be one of `:DOMAIN`, + `:CLASS`, `:CULTIVAR`, `:FAMILY`, `:FORM`, `:GENUS`, `:INFORMAL`, `:ORDER`, + `:PHYLUM,`, `:SECTION`, `:SUBCLASS`, `:VARIETY`, `:TRIBE`, `:KINGDOM`, + `:SUBFAMILY`, `:SUBFORM`, `:SUBGENUS`, `:SUBKINGDOM`, `:SUBORDER`, + `:SUBPHYLUM`, `:SUBSECTION`, `:SUBSPECIES`, `:SUBTRIBE`, `:SUBVARIETY`, + `:SUPERCLASS`, `:SUPERFAMILY`, `:SUPERORDER`, and `:SPECIES` -- `strict::Bool=true` -- whether the match should be strict, or fuzzy + - `strict::Bool=true` -- whether the match should be strict, or fuzzy Finally, one can also specify other levels of the taxonomy, using `kingdom`, `phylum`, `class`, `order`, `family`, and `genus`, all of which can either be @@ -26,47 +26,49 @@ If a match is found, the result will be given as a `GBIFTaxon`. If not, this function will return `nothing` and give a warning. """ function taxon(name::String; - rank::Union{Symbol,Nothing}=:SPECIES, strict::Bool=true, - kingdom::Union{String,Nothing}=nothing, phylum::Union{String,Nothing}=nothing, class::Union{String,Nothing}=nothing, - order::Union{String,Nothing}=nothing, family::Union{String,Nothing}=nothing, genus::Union{String,Nothing}=nothing) - @assert rank ∈ [ - :DOMAIN, :CLASS, :CULTIVAR, :FAMILY, :FORM, :GENUS, :INFORMAL, :ORDER, :PHYLUM, - :SECTION, :SUBCLASS, :VARIETY, :TRIBE, :KINGDOM, :SUBFAMILY , :SUBFORM, - :SUBGENUS, :SUBKINGDOM, :SUBORDER, :SUBPHYLUM, :SUBSECTION , :SUBSERIES, - :SUBSPECIES, :SUBTRIBE, :SUBVARIETY, :SUPERCLASS , :SUPERFAMILY, :SUPERORDER, - :SPECIES - ] - args = Dict{String, Any}("name" => name, "strict" => strict) + rank::Union{Symbol, Nothing} = :SPECIES, strict::Bool = true, + kingdom::Union{String, Nothing} = nothing, phylum::Union{String, Nothing} = nothing, + class::Union{String, Nothing} = nothing, + order::Union{String, Nothing} = nothing, family::Union{String, Nothing} = nothing, + genus::Union{String, Nothing} = nothing) + @assert rank ∈ [ + :DOMAIN, :CLASS, :CULTIVAR, :FAMILY, :FORM, :GENUS, :INFORMAL, :ORDER, :PHYLUM, + :SECTION, :SUBCLASS, :VARIETY, :TRIBE, :KINGDOM, :SUBFAMILY, :SUBFORM, + :SUBGENUS, :SUBKINGDOM, :SUBORDER, :SUBPHYLUM, :SUBSECTION, :SUBSERIES, + :SUBSPECIES, :SUBTRIBE, :SUBVARIETY, :SUPERCLASS, :SUPERFAMILY, :SUPERORDER, + :SPECIES, + ] + args = Dict{String, Any}("name" => name, "strict" => strict) - isnothing(rank) || (args["rank"] = String(rank)) - isnothing(kingdom) || (args["kingdom"] = String(kingdom)) - isnothing(phylum) || (args["phylum"] = String(phylum)) - isnothing(class) || (args["class"] = String(class)) - isnothing(order) || (args["order"] = String(order)) - isnothing(family) || (args["family"] = String(family)) - isnothing(genus) || (args["genus"] = String(genus)) - - sp_s_url = gbifurl * "species/match" - sp_s_req = HTTP.get(sp_s_url, query=args) - if sp_s_req.status == 200 - body = JSON.parse(String(sp_s_req.body)) - # This will throw warnings for various reasons related to matchtypes - matchtype = get(body, "matchType", "WELP...") - # The first one will catch issues - if matchtype == "WELP..." - @warn "Impossible to get information for $(name) at level $(rank)" - return nothing - end - if matchtype == "NONE" - @warn "No match for $(name) at level $(rank) -- try with strict=false" - return nothing - end - return GBIFTaxon(body) - else - @warn "Impossible to retrieve information for $(name) -- HTML error code $(sp_s_req.status)" - return nothing - end - return nothing + isnothing(rank) || (args["rank"] = String(rank)) + isnothing(kingdom) || (args["kingdom"] = String(kingdom)) + isnothing(phylum) || (args["phylum"] = String(phylum)) + isnothing(class) || (args["class"] = String(class)) + isnothing(order) || (args["order"] = String(order)) + isnothing(family) || (args["family"] = String(family)) + isnothing(genus) || (args["genus"] = String(genus)) + + sp_s_url = gbifurl * "species/match" + sp_s_req = HTTP.get(sp_s_url; query = args) + if sp_s_req.status == 200 + body = JSON.parse(String(sp_s_req.body)) + # This will throw warnings for various reasons related to matchtypes + matchtype = get(body, "matchType", "WELP...") + # The first one will catch issues + if matchtype == "WELP..." + @warn "Impossible to get information for $(name) at level $(rank)" + return nothing + end + if matchtype == "NONE" + @warn "No match for $(name) at level $(rank) -- try with strict=false" + return nothing + end + return GBIFTaxon(body) + else + @warn "Impossible to retrieve information for $(name) -- HTML error code $(sp_s_req.status)" + return nothing + end + return nothing end """ @@ -78,17 +80,20 @@ This function will look for a taxon by its taxonID in the GBIF reference taxonomy. """ function taxon(id::Int) - args = Dict{String, Any}("id" => id) - - sp_s_url = gbifurl * "species/$id" - sp_s_req = HTTP.get(sp_s_url, query=args) - if sp_s_req.status == 200 - body = JSON.parse(String(sp_s_req.body)) - return GBIFTaxon(body) - else - throw(ErrorException("Impossible to retrieve information for taxonID $(id) -- HTML error code $(sp_s_req.status)")) - end + args = Dict{String, Any}("id" => id) + sp_s_url = gbifurl * "species/$id" + sp_s_req = HTTP.get(sp_s_url; query = args) + if sp_s_req.status == 200 + body = JSON.parse(String(sp_s_req.body)) + return GBIFTaxon(body) + else + throw( + ErrorException( + "Impossible to retrieve information for taxonID $(id) -- HTML error code $(sp_s_req.status)", + ), + ) + end end taxon(t::Pair) = taxon(t.second) diff --git a/src/types/GBIFRecords.jl b/src/types/GBIFRecords.jl index d1d9ec3..b74ce89 100644 --- a/src/types/GBIFRecords.jl +++ b/src/types/GBIFRecords.jl @@ -61,10 +61,10 @@ end function GBIFRecord(o::Dict{String, Any}) # Prepare the taxon object levels = ["kingdom", "phylum", "class", "order", "family", "genus", "species"] - r = Dict{Any,Any}() + r = Dict{Any, Any}() for l in levels if haskey(o, l) - r[l] = Pair(o[l], o[l*"Key"]) + r[l] = Pair(o[l], o[l * "Key"]) else r[l] = missing end @@ -75,7 +75,7 @@ function GBIFRecord(o::Dict{String, Any}) if !ismissing(get(o, "genericName", missing)) this_name = o["genericName"] if !ismissing(get(o, "specificEpithet", missing)) - this_name *= " "*get(o, "specificEpithet", missing) + this_name *= " " * get(o, "specificEpithet", missing) end else this_name = o["scientificName"] @@ -94,10 +94,10 @@ function GBIFRecord(o::Dict{String, Any}) r["genus"], r["species"], 100, - false + false, ) - formatted_record = GBIFRecord( + formatted_record = GBIFRecord( o["key"], o["datasetKey"], get(o, "datasetName", missing), @@ -125,7 +125,7 @@ function GBIFRecord(o::Dict{String, Any}) get(o, "vernacularName", missing), get(o, "scientificName", missing), get(o, "recordedBy", missing), - get(o, "license", missing) + get(o, "license", missing), ) return formatted_record end diff --git a/src/types/GBIFTaxon.jl b/src/types/GBIFTaxon.jl index 52c7f3d..0c75c6f 100644 --- a/src/types/GBIFTaxon.jl +++ b/src/types/GBIFTaxon.jl @@ -31,44 +31,44 @@ of the taxon/level to its unique key in the GBIF database. `synonym` - a `Boolean` indicating whether the taxon is a synonym """ struct GBIFTaxon - name::AbstractString - scientific::AbstractString - status::Union{Missing, Symbol} - match::Union{Missing, Symbol} - kingdom::Union{Missing, Pair{String, Int64}} - phylum::Union{Missing, Pair{String, Int64}} - class::Union{Missing, Pair{String, Int64}} - order::Union{Missing, Pair{String, Int64}} - family::Union{Missing, Pair{String, Int64}} - genus::Union{Missing, Pair{String, Int64}} - species::Union{Missing, Pair{String, Int64}} - confidence::Union{Missing, Int64} - synonym::Bool + name::AbstractString + scientific::AbstractString + status::Union{Missing, Symbol} + match::Union{Missing, Symbol} + kingdom::Union{Missing, Pair{String, Int64}} + phylum::Union{Missing, Pair{String, Int64}} + class::Union{Missing, Pair{String, Int64}} + order::Union{Missing, Pair{String, Int64}} + family::Union{Missing, Pair{String, Int64}} + genus::Union{Missing, Pair{String, Int64}} + species::Union{Missing, Pair{String, Int64}} + confidence::Union{Missing, Int64} + synonym::Bool end function GBIFTaxon(o::Dict{String, Any}) - levels = ["kingdom", "phylum", "class", "order", "family", "genus", "species"] - r = Dict{Any,Any}() - for l in levels - if haskey(o, l) - r[l] = Pair(o[l], o[l*"Key"]) - else - r[l] = missing - end - end - return GBIFTaxon( - o["canonicalName"], - o["scientificName"], - haskey(o, "status") ? Symbol(o["status"]) : missing, - haskey(o, "matchType") ? Symbol(o["matchType"]) : missing, - r["kingdom"], - r["phylum"], - r["class"], - r["order"], - r["family"], - r["genus"], - r["species"], - get(o, "confidence", missing), - o["synonym"] - ) + levels = ["kingdom", "phylum", "class", "order", "family", "genus", "species"] + r = Dict{Any, Any}() + for l in levels + if haskey(o, l) + r[l] = Pair(o[l], o[l * "Key"]) + else + r[l] = missing + end + end + return GBIFTaxon( + o["canonicalName"], + o["scientificName"], + haskey(o, "status") ? Symbol(o["status"]) : missing, + haskey(o, "matchType") ? Symbol(o["matchType"]) : missing, + r["kingdom"], + r["phylum"], + r["class"], + r["order"], + r["family"], + r["genus"], + r["species"], + get(o, "confidence", missing), + o["synonym"], + ) end diff --git a/src/types/iterators.jl b/src/types/iterators.jl index 5311664..702acb5 100644 --- a/src/types/iterators.jl +++ b/src/types/iterators.jl @@ -2,29 +2,29 @@ import Base.length, Base.getindex, Base.iterate, Base.view, Base.size function view(o::GBIFRecords) defined = filter(i -> isassigned(o.occurrences, i), eachindex(o.occurrences)) - view(o.occurrences, defined) + return view(o.occurrences, defined) end function size(o::GBIFRecords) - length(o.occurrences) + return length(o.occurrences) end function length(o::GBIFRecords) - length(view(o)) + return length(view(o)) end function getindex(o::GBIFRecords, i::Int64) - view(o)[i] + return view(o)[i] end function getindex(o::GBIFRecords, r::UnitRange{Int64}) - view(o)[r] + return view(o)[r] end function iterate(o::GBIFRecords) - iterate(collect(view(o))) + return iterate(collect(view(o))) end -function iterate(o::GBIFRecords, t::Union{Int64,Nothing}) - iterate(collect(view(o)), t) +function iterate(o::GBIFRecords, t::Union{Int64, Nothing}) + return iterate(collect(view(o)), t) end diff --git a/src/types/show.jl b/src/types/show.jl index 7f308a7..3d32f7f 100644 --- a/src/types/show.jl +++ b/src/types/show.jl @@ -1,6 +1,6 @@ import Base.show -_strfmt(str::T) where {T} = str +_strfmt(str::T) where {T} = str _strfmt(::Nothing) = "" """ @@ -11,7 +11,10 @@ _strfmt(::Nothing) = "" Displays the key, the taxon name, and the country of observation. """ function show(io::IO, o::GBIFRecord) - println(io, "GBIF record $(_strfmt(o.key))\t$(_strfmt(o.taxon.name))\t($(_strfmt(o.country)))") + return println( + io, + "GBIF record $(_strfmt(o.key))\t$(_strfmt(o.taxon.name))\t($(_strfmt(o.country)))", + ) end """ @@ -22,7 +25,7 @@ end Displays the total number, and the number of currently unmasked records. """ function show(io::IO, o::GBIFRecords) - println(io, "GBIF records: downloaded $(length(o)) out of $(size(o))") + return println(io, "GBIF records: downloaded $(length(o)) out of $(size(o))") end """ @@ -33,5 +36,5 @@ end Displays the taxon name. """ function show(io::IO, t::GBIFTaxon) - println(io, "GBIF taxon -- $(_strfmt(t.name))") + return println(io, "GBIF taxon -- $(_strfmt(t.name))") end