Source code for grass.jupyter.map

# MODULE:    grass.jupyter.map
#
# AUTHOR(S): Caitlin Haedrich <caitlin DOT haedrich AT gmail>
#
# PURPOSE:   This module contains functions for non-interactive display
#            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.

"""2D rendering and display functionality"""

import os
import shutil
import tempfile
import weakref

from grass.tools import Tools
from grass.tools.support import ToolFunctionResolver

from .region import RegionManagerFor2D


[docs] class Map: """Map creates and displays GRASS maps in Jupyter Notebooks. Elements are added to the display by calling GRASS display modules. :Basic usage: .. code-block:: pycon >>> m = Map() >>> m.run("d.rast", map="elevation") >>> m.run("d.legend", raster="elevation") >>> m.show() GRASS display modules can also be called by using the name of module as a class method and replacing "." with "_" in the name. :Shortcut usage: .. code-block:: pycon >>> m = Map() >>> m.d_rast(map="elevation") >>> m.d_legend(raster="elevation") >>> m.show() """ def __init__( self, width=None, height=None, filename=None, env=None, session=None, font="sans", text_size=12, renderer="cairo", use_region=False, saved_region=None, read_file=False, ): """Creates an instance of the Map class. :param int height: height of map in pixels :param int width: width of map in pixels :param str filename: filename or path to save a PNG of map :param str env: runtime environment to use for execution (defaults to global) :param str session: session with environment (used if it has an env attribute) :param str font: font to use in rendering; either the name of a font from $GISBASE/etc/fontcap (or alternative fontcap file specified by GRASS_FONT_CAP), or alternatively the full path to a FreeType font file :param int text_size: default text size, overwritten by most display modules :param renderer: GRASS renderer driver (options: cairo, png, ps, html) :param use_region: if True, use either current or provided saved region, else derive region from rendered layers :param saved_region: if name of saved_region is provided, this region is then used for rendering :param bool read_file: if False (default), erase filename before re-writing to clear contents. If True, read file without clearing contents first. """ # Copy Environment if env: self._env = env.copy() elif session and hasattr(session, "env"): self._env = session.env.copy() else: self._env = os.environ.copy() # Environment Settings self._env["GRASS_RENDER_WIDTH"] = str(width) if width else "600" self._env["GRASS_RENDER_HEIGHT"] = str(height) if height else "400" self._env["GRASS_FONT"] = font self._env["GRASS_RENDER_TEXT_SIZE"] = str(text_size) self._env["GRASS_RENDER_IMMEDIATE"] = renderer self._env["GRASS_RENDER_FILE_READ"] = "TRUE" self._env["GRASS_RENDER_TRANSPARENT"] = "TRUE" # Create PNG file for map # If not user-supplied, we will write it to a map.png in a # temporary directory that we can delete later. We need # this temporary directory for the legend anyways so we'll # make it now # Resource managed by weakref.finalize. self._tmpdir = ( tempfile.TemporaryDirectory() # pylint: disable=consider-using-with ) def cleanup(tmpdir): tmpdir.cleanup() weakref.finalize(self, cleanup, self._tmpdir) if filename: self._filename = filename if not read_file and os.path.exists(self._filename): os.remove(self._filename) else: self._filename = os.path.join(self._tmpdir.name, "map.png") # Set environment var for file self._env["GRASS_RENDER_FILE"] = self._filename # Create Temporary Legend File self._legend_file = os.path.join(self._tmpdir.name, "legend.txt") self._env["GRASS_LEGEND_FILE"] = str(self._legend_file) # rendering region setting self._region_manager = RegionManagerFor2D( use_region=use_region, saved_region=saved_region, width=width, height=height, env=self._env, ) self._name_resolver = None @property def filename(self): """Filename or full path to the file with the resulting image. The value can be set during initialization. When the filename was not provided during initialization, a path to temporary file is returned. In that case, the file is guaranteed to exist as long as the object exists. """ return self._filename @property def region_manager(self): """Region manager object""" return self._region_manager
[docs] def run(self, tool_name_: str, /, **kwargs): """Run tools from the GRASS display family (tools starting with "d"). This function passes arguments directly to grass.tools.run(), so the syntax is the same. :param tool_name_: name of a GRASS tool :param `**kwargs`: parameters passed to the tool """ # Check module is from display library then run if tool_name_[0] != "d": msg = f"Tool must be a display tool starting with 'd', got: {tool_name_}" raise ValueError(msg) self._region_manager.set_region_from_command(tool_name_, **kwargs) self._region_manager.adjust_rendering_size_from_region() Tools(env=self._env).run(tool_name_, **kwargs)
def __getattr__(self, name): """Get a function representing a GRASS display tool. Attributes should be in the form 'd_tool_name'. For example, 'd.rast' is called with 'd_rast'. """ if not self._name_resolver: self._name_resolver = ToolFunctionResolver( run_function=self.run, env=self._env, allowed_prefix="d_", ) return self._name_resolver.get_function(name, exception_type=AttributeError) def __dir__(self): """List available tools and standard attributes.""" if not self._name_resolver: self._name_resolver = ToolFunctionResolver( run_function=self.run, env=self._env, allowed_prefix="d_", ) # Collect instance and class attributes static_attrs = set(dir(type(self))) | set(self.__dict__.keys()) return list(static_attrs) + self._name_resolver.names()
[docs] def show(self): """Displays a PNG image of map""" # Lazy import to avoid an import-time dependency on IPython. from IPython.display import Image, display # pylint: disable=import-outside-toplevel display(Image(self._filename))
[docs] def save(self, filename): """Saves a PNG image of map to the specified *filename*""" shutil.copy(self._filename, filename)