Skip to content

Choose imagery and zoom

mapcv reads three kinds of imagery: XYZ tiles (including Google Earth Engine), Sentinel-2 products (EOPF Zarr, or COGs from a STAC catalog) and your own GeoTIFF or COG (drone and aerial orthophotos, commercial or national-mapping imagery, any raster you already have, at its own resolution and band layout). Choose by what your model needs to see:

XYZ tiles Sentinel-2 L2A (EOPF Zarr or COGs)
Pixel size ~0.1–40 m, set by zoom 10, 20 or 60 m
Channels RGB (3 × uint8) Up to 12 spectral bands (float32 reflectance from EOPF Zarr, uint16 values from COGs)
Good for Buildings, roads, trees, solar panels, anything you can see at sub-metre scale Land cover, crops, water, vegetation indices, change over large areas
Licensing Provider-specific; often restrictive Free and open Copernicus data
Install pip install mapcv (pip install "mapcv[gee]" for Earth Engine) pip install "mapcv[zarr]" for EOPF Zarr (Python 3.10–3.13); COGs (type: stac_cog) need no extra
source Content
esri_satellite Esri World Imagery (aerial/satellite photos)
esri_topo Esri World Topographic Map
esri_street Esri World Street Map
cartodb_positron CARTO light basemap
cartodb_dark_matter CARTO dark basemap

Basemaps (topo, street, CARTO) are rendered maps, not photos; they are mostly useful for map-style tasks or as a quick test. These names are conveniences, not licences: check Providers & licensing before you download.

Each zoom level halves the pixel size and quadruples the number of tiles. Ground resolution in Web Mercator depends on latitude: about 156 543 × cos(latitude) / 2zoom metres per pixel.

Zoom Equator 30° 45° 52° (Amsterdam) 60° Tiles per km² at 45°
14 9.55 m 8.27 m 6.76 m 5.88 m 4.78 m ~0.3
15 4.78 m 4.14 m 3.38 m 2.94 m 2.39 m ~1.3
16 2.39 m 2.07 m 1.69 m 1.47 m 1.19 m ~5
17 1.19 m 1.03 m 0.84 m 0.74 m 0.60 m ~21
18 0.60 m 0.52 m 0.42 m 0.37 m 0.30 m ~86
19 0.30 m 0.26 m 0.21 m 0.18 m 0.15 m ~340
20 0.15 m 0.13 m 0.11 m 0.09 m 0.07 m ~1,370

mapcv init prints this table for your area’s latitude, and mapcv plan shows the exact pixel size and tile count for your region.

Rules of thumb:

  • An object should span at least a few pixels. Single buildings work from zoom 17 (~1 m); cars and solar panels need 19 or more.
  • The source must actually have imagery at that zoom. Above the native resolution, providers upsample (blurry) or return errors or placeholder tiles. Look at a few patches before scaling up.
  • Patch size and zoom together set the context a model sees: a 256 px patch at zoom 18 covers about 100 m; at zoom 16, about 400 m.

Use url_template for any server that speaks the standard {z}/{x}/{y} scheme and returns 256 × 256 tiles:

imagery:
type: xyz
zoom: 16
url_template: "https://tiles.example.com/imagery/{z}/{x}/{y}.jpg"
max_connections: 4

The template needs {z}, {x} and {y} and nothing else in braces: replace {s} subdomains with one fixed subdomain, and {r} retina markers with an empty string. TMS servers with a flipped {-y} are not supported. Use exactly one of source, url_template and earth_engine.

Any image or collection in the Earth Engine catalog (Sentinel-2, Landsat, NAIP, elevation and many more) can be read as XYZ tiles that Earth Engine renders for you: install pip install "mapcv[gee]", log in once with earthengine authenticate, and describe the image under imagery.earth_engine:

imagery:
type: xyz
zoom: 15
earth_engine:
collection: COPERNICUS/S2_SR_HARMONIZED
start: "2024-06-01"
end: "2024-09-01"
max_cloud: 40 # skip cloudy scenes
cloud_score_plus: 0.6 # mask the cloudy pixels left
vis: {bands: [B4, B3, B2], min: 0, max: 3000, gamma: 1.2}
project: my-cloud-project

Or answer the questions of mapcv init (choose gee) or start from mapcv init --template earth-engine; both pick a dataset (Sentinel-2, Landsat 8, NAIP or any asset) with colours that suit it. Without the two cloud settings, a summer median over a cloudy region is mostly cloud. The tiles are 8-bit RGB renderings (vis), not the raw band values; for reflectance values use Sentinel-2 L2A. Requests count against your own Earth Engine quota and project, under Earth Engine’s terms. All fields: configuration reference.

Many commercial tile services put a key in the URL. mapcv never writes url_template into the manifest (it records only custom-xyz:<hostname>) and shows only the host in its output, but your YAML file is yours to protect. Two patterns:

Commit a template with a placeholder, generate the real config locally, and ignore it in git:

mapcv.template.yaml
imagery:
type: xyz
zoom: 17
url_template: "https://tiles.example.com/{z}/{x}/{y}.jpg?key=${TILE_API_KEY}"
Terminal window
export TILE_API_KEY=… # from your secret store
envsubst '$TILE_API_KEY' < mapcv.template.yaml > mapcv.local.yaml
echo mapcv.local.yaml >> .gitignore
mapcv generate mapcv.local.yaml
  • Keep max_connections low (mapcv’s default is 16; mapcv init writes 4). Raise it only if the provider allows it.
  • mapcv retries failures with backoff, honours Retry-After, and identifies itself with a mapcv/<version> user agent that links to the project.
  • Run mapcv plan first and stay within the provider’s quotas.

mapcv reads a Sentinel-2 Level-2A product in the ESA EOPF Zarr format, from a local copy or an anonymous https:// or s3:// URL. You can browse products in the EOPF STAC browser and use the product (.zarr) link as imagery.path, or let mapcv find the least cloudy product that covers the region with imagery.search. The same bands are also available as Cloud Optimized GeoTIFFs from Earth Search with type: stac_cog, which needs no extra.

Band Name Native resolution
b01 Coastal aerosol 60 m
b02 Blue 10 m
b03 Green 10 m
b04 Red 10 m
b05, b06, b07 Red edge 20 m
b08 NIR 10 m
b8a Narrow NIR 20 m
b09 Water vapour 60 m
b11, b12 SWIR 20 m

imagery.bands defaults to all twelve, in the order above. List them in the order you want them stored; [b04, b03, b02, b08] gives red, green, blue, NIR.

imagery.resolution (10, 20 or 60) is the output grid; the EOPF reader resamples bands with a different native resolution onto it, on the product’s own pixel grid. 10 m keeps the four 10 m bands sharp; 20 m is the native grid for red-edge and SWIR; 60 m is mainly for quick tests.

Output patches are bands-first float32 NumPy arrays with decoded reflectance and NaN for no data; use writer.image_format: npy (or tif for georeferenced GeoTIFF patches) and usually edge_strategy: drop and max_empty_ratio: 0.2. Patches keep the product’s UTM CRS (see Coordinates and grids).

Each source reads one product; mapcv does not mosaic several scenes into one. imagery.search picks the least cloudy product that covers the whole region, and scl_mask turns cloud and shadow pixels into pixels without imagery. For a time series, list one source per date on the same tile (several sources). Private buckets and URLs with credentials, query strings or fragments are rejected. When you pick a product yourself, choose one with low cloud cover over your region (the STAC browser shows eo:cloud_cover), and check that the region falls inside the product’s footprint: swath edges contain no-data areas.

The Land cover from Sentinel-2 tutorial walks through a complete example.