#
# AUTHOR(S): Caitlin Haedrich <caitlin DOT haedrich AT gmail>
#
# PURPOSE: This module contains functions for interactive visualizations
# in Jupyter Notebooks.
#
# COPYRIGHT: (C) 2021-2022 Caitlin Haedrich, and by the GRASS Development Team
#
# This program is free software under the GNU General Public
# License (>=v2). Read the file COPYING that comes with GRASS
# for details.
"""Interactive visualizations map with folium"""
from .reprojection_renderer import ReprojectionRenderer
[docs]class Raster:
"""Overlays rasters on folium maps.
Basic Usage:
>>> m = folium.Map()
>>> gj.Raster("elevation", opacity=0.5).add_to(m)
>>> m
"""
def __init__(
self,
name,
title=None,
use_region=False,
saved_region=None,
renderer=None,
**kwargs,
):
"""Reproject GRASS raster, export to PNG, and compute bounding box.
param str name: raster name
param str title: title of raster to display in layer control legend
param bool use_region: use computational region of current mapset
param str saved_region: name of saved computation region
param renderer: instance of ReprojectionRenderer
**kwargs: keyword arguments passed to folium.raster_layers.ImageOverlay()
"""
import folium # pylint: disable=import-outside-toplevel
self._folium = folium
self._name = name
self._overlay_kwargs = kwargs
self._title = title
if not self._title:
self._title = self._name
if not renderer:
self._renderer = ReprojectionRenderer(
use_region=use_region, saved_region=saved_region
)
else:
self._renderer = renderer
# Render overlay
# By doing this here instead of in add_to, we avoid rendering
# twice if added to multiple maps. This mimics the behavior
# folium.raster_layers.ImageOverlay()
self._filename, self._bounds = self._renderer.render_raster(name)
[docs] def add_to(self, folium_map):
"""Add raster to folium map with folium.raster_layers.ImageOverlay()
A folium map is an instance of folium.Map.
"""
# Overlay image on folium map
img = self._folium.raster_layers.ImageOverlay(
image=self._filename,
bounds=self._bounds,
name=self._title,
**self._overlay_kwargs,
)
# Add image to map
img.add_to(folium_map)
[docs]class Vector:
"""Adds vectors to a folium map.
Basic Usage:
>>> m = folium.Map()
>>> gj.Vector("roadsmajor").add_to(m)
>>> m
"""
def __init__(
self,
name,
title=None,
use_region=False,
saved_region=None,
renderer=None,
**kwargs,
):
"""Reproject GRASS vector and export to folium-ready PNG. Also computes bounding
box for PNG overlay in folium map.
param str name: vector name
param str title: title of vector to display in layer control legend
param bool use_region: use computational region of current mapset
param str saved_region: name of saved computation region
renderer: instance of ReprojectionRenderer
**kwargs: keyword arguments passed to folium.GeoJson()
"""
import folium # pylint: disable=import-outside-toplevel
self._folium = folium
self._name = name
self._title = title
if not self._title:
self._title = self._name
self._geojson_kwargs = kwargs
if not renderer:
self._renderer = ReprojectionRenderer(
use_region=use_region, saved_region=saved_region
)
else:
self._renderer = renderer
self._filename = self._renderer.render_vector(name)
[docs] def add_to(self, folium_map):
"""Add vector to folium map with folium.GeoJson()"""
self._folium.GeoJson(
str(self._filename), name=self._title, **self._geojson_kwargs
).add_to(folium_map)
[docs]class InteractiveMap:
"""This class creates interactive GRASS maps with folium.
Basic Usage:
>>> m = InteractiveMap()
>>> m.add_vector("streams")
>>> m.add_raster("elevation")
>>> m.add_layer_control()
>>> m.show()
"""
def __init__(
self,
width=400,
height=400,
tiles="CartoDB positron",
API_key=None, # pylint: disable=invalid-name
use_region=False,
saved_region=None,
):
"""Creates a blank folium map centered on g.region.
tiles parameter is passed directly to folium.Map() which supports several
built-in tilesets (including "OpenStreetMap", "Stamen Toner", "Stamen Terrain",
"Stamen Watercolor", "Mapbox Bright", "Mapbox Control Room", "CartoDB positron",
"CartoDB dark_matter") as well as custom tileset URL (i.e.
"http://{s}.yourtiles.com/{z}/{x}/{y}.png"). For more information, visit
folium documentation:
https://python-visualization.github.io/folium/modules.html
:param int height: height in pixels of figure (default 400)
:param int width: width in pixels of figure (default 400)
:param str tiles: map tileset to use
:param str API_key: API key for Mapbox or Cloudmade tiles
:param bool use_region: use computational region of current mapset
:param str saved_region: name of saved computation region
"""
import folium # pylint: disable=import-outside-toplevel
self._folium = folium
# Store height and width
self.width = width
self.height = height
# Create Folium Map
self.map = self._folium.Map(
width=self.width,
height=self.height,
tiles=tiles,
API_key=API_key, # pylint: disable=invalid-name
)
# Set LayerControl default
self.layer_control = False
self.layer_control_object = None
self._renderer = ReprojectionRenderer(
use_region=use_region, saved_region=saved_region
)
[docs] def add_vector(self, name, title=None, **kwargs):
"""Imports vector into temporary WGS84 location, re-formats to a GeoJSON and
adds to folium map.
:param str name: name of vector to be added to map;
positional-only parameter
:param str title: vector name for layer control
:**kwargs: keyword arguments passed to folium.GeoJson()
"""
Vector(name, title=title, renderer=self._renderer, **kwargs).add_to(self.map)
[docs] def add_raster(self, name, title=None, **kwargs):
"""Imports raster into temporary WGS84 location,
exports as png and overlays on folium map.
Color table for the raster can be modified with `r.colors` before calling
this function.
.. note:: This will only work if the raster is located in the current mapset.
To change the color table of a raster located outside the current mapset,
switch to that mapset with `g.mapset`, modify the color table with `r.color`
then switch back to the initial mapset and run this function.
:param str name: name of raster to add to display; positional-only parameter
:param str title: raster name for layer control
:**kwargs: keyword arguments passed to folium.raster_layers.ImageOverlay()
"""
Raster(name, title=title, renderer=self._renderer, **kwargs).add_to(self.map)
[docs] def add_layer_control(self, **kwargs):
"""Add layer control to display"
Accepts keyword arguments to be passed to folium.LayerControl()"""
self.layer_control = True
self.layer_control_object = self._folium.LayerControl(**kwargs)
[docs] def show(self):
"""This function returns a folium figure object with a GRASS raster
overlayed on a basemap.
If map has layer control enabled, additional layers cannot be
added after calling show()."""
if self.layer_control:
self.map.add_child(self.layer_control_object)
# Create Figure
fig = self._folium.Figure(width=self.width, height=self.height)
# Add map to figure
fig.add_child(self.map)
self.map.fit_bounds(self._renderer.get_bbox())
return fig
[docs] def save(self, filename):
"""Save map as an html map.
:param str filename: name of html file
"""
self.map.save(filename)