Skip to content

How to create an activity

Anna Petrasova edited this page Oct 1, 2019 · 6 revisions

Trail

How to create an activity

In this tutorial we will create a Tangible Landscape activity in which we design a trail based on its views on the landscape.

Besides working Tangible Landscape and any model, you will need also two markers to specify the start and end of the trail. The markers should be at least 1.5 cm (0.5 inch) sized cube for reliable detection.

Marker

We will need two files:

  1. Python file we call trail.py which computes the actual geospatial analysis
import analyses
import grass.script as gs

def run_trail_analysis(scanned_elev, env, **kwargs):
    # do some stuff
    print("running")

def run_trail_points(scanned_elev, env, **kwargs):
    # detect markers as start and end points of a trail
    pass
  1. JSON configuration file config.py which specifies various details of this activity.
{
  "tasks": [
    {
      "layers": [
      ],
      "base": "dem",
      "analyses": "trail.py",
      "title": "Trails with nice views"
    }
  ]
}

Note that the "base" needs to be our reference DEM, see e.g. Preparing-a-simple-sand-model.

Save these two files into the same folder. In Tangible Landscape plugin, go to tab activities and browse to that configuration file. The activity is loaded and while you are scanning, press Start activity. You should see our print "running" in the Layer Manager's output window (Console tab).

Having the basic structure, we can now start working on our activity.

Detecting markers

Detecting markers works by comparing a scanned DEM and an initial DEM without a marker. We will use an available function change_detection which identifies markers.

def run_trail_points(scanned_elev, env, **kwargs):
    analyses.change_detection('scan_saved', scanned_elev, 'change',
                              height_threshold=[10, 100], cells_threshold=[5, 100],
                              add=True, max_detected=2, debug=True, env=env)

It tries to identify up to 2 markers (max_detected) based on the difference between scanned DEM and a base DEM (scan_saved), which we prepare during activity calibration phase. Parameters height_threshold and cells_threshold need to be selected based on the size of the marker and model scale. Identified markers are saved as vector map change.

To make this work, we add couple things to the configuration file:

  • "calibrate": true, - this results in a button in the Tangible Landscape Activities tab. Before we start the activity, we need to press it and wait couple seconds, during which the system scans and saves raster under scan_saved (the name is fixed).

  • We also added scanning_params, which change settings for a specific activity. In this case, we want to use binning, low smoothing to speed up processing. We also added calibration_scanning_params to change the scanning_params specifically for the activity calibration phase. In this case we want to have interpolated scan_saved raster to avoid having any holes which would be present in binned raster and would cause issues later on for certain processing tasks.

  • We added two layers which should be automatically loaded whenever activity starts. See d.rast and d.vect for details about parameters.

{
  "tasks": [
    {
      "layers": [
 	      ["d.rast", "map=scan_saved"],
        ["d.vect", "map=change", "icon=basic/cross2", "size=30", "fill_color=255:0:0"]
      ],
      "base": "dem",
      "analyses": "trail.py",
      "calibrate": true,
      "scanning_params": {"smooth": 7, "numscans": 1, "interpolate": false},
      "calibration_scanning_params": {"smooth": 10, "interpolate": true},
      "title": "Trails with nice views"
    }
  ]
}

With the new changes:

  1. Stop the activity if still running. Keep scanning on.
  2. Reload the configuration file.
  3. Press Calibrate button and wait couple seconds.
  4. Start activity.
  5. If the vector change was not there before, it will complain when trying to load it. Ignore it for now.
  6. Place the markers and you should see red crosses projected where the markers are. If not try to change the threshold values in trails.py.

Creating a trail

With the markers detected, we will create a trail connecting the two markers using least cost path based on slope values.

  1. We will compute slope based on our base scan_saved raster which doesn't have the markers captured.
  2. We use slope as cost to compute cumulative cost layer cost using one marker as the start point.
  3. Compute Least Cost Path between the two points.
def run_trail_analysis(scanned_elev, env, **kwargs):
    elevation = 'scan_saved'
    # contours for visualization
    gs.run_command('r.contour', input=elevation, output='scanned_contours', step=3, flags='t', env=env)
    # get point coordinates
    data = gs.read_command('v.out.ascii', input='change', type='point', format='point').strip().split()
    points = [point.split('|')[:2] for point in data]
    if len(points) == 2:
        # compute slope
        gs.run_command('r.slope.aspect', elevation=elevation, slope='slope', env=env)
        # compute cumulative cost layer
        gs.run_command('r.cost', input='slope', output='cost', start_coordinates=points[0],
                       outdir='outdir', flags='k', env=env)
        # compute Least Cost path between the 2 points
        gs.run_command('r.drain', input='cost', output='drain', direction='outdir',
                       drain='trail', flags='d', start_coordinates=points[1], env=env)

We adjust the layers we want to see projected:

      "layers": [
        ["d.vect", "map=scanned_contours", "color=255:238:185"],
        ["d.vect", "map=trail", "width=6"]
      ],

Cumulative viewshed along the trail

Finally, we compute viewsheds along the trail and aggregate them into Cumulative viewshed using GRASS addon r.viewshed.cva. Install the addon first:

g.extension r.viewshed.cva

Then we generate points along the trail with v.to.points and use these points to compute cumulative viewshed map cva.

def run_trail_analysis(scanned_elev, env, **kwargs):
    elevation = 'scan_saved'
    # contours for visualization
    gs.run_command('r.contour', input=elevation, output='scanned_contours', step=3, flags='t', env=env)
    # get point coordinates
    data = gs.read_command('v.out.ascii', input='change', type='point', format='point').strip().split()
    points = [point.split('|')[:2] for point in data]
    if len(points) == 2:
        # compute slope
        gs.run_command('r.slope.aspect', elevation=elevation, slope='slope', env=env)
        # compute cumulative cost layer
        gs.run_command('r.cost', input='slope', output='cost', start_coordinates=points[0],
                       outdir='outdir', flags='k', env=env)
        # compute Least Cost path between the 2 points
        gs.run_command('r.drain', input='cost', output='drain', direction='outdir',
                       drain='trail', flags='d', start_coordinates=points[1], env=env)
        # create points along a line
        gs.run_command('v.to.points', input='trail', output='points', dmax='5', flags='pr', env=env)
        # compute cumulative viewshed based on the points along the trail
        gs.run_command('r.viewshed.cva', input=elevation, vector='points', output='cva', env=env)
        # set color ramp for cumulative viewshed
        gs.run_command('r.colors', map='cva', color='magma', env=env)

Now add cva raster to the layers:

      "layers": [
        ["d.rast", "map=cva"],
        ["d.vect", "map=scanned_contours", "color=255:238:185"],
        ["d.vect", "map=trail", "width=6"]
      ],

Examples:

Cumulative viewshed 2 Cumulative viewshed 1

To change the topography, stop activity, remove markers, change topography and start activity again.

Cumulative viewshed 3