{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The grass.jupyter Package\n", "\n", "This notebook introduces the [_grass.jupyter_](https://grass.osgeo.org/grass-stable/manuals/libpython/grass.jupyter.html) package which simplifies the usage of *GRASS GIS* in *Jupyter Notebook*.\n", "\n", "The _grass.jupyter_ package was initially written as part of [Google Summer of Code in 2021](https://trac.osgeo.org/grass/wiki/GSoC/2021/JupyterAndGRASS) by Caitlin Haedrich and was experimentally included in version 8.0.0. Caitlin further improved it thanks to the [GRASS Mini Grant 2022](https://trac.osgeo.org/grass/wiki/GSoC/2021/JupyterAndGRASS/MiniGrant2022). The package was officially released for the first time as part of version 8.2.0. Credits for mentoring and additional development go to Vaclav Petras, Helena Mitasova, Stefan Blumentrath, and Anna Petrasova as well as to many members of the GRASS community who provided important feedback.\n", "\n", "In addition to simplifying the launch of *GRASS GIS* with a dedicated _init_ function, _grass.jupyter_ has two main display classes, _Map_ and _InteractiveMap_. Using the GRASS rendering engine in the background, _Map_ creates maps as PNG images. _InteractiveMap_ displays GRASS rasters and vectors either with [*folium*](http://python-visualization.github.io/folium/), or [*ipyleaflet*](https://ipyleaflet.readthedocs.io/en/latest/), both are [*leaflet*](https://leafletjs.com/)-based libraries for Python. The package includes also _Map3D_ and [_TimeSeriesMap_](temporal.ipynb).\n", "\n", "This interactive notebook is available online thanks to the [Binder](https://mybinder.org) service. To run the select part (called a *cell*), hit `Shift + Enter`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Start GRASS GIS" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Import Python standard library and IPython packages we need.\n", "import subprocess\n", "import sys\n", "\n", "# Ask GRASS GIS where its Python packages are.\n", "sys.path.append(\n", " subprocess.check_output([\"grass\", \"--config\", \"python_path\"], text=True).strip()\n", ")\n", "\n", "# Import GRASS packages\n", "import grass.script as gs\n", "import grass.jupyter as gj\n", "\n", "# Start GRASS Session\n", "session = gj.init(\"../../../grassdata\", \"nc_basic_spm_grass7\", \"user1\")\n", "\n", "# Set computational region to the elevation raster.\n", "gs.run_command(\"g.region\", raster=\"elevation\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## GRASS Renderer\n", "\n", "The `Map` class creates and displays GRASS maps as PNG images. There are two ways to add elements to the display. First, the name of the *GRASS* display module can be called as an attribute by replacing the \".\" with \"\\_\" in the module name. For example:\n", "````\n", "m = Map()\n", "m.d_rast(map=\"elevation\")\n", "````\n", "\n", "Alternatively, *GRASS* display modules can be called with the `run()` method:\n", "````\n", "m = Map()\n", "m.run(\"d.rast\", map=\"elevation\")\n", "````\n", "\n", "To display the image, call `show()`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create Map instance\n", "example_map = gj.Map()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Add a raster, vector and legend to the map\n", "example_map.d_rast(map=\"elevation\")\n", "example_map.d_vect(map=\"streams\")\n", "example_map.d_legend(raster=\"elevation\", at=(55, 95, 80, 84), flags=\"b\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Display map\n", "example_map.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also have multiple instances of `Map`. Here, we create another map then go back and modify the first map." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Make a second instance.\n", "# Just for variety, we'll make this one a different size\n", "small_map = gj.Map(height=200, width=220)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Add a some layers\n", "# We can also add layers with the run() methods\n", "small_map.run(\"d.rast\", map=\"elevation_shade\")\n", "small_map.run(\"d.vect\", map=\"roadsmajor\")\n", "\n", "# Display second map\n", "small_map.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Then, we return to the first instance and continue to modify and display it\n", "# Notice that layers a drawn in the order they are added\n", "example_map.run(\"d.vect\", map=\"zipcodes\", color=\"red\", fill_color=\"none\")\n", "example_map.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default the display extent (and resolution if applicable) is derived from the first raster or vector layer:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "nc_map = gj.Map()\n", "nc_map.d_vect(map=\"boundary_state\")\n", "nc_map.d_rast(map=\"geology\")\n", "nc_map.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To respect computational region, set `use_region=True`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "geol_map = gj.Map(use_region=True)\n", "geol_map.d_vect(map=\"boundary_state\")\n", "geol_map.d_rast(map=\"geology\")\n", "geol_map.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use a saved region:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gs.run_command(\"g.region\", save=\"myregion\", n=224000, s=222000, w=633500, e=637300)\n", "myregion_map = gj.Map(saved_region=\"myregion\")\n", "myregion_map.d_rast(map=\"elevation\")\n", "myregion_map.d_rast(map=\"lakes\")\n", "myregion_map.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interactive Map Display\n", "\n", "The `InteractiveMap` class displays *GRASS GIS* rasters and vectors with [*folium*](http://python-visualization.github.io/folium/) or [*ipyleaflet*](https://ipyleaflet.readthedocs.io/en/latest/).\n", "Backend is detected automatically; when both libraries are available, ipyleaflet is used. The backend can be also selected with the `map_backend` parameter." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Only when using ipyleaflet**, the map display includes a button for querying data ( ℹ ), a button for displaying and editing computational region (□), and a button for drawing simple vector geometries (🖊) that can be saved as a GRASS native vector map.\n", "\n", "1. Query raster/vector layers: Click the **info** button ( ℹ ) to enable query mode, then select a point on the map to retrieve information. Toggle the button off when finished.\n", "2. View and edit the computational region: Click the **computational region** button (□) to display and optionally adjust the region. Update it by clicking **Update region**, then toggle the button off when done.\n", "3. Draw and save geometries: Click the **pencil** button (🖊) to draw shapes on the map, name the vector map, and save it. The geometry will be added as a new layer and the drawing tool will close automatically.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create Interactive Map\n", "raleigh_map = gj.InteractiveMap(width=600)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Add raster, vector and layer control to map\n", "raleigh_map.add_raster(\"elevation\")\n", "raleigh_map.add_vector(\"roadsmajor\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Display Interactive Map\n", "raleigh_map.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Save InteractiveMap as HTML\n", "\n", "To share or embed the map in a website, we can export it has an HTML file." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "raleigh_map.save(filename=\"raleigh_map.html\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## GRASS-folium Integration\n", "\n", "We can also pass GRASS rasters and vectors directly to folium with the Raster and Vector classes. This provides much more flexibility when creating maps since we can access all of folium's capabilities." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import folium\n", "\n", "\n", "# Create a map\n", "m = folium.Map(location=[35.761168, -78.668271], zoom_start=13)\n", "\n", "# Create and add elevation layer to map\n", "gj.Raster(\"elevation\", opacity=0.5).add_to(m)\n", "\n", "# Do some cool folium stuff!\n", "# Like make a tooltip\n", "tooltip = \"Click me!\"\n", "# and add a marker\n", "folium.Marker(\n", " [35.781608, -78.675800], popup=\"Point of Interest\", tooltip=tooltip\n", ").add_to(m)\n", "\n", "# Display map\n", "m" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## GRASS-ipyleaflet Integration\n", "\n", "We can also pass GRASS rasters and vectors directly to ipyleaflet with the Raster and Vector classes. This provides much more flexibility when creating maps since we can access all of ipyleaflet's capabilities." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import ipyleaflet\n", "\n", "# Create map\n", "m = ipyleaflet.Map(center=[35.761168, -78.668271], zoom=13)\n", "\n", "# Create and add elevation layer to map\n", "gj.Raster(\"elevation\", opacity=0.5).add_to(m)\n", "\n", "# Do some cool ipyleaflet stuff!\n", "# Like make a tooltip\n", "title = \"Click me!\"\n", "# and add a marker\n", "marker = ipyleaflet.Marker(name=\"marker\", location=(35.781608, -78.675800), title=title)\n", "\n", "# Add the marker to the map\n", "m.add(marker)\n", "\n", "control = ipyleaflet.LayersControl(position=\"topright\")\n", "m.add(control)\n", "m" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## GRASS 3D Renderer\n", "\n", "The `Map3D` class creates 3D visualizations as PNG images. The *m.nviz.image* module is used in the background and the function `render()` accepts parameters of this module.\n", "The `Map3D` objects have `overlay` attribute which can be used in the same way as `Map` and 2D images on top of the 3D visualization.\n", "To display the image, call `show()`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, let's create the object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "elev_map = gj.Map3D()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, render a 3D visualization of an elevation raster as a surface colored using, again, the elevation raster:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "elev_map.render(elevation_map=\"elevation\", color_map=\"elevation\", perspective=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To add a raster legend on the image as an overlay using the 2D rendering capabilities accessible with `overlay.d_legend`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "elev_map.overlay.d_legend(raster=\"elevation\", at=(60, 97, 87, 92))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we show " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "elev_map.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's color the elevation surface using a landuse raster (note that the call to `render` removes the result of the previous `render` as well as the current overlays):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "elev_map.render(elevation_map=\"elevation\", color_map=\"landuse\", perspective=20)\n", "elev_map.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Animating Series of Maps\n", "\n", "The `SeriesMap` class animates a series of maps. The `ipywidgets` slider allows users to slide between maps and play a continuous loop. This class is similar to the `TimeSeriesMap` class for space-time datasets and shown in the [temporal notebook](./temporal.ipynb)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!r.slope.aspect elevation=elevation slope=slope" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Managing Core Usage with SeriesMap\n", "\n", "The `SeriesMap` module automatically uses multiple CPU cores to improve rendering performance. If you want to control the number of cores used, you can set the `NPROCS` [variable](https://grass.osgeo.org/grass83/manuals/variables.html#list-of-selected-grass-gisenv-variables).\n", "To specify the number of cores for rendering, set the `NPROCS` variable to the desired number of cores before running `gj.SeriesMap`. For example, to use 4 cores, execute:\n", "\n", " ```\n", " gs.run_command(\"g.gisenv\", set=\"NPROCS=4\")\n", " ```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "series = gj.SeriesMap(height=500)\n", "series.add_rasters([\"elevation\", \"elevation_shade\", \"slope\"])\n", "series.add_vectors([\"streams\", \"streets\", \"viewpoints\"])\n", "series.d_vect(map=\"streets\")\n", "series.d_barscale()\n", "series.show() # Create Slider" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The series can also be saved as a GIF." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "series.save(\"image.gif\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Switching Mapsets and Session Management\n", "\n", "The `init` function returns a reference to a session object which can be used to manipulate the current session. The session is global, i.e., the global state of the environment is changed. The session object is a handle for accessing this global session. When the kernel for the notebooks shuts down or is restarted, the session ends automatically. The session can be explicitly ended using `session.finish()`, but that's usually not needed in notebooks.\n", "\n", "Additionally, the session object can be used to change the current mapset. Here, we will switch to mapset called *PERMANENT*:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "session.switch_mapset(\"PERMANENT\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we could add more data to the PERMANENT mapset or modify the existing data there. We don't need to do anything there, so we switch back to the mapset we were in before:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "session.switch_mapset(\"user1\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 4 }