-
Notifications
You must be signed in to change notification settings - Fork 11
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.
We will need two files:
- 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
- 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 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 underscan_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 addedcalibration_scanning_params
to change thescanning_params
specifically for the activity calibration phase. In this case we want to have interpolatedscan_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:
- Stop the activity if still running. Keep scanning on.
- Reload the configuration file.
- Press Calibrate button and wait couple seconds.
- Start activity.
- If the vector
change
was not there before, it will complain when trying to load it. Ignore it for now. - 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
.
With the markers detected, we will create a trail connecting the two markers using least cost path based on slope values.
- We will compute slope based on our base
scan_saved
raster which doesn't have the markers captured. - We use slope as cost to compute cumulative cost layer
cost
using one marker as the start point. - 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"]
],
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:
To change the topography, stop activity, remove markers, change topography and start activity again.
Getting started
- Software installation
- Physical setup
- Preparing a simple sand model
- Calibration
- Putting it all together
Tangible Landscape plugin manual
- Tangible Landscape plugin: Scanning
- Tangible Landscape plugin: Output
- Tangible Landscape plugin: Analyses
Advanced features
Resources