Copernicus DEM — quickstart (anonymous, no account)¶
Fetch Copernicus DEM coverage of the Nile Delta from the public AWS Open
Data bucket, then read + plot it with pyramids. No credentials, no SDK
login, no API key — the bucket is anonymous. earthlens.dem just hands you
the raw COGs; the cropping / mosaicking / hillshade is pyramids' job.
The delta spans eight 1° tiles, so this goes in two steps: one tile first, to show what a single COG looks like, then the full eight-tile bbox mosaicked into one continuous raster.
Setup¶
earthlens provides the unified EarthLens entry point; pyramids reads the
downloaded COG; downloads go to a per-notebook temp directory.
import tempfile
from pathlib import Path
from pyramids.dataset import Dataset
from pyramids.plot import ColorBar, ColorScaling
from earthlens.core import EarthLens
from earthlens.dem import Catalog
download_root = Path(tempfile.mkdtemp(prefix='dem-quickstart-'))
print(f'downloads will land under: {download_root}')
# Shared class breaks so all three maps are directly comparable. Most of the
# delta sits in the first few metres, so the breaks crowd there.
ELEVATION_BREAKS = [-25, -2, 0, 2, 4, 6, 10, 20, 50, 200, 750]
# One spelling of the colour-bar label, shared by all three maps.
ELEVATION_LABEL = 'elevation (m, EGM2008)'
# The axis labels are shared for the same reason.
LON_LABEL = 'longitude (°E)'
LAT_LABEL = 'latitude (°N)'
The DEM catalog¶
Two datasets ship today — the ~30 m GLO-30 and the ~90 m GLO-90 grids. Reading the catalog is offline.
catalog = Catalog()
for dataset_id in sorted(catalog.datasets):
row = catalog.get_dataset(dataset_id)
print(
f'{row}\n bucket={row.bucket:22} token={row.resolution_token}'
)
Download one GLO-30 tile¶
The Copernicus DEM grid is 1° x 1° tiles keyed by their SW corner. The bbox
here (lat=[30.2, 30.8], lon=[31.2, 31.8]) lies entirely inside the tile at
(30 N, 31 E), so exactly one COG is returned.
That tile holds the delta's south-eastern margin — the flat farmland south-east of the apex, running up against the eastern desert escarpment. It is not the delta itself: the coastline, the Rosetta and Damietta branches and the lagoons all sit outside it. The mosaic further down covers the whole thing.
single_out = download_root / 'delta_margin'
paths = EarthLens(
data_source='dem',
dataset='cop-dem-glo-30',
lat_lim=[30.2, 30.8],
lon_lim=[31.2, 31.8],
path=single_out,
).download()
for path in paths:
print(f' {path.name} ({path.stat().st_size / 1024**2:.1f} MB)')
Read the tile back with pyramids¶
The returned COG carries a WGS84 CRS and one elevation band (metres above
the EGM2008 geoid). Read it into a pyramids.Dataset and plot the raw
elevation.
dem = Dataset.read_file(paths[0])
print(f'EPSG : {dem.epsg}')
print(f'shape (y, x): {dem.rows} x {dem.columns}')
print(f'pixel size : {dem.cell_size}')
print(f'no-data : {dem.no_data_value}')
glyph = dem.plot(
cmap='terrain',
color=ColorScaling.boundary(bounds=ELEVATION_BREAKS),
colorbar=ColorBar(label=ELEVATION_LABEL),
title='Copernicus DEM GLO-30 — tile N30/E031, eastern delta margin',
)
glyph.ax.set_xlabel(LON_LABEL)
glyph.ax.set_ylabel(LAT_LABEL)
Crop the tile to the exact bbox¶
earthlens returns the whole 1° tile. To keep only the bbox pixels, hand
the raster to pyramids.Dataset.crop with the bbox in [west, south, east, north] order.
cropped = dem.crop(bbox=(31.2, 30.2, 31.8, 30.8), epsg=4326)
print(f'cropped shape (y, x): {cropped.rows} x {cropped.columns}')
glyph = cropped.plot(
cmap='terrain',
color=ColorScaling.boundary(bounds=ELEVATION_BREAKS),
colorbar=ColorBar(label=ELEVATION_LABEL),
title='Copernicus DEM GLO-30 — cropped bbox',
)
glyph.ax.set_xlabel(LON_LABEL)
glyph.ax.set_ylabel(LAT_LABEL)
The whole delta — multi-tile bbox and mosaic¶
A wider bbox spans several tiles — earthlens returns one COG per
intersected tile in row-major order, and
pyramids.dataset.merge.merge_rasters composites them into one continuous
raster.
The Nile Delta runs from the apex at Cairo (~30.0 N) to the Mediterranean (~31.5 N), and from the Rosetta branch (~29.8 E) east to Port Said (~32.4 E). That bbox intersects eight tiles. GLO-90 is used here rather than GLO-30: at 90 m the eight tiles are ~40 MB instead of ~380 MB, which is plenty to see the delta's shape.
merge_rasters inherits each source's own declared no-data by default
(serapeum-org/pyramids#1086, in the 0.65 floor). The Copernicus DEM tiles
declare none, so nothing is masked and every real 0 m cell survives — the
delta's water surfaces and its dead-flat, sea-level farmland all sit at
exactly 0 m and stay in the mosaic. An earlier default of 0 masked them,
punching the lagoons, the coast and the sea-level delta out as holes, so the
notebook used to pass an explicit sentinel; the fixed default makes that
unnecessary.
A linear colour ramp is useless here. Sixty percent of the delta lies below 10 m
while the flanking desert reaches 744 m, so a straight scale spends almost all
its colour on the margins and renders the delta as one flat wash. Explicit class
breaks — ColorScaling.boundary — concentrated in the first few metres
give the delta real contrast, and put a labelled tick on every break instead of
a single number floating mid-bar.
delta_out = download_root / 'nile_delta'
delta_paths = EarthLens(
data_source='dem',
dataset='cop-dem-glo-90',
lat_lim=[30.0, 31.7],
lon_lim=[29.8, 32.4],
path=delta_out,
).download()
for path in sorted(delta_paths):
print(f' {path.name} ({path.stat().st_size / 1024**2:.2f} MB)')
print(f'{len(delta_paths)} tiles fetched')
from pyramids.dataset.merge import merge_rasters
mosaic_path = delta_out / 'nile_delta_mosaic.tif'
merge_rasters(delta_paths, mosaic_path)
mosaic = Dataset.read_file(mosaic_path)
print(f'mosaic shape (y, x) : {mosaic.rows} x {mosaic.columns}')
delta = mosaic.crop(bbox=(29.8, 30.0, 32.4, 31.7), epsg=4326)
print(f'cropped shape (y, x): {delta.rows} x {delta.columns}')
glyph = delta.plot(
cmap='terrain',
color=ColorScaling.boundary(bounds=ELEVATION_BREAKS),
colorbar=ColorBar(label=ELEVATION_LABEL),
title='Copernicus DEM GLO-90 — the Nile Delta',
)
glyph.ax.set_xlabel(LON_LABEL)
glyph.ax.set_ylabel(LAT_LABEL)
What just happened — the account-free path¶
Every step above spoke to s3://copernicus-dem-30m and
s3://copernicus-dem-90m anonymously: no ~/.aws/credentials, no
AWS profile, no environment variable, no signer. Copernicus DEM is the
only globally-consistent DEM reachable through earthlens without an
account, which is the entire justification for the dem backend.