Terrain

Digital elevation models and terrain analysis.

Terrain Models and Visibility Analysis.

This module provides tools for working with digital elevation models (DEMs) and computing terrain-related effects:

  • Digital Elevation Model interface for elevation queries

  • GEBCO bathymetry/topography data loading

  • Earth2014 global terrain model loading

  • Terrain gradient and slope calculations

  • Line-of-sight and viewshed analysis

  • Terrain masking for radar/sensor coverage

Submodules

dem

DEM data structures and elevation query interface.

loaders

GEBCO and Earth2014 data loaders.

visibility

Line-of-sight, viewshed, and terrain masking functions.

Examples

>>> from pytcl.terrain import DEMGrid, create_synthetic_terrain
>>> import numpy as np
>>> # Create synthetic terrain for testing
>>> dem = create_synthetic_terrain(
...     lat_min=np.radians(35.0),
...     lat_max=np.radians(36.0),
...     lon_min=np.radians(-120.0),
...     lon_max=np.radians(-119.0),
...     amplitude=500.0
... )
>>> # Query elevation
>>> point = dem.get_elevation(np.radians(35.5), np.radians(-119.5))
>>> print(f"Elevation: {point.elevation:.1f} m")
>>> # Check line of sight
>>> from pytcl.terrain import line_of_sight
>>> los = line_of_sight(
...     dem,
...     obs_lat=np.radians(35.2), obs_lon=np.radians(-119.8), obs_height=10.0,
...     tgt_lat=np.radians(35.8), tgt_lon=np.radians(-119.2), tgt_height=10.0
... )
>>> print(f"Visible: {los.visible}, Clearance: {los.clearance:.1f} m")
>>> # Load GEBCO/Earth2014 data (requires external files)
>>> from pytcl.terrain import load_gebco, load_earth2014, create_test_gebco_dem
>>> # Use test data for demonstration
>>> dem = create_test_gebco_dem()
class pytcl.terrain.DEMPoint(latitude, longitude, elevation, valid)[source]

Bases: NamedTuple

Single point elevation query result.

Parameters:
  • latitude (float) – Latitude in radians.

  • longitude (float) – Longitude in radians.

  • elevation (float) – Elevation in meters above reference (MSL or ellipsoid).

  • valid (bool) – Whether the elevation value is valid (not a fill/no-data value).

latitude: float

Alias for field number 0

longitude: float

Alias for field number 1

elevation: float

Alias for field number 2

valid: bool

Alias for field number 3

class pytcl.terrain.TerrainGradient(slope, aspect, dz_dx, dz_dy)[source]

Bases: NamedTuple

Terrain gradient at a point.

Parameters:
  • slope (float) – Terrain slope in radians (angle from horizontal).

  • aspect (float) – Aspect (downslope direction) in radians, measured clockwise from north.

  • dz_dx (float) – Elevation gradient in east direction (m/m).

  • dz_dy (float) – Elevation gradient in north direction (m/m).

slope: float

Alias for field number 0

aspect: float

Alias for field number 1

dz_dx: float

Alias for field number 2

dz_dy: float

Alias for field number 3

class pytcl.terrain.DEMMetadata(name, resolution, lat_min, lat_max, lon_min, lon_max, vertical_datum, horizontal_datum)[source]

Bases: NamedTuple

Metadata for a DEM dataset.

Parameters:
  • name (str) – Name of the DEM dataset.

  • resolution (float) – Grid resolution in arc-seconds.

  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • vertical_datum (str) – Vertical reference (e.g., ‘MSL’, ‘WGS84’).

  • horizontal_datum (str) – Horizontal reference (e.g., ‘WGS84’).

name: str

Alias for field number 0

resolution: float

Alias for field number 1

lat_min: float

Alias for field number 2

lat_max: float

Alias for field number 3

lon_min: float

Alias for field number 4

lon_max: float

Alias for field number 5

vertical_datum: str

Alias for field number 6

horizontal_datum: str

Alias for field number 7

class pytcl.terrain.DEMGrid(data, lat_min, lat_max, lon_min, lon_max, nodata_value=-9999.0, name='Custom DEM')[source]

Bases: object

In-memory DEM grid for elevation queries.

This class provides a simple in-memory representation of a DEM grid for efficient elevation queries. Data can be loaded from arrays or from external files.

Parameters:
  • data (ndarray) – 2D array of elevation values (rows=latitude, cols=longitude). Latitude increases with row index (south to north).

  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • nodata_value (float, optional) – Value representing no data. Default is -9999.

  • name (str, optional) – Name identifier for the DEM. Default is “Custom DEM”.

Examples

>>> import numpy as np
>>> # Create a simple 10x10 DEM grid
>>> data = np.random.rand(10, 10) * 1000  # 0-1000m elevation
>>> dem = DEMGrid(
...     data,
...     lat_min=np.radians(35.0),
...     lat_max=np.radians(36.0),
...     lon_min=np.radians(-120.0),
...     lon_max=np.radians(-119.0)
... )
>>> elev = dem.get_elevation(np.radians(35.5), np.radians(-119.5))
__init__(data, lat_min, lat_max, lon_min, lon_max, nodata_value=-9999.0, name='Custom DEM')[source]
get_metadata()[source]

Get DEM metadata.

Returns:

Metadata about this DEM.

Return type:

DEMMetadata

get_elevation(lat, lon, interpolation='bilinear')[source]

Get elevation at a geographic coordinate.

Parameters:
  • lat (float) – Latitude in radians.

  • lon (float) – Longitude in radians.

  • interpolation (str, optional) – Interpolation method: ‘nearest’ or ‘bilinear’. Default is ‘bilinear’.

Returns:

Elevation query result with validity flag.

Return type:

DEMPoint

get_elevations(lats, lons, interpolation='bilinear')[source]

Get elevations at multiple geographic coordinates.

Parameters:
  • lats (ndarray) – Latitudes in radians.

  • lons (ndarray) – Longitudes in radians.

  • interpolation (str, optional) – Interpolation method. Default is ‘bilinear’.

Returns:

Array of elevation values. Invalid points contain nodata_value.

Return type:

ndarray

get_gradient(lat, lon, earth_radius=6371000.0)[source]

Compute terrain gradient at a point.

Uses central differences to estimate the gradient.

Parameters:
  • lat (float) – Latitude in radians.

  • lon (float) – Longitude in radians.

  • earth_radius (float, optional) – Earth radius in meters for converting angular to linear distances. Default is 6371000.0.

Returns:

Terrain gradient including slope and aspect.

Return type:

TerrainGradient

pytcl.terrain.get_elevation_profile(dem, lat_start, lon_start, lat_end, lon_end, n_points=100)[source]

Extract elevation profile along a path.

Parameters:
  • dem (DEMGrid) – DEM grid to query.

  • lat_start (float) – Starting latitude in radians.

  • lon_start (float) – Starting longitude in radians.

  • lat_end (float) – Ending latitude in radians.

  • lon_end (float) – Ending longitude in radians.

  • n_points (int, optional) – Number of sample points along the path. Default is 100.

Returns:

  • distances (ndarray) – Array of distances from start in meters.

  • elevations (ndarray) – Array of elevation values in meters.

Return type:

Tuple[ndarray[tuple[Any, …], dtype[floating]], ndarray[tuple[Any, …], dtype[floating]]]

Examples

>>> import numpy as np
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119),
...     elevation=500)
>>> dists, elevs = get_elevation_profile(
...     dem, np.radians(35.2), np.radians(-119.8),
...     np.radians(35.8), np.radians(-119.2), n_points=10)
>>> len(dists) == 10
True
pytcl.terrain.interpolate_dem(dem, new_lat_min, new_lat_max, new_lon_min, new_lon_max, new_n_lat, new_n_lon, method='bilinear')[source]

Interpolate DEM to a new grid.

Parameters:
  • dem (DEMGrid) – Source DEM grid.

  • new_lat_min (float) – New minimum latitude in radians.

  • new_lat_max (float) – New maximum latitude in radians.

  • new_lon_min (float) – New minimum longitude in radians.

  • new_lon_max (float) – New maximum longitude in radians.

  • new_n_lat (int) – Number of latitude points in new grid.

  • new_n_lon (int) – Number of longitude points in new grid.

  • method (str, optional) – Interpolation method. Default is ‘bilinear’.

Returns:

New interpolated DEM grid.

Return type:

DEMGrid

Examples

>>> import numpy as np
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119),
...     elevation=100)
>>> new_dem = interpolate_dem(
...     dem,
...     np.radians(35.2), np.radians(35.8),
...     np.radians(-119.8), np.radians(-119.2),
...     new_n_lat=5, new_n_lon=5)
>>> new_dem.data.shape
(5, 5)
pytcl.terrain.merge_dems(dems, lat_min, lat_max, lon_min, lon_max, resolution_arcsec=30.0)[source]

Merge multiple DEMs into a single grid.

Parameters:
  • dems (list of DEMGrid) – List of DEM grids to merge.

  • lat_min (float) – Output minimum latitude in radians.

  • lat_max (float) – Output maximum latitude in radians.

  • lon_min (float) – Output minimum longitude in radians.

  • lon_max (float) – Output maximum longitude in radians.

  • resolution_arcsec (float, optional) – Output resolution in arc-seconds. Default is 30.0.

Returns:

Merged DEM grid.

Return type:

DEMGrid

Examples

>>> import numpy as np
>>> dem1 = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> dem2 = create_flat_dem(
...     np.radians(36), np.radians(37),
...     np.radians(-120), np.radians(-119), elevation=200)
>>> merged = merge_dems(
...     [dem1, dem2],
...     np.radians(35), np.radians(37),
...     np.radians(-120), np.radians(-119))
>>> merged.name
'Merged DEM'
pytcl.terrain.create_flat_dem(lat_min, lat_max, lon_min, lon_max, elevation=0.0, resolution_arcsec=30.0)[source]

Create a flat DEM with constant elevation.

Useful for testing or as a placeholder when terrain data is unavailable.

Parameters:
  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • elevation (float, optional) – Constant elevation in meters. Default is 0.0.

  • resolution_arcsec (float, optional) – Grid resolution in arc-seconds. Default is 30.0.

Returns:

Flat DEM grid.

Return type:

DEMGrid

Examples

>>> import numpy as np
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119),
...     elevation=500)
>>> dem.name
'Flat DEM'
>>> result = dem.get_elevation(np.radians(35.5), np.radians(-119.5))
>>> abs(result.elevation - 500) < 1
True
pytcl.terrain.create_synthetic_terrain(lat_min, lat_max, lon_min, lon_max, base_elevation=0.0, amplitude=1000.0, wavelength_km=50.0, resolution_arcsec=30.0, seed=None)[source]

Create synthetic terrain for testing.

Generates terrain using a combination of sinusoidal functions and random noise to simulate realistic topography.

Parameters:
  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • base_elevation (float, optional) – Base elevation in meters. Default is 0.0.

  • amplitude (float, optional) – Amplitude of elevation variations in meters. Default is 1000.0.

  • wavelength_km (float, optional) – Characteristic wavelength of terrain features in km. Default is 50.0.

  • resolution_arcsec (float, optional) – Grid resolution in arc-seconds. Default is 30.0.

  • seed (int, optional) – Random seed for reproducibility.

Returns:

Synthetic terrain DEM.

Return type:

DEMGrid

Examples

>>> import numpy as np
>>> dem = create_synthetic_terrain(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119),
...     base_elevation=500, amplitude=200, seed=42)
>>> dem.name
'Synthetic Terrain'
>>> dem.data.min() < dem.data.max()  # Has elevation variation
True
class pytcl.terrain.LOSResult(visible, grazing_angle, obstacle_distance, obstacle_elevation, clearance)[source]

Bases: NamedTuple

Line-of-sight analysis result.

Parameters:
  • visible (bool) – Whether there is unobstructed line of sight between observer and target.

  • grazing_angle (float) – Grazing angle to terrain in radians (angle above/below obstacle). Positive means clearing terrain, negative means blocked.

  • obstacle_distance (float) – Distance to the blocking obstacle in meters (if blocked). 0 if line of sight is clear.

  • obstacle_elevation (float) – Elevation of blocking obstacle in meters (if blocked).

  • clearance (float) – Minimum clearance above terrain along path in meters. Negative if blocked.

visible: bool

Alias for field number 0

grazing_angle: float

Alias for field number 1

obstacle_distance: float

Alias for field number 2

obstacle_elevation: float

Alias for field number 3

clearance: float

Alias for field number 4

class pytcl.terrain.ViewshedResult(visible, observer_lat, observer_lon, observer_height, lat_min, lat_max, lon_min, lon_max)[source]

Bases: NamedTuple

Viewshed computation result.

Parameters:
  • visible (ndarray) – 2D boolean array indicating visibility from observer.

  • observer_lat (float) – Observer latitude in radians.

  • observer_lon (float) – Observer longitude in radians.

  • observer_height (float) – Observer height above terrain in meters.

  • lat_min (float) – Minimum latitude of viewshed grid in radians.

  • lat_max (float) – Maximum latitude of viewshed grid in radians.

  • lon_min (float) – Minimum longitude of viewshed grid in radians.

  • lon_max (float) – Maximum longitude of viewshed grid in radians.

visible: ndarray[tuple[Any, ...], dtype[bool]]

Alias for field number 0

observer_lat: float

Alias for field number 1

observer_lon: float

Alias for field number 2

observer_height: float

Alias for field number 3

lat_min: float

Alias for field number 4

lat_max: float

Alias for field number 5

lon_min: float

Alias for field number 6

lon_max: float

Alias for field number 7

class pytcl.terrain.HorizonPoint(azimuth, elevation_angle, distance, terrain_elevation)[source]

Bases: NamedTuple

Point on the terrain horizon.

Parameters:
  • azimuth (float) – Azimuth from observer in radians (clockwise from north).

  • elevation_angle (float) – Elevation angle to horizon in radians (above horizontal).

  • distance (float) – Distance to horizon point in meters.

  • terrain_elevation (float) – Terrain elevation at horizon point in meters.

azimuth: float

Alias for field number 0

elevation_angle: float

Alias for field number 1

distance: float

Alias for field number 2

terrain_elevation: float

Alias for field number 3

pytcl.terrain.line_of_sight(dem, obs_lat, obs_lon, obs_height, tgt_lat, tgt_lon, tgt_height, n_samples=100, earth_radius=6371000.0, refraction_coeff=0.0)[source]

Compute line of sight between observer and target.

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • obs_lat (float) – Observer latitude in radians.

  • obs_lon (float) – Observer longitude in radians.

  • obs_height (float) – Observer height above terrain in meters.

  • tgt_lat (float) – Target latitude in radians.

  • tgt_lon (float) – Target longitude in radians.

  • tgt_height (float) – Target height above terrain in meters.

  • n_samples (int, optional) – Number of sample points along path. Default is 100.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

  • refraction_coeff (float, optional) – Atmospheric refraction coefficient (typically 0.13 for radio). Set to 0 for optical line of sight. Default is 0.0.

Returns:

Line-of-sight analysis result.

Return type:

LOSResult

Notes

The refraction coefficient models atmospheric bending of radio waves. A typical value for radio frequencies is 0.13 (4/3 Earth model). For optical line of sight, use 0.

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> result = line_of_sight(
...     dem,
...     np.radians(35.3), np.radians(-119.7), 10,
...     np.radians(35.7), np.radians(-119.3), 10)
>>> result.visible  # Clear LOS over flat terrain
True
pytcl.terrain.viewshed(dem, obs_lat, obs_lon, obs_height, max_range=50000.0, target_height=0.0, n_radials=360, samples_per_radial=100, earth_radius=6371000.0, refraction_coeff=0.0)[source]

Compute viewshed (visible area) from an observer location.

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • obs_lat (float) – Observer latitude in radians.

  • obs_lon (float) – Observer longitude in radians.

  • obs_height (float) – Observer height above terrain in meters.

  • max_range (float, optional) – Maximum analysis range in meters. Default is 50000.0.

  • target_height (float, optional) – Target height above terrain in meters. Default is 0.0.

  • n_radials (int, optional) – Number of radial directions to analyze. Default is 360.

  • samples_per_radial (int, optional) – Number of samples per radial. Default is 100.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

  • refraction_coeff (float, optional) – Atmospheric refraction coefficient. Default is 0.0.

Returns:

Viewshed computation result with visibility grid.

Return type:

ViewshedResult

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> result = viewshed(
...     dem, np.radians(35.5), np.radians(-119.5), 20,
...     max_range=10000, n_radials=36, samples_per_radial=10)
>>> result.visible.any()  # Some cells visible
True
pytcl.terrain.compute_horizon(dem, obs_lat, obs_lon, obs_height, n_azimuths=360, max_range=50000.0, samples_per_radial=100, earth_radius=6371000.0)[source]

Compute terrain horizon profile from an observer location.

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • obs_lat (float) – Observer latitude in radians.

  • obs_lon (float) – Observer longitude in radians.

  • obs_height (float) – Observer height above terrain in meters.

  • n_azimuths (int, optional) – Number of azimuth directions. Default is 360.

  • max_range (float, optional) – Maximum analysis range in meters. Default is 50000.0.

  • samples_per_radial (int, optional) – Number of samples per radial. Default is 100.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

Returns:

Horizon points for each azimuth direction.

Return type:

list of HorizonPoint

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> horizon = compute_horizon(
...     dem, np.radians(35.5), np.radians(-119.5), 10,
...     n_azimuths=8, max_range=10000, samples_per_radial=10)
>>> len(horizon)
8
pytcl.terrain.terrain_masking_angle(dem, obs_lat, obs_lon, obs_height, azimuth, max_range=50000.0, n_samples=100, earth_radius=6371000.0)[source]

Compute terrain masking angle in a specific direction.

The masking angle is the minimum elevation angle at which a target would be visible (not blocked by terrain).

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • obs_lat (float) – Observer latitude in radians.

  • obs_lon (float) – Observer longitude in radians.

  • obs_height (float) – Observer height above terrain in meters.

  • azimuth (float) – Azimuth direction in radians (clockwise from north).

  • max_range (float, optional) – Maximum analysis range in meters. Default is 50000.0.

  • n_samples (int, optional) – Number of sample points. Default is 100.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

Returns:

Masking angle in radians above horizontal.

Return type:

float

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> angle = terrain_masking_angle(
...     dem, np.radians(35.5), np.radians(-119.5), 10, azimuth=0)
>>> -np.pi/2 <= angle <= np.pi/2  # Valid angle range
True
pytcl.terrain.radar_coverage_map(dem, radar_lat, radar_lon, radar_height, min_elevation=0.0, max_range=100000.0, target_height=1000.0, n_radials=360, samples_per_radial=200, earth_radius=6371000.0, refraction_coeff=0.13)[source]

Compute radar coverage map accounting for terrain masking.

Similar to viewshed but with radar-specific parameters including minimum elevation angle and atmospheric refraction.

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • radar_lat (float) – Radar latitude in radians.

  • radar_lon (float) – Radar longitude in radians.

  • radar_height (float) – Radar antenna height above terrain in meters.

  • min_elevation (float, optional) – Minimum radar elevation angle in radians. Default is 0.0.

  • max_range (float, optional) – Maximum radar range in meters. Default is 100000.0.

  • target_height (float, optional) – Target altitude above terrain in meters. Default is 1000.0.

  • n_radials (int, optional) – Number of radial directions. Default is 360.

  • samples_per_radial (int, optional) – Samples per radial. Default is 200.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

  • refraction_coeff (float, optional) – Atmospheric refraction coefficient (0.13 for 4/3 Earth). Default is 0.13.

Returns:

Radar coverage map.

Return type:

ViewshedResult

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> coverage = radar_coverage_map(
...     dem, np.radians(35.5), np.radians(-119.5), 30,
...     max_range=20000, n_radials=36, samples_per_radial=20)
>>> coverage.visible.any()  # Some coverage exists
True
class pytcl.terrain.GEBCOMetadata(version, resolution_arcsec, lat_min, lat_max, lon_min, lon_max, source)[source]

Bases: NamedTuple

GEBCO dataset metadata.

version

GEBCO version (e.g., “GEBCO2025”).

Type:

str

resolution_arcsec

Grid resolution in arc-seconds.

Type:

float

lat_min

Minimum latitude in radians.

Type:

float

lat_max

Maximum latitude in radians.

Type:

float

lon_min

Minimum longitude in radians.

Type:

float

lon_max

Maximum longitude in radians.

Type:

float

source

Data source identifier.

Type:

str

version: str

Alias for field number 0

resolution_arcsec: float

Alias for field number 1

lat_min: float

Alias for field number 2

lat_max: float

Alias for field number 3

lon_min: float

Alias for field number 4

lon_max: float

Alias for field number 5

source: str

Alias for field number 6

class pytcl.terrain.Earth2014Metadata(layer, description, resolution_arcsec, lat_min, lat_max, lon_min, lon_max)[source]

Bases: NamedTuple

Earth2014 dataset metadata.

layer

Layer type (SUR, BED, TBI, RET, ICE).

Type:

str

description

Layer description.

Type:

str

resolution_arcsec

Grid resolution in arc-seconds.

Type:

float

lat_min

Minimum latitude in radians.

Type:

float

lat_max

Maximum latitude in radians.

Type:

float

lon_min

Minimum longitude in radians.

Type:

float

lon_max

Maximum longitude in radians.

Type:

float

layer: str

Alias for field number 0

description: str

Alias for field number 1

resolution_arcsec: float

Alias for field number 2

lat_min: float

Alias for field number 3

lat_max: float

Alias for field number 4

lon_min: float

Alias for field number 5

lon_max: float

Alias for field number 6

pytcl.terrain.get_data_dir()[source]

Get the pytcl data directory for external data files.

The data directory is located at ~/.pytcl/data/ by default. Can be overridden by setting the PYTCL_DATA_DIR environment variable.

Returns:

Path to the data directory.

Return type:

Path

pytcl.terrain.load_gebco(lat_min, lat_max, lon_min, lon_max, version='GEBCO2025')[source]

Load GEBCO bathymetry/topography data for a region.

GEBCO (General Bathymetric Chart of the Oceans) provides global bathymetry and land topography at 15 arc-second resolution.

Parameters:
  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • version (str, optional) – GEBCO version (“GEBCO2025”, “GEBCO2024”, “GEBCO2023”, “GEBCO2022”). Default is “GEBCO2025”.

Returns:

DEM grid containing bathymetry/topography data.

Return type:

DEMGrid

Raises:

Examples

>>> import numpy as np
>>> dem = load_gebco(
...     lat_min=np.radians(35.0),
...     lat_max=np.radians(40.0),
...     lon_min=np.radians(-125.0),
...     lon_max=np.radians(-120.0),
...     version="GEBCO2025"
... )
>>> point = dem.get_elevation(np.radians(37.5), np.radians(-122.5))
>>> print(f"Elevation: {point.elevation:.1f} m")

Notes

GEBCO data files are not included in the package due to their size (~7.5 GB). Download from: https://www.gebco.net/data-products/gridded-bathymetry-data/ Save to: ~/.pytcl/data/GEBCO2025.nc

pytcl.terrain.load_earth2014(lat_min, lat_max, lon_min, lon_max, layer='SUR')[source]

Load Earth2014 terrain data for a region.

Earth2014 provides global topography at 1 arc-minute resolution with multiple layer representations.

Parameters:
  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • layer (str, optional) – Layer type. Options: - “SUR”: Physical surface (topography, ice surface, 0 over oceans) - “BED”: Bedrock (topography minus ice/water) - “TBI”: Topography, bedrock, ice sheet surfaces - “RET”: Rock-equivalent topography - “ICE”: Ice sheet thickness Default is “SUR”.

Returns:

DEM grid containing terrain data.

Return type:

DEMGrid

Raises:

Examples

>>> import numpy as np
>>> dem = load_earth2014(
...     lat_min=np.radians(35.0),
...     lat_max=np.radians(40.0),
...     lon_min=np.radians(-125.0),
...     lon_max=np.radians(-120.0),
...     layer="SUR"
... )
>>> point = dem.get_elevation(np.radians(37.5), np.radians(-122.5))
>>> print(f"Elevation: {point.elevation:.1f} m")

Notes

Earth2014 data files are not included in the package (~455 MB per layer). Download from: https://ddfe.curtin.edu.au/models/Earth2014/ Save to: ~/.pytcl/data/Earth2014.SUR2014.1min.geod.bin (for SUR layer)

References

Hirt, C. and Rexer, M. (2015) Earth2014: 1 arc-min shape, topography, bedrock and ice-sheet models. Int. J. Appl. Earth Observ. Geoinform. 39.

pytcl.terrain.create_test_gebco_dem(lat_min=np.float64(0.6108652381980153), lat_max=np.float64(0.6981317007977318), lon_min=np.float64(-2.181661564992912), lon_max=np.float64(-2.0943951023931953), resolution_arcsec=60.0, include_bathymetry=True, seed=42)[source]

Create synthetic GEBCO-like DEM for testing.

Generates a synthetic terrain with both land and ocean areas, useful for testing without requiring actual GEBCO data files.

Parameters:
  • lat_min (float, optional) – Minimum latitude in radians. Default is 35°N.

  • lat_max (float, optional) – Maximum latitude in radians. Default is 40°N.

  • lon_min (float, optional) – Minimum longitude in radians. Default is 125°W.

  • lon_max (float, optional) – Maximum longitude in radians. Default is 120°W.

  • resolution_arcsec (float, optional) – Grid resolution in arc-seconds. Default is 60.0 (1 arc-min).

  • include_bathymetry (bool, optional) – If True, includes ocean bathymetry. Default is True.

  • seed (int, optional) – Random seed for reproducibility. Default is 42.

Returns:

Synthetic DEM mimicking GEBCO characteristics.

Return type:

DEMGrid

pytcl.terrain.create_test_earth2014_dem(lat_min=np.float64(0.6108652381980153), lat_max=np.float64(0.6981317007977318), lon_min=np.float64(-2.181661564992912), lon_max=np.float64(-2.0943951023931953), layer='SUR', resolution_arcsec=60.0, seed=42)[source]

Create synthetic Earth2014-like DEM for testing.

Generates a synthetic terrain mimicking Earth2014 characteristics, useful for testing without requiring actual data files.

Parameters:
  • lat_min (float, optional) – Minimum latitude in radians. Default is 35°N.

  • lat_max (float, optional) – Maximum latitude in radians. Default is 40°N.

  • lon_min (float, optional) – Minimum longitude in radians. Default is 125°W.

  • lon_max (float, optional) – Maximum longitude in radians. Default is 120°W.

  • layer (str, optional) – Layer type to simulate. Default is “SUR”.

  • resolution_arcsec (float, optional) – Grid resolution in arc-seconds. Default is 60.0 (1 arc-min).

  • seed (int, optional) – Random seed for reproducibility. Default is 42.

Returns:

Synthetic DEM mimicking Earth2014 characteristics.

Return type:

DEMGrid

pytcl.terrain.get_gebco_metadata(version='GEBCO2025')[source]

Get metadata for a GEBCO version.

Parameters:

version (str) – GEBCO version.

Returns:

Metadata about the dataset.

Return type:

GEBCOMetadata

pytcl.terrain.get_earth2014_metadata(layer='SUR')[source]

Get metadata for an Earth2014 layer.

Parameters:

layer (str) – Layer type.

Returns:

Metadata about the dataset.

Return type:

Earth2014Metadata

DEM Interface

Digital Elevation Model interface and operations.

Digital Elevation Model (DEM) Interface.

This module provides a generic interface for working with digital elevation models, including: - Elevation queries at geographic coordinates - Terrain gradient and slope calculations - Profile extraction along paths - Support for various DEM data formats

The interface is designed to work with common DEM formats including: - SRTM (Shuttle Radar Topography Mission) - ASTER GDEM - GEBCO (General Bathymetric Chart of the Oceans) - Earth2014 - ETOPO1/ETOPO2

References

class pytcl.terrain.dem.DEMPoint(latitude, longitude, elevation, valid)[source]

Bases: NamedTuple

Single point elevation query result.

Parameters:
  • latitude (float) – Latitude in radians.

  • longitude (float) – Longitude in radians.

  • elevation (float) – Elevation in meters above reference (MSL or ellipsoid).

  • valid (bool) – Whether the elevation value is valid (not a fill/no-data value).

latitude: float

Alias for field number 0

longitude: float

Alias for field number 1

elevation: float

Alias for field number 2

valid: bool

Alias for field number 3

class pytcl.terrain.dem.TerrainGradient(slope, aspect, dz_dx, dz_dy)[source]

Bases: NamedTuple

Terrain gradient at a point.

Parameters:
  • slope (float) – Terrain slope in radians (angle from horizontal).

  • aspect (float) – Aspect (downslope direction) in radians, measured clockwise from north.

  • dz_dx (float) – Elevation gradient in east direction (m/m).

  • dz_dy (float) – Elevation gradient in north direction (m/m).

slope: float

Alias for field number 0

aspect: float

Alias for field number 1

dz_dx: float

Alias for field number 2

dz_dy: float

Alias for field number 3

class pytcl.terrain.dem.DEMMetadata(name, resolution, lat_min, lat_max, lon_min, lon_max, vertical_datum, horizontal_datum)[source]

Bases: NamedTuple

Metadata for a DEM dataset.

Parameters:
  • name (str) – Name of the DEM dataset.

  • resolution (float) – Grid resolution in arc-seconds.

  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • vertical_datum (str) – Vertical reference (e.g., ‘MSL’, ‘WGS84’).

  • horizontal_datum (str) – Horizontal reference (e.g., ‘WGS84’).

name: str

Alias for field number 0

resolution: float

Alias for field number 1

lat_min: float

Alias for field number 2

lat_max: float

Alias for field number 3

lon_min: float

Alias for field number 4

lon_max: float

Alias for field number 5

vertical_datum: str

Alias for field number 6

horizontal_datum: str

Alias for field number 7

class pytcl.terrain.dem.DEMGrid(data, lat_min, lat_max, lon_min, lon_max, nodata_value=-9999.0, name='Custom DEM')[source]

Bases: object

In-memory DEM grid for elevation queries.

This class provides a simple in-memory representation of a DEM grid for efficient elevation queries. Data can be loaded from arrays or from external files.

Parameters:
  • data (ndarray) – 2D array of elevation values (rows=latitude, cols=longitude). Latitude increases with row index (south to north).

  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • nodata_value (float, optional) – Value representing no data. Default is -9999.

  • name (str, optional) – Name identifier for the DEM. Default is “Custom DEM”.

Examples

>>> import numpy as np
>>> # Create a simple 10x10 DEM grid
>>> data = np.random.rand(10, 10) * 1000  # 0-1000m elevation
>>> dem = DEMGrid(
...     data,
...     lat_min=np.radians(35.0),
...     lat_max=np.radians(36.0),
...     lon_min=np.radians(-120.0),
...     lon_max=np.radians(-119.0)
... )
>>> elev = dem.get_elevation(np.radians(35.5), np.radians(-119.5))
__init__(data, lat_min, lat_max, lon_min, lon_max, nodata_value=-9999.0, name='Custom DEM')[source]
get_metadata()[source]

Get DEM metadata.

Returns:

Metadata about this DEM.

Return type:

DEMMetadata

get_elevation(lat, lon, interpolation='bilinear')[source]

Get elevation at a geographic coordinate.

Parameters:
  • lat (float) – Latitude in radians.

  • lon (float) – Longitude in radians.

  • interpolation (str, optional) – Interpolation method: ‘nearest’ or ‘bilinear’. Default is ‘bilinear’.

Returns:

Elevation query result with validity flag.

Return type:

DEMPoint

get_elevations(lats, lons, interpolation='bilinear')[source]

Get elevations at multiple geographic coordinates.

Parameters:
  • lats (ndarray) – Latitudes in radians.

  • lons (ndarray) – Longitudes in radians.

  • interpolation (str, optional) – Interpolation method. Default is ‘bilinear’.

Returns:

Array of elevation values. Invalid points contain nodata_value.

Return type:

ndarray

get_gradient(lat, lon, earth_radius=6371000.0)[source]

Compute terrain gradient at a point.

Uses central differences to estimate the gradient.

Parameters:
  • lat (float) – Latitude in radians.

  • lon (float) – Longitude in radians.

  • earth_radius (float, optional) – Earth radius in meters for converting angular to linear distances. Default is 6371000.0.

Returns:

Terrain gradient including slope and aspect.

Return type:

TerrainGradient

pytcl.terrain.dem.get_elevation_profile(dem, lat_start, lon_start, lat_end, lon_end, n_points=100)[source]

Extract elevation profile along a path.

Parameters:
  • dem (DEMGrid) – DEM grid to query.

  • lat_start (float) – Starting latitude in radians.

  • lon_start (float) – Starting longitude in radians.

  • lat_end (float) – Ending latitude in radians.

  • lon_end (float) – Ending longitude in radians.

  • n_points (int, optional) – Number of sample points along the path. Default is 100.

Returns:

  • distances (ndarray) – Array of distances from start in meters.

  • elevations (ndarray) – Array of elevation values in meters.

Return type:

Tuple[ndarray[tuple[Any, …], dtype[floating]], ndarray[tuple[Any, …], dtype[floating]]]

Examples

>>> import numpy as np
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119),
...     elevation=500)
>>> dists, elevs = get_elevation_profile(
...     dem, np.radians(35.2), np.radians(-119.8),
...     np.radians(35.8), np.radians(-119.2), n_points=10)
>>> len(dists) == 10
True
pytcl.terrain.dem.interpolate_dem(dem, new_lat_min, new_lat_max, new_lon_min, new_lon_max, new_n_lat, new_n_lon, method='bilinear')[source]

Interpolate DEM to a new grid.

Parameters:
  • dem (DEMGrid) – Source DEM grid.

  • new_lat_min (float) – New minimum latitude in radians.

  • new_lat_max (float) – New maximum latitude in radians.

  • new_lon_min (float) – New minimum longitude in radians.

  • new_lon_max (float) – New maximum longitude in radians.

  • new_n_lat (int) – Number of latitude points in new grid.

  • new_n_lon (int) – Number of longitude points in new grid.

  • method (str, optional) – Interpolation method. Default is ‘bilinear’.

Returns:

New interpolated DEM grid.

Return type:

DEMGrid

Examples

>>> import numpy as np
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119),
...     elevation=100)
>>> new_dem = interpolate_dem(
...     dem,
...     np.radians(35.2), np.radians(35.8),
...     np.radians(-119.8), np.radians(-119.2),
...     new_n_lat=5, new_n_lon=5)
>>> new_dem.data.shape
(5, 5)
pytcl.terrain.dem.merge_dems(dems, lat_min, lat_max, lon_min, lon_max, resolution_arcsec=30.0)[source]

Merge multiple DEMs into a single grid.

Parameters:
  • dems (list of DEMGrid) – List of DEM grids to merge.

  • lat_min (float) – Output minimum latitude in radians.

  • lat_max (float) – Output maximum latitude in radians.

  • lon_min (float) – Output minimum longitude in radians.

  • lon_max (float) – Output maximum longitude in radians.

  • resolution_arcsec (float, optional) – Output resolution in arc-seconds. Default is 30.0.

Returns:

Merged DEM grid.

Return type:

DEMGrid

Examples

>>> import numpy as np
>>> dem1 = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> dem2 = create_flat_dem(
...     np.radians(36), np.radians(37),
...     np.radians(-120), np.radians(-119), elevation=200)
>>> merged = merge_dems(
...     [dem1, dem2],
...     np.radians(35), np.radians(37),
...     np.radians(-120), np.radians(-119))
>>> merged.name
'Merged DEM'
pytcl.terrain.dem.create_flat_dem(lat_min, lat_max, lon_min, lon_max, elevation=0.0, resolution_arcsec=30.0)[source]

Create a flat DEM with constant elevation.

Useful for testing or as a placeholder when terrain data is unavailable.

Parameters:
  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • elevation (float, optional) – Constant elevation in meters. Default is 0.0.

  • resolution_arcsec (float, optional) – Grid resolution in arc-seconds. Default is 30.0.

Returns:

Flat DEM grid.

Return type:

DEMGrid

Examples

>>> import numpy as np
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119),
...     elevation=500)
>>> dem.name
'Flat DEM'
>>> result = dem.get_elevation(np.radians(35.5), np.radians(-119.5))
>>> abs(result.elevation - 500) < 1
True
pytcl.terrain.dem.create_synthetic_terrain(lat_min, lat_max, lon_min, lon_max, base_elevation=0.0, amplitude=1000.0, wavelength_km=50.0, resolution_arcsec=30.0, seed=None)[source]

Create synthetic terrain for testing.

Generates terrain using a combination of sinusoidal functions and random noise to simulate realistic topography.

Parameters:
  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • base_elevation (float, optional) – Base elevation in meters. Default is 0.0.

  • amplitude (float, optional) – Amplitude of elevation variations in meters. Default is 1000.0.

  • wavelength_km (float, optional) – Characteristic wavelength of terrain features in km. Default is 50.0.

  • resolution_arcsec (float, optional) – Grid resolution in arc-seconds. Default is 30.0.

  • seed (int, optional) – Random seed for reproducibility.

Returns:

Synthetic terrain DEM.

Return type:

DEMGrid

Examples

>>> import numpy as np
>>> dem = create_synthetic_terrain(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119),
...     base_elevation=500, amplitude=200, seed=42)
>>> dem.name
'Synthetic Terrain'
>>> dem.data.min() < dem.data.max()  # Has elevation variation
True

Data Loaders

GEBCO and Earth2014 terrain data loaders. The default GEBCO version is GEBCO 2025. Requires pip install nrl-tracker[terrain] for NetCDF support.

GEBCO and Earth2014 terrain data loaders.

This module provides functions for loading elevation data from: - GEBCO (General Bathymetric Chart of the Oceans): Global bathymetry and topography - Earth2014: High-resolution global terrain model with multiple representations

Both datasets require external files that are too large to bundle with the package. Users should download files from the official sources and place them in ~/.pytcl/data/.

References

class pytcl.terrain.loaders.GEBCOMetadata(version, resolution_arcsec, lat_min, lat_max, lon_min, lon_max, source)[source]

Bases: NamedTuple

GEBCO dataset metadata.

version

GEBCO version (e.g., “GEBCO2025”).

Type:

str

resolution_arcsec

Grid resolution in arc-seconds.

Type:

float

lat_min

Minimum latitude in radians.

Type:

float

lat_max

Maximum latitude in radians.

Type:

float

lon_min

Minimum longitude in radians.

Type:

float

lon_max

Maximum longitude in radians.

Type:

float

source

Data source identifier.

Type:

str

version: str

Alias for field number 0

resolution_arcsec: float

Alias for field number 1

lat_min: float

Alias for field number 2

lat_max: float

Alias for field number 3

lon_min: float

Alias for field number 4

lon_max: float

Alias for field number 5

source: str

Alias for field number 6

class pytcl.terrain.loaders.Earth2014Metadata(layer, description, resolution_arcsec, lat_min, lat_max, lon_min, lon_max)[source]

Bases: NamedTuple

Earth2014 dataset metadata.

layer

Layer type (SUR, BED, TBI, RET, ICE).

Type:

str

description

Layer description.

Type:

str

resolution_arcsec

Grid resolution in arc-seconds.

Type:

float

lat_min

Minimum latitude in radians.

Type:

float

lat_max

Maximum latitude in radians.

Type:

float

lon_min

Minimum longitude in radians.

Type:

float

lon_max

Maximum longitude in radians.

Type:

float

layer: str

Alias for field number 0

description: str

Alias for field number 1

resolution_arcsec: float

Alias for field number 2

lat_min: float

Alias for field number 3

lat_max: float

Alias for field number 4

lon_min: float

Alias for field number 5

lon_max: float

Alias for field number 6

pytcl.terrain.loaders.get_data_dir()[source]

Get the pytcl data directory for external data files.

The data directory is located at ~/.pytcl/data/ by default. Can be overridden by setting the PYTCL_DATA_DIR environment variable.

Returns:

Path to the data directory.

Return type:

Path

pytcl.terrain.loaders.load_gebco(lat_min, lat_max, lon_min, lon_max, version='GEBCO2025')[source]

Load GEBCO bathymetry/topography data for a region.

GEBCO (General Bathymetric Chart of the Oceans) provides global bathymetry and land topography at 15 arc-second resolution.

Parameters:
  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • version (str, optional) – GEBCO version (“GEBCO2025”, “GEBCO2024”, “GEBCO2023”, “GEBCO2022”). Default is “GEBCO2025”.

Returns:

DEM grid containing bathymetry/topography data.

Return type:

DEMGrid

Raises:

Examples

>>> import numpy as np
>>> dem = load_gebco(
...     lat_min=np.radians(35.0),
...     lat_max=np.radians(40.0),
...     lon_min=np.radians(-125.0),
...     lon_max=np.radians(-120.0),
...     version="GEBCO2025"
... )
>>> point = dem.get_elevation(np.radians(37.5), np.radians(-122.5))
>>> print(f"Elevation: {point.elevation:.1f} m")

Notes

GEBCO data files are not included in the package due to their size (~7.5 GB). Download from: https://www.gebco.net/data-products/gridded-bathymetry-data/ Save to: ~/.pytcl/data/GEBCO2025.nc

pytcl.terrain.loaders.load_earth2014(lat_min, lat_max, lon_min, lon_max, layer='SUR')[source]

Load Earth2014 terrain data for a region.

Earth2014 provides global topography at 1 arc-minute resolution with multiple layer representations.

Parameters:
  • lat_min (float) – Minimum latitude in radians.

  • lat_max (float) – Maximum latitude in radians.

  • lon_min (float) – Minimum longitude in radians.

  • lon_max (float) – Maximum longitude in radians.

  • layer (str, optional) – Layer type. Options: - “SUR”: Physical surface (topography, ice surface, 0 over oceans) - “BED”: Bedrock (topography minus ice/water) - “TBI”: Topography, bedrock, ice sheet surfaces - “RET”: Rock-equivalent topography - “ICE”: Ice sheet thickness Default is “SUR”.

Returns:

DEM grid containing terrain data.

Return type:

DEMGrid

Raises:

Examples

>>> import numpy as np
>>> dem = load_earth2014(
...     lat_min=np.radians(35.0),
...     lat_max=np.radians(40.0),
...     lon_min=np.radians(-125.0),
...     lon_max=np.radians(-120.0),
...     layer="SUR"
... )
>>> point = dem.get_elevation(np.radians(37.5), np.radians(-122.5))
>>> print(f"Elevation: {point.elevation:.1f} m")

Notes

Earth2014 data files are not included in the package (~455 MB per layer). Download from: https://ddfe.curtin.edu.au/models/Earth2014/ Save to: ~/.pytcl/data/Earth2014.SUR2014.1min.geod.bin (for SUR layer)

References

Hirt, C. and Rexer, M. (2015) Earth2014: 1 arc-min shape, topography, bedrock and ice-sheet models. Int. J. Appl. Earth Observ. Geoinform. 39.

pytcl.terrain.loaders.create_test_gebco_dem(lat_min=np.float64(0.6108652381980153), lat_max=np.float64(0.6981317007977318), lon_min=np.float64(-2.181661564992912), lon_max=np.float64(-2.0943951023931953), resolution_arcsec=60.0, include_bathymetry=True, seed=42)[source]

Create synthetic GEBCO-like DEM for testing.

Generates a synthetic terrain with both land and ocean areas, useful for testing without requiring actual GEBCO data files.

Parameters:
  • lat_min (float, optional) – Minimum latitude in radians. Default is 35°N.

  • lat_max (float, optional) – Maximum latitude in radians. Default is 40°N.

  • lon_min (float, optional) – Minimum longitude in radians. Default is 125°W.

  • lon_max (float, optional) – Maximum longitude in radians. Default is 120°W.

  • resolution_arcsec (float, optional) – Grid resolution in arc-seconds. Default is 60.0 (1 arc-min).

  • include_bathymetry (bool, optional) – If True, includes ocean bathymetry. Default is True.

  • seed (int, optional) – Random seed for reproducibility. Default is 42.

Returns:

Synthetic DEM mimicking GEBCO characteristics.

Return type:

DEMGrid

pytcl.terrain.loaders.create_test_earth2014_dem(lat_min=np.float64(0.6108652381980153), lat_max=np.float64(0.6981317007977318), lon_min=np.float64(-2.181661564992912), lon_max=np.float64(-2.0943951023931953), layer='SUR', resolution_arcsec=60.0, seed=42)[source]

Create synthetic Earth2014-like DEM for testing.

Generates a synthetic terrain mimicking Earth2014 characteristics, useful for testing without requiring actual data files.

Parameters:
  • lat_min (float, optional) – Minimum latitude in radians. Default is 35°N.

  • lat_max (float, optional) – Maximum latitude in radians. Default is 40°N.

  • lon_min (float, optional) – Minimum longitude in radians. Default is 125°W.

  • lon_max (float, optional) – Maximum longitude in radians. Default is 120°W.

  • layer (str, optional) – Layer type to simulate. Default is “SUR”.

  • resolution_arcsec (float, optional) – Grid resolution in arc-seconds. Default is 60.0 (1 arc-min).

  • seed (int, optional) – Random seed for reproducibility. Default is 42.

Returns:

Synthetic DEM mimicking Earth2014 characteristics.

Return type:

DEMGrid

pytcl.terrain.loaders.get_gebco_metadata(version='GEBCO2025')[source]

Get metadata for a GEBCO version.

Parameters:

version (str) – GEBCO version.

Returns:

Metadata about the dataset.

Return type:

GEBCOMetadata

pytcl.terrain.loaders.get_earth2014_metadata(layer='SUR')[source]

Get metadata for an Earth2014 layer.

Parameters:

layer (str) – Layer type.

Returns:

Metadata about the dataset.

Return type:

Earth2014Metadata

pytcl.terrain.loaders.parse_gebco_netcdf(filepath, lat_min=None, lat_max=None, lon_min=None, lon_max=None)[source]

Parse GEBCO NetCDF file and extract region.

Parameters:
  • filepath (Path) – Path to GEBCO NetCDF file.

  • lat_min (float, optional) – Minimum latitude in radians (default: -90°).

  • lat_max (float, optional) – Maximum latitude in radians (default: +90°).

  • lon_min (float, optional) – Minimum longitude in radians (default: -180°).

  • lon_max (float, optional) – Maximum longitude in radians (default: +180°).

Returns:

  • data (ndarray) – Elevation data array.

  • lat_min_actual (float) – Actual minimum latitude of extracted region.

  • lat_max_actual (float) – Actual maximum latitude of extracted region.

  • lon_min_actual (float) – Actual minimum longitude of extracted region.

  • lon_max_actual (float) – Actual maximum longitude of extracted region.

Return type:

tuple[ndarray[tuple[Any, …], dtype[floating]], float, float, float, float]

pytcl.terrain.loaders.parse_earth2014_binary(filepath, layer, lat_min=None, lat_max=None, lon_min=None, lon_max=None)[source]

Parse Earth2014 binary file and extract region.

Earth2014 files are stored as int16 big-endian binary data, organized as rows from south to north, columns from west to east.

Parameters:
  • filepath (Path) – Path to Earth2014 binary file.

  • layer (str) – Layer type (SUR, BED, TBI, RET, ICE).

  • lat_min (float, optional) – Minimum latitude in radians (default: -90°).

  • lat_max (float, optional) – Maximum latitude in radians (default: +90°).

  • lon_min (float, optional) – Minimum longitude in radians (default: -180°).

  • lon_max (float, optional) – Maximum longitude in radians (default: +180°).

Returns:

  • data (ndarray) – Elevation data array in meters.

  • lat_min_actual (float) – Actual minimum latitude of extracted region.

  • lat_max_actual (float) – Actual maximum latitude of extracted region.

  • lon_min_actual (float) – Actual minimum longitude of extracted region.

  • lon_max_actual (float) – Actual maximum longitude of extracted region.

Return type:

tuple[ndarray[tuple[Any, …], dtype[floating]], float, float, float, float]

Visibility Analysis

Line-of-sight and viewshed computation.

Terrain Visibility and Masking Functions.

This module provides functions for computing terrain visibility and masking, including: - Line-of-sight (LOS) analysis between points - Viewshed computation (visible area from a point) - Terrain masking for radar/sensor coverage - Horizon computation

These functions are essential for: - Radar coverage analysis - Communication link budget calculations - Sensor placement optimization - Target detection range estimation

References

class pytcl.terrain.visibility.LOSResult(visible, grazing_angle, obstacle_distance, obstacle_elevation, clearance)[source]

Bases: NamedTuple

Line-of-sight analysis result.

Parameters:
  • visible (bool) – Whether there is unobstructed line of sight between observer and target.

  • grazing_angle (float) – Grazing angle to terrain in radians (angle above/below obstacle). Positive means clearing terrain, negative means blocked.

  • obstacle_distance (float) – Distance to the blocking obstacle in meters (if blocked). 0 if line of sight is clear.

  • obstacle_elevation (float) – Elevation of blocking obstacle in meters (if blocked).

  • clearance (float) – Minimum clearance above terrain along path in meters. Negative if blocked.

visible: bool

Alias for field number 0

grazing_angle: float

Alias for field number 1

obstacle_distance: float

Alias for field number 2

obstacle_elevation: float

Alias for field number 3

clearance: float

Alias for field number 4

class pytcl.terrain.visibility.ViewshedResult(visible, observer_lat, observer_lon, observer_height, lat_min, lat_max, lon_min, lon_max)[source]

Bases: NamedTuple

Viewshed computation result.

Parameters:
  • visible (ndarray) – 2D boolean array indicating visibility from observer.

  • observer_lat (float) – Observer latitude in radians.

  • observer_lon (float) – Observer longitude in radians.

  • observer_height (float) – Observer height above terrain in meters.

  • lat_min (float) – Minimum latitude of viewshed grid in radians.

  • lat_max (float) – Maximum latitude of viewshed grid in radians.

  • lon_min (float) – Minimum longitude of viewshed grid in radians.

  • lon_max (float) – Maximum longitude of viewshed grid in radians.

visible: ndarray[tuple[Any, ...], dtype[bool]]

Alias for field number 0

observer_lat: float

Alias for field number 1

observer_lon: float

Alias for field number 2

observer_height: float

Alias for field number 3

lat_min: float

Alias for field number 4

lat_max: float

Alias for field number 5

lon_min: float

Alias for field number 6

lon_max: float

Alias for field number 7

class pytcl.terrain.visibility.HorizonPoint(azimuth, elevation_angle, distance, terrain_elevation)[source]

Bases: NamedTuple

Point on the terrain horizon.

Parameters:
  • azimuth (float) – Azimuth from observer in radians (clockwise from north).

  • elevation_angle (float) – Elevation angle to horizon in radians (above horizontal).

  • distance (float) – Distance to horizon point in meters.

  • terrain_elevation (float) – Terrain elevation at horizon point in meters.

azimuth: float

Alias for field number 0

elevation_angle: float

Alias for field number 1

distance: float

Alias for field number 2

terrain_elevation: float

Alias for field number 3

pytcl.terrain.visibility.line_of_sight(dem, obs_lat, obs_lon, obs_height, tgt_lat, tgt_lon, tgt_height, n_samples=100, earth_radius=6371000.0, refraction_coeff=0.0)[source]

Compute line of sight between observer and target.

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • obs_lat (float) – Observer latitude in radians.

  • obs_lon (float) – Observer longitude in radians.

  • obs_height (float) – Observer height above terrain in meters.

  • tgt_lat (float) – Target latitude in radians.

  • tgt_lon (float) – Target longitude in radians.

  • tgt_height (float) – Target height above terrain in meters.

  • n_samples (int, optional) – Number of sample points along path. Default is 100.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

  • refraction_coeff (float, optional) – Atmospheric refraction coefficient (typically 0.13 for radio). Set to 0 for optical line of sight. Default is 0.0.

Returns:

Line-of-sight analysis result.

Return type:

LOSResult

Notes

The refraction coefficient models atmospheric bending of radio waves. A typical value for radio frequencies is 0.13 (4/3 Earth model). For optical line of sight, use 0.

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> result = line_of_sight(
...     dem,
...     np.radians(35.3), np.radians(-119.7), 10,
...     np.radians(35.7), np.radians(-119.3), 10)
>>> result.visible  # Clear LOS over flat terrain
True
pytcl.terrain.visibility.viewshed(dem, obs_lat, obs_lon, obs_height, max_range=50000.0, target_height=0.0, n_radials=360, samples_per_radial=100, earth_radius=6371000.0, refraction_coeff=0.0)[source]

Compute viewshed (visible area) from an observer location.

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • obs_lat (float) – Observer latitude in radians.

  • obs_lon (float) – Observer longitude in radians.

  • obs_height (float) – Observer height above terrain in meters.

  • max_range (float, optional) – Maximum analysis range in meters. Default is 50000.0.

  • target_height (float, optional) – Target height above terrain in meters. Default is 0.0.

  • n_radials (int, optional) – Number of radial directions to analyze. Default is 360.

  • samples_per_radial (int, optional) – Number of samples per radial. Default is 100.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

  • refraction_coeff (float, optional) – Atmospheric refraction coefficient. Default is 0.0.

Returns:

Viewshed computation result with visibility grid.

Return type:

ViewshedResult

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> result = viewshed(
...     dem, np.radians(35.5), np.radians(-119.5), 20,
...     max_range=10000, n_radials=36, samples_per_radial=10)
>>> result.visible.any()  # Some cells visible
True
pytcl.terrain.visibility.compute_horizon(dem, obs_lat, obs_lon, obs_height, n_azimuths=360, max_range=50000.0, samples_per_radial=100, earth_radius=6371000.0)[source]

Compute terrain horizon profile from an observer location.

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • obs_lat (float) – Observer latitude in radians.

  • obs_lon (float) – Observer longitude in radians.

  • obs_height (float) – Observer height above terrain in meters.

  • n_azimuths (int, optional) – Number of azimuth directions. Default is 360.

  • max_range (float, optional) – Maximum analysis range in meters. Default is 50000.0.

  • samples_per_radial (int, optional) – Number of samples per radial. Default is 100.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

Returns:

Horizon points for each azimuth direction.

Return type:

list of HorizonPoint

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> horizon = compute_horizon(
...     dem, np.radians(35.5), np.radians(-119.5), 10,
...     n_azimuths=8, max_range=10000, samples_per_radial=10)
>>> len(horizon)
8
pytcl.terrain.visibility.terrain_masking_angle(dem, obs_lat, obs_lon, obs_height, azimuth, max_range=50000.0, n_samples=100, earth_radius=6371000.0)[source]

Compute terrain masking angle in a specific direction.

The masking angle is the minimum elevation angle at which a target would be visible (not blocked by terrain).

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • obs_lat (float) – Observer latitude in radians.

  • obs_lon (float) – Observer longitude in radians.

  • obs_height (float) – Observer height above terrain in meters.

  • azimuth (float) – Azimuth direction in radians (clockwise from north).

  • max_range (float, optional) – Maximum analysis range in meters. Default is 50000.0.

  • n_samples (int, optional) – Number of sample points. Default is 100.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

Returns:

Masking angle in radians above horizontal.

Return type:

float

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> angle = terrain_masking_angle(
...     dem, np.radians(35.5), np.radians(-119.5), 10, azimuth=0)
>>> -np.pi/2 <= angle <= np.pi/2  # Valid angle range
True
pytcl.terrain.visibility.radar_coverage_map(dem, radar_lat, radar_lon, radar_height, min_elevation=0.0, max_range=100000.0, target_height=1000.0, n_radials=360, samples_per_radial=200, earth_radius=6371000.0, refraction_coeff=0.13)[source]

Compute radar coverage map accounting for terrain masking.

Similar to viewshed but with radar-specific parameters including minimum elevation angle and atmospheric refraction.

Parameters:
  • dem (DEMGrid) – Digital elevation model.

  • radar_lat (float) – Radar latitude in radians.

  • radar_lon (float) – Radar longitude in radians.

  • radar_height (float) – Radar antenna height above terrain in meters.

  • min_elevation (float, optional) – Minimum radar elevation angle in radians. Default is 0.0.

  • max_range (float, optional) – Maximum radar range in meters. Default is 100000.0.

  • target_height (float, optional) – Target altitude above terrain in meters. Default is 1000.0.

  • n_radials (int, optional) – Number of radial directions. Default is 360.

  • samples_per_radial (int, optional) – Samples per radial. Default is 200.

  • earth_radius (float, optional) – Earth radius in meters. Default is 6371000.0.

  • refraction_coeff (float, optional) – Atmospheric refraction coefficient (0.13 for 4/3 Earth). Default is 0.13.

Returns:

Radar coverage map.

Return type:

ViewshedResult

Examples

>>> import numpy as np
>>> from pytcl.terrain.dem import create_flat_dem
>>> dem = create_flat_dem(
...     np.radians(35), np.radians(36),
...     np.radians(-120), np.radians(-119), elevation=100)
>>> coverage = radar_coverage_map(
...     dem, np.radians(35.5), np.radians(-119.5), 30,
...     max_range=20000, n_radials=36, samples_per_radial=20)
>>> coverage.visible.any()  # Some coverage exists
True