Skip to content

Commit

Permalink
Merge pull request #12 from nens/casper-ipyleaflet
Browse files Browse the repository at this point in the history
An ipyleaflet plugin for visualizing views in Jupyter notebook
  • Loading branch information
caspervdw committed Oct 31, 2019
2 parents 2d4e35b + fd346b5 commit b86f102
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ Changelog of dask-geomodeling

- Implemented the geo_transform attribute of elementwise raster blocks.

- Added an ipyleaflet plugin for visualizing RasterBlocks in jupyter notebook.

- Changed the default geomodeling.root setting to the current working directory


2.0.3 (2019-10-08)
------------------
Expand Down
3 changes: 2 additions & 1 deletion dask_geomodeling/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import dask.config
import os

defaults = {
"root": "/",
"root": os.getcwd(),
"raster-limit": 12 * (1024 ** 2), # ca. 100 MB of float64
"geometry-limit": 10000,
}
Expand Down
137 changes: 137 additions & 0 deletions dask_geomodeling/ipyleaflet_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from datetime import datetime
from io import BytesIO

import numpy as np
import traitlets
from ipyleaflet import Map, WMSLayer
from matplotlib import cm
from matplotlib.colors import Normalize
from notebook import notebookapp
from notebook.base.handlers import IPythonHandler
from notebook.utils import url_path_join
from PIL import Image

from dask_geomodeling.core import Block


class GeomodelingWMSHandler(IPythonHandler):
"""This Tornado request handler adds a WMS functionality for displaying
dask-geomodeling results in a notebook
See:
https://jupyter-notebook.readthedocs.io/en/stable/extending/handlers.html
"""
def get(self):
block = Block.from_json(self.get_query_argument("layers"))
style = self.get_query_argument("styles")
vmin = float(self.get_query_argument("vmin"))
vmax = float(self.get_query_argument("vmax"))
format = self.get_query_argument("format")
if format.lower() != "image/png":
self.set_status(400)
self.finish("Only image/png is supported")
return
srs = self.get_query_argument("srs")
height = int(self.get_query_argument("height"))
max_cell_size = float(self.get_query_argument("maxcellsize"))
time_isoformat = self.get_query_argument("time")
if time_isoformat:
time = datetime.strptime(time_isoformat, "%Y-%m-%dT%H:%M:%S.%fZ")
else:
time = None
width = int(self.get_query_argument("width"))
bbox = [float(x) for x in self.get_query_argument("bbox").split(",")]

# overload protection
cell_size_x = (bbox[2] - bbox[0]) / width
cell_size_y = (bbox[3] - bbox[1]) / height
if cell_size_x > max_cell_size or cell_size_y > max_cell_size:
self.set_status(400)
self.finish("Too large area requested")
return

# get cmap
data = block.get_data(
mode="vals",
bbox=bbox,
height=height,
width=width,
projection=srs,
start=time,
)
masked = np.ma.masked_equal(data["values"][0], data["no_data_value"])
stream = BytesIO()

normalized = Normalize(vmin=vmin, vmax=vmax, clip=True)(masked)
img = cm.get_cmap(style)(normalized)
img[normalized.mask, 3] = 0.0
img_uint8 = (img * 255).astype(np.uint8)
Image.fromarray(img_uint8).save(stream, format="png")
raw = stream.getvalue()

self.set_header("Content-Length", len(raw))
self.set_header("Content-Type", "image/png")
self.set_header("Pragma", "no-cache")
self.set_header(
"Cache-Control",
"no-store, "
"no-cache=Set-Cookie, "
"proxy-revalidate, "
"max-age=0, "
"post-check=0, pre-check=0",
)
self.set_header("Expires", "Wed, 2 Dec 1837 21:00:12 GMT")
self.write(raw)
self.finish()


class GeomodelingLayer(WMSLayer):
"""Visualize a dask_geomodeling.RasterBlock on a ipyleaflet Map.
:param block: a dask_geomodeling.RasterBlock instance to visualize
:param hostname: The hostname of the jupyter server
:param style: a valid matplotlib colormap
:param vmin: the minimum value (for the colormap)
:param vmax: the maximum value (for the colormap)
Notes
-----
To use this ipyleaflet extension, you have to include this plugin into your
Jupyter Notebook server by calling::
$ jupyter notebook --NotebookApp.nbserver_extensions="{'dask_geomodeling.ipyleaflet_plugin':True}"
Or, by adding this setting to the config file in ~/.jupyter, which can be
generated by calling::
$ jupyter notebook --generate-config
This plugin extends the Jupyter notebook server with a server that responds
with PNG images for WMS requests generated by ipyleaflet.
"""
format = traitlets.Unicode("image/png").tag(sync=True, o=True)
maxcellsize = traitlets.Float(10.0).tag(sync=True, o=True)
time = traitlets.Unicode("").tag(sync=True, o=True)
vmin = traitlets.Float(0.0).tag(sync=True, o=True)
vmax = traitlets.Float(1.0).tag(sync=True, o=True)

def __init__(self, block, hostname="localhost", **kwargs):
for server in notebookapp.list_running_servers():
if server["hostname"] == hostname:
kwargs["url"] = server["url"] + "wms"
break
self.layers = block.to_json()
super().__init__(**kwargs)


def load_jupyter_server_extension(nb_server_app):
"""
Called when the extension is loaded.
Args:
nb_server_app (NotebookWebApplication): handle to the Notebook webserver instance.
"""
web_app = nb_server_app.web_app
host_pattern = ".*$"
route_pattern = url_path_join(web_app.settings["base_url"], "/wms")
web_app.add_handlers(host_pattern, [(route_pattern, GeomodelingWMSHandler)])
27 changes: 22 additions & 5 deletions docs/installation.rst
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
Installation
============

Local setup (using conda)
-------------------------
Recommended: use conda
----------------------

1. `Install anaconda <https://docs.anaconda.com/anaconda/install/>`_
2. Run ``conda install dask-geomodeling -c conda-forge``


Local setup with system Python (Ubuntu)
---------------------------------------
Using the ipyleaflet plugin
---------------------------

dask-geomodeling comes with a ipyleaflet plugin for `Jupyter<https://jupyter.org/>`_
so that you can show your generated views on a mapviewer. If you want to use
it, install some additional dependencies::

$ conda install jupyter ipyleaflet matplotlib pillow

And start your notebook server with the plugin::

$ jupyter notebook --NotebookApp.nbserver_extensions="{'dask_geomodeling.ipyleaflet_plugin':True}"

Alternatively, you can add this extension to your
`Jupyter configuration<https://jupyter-notebook.readthedocs.io/en/stable/config_overview.html>`_


Advanced: local setup with system Python (Ubuntu)
-------------------------------------------------

These instructions make use of the system-wide Python 3 interpreter.

Expand All @@ -24,7 +41,7 @@ Run the tests::
$ pytest


Local setup for development (Ubuntu)
Advanced: local setup for development (Ubuntu)
------------------------------------

These instructions assume that ``git``, ``python3``, ``pip``, and
Expand Down
32 changes: 32 additions & 0 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,38 @@ type of the block used. In this example, we used a RasterBlock. The request
and response specifications are listed in the documentation of the specific
block type.


Showing data on the map
-----------------------

If you a are using Jupyter and our ipyleaflet plugin (see `installation`__),
you can inspect your dask-geomodeling View on an interactive map widget.

.. code:: python
from ipyleaflet import Map, basemaps, basemap_to_tiles
from dask_geomodeling.ipyleaflet_plugin import GeomodelingLayer
# create the geomodeling layer and the background layer
# the 'styles' parameter refers to a matplotlib colormap;
# the 'vmin' and 'vmax' parameters determine the range of the colormap
geomodeling_layer = GeomodelingLayer(
add, styles="viridis", vmin=0, vmax=10, opacity=0.5
)
osm_layer = basemap_to_tiles(basemaps.OpenStreetMap.Mapnik)
# center the map on the middle of the View's extent
extent = add.extent
Map(
center=((extent[1] + extent[3]) / 2, (extent[0] + extent[2]) / 2),
zoom=14,
layers=[osm_layer, geoomdeling_layer]
)
Please consult the `ipyleaflet<https://ipyleaflet.readthedocs.io>`_ docs for
examples in how to add different basemaps, other layers, or add controls.


Delayed evaluation
------------------

Expand Down

0 comments on commit b86f102

Please sign in to comment.