123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- # MODULE: grass.jupyter.display
- #
- # AUTHOR(S): Caitlin Haedrich <caitlin DOT haedrich AT gmail>
- #
- # PURPOSE: This module contains functions for non-interactive display
- # in Jupyter Notebooks
- #
- # COPYRIGHT: (C) 2021 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
- import grass.script as gs
- from .region import RegionManagerFor2D
- class GrassRenderer:
- """GrassRenderer creates and displays GRASS maps in
- Jupyter Notebooks.
- Elements are added to the display by calling GRASS display modules.
- Basic usage::
- >>> m = GrassRenderer()
- >>> 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::
- >>> m = GrassRenderer()
- >>> m.d_rast(map="elevation")
- >>> m.d_legend(raster="elevation")
- >>> m.show()
- """
- def __init__(
- self,
- height=400,
- width=600,
- filename=None,
- env=None,
- font="sans",
- text_size=12,
- renderer="cairo",
- use_region=False,
- saved_region=None,
- ):
- """Creates an instance of the GrassRenderer 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: environment
- :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
- """
- # Copy Environment
- if env:
- self._env = env.copy()
- else:
- self._env = os.environ.copy()
- # Environment Settings
- self._env["GRASS_RENDER_WIDTH"] = str(width)
- self._env["GRASS_RENDER_HEIGHT"] = str(height)
- 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
- 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, saved_region, self._env)
- @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
- def run(self, module, **kwargs):
- """Run modules from the GRASS display family (modules starting with "d.").
- This function passes arguments directly to grass.script.run_command()
- so the syntax is the same.
- :param str module: name of GRASS module
- :param `**kwargs`: named arguments passed to run_command()"""
- # Check module is from display library then run
- if module[0] == "d":
- self._region_manager.set_region_from_command(module, **kwargs)
- gs.run_command(module, env=self._env, **kwargs)
- else:
- raise ValueError("Module must begin with letter 'd'.")
- def __getattr__(self, name):
- """Parse attribute to GRASS display module. Attribute should be in
- the form 'd_module_name'. For example, 'd.rast' is called with 'd_rast'.
- """
- # Check to make sure format is correct
- if not name.startswith("d_"):
- raise AttributeError(_("Module must begin with 'd_'"))
- # Reformat string
- grass_module = name.replace("_", ".")
- # Assert module exists
- if not shutil.which(grass_module):
- raise AttributeError(_("Cannot find GRASS module {}").format(grass_module))
- def wrapper(**kwargs):
- # Run module
- self.run(grass_module, **kwargs)
- return wrapper
- def show(self):
- """Displays a PNG image of map"""
- # Lazy import to avoid an import-time dependency on IPython.
- from IPython.display import Image # pylint: disable=import-outside-toplevel
- return Image(self._filename)
|