From 12d567c88546d7d44dee16a425c9c71330cca805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Poisot?= Date: Tue, 25 Apr 2023 19:32:17 -0400 Subject: [PATCH] feat(phylopic): initial release * feat(phylopic): initial commit with an empty package * ci(phylopic): setup testing * dependencies(phylopic): add required packages * feat(phylopic): add a non-exported function to ping * feat(phylopic): autocomplete endpoint * test(phylopic): ping * dependencies(phylopic): add UUIDs * feat(phylopic): search images * refactor(phylopic): move functions to their own files * feat(phylopic): get the images after a search * doc(phylopic): comments in the top level file * ci: add phylopic too the top level bootstrap * license(phylopic): MIT * doc(phylopic): README * refactor(phylopic): use a more use-case inspired design * test(phylopic): add test for names * refactor(phylopic): move names to their own files * refactor(phylopic): overload some methods for easier piping * refactor(phylopic): replace images by thumbnail and vector * dependencies(phylopic): import UUIDs * doc(phylopic): update some comments in the code * doc(phylopic): update the man pages * doc(phylopic): manpage and vignette * feat: reexport phylopic from top-level package * doc(phylopic): README * doc(phylopic): correct manpage path * doc(phylopic): pick the first silhouette in the vignette * doc(phylopic): correct index path * bug: wrong module in the phylopic example * doc(phylopic): fix axis for the figure * doc(phylopic): some more context on how to plot * doc(phylopic): use of the items argument * feat(phylopic): restrict search by license terms Closes #173 * feat(phylopic): rename names to imagesof * doc(phylopic): update vignette use-case * doc(phylopic): add docstrings in the manual section * test(phylopic): name autocompletion * bug(phylopic): wrong return when using items=1 * doc(phylopic): README * feat(phylopic): twitter image * feat(phylopic): add additional twitterimage methods * refactor(phylopic): remove unused keyword arguments * refactor(phylopic): get the link function outside of the data function * feat(phylopic): get source image * dependencies(phylopic): using Markdown * feat(phylopic): attribution string * bug(phylopic): wrong call to the function to get the links * semver(phylopic): 0.0.1 --- .github/workflows/CI.yml | 26 ++++++- Phylopic/LICENSE.md | 22 ++++++ Phylopic/Project.toml | 10 +++ Phylopic/README.md | 18 +++++ Phylopic/bootstrap.jl | 2 + Phylopic/src/Phylopic.jl | 33 +++++++++ Phylopic/src/attribution.jl | 16 +++++ Phylopic/src/autocomplete.jl | 24 +++++++ Phylopic/src/images.jl | 67 ++++++++++++++++++ Phylopic/src/imagesof.jl | 69 +++++++++++++++++++ Phylopic/src/ping.jl | 34 +++++++++ Phylopic/test/Project.toml | 2 + Phylopic/test/autocomplete.jl | 8 +++ Phylopic/test/imagesof.jl | 9 +++ Phylopic/test/ping.jl | 8 +++ Phylopic/test/runtests.jl | 27 ++++++++ bootstrap.jl | 2 +- docs/Project.toml | 2 + docs/make.jl | 3 + docs/src/manual/Phylopic/index.md | 32 +++++++++ .../occurrences/02_occurrences_and_layers.jl | 36 +++++++++- src/SpeciesDistributionToolkit.jl | 1 + 22 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 Phylopic/LICENSE.md create mode 100644 Phylopic/Project.toml create mode 100644 Phylopic/README.md create mode 100644 Phylopic/bootstrap.jl create mode 100644 Phylopic/src/Phylopic.jl create mode 100644 Phylopic/src/attribution.jl create mode 100644 Phylopic/src/autocomplete.jl create mode 100644 Phylopic/src/images.jl create mode 100644 Phylopic/src/imagesof.jl create mode 100644 Phylopic/src/ping.jl create mode 100644 Phylopic/test/Project.toml create mode 100644 Phylopic/test/autocomplete.jl create mode 100644 Phylopic/test/imagesof.jl create mode 100644 Phylopic/test/ping.jl create mode 100644 Phylopic/test/runtests.jl create mode 100644 docs/src/manual/Phylopic/index.md diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index eda854ebf..11b1d8fab 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -33,6 +33,30 @@ jobs: with: name: coverage path: GBIF-lcov.info + Phylopic: + name: "Phylopic" + runs-on: "ubuntu-latest" + continue-on-error: true + steps: + - uses: actions/checkout@v3 + - uses: julia-actions/setup-julia@v1 + - name: "Bootstrap the package" + run: julia --project=./Phylopic Phylopic/bootstrap.jl + - uses: julia-actions/julia-buildpkg@latest + with: + project: Phylopic + - uses: julia-actions/julia-runtest@latest + with: + project: Phylopic + annotate: true + - uses: julia-actions/julia-processcoverage@v1 + with: + directories: Phylopic/src + - run: mv lcov.info Phylopic-lcov.info + - uses: actions/upload-artifact@v3 + with: + name: coverage + path: Phylopic-lcov.info SimpleSDMLayers: name: "SimpleSDMLayers" runs-on: "ubuntu-latest" @@ -107,7 +131,7 @@ jobs: name: coverage path: Fauxcurrences-lcov.info SpeciesDistributionToolkit: - needs: [GBIF, Fauxcurrences, SimpleSDMLayers, SimpleSDMDatasets] + needs: [GBIF, Fauxcurrences, SimpleSDMLayers, SimpleSDMDatasets, Phylopic] name: "SpeciesDistributionToolkit" runs-on: "ubuntu-latest" continue-on-error: true diff --git a/Phylopic/LICENSE.md b/Phylopic/LICENSE.md new file mode 100644 index 000000000..42a905dc1 --- /dev/null +++ b/Phylopic/LICENSE.md @@ -0,0 +1,22 @@ +The Phylopic.jl package is licensed under the MIT "Expat" License: + +> Copyright (c) 2023: Timothée Poisot. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. +> diff --git a/Phylopic/Project.toml b/Phylopic/Project.toml new file mode 100644 index 000000000..46885168c --- /dev/null +++ b/Phylopic/Project.toml @@ -0,0 +1,10 @@ +name = "Phylopic" +uuid = "c889285c-44aa-4473-b1e1-56f5d4e3ccf5" +authors = ["Timothée Poisot "] +version = "0.0.1" + +[deps] +HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" diff --git a/Phylopic/README.md b/Phylopic/README.md new file mode 100644 index 000000000..bd37d7726 --- /dev/null +++ b/Phylopic/README.md @@ -0,0 +1,18 @@ +# Phylopic.jl + +This package is a *thin* wrapper around the Phylopic API, which is currently not covering +the entire API. + +~~~julia +using Phylopic +import Downloads + +# Get a series of UUIDs from a name +org_uuid = Phylopic.imagesof("chiroptera"; items=1) + +# We can query the thumbnails for this UUID +thumb_url = Phylopic.thumbnail(org_uuid) + +# We can download the thumbnail (to a temp file) +thumb_file = Downloads.download(first(thumb_url)) +~~~ diff --git a/Phylopic/bootstrap.jl b/Phylopic/bootstrap.jl new file mode 100644 index 000000000..49d3b0b3a --- /dev/null +++ b/Phylopic/bootstrap.jl @@ -0,0 +1,2 @@ +main = () -> nothing +main() diff --git a/Phylopic/src/Phylopic.jl b/Phylopic/src/Phylopic.jl new file mode 100644 index 000000000..9fec91c5f --- /dev/null +++ b/Phylopic/src/Phylopic.jl @@ -0,0 +1,33 @@ +module Phylopic + +import HTTP +import JSON +import UUIDs +using Markdown + +const api = "https://api.phylopic.org/" + +# This file contains the ping and build functions, which are only really called when the +# package is initially loaded. Ping ensures that the API responds, and build gives the value +# of the current build by default, as it is required for most operations. +include("ping.jl") + +# We do a first ping to fail ASAP if the API is not responsive +@assert isnothing(Phylopic.ping()) + +# We put the buildnumber in a const to avoid calling it multiple times -- this is a required +# parameter for a large number of queries (most of the queries, in fact), so it makes sense +# to get it as a const ASAP +const buildnumber = Phylopic.build() +# TODO: allow an environmental variable to specify a different build version for +# reproducibility + +# The autocomplete endpoint is meant to give an overview of possible names starting from +# a stem - this is not necessarilly going to give all of the names, and I am not sure why +include("autocomplete.jl") + +include("imagesof.jl") +include("images.jl") +include("attribution.jl") + +end # module hylopic diff --git a/Phylopic/src/attribution.jl b/Phylopic/src/attribution.jl new file mode 100644 index 000000000..ef31b8677 --- /dev/null +++ b/Phylopic/src/attribution.jl @@ -0,0 +1,16 @@ +""" + Phylopic.attribution(uuid::UUIDs.UUID) + +Generates a string for the attribution of an image, as identified by its `uuid`. This string is markdown-formatted, and will include a link to the license. +""" +function attribution(uuid::UUIDs.UUID) + data = Phylopic.images_data(uuid) + contributorname = data["attribution"] + nodename = data["_links"]["specificNode"]["title"] + license = data["_links"]["license"]["href"] + attr_string = "Image of *$(nodename)* provided by [$(contributorname)]($license)" + return Markdown.parse(attr_string) +end + +attribution(pair::Pair{String,UUIDs.UUID}; kwargs...) = attribution(pair.second; kwargs...) +attribution(dict::Dict{String,UUIDs.UUID}; kwargs...) = attribution.(collect(dict); kwargs...) diff --git a/Phylopic/src/autocomplete.jl b/Phylopic/src/autocomplete.jl new file mode 100644 index 000000000..ea186451a --- /dev/null +++ b/Phylopic/src/autocomplete.jl @@ -0,0 +1,24 @@ +""" + Phylopic.autocomplete(query::AbstractString) + +Performs an autocomplete query based on a string, which must be at least two characters in length. + +This function will return an *array* of strings, which can be empty if there are no matches. In you want to do things depending on the values returned, check them with `isempty`, not `isnothing`. + +The output of this function, when not empty, can be passed to either `Phylopic.nodes` or `Phylopic.images` using their `filter_name` keyword argument. Note that the `filter_name` argument accepts a *single* name, not an array of names. +""" +function autocomplete(query::AbstractString) + if length(query) < 2 + throw(ArgumentError("The query ($(query)) must be at least two characters")) + end + req = HTTP.get(Phylopic.api * "autocomplete?query=$(query)") + if isequal(200)(req.status) + matches = JSON.parse(String(req.body))["matches"] + if ~isempty(matches) + return convert(Vector{String}, matches) + end + end + # WARNING: This probably should not return an empty list here, maybe a `nothing` would + # make more sense + return AbstractString[] +end diff --git a/Phylopic/src/images.jl b/Phylopic/src/images.jl new file mode 100644 index 000000000..cd0252416 --- /dev/null +++ b/Phylopic/src/images.jl @@ -0,0 +1,67 @@ +""" + Phylopic.vector(uuid::UUIDs.UUID) + +Returns the URL (if it exists) to the original vector image for the silhouette. Note that the image must be identified by its UUID, not by a string. +""" +function vector(uuid::UUIDs.UUID) + lnks = Phylopic.images_links(uuid) + return lnks["vectorFile"]["href"] +end + +""" + Phylopic.twitterimage(uuid::UUIDs.UUID) + +Returns the twitter image for a UUID. +""" +function twitterimage(uuid::UUIDs.UUID) + links = Phylopic.images_links(uuid) + return links["twitter:image"]["href"] +end + +""" + Phylopic.source(uuid::UUIDs.UUID) + +Returns the source image for a UUID. +""" +function source(uuid::UUIDs.UUID) + links = Phylopic.images_links(uuid) + return links["sourceFile"]["href"] +end + +""" + Phylopic.thumbnail(uuid::UUIDs.UUID; resolution=192) + +Returns the URL (if it exists) to the thumbnails for the silhouette. The thumbnail `resolution` can be `64`, `128`, or `192` (the default). +""" +function thumbnail(uuid::UUIDs.UUID; resolution=192) + @assert resolution in [64, 128, 192] + lnks = Phylopic.images_links(uuid) + required_size = "$(resolution)x$(resolution)" + img = filter(f -> f["sizes"] == required_size, lnks["thumbnailFiles"]) + return only(img)["href"] +end + +thumbnail(pair::Pair{String,UUIDs.UUID}; kwargs...) = thumbnail(pair.second; kwargs...) +thumbnail(dict::Dict{String,UUIDs.UUID}; kwargs...) = thumbnail.(collect(dict); kwargs...) +vector(pair::Pair{String,UUIDs.UUID}; kwargs...) = vector(pair.second; kwargs...) +vector(dict::Dict{String,UUIDs.UUID}; kwargs...) = vector.(collect(dict); kwargs...) +twitterimage(pair::Pair{String,UUIDs.UUID}; kwargs...) = twitterimage(pair.second; kwargs...) +twitterimage(dict::Dict{String,UUIDs.UUID}; kwargs...) = twitterimage.(collect(dict); kwargs...) +source(pair::Pair{String,UUIDs.UUID}; kwargs...) = source(pair.second; kwargs...) +source(dict::Dict{String,UUIDs.UUID}; kwargs...) = source.(collect(dict); kwargs...) + +function images_data(uuid::UUIDs.UUID) + query = [ + "build" => Phylopic.buildnumber, + ] + req = HTTP.get(Phylopic.api * "images/$(string(uuid))", query=query) + if isequal(200)(req.status) + response = JSON.parse(String(req.body)) + return response + end + return nothing +end + +function images_links(uuid::UUIDs.UUID) + return images_data(uuid)["_links"] +end diff --git a/Phylopic/src/imagesof.jl b/Phylopic/src/imagesof.jl new file mode 100644 index 000000000..0f18b598d --- /dev/null +++ b/Phylopic/src/imagesof.jl @@ -0,0 +1,69 @@ +""" + _get_uuids_at_page(query, page) + +This function is an internal helped function to return an array of pairs, wherein each pair maps a name to a UUID, for a given query and page. These outpurs are collected in a dictionary by `Phylopic.names`. +""" +function _get_uuids_at_page(query, page) + page_query = push!(query, "page" => page - 1) + req = HTTP.get(Phylopic.api * "images", query=page_query) + if isequal(200)(req.status) + response = JSON.parse(String(req.body)) + return [item["title"] => UUIDs.UUID(replace(item["href"], "/images/" => "", "?build=$(Phylopic.buildnumber)" => "")) for item in response["_links"]["items"]] + end + return nothing +end + +""" + imagesof(name::AbstractString; items=1, attribution=false, sharealike=false, nocommercial=false) + +Returns a mapping between names and UUIDs of images for a given text (see also `Phylopic.autocomplete` to find relevant names). By default, the search will return images that come without BY, SA, and NC clauses (*i.e.* public domain dedication), but this can be changed using the keyword arguments. + +`items` +: Default to 1 +: Specifies the number of items to return. When a single item is returned, it is return as a pair mapping the name to the UUID; when there are more than 1, they are returned as a dictionary + +`attribution` +: Default to `false` +: Specifies whether the images returned require attribution to the creator + +`sharealike` +: Default to `false` +: Specifies whether the images returned require sharing of derived products using a license with a SA clause + +`nocommercial` +: Default to `false` +: Specifies whether the images returned are prevented from being used in commercial projects +""" +function imagesof(name::AbstractString; items=1, attribution=false, sharealike=false, nocommercial=false) + name = lowercase(replace(name, r"\s+" => " ")) + query = [ + "filter_name" => name, + "filter_license_by" => attribution, + "filter_license_nc" => nocommercial, + "filter_license_sa" => sharealike, + "build" => Phylopic.buildnumber + ] + req = HTTP.get(Phylopic.api * "images", query=query) + if isequal(200)(req.status) + response = JSON.parse(String(req.body)) + if iszero(response["totalItems"]) + return nothing + end + if response["totalItems"] < items + @warn "Only $(response["totalItems"]) are available, you requested $(items)" + end + n_pages = ceil(items / response["itemsPerPage"]) + uuids = vcat([_get_uuids_at_page(query, page) for page in n_pages]...) + if isone(length(uuids)) + return only(uuids) + else + toreturn = min(items, response["totalItems"]) + if isone(toreturn) + return first(uuids) + else + return Dict(uuids[1:toreturn]) + end + end + end + return nothing +end diff --git a/Phylopic/src/ping.jl b/Phylopic/src/ping.jl new file mode 100644 index 000000000..253d8cd1d --- /dev/null +++ b/Phylopic/src/ping.jl @@ -0,0 +1,34 @@ +import HTTP +import JSON + +""" + Phylopic.ping() + +This function will perform a simple ping of the API, and return `nothing` if it is responding, and throw and `ErrorException` (containing the string `"not responding"`) if the API does not returns a `204 No Content` success status. +""" +function ping() + req = HTTP.get(Phylopic.api * "ping") + if isequal(204)(req.status) + return nothing + else + throw(ErrorException("The API at $(Phylopic.api) is not responding")) + end + return nothing +end + +""" + Phylopic.build() + +Returns the current build to perform the queries +""" +function build() + req = HTTP.get(Phylopic.api) + if isequal(200)(req.status) + infostring = JSON.parse(String(req.body)) + return infostring["build"] + else + throw(ErrorException("The API at $(Phylopic.api) is not responding")) + end + return nothing + +end diff --git a/Phylopic/test/Project.toml b/Phylopic/test/Project.toml new file mode 100644 index 000000000..0c363327c --- /dev/null +++ b/Phylopic/test/Project.toml @@ -0,0 +1,2 @@ +[deps] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/Phylopic/test/autocomplete.jl b/Phylopic/test/autocomplete.jl new file mode 100644 index 000000000..c03454dc3 --- /dev/null +++ b/Phylopic/test/autocomplete.jl @@ -0,0 +1,8 @@ +module TestPhylopicAutocomplete + +using Test +using Phylopic + +@test ~isempty(Phylopic.autocomplete("chiro")) + +end diff --git a/Phylopic/test/imagesof.jl b/Phylopic/test/imagesof.jl new file mode 100644 index 000000000..105b0ed2a --- /dev/null +++ b/Phylopic/test/imagesof.jl @@ -0,0 +1,9 @@ +module TestPhylopicNames + +using Phylopic +using Test + +@test typeof(Phylopic.imagesof("chiroptera")) == Pair{String,Base.UUID} +@test typeof(Phylopic.imagesof("chiroptera"; items=2)) == Dict{String,Base.UUID} + +end diff --git a/Phylopic/test/ping.jl b/Phylopic/test/ping.jl new file mode 100644 index 000000000..9f80a5207 --- /dev/null +++ b/Phylopic/test/ping.jl @@ -0,0 +1,8 @@ +module TestPhylopicPing + + using Phylopic + using Test + +@test isnothing(Phylopic.ping()) + +end diff --git a/Phylopic/test/runtests.jl b/Phylopic/test/runtests.jl new file mode 100644 index 000000000..2d9436711 --- /dev/null +++ b/Phylopic/test/runtests.jl @@ -0,0 +1,27 @@ +using Phylopic +using Test + +global anyerrors = false + +tests = [ + "ping" => "ping.jl", + "images of" => "imagesof.jl", + "autocomplete" => "autocomplete.jl",] + +for test in tests + try + include(test.second) + println("\033[1m\033[32m✓\033[0m\t$(test.first)") + catch e + global anyerrors = true + println("\033[1m\033[31m×\033[0m\t$(test.first)") + println("\033[1m\033[38m→\033[0m\ttest/$(test.second)") + showerror(stdout, e, backtrace()) + println() + break + end +end + +if anyerrors + throw("Tests failed") +end diff --git a/bootstrap.jl b/bootstrap.jl index 55a82e35f..590b9ef31 100644 --- a/bootstrap.jl +++ b/bootstrap.jl @@ -1,6 +1,6 @@ import Pkg -components = ["SimpleSDMDatasets", "SimpleSDMLayers", "GBIF", "Fauxcurrences"] +components = ["SimpleSDMDatasets", "SimpleSDMLayers", "GBIF", "Fauxcurrences", "Phylopic"] # Cleanup local install and develop for package in components diff --git a/docs/Project.toml b/docs/Project.toml index 3e10854e8..e8442624d 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,7 +3,9 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6" +Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" diff --git a/docs/make.jl b/docs/make.jl index 11cf4a421..2324935f5 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -51,6 +51,9 @@ makedocs(; "Occurrences and layers" => "manual/SpeciesDistributionToolkit/gbif.jl.md", "Pseudo-absences" => "manual/SpeciesDistributionToolkit/pseudoabsences.md", ], + "Phylopic.jl" => [ + "Integration with Phylopic" => "manual/Phylopic/index.md", + ], "SimpleSDMLayers.jl" => [ "Easy manipulation of layers" => "manual/SimpleSDMLayers/index.md", "Layer data representation" => "manual/SimpleSDMLayers/types.md", diff --git a/docs/src/manual/Phylopic/index.md b/docs/src/manual/Phylopic/index.md new file mode 100644 index 000000000..d19070d91 --- /dev/null +++ b/docs/src/manual/Phylopic/index.md @@ -0,0 +1,32 @@ +# Integration with the Phylopic API + +The `Phylopic.jl` package offers a simple wrapper on the [Phylopic](https://www.phylopic.org/) API, to collect silhouettes of species for plots. At the moment, it is a minimal viable product that allows to search for the UUID of images by taxonomic names, then to retrieve the vector file or the thumbnail associated to each image. It also provides the ability to store the credit information for all of the images used in a project. + +## Finding images by names + +```@docs +Phylopic.autocomplete +Phylopic.imagesof +``` + +## Retrieving images + +```@docs +Phylopic.thumbnail +Phylopic.vector +Phylopic.twitterimage +Phylopic.source +``` + +## Additional functions + +```@docs +Phylopic.attribution +``` + +## API interaction functions + +```@docs +Phylopic.ping +Phylopic.build +``` diff --git a/docs/src/vignettes/occurrences/02_occurrences_and_layers.jl b/docs/src/vignettes/occurrences/02_occurrences_and_layers.jl index 8266e4354..ffca96950 100644 --- a/docs/src/vignettes/occurrences/02_occurrences_and_layers.jl +++ b/docs/src/vignettes/occurrences/02_occurrences_and_layers.jl @@ -3,9 +3,13 @@ # In this vignette, we will have a look at the ways in which occurrence data (from GBIF) and # layer data can interact. In order to illustrate this, we will get information about the # occurrences of *Mystacina tuberculata*, a species of bat endemic to Aotearoa New Zealand. +# Finally, we will rely on the `Phylopic` package to download a silhouette of a bat to +# illustrate the figure. using SpeciesDistributionToolkit using CairoMakie +import Images +import Downloads # This sets up a bounding box for the region of interest: @@ -53,5 +57,35 @@ map = Axis(figure[1, 2]; aspect = DataAspect()) hidedecorations!(map) hidespines!(map) heatmap!(map, temperature; colormap = :heat) -scatter!(observations; color=:black) +scatter!(observations; color = :black) +current_figure() + +# We can now add a silhouette of a bat using Phylopic. We only want a single item here, and +# the search will by default be restricted to images that can be used with the least +# constraints. + +bat_uuid = Phylopic.imagesof("chiroptera"; items = 1) + +# The next step is to get the url of the image -- we are going to get the largest thumbnail +# (which is the default): + +bat_thumbnail_url = Phylopic.thumbnail(bat_uuid) +bat_thumbnail_tmp = Downloads.download(bat_thumbnail_url) +bat_image = Images.load(bat_thumbnail_tmp) + +# We can also check how to credit the person who created this image. This will create +# a markdown string, with the node name, the contributor name, and a link to the license. + +Phylopic.attribution(bat_uuid) + +# We can now use this image in a scatter plot -- this uses the thumbnail as a scatter +# symbol, so we need to plot this like any other point. Because the thumbnail returned by +# default is rather large, we can rescale it based on the image size: + +bat_size = Vec2f(reverse(size(bat_image) ./ 3)) + +# Finally, we can plot everything (note that the Phylopic images have a transparent +# background, so we are not hiding any information!): + +scatter!(envirovars, [14.0], [2400.0]; marker = bat_image, markersize = bat_size) current_figure() diff --git a/src/SpeciesDistributionToolkit.jl b/src/SpeciesDistributionToolkit.jl index 8b9017e47..db8210969 100644 --- a/src/SpeciesDistributionToolkit.jl +++ b/src/SpeciesDistributionToolkit.jl @@ -19,6 +19,7 @@ using Reexport @reexport using GBIF @reexport using SimpleSDMLayers @reexport using Fauxcurrences +@reexport using Phylopic # SimpleSDMLayers to wrap everything together include("integrations/datasets_layers.jl")