-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support to mask/reveal/hide by polygons (#263)
* feat(sdt)?: polygon * doc(sdt)?: OSM data * doc: shaping up * dependencies(sdt): PolygonOps * doc(sdt): mask with poly * feat(sdt): plot vectors of GBIF records * feat(sdt): polygons * doc: add GBIF section * chore: export hide!, reveal! * doc: masking tutorial done * doc: hide/reval * doc: hide some output for the polygon * doc: warning about WGS84 * doc: second polygon * feat(sdt): getindex for polygons * doc: change bbox * bug: revert dep * xxx(sdt)!?: much better version of masking * bug: mask! cannot select a masked pixel * xxx: test * doc: test * doc: gbif from layer * compat(sdt): fix * doc: use correct packages
- Loading branch information
Showing
9 changed files
with
331 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,36 @@ | ||
name = "SpeciesDistributionToolkit" | ||
uuid = "72b53823-5c0b-4575-ad0e-8e97227ad13b" | ||
authors = ["Timothée Poisot <[email protected]>"] | ||
version = "0.1.1" | ||
version = "0.1.2" | ||
|
||
[deps] | ||
Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" | ||
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" | ||
Fauxcurrences = "a2d61402-033a-4ca9-aef4-652d70cf7c9c" | ||
GBIF = "ee291a33-5a6c-5552-a3c8-0f29a1181037" | ||
GeoJSON = "61d90e0f-e114-555e-ac52-39dfb47a3ef9" | ||
MakieCore = "20f20a25-4f0e-4fdf-b5d1-57303727442b" | ||
Phylopic = "c889285c-44aa-4473-b1e1-56f5d4e3ccf5" | ||
PolygonOps = "647866c9-e3ac-4575-94e7-e3d426903924" | ||
Reexport = "189a3867-3050-52da-a836-e630ba90ab69" | ||
SimpleSDMDatasets = "2c7d61d0-5c73-410d-85b2-d2e7fbbdcefa" | ||
SimpleSDMLayers = "2c645270-77db-11e9-22c3-0f302a89c64c" | ||
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" | ||
TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" | ||
ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" | ||
|
||
[compat] | ||
Distances = "0.10" | ||
Fauxcurrences = "0.2" | ||
GBIF = "0.4" | ||
GeoJSON = "0.8" | ||
MakieCore = "0.8" | ||
Phylopic = "0.0" | ||
PolygonOps = "0.1" | ||
Reexport = "1.2" | ||
SimpleSDMDatasets = "0.2" | ||
SimpleSDMLayers = "1.0" | ||
StatsBase = "0.34" | ||
TestItems = "1" | ||
ZipFile = "0.10" | ||
julia = "1.8" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# # Working with polygons | ||
|
||
using SpeciesDistributionToolkit | ||
using CairoMakie | ||
import GeoJSON | ||
CairoMakie.activate!(; type = "png", px_per_unit = 3.0) #hide | ||
|
||
# In this tutorial, we will clip a layer to a polygon (in GeoJSON format), then | ||
# use the same polygon to filter GBIF records. | ||
|
||
# ::: warning About coordinates | ||
# | ||
# [GeoJSON](https://geojson.org/) coordinates are expressed in WGS84. For this | ||
# reason, *any* polygon is assumed to be in this CRS, and all operations will be | ||
# done by projecting the layer coordinates to this CRS. | ||
# | ||
# ::: | ||
|
||
# We provide a very lightweight wrapper around the | ||
# [GADM](https://gadm.org/index.html) database, which will return data as | ||
# ready-to-use GeoJSON files. For example, we can get the borders of | ||
# Switzerland, as featured in [Damaris Zurell excellent SDM | ||
# tutorial](https://damariszurell.github.io/SDM-Intro/). | ||
|
||
CHE = SpeciesDistributionToolkit.gadm("CHE") | ||
|
||
# ::: details More about GADM | ||
# | ||
# The only other function to interact with GADM is `gadmlist`, which takes either | ||
# a series of places, as in | ||
# | ||
SpeciesDistributionToolkit.gadmlist("FRA", "Bretagne") | ||
# | ||
# or a level, to provide a list of places within a three-letter coded area at a | ||
# specific depth: | ||
# | ||
SpeciesDistributionToolkit.gadmlist("FRA", 3)[1:3] | ||
# | ||
# ::: | ||
|
||
# The next step is to get a layer, and so we will download the data about | ||
# deciduous broadleaf trees from [EarthEnv](/datasets/EarthEnv#landcover): | ||
|
||
provider = RasterData(WorldClim2, Elevation) | ||
layer = SDMLayer( | ||
provider; | ||
resolution = 0.5, | ||
left = 0.0, | ||
right = 20.0, | ||
bottom = 35.0, | ||
top = 55.0, | ||
) | ||
|
||
# We can check that this polygon is larger than we want: | ||
|
||
heatmap(layer; colormap = :navia, axis = (; aspect = DataAspect())) | ||
|
||
# We can now mask this layer according to the polygon: | ||
|
||
mask!(layer, CHE[1].geometry) | ||
heatmap(layer; colormap = :navia, axis = (; aspect = DataAspect())) | ||
|
||
# This is a much larger layer than we need! For this reason, we will trim it so that the empty areas are removed: | ||
|
||
heatmap(trim(layer); colormap = :navia, axis = (; aspect = DataAspect())) | ||
|
||
# Let's now get some occurrences in the area defined by the layer boundingbox, | ||
# while specifying the dataset key for the [eBird Observation | ||
# Dataset](https://www.gbif.org/dataset/4fa7b334-ce0d-4e88-aaae-2e0c138d049e): | ||
|
||
ouzel = taxon("Turdus torquatus") | ||
presences = occurrences( | ||
ouzel, | ||
trim(layer), | ||
"occurrenceStatus" => "PRESENT", | ||
"limit" => 300, | ||
"datasetKey" => "4fa7b334-ce0d-4e88-aaae-2e0c138d049e", | ||
) | ||
|
||
# ::: details Occurrences from a layer | ||
# | ||
# The `GBIF.occurrences` method can accept a layer as its second argument, to | ||
# limit to the occurrence of a species within the bounding box of this layer. If | ||
# a layer is used as the sole argument, all occurrences in the bounding box are | ||
# queried. | ||
# | ||
# ::: | ||
|
||
while length(presences) < count(presences) | ||
occurrences!(presences) | ||
end | ||
|
||
# We can plot the layer and all occurrences: | ||
|
||
heatmap(trim(layer); colormap = :navia, axis = (; aspect = DataAspect())) | ||
scatter!(presences; color = :orange, markersize = 4) | ||
current_figure() #hide | ||
|
||
# Some of these occurrences are outside of the masked region in the layer. For | ||
# this reason, we will use the *non-mutating* `mask` method on the GBIF records: | ||
|
||
heatmap(trim(layer); colormap = :navia, axis = (; aspect = DataAspect())) | ||
scatter!(mask(presences, CHE[1].geometry); color = :orange, markersize = 4) | ||
current_figure() #hide | ||
|
||
# ::: details A note about vectors of occurrences | ||
# | ||
# The reason why `mask` called on a GBIF result is not mutating the result is | ||
# that GBIF results also store the query that was used. For this reason, it | ||
# makes little sense to modify this object. The non-mutating `mask` returns a | ||
# vector of GBIF records, which for most purposes can be used in-place of the | ||
# result. | ||
# | ||
# ::: | ||
|
||
#- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
function _get_gadm_file(code, level) | ||
# Prepare to store the data | ||
gadm_root = joinpath(SimpleSDMDatasets._LAYER_PATH, "GADM", code) | ||
if !ispath(gadm_root) | ||
mkpath(gadm_root) | ||
end | ||
# Get the URL for the file | ||
fname = "gadm41_$(code)_$(level).json" | ||
if level > 0 | ||
fname *= ".zip" | ||
end | ||
url = "https://geodata.ucdavis.edu/gadm/gadm4.1/json/$(fname)" | ||
# Download the file | ||
if ~isfile(joinpath(gadm_root, fname)) | ||
try | ||
Downloads.download(url, joinpath(gadm_root, fname)) | ||
catch | ||
return nothing | ||
end | ||
end | ||
# Read the file in GeoJSON | ||
gadm_file = joinpath(gadm_root, fname) | ||
if level > 0 | ||
gadm_zip = ZipFile.Reader(gadm_file) | ||
for f in gadm_zip.files | ||
if f.name == replace(fname, ".zip" => "") | ||
out = open(joinpath(gadm_root, f.name), "w") | ||
write(out, read(f, String)) | ||
close(out) | ||
end | ||
end | ||
end | ||
# Return | ||
return GeoJSON.read(replace(gadm_file, ".zip" => "")) | ||
end | ||
|
||
gadm(code::String) = _get_gadm_file(code, 0) | ||
|
||
function gadm(code::String, places::String...) | ||
level = length(places) | ||
avail = _get_gadm_file(code, level) | ||
position = only( | ||
reduce( | ||
intersect, | ||
[ | ||
findall(isequal(replace(places[i], " " => "")), getproperty(avail, Symbol("NAME_$(i)"))) | ||
for | ||
i in 1:level | ||
], | ||
), | ||
) | ||
return avail.geometry[position] | ||
end | ||
|
||
gadmlist(code::String) = getproperty(_get_gadm_file(code, 1), :NAME_1) | ||
gadmlist(code::String, level::Integer) = | ||
getproperty(_get_gadm_file(code, level), Symbol("NAME_$(level)")) | ||
function gadmlist(code::String, places::String...) | ||
level = length(places) + 1 | ||
avail = _get_gadm_file(code, level) | ||
position = reduce( | ||
intersect, | ||
[ | ||
findall(isequal(replace(places[i], " " => "")), getproperty(avail, Symbol("NAME_$(i)"))) for | ||
i in 1:(level - 1) | ||
], | ||
) | ||
return getproperty(avail, Symbol("NAME_$(level)"))[position] | ||
end |
Oops, something went wrong.