Reference Data — Coastlines, Borders & Relief#
The cleopatra.basemap.reference module draws public cartographic reference data
underneath your own plot — the cartopy ax.coastlines() /
GeoAxes.stock_img() niche, and the vector/raster sibling of
add_tiles:
add_features— a Natural Earth vector layer:coastline,borders,land,ocean,rivers, orlakes.add_relief— a global hypsometric relief backdrop.
Both fetch a small, fixed public dataset that cleopatra re-hosts as a dependency-light artifact (gzipped GeoJSON / PNG), cache it on disk, and render it with matplotlib. They acquire reference data only — they never read your files and never import GDAL or geopandas.
Dependencies#
add_features in EPSG:4326 needs nothing beyond numpy + matplotlib: the
layers are pre-converted to gzipped GeoJSON and read with the standard library.
Two paths use the optional cleopatra[tiles] extra:
add_reliefdecodes a PNG with Pillow.add_features(..., crs=...)reprojection uses pyproj.
If a required package is missing, the function raises a clear ImportError with
the install hint.
Usage#
Draw a relief backdrop with coastlines and country borders on a global lon/lat (EPSG:4326) map:
import matplotlib
matplotlib.use("Agg") # any backend; Agg shown for headless rendering
import matplotlib.pyplot as plt
from cleopatra.basemap.reference import add_relief, add_features
fig, ax = plt.subplots(figsize=(8, 4))
ax.set_xlim(-180, 180)
ax.set_ylim(-90, 90)
ax.set_aspect("equal") # 1° lon == 1° lat, so the map is not stretched
add_relief(ax, resolution="low") # hypsometric backdrop
add_features(ax, "coastline", "110m", colors="black")
add_features(ax, "borders", "110m", colors="0.4")
fig.savefig("world.png", dpi=100)

A regional map with a filled land layer and higher-resolution coastline:
fig, ax = plt.subplots()
ax.set_xlim(-20, 40) # Europe / N. Africa, in lon/lat
ax.set_ylim(0, 60)
ax.set_aspect("equal")
add_features(ax, "ocean", "50m", facecolors="#bdd7e7")
add_features(ax, "land", "50m", facecolors="0.9", edgecolors="0.5")
add_features(ax, "coastline", "50m", colors="navy", linewidths=0.8)
fig.savefig("europe.png")

If your data is in a projected CRS, pass crs= so the vectors are reprojected
to match (requires pyproj):
The raw data is also available without drawing:
from cleopatra.basemap.reference import natural_earth, relief
parts = natural_earth("coastline", "110m") # list of (N, 2) lon/lat arrays
rgb = relief("low") # (H, W, 3) uint8 RGB array
To discover the valid arguments, call available_layers() and
available_resolutions() for the vector layers, and
available_relief_resolutions() for the relief products.
Note
add_features / add_relief read the axes' current xlim/ylim and
preserve them, so plot your data first. Polygon layers
(land/ocean/lakes) are drawn as a filled PathCollection with
interior holes cut out (style with facecolors / edgecolors /
linewidths); line layers (coastline/rivers/borders) as a
LineCollection (style with colors / linewidths). Coordinates are
EPSG:4326 unless you pass crs=. Set ax.set_aspect("equal") on lon/lat
maps so degrees render at the same scale (matplotlib's default
aspect="auto" otherwise stretches the map to fill the figure).
Caching
The first call downloads the asset from the cleopatra
basemap-data-v1
release and caches it under ~/.cleopatra/naturalearth; subsequent calls
work offline. Override the location with the CLEOPATRA_CACHE_DIR
environment variable, or discover/resolve it programmatically with
Config.get_cache_dir(). Downloads are restricted to http(s) URLs.
Migrating from pyramids.basemap#
This data used to live in pyramids.basemap (natural_earth / relief). It
has moved to cleopatra.basemap.reference, which is the matplotlib map-decoration layer
— the same boundary the web-tile basemaps already follow (pyramids.basemap
forwarded to cleopatra.basemap.tiles). cleopatra hosts its own copy of the
assets and has no dependency on pyramids.
Old (pyramids.basemap) |
New (cleopatra.basemap.reference) |
Notes |
|---|---|---|
natural_earth(layer, resolution) → FeatureCollection |
natural_earth(layer, resolution) → list[np.ndarray] |
Now returns plain (N, 2) lon/lat arrays (exterior rings for polygons), not a GIS feature object. |
relief(resolution) → GDAL Dataset |
relief(resolution) → (H, W, 3) uint8 array |
Now a NumPy RGB array; the asset is a PNG (no GeoTIFF / GDAL). |
| (draw it yourself) | add_features(ax, layer, resolution) |
New axes helper — the ax.coastlines() analogue. |
| (draw it yourself) | add_relief(ax, resolution) |
New axes helper — the stock_img() analogue. |
PYRAMIDS_CACHE_DIR, ~/.pyramids/naturalearth |
CLEOPATRA_CACHE_DIR, ~/.cleopatra/naturalearth |
Cache env var and default directory renamed. |
The pyramids.basemap.natural_earth / relief entry points are deprecated and
emit a DeprecationWarning; update imports to cleopatra.basemap.reference. Resolutions
are unchanged (110m / 50m / 10m for vectors; low / medium for relief),
as are the six layer names.
Module Documentation#
cleopatra.basemap.reference
#
Reference-basemap backdrops for matplotlib axes.
Two axes-level helpers that draw public cartographic reference data
underneath your own plotted data -- the cartopy
GeoAxes.stock_img() / ax.coastlines() niche, and the vector/raster
sibling of cleopatra.basemap.tiles.add_tiles:
add_relief-- a global hypsometric relief image (thestock_img()analogue).add_features-- a Natural Earth vector layer: coastline, borders, land, ocean, rivers, or lakes (thecoastlines()analogue).
Both fetch a small, fixed public dataset that cleopatra re-hosts as a dependency-light artifact, cache it on disk, and render it with matplotlib. They acquire reference data only; they never read user files and never touch GDAL/geopandas:
- Relief is re-hosted as a plain PNG, so decoding needs only Pillow
(already part of the
cleopatra[tiles]extra). Every relief product is a global EPSG:4326 raster, so its extent is hardcoded rather than read from a geotransform. - Natural Earth layers are pre-converted (offline, maintainer-side) to
gzipped GeoJSON, so reading them needs only the standard library
(
json+gzip) plus numpy. Drawing in EPSG:4326 needs nothing beyond matplotlib; reprojecting to another CRS lazily usespyproj(also in the[tiles]extra).
The cache directory defaults to ~/.cleopatra/naturalearth and can be
overridden with the CLEOPATRA_CACHE_DIR environment variable; it is
resolved by cleopatra.config.Config.get_cache_dir, where the setting is
also discoverable. Downloads are restricted to http(s) URLs.
Examples:
Draw a relief backdrop and a coastline over data plotted in lon/lat (EPSG:4326):
>>> import matplotlib
>>> matplotlib.use("Agg")
>>> import matplotlib.pyplot as plt
>>> from cleopatra.basemap.reference import add_relief, add_features
>>> fig, ax = plt.subplots()
>>> ax.set_xlim(-20, 40); ax.set_ylim(0, 60)
>>> _ = add_relief(ax, resolution="low")
>>> _ = add_features(ax, "coastline", "50m")
add_features(ax, layer='coastline', resolution='110m', *, crs=None, zorder=0, **style)
#
Draw a Natural Earth reference layer on an Axes.
The cartopy ax.coastlines() analogue. Polygon layers
(land/ocean/lakes) are drawn hole-aware as a filled
PathCollection (so ocean's continent cut-outs and islands-in-lakes
render correctly); line layers (coastline/rivers/borders) as a
LineCollection. The source data is EPSG:4326; pass crs to reproject
the geometry into the axes' CRS (requires pyproj). The current axis
limits are preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ax
|
Any
|
A matplotlib |
required |
layer
|
str
|
One of |
'coastline'
|
resolution
|
str
|
One of |
'110m'
|
crs
|
int | str | None
|
CRS of the data on |
None
|
zorder
|
int
|
Matplotlib draw order for the layer. |
0
|
**style
|
Any
|
Overrides merged over the per-kind defaults and
forwarded to the underlying collection. Use polygon keys for
polygon layers ( |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
matplotlib.axes.Axes: The same axes, for chaining. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
ImportError
|
If |
ConnectionError
|
If the asset must be downloaded and the fetch fails. |
Examples:
- Overlay a coastline and country borders on a lon/lat map
(downloads each layer on first use, then caches it):
>>> import matplotlib >>> matplotlib.use("Agg") >>> import matplotlib.pyplot as plt >>> from cleopatra.basemap.reference import add_features >>> fig, ax = plt.subplots() >>> ax.set_xlim(-20, 40); ax.set_ylim(0, 60) # doctest: +SKIP >>> ax = add_features(ax, "coastline", "50m", colors="navy") # doctest: +SKIP >>> ax = add_features(ax, "borders", "50m") # doctest: +SKIP - Unknown layers are rejected before any download:
>>> import matplotlib >>> matplotlib.use("Agg") >>> import matplotlib.pyplot as plt >>> from cleopatra.basemap.reference import add_features >>> fig, ax = plt.subplots() >>> add_features(ax, "countries") Traceback (most recent call last): ... ValueError: Unknown layer 'countries'. Choose from ['coastline', 'land', 'ocean', 'rivers', 'lakes', 'borders'].
Source code in src/cleopatra/basemap/reference.py
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 | |
add_relief(ax, resolution='low', *, extent=None, alpha=1.0, zorder=-1, interpolation='bilinear', crs=None)
#
Draw a global hypsometric relief backdrop under existing data.
The cartopy GeoAxes.stock_img() analogue. Assumes the axes are in
EPSG:4326 (lon/lat) unless crs says otherwise, in which case the relief
is warped into that CRS. The current axis limits are preserved so adding
the backdrop never changes the view.
Note
The relief image is equirectangular (EPSG:4326). On an EPSG:4326 axis
(the default, crs=None) a lon/lat extent within the global bounds
is cropped out of the global image and placed over that box, so a
regional call shows that region's terrain rather than the whole world
squashed into it (the recurring #177 footgun). The crop is placed at
its snapped pixel edges so the terrain registers at true scale; the
axis limits (preserved below) then clip to the requested view.
extent=None places the whole globe. An extent OUTSIDE the global
lon/lat bounds on a 4326 axis is instead stretched by imshow to fit
-- visually acceptable for small extents but not a true reprojection.
For a non-EPSG:4326 crs the relief is warped into the axis
CRS (per-pixel inverse reprojection via pyproj) so it lines up under
data plotted in that CRS, exactly like add_features / add_tiles.
The placement box defaults to the current axis view; parts of the box
that fall outside the CRS's domain are left transparent. The box (and
the axis view it defaults from) is assumed non-inverted -- west <
east, south < north. This path needs pyproj (the [tiles] extra).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ax
|
Any
|
A matplotlib |
required |
resolution
|
str
|
|
'low'
|
extent
|
tuple[float, float, float, float] | None
|
|
None
|
alpha
|
float
|
Backdrop opacity in |
1.0
|
zorder
|
int
|
Matplotlib draw order ( |
-1
|
interpolation
|
str
|
Interpolation passed to |
'bilinear'
|
crs
|
int | str | None
|
CRS of the data on the axis. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
matplotlib.axes.Axes: The same axes, for chaining. |
Raises:
| Type | Description |
|---|---|
ImportError
|
If Pillow (the |
TypeError
|
If |
ValueError
|
If |
ConnectionError
|
If the asset must be downloaded and the fetch fails. |
Examples:
- Draw a relief backdrop under data already plotted in lon/lat
(downloads the asset on first use, then caches it):
>>> import matplotlib >>> matplotlib.use("Agg") >>> import matplotlib.pyplot as plt >>> from cleopatra.basemap.reference import add_relief >>> fig, ax = plt.subplots() >>> ax.set_xlim(-180, 180); ax.set_ylim(-90, 90) # doctest: +SKIP >>> ax = add_relief(ax, "low") # doctest: +SKIP >>> len(ax.images) # doctest: +SKIP 1 - Unknown resolutions are rejected before any download:
>>> import matplotlib >>> matplotlib.use("Agg") >>> import matplotlib.pyplot as plt >>> from cleopatra.basemap.reference import add_relief >>> fig, ax = plt.subplots() >>> add_relief(ax, "high") Traceback (most recent call last): ... ValueError: Unknown relief resolution 'high'. Choose from ['low', 'medium'].
Source code in src/cleopatra/basemap/reference.py
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 | |
available_layers()
#
Return the Natural Earth layers that can be requested.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: Valid |
Examples:
>>> from cleopatra.basemap.reference import available_layers
>>> available_layers()
['coastline', 'land', 'ocean', 'rivers', 'lakes', 'borders']
Source code in src/cleopatra/basemap/reference.py
available_relief_resolutions()
#
Return the relief resolutions that can be requested.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: The valid |
Examples:
>>> from cleopatra.basemap.reference import available_relief_resolutions
>>> available_relief_resolutions()
['low', 'medium']
Source code in src/cleopatra/basemap/reference.py
available_resolutions()
#
Return the supported Natural Earth resolutions.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: |
Examples:
>>> from cleopatra.basemap.reference import available_resolutions
>>> available_resolutions()
['110m', '50m', '10m']
Source code in src/cleopatra/basemap/reference.py
natural_earth(layer='coastline', resolution='110m')
#
Fetch (and cache) a Natural Earth layer as coordinate arrays.
The layer is downloaded as preprocessed gzipped GeoJSON and parsed
with the standard library only -- no GDAL/geopandas. Coordinates are
EPSG:4326 lon/lat. Polygon layers return exterior rings only; use
add_features for hole-aware filled rendering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer
|
str
|
One of |
'coastline'
|
resolution
|
str
|
One of |
'110m'
|
Returns:
| Type | Description |
|---|---|
list[ndarray]
|
list[numpy.ndarray]: One |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
ConnectionError
|
If the asset must be downloaded and the fetch fails. |
Examples:
- Fetch coastlines and inspect the parts (downloads on first use, then reads from the cache):
- Unknown layers are rejected before any download:
Source code in src/cleopatra/basemap/reference.py
relief(resolution='low')
#
Fetch (and cache) a hypsometric relief product as an RGB array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resolution
|
str
|
|
'low'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
numpy.ndarray: An |
Raises:
| Type | Description |
|---|---|
ImportError
|
If Pillow (the |
ValueError
|
If |
ConnectionError
|
If the asset must be downloaded and the fetch fails. |
OSError
|
If the cached file cannot be decoded as an image (the poisoned file is removed first so a retry re-downloads). |
Examples:
- Unknown resolutions raise
ValueErrorbefore any download: