Skip to content

Imagery and land

Markdown

Statistics of a raster per polygon

A raster summarised for each polygon of a layer: population, land cover shares, or elevation and slope per district, parcel or catchment.

RESTPOST https://mcp.terra-lab.ai/v1/tools/zonal_statistics
MCP toolzonal_statistics on https://mcp.terra-lab.ai/mcp
GroupImagery and land

Request

curl -X POST https://mcp.terra-lab.ai/v1/tools/zonal_statistics \
  -H "Authorization: Bearer $TERRALAB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dataset":"population","url":"https://mcp.terra-lab.ai/dl/geojson/TOKEN/divisions.geojson","name_field":"name"}'
import os
import requests

response = requests.post(
    "https://mcp.terra-lab.ai/v1/tools/zonal_statistics",
    headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"},
    json={
        "dataset": "population",
        "url": "https://mcp.terra-lab.ai/dl/geojson/TOKEN/divisions.geojson",
        "name_field": "name",
    },
    timeout=120,
)
response.raise_for_status()
print(response.json()["structuredContent"])
const response = await fetch('https://mcp.terra-lab.ai/v1/tools/zonal_statistics', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "dataset": "population",
    "url": "https://mcp.terra-lab.ai/dl/geojson/TOKEN/divisions.geojson",
    "name_field": "name"
  }),
});
const data = await response.json();
console.log(data.structuredContent);

Other valid request bodies:

{"dataset":"land_cover","geojson":{"type":"Polygon","coordinates":[[[3.86,43.6],[3.9,43.6],[3.9,43.62],[3.86,43.62],[3.86,43.6]]]}}

Parameters

A JSON object in the request body; in an MCP client, the arguments of the tool call.

NameTypeRequiredDescription
datasetstringYes"population", "land_cover", "elevation", or a raster id from find_datasets such as "ghs_pop", "worldpop_1km", "esa_worldcover", "clc2018_cog", "ghs_built_h". Up to 80 characters.
urlstringNoThe zones: a public http(s) link to polygons, or a TerraLab download link from an earlier answer.
fileobjectNoThe zones as a file uploaded in the conversation (GeoJSON, KML, KMZ, GPX, zipped Shapefile, CSV or WKT).
file.download_urlstringYes
file.file_idstringYes
file.file_namestringNo
file.mime_typestringNo
geojsonobjectNoThe zones as a GeoJSON FeatureCollection, Feature or polygon in WGS 84 (longitude, latitude).
name_fieldstringNoThe attribute that names each zone in the rows. Left out: name, label or id when present, else the zones are numbered. Up to 80 characters.
variablesobjectNoValues for the dataset's variables, as find_datasets lists them: {"YEAR": "2015"}, {"RETURN_PERIOD": "100"}.

Response

structuredContent holds the result, content the same result as text for a model, and isError is true when the tool refused the request or failed; then structuredContent.code, message and hint say what to change.

{
  "structuredContent": {
    "sources": [{ "name": "...", "licence": "...", "url": "..." }],
    "downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }]
  },
  "content": [{ "type": "text", "text": "..." }],
  "isError": false
}

Every result lists its sources with their licence. File results come as downloads: each link builds its file when opened.

Errors

A missing key answers 401, an empty balance 402, too many calls 429. A call that fails or finds nothing is not counted. See Errors and rate limits.

Description for agents

The text the server gives an agent to decide when to call this tool:

Use this when the user wants zonal statistics, a raster summarised for each polygon of a layer (per district, parcel, catchment, buffer or isochrone): "population of each commune", "land cover shares per parcel", "mean elevation and slope of each catchment", "flood depth per neighbourhood". Give the zones as url (a public link or a TerraLab download link, such as the divisions of extract_open_data or a buffer of process_geometry), file or geojson, and dataset: "population" (GHS-POP 2025, 100 m), "land_cover" (ESA WorldCover, 10 m), "elevation" (Copernicus DEM, with mean slope) or any raster id from find_datasets. Returns one row per zone (population and density; share and hectares per class; or min, max, mean, sum) and the zones with these as attributes (GeoPackage, GeoJSON, CSV), never the pixels. A pixel counts when its centre lies inside the zone, on the dataset's own grid. Limits: 5,000 zones, 16 million pixels over their extent (8 million for 64-bit data such as population); a refusal gives the numbers. Not for one area (estimate_population, extract_dataset with summary), a Sentinel-2 index per zone (get_satellite_index with zones_url) or counting features per polygon (process_geometry). Download links build their file when opened; no file is saved. The user opens the file in QGIS or any GIS: this server cannot load it into the user's own QGIS or any app on their computer. Never pass these links to open_gis_file. TerraLab reads the zones to answer and keeps no copy.

Updated 2026-11-15