Skip to main content

Persistent, cross-version building IDs for PLATEAU. gml:id changes every release; we recognise the same physical building across versions and mint a surrogate ID that never changes — honestly, with a match confidence.

Project description

Yodo Labs

plateau-id-resolve

Persistent, cross-version building IDs for Project PLATEAU.

Recognises the same physical building across PLATEAU releases and mints a surrogate_id that never changes — so records pinned to it survive the gml:id churn.

🇯🇵 日本語版 README

An open-source project by Yodo Labs · Contact pan@yodolabs.jp

PyPI CI License: MIT Python Yodo Labs

gml:id changes every PLATEAU release; the surrogate_id never does. The same physical building is recognised across versions by geometry, with a match confidence, and cross-version new/demolished/split/merged/modified changes are tracked as honest events.

"Your records are only as durable as the IDs they're pinned to."


The problem

PLATEAU's documentation states plainly that gml:id is not guaranteed to persist across releases: the same physical building can carry a completely different gml:id from one year's CityGML to the next. The 建物ID (市区町村コード-bldg-連番, e.g. 01100-bldg-344507) is more stable in format but is also not guaranteed persistent, and is assigned per-municipality in sequence rather than as a global GUID.

So every system that pins records — damage state, inspection history, hazards — to a building id silently breaks the moment the map updates: the ids drift and the history no longer lines up with the building it describes. This is the #1 shared technical blocker of the whole Physical AI Record Layer stack, and nobody (MLIT included) has solved it cleanly.

What this does

plateau-id-resolve recognises the same physical building across releases by geometry (footprint overlap, centroid, area, topology) and mints a permanent surrogate_id that never changes. It keeps the full history surrogate ↔ {year: gml_id, 建物ID}, detects cross-version 新築 / 滅失 / 分割 / 合併 / 増改築, and bridges 建物ID ↔ 不動産ID — always with a match confidence, and never pretending an ambiguous split/merge/rebuild is a clean 1:1.

This is a foundation layer (role in the stack). Everyone who uses PLATEAU across years hits this pit — so it is independently useful on its own.

Install

pip install plateau-id-resolve
# optional extras
pip install 'plateau-id-resolve[geoparquet]'   # read plateau-bridge GeoParquet
pip install 'plateau-id-resolve[http]'         # the HTTP API server

Core depends only on shapely. The registry is SQLite (stdlib) — one file, no server.

Quickstart (library)

from plateau_id_resolve import Resolver, read_footprints

r = Resolver("city.db")                       # a SQLite file (or ":memory:")
r.ingest_version("2020", read_footprints("city_2020.geojson"))
r.ingest_version("2023", read_footprints("city_2023.geojson"))   # gml:ids all changed

# A record pinned to the surrogate survives the gml:id churn:
res = r.resolve({"gml_id": "bldg_2023_x99"})
print(res.surrogate_id)        # sg_01jh...  (stable, permanent)
print(res.match_confidence)    # 1.0  (exact id lookup)
for h in res.history:
    print(h.dataset_year, h.gml_id, h.building_id, h.match_confidence)

# What changed between releases — honestly:
for e in r.changes(from_year="2020", to_year="2023"):
    print(e.kind, e.surrogate_id, e.confidence, e.related_surrogate_ids)

A complete, no-download runnable demo: examples/quickstart.py.

Resolve by anything

r.resolve({"gml_id": "..."})              # a release's gml:id
r.resolve({"building_id": "01100-bldg-1"})# 建物ID
r.resolve({"surrogate_id": "sg_..."})     # the persistent id itself
r.resolve({"lat": 35.68, "lon": 139.76})  # a coordinate (point-in-polygon)
r.resolve({"footprint": "POLYGON((...))"})# a footprint (WKT / WKB-hex / GeoJSON)
# Unknown? You get None — never a fabricated id.

CLI

plateau-id-resolve ingest  --db city.db --year 2020 city_2020.geojson
plateau-id-resolve ingest  --db city.db --year 2023 city_2023.parquet
plateau-id-resolve resolve --db city.db --gml-id bldg_2023_x99
plateau-id-resolve resolve --db city.db --lat 35.68 --lon 139.76
plateau-id-resolve changes --db city.db --from-year 2020 --to-year 2023
plateau-id-resolve bridge  --db city.db --csv crosswalk.csv     # 建物ID -> 不動産ID
plateau-id-resolve stats   --db city.db

All output is JSON on stdout, so it composes with jq.

HTTP API (optional)

pip install 'plateau-id-resolve[http]'
plateau-id-resolve serve --db city.db        # http://127.0.0.1:8000

GET /resolve?gml_id=… · GET /changes?from_year=…&to_year=…&bbox=… · GET /stats. An unresolved reference returns 404 — not a fabricated id.

建物ID ↔ 不動産ID bridge

PLATEAU's official 不動産ID matching is probabilistic and partial (≈7–69 % by city). We don't re-implement it and we don't fabricate a 1:1 mapping — if a backend has no answer, the 不動産ID is null / covered=false = unknown.

from plateau_id_resolve import TableBridge, bridge_all
backend = TableBridge.from_csv("crosswalk.csv")    # building_id,real_estate_id,confidence
coverage = bridge_all(r.registry, backend)
print(coverage.to_dict())   # honest: total / bridgeable / covered / coverage_ratio

Plug the official WebAPI (or a DB) via CallableBridge(fn) without this package taking a network dependency. For the two official MLIT systems specifically — RealEstateID-matching-system (batch/file) and Building-matching-WebAPI (online) — see the copy-me, runnable reference examples/official_bridge_adapter.py.

The honesty invariant

This package is part of a stack with one shared rule: unsurveyed = unknown, never pretend it's safe (covered=false ⇒ value=null). Here that means:

  • a match_confidence is always present — split/merge/rebuild are genuinely ambiguous, so we expose the uncertainty instead of forcing a 1:1;
  • a surrogate id is permanent and never reused;
  • an unknown 不動産ID is null, never guessed;
  • resolve() returns None rather than inventing a building.

See docs/matching.md and docs/confidence.md for the full method, and docs/validation.md for a real-geometry calibration on 55k PLATEAU Meguro-ku buildings.

Data inputs

Footprints come as GeoJSON or GeoParquet (the latter is what plateau-bridge publishes). Extracting footprints from raw CityGML is plateau-bridge's job — this package consumes footprints, not CityGML. You need ≥2 release years to do anything cross-version.

Where this fits

Part of the Physical AI Record Layer open-source ecosystem. This repo implements the resolve() contract (§③) that the other repos bind to:

🧱 plateau-bridge (records + honesty/query core)   🧱 plateau-id-resolve (this repo — persistent IDs)
🚩 plateau-triage (damage state)   robo-permit (deployment rules)
🔌 caveat-mcp     🤖 sixth-sense     🏛 terra-incognita (benchmark)

plateau-triage / robo-permit / terra-incognita / caveat-mcp all bind their records to the surrogate_id this package mints, so history survives map updates.

Development

pip install -e '.[dev]'
pytest                       # full test suite
ruff check . && mypy plateau_id_resolve
python examples/quickstart.py

Contributing & contact

Issues and PRs welcome. The contracts (record.schema.json, resolve()) follow add-only, minor-bump versioning — backward compatibility is a discipline, not an aspiration.

License

MIT © 2026 PixelX Inc. (ピクセルエックス株式会社) / Yodo Labs

Built by Yodo Labs · PixelX Inc. · ピクセルエックス株式会社

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plateau_id_resolve-0.1.1.tar.gz (161.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plateau_id_resolve-0.1.1-py3-none-any.whl (35.6 kB view details)

Uploaded Python 3

File details

Details for the file plateau_id_resolve-0.1.1.tar.gz.

File metadata

  • Download URL: plateau_id_resolve-0.1.1.tar.gz
  • Upload date:
  • Size: 161.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for plateau_id_resolve-0.1.1.tar.gz
Algorithm Hash digest
SHA256 9af67b7ee59b31f7e6f66dbdff1bf239192159be0dc209acf4b0d972687c2cb1
MD5 08eb46b9c420c35046766a580fb580d9
BLAKE2b-256 501d4c3316d61c6900399cfc081b658049fb120f55512e38b099c004d895adaa

See more details on using hashes here.

Provenance

The following attestation bundles were made for plateau_id_resolve-0.1.1.tar.gz:

Publisher: release.yml on pixelx-jp/plateau_id_resolve

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file plateau_id_resolve-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for plateau_id_resolve-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f92bc1b2cac538256a067c95069f3589903b80fcffd81a3e409595c2819c0195
MD5 6d25196fef7aeb9454417a9f86b44aea
BLAKE2b-256 eef83d755de741c4bd0d3d589aa38c45fb19885ce26632e0c5dbf47801d828de

See more details on using hashes here.

Provenance

The following attestation bundles were made for plateau_id_resolve-0.1.1-py3-none-any.whl:

Publisher: release.yml on pixelx-jp/plateau_id_resolve

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page