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.2.0.tar.gz (170.3 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.2.0-py3-none-any.whl (37.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: plateau_id_resolve-0.2.0.tar.gz
  • Upload date:
  • Size: 170.3 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.2.0.tar.gz
Algorithm Hash digest
SHA256 63c38d2074db5efa7e49602883952a441c6d18b750a425a7a4f6bd9eb5e2bc10
MD5 bd57db1c1a4c4e91ec2517d5ebba0459
BLAKE2b-256 8b94809738de81e2ca8380d39bce76edde57b9b606ce3e2e95cd535995e2c7e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for plateau_id_resolve-0.2.0.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.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for plateau_id_resolve-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 448d2e45dbc5191af86d0335e649a1f9f5baa7a0f2c7eb06394939e61750d056
MD5 1c629d5aec76fac5d799c914ccfdc903
BLAKE2b-256 c08b55790eea7ada379008c716889396e05dbdae9ff4582e6b5e9ab8c483de41

See more details on using hashes here.

Provenance

The following attestation bundles were made for plateau_id_resolve-0.2.0-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