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 |
XYZ tiles
Section titled “XYZ tiles”Built-in sources
Section titled “Built-in sources”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.
Zoom and pixel size
Section titled “Zoom and pixel size”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.
Your own tile URL
Section titled “Your own tile URL”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: 4The 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.
Google Earth Engine
Section titled “Google 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-projectOr 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.
Keep API keys out of YAML
Section titled “Keep API keys out of YAML”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:
imagery: type: xyz zoom: 17 url_template: "https://tiles.example.com/{z}/{x}/{y}.jpg?key=${TILE_API_KEY}"export TILE_API_KEY=… # from your secret storeenvsubst '$TILE_API_KEY' < mapcv.template.yaml > mapcv.local.yamlecho mapcv.local.yaml >> .gitignoremapcv generate mapcv.local.yamlLoad the committed YAML and inject the URL at run time:
import osfrom pathlib import Path
import yamlfrom mapcv import MapcvConfig, generate, plan
raw = yaml.safe_load(Path("mapcv.yaml").read_text())raw["imagery"]["url_template"] = ( "https://tiles.example.com/{z}/{x}/{y}.jpg?key=" + os.environ["TILE_API_KEY"])config = MapcvConfig.model_validate(raw)print(plan(config).tiles)generate(config)Be a polite client
Section titled “Be a polite client”- Keep
max_connectionslow (mapcv’s default is16;mapcv initwrites4). Raise it only if the provider allows it. - mapcv retries failures with backoff, honours
Retry-After, and identifies itself with amapcv/<version>user agent that links to the project. - Run
mapcv planfirst and stay within the provider’s quotas.
Sentinel-2 L2A
Section titled “Sentinel-2 L2A”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.
Resolution
Section titled “Resolution”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).
Limits
Section titled “Limits”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.