Filters#
Raw DEM elevation profiles can have outliers and incorrect terrain. Causes include GPS position
noise, DEM resolution, and bare-earth artifacts below road structures. GradeIT uses one or more
ElevationFilter objects before it calculates grade.
from gradeit import BridgeFilter, Wood2014Filter, gradeit
gradeit(trace, elevation_model=model) # Wood2014Filter, the default
gradeit(trace, elevation_model=model, elevation_filter=None) # no filtering
gradeit(trace, elevation_model=model, elevation_filter=Wood2014Filter(savgol_window_ft=1200))
gradeit(
trace,
elevation_model=model,
elevation_filter=[BridgeFilter(baseline_radius_ft=6000), Wood2014Filter()],
)
GradeIT applies filter sequences in order. Each filter uses the output from the last filter. GradeIT calculates grade from final elevation. All parameters use feet.
Wood2014Filter — the default#
This filter does steps B–E of the Wood et al. method. It resamples to a uniform distance grid, smooths, removes and fills unusual nodes, smooths again, and interpolates.
This is the default filter. It is suitable for most traces. It also removes ordinary bridge and overpass artifacts without bridge-specific logic.
Parameter |
Default |
What it controls |
|---|---|---|
|
|
Uniform distance-grid spacing (step B). ~3× the DEM's 33 ft post spacing; below one post, adjacent nodes read the same cell. |
|
|
Savitzky-Golay width. Wider means a smoother grade signal and more attenuation of short real features. |
|
|
Polynomial order within the window. |
|
|
Binomial stage width, as a Gaussian-equivalent sigma. |
|
|
Step D discard threshold on |pre − post|. Equals the DEM's 2.44 m vertical RMSE. |
|
|
Hysteresis: a run above |
|
|
Runs longer than this are treated as real topography, not artifacts. |
|
|
Safety valve so unusually noisy input cannot silently erase a quarter of the trace. |
|
|
Unobserved stretches longer than this split the trace into independently filtered segments; points inside such a gap return |
|
|
Guard on |
Tuning notes#
savgol_window_ft controls smoothness. A wider value reduces grade noise. It also reduces real
short features.
residual_threshold_ft captures the size of a typical artifact. Raising or lowering this values balances between removing real artifacts and preserving genuine road features.
BridgeFilter — targeted bare-earth correction#
This filter is useful for large bridges that can't be detected by the residual method alone.
To do this the BridgeFilter looks for a dip that is lower than the nearby road on both sides.
It interpolates road elevation across each dip.
Parameter |
Default |
What it controls |
|---|---|---|
|
|
Half-width of the rolling-max window on each side. This is the parameter to tune. |
|
|
Per-point threshold for inclusion in a candidate dip. |
|
|
At least one point in a run must reach this depth. Filters out wide, shallow noise. |
|
|
Minimum accepted span. Shorter runs are usually noise. |
|
|
Maximum accepted span. Longer runs are usually real terrain. |
|
|
Reject runs whose span ÷ peak depth exceeds this. Bridges are short relative to their depth; valleys are long relative to theirs. |
|
|
Reject a correction whose recovered grade differs from the surrounding median segment grade by more than this. |
Warning
Elevation does not distinguish a valley without a bridge from a valley with a bridge. The checks above cannot always separate them.
baseline_radius_ft defines the difference. Use hundreds of feet for typical overpasses and creek crossings. Use thousands of feet
for a major water crossing. But, be aware that a value that is too wide can interpolate a straight line across a real valley.
These examples show both errors:
How Filtration Works — the one-mile default flattening 1.5 miles of real Colorado canyon by up to 130 ft, on a trace the default
Wood2014Filterhandles perfectly.Bare-Earth Bridges — a 5,332 ft crossing of the Carquinez Strait that
Wood2014Filtercannot touch at any setting, cleared oncebaseline_radius_ftcovers the span.
Order matters. Put BridgeFilter first. A smoother reduces raw dip magnitude.
Writing your own#
ElevationFilter requires one method:
from typing import List
from gradeit import Coordinate, ElevationFilter
class ClampFilter(ElevationFilter):
"""Clip elevation to a plausible range."""
def __init__(self, min_ft: float, max_ft: float):
self.min_ft = min_ft
self.max_ft = max_ft
def filter(
self,
elevation_profile: List[float],
coordinates: List[Coordinate],
) -> List[float]:
return [min(max(e, self.min_ft), self.max_ft) for e in elevation_profile]
Take an elevation profile. Return an elevation profile of the same length in feet. Do not return grade. GradeIT calculates grade from final filtered elevation.