ingv-shakemap
How hard did the ground shake here? INGV's earthquakes and ShakeMap grids, read into numbers you can sample at any point in Italy.
grid = await IngvClient().shakemap(47606532)
grid.pga_g(15.55, 38.19) # 0.0029 g in Messina, from the Mw 5.5 of 23 October 2026
docs/make_images.py with this package: 542 × 362 points, peak 0.33 g.
- What is it, in 30 seconds
- Who is it for
- Quick start
- How it works
- Recipes
- The traps we already fell into
- API at a glance
- Data licence and credits
What is it, in 30 seconds
After every significant earthquake INGV publishes a ShakeMap: its estimate
of how strongly the ground shook at every point around the epicentre, built from
the stations that recorded it and from ground-motion models where there are
none. The map is a grid.xml of up to 12 MB of text. This package turns it into
arrays and answers questions about a point.
| What you get | |
|---|---|
| 🌍 | Earthquakes from the INGV FDSN catalogue: id, time (UTC), magnitude and its type, epicentre, depth, place |
| 📈 | The ShakeMap grid of an event: PGA, PGV, MMI, spectral accelerations, Vs30, as numpy arrays |
| 📍 | The value at a point: pga_g(lon, lat) in g, pgv_cm_s, mmi, or any column with value_at |
| 🧾 | The map's own metadata: version, map_status (automatic until reviewed), processing time |
| 🔌 | Your HTTP layer: inject an httpx.AsyncClient, or route every call through your own send |
| 🧪 | Pure parsers: parse_grid and parse_events do no I/O, so you can test them on a file |
Who is it for
- Anyone who needs shaking at a site, not just a magnitude: a building, a dam, a road, the cells of a hazard model. Magnitude says how big the earthquake was; PGA says how hard it hit here.
- Landslide and multi-hazard models: earthquake-triggered landslides scale with ground acceleration, which this gives per point.
- Researchers and journalists who want INGV's own estimate rather than a distance-based guess.
It was extracted from Limen, the landslide, flood and wildfire monitor for Italy, where it reads the ShakeMap of every earthquake above magnitude 3.5 onto a 1 km grid. On three real grids its PGA matches Limen's own parser value for value.
Quick start
pip install ingv-shakemap
import asyncio
from datetime import UTC, datetime
from ingv_shakemap import IngvClient
async def main() -> None:
async with IngvClient() as ingv:
quakes = await ingv.events(
datetime(2026, 10, 23, tzinfo=UTC),
datetime(2026, 10, 24, tzinfo=UTC),
min_magnitude=4.5,
)
for q in quakes:
print(q.id, q.time, q.magnitude, q.magnitude_type, q.place)
grid = await ingv.shakemap(quakes[1].id) # None if INGV published no map
if grid is not None:
print(grid.map_status, grid.pga_g(15.55, 38.19), grid.mmi(15.55, 38.19))
asyncio.run(main())
47611152 2026-10-23 13:55:34.920000+00:00 5.0 Mw Tirreno Meridionale [Mare]
47606532 2026-10-23 10:19:15.470000+00:00 5.5 Mw Tirreno Meridionale [Mare]
47605832 2026-10-23 09:51:38.850000+00:00 4.8 Mw Tirreno Meridionale [Mare]
automatic 0.002939 3.4
How it works
flowchart LR
F["INGV FDSN<br/>webservices.ingv.it"] -->|GeoJSON| E["parse_events<br/>Event"]
E -->|event id| S["shakemap.ingv.it<br/>/data/{id}/current/products/grid.xml"]
S -->|up to 12 MB of XML| G["parse_grid<br/>ShakeGrid"]
G --> P["pga_g · pgv_cm_s · mmi<br/>value_at · column"]
The FDSN event id is the ShakeMap id: the same integer joins the two services.
Recipes
The shaking on many points at once. sample and sample_g take arrays
of positions and answer in one NumPy call: on 100,000 points of a real grid,
10 ms against 280 ms for a loop over pga_g, with the same values. Outside the
grid you get NaN:
pga = grid.sample_g("PGA", lons, lats) # nearest point, in g
pga = grid.sample_g("PGA", lons, lats, method="bilinear") # interpolated
pgv = grid.sample("PGV", lons, lats) # native unit, cm/s
column still gives the whole grid as an (nlat, nlon) array, north row first.
Has the map changed? info reads info.json, a few kB, instead of the
12 MB grid: compare its version with the one you stored and download the grid
only when it grew.
info = await ingv.info(event_id)
if info is not None and info.version != stored_version:
grid = await ingv.shakemap(event_id)
How much of the map is measured. uncertainty is the map's standard
deviation per point (STDPGA in ln g): low near the stations, high where the
map is the ground-motion model alone. stations gives the stations themselves,
what each recorded and what the model predicted there.
unc = await ingv.uncertainty(event_id)
sigma = unc.sample("STDPGA", lons, lats)
stations = await ingv.stations(event_id)
residuals = [s.pga_g / s.predicted_pga_g for s in stations if s.pga_g and s.predicted_pga_g]
Only what changed. events(..., updated_after=last_run) asks the catalogue
for the events created or revised since then (FDSN updatedafter).
Parse in a thread. A big grid takes about a quarter of a second to parse.
IngvClient.shakemap already runs it in asyncio.to_thread; when you parse
yourself, do the same:
xml = await ingv.shakemap_xml(event_id)
grid = await asyncio.to_thread(parse_grid, xml)
Your own HTTP layer. Pass send, and the client uses it for every request
and does no retrying of its own:
async def send(method: str, url: str, **kwargs):
return await my_shared_policy(method, url, **kwargs)
ingv = IngvClient(send=send)
No network at all. parse_grid(Path("grid.xml").read_bytes()) and
parse_events(json.loads(text)) work on files.
The traps we already fell into
- PGA is in percent of g, not in g. The
grid.xmlwritesunits="%g":0.11is 0.0011 g, barely felt.pga_gconverts;value_at("PGA", ...)andcolumn("PGA")return the file's own unit. - The row order is a convention, not a statement. Rows go from the north
edge southwards, longitude fastest.
parse_gridchecks this on the first rows and refuses a file in another order, because a wrong guess would move every value without raising. - The grid step changes from event to event: about 90 m at Campi Flegrei,
0.0083° (about 900 m) for the 23 October 2026 sequence. Never hard-code it;
read
lon_stepandlat_step. - An empty week is a 204, an outage is not. The FDSN service answers
204 No Contentto a window without earthquakes. A 5xx, even with no body, is a failure:eventsraisesIngvErrorfor it and returns[]only for the 204, so an unreachable catalogue never looks like a quiet week. - No ShakeMap is a 404, and it is normal. Small or very recent events
have none:
shakemapreturnsNone. Any other error raises. automaticcan last, and the map keeps changing. The maps of the 23 October 2026 sequence still saidmap_status="automatic"twelve days later, and the Mw 5.5's went through four versions, the last one forty hours after the earthquake. A rule like "stable after 24 hours" misses it: compareinfo(event_id).versioninstead.- The old URL is dead. The
/shake4/prefix answered 404 for every event on 19 October 2026; the grid lives under/data/{id}/current/products/. - No top-level id in the GeoJSON. INGV puts the event id in
properties.eventId, as an integer.parse_eventreads it from there. - Times are UTC without an offset, both ways. The package sends
2026-10-23T10:19:15and gives back aware UTC datetimes. - In
stationlist.jsona missing value is the string"null". 263 of the 469 stations of the Mw 5.5 have"pga": "null", as text.parse_stationsreads it asNone; a plainfloat()would raise, and a check for JSONnullwould let it through.
API at a glance
| Name | What it does |
|---|---|
IngvClient(client=None, *, send=None, retries=3, backoff_s=2.0, timeout_s=60.0) |
async client; retries transport errors, 429 and 5xx |
IngvClient.events(start, end, *, bbox=ITALY, min_magnitude=None, max_depth_km=None, limit=None, updated_after=None) |
earthquakes, newest first |
IngvClient.shakemap(event_id) / .shakemap_xml(event_id) |
the parsed grid, or the raw bytes; None if there is no map |
IngvClient.uncertainty(event_id) |
the map's standard deviation, as a ShakeGrid |
IngvClient.info(event_id) → ShakeMapInfo |
version, status, history of runs, GMPE, stations, peaks |
IngvClient.stations(event_id) → list[Station] |
what each station recorded, and what the model predicted |
IngvClient.product(event_id, name) |
any file of the map's products, raw |
parse_grid(xml) → ShakeGrid |
pure parser; raises only GridError |
ShakeGrid.pga_g / .pgv_cm_s / .mmi / .value_at |
values at a point |
ShakeGrid.sample / .sample_g |
values at many points, nearest or bilinear |
ShakeGrid.column / .bbox |
the whole grid as arrays |
parse_info(json), parse_stations(json) |
pure parsers of info.json and stationlist.json |
ShakeGrid.event, .map_status, .version, .process_time, .fields |
the map's metadata |
parse_events(geojson) / parse_event(feature) → Event |
pure parsers of the FDSN answer |
event_query(...), shakemap_grid_url(id), shakemap_product_url(id, name) |
the requests, without sending them |
IngvError, GridError |
the only exceptions; GridError is also a ValueError |
The full reference is on the documentation site.
Development
uv sync
make check # ruff, mypy --strict, pytest with coverage
make docs # the documentation site, built with --strict
Data licence and credits
- Earthquake catalogue: © Istituto Nazionale di Geofisica e Vulcanologia (INGV). The data and results published on terremoti.ingv.it are under CC BY 4.0: credit INGV.
- ShakeMap: computed and published by INGV at shakemap.ingv.it. That site states no licence of its own (checked on 4 November 2026); credit INGV as for the catalogue.
- ShakeMap is software by the USGS; the
grid.xmlformat is ShakeMap 4's.
This package is not affiliated with INGV or the USGS. Code under Apache-2.0.
Metadata
Release files for ingv-shakemap 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ingv_shakemap-0.2.0.tar.gz | 25.9 kB | With provenance | Details for ingv_shakemap-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ingv_shakemap-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for ingv_shakemap-0.2.0-py3-none-any.whl |
Total release size: 50.4 kB
Release files / ingv_shakemap-0.2.0.tar.gz
| Download URL | ingv_shakemap-0.2.0.tar.gz |
|---|---|
| Size | 25.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d90a4449a91a49660c7cf3b251ee5fe38cd6c1c3d3f6efe65e8f70a7e40e2195
|
|
BLAKE2b-256 checksum How to use checksums |
8ea2f9e825a265ec4279a0ba59a31e12043974e9f8ab4755e25ecbda6bd8adf4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logRelease files / ingv_shakemap-0.2.0-py3-none-any.whl
| Download URL | ingv_shakemap-0.2.0-py3-none-any.whl |
|---|---|
| Size | 24.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0242c0ba97ab2bb1a74e19cff972570b45af5ab40761f96cc44a9cc8557e1ae4
|
|
BLAKE2b-256 checksum How to use checksums |
00dca8f989e8dd851a66dab37959fcc13f51dc3f818df64d36d5bfd0db331c8e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logSecurity for ingv-shakemap 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for ingv_shakemap-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.