Natural Earth map layers: lakes, rivers, glaciers, places#

esd.boundaries.natural_earth.load(aoi, layer=...) reads any of Natural Earth’s public-domain vector layers by name, at 1:10m, 1:50m or 1:110m: lakes, rivers and lake centrelines, coastline, land and ocean, glaciated areas, populated places, boundary lines, and the point-of-view editions of the countries layer. Each is one zipped shapefile, fetched once into the cache.

These layers are generalized for maps at their scale. They are the labels and outlines around snow data, not an analysis input: the glaciated areas here are a cartographic simplification of what the RGI glacier example measures.

The figures build a context map of the Pacific Northwest over the Natural Earth hillshade, and compare the coastline of Puget Sound at the three scales.

import matplotlib.pyplot as plt

import easysnowdata as esd

aoi = (-125.0, 45.0, -116.0, 50.0)  # Puget Sound, the Cascades and the Columbia

box = esd.parse_aoi(aoi)
grid = box.to_geobox(crs="utm", resolution=1000)
print(box)
AOI(bounds=(-125.0000, 45.0000, -116.0000, 50.0000), clip=True)

The layers LAYERS knows, with their category and scales; anything else in Natural Earth’s catalogue works with category=.

for layer, (category, scales) in esd.boundaries.natural_earth.LAYERS.items():
    print(f"{layer:36} {category:9} {', '.join(scales)}")
admin_0_countries                    cultural  10m, 50m, 110m
admin_0_boundary_lines_land          cultural  10m, 50m, 110m
admin_1_states_provinces             cultural  10m, 50m
admin_1_states_provinces_lines       cultural  10m, 50m, 110m
populated_places_simple              cultural  10m, 50m, 110m
urban_areas                          cultural  10m, 50m
roads                                cultural  10m
coastline                            physical  10m, 50m, 110m
land                                 physical  10m, 50m, 110m
ocean                                physical  10m, 50m, 110m
lakes                                physical  10m, 50m, 110m
rivers_lake_centerlines              physical  10m, 50m, 110m
glaciated_areas                      physical  10m, 50m, 110m
antarctic_ice_shelves_polys          physical  10m, 50m
geography_regions_polys              physical  10m, 50m, 110m
geography_regions_elevation_points   physical  10m, 50m, 110m

Lakes, rivers, glaciated areas and populated places at 1:10m. Features are returned whole where they cross the AOI edge.

lakes_gdf = esd.boundaries.natural_earth.load(aoi, layer="lakes")
rivers_gdf = esd.boundaries.natural_earth.load(aoi, layer="rivers_lake_centerlines")
ice_gdf = esd.boundaries.natural_earth.load(aoi, layer="glaciated_areas")
places_gdf = esd.boundaries.natural_earth.load(aoi, layer="populated_places_simple")
borders_gdf = esd.boundaries.natural_earth.load(
    aoi, layer="admin_1_states_provinces_lines"
)
for label, frame_gdf in (
    ("lakes", lakes_gdf),
    ("rivers", rivers_gdf),
    ("glaciated areas", ice_gdf),
    ("places", places_gdf),
    ("state lines", borders_gdf),
):
    print(f"{label:16} {len(frame_gdf):4d} features")

cities_gdf = places_gdf[places_gdf["pop_max"] > 150_000].to_crs(grid.crs)
print(
    cities_gdf[["name", "pop_max"]]
    .sort_values("pop_max", ascending=False)
    .to_string(index=False)
)

hillshade_da = esd.terrain.hillshade.load(box.buffer(80_000), style="shaded-relief")
ax = esd.plotting.map(
    hillshade_da.odc.reproject(grid, resampling="bilinear"),
    cmap="gray",
    vmin=0,
    vmax=255,
    colorbar=False,
    title="Natural Earth 1:10m layers over shaded relief",
    figsize=(10, 7),
)
esd.plotting.add_outline(
    ax, borders_gdf, edgecolor="0.2", linewidth=1.0, linestyle="--"
)
esd.plotting.add_outline(ax, rivers_gdf, edgecolor="#2171b5", linewidth=1.0)
esd.plotting.add_outline(
    ax, lakes_gdf, facecolor="#9ecae1", edgecolor="#2171b5", linewidth=0.5
)
esd.plotting.add_outline(
    ax, ice_gdf, facecolor="white", edgecolor="#08306b", linewidth=0.6
)
ax.scatter(
    cities_gdf.geometry.x, cities_gdf.geometry.y, s=12, color="#a50f15", zorder=5
)
for _, city in cities_gdf.iterrows():
    ax.annotate(
        city["name"],
        (city.geometry.x, city.geometry.y),
        xytext=(4, 3),
        textcoords="offset points",
        fontsize=8,
        zorder=5,
    )
ax.figure.tight_layout()
Natural Earth 1:10m layers over shaded relief
  0%|                                              | 0.00/2.35M [00:00<?, ?B/s]
  0%|                                              | 0.00/2.35M [00:00<?, ?B/s]
100%|█████████████████████████████████████| 2.35M/2.35M [00:00<00:00, 14.8GB/s]

  0%|                                              | 0.00/2.08M [00:00<?, ?B/s]
  0%|                                              | 0.00/2.08M [00:00<?, ?B/s]
100%|█████████████████████████████████████| 2.08M/2.08M [00:00<00:00, 13.9GB/s]

  0%|                                               | 0.00/652k [00:00<?, ?B/s]
  0%|                                               | 0.00/652k [00:00<?, ?B/s]
100%|███████████████████████████████████████| 652k/652k [00:00<00:00, 4.68GB/s]

  0%|                                              | 0.00/5.99M [00:00<?, ?B/s]
  0%|                                              | 0.00/5.99M [00:00<?, ?B/s]
100%|█████████████████████████████████████| 5.99M/5.99M [00:00<00:00, 31.2GB/s]
lakes               4 features
rivers             11 features
glaciated areas    13 features
places             34 features
state lines         5 features
      name  pop_max
   Seattle  3074000
 Vancouver  2313328
  Portland  1875000
    Tacoma   719868
 Vancouver   525802
   Everett   486903
   Spokane   347705
  Victoria   289625
   Olympia   156984
Abbotsford   151683

The same coastline at the three scales. 1:110m is for a world map, 1:50m for a continent, and only 1:10m resolves Puget Sound’s inlets; the file grows with the detail (85 kB, 0.46 MB and 3.1 MB for the whole world).

sound = (-123.4, 47.0, -122.2, 48.2)
sound_utm = esd.parse_aoi(sound).utm_crs
fig, axes = plt.subplots(1, 3, figsize=(13, 5))
for ax, scale in zip(axes, ("110m", "50m", "10m")):
    coast_gdf = esd.boundaries.natural_earth.load(sound, layer="coastline", scale=scale)
    coast_gdf.to_crs(sound_utm).plot(ax=ax, color="#08519c", linewidth=1.0)
    x0, y0, x1, y1 = esd.parse_aoi(sound).total_bounds(sound_utm)
    ax.set_xlim(x0, x1)
    ax.set_ylim(y0, y1)
    esd.plotting.finish_map(ax, sound_utm, scalebar=scale == "10m")
    ax.set_title(f"coastline, 1:{scale} ({len(coast_gdf)} features)")
fig.tight_layout()
coastline, 1:110m (1 features), coastline, 1:50m (5 features), coastline, 1:10m (7 features)
  0%|                                              | 0.00/85.4k [00:00<?, ?B/s]
  0%|                                              | 0.00/85.4k [00:00<?, ?B/s]
100%|██████████████████████████████████████| 85.4k/85.4k [00:00<00:00, 617MB/s]

  0%|                                               | 0.00/456k [00:00<?, ?B/s]
  0%|                                               | 0.00/456k [00:00<?, ?B/s]
100%|███████████████████████████████████████| 456k/456k [00:00<00:00, 3.37GB/s]

  0%|                                              | 0.00/3.07M [00:00<?, ?B/s]
  0%|                                              | 0.00/3.07M [00:00<?, ?B/s]
100%|█████████████████████████████████████| 3.07M/3.07M [00:00<00:00, 16.5GB/s]