Bringing Your Own Elevation Source#
Use this interface when your elevation data comes from a source gradeit does not know about: a different national DEM, a lidar survey, a database, a vendor API, or synthetic terrain in a test.
import math
from gradeit import Coordinate, ElevationModel
The interface#
ElevationModel is an abstract base class with one abstract method:
class ElevationModel(metaclass=ABCMeta):
@abstractmethod
def get_elevation(self, trace: List[Coordinate]) -> List[float]:
...
The contract is small but strict:
Input is a list of
Coordinateobjects. These are frozen dataclasses with.latitudeand.longitudeattributes, in decimal degrees.Output is elevation in feet, one value per input coordinate, in the same order. gradeit does not convert units for you. If your source reports meters, convert the values before you return them.
Unknown elevation is
float("nan"). It is notNone, and it is not a sentinel value like-9999. Both built-in models returnNaNfor points outside their coverage, and the filters expect this value.
A synthetic model#
Here is a complete implementation. It places a hill on the landscape as a Gaussian bump. This gives a smooth profile with a known shape, which is useful for testing filters against a signal whose true shape you already know.
class SyntheticTerrain(ElevationModel):
"""Analytic terrain: a Gaussian hill on a tilted plane."""
def __init__(self, center: Coordinate, peak_ft: float = 350.0, width_deg: float = 0.02):
self.center = center
self.peak_ft = peak_ft
self.width_deg = width_deg
def get_elevation(self, trace: list[Coordinate]) -> list[float]:
out = []
for coord in trace:
d_lat = coord.latitude - self.center.latitude
d_lon = coord.longitude - self.center.longitude
r2 = (d_lat**2 + d_lon**2) / self.width_deg**2
base = 5000.0 + 1000.0 * d_lon # a gentle eastward tilt
out.append(base + self.peak_ft * math.exp(-r2))
return out