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
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
"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_confidenceis 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()returnsNonerather 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9af67b7ee59b31f7e6f66dbdff1bf239192159be0dc209acf4b0d972687c2cb1
|
|
| MD5 |
08eb46b9c420c35046766a580fb580d9
|
|
| BLAKE2b-256 |
501d4c3316d61c6900399cfc081b658049fb120f55512e38b099c004d895adaa
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
plateau_id_resolve-0.1.1.tar.gz -
Subject digest:
9af67b7ee59b31f7e6f66dbdff1bf239192159be0dc209acf4b0d972687c2cb1 - Sigstore transparency entry: 1810935685
- Sigstore integration time:
-
Permalink:
pixelx-jp/plateau_id_resolve@27ec3d7951863fa7ad7b4794ed917fc26842ad43 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pixelx-jp
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@27ec3d7951863fa7ad7b4794ed917fc26842ad43 -
Trigger Event:
release
-
Statement type:
File details
Details for the file plateau_id_resolve-0.1.1-py3-none-any.whl.
File metadata
- Download URL: plateau_id_resolve-0.1.1-py3-none-any.whl
- Upload date:
- Size: 35.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f92bc1b2cac538256a067c95069f3589903b80fcffd81a3e409595c2819c0195
|
|
| MD5 |
6d25196fef7aeb9454417a9f86b44aea
|
|
| BLAKE2b-256 |
eef83d755de741c4bd0d3d589aa38c45fb19885ce26632e0c5dbf47801d828de
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
plateau_id_resolve-0.1.1-py3-none-any.whl -
Subject digest:
f92bc1b2cac538256a067c95069f3589903b80fcffd81a3e409595c2819c0195 - Sigstore transparency entry: 1810935727
- Sigstore integration time:
-
Permalink:
pixelx-jp/plateau_id_resolve@27ec3d7951863fa7ad7b4794ed917fc26842ad43 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pixelx-jp
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@27ec3d7951863fa7ad7b4794ed917fc26842ad43 -
Trigger Event:
release
-
Statement type: