API reference¶
At a glance¶
| Name | What it does |
|---|---|
IdroGEO(client=None, *, retries=3, backoff_s=2.0) |
The async client; opens (and closes) its own HTTP client if not given one. |
.fetch(dataset, directory=None, *, unpack=True) |
The dataset ready to read: the unpacked folder, or the file. |
.download(dataset, directory=None) |
Download(path, changed): the file, and whether this call changed it. |
.summary(summary, level, code=None) |
"iffi" counts or "pir" indicators; without code, the list of that level. |
.dictionaries() / .dictionary(name) |
The names of the code lists / one list. |
.landslide(id_frana) |
The full record of one validated landslide. |
.iter_landslides(filters, modified_since=, page_size=1000, select=, order="id_frana") |
Every matching IFFI landslide, page after page; only the changed ones with modified_since. |
.iter_events(filters, modified_since=, page_size=1000, select=, order="uid") |
Every matching landslide event, page after page. |
.sites(filters, modified_since=, select=) |
The monitoring sites (1,063). |
.site(uid) / .stations(uid) |
One site / its stations, instruments and sensors. |
.webcams(filters) |
The webcams pointed at landslides. |
.iter_landslides_in(region=, province=, municipality=, filters=, select=) |
Every landslide of an area, checked on the record's own area fields. |
.search_landslides(filters, limit=, offset=, select=, order=) |
One page of IFFI landslides. |
.search_events(filters, limit=, offset=, select=, order=) |
One page of validated landslide events. |
.municipalities(summary="pir", directory=None, *, concurrency=8, max_age=None, region=None, province=None) |
The summary of all 7,899 municipalities (or those of a region or province), saved one JSON per municipality. |
.summaries(summary, level, directory=None, *, concurrency=8, max_age=None) |
The same for every region, province or municipality. |
opendata(layer, area="ispra", fmt="shapefile") |
An open-data Dataset (URL + file name). |
mosaic("pai_frane" \| "idraulica") |
A national hazard mosaic. |
sensor_data() |
The monitoring network readings. |
REGIONS / PROVINCES / region_area(cod_reg) |
Area file names by ISTAT code. |
LANDSLIDE_LAYERS / EVENT_LAYERS / MOSAICS |
What there is. |
default_directory() |
Where downloads go when no directory is given. |
.hazard_at(lat, lon, lithology=False) |
PointHazard: landslide class, flood scenarios, municipality, slope. |
.landslides_near(lat, lon, radius_m=500, limit=200) |
IFFI landslides around a point, with distance_m. |
.lookup(kind, text, limit=10) |
Search geological units, flights, bibliography, institutions, funding. |
IdrogeoError |
IdroGEO answered, but not with what was asked for. |
.labels(name, lang) / .code_labels(fields, lang) / decode(record, labels) |
Code lists, and records with <field>_label added. |
ispra_idrogeo.geo.read(path) |
A downloaded file or folder as a GeoDataFrame (extra [geo]). |
ispra_idrogeo.geo.frame(rows, geometry="point") |
API rows as a GeoDataFrame, in their own CRS. |
ispra_idrogeo.geo.add_labels(frame, labels) |
<column>_label columns for coded columns. |
ispra_idrogeo.warehouse.Warehouse(path=None, *, read_only=False) |
A local DuckDB copy of the layers (extra [warehouse]). |
.sync(idrogeo, layers, area="ispra") |
Download (if changed) and load the layers that changed. |
.query(sql, params, limit=1000) / .schema() |
One read-only SELECT / the tables and columns. |
.hazard_at(lat, lon) / .landslides_near(lat, lon, radius_m) / .landslides_in(...) |
The same questions as the client's, answered offline. |
.download / .fetch(..., progress=callback) |
callback(done, total) after each chunk, for your own progress bar. |
ispra_idrogeo.watch.changes(idrogeo, kind, mark) |
One round of idrogeo watch: what changed since a Mark. |
idrogeo-mcp |
An MCP server with the same data as 16 tools, 3 resources and 2 prompts (extra [mcp]). |
idrogeo <command> |
The same from the shell: fetch, summary, municipalities, landslides, events, sites, webcams, landslide, labels, dictionaries, lookup, hazard, near, watch. |
Lower level, for your own HTTP layer: summary_path, search_params,
level_codes, since_filter, parse / parse_list, read_validators / write_validators,
read_json / write_json, extract.
The client¶
IdroGEO ¶
Open-data downloads into a persistent folder, and the read-only API.
download
async
¶
download(
dataset: Dataset,
directory: Path | None = None,
*,
progress: Progress | None = None,
) -> Download
The file in directory, downloaded only if missing or changed upstream.
directory defaults to :func:~ispra_idrogeo.cache.default_directory.
A 404 raises :class:IdrogeoError: the layer or area does not exist.
progress(done, total) is called after each chunk of a download,
total being None when the server does not say the size.
fetch
async
¶
fetch(
dataset: Dataset,
directory: Path | None = None,
*,
unpack: bool = True,
progress: Progress | None = None,
) -> Path
The dataset ready to read: the folder its ZIP unpacks to, or the file.
The folder is <directory>/<dataset.stem> and is unpacked again
only when the download changed (or the folder is missing).
progress is passed to :meth:download.
summary
async
¶
IFFI counts ("iffi") or hazard indicators ("pir"), or a level's list.
summaries
async
¶
summaries(
summary: Summary,
level: Level,
directory: Path | None = None,
*,
concurrency: int = 8,
max_age: timedelta | None = None,
region: int | None = None,
province: int | None = None,
) -> list[dict[str, Any]]
The summary of every region, province or municipality, kept on disk.
One call lists the level, then one call per area (7,899 for
"comuni", ~17 MB) asks for its summary, concurrency at a
time. Each answer is saved in <directory>/<summary>_<level>/<code>.json
and read back instead of asked again until it is older than
max_age (never, by default), so an interrupted run resumes where
it stopped. The result follows the order of the API's list.
region and province (ISTAT codes) keep only the areas inside
them, so the 550 municipalities of Campania cost 550 calls, not 7,899.
municipalities
async
¶
municipalities(
summary: Summary = "pir",
directory: Path | None = None,
*,
concurrency: int = 8,
max_age: timedelta | None = None,
region: int | None = None,
province: int | None = None,
) -> list[dict[str, Any]]
Hazard and risk indicators (or IFFI counts) of every municipality.
:meth:summaries for "comuni": see there for the folder,
max_age, region and province.
labels
async
¶
One dictionary as {code: label} in lang (Italian where untranslated).
code_labels
async
¶
code_labels(
fields: Iterable[str] | None = None,
lang: Language = "en",
) -> dict[str, dict[str, str]]
{field: {code: label}} for :func:~ispra_idrogeo.codes.decode.
fields defaults to every field in
:data:~ispra_idrogeo.codes.CODE_DICTIONARIES; each dictionary is
asked for once.
hazard_at
async
¶
PAI landslide hazard, flood scenarios, municipality and slope of a point.
One request to IdroGEO's public map service, about a second;
latitude and longitude in degrees (WGS 84). lithology=True adds
the rock type, but the map service takes ~16 s more to answer it. A
point outside Italy, or at sea, answers None everywhere.
landslides_near
async
¶
landslides_near(
lat: float,
lon: float,
radius_m: float = 500,
*,
limit: int = 200,
) -> list[dict[str, Any]]
The IFFI landslides within radius_m of a point, nearest first.
Each row has the landslide's fields, distance_m (0 when the point
is inside it) and shapes (polygon, line, point…). One request;
limit caps the features the map service returns (1,000 at most),
so a dense area may need a smaller radius.
lookup
async
¶
Search a reference list by text: geological units, flights, bibliography…
kind is one of :data:~ispra_idrogeo.api.LOOKUPS. Most lists
answer objects; "ordini" and "finanziamenti" answer names.
landslide
async
¶
The full record of one validated IFFI landslide (~140 fields).
Causes, activations, state and velocity, damage, interventions,
precursors: many fields are often empty. IdrogeoError if the id
is unknown or the landslide is not public (IdroGEO answers 401, not
404, to both).
iter_landslides ¶
iter_landslides(
filters: dict[str, Any] | None = None,
*,
modified_since: datetime | None = None,
page_size: int = 1000,
select: list[str] | None = None,
order: str = "id_frana",
) -> AsyncIterator[dict[str, Any]]
Every IFFI landslide matching filters, page after page.
Filters take plain values ({"tipo_movimento": 6}). With
modified_since only the landslides changed since then: the
incremental update of a local copy (select=["*"] brings the
geometry too). A landslide that is removed does not show up as
changed, it just stops being listed.
Pages are ordered by order so that they do not overlap; IdroGEO
changes daily, so a record edited while you iterate may be seen
twice or missed.
iter_landslides_in
async
¶
iter_landslides_in(
*,
region: int | None = None,
province: int | None = None,
municipality: int | None = None,
filters: dict[str, Any] | None = None,
modified_since: datetime | None = None,
select: list[str] | None = None,
page_size: int = 1000000,
) -> AsyncIterator[dict[str, Any]]
Every IFFI landslide in a region, province or municipality (ISTAT codes).
The search has no reliable area filter (regione_2019 is empty
for 18% of the landslides), but every record knows its regione,
provincia and comune: the other filters run on the server and
the area is checked here. Without them it reads the whole inventory,
about 15 s with the default fields, in one page by default because
deep offsets are slow on IdroGEO (keyset paging is not possible:
id_frana takes no gt.). Keep select short, and use
opendata(layer, area) for full records with geometry.
iter_events ¶
iter_events(
filters: dict[str, Any] | None = None,
*,
modified_since: datetime | None = None,
page_size: int = 1000,
select: list[str] | None = None,
order: str = "uid",
) -> AsyncIterator[dict[str, Any]]
Every validated landslide event matching filters, page after page.
Filters take operators ({"regione": "eq.16"}); see
:meth:iter_landslides for modified_since and the order.
sites
async
¶
sites(
filters: dict[str, Any] | None = None,
*,
modified_since: datetime | None = None,
select: list[str] | None = None,
) -> list[dict[str, Any]]
The landslide monitoring sites matching filters (1,063 in all).
Filters take operators ({"stato_monitoraggio": "eq.1"} for the
active ones); modified_since keeps the sites changed since then.
point is longitude and latitude (WGS 84); id_frane_correlati
links a site to its IFFI landslides.
site
async
¶
One monitoring site by its uid ("SIPIE001"), not by its id.
stations
async
¶
The stations of a monitoring site, with their instruments and sensors.
webcams
async
¶
The webcams pointed at landslides (14, all in Lombardy in 2026).
Filters take operators ({"regione": "eq.3"}). Mind geom: its
coordinates are EPSG:3857 metres, not longitude and latitude.
search_landslides
async
¶
search_landslides(
filters: dict[str, Any] | None = None,
*,
limit: int = 100,
offset: int = 0,
select: list[str] | None = None,
order: str | None = None,
) -> list[dict[str, Any]]
One page of IFFI landslides matching filters.
search_events
async
¶
search_events(
filters: dict[str, Any] | None = None,
*,
limit: int = 100,
offset: int = 0,
select: list[str] | None = None,
order: str | None = None,
) -> list[dict[str, Any]]
One page of validated landslide events matching filters.
Download
dataclass
¶
A file on disk, and whether this call changed it.
Sources: what there is to download¶
sources ¶
Where IdroGEO publishes its open data, as pure URL builders.
The open-data downloads are the ones the IdroGEO web app links to under
"Download massivo": /opendata/<group>/<layer>_<area>_opendata.zip, a
shapefile, or ….json.zip for GeoJSON. <area> is ispra for the
whole of Italy, or the file name of a region or province (:data:REGIONS,
:data:PROVINCES). ISPRA regenerates them every day, with an ETag and a
Last-Modified, so a conditional GET tells whether anything changed.
The older paths /opendata/iffi/<region>/iffi_<region>_<layer>.zip and
/opendata/iffi/dizionari/*.json answer 404 since 2026: the dictionaries
are now served by the API (:mod:ispra_idrogeo.api).
Coordinates in the files are in EPSG:32632 (WGS 84 / UTM zone 32N).
Search filters¶
filters ¶
Search filters without the technical manual.
Each IdroGEO search reads its JSON filter its own way, and the differences fail with cryptic messages. These builders know them (checked on the live service, November 2026):
- landslides: most fields take operators (
"eq.100","in.(5,6)"), buttipo_movimentotakes a plain number and no list. There is no reliable area filter:regioneis a relation the search refuses, andregione_2019is empty for 18% of the landslides (most of Campania). For an area, use :meth:~ispra_idrogeo.IdroGEO.iter_landslides_in. - events and sites: operators on every field, areas included; a
range of dates is an
"and"of two conditions.
Every builder returns a plain dict for the filters argument of
iter_landslides, iter_events, sites and webcams; combine it
with modified_since there.
landslide_filter ¶
landslide_filter(
*,
movement: int | None = None,
activity: Codes | None = None,
district: Codes | None = None,
) -> dict[str, Any]
Landslides by type of movement, state of activity, river basin district.
movement is one tipo_movimento code (the search takes no list);
activity the stato_attivita_1liv codes (100 active, reactivated
or suspended; 200 dormant; 300 stabilised; 400 relict); district the
autorita_distretto codes.
event_filter ¶
event_filter(
*,
region: Codes | None = None,
province: Codes | None = None,
municipality: Codes | None = None,
movement: Codes | None = None,
start: date | None = None,
end: date | None = None,
) -> dict[str, Any]
Landslide events by area (ISTAT codes), type of movement, date.
start is included and end excluded, like a Python range:
start=date(2023, 1, 1), end=date(2024, 1, 1) is the year 2023.
site_filter ¶
site_filter(
*,
region: Codes | None = None,
province: Codes | None = None,
municipality: Codes | None = None,
active: bool | None = None,
) -> dict[str, Any]
Monitoring sites by area (ISTAT codes) and whether active.
Webcams take the area part too; they have no active.
Points: the public map service¶
wms ¶
What IdroGEO's public map service says about a point.
IdroGEO's GeoServer publishes its layers over WMS to anyone (the WFS asks
for a login). A GetFeatureInfo request asks what is under a pixel, and
with a tiny bounding box around a point that pixel is the point: one call
answers the PAI landslide hazard class, the flood scenarios, the
municipality, the lithology and the slope there. With a box of a few
hundred metres and a wide buffer, the same request lists the IFFI
landslides around the point, with their geometry.
Pure functions: :func:point_params / :func:area_params build the
request, :func:parse_hazard and :func:nearby read the answer.
PointHazard
dataclass
¶
The hazard and context of one point; None where a layer has nothing.
landslide is the highest PAI class there ("P4" very high to
"P1" moderate, "AA" attention area). flood is the most
frequent flood scenario that reaches the point ("P3" high
probability, "P2" medium, "P1" low); flood_scenarios all of
them. slope_deg is the slope in degrees. lithology is filled
only when asked for.
point_params ¶
The GetFeatureInfo query asking :data:HAZARD_LAYERS about a point.
lithology adds :data:LITHOLOGY_LAYER, much slower to answer.
area_params ¶
The query listing the IFFI landslides within a square around a point.
parse_hazard ¶
Read the answer to :func:point_params.
Each layer is told apart by its fields, not by the feature ids, which the slope layer leaves empty.
nearby ¶
nearby(
lat: float,
lon: float,
features: Iterable[Mapping[str, Any]],
radius_m: float,
) -> list[dict[str, Any]]
The landslides of an :func:area_params answer within radius_m, nearest first.
One row per id_frana: its fields, distance_m (0 inside a polygon)
and shapes, the kinds of shape it is mapped as.
distance_m ¶
Metres from a point to a GeoJSON geometry in lon/lat; 0 inside a polygon.
A local flat projection around the point: within a few kilometres the error is far below the precision of the maps.
Watching for changes¶
watch ¶
What changed in IdroGEO since last time: the engine of idrogeo watch.
For each kind of record (landslides, events, monitoring sites) a
:class:Mark remembers the latest modified seen and the records
modified at that very instant. The next :func:changes asks for
modified >= since and drops those records, which the "at or after"
filter would send again: every change comes out once.
The marks live in a small JSON file (:func:load_marks, :func:save_marks),
written atomically, so a cron job or a loop can stop and resume anywhere.
Mark
dataclass
¶
Where one kind's watch stands: the latest modified and who had it.
load_marks ¶
The marks saved in path; none if the file is missing or unreadable.
save_marks ¶
Write the marks to path, atomically.
changes
async
¶
The records of kind changed since mark, oldest first, and the next mark.
Code lists¶
codes ¶
Codes in the data, and the words they stand for.
IFFI records, the open-data files and the monitoring sites store codes
(tipo_movimento = 4); the API's dictionaries (/api/dizionari) say
what each code means, in Italian, English, French and German.
:data:CODE_DICTIONARIES says which dictionary decodes which field, and
:func:decode adds a <field>_label next to each coded field.
parse_labels ¶
A dictionary's entries as {code: label}; Italian where a translation is missing.
decode ¶
A copy of record with <field>_label next to each field in labels.
labels maps a field to its {code: label} (see
:meth:~ispra_idrogeo.IdroGEO.code_labels). A list of codes becomes a
list of labels; an empty value stays None, and a code missing from
the dictionary becomes None too rather than a guess.
GeoDataFrames (extra [geo])¶
geo ¶
IdroGEO data as GeoDataFrames: pip install "ispra-idrogeo[geo]".
:func:read opens what :meth:~ispra_idrogeo.IdroGEO.fetch unpacked;
:func:frame turns API rows (landslides, events, sites, webcams) into a
frame in the right coordinate system; :func:add_labels adds the words of
the coded columns. frame.to_parquet(path) writes GeoParquet.
read ¶
A file or an unpacked folder (from fetch) as a GeoDataFrame.
A folder must hold exactly one shapefile or GeoJSON. kwargs go to
:func:geopandas.read_file (bbox=, columns=, rows=…).
frame ¶
API rows as a GeoDataFrame, geometry from geometry.
"point" is [lon, lat] (EPSG:4326): landslides, sites. The
GeoJSON fields ("geom", "geom_polygon", "geom_linestring")
carry their own CRS: EPSG:32632 for landslides, EPSG:3857 for webcams.
Rows without that geometry get an empty one rather than being dropped.
add_labels ¶
data with a <column>_label next to each coded column in labels.
labels comes from :meth:~ispra_idrogeo.IdroGEO.code_labels; lists of
codes become lists of labels, unknown codes None.
The MCP server (extra [mcp])¶
mcp_server ¶
An MCP server over IdroGEO, for AI agents: pip install "ispra-idrogeo[mcp]".
idrogeo-mcp # stdio: Claude Desktop, IDEs
idrogeo-mcp --transport streamable-http --host 0.0.0.0 --port 8767
Over HTTP the MCP endpoint is /mcp and GET /health answers
{"status": "ok"} for container healthchecks. Every option also reads
an environment variable (IDROGEO_MCP_TRANSPORT, IDROGEO_MCP_HOST,
IDROGEO_MCP_PORT, IDROGEO_MCP_ALLOWED_HOSTS), so a compose file
needs no command line.
Every tool reads the public API (no account), except download_dataset,
which writes the file to the local download folder and returns its path.
Codes come back with their English labels, and long answers are cut at
limit rows, so an agent never receives 700,000 landslides at once.
server ¶
The MCP server; idrogeo defaults to a client it opens and closes itself.
warehouse is the DuckDB file of idrogeo sync (default: in the
download folder) that warehouse_schema and warehouse_sql read.
Low level: requests, parsers, cache¶
api ¶
The IdroGEO REST API (/api), as pure request builders and parsers.
Reading needs no account; only writes and a few detail endpoints ask for a login. Summaries and hazard indicators exist for Italy, macro-regions, regions, provinces and municipalities (ISTAT codes); the dictionaries are the code lists the files use (movement types, causes, damage, …).
Searches take a JSON object of filters whose syntax is described in ISPRA's
technical manual; an empty object matches everything, so always page with
limit and offset.
summary_path ¶
iffi/regioni/16; without code, the list of that level (italia takes none).
level_codes ¶
The ISTAT codes in the list of a level, in the order the API gives them.
within ¶
within(
items: list[dict[str, Any]],
*,
region: int | None = None,
province: int | None = None,
) -> list[dict[str, Any]]
The items of a level's list inside a region and/or province (ISTAT codes).
Each item of regioni, province and comuni carries cod_reg
and cod_prov; None keeps everything.
since_filter ¶
filters plus "modified at or after since", for incremental updates.
since must carry a time zone (datetime.now(UTC)): IdroGEO stores
modified in UTC, and a naive time would be read in the server's.
lookup_params ¶
The query string of a text search in a reference list.
search_params ¶
search_params(
*,
limit: int,
offset: int = 0,
select: list[str] | None = None,
order: str | None = None,
) -> dict[str, str]
The query string of a search.
parse ¶
The decoded JSON of a 200; :class:IdrogeoError with the API's message otherwise.
parse_list ¶
A JSON list of objects (searches, level lists).
cache ¶
The persistent download folder, and what it remembers about each file.
Next to every downloaded file sits <file>.meta.json with the URL and the
ETag / Last-Modified the server sent. They become the
If-None-Match / If-Modified-Since of the next request, so a file
that has not changed is answered 304 and costs no download.
Validators
dataclass
¶
What the server said about the copy on disk.
default_directory ¶
Where downloads go when no directory is given.
IDROGEO_DATA_DIR if set, else ispra-idrogeo in the user cache
($XDG_CACHE_HOME, or ~/.cache). Use a folder that survives
restarts: the national files weigh hundreds of MB.
read_validators ¶
The validators of a file on disk, or None if it cannot be revalidated.
A file without metadata, with unreadable metadata, or downloaded from another URL is downloaded again rather than trusted.
read_json ¶
A saved API answer, or None if missing, unreadable or older than max_age.
write_json ¶
Save data as JSON, atomically: a reader finds the old file or the new one.
extract ¶
Unpack a ZIP into folder, replacing what was there; folder.
The new content is unpacked beside the old one and swapped in only when
complete, so a reader never finds half a shapefile. Members that would
land outside folder raise :class:IdrogeoError.