Elevation Maps#

Elevation maps describe the height of the Earth’s surface above sea level. They can be stored as vector data (contour lines) or raster data (height grids).

Data Structure#

Vector Format (geopandas.GeoDataFrame)

Columns:
    elev      - elevation value (meters above sea level)
    geometry  - LINESTRING (contour lines) or POLYGON

Raster Format (xarray.DataArray)

DataArray:
    name: elevation
    dims: (south_north, west_east)
    dtype: float (meters above sea level)

I/O#

Reading:

windkit.read_elevation_map() reads elevation from raster or vector files:

elev_vector = wk.read_elevation_map("elev.map", crs="EPSG:32629")
elev_raster = wk.read_elevation_map("elev.tif")

See I/O for the supported formats and for reading maps from zip archives.

Writing:

windkit.elevation_map_to_file() writes elevation maps:

wk.elevation_map_to_file(elev_raster, "output.tif")

Working with Raster Elevation#

Raster data can be reprojected using windkit.spatial.warp():

warped = wk.spatial.warp(raster_in_latlon, to_crs="EPSG:32632")

Clip raster to smaller area using windkit.spatial.clip()

# Clip raster to a smaller area
bbox = wk.spatial.BBox.from_cornerpts(
    minx=502000, miny=4602000,
    maxx=508000, maxy=4608000,
    crs="EPSG:32629"
)
clipped = wk.spatial.clip(raster, bbox)

Filling No-Data Cells#

Raster elevation data often has missing cells, for example over water or at the edge of a tile, stored as NaN or infinite values. The flow models cannot use such cells, so windkit.fill_elevation_nodata() replaces them explicitly:

  • Cells connected to the edge of the raster take the value of the nearest valid cell.

  • Interior holes are interpolated linearly from the valid cells around them.

elev = xr.DataArray(
    [[10.0, 12.0, 14.0, 16.0],
     [11.0, np.nan, 15.0, 17.0],
     [12.0, 14.0, 16.0, 18.0],
     [np.nan, 15.0, 17.0, 19.0]],
    dims=["south_north", "west_east"],
    coords={"south_north": [0.0, 100.0, 200.0, 300.0],
            "west_east": [0.0, 100.0, 200.0, 300.0]},
    name="elevation",
)

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    filled = wk.fill_elevation_nodata(elev, max_fill_fraction=0.2)

for warning in caught:
    print(warning.message)
print(filled.values)
Filling 1 boundary no-data cell(s) in elevation raster 'elevation' using nearest-neighbour interpolation.
Filling 1 interior no-data cell(s) in elevation raster 'elevation' using piecewise-linear interpolation.
[[10. 12. 14. 16.]
 [11. 13. 15. 17.]
 [12. 14. 16. 18.]
 [12. 15. 17. 19.]]

A warning reports how many cells were filled each way. Filling invents terrain, so by default it is refused when more than 5 % of the cells are missing (max_fill_fraction=0.05); for large gaps it is usually better to clip the raster to its valid extent. Pass a larger fraction, or None, to fill anyway.