Skip to content

Commit

Permalink
Merge pull request #29 from raphaelquast/dev
Browse files Browse the repository at this point in the history
merge for v2.0.2
  • Loading branch information
raphaelquast authored Nov 30, 2021
2 parents debdb54 + eb23590 commit 1006d57
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 51 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ docs/_build
docs/debug.log
docs/.vscode
docs/generated
.spyproject
58 changes: 38 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,49 @@
[![codecov](https://codecov.io/gh/raphaelquast/EOmaps/branch/dev/graph/badge.svg?token=25M85P7MJG)](https://codecov.io/gh/raphaelquast/EOmaps)
[![pypi](https://img.shields.io/pypi/v/eomaps)](https://pypi.org/project/eomaps/)
[![Documentation Status](https://readthedocs.org/projects/eomaps/badge/?version=latest)](https://eomaps.readthedocs.io/en/latest/?badge=latest)
<a href="https://www.buymeacoffee.com/raphaelquast" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png" alt="Buy Me A Coffee" align="right" style="height: 33px !important;width: 139px !important;" ></a>


# EOmaps

### A library to create interactive maps of geographical datasets.
### ... a library to create interactive maps of geographical datasets

#### 🌍 Simple interface to visualize geographical datasets
- A `pandas.DataFrame` is all you need as input!
- plots of large (>1M datapoints) irregularly sampled datasets are generated in a few seconds!
- Represent your data as shapes with actual geographic dimensions
- Re-project the data to any crs supported by `cartopy`
- Add annotations, overlays, WebMap-layers etc. to the maps
- ... and get a nice colorbar with a colored histogram on top!
<ol type="none">
<li>🌍 A simple interface to visualize geographical datasets ... a pandas DataFrame is all you need!</li>
<ul type="none">
<li>⬥ applicable also for large datasets with ~ 1M datapoints! </li>
</ul>
<li>🌎 Quickly turn your maps into powerful interactive data-analysis widgets!</li>
<ul type="none">
<li>⬥ compare multiple data-layers, WebMaps etc. with only a few lines of code! </li>
<li>⬥ use callback functions to interact with the data (or an underlying database) </li>
</ul>
</ol>

#### 🌎 Turn your maps into powerful interactive data-analysis widgets
- Add "callbacks" to interact with your data
- Many pre-defined functions for common tasks are available!
- display coordinates and values, add markers, compare data-layers etc.
- ... or define your own function and attach it to the plot!
- Maps objects can be interactively connected to analyze relations between datasets!

#### 🛸 check out the [documentation](https://eomaps.readthedocs.io) for more details and [examples](https://eomaps.readthedocs.io/en/latest/EOmaps_examples.html)! 🛸
#### 🛸 checkout the [documentation](https://eomaps.readthedocs.io>documentation) for more details and [examples](https://eomaps.readthedocs.io/en/latest/EOmaps_examples.html) 🛸

## 🔨 installation

![EOmaps example image](https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig2.gif?raw=true)
Installing EOmaps can be done via `pip`.
However, to make sure all dependencies are correctly installed, make sure to have a look at the [installation instructions](https://eomaps.readthedocs.io/en/latest/usage.html#installation) in the documentation!

<br/>

## install
<p align="center">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig2.gif?raw=true" alt="EOmaps example image 1">
<img src="https://github.com/raphaelquast/EOmaps/blob/dev/docs/_static/fig6.gif?raw=true" alt="EOmaps example image 2">
</p>

Installing EOmaps can be done via `pip`. To make sure all dependencies are correctly installed, please have a look at the [🛸 installation instructions 🛸](https://eomaps.readthedocs.io/en/latest/usage.html#installation) in the documentation.

## 🌳 basic usage
- A pandas DataFrame is all you need as input!
- plots of large (>1M datapoints) irregularly sampled datasets are generated in a few seconds!
- Represent your data as shapes with actual geographic dimensions
- Re-project the data to any crs supported by `cartopy`
- Add annotations, overlays, WebMap-layers etc. to the maps
- ... and get a nice colorbar with a colored histogram on top!

## basic usage
```python
import pandas as pd
from eomaps import Maps
Expand All @@ -54,6 +66,12 @@ m.set_classify_specs(scheme=Maps.CLASSIFIERS.Quantiles, k=5)
m.plot_map()
```
#### attach callback functions to interact with the plot

- Many pre-defined functions for common tasks are available!
- display coordinates and values, add markers, compare data-layers etc.
- ... or define your own function and attach it to the plot!
- Maps objects can be interactively connected to analyze relations between datasets!

```python
# get a nice annotation if you click on a datapoint
m.cb.pick.attach.annotate()
Expand Down Expand Up @@ -97,7 +115,7 @@ m2.set_shape(...)
m2.plot_map(layer=2) # plot another layer of data
m2.cb.attach.peek_layer(layer=2, how=0.25)
```
#### plot map-grids
#### plot grids of maps
```python
from eomaps import MapsGrid
mgrid = MapsGrid(2, 2, connect=True)
Expand Down
76 changes: 72 additions & 4 deletions docs/EOmaps_examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,11 @@ There are 3 basic steps required to visualize your data:
🌲 🏡🌳 Add overlays and indicators
-----------------------------------

| … and finally, here's an a bit more advanced example
| (… plot-generation might take a bit longer since overlays need to be downloaded first!)
- with overlays add fancy static annotations / markers
- ...and “connected” Maps-objects to get multiple interactive data-layers!
(… plot-generation might take a bit longer since overlays need to be downloaded first!)

- add basic overlays with `m.add_overlay`
- add static annotations / markers with `m.add_annotation` and `m.add_marker`
- use “connected” Maps-objects to get multiple interactive data-layers!


|toggleStart|
Expand Down Expand Up @@ -429,3 +430,70 @@ There are 3 basic steps required to visualize your data:
|toggleEnd|

.. image:: _static/fig5.gif



🛰 WebMap services and layer-switching
--------------------------------------

- add WebMap services using ``m.add_wms`` and ``m.add_wmts``
- compare different data-layers and WebMap services using ``m.cb.click.peek_layer`` and ``m.cb.keypress.switch_layer``

|toggleStart|

.. code-block:: python
from eomaps import Maps
import numpy as np
import pandas as pd
# create some data
lon, lat = np.meshgrid(np.linspace(-50, 50, 150), np.linspace(30,60, 150))
data = pd.DataFrame(dict(lon=lon.flat, lat=lat.flat, data=np.sqrt(lon**2 + lat**2).flat))
# --------------------------------
m = Maps()
# set the crs to GOOGLE_MERCATOR to avoid reprojecting the WebMap data
# (makes it a lot faster and it will also look much nicer!)
m.plot_specs.crs = Maps.CRS.GOOGLE_MERCATOR
# ------------- LAYER 0
# add S1GBM as a base-layer
wms1 = m.add_wms.S1GBM
wms1.add_layer.vv()
# ------------- LAYER 1
# add OpenStreetMap on the currently invisible layer (1)
wms2 = m.add_wms.OpenStreetMap.OSM_mundialis
wms2.add_layer.OSM_WMS(layer=1)
# ------------- LAYER 2
# create a connected maps-object and plot some data on a new layer (2)
m2 = m.copy(connect=True, layer=2)
m2.set_data(data=data.sample(5000), xcoord="lon", ycoord="lat", crs=4326)
m2.set_shape.geod_circles(radius=20000)
m2.plot_map(coastlines=False)
m2.add_wms.S1GBM.add_layer.vv() # add S1GBM as background on layer 2 as well
# ------------ CALLBACKS
# on a left-click, show layer 1 in a rectangle (with a size of 20% of the axis)
m.cb.click.attach.peek_layer(layer=1, how=(.2, .2))
# on a right-click, "swipe" layer (2) from the left
m.cb.click.attach.peek_layer(layer=2, how="left", button=3)
m.cb.keypress.attach.switch_layer(layer=0, key="0")
m.cb.keypress.attach.switch_layer(layer=1, key="1")
m.cb.keypress.attach.switch_layer(layer=2, key="2")
# ------------------------------
m.figure.f.set_size_inches(9, 4)
m.figure.gridspec.update(left=0.01, right=0.99, bottom=0.01, top=0.99)
|toggleEnd|

.. image:: _static/fig6.gif

The data displayed in the above gif is taken from:
- Sentinel-1 Global Backscatter Model (https://researchdata.tuwien.ac.at/records/n2d1v-gqb91)
- OpenStreetMap hosted by Mundialis (https://www.mundialis.de/en/ows-mundialis/)
Binary file added docs/_static/fig6.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

🐛 Installation
############
(To speed up the process... have a look at `(mamba) <https://github.com/mamba-org/mamba>`_ )


🐜 Manual installation
-------------------
Expand Down Expand Up @@ -54,7 +56,7 @@ Here's a yaml-file that you can use to install all you need in one go:
dependencies:
- python=3.7
- default::rtree
- defaults::rtree
- numpy
- scipy
- pandas
Expand Down
4 changes: 2 additions & 2 deletions eomaps/_cb_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,8 @@ def _add_callback(self, callback, double_click=False, button=1, **kwargs):

if callback.__name__ not in multi_cb_functions:
assert len(ncb) == 0, (
"Multiple assignments of the callback"
+ f" '{callback.__name__}' are not (yet) supported..."
f"Multiple assignments of the callback '{callback.__name__}' using "
+ "the same button is not (yet) supported... use a different button!"
)

d[cbkey] = partial(callback, **kwargs)
Expand Down
10 changes: 7 additions & 3 deletions eomaps/_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,13 @@ def MARINE(self):

@property
def S1GBM(self):
return SimpleNamespace(add_layer=self._S1GBM_layers(self._m))
ret = SimpleNamespace(
add_layer=self._S1GBM_layers(self._m), layers=["vv", "vh"]
)

ret.__doc__ = self._S1GBM_layers.__doc__

return ret

class _S1GBM_layers:
"""
Expand All @@ -939,8 +945,6 @@ class _S1GBM_layers:

def __init__(self, m):
self._m = m
# make sure axes are set
self._m._set_axes()

@property
@lru_cache()
Expand Down
48 changes: 28 additions & 20 deletions eomaps/_webmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,10 @@ def __call__(self, layer=None, **kwargs):
art = self._m.figure.ax.add_wmts(
self._wms, self.name, wmts_kwargs=kwargs, interpolation="spline36"
)
if layer is not None:
self._m.BM.add_bg_artist(art, layer)
if layer is None:
layer = self._m.layer

self._m.BM.add_bg_artist(art, layer)


class _wms_layer(_WebMap_layer):
Expand All @@ -289,8 +291,11 @@ def __call__(self, layer=None, **kwargs):
art = self._m.figure.ax.add_wms(
self._wms, self.name, wms_kwargs=kwargs, interpolation="spline36"
)
if layer is not None:
self._m.BM.add_bg_artist(art, layer)

if layer is None:
layer = self._m.layer

self._m.BM.add_bg_artist(art, layer)


def _sanitize(s):
Expand Down Expand Up @@ -616,16 +621,17 @@ def __call__(self, layer=None):
self._event_attached = self._m.figure.f.canvas.mpl_connect(
"draw_event", self.ondraw
)
# TODO do this only once on the grandparent!
if self._m.figure.f.canvas.toolbar is not None:
self._m.figure.f.canvas.toolbar.release_zoom = self.zoom_decorator(
self._m.figure.f.canvas.toolbar.release_zoom
)
self._m.figure.f.canvas.toolbar.release_pan = self.zoom_decorator(
self._m.figure.f.canvas.toolbar.release_pan
)
toolbar = self._m.figure.f.canvas.toolbar

if toolbar is not None:
toolbar.release_zoom = self.zoom_decorator(toolbar.release_zoom)
toolbar.release_pan = self.zoom_decorator(toolbar.release_pan)
toolbar._update_view = self.update_decorator(toolbar._update_view)

self._layer = layer
if layer is None:
self._layer = self._m.layer
else:
self._layer = layer

self._S1GBM_factory = self.S1GBM_tiles()
self._S1GBM_factory.polarization = self.pol
Expand All @@ -649,12 +655,7 @@ def redraw(self):
img, extent=extent, origin=origin, transform=self._S1GBM_factory.crs
)

if self._layer is not None:
self._m.BM.add_bg_artist(self._S1GBM, self._layer)
else:
# always put the images on a background-layer
self._layer = 0
self._m.BM.add_bg_artist(self._S1GBM, 0)
self._m.BM.add_bg_artist(self._S1GBM, self._layer)

def ondraw(self, event):
if self._event_attached is not None:
Expand All @@ -665,8 +666,15 @@ def ondraw(self, event):
def zoom_decorator(self, f):
def newzoom(event):
ret = f(event)

self.redraw()
return ret

return newzoom

def update_decorator(self, f):
def newupdate():
ret = f()
self.redraw()
return ret

return newupdate
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
this_directory = Path(__file__).parent
long_description = (this_directory / "README.md").read_text()

version = "2.0.1"
version = "2.0.2"

setup(
name="EOmaps",
Expand Down

0 comments on commit 1006d57

Please sign in to comment.