Skip to content

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.

aclose async

aclose() -> None

Close the HTTP client if this instance opened it.

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

summary(
    summary: Summary, level: Level, code: int | None = None
) -> Any

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.

dictionaries async

dictionaries() -> list[str]

The names of the code lists.

dictionary async

dictionary(name: str) -> Any

One code list (IdrogeoError if it does not exist).

labels async

labels(name: str, lang: Language = 'en') -> dict[str, str]

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

hazard_at(
    lat: float, lon: float, *, lithology: bool = False
) -> PointHazard

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

lookup(
    kind: Lookup, text: str, *, limit: int = 10
) -> list[Any]

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

landslide(id_frana: str) -> dict[str, Any]

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

site(uid: str) -> dict[str, Any]

One monitoring site by its uid ("SIPIE001"), not by its id.

stations async

stations(uid: str) -> list[dict[str, Any]]

The stations of a monitoring site, with their instruments and sensors.

webcams async

webcams(
    filters: dict[str, Any] | None = None,
) -> list[dict[str, Any]]

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).

Dataset dataclass

One downloadable file: its URL and the name it is saved under.

stem property

stem: str

The file name without its archive suffix: also the extraction folder.

opendata

opendata(
    layer: Layer,
    area: str = ITALY,
    fmt: Format = "shapefile",
) -> Dataset

An open-data download: a layer, for Italy (default) or one area.

ValueError for an unknown layer or area.

mosaic

mosaic(name: Mosaic) -> Dataset

A national hazard mosaic (shapefile in a ZIP).

sensor_data

sensor_data() -> Dataset

The monitoring network's readings, one bzip2-compressed CSV.

region_area

region_area(cod_reg: int) -> str

The file name of a region from its ISTAT code; KeyError for 4 or unknown.

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)"), but tipo_movimento takes a plain number and no list. There is no reliable area filter: regione is a relation the search refuses, and regione_2019 is 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

point_params(
    lat: float, lon: float, *, lithology: bool = False
) -> dict[str, str]

The GetFeatureInfo query asking :data:HAZARD_LAYERS about a point.

lithology adds :data:LITHOLOGY_LAYER, much slower to answer.

area_params

area_params(
    lat: float, lon: float, radius_m: float, limit: int
) -> dict[str, str]

The query listing the IFFI landslides within a square around a point.

parse_hazard

parse_hazard(
    lat: float,
    lon: float,
    features: Iterable[Mapping[str, Any]],
) -> PointHazard

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

distance_m(
    lat: float, lon: float, geometry: Mapping[str, Any]
) -> float

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

load_marks(path: Path) -> dict[str, Mark]

The marks saved in path; none if the file is missing or unreadable.

save_marks

save_marks(path: Path, marks: dict[str, Mark]) -> None

Write the marks to path, atomically.

changes async

changes(
    idrogeo: IdroGEO, kind: Kind, mark: Mark
) -> tuple[list[dict[str, Any]], Mark]

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

parse_labels(
    entries: Any, lang: Language = "en"
) -> dict[str, str]

A dictionary's entries as {code: label}; Italian where a translation is missing.

decode

decode(
    record: Mapping[str, Any],
    labels: Mapping[str, Mapping[str, str]],
) -> dict[str, Any]

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

read(path: Path, **kwargs: Any) -> Any

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

frame(
    rows: Iterable[Mapping[str, Any]],
    geometry: Geometry = "point",
) -> Any

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

add_labels(
    data: Any, labels: Mapping[str, Mapping[str, str]]
) -> Any

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

server(
    idrogeo: IdroGEO | None = None,
    *,
    warehouse: Path | None = None,
) -> MCPServer[Any]

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.

main

main(argv: Sequence[str] | None = None) -> None

Entry point of idrogeo-mcp.

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

summary_path(
    summary: Summary, level: Level, code: int | None = None
) -> str

iffi/regioni/16; without code, the list of that level (italia takes none).

level_codes

level_codes(
    level: Level, items: list[dict[str, Any]]
) -> list[int]

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

since_filter(
    filters: dict[str, Any] | None, since: datetime | None
) -> dict[str, Any]

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

lookup_params(
    kind: str, text: str, limit: int
) -> dict[str, str]

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

parse(status_code: int, body: bytes | str) -> Any

The decoded JSON of a 200; :class:IdrogeoError with the API's message otherwise.

parse_list

parse_list(
    status_code: int, body: bytes | str
) -> list[dict[str, Any]]

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.

headers

headers() -> dict[str, str]

The conditional-request headers for the next download.

default_directory

default_directory() -> Path

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

read_validators(path: Path, url: str) -> Validators | None

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

read_json(
    path: Path, max_age: timedelta | None = None
) -> Any | None

A saved API answer, or None if missing, unreadable or older than max_age.

write_json

write_json(path: Path, data: Any) -> None

Save data as JSON, atomically: a reader finds the old file or the new one.

extract

extract(archive: Path, folder: Path) -> Path

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.

errors

The one exception this package raises for answers it cannot use.

IdrogeoError

Bases: Exception

IdroGEO answered, but not with what was asked for.