Overview
This manual is the long-form user guide for WbW-QGIS.
WbW-QGIS (Whitebox Workflows for QGIS) is the QGIS frontend for Whitebox Next Gen. It provides a QGIS-native way to discover, configure, and run Whitebox tools through the Processing framework and plugin UI.
Whitebox Next Gen uses a layered architecture:
- backend geospatial engines and tools in Rust,
- frontend runtimes for Python, R, and QGIS,
- shared tool taxonomy and capability metadata.
Whitebox Next Gen is intentionally full-stack: core geospatial capabilities that are often delegated to external C/C++ dependencies in other GIS platforms (for example raster I/O, projections, geometry/topology operations, and lidar handling) are implemented in the Whitebox codebase itself. This architecture is unusual in GIS and provides practical benefits for users: consistent behavior across platforms, tighter control over correctness and performance, fewer system-level dependency issues during installation, and faster iteration when fixing bugs or introducing new capabilities.
Within this model, WbW-QGIS is intentionally a thin integration layer. It handles QGIS presentation and orchestration while computation remains in the Whitebox backend runtime.
What This Manual Covers
This guide focuses on practical use of WbW-QGIS:
- setting up the plugin and runtime correctly,
- understanding discovery and provider refresh,
- running tools through QGIS Processing,
- handling output and troubleshooting common issues.
The manual is written for both analysts and developers who use QGIS as the primary working environment.
Goals
- Provide a stable onboarding path for local installation.
- Document the operational behavior of plugin discovery and execution.
- Clarify tier and licensing behavior in QGIS.
- Reduce setup friction and runtime ambiguity.
How to Use This Manual
For first-time setup, read chapters in order:
- Installation and Setup
- Build and Preview
- Quick Start
- Runtime and Discovery
After setup is stable, use the remaining chapters as reference material.
Installation and Setup
This chapter covers local installation of WbW-QGIS from source.
Prerequisites
- QGIS 4.x
- A Python environment used by QGIS
- Local checkout of whitebox_next_gen
Install the Whitebox Python Runtime
From the repository root, install whitebox_workflows into the same Python environment used by QGIS:
./scripts/dev_python_install.sh
If you are working in an environment that supports Pro-enabled integration builds, use:
./scripts/dev_python_install.sh --pro
Install or Symlink the Plugin
Plugin source directory:
- crates/wbw_qgis/plugin/whitebox_workflows_qgis
Target QGIS plugins directory:
/python/plugins/whitebox_workflows_qgis
Typical local workflow from repository root:
export QGIS_PLUGIN_DIR="<QGIS settings dir>/python/plugins"
mkdir -p "$QGIS_PLUGIN_DIR"
ln -snf "$PWD/crates/wbw_qgis/plugin/whitebox_workflows_qgis" \
"$QGIS_PLUGIN_DIR/whitebox_workflows_qgis"
Verify Python Import Path
Before opening QGIS, verify whitebox_workflows import in the same Python environment QGIS uses:
python3 -c "import whitebox_workflows as wb; print(wb.__file__)"
If this fails, fix environment alignment before continuing.
Build and Preview
The WbW-QGIS manual uses mdBook, matching the WbW-Python and WbW-R manuals.
Build the Manual
From the manual directory:
cd crates/wbw_qgis/manual
mdbook build
Generated output will be written to:
- crates/wbw_qgis/manual/book
Live Preview
For a local preview server:
cd crates/wbw_qgis/manual
mdbook serve --open
This starts a local server and opens the manual in your browser.
Writing Conventions
- Keep examples task-oriented and reproducible.
- Prefer short, complete QGIS workflows over abstract API descriptions.
- Document expected outputs and validation checks where possible.
Quick Start
This walkthrough verifies that WbW-QGIS is running and can execute tools.
1. Enable Plugin
- Start QGIS.
- Open Plugin Manager.
- Enable Whitebox Workflows.
2. Confirm Provider Availability
- Open the Processing Toolbox.
- Confirm Whitebox provider entries appear.
- If tools are missing, trigger a discovery refresh from the plugin panel.
3. Run a First Tool
A common smoke test is a simple raster analysis tool with a small input file.
Recommended pattern:
- Choose a small test raster.
- Run a lightweight tool from the Whitebox provider.
- Write output to a temporary file.
- Load and inspect the output layer in QGIS.
4. Validate Results
- Confirm the output file exists.
- Confirm layer metadata and CRS are as expected.
- Confirm visual result is plausible for the input.
If any step fails, continue to the Troubleshooting chapter.
Runtime and Discovery
WbW-QGIS discovers tool availability at runtime using the active whitebox_workflows environment.
Discovery Flow
At a high level:
- Import whitebox_workflows in the QGIS Python environment.
- Create a runtime session.
- Read runtime capability metadata.
- Read tool catalog metadata.
- Partition available vs locked tools.
- Refresh Processing provider algorithms.
When to Refresh
Refresh discovery when:
- plugin settings change,
- runtime tier/entitlement changes,
- whitebox_workflows is rebuilt or reinstalled,
- tool taxonomy updates are introduced.
Common Discovery Symptoms
- Provider missing entirely: plugin import/runtime bootstrap failure.
- Provider appears but tools are absent: catalog read failure or stale cache.
- Tools show as locked unexpectedly: runtime capability/tier mismatch.
In all cases, validate environment alignment first.
Tool Execution in QGIS
WbW-QGIS executes tools through the QGIS Processing framework.
Typical Execution Path
- Select a Whitebox algorithm in Processing Toolbox.
- Fill parameters in the algorithm dialog.
- Execute and monitor progress/messages.
- Load or inspect output artifacts.
Recommended Execution Practices
- Use explicit output paths for reproducibility.
- Start with small representative datasets before full runs.
- Validate intermediate outputs for CRS, schema, and metadata.
- Keep task logs for long workflows and batch operations.
Output Handling
Whitebox tools may produce:
- raster outputs,
- vector outputs,
- lidar outputs,
- text/report sidecar artifacts.
Confirm output type and format before chaining into downstream steps.
Progress and Messaging
Execution status and warnings should be treated as part of result validation. If a tool completes with warnings, inspect outputs before continuing.
Recipes
Recipes in WbW-QGIS are guided workflow entries that help users launch common multi-tool patterns faster.
A recipe is not a new backend algorithm. It is a curated sequence of existing tools with summary guidance, launch defaults, and tier-aware visibility.
What Recipes Provide
Recipes provide:
- A short purpose statement.
- A launch tool (the first tool dialog opened when you run the recipe).
- A step list (ordered tool IDs for the workflow).
- Optional input and output hints.
- Tier gating (Open, Pro, or Enterprise).
Where Recipes Appear in QGIS
Recipes are available in the Whitebox Workflows dock panel under Workflow Recipes.
The panel includes:
- Open Recipe
- Copy Recipe Steps
- Why Is This Locked?
- Open Recipe File
- Reload Recipe File
- Validate Recipe File
- Include locked recipes toggle
Built-in and User Recipes
WbW-QGIS merges two sources:
- Built-in recipes shipped with the plugin.
- User-defined recipes loaded from a local JSON file.
If a user recipe has the same id as a built-in recipe, the user recipe overrides the built-in entry.
User Recipe File Location
Default file path:
- ~/.whitebox_workflows_qgis/recipes.json
Override path with environment variable:
- WBW_QGIS_USER_RECIPES
When you press Open Recipe File, the plugin creates the file from a template if it does not exist.
User Recipe File Format
The file may be either:
- An object with a recipes array.
- A direct array of recipes.
Each recipe should include:
- id (required)
- tools array (required)
Optional fields:
- title
- summary
- tier (open, pro, enterprise)
- launch_tool
- input_hint
- output_hint
Example:
{
"recipes": [
{
"id": "my_custom_terrain_recipe",
"title": "My Custom Terrain Recipe",
"summary": "User-defined recipe example.",
"tier": "open",
"launch_tool": "slope",
"tools": ["slope", "aspect", "hillshade"],
"input_hint": "Set a DEM raster as the primary input.",
"output_hint": "Write outputs to a dedicated project output folder."
}
]
}
Validation and Error Reporting
Use Validate Recipe File in the panel to run validation and see a full report.
Validation checks include:
- Entry structure is a JSON object.
- id exists and is non-empty.
- tools exists and is a non-empty array.
- tier value is valid when supplied.
Invalid entries are skipped, while valid entries continue to load.
Warnings include recipe index and, when available, recipe id to speed up fixes.
Recipe Visibility and Tier Behavior
Recipes are filtered by:
- Runtime tier entitlement.
- Tool availability in the current runtime catalog.
- Include locked recipes panel setting.
When Include locked recipes is enabled, recipes that are not runnable in the current runtime remain visible with lock messaging for discovery.
Discovery and Sorting
Recipes are shown alphabetically in the panel for easier scanning.
Sorting applies to both built-in and user-defined recipes.
Recommended Team Practice
For teams, keep a shared recipe JSON under version control and point WBW_QGIS_USER_RECIPES to that path in your local environment setup.
This gives you repeatable, reviewable workflow definitions without modifying plugin source files.
Licensing and Tiers
Whitebox NG is available in two licensing tiers with different capabilities and licensing models.
License Tiers
-
Open Tier (free): Governed by MIT/Apache 2.0 dual licensing. All Open-tier tools are free and open-source with no entitlement or activation required. Use this tier for learning, research, and open development.
-
Pro Tier (commercial): Proprietary software governed by EULA. Pro-tier tools provide advanced capabilities and require activation with a valid license key. Once activated, the license persists locally so you do not need to re-authenticate on each QGIS session.
How QGIS Reflects Licensing
The Whitebox Workflows QGIS plugin is a frontend layer. Licensing authority and rules are enforced in the backend runtime.
Core Principle
The plugin reflects backend capabilities; it does not define licensing rules. Runtime mode (open vs. pro) determines which tools are available and functional.
Practical Behavior
- Open-tier tools are expected to run in all standard public QGIS environments.
- Pro-tier tools may be visible in the plugin but locked without an active Pro license.
- You can request a specific tier, but the effective tier depends on your entitlement state.
Why This Matters
- One plugin surface adapts to both open and pro capability tiers.
- Tool discovery remains consistent across Python, R, and QGIS frontends.
- Licensing decisions and enforcement remain centralized in backend logic.
Interactive License Management
The plugin provides convenient menu actions for license management without requiring external tools or command lines.
Activating a Pro License
- In QGIS, navigate to Plugins > Whitebox Workflows > Activate License.
- Enter your license information when prompted:
- License key (required)
- First name (required)
- Last name (required)
- Email (required)
- Provider URL (optional; defaults to production)
- Accept the EULA terms.
- Click OK. The plugin will activate and persist your license locally.
- The tool catalog automatically refreshes to show Pro-tier tools.
Important: License activation is tied to your machine. See Transferring a License to move to another machine.
Checking License Status
Navigate to Plugins > Whitebox Workflows > Plugin Settings (or look for diagnostics output) to see:
- License validity (active or expired)
- Effective tier (open or pro)
- License expiration time
Transferring a License
If you need to use your Pro license on a different machine, you must first deactivate it on the current machine and then activate on the destination.
- On the current machine, navigate to Plugins > Whitebox Workflows > Transfer License. This generates a portable activation payload and clears your local license state.
- Share the activation payload with the destination machine (or keep it for your own use on the other machine).
- On the destination machine, navigate to Plugins > Whitebox Workflows > Activate License and enter your license key using the same process as above. The destination will obtain its own local license state.
Deactivating a License
If you no longer plan to use Pro tools on this machine, navigate to Plugins > Whitebox Workflows > Deactivate License. This clears your local license state. Future sessions will fall back to Open-tier tools only.
Local License State
Once activated, your license information is stored locally at
~/.whitebox/wbw_ng_license_state.json (or override via the
WBW_LICENSE_STATE_PATH environment variable). On each QGIS startup:
- If valid local state exists, it is automatically loaded.
- If local state is expired or missing, the plugin falls back to Open-tier mode.
- You do not need to re-authenticate in QGIS on every session.
Expected Local-Dev Outcome
For most source-based setups, assume open-tier behavior unless your runtime environment is explicitly configured for Pro-enabled integration testing or you have an active Pro license activated on your machine.
Supported Data Formats
This chapter documents format support exposed through WbW-QGIS.
Authoritative backend support comes from Whitebox core crates:
- Raster I/O:
wbraster - Vector I/O:
wbvector - LiDAR I/O:
wblidar
The format tables below are aligned with those backend crates' README "Supported Formats" sections.
Raster Formats
Raster support in Whitebox is provided by wbraster.
| Format | Extension(s) | Read | Write | Notes |
|---|---|---|---|---|
| DTED | .dt0, .dt1, .dt2 | Yes | Yes | DTED 0/1/2 elevation; WGS-84 geographic only |
| ENVI HDR Labelled | .hdr + sidecar data | Yes | Yes | Multi-band (BSQ / BIL / BIP) |
| ER Mapper | .ers + data | Yes | Yes | Hierarchical header |
| ERDAS IMAGINE (HFA) | .img | Yes | No | Read-only MVP; RLC compression supported |
| Esri ASCII Grid | .asc, .grd | Yes | Yes | Handles xllcorner and xllcenter |
| Esri Binary Grid | workspace dir / .adf | Yes | Yes | Single-band float32, big-endian |
| Esri Float Grid | .flt, .hdr | Yes | Yes | Single-band float grid with header |
| JPEG + World File | .jpg, .jpeg + .jgw/.wld | Yes | Yes | Non-rotated georeferencing |
| PNG + World File | .png + .pgw/.wld | Yes | Yes | Non-rotated georeferencing |
| GeoTIFF / BigTIFF / COG | .tif, .tiff | Yes | Yes | Stripped/tiled GeoTIFF, BigTIFF, COG |
| GeoPackage Raster (Phase 4) | .gpkg | Yes | Yes | Multi-band tiled raster |
| GRASS ASCII Raster | .asc, .txt | Yes | Yes | north/south/east/west, rows/cols headers |
| Idrisi/TerrSet Raster | .rdc, .rst | Yes | Yes | byte, integer, real, RGB24 |
| JPEG2000 / GeoJP2 | .jp2 | Yes | Yes | Pure-Rust reader and writer |
| PCRaster | .map | Yes | Yes | Value-scale aware writer |
| SAGA GIS Binary | .sgrd, .sdat | Yes | Yes | SAGA data types supported |
| Surfer GRD | .grd | Yes | Yes | DSAA and DSRB |
| Zarr v2/v3 | .zarr | Yes | Yes | 2D and 3D (band,y,x) chunked arrays |
| XYZ ASCII Grid | .xyz | Yes | Yes | Whitespace or comma-delimited X Y Z points |
Notes:
- Whitebox avoids runtime dependence on GDAL.
- In QGIS workflows, GeoTIFF remains the safest default interchange raster.
Vector Formats
Vector support in Whitebox is provided by wbvector.
| Format | Read | Write | Notes |
|---|---|---|---|
FlatGeobuf (.fgb) | Yes | Yes | High-performance binary interchange |
GeoJSON (.geojson) | Yes | Yes | Web-friendly text format |
TopoJSON (.topojson) | Yes | Yes | Topology-preserving JSON format |
GeoPackage (.gpkg) | Yes | Yes | SQLite container; multi-layer workflows |
GML (.gml) | Yes | Yes | Standards-based XML exchange |
GPX (.gpx) | Yes | Yes | GPS tracks/routes/waypoints |
KML (.kml) | Yes | Yes | Google Earth-style visualization |
MapInfo Interchange (.mif + .mid) | Yes | Yes | Legacy MapInfo interoperability |
ESRI Shapefile (.shp + sidecars) | Yes | Yes | Broad legacy compatibility |
GeoParquet (.parquet) | Yes | Yes | Optional geoparquet feature |
KMZ (.kmz) | Yes | Yes | Optional kmz feature |
OSM PBF (.osm.pbf) | Yes | No | Read-only; optional osmpbf feature |
Feature-gated formats in wbvector:
geoparquetfor GeoParquet supportkmzfor KMZ supportosmpbffor OSM PBF read support
In QGIS workflows, GeoPackage and FlatGeobuf are good modern interchange choices; Shapefile remains a compatibility fallback.
LiDAR / Point Cloud Formats
LiDAR support in Whitebox is provided by wblidar.
| Format | Read | Write | Notes |
|---|---|---|---|
| LAS | Yes | Yes | LAS 1.1-1.5, PDRF 0-15 |
| LAZ | Yes | Yes | Standards-compliant LASzip v2/v3 Point10/Point14 codecs |
| COPC | Yes | Yes | COPC 1.0 hierarchy with Point14-family payloads |
| PLY | Yes | Yes | ASCII, binary little-endian, binary big-endian |
| E57 | Yes | Yes | ASTM E2807 with CRC-32 page validation |
Optional features in wblidar:
copc-httpfor HTTP range fetching of remote COPCcopc-parallelfor parallel COPC writing pathslaz-parallelfor optional parallel LAZ decode pathsparallelumbrella feature (enables both parallel paths)
In QGIS workflows, .copc.laz is a strong default for large point-cloud
delivery and archive.
QGIS Practical Defaults
For most WbW-QGIS production workflows:
- Raster default: GeoTIFF (
.tif) - Vector default: GeoPackage (
.gpkg) or FlatGeobuf (.fgb) - LiDAR default: COPC LAZ (
.copc.laz) or LAZ (.laz)
These defaults balance compatibility, file size, and performance.
Important Distinction
Backend format support means the Whitebox runtime can read/write those formats. Specific QGIS tool dialogs may still constrain certain outputs or defaults depending on parameter wiring and the tool category.
When in doubt:
- Use the tool's default output extension in QGIS.
- Re-open output and validate metadata.
- Use QGIS conversion tools only when you need a different interchange format.
Common Format Problems
| Problem | Likely cause | Fix |
|---|---|---|
| Output opens but schema is unexpected | Format-specific field/type constraints | Use GeoPackage or FlatGeobuf for richer schema |
| Shapefile field names truncated | 10-character DBF limit | Switch output to GeoPackage |
| Large cloud is slow to browse | Non-indexed point-cloud format | Use COPC LAZ for tiled access |
| Optional format not available | Feature not enabled in build | Use a non-optional format (for example GeoPackage/GeoJSON/Shapefile) |
| CRS appears missing in output | Sidecar or metadata issue | Confirm CRS in layer properties and re-export if needed |
Reprojection and CRS
A Coordinate Reference System (CRS) defines how coordinates in a dataset map to real-world locations. CRS mismatches are one of the most common sources of silent errors in GIS workflows: two layers may display correctly on screen (because QGIS reprojects them on-the-fly for display) while producing wrong results when passed to an analysis tool that expects matching CRS inputs.
This chapter explains how to identify, verify, and correct CRS issues in WbW-QGIS workflows.
Key Concepts
- Geographic CRS (GCS): Coordinates in angular units (degrees of latitude and longitude). Common examples: WGS84 (EPSG:4326), NAD83 (EPSG:4269). Not suitable as a working CRS for distance/area calculations.
- Projected CRS (PCS): Coordinates in linear units (metres or feet) on a flat map projection. Examples: UTM zones, Lambert Conformal Conic, Albers Equal Area.
- EPSG code: A numeric registry identifier for a CRS. EPSG:4326 = WGS84; EPSG:32617 = UTM Zone 17N (WGS84); EPSG:3978 = Canada Atlas Lambert.
- On-the-fly reprojection: QGIS displays all layers in the project CRS regardless of their native CRS. This is for display only — it does not change the file on disk.
- Reproject (warp): Permanently transform raster or vector data to a new CRS, writing a new file. Required before passing data to analysis tools.
- Z factor: A unit-conversion factor applied when DEM horizontal units (metres) differ from vertical units (feet), or vice versa.
Choosing a Working CRS
| Scenario | Recommended CRS type |
|---|---|
| Global or continental analysis | Geographic (WGS84 / EPSG:4326) for data exchange; Equal-Area projection for area measurements |
| Regional / national analysis | National projected CRS (e.g. Canada Atlas Lambert / EPSG:3978) |
| Local analysis (< 500 km extent) | UTM zone covering the study area |
| Terrain analysis, hydrology, LiDAR | Projected CRS in metres (UTM recommended) |
| Slope / distance calculations | Always use a projected CRS |
Finding your UTM zone: The UTM zone number equals ⌊(longitude + 180) / 6⌋ + 1. For Ottawa, Canada (longitude ≈ –75.7°): zone 18, northern hemisphere → EPSG:32618.
Checking the CRS of a Layer
- Right-click a layer in the Layers panel → Properties.
- Select the Information tab.
- Read the CRS field. Confirm:
- Authority and code (e.g.
EPSG:32618) - Unit (metres vs degrees)
- Datum (WGS84, NAD83, etc.)
- Authority and code (e.g.
Or in the Python Console:
from qgis.core import QgsProject
layer = QgsProject.instance().mapLayersByName('dem')[0]
crs = layer.crs()
print(crs.authid()) # e.g. "EPSG:32618"
print(crs.mapUnits()) # 0 = metres, 6 = degrees
print(crs.isGeographic()) # True if GCS
Setting the Project CRS
The project CRS controls the display and the default output CRS for tools that do not inherit CRS from their inputs.
Project → Properties → CRS tab → search by EPSG code or name → click OK.
Or use View → Panels → CRS Status at the bottom-right of the QGIS window to set the project CRS from any loaded layer.
Reprojecting a Raster
Use Reproject Raster to permanently transform a raster to a new CRS. This is required before any terrain analysis on a DEM stored in geographic (degree) coordinates.
Processing Toolbox → Whitebox Workflows → Raster → Reproject Raster
| Parameter | Recommended value |
|---|---|
| Input layer | dem_wgs84.tif |
| Target EPSG code | 32618 |
| Resampling method | bilinear (elevation surfaces) |
| Output | dem_utm18n.tif |
The Resampling method parameter accepts any of the methods supported by WbW's raster engine:
| Method | Best for |
|---|---|
nearest | Categorical / integer rasters (classification maps, stream grids) |
bilinear | Continuous surfaces (DEMs, slope, TWI, reflectance) |
cubic | High-quality continuous-surface resampling |
lanczos | High-quality sinc-window resampling |
average | 3×3 mean statistic |
min / max | 3×3 extremum statistics |
mode | 3×3 majority-class (smoothed categorical) |
median | 3×3 median statistic |
stddev | 3×3 standard deviation |
import processing
processing.run('whitebox_workflows:reproject_raster', {
'input': '/data/dem_wgs84.tif',
'epsg': 32618,
'resample': 'bilinear',
'output': '/data/dem_utm18n.tif',
})
After running, load the output and confirm in Layer Properties → Information:
CRS shows EPSG:32618 and the extent is in metres.
Reprojecting a Vector Layer
Use Reproject Vector to transform a vector dataset to a new CRS.
Processing Toolbox → Whitebox Workflows → Vector → Reproject Vector
| Parameter | Recommended value |
|---|---|
| Input layer | roads_wgs84.shp |
| Target EPSG code | 32618 |
| Output | roads_utm18n.shp |
import processing
processing.run('whitebox_workflows:reproject_vector', {
'input': '/data/roads_wgs84.shp',
'epsg': 32618,
'output': '/data/roads_utm18n.shp',
})
Reprojecting a LiDAR Dataset
Use Reproject LiDAR to transform a point cloud to a new CRS.
Processing Toolbox → Whitebox Workflows → LiDAR → Reproject LiDAR
| Parameter | Recommended value |
|---|---|
| Input LiDAR file | cloud_wgs84.laz |
| Target EPSG code | 32618 |
| Output | cloud_utm18n.laz |
import processing
processing.run('whitebox_workflows:reproject_lidar', {
'input': '/data/cloud_wgs84.laz',
'epsg': 32618,
'output': '/data/cloud_utm18n.laz',
})
Assigning a Missing CRS
If a file has correct coordinates but missing or wrong CRS metadata (shown as "Unknown CRS" in Layer Properties), use one of the three Assign Projection tools to write the correct EPSG code into the file without moving any coordinates.
Assign vs. Reproject: Assigning a CRS only updates the metadata label. Use it when coordinates are already in the target system but the file has no CRS tag. If the coordinate values themselves need to change, use the Reproject tools above instead.
Assign Projection to a Raster
Processing Toolbox → Whitebox Workflows → Raster → Assign Projection Raster
| Parameter | Value |
|---|---|
| Input layer | dem_no_crs.tif |
| EPSG code to assign | 32618 |
import processing
processing.run('whitebox_workflows:assign_projection_raster', {
'input': '/data/dem_no_crs.tif',
'epsg': 32618,
})
Assign Projection to a Vector
Processing Toolbox → Whitebox Workflows → Vector → Assign Projection Vector
| Parameter | Value |
|---|---|
| Input layer | roads_no_crs.shp |
| EPSG code to assign | 32618 |
import processing
processing.run('whitebox_workflows:assign_projection_vector', {
'input': '/data/roads_no_crs.shp',
'epsg': 32618,
})
Assign Projection to a LiDAR File
Processing Toolbox -> Whitebox Workflows -> LiDAR -> Assign Projection LiDAR
| Parameter | Value |
|---|---|
| Input LiDAR file | cloud_no_crs.laz |
| EPSG code to assign | 32618 |
import processing
processing.run('whitebox_workflows:assign_projection_lidar', {
'input': '/data/cloud_no_crs.laz',
'epsg': 32618,
})
Georeferencing from Control Points
Use Georeference Raster From Control Points when the raster has no valid georeferencing and you have control points that relate pixel coordinates to map coordinates.
Processing Toolbox -> Whitebox Workflows -> Raster -> Georeference Raster From Control Points
| Parameter | Recommended value |
|---|---|
| Input raster | historical_scan.tif |
| Control points CSV | historical_scan_gcps.csv |
| Destination EPSG code | 32618 |
| Resampling method | bilinear (continuous) or nearest (categorical) |
| Georeferenced raster output | historical_scan_georef.tif |
| Diagnostics report output | historical_scan_georef_report.json (optional) |
Control-points CSV fields must include source image coordinates and target map coordinates:
source_colsource_rowtarget_xtarget_y
import processing
processing.run('whitebox_workflows:georeference_raster_from_control_points', {
'input': '/data/historical_scan.tif',
'control_points': '/data/historical_scan_gcps.csv',
'epsg': 32618,
'resample': 'bilinear',
'output': '/data/historical_scan_georef.tif',
'report': '/data/historical_scan_georef_report.json',
})
Assign Projection to a LiDAR Dataset
Processing Toolbox → Whitebox Workflows → LiDAR → Assign Projection LiDAR
| Parameter | Value |
|---|---|
| Input LiDAR file | cloud_no_crs.laz |
| EPSG code to assign | 32618 |
import processing
processing.run('whitebox_workflows:assign_projection_lidar', {
'input': '/data/cloud_no_crs.laz',
'epsg': 32618,
})
The Z Factor for Terrain Tools
When a DEM's horizontal units differ from its vertical units, slope and curvature calculations are incorrect unless a Z factor is applied.
| Horizontal unit | Vertical unit | Z factor |
|---|---|---|
| Metres | Metres | 1.0 (no conversion needed) |
| Metres | Feet | 0.3048 |
| Feet | Feet | 1.0 |
| Degrees (geographic) | Metres | Do not use — reproject first |
All WbW terrain tools that accept a Z factor parameter apply the conversion
as: slope = atan(rise × z_factor / run).
Best practice: Reproject the DEM to a projected CRS in metres before running any terrain analysis. Set Z factor to
1.0after reprojection if vertical units are also metres.
Batch Reprojection via Python Console
Reproject all GeoPackages in a folder to EPSG:32618 using the Whitebox vector reprojection tool:
import processing
from pathlib import Path
src_dir = Path('/data/raw_vectors')
out_dir = Path('/data/projected')
out_dir.mkdir(exist_ok=True)
for gpkg in src_dir.glob('*.gpkg'):
out = out_dir / gpkg.name
processing.run('whitebox_workflows:reproject_vector', {
'input': str(gpkg),
'epsg': 32618,
'output': str(out),
})
print(f"Reprojected: {gpkg.name}")
print("Batch reprojection complete.")
Reproject a folder of LiDAR files in the same pattern:
import processing
from pathlib import Path
src_dir = Path('/data/raw_lidar')
out_dir = Path('/data/projected_lidar')
out_dir.mkdir(exist_ok=True)
for las in src_dir.glob('*.laz'):
out = out_dir / las.name
processing.run('whitebox_workflows:reproject_lidar', {
'input': str(las),
'epsg': 32618,
'output': str(out),
})
print(f"Reprojected: {las.name}")
print("Batch LiDAR reprojection complete.")
Common CRS Problems
| Problem | Likely cause | Fix |
|---|---|---|
| Layers display in wrong location | Layer has incorrect assigned CRS | Assign correct CRS (do not reproject) |
| Slope values in hundreds of degrees | DEM in geographic CRS (degrees) — cell size << 1° | Reproject DEM to metres before running slope |
| Area calculations wildly wrong | Layer CRS is geographic (degrees) | Reproject to equal-area projected CRS |
| Watershed does not close properly | Raster and vector inputs in different CRS | Reproject all inputs to same CRS before processing |
| WbW tool silently returns NoData everywhere | CRS mismatch causes spatial extents not to overlap | Verify all inputs share the same CRS and extent |
| "Datum transform not found" warning in QGIS | Datum shift grid file not installed | Install proj-data package, or accept approximate transform |
Validation Checklist
- Project CRS is set to the intended working CRS before analysis.
- All raster inputs share the same CRS, extent, and cell size.
- All vector inputs share the same CRS as the raster grid.
- DEM CRS is projected (linear units — metres or feet), not geographic.
-
Z factor is set to
1.0when both horizontal and vertical units are metres. - Reprojected outputs have been inspected (extent in metres, CRS code confirmed).
- No layers show "Unknown CRS" in the Layers panel.
Terrain Analysis and Geomorphometry
Terrain analysis — or geomorphometry — is the quantitative characterisation of land-surface form from digital elevation models (DEMs). It is one of the original strengths of the Whitebox platform and covers first-order derivatives (slope, aspect), curvature families, terrain position, roughness, multiscale analysis, and visibility.
This chapter walks through a complete primary-derivative workflow in the QGIS Processing Toolbox, followed by a Python console version for batch scripting.
Key Concepts
- DEM: Raster where each cell stores surface elevation. All terrain derivatives begin here. Common sources: LiDAR bare-earth, drone photogrammetry, SRTM, Copernicus DEM.
- Slope: Maximum rate of elevation change per unit distance (degrees or percent). Core input for erosion, landslide, and routing models.
- Aspect: Compass direction a slope faces (0–360°, clockwise from north). Flat cells are assigned –1. Controls solar insolation and moisture.
- Curvature: Rate of change of slope. Profile curvature describes flow acceleration/deceleration; plan curvature describes flow convergence/divergence.
- TPI / geomorphons: Terrain position indices and landform classification assign cells to ridge, slope, valley, etc., without manual thresholds.
- TWI: Topographic Wetness Index — ln(upslope area / tan(slope)) — predicts persistent soil moisture and runoff zones.
End-to-End Workflow: Primary Terrain Derivatives
This workflow takes a raw DEM through sink filling, then derives the most commonly used terrain surfaces.
Inputs
| Layer | Format | Notes |
|---|---|---|
dem.tif | GeoTIFF raster | Projected CRS (e.g. UTM) strongly recommended |
Step 1 — Fill Depressions
Sinks (isolated low cells) in a DEM cause flow-routing artifacts in all downstream terrain derivatives. Fill them first.
Processing Toolbox → Whitebox Workflows → Spatial Hydrology →
Fill Depressions
| Parameter | Recommended value |
|---|---|
| Input DEM | dem.tif |
| Fix flats | ✓ enabled |
| Flat increment | 0.001 (one thousandth of the DEM z unit) |
| Output | dem_filled.tif |
Why fix flats? Perfectly flat areas produce ambiguous flow directions. Adding a tiny gradient across flats ensures a routable surface.
Step 2 — Slope
Processing Toolbox → Whitebox Workflows → Terrain Analysis →
Slope
| Parameter | Recommended value |
|---|---|
| Input DEM | dem_filled.tif |
| Output units | Degrees |
| Z conversion factor | 1.0 (set to 0.3048 if DEM z is in feet but CRS is metres) |
| Output | slope.tif |
Expected output range: 0° (flat) to ~85° (near-vertical cliff). Values above 70° often indicate interpolation artefacts — inspect those cells.
Step 3 — Aspect
Processing Toolbox → Whitebox Workflows → Terrain Analysis →
Aspect
| Parameter | Recommended value |
|---|---|
| Input DEM | dem_filled.tif |
| Output | aspect.tif |
Flat cells receive –1. Apply a pseudocolor ramp (circular HSV) to aspect for intuitive visualisation of slope direction.
Step 4 — Hillshade
Processing Toolbox → Whitebox Workflows → Terrain Analysis →
Hillshade
| Parameter | Recommended value |
|---|---|
| Input DEM | dem_filled.tif |
| Azimuth (°) | 315 (NW sun — standard cartographic convention) |
| Altitude (°) | 45 |
| Z factor | 1.0 |
| Output | hillshade.tif |
Set the hillshade layer to Multiply blend mode in QGIS and overlay it on a coloured DEM for a publication-quality relief map.
Step 5 — Profile and Plan Curvature
Processing Toolbox → Whitebox Workflows → Terrain Analysis →
Profile Curvature
| Parameter | Recommended value |
|---|---|
| Input DEM | dem_filled.tif |
| Output | profile_curv.tif |
Repeat for Plan Curvature → plan_curv.tif.
Style both outputs with a diverging colour ramp centred on 0. Negative profile curvature (blue) marks deceleration zones (valley bottoms); positive (red) marks acceleration zones (ridge crests). Plan curvature negatives mark convergent hollows; positives mark divergent noses.
Step 6 — Topographic Wetness Index
Processing Toolbox → Whitebox Workflows → Terrain Analysis →
Wetness Index
| Parameter | Recommended value |
|---|---|
| Slope raster | slope.tif |
| Specific contributing area raster | (run D8 Flow Accumulation first — see Spatial Hydrology chapter) |
| Output | twi.tif |
High TWI values (> 8–10) indicate persistent moisture zones. Use as a predictor variable in soil, flood, and habitat models.
Python Console Equivalent
Paste the following into the QGIS Python Console (Plugins → Python Console) or save as a Processing script to batch multiple DEMs.
import processing
dem = '/data/dem.tif'
# Step 1: fill depressions
processing.run('whitebox_workflows:fill_depressions', {
'dem': dem,
'fix_flats': True,
'flat_increment': 0.001,
'output': '/data/dem_filled.tif',
})
# Step 2: slope
processing.run('whitebox_workflows:slope', {
'dem': '/data/dem_filled.tif',
'units': 'Degrees',
'zfactor': 1.0,
'output': '/data/slope.tif',
})
# Step 3: aspect
processing.run('whitebox_workflows:aspect', {
'dem': '/data/dem_filled.tif',
'output': '/data/aspect.tif',
})
# Step 4: hillshade
processing.run('whitebox_workflows:hillshade', {
'dem': '/data/dem_filled.tif',
'azimuth': 315.0,
'altitude': 45.0,
'zfactor': 1.0,
'output': '/data/hillshade.tif',
})
# Step 5: curvature
processing.run('whitebox_workflows:profile_curvature', {
'dem': '/data/dem_filled.tif',
'output': '/data/profile_curv.tif',
})
processing.run('whitebox_workflows:plan_curvature', {
'dem': '/data/dem_filled.tif',
'output': '/data/plan_curv.tif',
})
print("Terrain derivatives complete.")
Advanced: Geomorphons Landform Classification
Geomorphons classify each cell into one of ten landform elements (peak, ridge, shoulder, spur, slope, hollow, footslope, valley, pit, flat) by analysing horizon profiles in eight compass directions.
Processing Toolbox → Whitebox Workflows → Terrain Analysis →
Geomorphons
| Parameter | Recommended value |
|---|---|
| Input DEM | dem_filled.tif |
| Search distance (cells) | 50 (adjust to DEM resolution and landscape scale) |
| Skip radius (cells) | 0 |
| Flatness threshold (°) | 1.0 |
| Output | geomorphons.tif |
The output is a categorical raster (1–10). Apply a predefined categorical colour map — the Geomorphons palette is available in many QGIS style repositories.
processing.run('whitebox_workflows:geomorphons', {
'dem': '/data/dem_filled.tif',
'search': 50,
'skip': 0,
'threshold': 1.0,
'output': '/data/geomorphons.tif',
})
Common Pitfalls
| Problem | Likely cause | Fix |
|---|---|---|
| Slope values are unrealistically high (> 85°) | DEM has interpolation artefacts or NoData spikes | Run Remove Off-terrain Objects or inspect raw DEM |
| Flat areas produce zero slope everywhere | Depressions not filled before slope derivation | Run Fill Depressions first |
| Aspect shows –1 across large areas | Large flat regions in DEM | Expected for flat input; check DEM resolution |
| Curvature is noisy on fine-resolution DEMs | Sensor noise dominates at small spatial scales | Apply Gaussian Filter (σ ≈ 1–2 cells) before curvature |
| Units mismatch — Z factor warning | Horizontal CRS in metres but DEM z in feet | Set Z conversion factor to 0.3048 |
Validation Checklist
- DEM uses a projected CRS (not geographic degrees).
- No unexpected flat artefacts introduced by depression filling.
- Slope range plausible for local relief (inspect histogram).
- Aspect –1 cells are spatially limited to genuine flats.
- Curvature raster has a near-symmetric distribution centred on 0.
- Hillshade visually matches known ridgelines and valley geometry.
Spatial Hydrology
Spatial hydrology workflows extract hydrographic structure — flow directions, stream networks, watersheds, and hydrologic indices — from a terrain model. All hydrologic processing begins with a hydrologically conditioned DEM. This chapter demonstrates a complete watershed delineation workflow from raw DEM to labelled catchments.
Key Concepts
- Hydrologic conditioning: Removing or breaching depressions so that flow can route from every cell to a basin outlet without interruption.
- Flow direction: Per-cell pointer indicating which of the eight cardinal and diagonal neighbours receives runoff (D8 model) or a fractional multi-direction model (D-infinity, MD8).
- Flow accumulation: Upslope contributing area (in cells or area units). High values mark channels; low values mark ridges.
- Stream extraction threshold: Minimum contributing area that defines a first-order channel. Smaller thresholds produce denser networks.
- Watershed / catchment: All cells draining to a common outlet. Delineated by tracing flow direction upstream from outlet points.
- Strahler order: Hierarchical stream ordering from headwaters (order 1) to main channel (highest order). Used to characterise drainage network complexity.
End-to-End Workflow: Watershed Delineation
Inputs
| Layer | Format | Notes |
|---|---|---|
dem.tif | GeoTIFF raster | Projected CRS, metres |
outlets.shp | Point vector | One or more pour points |
Step 1 — Breach Depressions (Preferred Conditioning Method)
Breaching cuts a narrow channel through depression rims rather than filling them, preserving more of the original topography.
Processing Toolbox → Whitebox Workflows → Spatial Hydrology →
Breach Depressions (Least Cost)
| Parameter | Recommended value |
|---|---|
| Input DEM | dem.tif |
| Maximum search distance (cells) | 10 |
| Maximum breach depth | 2.0 (metres) |
| Flat increment | 0.001 |
| Fill remaining depressions | ✓ enabled |
| Output | dem_conditioned.tif |
If the DEM has large lake or wetland depressions that should not be breached, use
Fill Depressionsinstead.
Step 2 — D8 Flow Direction
Processing Toolbox → Whitebox Workflows → Spatial Hydrology →
D8 Pointer
| Parameter | Recommended value |
|---|---|
| Input DEM | dem_conditioned.tif |
| Output | d8_pointer.tif |
The output is an integer raster (powers of 2: 1, 2, 4, 8, 16, 32, 64, 128) encoding the direction to the steepest downslope neighbour.
Step 3 — D8 Flow Accumulation
Processing Toolbox → Whitebox Workflows → Spatial Hydrology →
D8 Flow Accumulation
| Parameter | Recommended value |
|---|---|
| Input D8 pointer | d8_pointer.tif |
| Output type | Cells |
| Log-transform output | ☐ (disable for threshold-based channel extraction) |
| Output | d8_accum.tif |
Visualise with a logarithmic stretch. The highest values form the main channel network.
Step 4 — Extract Stream Network
Processing Toolbox → Whitebox Workflows → Spatial Hydrology →
Extract Streams
| Parameter | Recommended value |
|---|---|
| Flow accumulation raster | d8_accum.tif |
| Threshold | 500 (cells — adjust for DEM resolution and drainage density) |
| Zero background | ✓ enabled |
| Output | streams.tif |
Rule of thumb: for a 10 m DEM, a threshold of 500 cells ≈ 0.05 km² contributing area, producing a moderately dense first-order network. Halve or double the threshold to adjust density.
Convert to vector for display: Processing → Raster Streams to Vector
→ streams.shp.
Step 5 — Snap Pour Points
Pour points must sit on the channel raster. Snap them to the nearest high-accumulation cell to avoid off-channel watershed boundaries.
Processing Toolbox → Whitebox Workflows → Spatial Hydrology →
Snap Pour Points
| Parameter | Recommended value |
|---|---|
| Pour points | outlets.shp |
| Flow accumulation | d8_accum.tif |
| Snap distance (map units) | 200 (metres — adjust to point accuracy) |
| Output | outlets_snapped.shp |
Step 6 — Watershed Delineation
Processing Toolbox → Whitebox Workflows → Spatial Hydrology →
Watershed
| Parameter | Recommended value |
|---|---|
| D8 pointer | d8_pointer.tif |
| Pour points | outlets_snapped.shp |
| Output | watersheds.tif |
Each outlet receives a unique integer ID; cells are assigned that ID. Use
Raster to Vector (Polygons) in QGIS to produce watershed boundary
polygons, then dissolve by ID if multiple raster cells share an outlet.
Step 7 — Strahler Stream Order (Optional)
Processing Toolbox → Whitebox Workflows → Spatial Hydrology →
Strahler Stream Order
| Parameter | Recommended value |
|---|---|
| D8 pointer | d8_pointer.tif |
| Streams raster | streams.tif |
| Output | strahler.tif |
Python Console Equivalent
import processing
dem = '/data/dem.tif'
outlets = '/data/outlets.shp'
# Step 1: condition DEM
processing.run('whitebox_workflows:breach_depressions_least_cost', {
'dem': dem,
'max_dist': 10,
'max_depth': 2.0,
'flat_increment': 0.001,
'fill': True,
'output': '/data/dem_conditioned.tif',
})
# Step 2: flow direction
processing.run('whitebox_workflows:d8_pointer', {
'dem': '/data/dem_conditioned.tif',
'output': '/data/d8_pointer.tif',
})
# Step 3: flow accumulation
processing.run('whitebox_workflows:d8_flow_accumulation', {
'input': '/data/d8_pointer.tif',
'output_type': 'Cells',
'log': False,
'output': '/data/d8_accum.tif',
})
# Step 4: extract streams
processing.run('whitebox_workflows:extract_streams', {
'flow_accum': '/data/d8_accum.tif',
'threshold': 500.0,
'zero_background': True,
'output': '/data/streams.tif',
})
# Step 5: snap pour points
processing.run('whitebox_workflows:snap_pour_points', {
'pour_pts': outlets,
'flow_accum': '/data/d8_accum.tif',
'snap_dist': 200.0,
'output': '/data/outlets_snapped.shp',
})
# Step 6: watershed
processing.run('whitebox_workflows:watershed', {
'd8_pntr': '/data/d8_pointer.tif',
'pour_pts': '/data/outlets_snapped.shp',
'output': '/data/watersheds.tif',
})
print("Watershed delineation complete.")
Advanced: Topographic Wetness Index
TWI requires the specific catchment area (flow accumulation in area units per unit contour width) rather than the raw cell count.
# SCA-based flow accumulation
processing.run('whitebox_workflows:d8_flow_accumulation', {
'input': '/data/d8_pointer.tif',
'output_type': 'Specific Contributing Area',
'log': False,
'output': '/data/sca.tif',
})
# Slope in radians (required for TWI)
processing.run('whitebox_workflows:slope', {
'dem': '/data/dem_conditioned.tif',
'units': 'Radians',
'output': '/data/slope_rad.tif',
})
# TWI
processing.run('whitebox_workflows:wetness_index', {
'sca': '/data/sca.tif',
'slope': '/data/slope_rad.tif',
'output': '/data/twi.tif',
})
Common Pitfalls
| Problem | Likely cause | Fix |
|---|---|---|
| Watershed does not extend to expected ridgeline | Pour point not on channel raster | Run Snap Pour Points before Watershed |
| Parallel flow stripes in accumulation raster | Flat areas in conditioned DEM | Enable fix-flats during conditioning |
| Stream network is too sparse / too dense | Threshold too high / too low | Halve or double threshold and re-inspect |
| Watershed covers entire DEM | Pour point is at or near the DEM outlet cell | Check that outlet coordinates fall inside the DEM extent |
| TWI has very high values in flat areas | Slope is near-zero, causing division by tan(0) | Mask flat areas or apply a minimum slope floor (e.g. 0.001 rad) |
Validation Checklist
- Conditioned DEM has no isolated flat areas (check flow direction raster for NoData).
- Flow accumulation values increase monotonically toward basin outlet.
- Extracted channels follow expected valley geometry in the DEM.
- Snapped pour points lie on the highest-accumulation cells within snap distance.
- Watershed boundary is a closed polygon that contains the pour point.
- Strahler orders are consistent with tributary junctions.
LiDAR Processing
LiDAR point clouds are the highest-resolution elevation data source available for most practitioners. WbW-QGIS exposes the full Whitebox LiDAR pipeline — from quality assurance through ground classification, surface modelling, and height normalisation — directly in the QGIS Processing Toolbox.
This chapter walks through a complete bare-earth and canopy-height workflow starting from a raw LAS/LAZ file.
Key Concepts
- Point cloud: A set of 3-D coordinates (X, Y, Z) plus attributes (intensity, return number, classification, scan angle, etc.) acquired by laser scanning from airborne or terrestrial platforms.
- LAS/LAZ: The industry-standard binary format for point clouds. LAZ is a losslessly compressed variant. COPC (Cloud-Optimised Point Cloud) is a tiled LAZ variant for efficient streaming.
- Classification codes: Numeric labels assigned to points indicating surface type (1 = unclassified, 2 = ground, 3–5 = vegetation, 6 = building, etc. per ASPRS convention).
- DTM: Digital Terrain Model — a raster surface interpolated from ground-classified points only.
- DSM: Digital Surface Model — a raster surface from first returns, representing the tops of all objects (vegetation, buildings).
- CHM: Canopy Height Model — DSM minus DTM, representing object height above ground.
- Height above ground (HAG): Per-point elevation relative to the interpolated ground surface. Enables classification of vegetation returns by height tier.
End-to-End Workflow: DTM, DSM, and Canopy Height Model
Inputs
| Layer | Format | Notes |
|---|---|---|
cloud.laz | LAZ point cloud | Any ASPRS LAS version 1.0–1.4 |
Step 1 — Point Cloud Quality Check
Processing Toolbox → Whitebox Workflows → LiDAR →
LiDAR Point Stats
| Parameter | Recommended value |
|---|---|
| Input LiDAR file | cloud.laz |
| Output | cloud_stats.html (HTML report) |
Review the report for:
- Point density (pts/m²)
- Classification distribution (% ground, vegetation, unclassified)
- Z range and intensity histogram
- Scan angle range (should be ±20° for most airborne missions)
Step 2 — Thin High-Density Files (Optional)
For files > 50 pts/m², thinning reduces processing time with minimal accuracy loss.
Processing Toolbox → Whitebox Workflows → LiDAR →
LiDAR Thin
| Parameter | Recommended value |
|---|---|
| Input LiDAR file | cloud.laz |
| Resolution | 0.5 (metres) |
| Retain ground points | ✓ enabled |
| Output | cloud_thin.laz |
Step 3 — Classify Ground Points
If the input file has all points as unclassified (class 1), classify ground returns before surface modelling.
Processing Toolbox → Whitebox Workflows → LiDAR →
LiDAR Ground Point Filter
| Parameter | Recommended value |
|---|---|
| Input LiDAR file | cloud.laz (or cloud_thin.laz) |
| Radius (m) | 2.0 |
| Minimum slope (°) | 5.0 |
| Maximum slope (°) | 85.0 |
| Terrain type | Normal |
| Output | cloud_classified.laz |
For complex terrain (steep slopes, dense vegetation), increase radius to
4.0and reduce minimum slope to2.0.
Step 4 — Build DTM from Ground Points
Processing Toolbox → Whitebox Workflows → LiDAR →
LiDAR IDW Interpolation
| Parameter | Recommended value |
|---|---|
| Input LiDAR file | cloud_classified.laz |
| IDW weight | 2.0 |
| Search radius (m) | 2.5 |
| Minimum number of points | 3 |
| Exclusion classes | (leave empty to use ground points only) |
| Returns | Last |
| Point classes included | 2 (ground) |
| Grid resolution | 0.5 |
| Output | dtm.tif |
Alternatively, use LiDAR TIN Gridding for faster interpolation on
uniformly distributed clouds.
Step 5 — Build DSM from First Returns
Processing Toolbox → Whitebox Workflows → LiDAR →
LiDAR IDW Interpolation (second pass)
| Parameter | Recommended value |
|---|---|
| Input LiDAR file | cloud_classified.laz |
| Returns | First |
| Point classes included | (all — leave blank) |
| Grid resolution | 0.5 |
| Output | dsm.tif |
Step 6 — Canopy Height Model
Subtract DTM from DSM using the QGIS Raster Calculator, or use the dedicated CHM tool.
Processing Toolbox → Whitebox Workflows → LiDAR →
Canopy Height Model
| Parameter | Recommended value |
|---|---|
| Input LiDAR file | cloud_classified.laz |
| DTM raster | dtm.tif |
| Grid resolution | 0.5 |
| Output | chm.tif |
Apply a stretch from 0 to the 98th percentile height value. Negative cells (< 0) indicate DTM–DSM interpolation artefacts; clamp to 0 in post-processing.
Step 7 — Height Above Ground Normalisation
Assign a HAG value to every point for per-return vertical stratification analysis.
Processing Toolbox → Whitebox Workflows → LiDAR →
Height Above Ground
| Parameter | Recommended value |
|---|---|
| Input LiDAR file | cloud_classified.laz |
| Output | cloud_hag.laz |
The tool sets the Z coordinate of each point to its height above the interpolated ground surface. Ground points are set to 0.
Python Console Equivalent
import processing
cloud = '/data/cloud.laz'
# Step 1: stats / QA
processing.run('whitebox_workflows:lidar_point_stats', {
'input': cloud,
'output': '/data/cloud_stats.html',
})
# Step 3: ground classification
processing.run('whitebox_workflows:lidar_ground_point_filter', {
'input': cloud,
'radius': 2.0,
'min_slope': 5.0,
'max_slope': 85.0,
'terrain_type': 'Normal',
'output': '/data/cloud_classified.laz',
})
# Step 4: DTM
processing.run('whitebox_workflows:lidar_idw_interpolation', {
'input': '/data/cloud_classified.laz',
'parameter': 'elevation',
'returns': 'Last',
'classes_included': '2',
'weight': 2.0,
'radius': 2.5,
'min_points': 3,
'resolution': 0.5,
'output': '/data/dtm.tif',
})
# Step 5: DSM
processing.run('whitebox_workflows:lidar_idw_interpolation', {
'input': '/data/cloud_classified.laz',
'parameter': 'elevation',
'returns': 'First',
'classes_included': '',
'weight': 2.0,
'radius': 2.5,
'min_points': 3,
'resolution': 0.5,
'output': '/data/dsm.tif',
})
# Step 6: CHM
processing.run('whitebox_workflows:canopy_height_model', {
'input': '/data/cloud_classified.laz',
'dtm': '/data/dtm.tif',
'resolution': 0.5,
'output': '/data/chm.tif',
})
# Step 7: HAG
processing.run('whitebox_workflows:height_above_ground', {
'input': '/data/cloud_classified.laz',
'output': '/data/cloud_hag.laz',
})
print("LiDAR pipeline complete.")
Common Pitfalls
| Problem | Likely cause | Fix |
|---|---|---|
| DTM has large NoData holes | Ground point density too low | Increase IDW search radius or use TIN gridding |
| CHM has negative values | DTM higher than DSM in flat/water areas | Clamp CHM ≥ 0 with Raster Calculator after generation |
| Classification codes all zero after processing | Input was LAS point format 6–10 and legacy writer bug (see known issues) | Use WbW Next Gen pipeline — classification is preserved correctly |
| Ground filter over-segments in steep terrain | Slope parameters too restrictive | Increase max slope to 85° and radius to 4 m |
| LiDAR stats report extreme Z values | Outlier high/low points present | Run LiDAR Remove Outliers before classification |
Validation Checklist
- Point stats report shows expected classification distribution (> 5 % ground points).
- DTM has no large NoData holes in vegetated areas.
- DTM is smooth with no pits deeper than 1–2 m.
- DSM ≥ DTM across the entire overlap extent.
- CHM values are 0 on roads and bare ground.
- HAG-normalised cloud has all ground points at Z ≈ 0 (±0.1 m).
Remote Sensing Analysis
Remote sensing workflows in WbW-QGIS cover multispectral and hyperspectral image analysis: spectral index computation, image enhancement, principal component analysis (PCA), segmentation, and change detection.
This chapter is aligned with the Python and R manuals while staying focused on QGIS Processing Toolbox execution patterns.
Core Concepts You Should Know First
- Spectral bands: Wavelength-specific image channels (for example blue, red, NIR, SWIR) used to separate land-cover materials.
- Spectral indices: Band combinations that highlight specific targets, such as NDVI (vegetation), NDWI (water), and NBR (burn severity).
- Spatial resolution: Pixel size affects detail and detectability of features.
- Temporal resolution: Revisit interval controls change-detection sensitivity.
- Atmospheric and cloud effects: Compare like-with-like by masking clouds and shadows and using corrected imagery where possible.
- Change detection: Compare index or class outputs across acquisition dates.
- Dimensionality reduction: PCA reduces band redundancy before segmentation or classification.
Typical Inputs
| Layer | Format | Notes |
|---|---|---|
| image_t1.tif | Multiband GeoTIFF | Time-1 scene, reflectance preferred |
| image_t2.tif | Multiband GeoTIFF | Time-2 scene, same sensor/preprocessing |
| cloud_mask_t1.tif | Raster | Optional cloud or QA-derived mask |
| cloud_mask_t2.tif | Raster | Optional cloud or QA-derived mask |
End-to-End Workflow
Step 1 - Quality Check and Harmonize Inputs
Before analysis:
- Confirm both scenes use the same CRS, grid, and pixel size.
- Confirm band order and data scale (for example 0-1 reflectance or scaled integer reflectance).
- Mask clouds/shadows using QA products or Raster Calculator conditions.
Use QGIS Raster menu tools as needed:
- Align Raster
- Warp (Reproject)
- Raster Calculator
Step 2 - Build Key Spectral Indices
Process both dates with the same settings.
Processing Toolbox -> Whitebox Workflows -> Remote Sensing:
- NDVI
- Normalized Difference Index (for NDWI, NBR, NDSI, NDBI patterns)
Recommended outputs:
- ndvi_t1.tif, ndvi_t2.tif
- ndwi_t1.tif, ndwi_t2.tif
- nbr_t1.tif, nbr_t2.tif
Example NDVI run:
| Parameter | Value |
|---|---|
| Input image | image_t1.tif |
| NIR band | sensor-specific (for example 4, 5, or 8 depending on product) |
| Red band | sensor-specific |
| Output | ndvi_t1.tif |
Repeat for time 2.
Step 3 - Create Change Surfaces
Use QGIS Raster Calculator for differencing:
- NDVI change: ndvi_t2 - ndvi_t1
- NBR change: nbr_t2 - nbr_t1
Then classify into practical bins (loss, stable, gain) using:
- Whitebox Workflows -> Raster Analysis -> Reclass
- or Raster Calculator threshold expressions
Suggested interpretation for NDVI change:
| Class | Threshold |
|---|---|
| Strong loss | < -0.20 |
| Moderate loss | -0.20 to -0.10 |
| Stable | -0.10 to 0.10 |
| Moderate gain | 0.10 to 0.20 |
| Strong gain | > 0.20 |
Step 4 - Dimensionality Reduction (PCA)
Processing Toolbox -> Whitebox Workflows -> Remote Sensing -> Principal Component Analysis
Use PCA when:
- Bands are highly correlated.
- You need compact inputs for segmentation or clustering.
- You are preparing a classification feature stack.
Inspect output variance/eigenvalue diagnostics and retain only the components needed for most variance.
Step 5 - Segmentation and Classification Prep
Processing Toolbox -> Whitebox Workflows -> Remote Sensing -> Image Segmentation
Typical tuning:
- Lower threshold -> more, smaller segments
- Higher threshold -> fewer, larger segments
- Minimum segment size removes speckle
After segmentation:
- Optionally polygonize segment rasters in QGIS.
- Join zonal metrics from index layers.
- Use resulting segment features for training/validation workflows.
QGIS Python Console Equivalent
Use this pattern for reproducible batch processing in QGIS:
import processing
img_t1 = '/data/image_t1.tif'
img_t2 = '/data/image_t2.tif'
for label, img in [('t1', img_t1), ('t2', img_t2)]:
processing.run('whitebox_workflows:ndvi', {
'input': img,
'nir_band': 4,
'red_band': 3,
'output': f'/data/ndvi_{label}.tif',
})
processing.run('qgis:rastercalculator', {
'EXPRESSION': '"ndvi_t2@1" - "ndvi_t1@1"',
'LAYERS': ['/data/ndvi_t1.tif', '/data/ndvi_t2.tif'],
'OUTPUT': '/data/ndvi_change.tif',
})
processing.run('whitebox_workflows:principal_component_analysis', {
'input': '/data/image_t1.tif',
'num_comp': 4,
'output': '/data/pca_t1.tif',
})
Common Pitfalls
| Problem | Likely cause | Fix |
|---|---|---|
| Index values are outside expected ranges | Wrong bands or scale mismatch | Verify band mapping and value scale |
| Apparent change is mostly cloud edges | Missing cloud/shadow masking | Mask QA classes before differencing |
| PCA output looks unstable | NoData included in stats | Mask NoData consistently |
| Segmentation over-merges features | Threshold too high | Lower threshold and retest |
| Change map is noisy | Different spatial grids or radiometry | Align rasters and normalize radiometry |
Validation Checklist
- Inputs are co-registered and in a common CRS.
- Band assignments match sensor metadata.
- Cloud/shadow/no-data masking applied consistently across dates.
- Index histograms are plausible for local land cover.
- Change classes were reviewed visually against source imagery.
- PCA/segmentation parameters were documented for reproducibility.
Raster Analysis
General raster analysis covers a broad set of cell-based operations: local algebra on single rasters, focal statistics across neighbourhoods, zonal summaries within polygon regions, reclassification, and suitability scoring. These tools form the computational backbone of many GIS modelling workflows.
This chapter demonstrates an environmental suitability analysis that combines multiple raster datasets through reclassification and weighted overlay.
Key Concepts
- Local operations: Applied to each cell independently — arithmetic, logic, trigonometry, conditional assignment. Result depends only on the cell's own value (and corresponding cells in other input rasters).
- Focal operations: Applied to each cell using values within a spatial neighbourhood (kernel). Examples: focal mean, focal max, focal standard deviation.
- Zonal statistics: Aggregate raster values by zone boundaries defined by a second raster or vector polygon layer.
- Reclassification: Map old cell values to new values via a lookup table or range intervals. Core step in suitability and habitat modelling.
- Weighted overlay: Combine multiple reclassified factor rasters using factor-specific weights. The weighted sum produces a composite suitability score.
- NoData / null handling: Cells with NoData propagate through most local operations. Ensure all input rasters share the same extent, resolution, and NoData mask before combining them.
End-to-End Workflow: Multi-Criteria Habitat Suitability
This workflow scores terrain for habitat suitability using slope, TWI, and distance from water as factors.
Inputs
| Layer | Format | Notes |
|---|---|---|
slope.tif | GeoTIFF raster | Degrees, from terrain analysis |
twi.tif | GeoTIFF raster | Topographic Wetness Index |
streams.tif | GeoTIFF raster | Binary stream network |
All rasters must share the same projected CRS, extent, and cell size before
being combined. Use Snap Raster Extents or QGIS Warp (Reproject)
to align if needed.
Step 1 — Compute Distance from Water
Processing Toolbox → Whitebox Workflows → GIS Analysis →
Euclidean Distance
| Parameter | Recommended value |
|---|---|
| Input feature | streams.tif |
| Output | dist_water.tif |
Cells closer to water receive smaller values. This will be reclassified so that proximity = higher suitability.
Step 2 — Reclassify Slope Factor
Assign suitability scores 1–5 to slope ranges.
Processing Toolbox → Whitebox Workflows → Raster Analysis →
Reclass
| Class | Slope range (°) | Suitability score |
|---|---|---|
| 1 | > 30 | 1 (unsuitable) |
| 2 | 20–30 | 2 |
| 3 | 10–20 | 3 |
| 4 | 5–10 | 4 |
| 5 | 0–5 | 5 (most suitable) |
Use Reclass From File with a two-column table file (old value; new value)
or set up intervals in the tool dialogue.
| Parameter | Recommended value |
|---|---|
| Input raster | slope.tif |
| Reclass intervals file | slope_reclass.txt |
| Output | slope_reclass.tif |
Step 3 — Reclassify TWI Factor
| Class | TWI range | Suitability score |
|---|---|---|
| 1 | < 4 | 1 |
| 2 | 4–6 | 2 |
| 3 | 6–8 | 3 |
| 4 | 8–10 | 4 |
| 5 | > 10 | 5 |
Processing Toolbox → Whitebox Workflows → Raster Analysis → Reclass
Input: twi.tif → Output: twi_reclass.tif
Step 4 — Reclassify Distance from Water
Closer = more suitable:
| Class | Distance range (m) | Suitability score |
|---|---|---|
| 1 | > 500 | 1 |
| 2 | 300–500 | 2 |
| 3 | 100–300 | 3 |
| 4 | 50–100 | 4 |
| 5 | 0–50 | 5 |
Processing Toolbox → Whitebox Workflows → Raster Analysis → Reclass
Input: dist_water.tif → Output: dist_water_reclass.tif
Step 5 — Weighted Overlay (Raster Calculator)
Combine the three factors using assigned weights (must sum to 1.0).
| Factor | Weight |
|---|---|
| Slope suitability | 0.4 |
| TWI suitability | 0.35 |
| Distance suitability | 0.25 |
QGIS Raster Calculator:
("slope_reclass@1" * 0.4) + ("twi_reclass@1" * 0.35) + ("dist_water_reclass@1" * 0.25)
Output: suitability.tif (range 1–5, continuous).
Step 6 — Zonal Statistics (Optional)
Summarise suitability scores by catchment polygon.
Processing Toolbox → QGIS → Vector Analysis →
Zonal Statistics (QGIS native)
| Parameter | Recommended value |
|---|---|
| Input raster | suitability.tif |
| Vector layer | watersheds.shp |
| Statistics | Mean, Max, Std Dev |
| Output column prefix | suit_ |
Python Console Equivalent
import processing
# Step 1: distance from water
processing.run('whitebox_workflows:euclidean_distance', {
'input': '/data/streams.tif',
'output': '/data/dist_water.tif',
})
# Step 2–4: reclassify each factor
for src, dst in [
('slope', 'slope_reclass'),
('twi', 'twi_reclass'),
('dist_water', 'dist_water_reclass'),
]:
processing.run('whitebox_workflows:reclass_from_file', {
'input': f'/data/{src}.tif',
'reclass_vals': f'/data/{src}_reclass.txt',
'output': f'/data/{dst}.tif',
})
# Step 5: weighted overlay via Raster Calculator
processing.run('qgis:rastercalculator', {
'EXPRESSION': '("slope_reclass@1" * 0.4) + ("twi_reclass@1" * 0.35) + ("dist_water_reclass@1" * 0.25)',
'LAYERS': [
'/data/slope_reclass.tif',
'/data/twi_reclass.tif',
'/data/dist_water_reclass.tif',
],
'OUTPUT': '/data/suitability.tif',
})
print("Suitability analysis complete.")
Advanced: Focal Statistics
Focal statistics smooth or enhance spatial patterns at a neighbourhood scale.
Processing Toolbox → Whitebox Workflows → Raster Analysis →
Mean Filter
| Parameter | Recommended value |
|---|---|
| Input raster | suitability.tif |
| Filter size X | 5 (cells) |
| Filter size Y | 5 |
| Output | suitability_smooth.tif |
Use Standard Deviation Filter to highlight areas of high local variability,
or Percentile Filter for rank-based neighbourhood smoothing.
Common Pitfalls
| Problem | Likely cause | Fix |
|---|---|---|
| Raster Calculator outputs all NoData | Rasters have different extents or CRS | Clip/warp all inputs to common grid before combining |
| Reclass produces unexpected values | Range gaps or overlaps in reclass table | Verify that intervals are contiguous with no gap or overlap |
| Zonal statistics returns wrong polygon counts | Raster–vector CRS mismatch | Reproject vector to match raster CRS before running |
| Weighted overlay result > 5 | Weights do not sum to 1.0 | Recalculate weights so they sum to exactly 1.0 |
| Focal filter introduces edge NoData | Kernel extends beyond raster boundary | Pad raster with Expand Raster before filtering, or ignore edge cells |
Validation Checklist
- All input rasters aligned (same CRS, extent, cell size, NoData value).
- Reclass table covers the full observed value range with no gaps.
- Weighted overlay weights sum to 1.0.
- Output suitability range matches expected 1–5 interval.
- Zonal statistics polygon CRS matches raster CRS.
- Focal filter kernel size is appropriate for target feature scale.
Vector Analysis
Vector analysis in WbW-QGIS covers geometry validation, overlay operations, attribute enrichment, spatial selection, proximity analysis, and spatial joining. Whitebox supplements the native QGIS vector toolbox with high-performance tools built on the wbtopology spatial index.
This chapter walks through a complete parcel-attribute enrichment workflow — a common task in land management and environmental assessment.
Key Concepts
- Geometry validity: Self-intersecting rings, duplicate vertices, and unclosed polygons cause silent failures in overlay tools. Always validate and repair geometry before any overlay operation.
- Spatial join: Assigns attributes from one layer to features in another based on spatial relationship (intersects, contains, nearest). Supports aggregation modes (first, last, sum, mean, count, min, max).
- Near analysis: Finds the nearest feature (or features within a distance) from a source layer to a target layer. Returns distance and optional target attributes.
- Clip / Intersection / Difference: Standard polygon overlay operations. Clip retains the geometry of input A bounded by B. Intersection produces the geometric overlap. Difference removes the overlap.
- Add geometry attributes: Computes and appends area, perimeter, length, centroid coordinates, or bounding box dimensions as new attribute fields.
- Select by location: Spatial predicate query (intersects, within, contains, etc.) that produces a feature selection or a new filtered layer.
End-to-End Workflow: Parcel Attribute Enrichment
This workflow assigns catchment statistics and proximity-to-road measurements to a parcel layer.
Inputs
| Layer | Format | Notes |
|---|---|---|
parcels.shp | Polygon vector | Land parcel boundaries |
catchments.shp | Polygon vector | Watershed polygons with area and slope stats |
roads.shp | Polyline vector | Road network |
Step 1 — Validate and Repair Geometry
Processing Toolbox → Vector Geometry → Fix Geometries (QGIS native)
| Parameter | Recommended value |
|---|---|
| Input layer | parcels.shp |
| Output | parcels_valid.shp |
Run Check Validity on both catchments.shp and roads.shp and fix any
errors before proceeding.
Step 2 — Add Geometry Attributes to Parcels
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Add Geometry Attributes
| Parameter | Recommended value |
|---|---|
| Input vector | parcels_valid.shp |
| Units | Metres |
| Output | parcels_geom.shp |
This appends AREA, PERIMETER, and centroid X/Y fields to each parcel.
Step 3 — Spatial Join: Assign Catchment Attributes to Parcels
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Spatial Join
| Parameter | Recommended value |
|---|---|
| Target layer | parcels_geom.shp |
| Join layer | catchments.shp |
| Spatial relationship | Intersects |
| Join strategy | First (largest overlap catchment) |
| Fields to join | catch_id, mean_slope, area_km2 |
| Output | parcels_joined.shp |
Each parcel now carries the attributes of the catchment it intersects most.
Step 4 — Near: Distance from Each Parcel to Nearest Road
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Near
| Parameter | Recommended value |
|---|---|
| Input vector (source) | parcels_joined.shp |
| Near vector (target) | roads.shp |
| Max search distance (m) | 5000 (0 = search all) |
| Output | parcels_near.shp |
Appended fields: NEAR_DIST (metres to nearest road segment),
NEAR_FID (FID of nearest road feature).
Step 5 — Select High-Priority Parcels
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Select By Attribute or use QGIS Select by Expression:
"AREA" > 10000 AND "NEAR_DIST" < 500 AND "mean_slope" < 10
Export the selection as priority_parcels.shp using
Layer → Export → Save Selected Features As.
Step 6 — Clip Parcels to Study Area (Optional)
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Line Polygon Clip (for lines) or QGIS Clip for polygon-on-polygon.
| Parameter | Recommended value |
|---|---|
| Input vector | priority_parcels.shp |
| Clip polygon | study_area.shp |
| Output | priority_parcels_clipped.shp |
TopoJSON Conversion Chain (QGIS Interop)
Use this workflow when you need to exchange shared-boundary vector data with web clients while keeping a GeoPackage working copy for analysis.
Inputs
| Layer | Format | Notes |
|---|---|---|
zones.gpkg | Polygon vector | Authoritative analysis dataset |
Step 1 — Run a Whitebox vector operation and emit TopoJSON
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Add Geometry Attributes
| Parameter | Recommended value |
|---|---|
| Input vector | zones.gpkg |
| Units | Metres |
| Output | zones_metrics.topojson |
This confirms the plugin accepts .topojson output targets in a normal vector
processing chain.
Step 2 — Re-open TopoJSON and convert back to GeoPackage
- Add
zones_metrics.topojsonto the QGIS project. - Right-click the layer, then choose Export → Save Features As....
- Set format to GeoPackage and save as
zones_metrics_roundtrip.gpkg.
Step 3 — Validate roundtrip integrity
Check these before publishing or reusing the roundtrip layer:
- Feature count matches source layer.
- Core attributes (e.g., ID fields and geometry metrics) are preserved.
- CRS is correctly populated on the roundtrip GeoPackage.
Recommended use pattern
- Keep
.gpkgas the editable analysis master. - Generate
.topojsonas interchange or web-delivery artifacts. - Re-import to
.gpkgfor heavier downstream spatial analysis.
TopoJSON Boundary-Preserving Generalization Chain
Use this chain when you need smaller delivery payloads while preserving shared boundary consistency during simplification.
Inputs
| Layer | Format | Notes |
|---|---|---|
admin_units.gpkg | Polygon vector | Shared boundaries between adjacent polygons |
Step 1 — Simplify and emit TopoJSON
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Simplify Features
| Parameter | Recommended value |
|---|---|
| Input vector | admin_units.gpkg |
| Algorithm | Douglas-Peucker |
| Tolerance | 25.0 (adjust to target scale) |
| Output | admin_units_simplified.topojson |
Step 2 — Inspect topology consistency in QGIS
- Add
admin_units_simplified.topojsonto the map. - Inspect shared boundaries at high zoom for slivers/gaps.
- Validate feature count versus source before publication.
Step 3 — Export analysis copy
Export to admin_units_simplified.gpkg for downstream joins/overlay work.
TopoJSON Transport + Enrichment Return Chain
Use this chain when TopoJSON is used only for transport and you need to return to an analysis-grade format for attribute enrichment.
Inputs
| Layer | Format | Notes |
|---|---|---|
transport_in.topojson | Topology-preserving vector | Interchange input received from external system |
Step 1 — Convert transport input to GeoPackage
- Add
transport_in.topojsonto the project. - Export as
transport_stage.gpkg.
Step 2 — Apply enrichment tools
Run Whitebox vector tools against transport_stage.gpkg:
Add Geometry Attributesfor geometry metrics.Spatial Joinfor contextual attribute enrichment.Nearfor proximity attributes.
Step 3 — Emit deliverables
Write two outputs:
transport_enriched.gpkgfor analytic persistence.transport_enriched.topojsonfor interchange/web handoff.
Python Console Equivalent
import processing
# Step 1: fix geometry
processing.run('native:fixgeometries', {
'INPUT': '/data/parcels.shp',
'OUTPUT': '/data/parcels_valid.shp',
})
# Step 2: add geometry attributes
processing.run('whitebox_workflows:add_geometry_attributes', {
'input': '/data/parcels_valid.shp',
'units': 'Metres',
'output': '/data/parcels_geom.shp',
})
# Step 3: spatial join
processing.run('whitebox_workflows:spatial_join', {
'input': '/data/parcels_geom.shp',
'join': '/data/catchments.shp',
'spatial_relation': 'Intersects',
'join_method': 'First',
'output': '/data/parcels_joined.shp',
})
# Step 4: near
processing.run('whitebox_workflows:near', {
'input': '/data/parcels_joined.shp',
'near': '/data/roads.shp',
'max_dist': 5000.0,
'output': '/data/parcels_near.shp',
})
print("Parcel enrichment complete.")
Advanced: Simplify Features for Cartographic Output
Large polygon datasets with many vertices slow down rendering and tile export. Simplify geometries while preserving topology.
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Simplify Features
| Parameter | Recommended value |
|---|---|
| Input vector | priority_parcels_clipped.shp |
| Algorithm | Douglas-Peucker |
| Tolerance (m) | 5.0 (adjust to display scale) |
| Output | parcels_simplified.shp |
processing.run('whitebox_workflows:simplify_features', {
'input': '/data/priority_parcels_clipped.shp',
'algorithm': 'DouglasPeucker',
'tolerance': 5.0,
'output': '/data/parcels_simplified.shp',
})
Common Pitfalls
| Problem | Likely cause | Fix |
|---|---|---|
| Spatial join returns no matches | CRS mismatch between target and join layers | Reproject both to the same CRS before joining |
| Near returns –1 for all distances | Search distance too small for data extent | Increase max_dist or set to 0 for unlimited search |
| Add geometry attributes returns wrong area | Layer CRS is geographic (degrees) | Reproject to a projected CRS (metres) first |
| Simplify removes valid narrow features | Tolerance too large | Use a smaller tolerance (< 1 m for cadastral data) |
| Select by location selects too many features | Predicate too inclusive (intersects vs. within) | Switch to Within or Contains for strict containment |
Validation Checklist
- All input layers pass geometry validity check.
- All vector layers share the same projected CRS.
- Spatial join result preserves original feature count (check attribute table row count).
- NEAR_DIST values are plausible (inspect histogram).
- Simplified geometry does not self-intersect at the chosen tolerance.
- Attribute field names in output do not exceed shapefile 10-character limit.
Network Analysis
Network analysis in WbW-QGIS spans both transportation and hydrologic networks. This chapter is aligned with the Python and R manuals and now covers three common tracks:
- Transportation routing and service areas
- OD and nearest-facility analysis
- Stream-network hierarchy and connectivity
Core Concepts You Should Know First
- Network: A graph of edges (line segments) and nodes (junctions/endpoints).
- Cost or impedance: Value minimized by routing (distance, minutes, or other weighted friction).
- OD pair: Origin and destination used in path queries.
- Service area: All network locations reachable under a cost budget.
- Closest facility: Nearest destination by network cost, not straight-line distance.
- Connectivity: Whether all required features are in connected components.
- Directed network: Edge direction matters (one-way roads, downstream streams).
Typical Inputs
| Layer | Format | Notes |
|---|---|---|
| roads.shp | Polyline vector | Cleaned road centerlines |
| facilities.shp | Point vector | Hospitals, depots, schools, etc. |
| demand_points.shp | Point vector | Incidents, customers, or population centroids |
| streams.tif | Raster | Binary stream raster for hydrologic hierarchy |
| d8_pointer.tif | Raster | D8 flow-direction raster |
Workflow A: Transportation Network Preparation
Step 1 - Topology QA and Geometry Cleanup
Use standard QGIS cleanup first:
- Check validity
- Snap Geometries to Layer
- Fix Geometries
Then enrich network attributes with Whitebox tools:
Processing Toolbox -> Whitebox Workflows -> Vector Analysis -> Add Geometry Attributes
This provides segment length fields needed for distance-based routing.
If travel-time routing is required, compute a time field such as:
- TIME_MIN = LENGTH_M / SPEED_M_PER_MIN
using Field Calculator.
Step 2 - Build Cost-Aware Road Layer
Recommended fields:
- LENGTH_M (meters)
- SPEED_KMH (if available)
- TIME_MIN (derived)
- ONEWAY (optional directional control)
Use this prepared layer as the routing network for native QGIS algorithms.
Step 2.5 - Build Network Topology and Snap Points (Optional)
If your network lacks proper node structure or you need to snap facility/demand points to the network:
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Build Network Topology
| Parameter | Value |
|---|---|
| Input vector | roads_prepared.shp |
| Snap tolerance | 0.5 |
| Output | roads_noded.shp |
| Output nodes | network_nodes.shp |
Then snap your facilities and demand points:
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Snap Points to Network
| Parameter | Value |
|---|---|
| Network layer | roads_noded.shp |
| Points layer | fire_stations.shp |
| Snap distance | 50.0 (meters) |
| Output | fire_stations_snapped.shp |
Output includes SNAP_DIST (offset to network) for diagnostics.
Workflow B: Routing, Service Areas, and Closest Facility
Step 3 - Shortest Path
Processing Toolbox -> Network Analysis:
- Shortest Path (Point to Point)
- Shortest Path (Layer to Point)
- Shortest Path (Point to Layer)
Use the prepared cost field (distance or time) consistently.
Step 4 - Service Area (Isochrone)
Processing Toolbox -> Network Analysis -> Service Area (From Layer)
Recommended parameters:
| Parameter | Example |
|---|---|
| Network layer | roads_prepared.shp |
| Strategy | Shortest |
| Start points | facilities.shp |
| Travel cost | 5.0 (minutes) or 3000 (meters) |
Export output lines and optional polygons for reporting.
Step 5 - Closest Facility Pattern
Use Service Area and shortest-path tools together:
- Build candidate facilities
- Route demand points to nearest reachable facilities
- Summarize cost by facility catchment
For large batches, run model-builder or Python Console loops.
Workflow C: OD-Style Batch Analysis in QGIS
QGIS does not provide a single OD matrix tool equivalent to the Python/R chapters, so the standard QGIS pattern is:
- Iterate origins and destinations in batch
- Run shortest path for each pair
- Aggregate travel cost in an output table
Use this when you need accessibility summaries or assignment baselines directly inside QGIS projects.
Workflow D: Hydrologic Stream Networks
Hydrologic network tools remain an important part of network analysis and are included here as a dedicated sub-workflow rather than the entire chapter.
Step 6 - Stream Hierarchy
Processing Toolbox -> Whitebox Workflows -> Spatial Hydrology:
- Strahler Stream Order
- Shreve Stream Magnitude
- Hack Stream Order
These tools characterize stream position and downstream accumulation.
Step 7 - Stream Vectorization
Processing Toolbox -> Whitebox Workflows -> Spatial Hydrology -> Raster Streams to Vector
Convert ordered stream rasters to vector lines for cartography and further network operations.
QGIS Python Console Equivalent
import processing
# Add geometry attributes for road cost preparation
processing.run('whitebox_workflows:add_geometry_attributes', {
'input': '/data/roads.shp',
'output': '/data/roads_prepared.shp',
})
# Service area from facilities
processing.run('native:serviceareafromlayer', {
'INPUT': '/data/roads_prepared.shp',
'STRATEGY': 0,
'START_POINTS': '/data/facilities.shp',
'TRAVEL_COST': 5.0,
'OUTPUT_LINES': '/data/service_area_lines.shp',
'OUTPUT': 'TEMPORARY_OUTPUT',
})
# Stream order
processing.run('whitebox_workflows:strahler_stream_order', {
'd8_pntr': '/data/d8_pointer.tif',
'streams': '/data/streams.tif',
'output': '/data/strahler.tif',
})
Common Pitfalls
| Problem | Likely cause | Fix |
|---|---|---|
| No route found between known-connected points | Topology gaps or unsnapped endpoints | Run snapping and revalidate connectivity |
| Service area too small or too large | Cost units inconsistent | Keep all costs in either meters or minutes |
| One-way streets ignored | Direction field not configured | Verify direction settings in network algorithm |
| Batch routing is slow | Unnecessary repeated reprojection or heavy geometry | Preprocess to common CRS and simplify where appropriate |
| Stream order appears uniform | Bad stream threshold or mismatched d8/stream rasters | Rebuild streams and ensure matching extent/grid |
Validation Checklist
- Routing network passes geometry validity and snapping checks.
- Cost field units are consistent across all analyses.
- Directionality assumptions are documented (directed vs undirected).
- Service-area outputs were spot-checked against known travel behavior.
- Stream-order outputs were checked at confluences.
- Workflow parameters were saved in model or processing history.
Linear Referencing
Linear referencing (LRS) is a data model where features are located along routes by a measured distance from a known origin — rather than by absolute X/Y coordinates. It is the foundation of road/rail inventory, pipeline inspection data, accident records, and pavement condition databases.
WbW-QGIS provides tools for building measure fields on route networks, locating point and line events along routes, and exporting event geometries for spatial analysis.
Key Concepts
- Route: A polyline feature with a unique, stable route identifier
(
ROUTE_ID) and a monotonically increasing measure value (M-value) along its length. - M-value: The accumulated distance (or time, or post number) from the route origin to each vertex. Stored as the M coordinate in an MZ geometry.
- Event: A point or interval on a route located by one measure (point event) or two measures (line event: from-measure, to-measure).
- Event table: A tabular record set with
ROUTE_ID,MEASURE(point) orFROM_M/TO_M(line), and any associated attributes. - Dynamic segmentation: The process of converting event tables to geometry by interpolating measure positions along routes.
- Calibration: Adjusting M-values to match real-world control points — for example, aligning stationing to kilometre posts.
End-to-End Workflow: Locating Inspection Events Along a Road Network
This workflow builds measure fields on a road network, then locates a set of field inspection points as events on their respective routes.
Inputs
| Layer | Format | Notes |
|---|---|---|
roads.shp | Polyline vector | Road centrelines, unique ROUTE_ID field |
inspections.csv | CSV table | Columns: ROUTE_ID, CHAINAGE_M, CONDITION |
Step 1 — Add Cumulative Distance (Measure) Field
WbW computes the cumulative distance from each route's start vertex to every subsequent vertex and writes it as the M coordinate.
Processing Toolbox → Whitebox Workflows → Vector Analysis →
Add Geometry Attributes
| Parameter | Recommended value |
|---|---|
| Input vector | roads.shp |
| Units | Metres |
| Output | roads_geom.shp |
This step appends LENGTH to each segment. For building full route M-values
use the QGIS Set M Value tool (from Geometry group) after merging
segments by ROUTE_ID.
Alternative — set M values from a field:
Processing Toolbox → Vector Geometry → Set M Value (QGIS native)
| Parameter | Recommended value |
|---|---|
| Input layer | roads.shp (merged per route) |
| M value | Expression: $length (QGIS expression — cumulative along merged route) |
| Output | routes_m.shp |
Route Calibration and Recalibration
Measures are only useful when anchored to real-world control points such as kilometre posts or survey stations. If your routes lack calibration or have been edited, use these tools to establish stable, field-verified measures.
Calibrate Routes from Control Points
Processing Toolbox → Whitebox Workflows → Linear Referencing →
Route Calibrate
| Parameter | Value |
|---|---|
| Input routes | roads.shp (with ROUTE_ID field) |
| Control points | km_posts.shp (with ROUTE_ID and KNOWN_MEASURE fields) |
| Control measure field | KNOWN_MEASURE |
| Snap tolerance | 10.0 (meters) |
| Output | routes_calibrated.shp |
Output adds FROM_MEASURE and TO_MEASURE fields containing the calibrated values.
Recalibrate After Route Edits
If you split, merge, or redraw routes, use recalibration to scale measures proportionally:
Processing Toolbox → Whitebox Workflows → Linear Referencing →
Route Recalibrate
| Parameter | Value |
|---|---|
| Original routes | routes_calibrated.shp (reference with valid measures) |
| Edited routes | routes_edited.shp (after geometric changes) |
| Output | routes_recalibrated.shp |
Step 2 — Validate Route Identifiers
Route IDs must be unique per route and stable across updates. Check for duplicates.
QGIS → Open Attribute Table → Field Calculator or via the Python Console:
from qgis.core import QgsVectorLayer
layer = QgsVectorLayer('/data/routes_m.shp', 'routes', 'ogr')
ids = [f['ROUTE_ID'] for f in layer.getFeatures()]
duplicates = [x for x in ids if ids.count(x) > 1]
if duplicates:
print(f"Duplicate route IDs found: {set(duplicates)}")
else:
print("All route IDs are unique.")
Step 3 — Locate Point Events (Dynamic Segmentation)
Processing Toolbox → Whitebox Workflows → Linear Referencing →
Locate Point Events
| Parameter | Recommended value |
|---|---|
| Input routes | routes_m.shp |
| Event table | inspections.csv |
| Route ID field (routes) | ROUTE_ID |
| Route ID field (events) | ROUTE_ID |
| Measure field | CHAINAGE_M |
| Output | inspection_points.shp |
Each CSV row becomes a point geometry placed at the corresponding measure position on its route. Rows with unmatched route IDs or out-of-range measures are written to an error table.
Step 4 — Locate Line Events (Optional)
If the inspection table records intervals (e.g. pavement condition rated over 100 m segments):
Processing Toolbox → Whitebox Workflows → Linear Referencing →
Locate Line Events
| Parameter | Recommended value |
|---|---|
| Input routes | routes_m.shp |
| Event table | pavement.csv |
| Route ID field (routes) | ROUTE_ID |
| Route ID field (events) | ROUTE_ID |
| From-measure field | FROM_M |
| To-measure field | TO_M |
| Output | pavement_segments.shp |
Step 5 — Inspect and Validate Event Geometry
Load inspection_points.shp in QGIS. Pan to several known inspection records
and confirm point positions against the road centreline.
Use QGIS Identify tool to click a point and verify that CHAINAGE_M
matches the M-value of the nearest route vertex within acceptable tolerance
(typically ± half the route vertex spacing).
Python Console Equivalent
import processing
# Step 3: locate point events
processing.run('whitebox_workflows:locate_point_events', {
'routes': '/data/routes_m.shp',
'events': '/data/inspections.csv',
'route_id_field': 'ROUTE_ID',
'event_route_id_field': 'ROUTE_ID',
'measure_field': 'CHAINAGE_M',
'output': '/data/inspection_points.shp',
})
# Step 4: locate line events
processing.run('whitebox_workflows:locate_line_events', {
'routes': '/data/routes_m.shp',
'events': '/data/pavement.csv',
'route_id_field': 'ROUTE_ID',
'event_route_id_field': 'ROUTE_ID',
'from_measure_field': 'FROM_M',
'to_measure_field': 'TO_M',
'output': '/data/pavement_segments.shp',
})
print("Linear referencing complete.")
Advanced: Calibrate Routes Against Control Points
If field-collected kilometre posts differ from computed cumulative distance, calibrate M-values by interpolating between control points.
Processing Toolbox → Whitebox Workflows → Linear Referencing →
Calibrate Route
| Parameter | Recommended value |
|---|---|
| Input routes | routes_m.shp |
| Calibration points | km_posts.shp (with ROUTE_ID and KNOWN_M fields) |
| Route ID field | ROUTE_ID |
| Measure field | KNOWN_M |
| Search tolerance (m) | 50 |
| Output | routes_calibrated.shp |
After calibration, re-run Locate Point Events on routes_calibrated.shp to
position events against field-verified measures.
Common Pitfalls
| Problem | Likely cause | Fix |
|---|---|---|
| Events do not locate (0 features output) | Route ID field names do not match | Check both ROUTE_ID parameter values match the actual field names |
| Events placed far from expected position | M-values in event table use different units | Confirm both routes and events use the same unit (metres vs km) |
| Out-of-range events produce no error output | Measure > route end measure | Check that FROM_M/TO_M do not exceed the route total length |
| Calibration shifts all events uniformly | Only one control point per route | Add at least two control points per route for interpolation |
| Duplicate route IDs cause incorrect event assignment | Merged route has repeated IDs | Dissolve route features by ROUTE_ID before building M-values |
Validation Checklist
- Route IDs are unique per route with no duplicates.
- M-values are monotonically increasing along each route (no reversals).
- Event table measures fall within the range [0, route total length].
- Located point events visually snap to the correct road centreline.
-
Line event segment lengths match
TO_M - FROM_Mwithin 0.1 m. -
Error table from
Locate Eventscontains zero unmatched records.
Workflow Index
This index provides task-first entry points for WbW-QGIS workflows. Each entry links to the chapter section where the complete step-by-step example can be found, and lists the key Processing Toolbox tool IDs needed for the task.
Terrain Analysis
| Task | Chapter section | Key tools |
|---|---|---|
| Compute slope from DEM | Terrain Analysis — Step 2 | whitebox_workflows:slope |
| Compute aspect from DEM | Terrain Analysis — Step 3 | whitebox_workflows:aspect |
| Generate hillshade for visualisation | Terrain Analysis — Step 4 | whitebox_workflows:hillshade |
| Compute profile and plan curvature | Terrain Analysis — Step 5 | whitebox_workflows:profile_curvature, whitebox_workflows:plan_curvature |
| Classify terrain into landform elements | Terrain Analysis — Geomorphons | whitebox_workflows:geomorphons |
| Compute topographic wetness index | Terrain Analysis — Step 6 | whitebox_workflows:wetness_index |
| Fill depressions before terrain derivatives | Terrain Analysis — Step 1 | whitebox_workflows:fill_depressions |
Spatial Hydrology
| Task | Chapter section | Key tools |
|---|---|---|
| Condition DEM for hydrologic routing | Spatial Hydrology — Step 1 | whitebox_workflows:breach_depressions_least_cost |
| Derive D8 flow direction | Spatial Hydrology — Step 2 | whitebox_workflows:d8_pointer |
| Compute flow accumulation | Spatial Hydrology — Step 3 | whitebox_workflows:d8_flow_accumulation |
| Extract stream network from accumulation | Spatial Hydrology — Step 4 | whitebox_workflows:extract_streams |
| Snap pour points to channel raster | Spatial Hydrology — Step 5 | whitebox_workflows:snap_pour_points |
| Delineate watershed / catchment | Spatial Hydrology — Step 6 | whitebox_workflows:watershed |
| Compute Topographic Wetness Index | Spatial Hydrology — TWI | whitebox_workflows:wetness_index |
LiDAR Processing
| Task | Chapter section | Key tools |
|---|---|---|
| QA — inspect point cloud statistics | LiDAR Processing — Step 1 | whitebox_workflows:lidar_point_stats |
| Thin high-density point cloud | LiDAR Processing — Step 2 | whitebox_workflows:lidar_thin |
| Classify ground returns | LiDAR Processing — Step 3 | whitebox_workflows:lidar_ground_point_filter |
| Build DTM from ground-classified cloud | LiDAR Processing — Step 4 | whitebox_workflows:lidar_idw_interpolation |
| Build DSM from first returns | LiDAR Processing — Step 5 | whitebox_workflows:lidar_idw_interpolation |
| Derive canopy height model (CHM) | LiDAR Processing — Step 6 | whitebox_workflows:canopy_height_model |
| Normalise heights above ground | LiDAR Processing — Step 7 | whitebox_workflows:height_above_ground |
Remote Sensing
| Task | Chapter section | Key tools |
|---|---|---|
| Compute NDVI from multispectral image | Remote Sensing — Step 2 | whitebox_workflows:ndvi |
| Threshold vegetation and classify change bins | Remote Sensing — Step 3 | QGIS Raster Calculator, whitebox_workflows:reclass |
| NDVI/NBR-based change detection | Remote Sensing — Step 3 | QGIS Raster Calculator |
| Reduce bands with PCA | Remote Sensing — Step 4 | whitebox_workflows:principal_component_analysis |
| Segment image into homogeneous objects | Remote Sensing — Step 5 | whitebox_workflows:image_segmentation |
Raster Analysis
| Task | Chapter section | Key tools |
|---|---|---|
| Compute distance from a binary feature raster | Raster Analysis — Step 1 | whitebox_workflows:euclidean_distance |
| Reclassify raster into suitability scores | Raster Analysis — Steps 2–4 | whitebox_workflows:reclass_from_file |
| Combine reclassified factors by weight | Raster Analysis — Step 5 | QGIS Raster Calculator |
| Summarise raster values within polygons | Raster Analysis — Step 6 | QGIS Zonal Statistics |
| Smooth raster with focal mean | Raster Analysis — Focal Statistics | whitebox_workflows:mean_filter |
Vector Analysis
| Task | Chapter section | Key tools |
|---|---|---|
| Validate and repair polygon geometry | Vector Analysis — Step 1 | QGIS native:fixgeometries |
| Add area, perimeter, centroid attributes | Vector Analysis — Step 2 | whitebox_workflows:add_geometry_attributes |
| Join attributes from overlapping polygons | Vector Analysis — Step 3 | whitebox_workflows:spatial_join |
| Compute distance to nearest feature | Vector Analysis — Step 4 | whitebox_workflows:near |
| Select features by spatial predicate | Vector Analysis — Step 5 | QGIS Select by Expression |
| Simplify polygon boundaries | Vector Analysis — Simplify | whitebox_workflows:simplify_features |
| Convert GeoPackage to TopoJSON and back | Vector Analysis — TopoJSON Conversion Chain | whitebox_workflows:add_geometry_attributes, QGIS Export |
| Simplify shared boundaries and emit TopoJSON | Vector Analysis — TopoJSON Boundary-Preserving Generalization Chain | whitebox_workflows:simplify_features, QGIS Export |
| Convert TopoJSON transport input, enrich, and re-emit | Vector Analysis — TopoJSON Transport + Enrichment Return Chain | QGIS Export, whitebox_workflows:add_geometry_attributes, whitebox_workflows:spatial_join, whitebox_workflows:near |
Network Analysis
| Task | Chapter section | Key tools |
|---|---|---|
| Prepare road network geometry and costs | Network Analysis — Workflow A | whitebox_workflows:add_geometry_attributes, QGIS geometry tools |
| Compute shortest path routes | Network Analysis — Workflow B | QGIS Network Analysis shortest path tools |
| Delineate road service areas | Network Analysis — Workflow B | QGIS native:serviceareafromlayer |
| Build OD-style batch travel-cost summaries | Network Analysis — Workflow C | QGIS shortest path batch/model workflows |
| Compute Strahler and Shreve stream hierarchy | Network Analysis — Workflow D | whitebox_workflows:strahler_stream_order, whitebox_workflows:shreve_stream_magnitude |
| Convert raster stream network to vector | Network Analysis — Workflow D | whitebox_workflows:raster_streams_to_vector |
Linear Referencing
| Task | Chapter section | Key tools |
|---|---|---|
| Add measure (M) values to route network | Linear Referencing — Step 1 | QGIS native:setmvalue |
| Validate unique route IDs | Linear Referencing — Step 2 | QGIS Python Console check |
| Locate point events along routes | Linear Referencing — Step 3 | whitebox_workflows:locate_point_events |
| Locate line events along routes | Linear Referencing — Step 4 | whitebox_workflows:locate_line_events |
| Calibrate M-values against control points | Linear Referencing — Calibrate | whitebox_workflows:calibrate_route |
By Data Type
Raster input tasks
- Fill depressions → see Terrain Analysis / Spatial Hydrology
- Slope, aspect, curvature → see Terrain Analysis
- Flow direction, accumulation → see Spatial Hydrology
- Reclassification, suitability → see Raster Analysis
- Spectral indices, PCA, change → see Remote Sensing
Point cloud input tasks
- Ground classification, DTM/DSM/CHM → see LiDAR Processing
- Height normalisation → see LiDAR Processing
Vector input tasks
- Geometry validation, overlay, joins → see Vector Analysis
- Routing, service areas, and stream hierarchy → see Network Analysis
- Route events, calibration → see Linear Referencing
Troubleshooting
Plugin Does Not Appear in QGIS
Checks:
- Confirm plugin directory path is correct for active QGIS profile.
- Confirm plugin folder name is whitebox_workflows_qgis.
- Restart QGIS after install/symlink changes.
Whitebox Provider Missing from Processing
Checks:
- Confirm plugin is enabled.
- Trigger discovery refresh.
- Confirm whitebox_workflows imports in QGIS Python environment.
Tools Are Missing or Unexpectedly Locked
Checks:
- Rebuild/reinstall whitebox_workflows.
- Refresh discovery.
- Confirm runtime capability metadata matches expected tier.
- Confirm tool taxonomy and generated provider state are synchronized.
Tool Runs but Output Is Missing
Checks:
- Verify output path exists and is writable.
- Verify input paths and formats are valid.
- Re-run on a small dataset to isolate data-specific failures.
- Check Processing logs for warnings/errors.
Environment Mismatch Problems
Symptoms:
- import failures in plugin startup,
- inconsistent behavior between terminal and QGIS,
- discovery succeeds in one environment but not another.
Resolution:
- Ensure QGIS and your install command use the same Python environment.
- Re-run runtime install in that exact environment.