Skip to content

Commit

Permalink
Merge pull request #79 from raphaelquast/dev
Browse files Browse the repository at this point in the history
EOmaps v4.2
  • Loading branch information
raphaelquast authored Jun 3, 2022
2 parents 8e9db5a + c7bece2 commit 11a5f8a
Show file tree
Hide file tree
Showing 12 changed files with 539 additions and 101 deletions.
15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,8 @@
</ul>
<br/>

---

### ❗ update notice ❗
> There are breaking API changes between `EOmaps v3.x` and `EOmaps v4.0`
> To quickly update existing scripts, see: [⚙ Port script from EOmaps v3.x to v4.x](https://eomaps.readthedocs.io/en/latest/FAQ.html#port-script-from-eomaps-v3-x-to-v4-x)
---
> ❗ update notice: [⚙ Port script from EOmaps v3.x to v4.x](https://eomaps.readthedocs.io/en/latest/FAQ.html#port-script-from-eomaps-v3-x-to-v4-x)

## 🔨 Installation
Expand Down Expand Up @@ -80,19 +75,19 @@ Open an [issue](https://github.com/raphaelquast/EOmaps/issues) or start a [discu
---------------
<table>
<tr>
<td valign="center">
<td valign="center" style="width:50%">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig6.gif?raw=true" alt="EOmaps example 6">
</td>
<td valign="center">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig2.gif?raw=true" alt="EOmaps example 2">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/minigifs/advanced_wms.gif?raw=true" alt="EOmaps example 2">
</td>
</tr>
<tr>
<td valign="center">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig7.gif?raw=true" alt="EOmaps example 7">
</td>
<td valign="center">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig8.gif?raw=true" alt="EOmaps example 8">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig2.gif?raw=true" alt="EOmaps example 8">
</td>
</tr>
<tr>
Expand All @@ -108,7 +103,7 @@ Open an [issue](https://github.com/raphaelquast/EOmaps/issues) or start a [discu
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/inset_maps.png?raw=true" alt="EOmaps inset-maps">
</td>
<td valign="center">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig3.png?raw=true" alt="EOmaps example 3">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig8.gif?raw=true" alt="EOmaps example 3">
</td>
</tr>
</table>
Expand Down
Binary file added docs/_static/minigifs/advanced_wms.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
109 changes: 104 additions & 5 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Possible ways for specifying the crs for plotting are:
Maps.new_layer
Maps.all
Maps.show_layer
Maps.copy
Maps.fetch_layers



Expand Down Expand Up @@ -613,6 +613,7 @@ Callbacks that can be used with `m.cb.keypress`
:template: only_names_in_toc.rst

switch_layer
fetch_layers

Pre-defined dynamic callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -751,9 +752,10 @@ and ``< LAYER >`` indicates the actual layer-name.
+------------------------------------------------------------------------------------------------+-----------------------------------------+


Pre-defined WebMap services:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Pre-defined (global) WebMap services:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Global:**

.. currentmodule:: eomaps._containers.wms_container

Expand All @@ -771,8 +773,9 @@ Pre-defined (global) WebMap services:
S1GBM
S2_cloudless
GEBCO
CAMS

Services specific for Austria (Europa)
**Services specific for Austria (Europe)**

.. currentmodule:: eomaps._containers

Expand All @@ -787,7 +790,7 @@ Services specific for Austria (Europa)

.. note::
Services might be nested directory structures!
The actual layer is always added via the `add_layer` directive.
The actual layer is always added via the ``add_layer`` directive.

:code:`m.add_wms.<...>. ... .<...>.add_layer.<LAYER NAME>()`

Expand All @@ -798,6 +801,102 @@ Services specific for Austria (Europa)

:code:`m.add_wms.<...>. ... .<LAYER NAME>.layers`

Using custom WebMap services
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It is also possible to use custom WMS/WMTS/XYZ services.
(see docstring of ``m.add_wms.get_service`` for more details and examples)

.. currentmodule:: eomaps._containers.wms_container

.. autosummary::
:toctree: generated
:nosignatures:
:template: only_names_in_toc.rst

get_service


.. code-block:: python
m = Maps()
# define the service
service = m.add_wms.get_service(<... link to GetCapabilities.xml ...>,
service_type="wms",
res_API=False,
maxzoom=19)
# once the service is defined, you can use it just like the pre-defined ones
service.layers # >> get a list of all layers provided by the service
# select one of the layers
layer = service.add_layer. ... .< LAYER >
layer.info # >> get some additional infos for the selected layer
layer.set_extent_to_bbox() # >> set the map-extent to the bbox of the layer
# call the layer to add it to the map
# (optionally providing additional kwargs for fetching map-tiles)
layer(...)
Setting date, style and other WebMap properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Some WebMap services allow passing additional arguments to set properties such as the **date** or the **style** of the map.
To pass additional arguments to a WebMap service, simply provide them when when calling the layer, e.g.:

.. code-block:: python
m = Maps()
m.add_wms.< SERVICE >. ... .add_layer.< LAYER >(time=..., styles=[...], ...)
To show an example, here's how to fetch multiple timestamps for the UV-index of the Copernicus Airquality service.
(provided by https://atmosphere.copernicus.eu/)

.. table::
:widths: 50 50
:align: center

+-------------------------------------------------------------------------------------+----------------------------------------------+
| .. code-block:: python | .. image:: _static/minigifs/advanced_wms.gif |
| | :align: center |
| from eomaps import Maps | |
| import pandas as pd | |
| | |
| m = Maps(layer="all", figsize=(8, 4)) | |
| m.subplots_adjust(left=0.05, right=0.95) | |
| m.all.add_feature.preset.coastline() | |
| m.add_logo() | |
| | |
| layer = m.add_wms.CAMS.add_layer.composition_uvindex_clearsky | |
| timepos = layer.wms_layer.timepositions # available time-positions | |
| all_styles = list(layer.wms_layer.styles) # available styles | |
| | |
| # create a list of timestamps to fetch | |
| start, stop, freq = timepos[1].split(r"/") | |
| times = pd.date_range(start, stop, freq=freq.replace("PT", "")) | |
| times = times.strftime("%Y-%m-%dT%H:%M:%SZ") | |
| | |
| style = all_styles[0] # use the first available style | |
| for time in times[:6]: | |
| # call the layer to add it to the map | |
| layer(time=time, | |
| styles=[style], # provide a list with 1 entry here | |
| layer=time # put each WebMap on an individual layer | |
| ) | |
| | |
| layer.add_legend() # add a legend for the WebMap service | |
| | |
| # add a "slider" and a "selector" widget | |
| m.util.layer_selector(ncol=3, loc="upper center", fontsize=6, labelspacing=1.3) | |
| m.util.layer_slider() | |
| | |
| # attach a callback to fetch all layers if you press l on the keyboard | |
| cid = m.all.cb.keypress.attach.fetch_layers(key="l") | |
| # fetch all layers so that they are cached and switching layers is fast | |
| m.fetch_layers() | |
| m.show_layer(times[0]) # make the first timestamp visible | |
+-------------------------------------------------------------------------------------+----------------------------------------------+


.. _geodataframe:

Expand Down
41 changes: 40 additions & 1 deletion eomaps/_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2031,7 +2031,7 @@ def vh(self):
@lru_cache()
def S2_cloudless(self):
"""
Global cloudless Sentinel-2 maps, crafted by EOX.
Global cloudless Sentinel-2 maps, crafted by EOX
https://s2maps.eu/
Endless sunshine, eternal summer - the Sentinel-2 cloudless layer combines
Expand Down Expand Up @@ -2065,6 +2065,45 @@ def S2_cloudless(self):
WMS.__doc__ = type(self).S2_cloudless.__doc__
return WMS

@property
@lru_cache()
def CAMS(self):
"""
Copernicus Atmosphere Monitoring Service (Global and European)
https://atmosphere.copernicus.eu/
A selection of global and European air quality products hosted by ECMWF
(http://eccharts.ecmwf.int)
For details on the available layers, see:
https://confluence.ecmwf.int/display/CKB/WMS+for+CAMS+Global+and+European+air+quality+products
Note
----
**LICENSE-info (without any warranty for correctness!!)**
Access to Copernicus Products is given for any purpose in so far as it
is lawful, whereas use may include, but is not limited to: reproduction;
distribution; communication to the public; adaptation, modification and
combination with other data and information; or any combination of the
foregoing.
All users of Copernicus Products must provide clear and visible attribution
to the Copernicus programme. The Licensee will communicate to the public
the source of the Copernicus Products by crediting the Copernicus Climate
Change and Atmosphere Monitoring Services.
(check: https://apps.ecmwf.int/datasets/licences/copernicus/ for full details)
"""
WMS = _WebServiec_collection(
m=self._m,
service_type="wms",
url="https://eccharts.ecmwf.int/wms/?token=public",
)

WMS.__doc__ = type(self).CAMS.__doc__
return WMS

@property
@lru_cache()
def ESRI_ArcGIS(self):
Expand Down
48 changes: 14 additions & 34 deletions eomaps/_shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,8 @@ def __call__(self, aggregator=None, shade_hook=None, agg_hook=None):

if aggregator is None:
aggregator = ds.mean("val")
elif isinstance(aggregator, str):
aggregator = getattr(ds, aggregator)("val")

if shade_hook is None:
shade_hook = partial(ds.tf.dynspread, max_px=5)
Expand Down Expand Up @@ -1384,7 +1386,6 @@ def __call__(self, aggregator="mean", shade_hook=None, agg_hook=None):

for m in self._m if isinstance(self._m, MapsGrid) else [self._m]:
shape = self.__class__(m)

shape.aggregator = aggregator
shape.shade_hook = shade_hook
shape.agg_hook = agg_hook
Expand Down Expand Up @@ -1485,14 +1486,14 @@ def _get_rectangle_verts(self, x, y, crs):

# try to find the radius based on the first row/col of the data
# (a shortcut for very large datasets...)
rx = np.diff(x[0])[0]
ry = np.diff(y.T[0])[0]
rx = np.diff(x[0])[0] / 2
ry = np.diff(y.T[0])[0] / 2
if not np.isfinite([rx, ry]).all():
# if no finite radius is found, search for the radius in the whole array
dx = np.diff(x, axis=1)
dy = np.diff(y, axis=0)
rx = abs(dx[np.isfinite(dx)][0])
ry = abs(dy[np.isfinite(dy)][0])
rx = abs(dx[np.isfinite(dx)][0]) / 2
ry = abs(dy[np.isfinite(dy)][0]) / 2

self._radius = rx, ry
p = x, y
Expand All @@ -1517,29 +1518,7 @@ def _get_rectangle_verts(self, x, y, crs):
else:
clipx, clipy = lambda x: x, lambda y: y

x0 = clipx(p[0] - rx)
x1 = clipx(p[0] + rx)
y0 = clipx(p[1] - ry)
y1 = clipx(p[1] + ry)

px = np.column_stack(
(
np.linspace(x0, x1, n).T.flat,
np.repeat([x1], n, axis=0).T.flat,
np.linspace(x1, x0, n).T.flat,
np.repeat([x0], n).T.flat,
)
)

py = np.column_stack(
(
np.repeat([y1], n, axis=0).T.flat,
np.linspace(y1, x0, n).T.flat,
np.repeat([y0], n, axis=0).T.flat,
np.linspace(y0, x1, n).T.flat,
)
)

# distribute the values as rectangle vertices
v = np.full((p[0].shape[0] + 1, p[0].shape[1] + 1, 2), None, dtype=float)

v[:-1, :-1, 0] = p[0] - rx
Expand Down Expand Up @@ -1576,22 +1555,23 @@ def _get_polygon_coll(self, x, y, crs, **kwargs):
# matplotlib.collections.QuadMesh.get_cursor_data
color_and_array[key] = val.ravel()

# temporary fix for https://github.com/matplotlib/matplotlib/issues/22908
QuadMesh.get_cursor_data = lambda *args, **kwargs: None

coll = QuadMesh(
verts,
**color_and_array,
**kwargs,
)

# temporary fix for https://github.com/matplotlib/matplotlib/issues/22908
# QuadMesh.get_cursor_data = lambda *args, **kwargs: None
coll.get_cursor_data = lambda *args, **kwargs: None
# temporary fix for https://github.com/matplotlib/matplotlib/issues/23164
# (no need for .contains in EOmaps since pixels are identified internally)
coll.contains = lambda *args, **kwargs: [False]

return coll

def get_coll(self, x, y, crs, **kwargs):
x, y = np.asanyarray(x), np.asanyarray(y)
assert (
len(x.shape) == 2 and len(y.shape) == 2
), "EOmaps: using 'raster' as plot-shape is only possible for 2D datasets!"
# don't use antialiasing by default since it introduces unwanted
# transparency for reprojected QuadMeshes!
kwargs.setdefault("antialiased", False)
Expand Down
Loading

0 comments on commit 11a5f8a

Please sign in to comment.