Mapping POI Categories to IMDF
Where POI Taxonomy & Classification meets an external standard. The internal vocabulary is shaped by routing and search; IMDF’s is shaped by rendering, and the mapping between them is a set of decisions worth making once and writing down.
Lossy on Purpose
Forty-four internal categories become nineteen IMDF categories, and that is the standard working correctly rather than failing. A booking system needs to know a quiet room from a meeting room; a map renderer draws both as a room. Preserving every internal distinction in the published archive would mean abusing alternate names and custom properties, which the consumer’s validator will either reject or ignore.
What matters is that the collapse is deliberate and recorded. A mapping table with meeting → room written in it is a decision someone can review; a .get(category, category) that passes
unknown values through is the same collapse happening by accident, and it fails at submission.
When the Standard Finds Your Gap
Three of the four branches are ordinary. The interesting one is the third: an internal category that would map to several standard categories.
If IMDF distinguishes stairs from escalator and the internal model has one vertical class,
the mapping cannot be written — and the right response is not a heuristic but a change to the
internal taxonomy. That distinction is needed anyway: an escalator is directional, is unusable for
step-free routing, and is weighted differently by
accessible routing profiles. The standard has
surfaced a real modelling gap.
This is the most useful side-effect of publishing to a standard. Mapping onto someone else’s carefully-argued enumeration is a review of your own.
Minimal Working Example
import logging
from dataclasses import dataclass
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
# Total, explicit, reviewable. Every internal (class, category) pair appears here.
IMDF_UNIT = {
("room", "office"): "room",
("room", "meeting"): "room",
("room", "quiet"): "room",
("room", "lab"): "laboratory",
("room", "classroom"): "classroom",
("restroom", "gendered"): "restroom",
("restroom", "accessible"): "restroom.family",
("corridor", None): "walkway",
("stair", None): "stairs",
("elevator", None): "elevator",
("escalator", None): "escalator",
("service", "riser"): "nonpublic",
("service", "plant"): "nonpublic",
}
class UnmappedCategory(KeyError):
"""Raised at build time so an unmapped value can never reach a submission."""
@dataclass(frozen=True)
class MappedUnit:
category: str
alt_name: str | None
def to_imdf_unit(space_class: str, category: str | None) -> MappedUnit:
"""Map one internal space onto an IMDF unit category, keeping the original."""
key = (space_class, category)
if key not in IMDF_UNIT:
fallback = (space_class, None)
if fallback not in IMDF_UNIT:
raise UnmappedCategory(
f"no IMDF category for {space_class!r}/{category!r} — "
"add a mapping rather than passing it through")
logger.info("using class-level mapping for %s/%s", space_class, category)
key = fallback
mapped = IMDF_UNIT[key]
# keep the internal term where it was merged away, so a curator can still see it
alt = category if category and mapped in ("room", "nonpublic") else None
return MappedUnit(mapped, alt)
The class-level fallback is deliberate and narrow: it lets a new tier-2 category publish sensibly the day it is added, without letting an unknown class through. An unknown class raises, because tier 1 is closed by design and a new value there is a genuine schema event.
Common Errors & Fixes
Submission rejected for an unknown category. An internal value was passed through. Every mapping should be a lookup in a total table that raises on a miss, checked in CI rather than at submission.
Every room becomes nonpublic. The class-level fallback is being hit because the tier-2
categories were never mapped. The log line above surfaces it; a build-time report of how many
features used a class-level fallback turns it into a number.
Detail vanishes and nobody expected it. The merge was not communicated. Publishing the mapping
table alongside the archive — even as a comment in the manifest — makes meeting → room a
documented decision rather than a surprise for whoever compares the two datasets.
A category maps correctly and renders wrongly. IMDF consumers style by category, so mapping a
plant room to nonpublic and a riser to nonpublic is correct semantically and makes them
identical on the map. Where the visual distinction matters to a consumer, the lever is the
alt_name and the display point rather than the category — and where it does not matter, the merge
was the right call and the surprise is only that someone expected otherwise.
Accessibility attributes are lost. Category is not the only field. IMDF carries accessibility on units and openings separately, and mapping the category correctly while leaving those attributes null publishes a map that cannot support step-free routing at the consumer.
Integration Point
The mapping runs inside the IMDF converter described in Indoor Map Data Standards, between reprojection and identifier derivation. Its input is the internal taxonomy that POI Taxonomy & Classification maintains; its output is validated by the checks in validating IMDF archives.
Keeping the table in one module rather than spread through the converter matters more than it sounds: it is the artefact a reviewer reads when asking what the estate publishes about itself, and it is the thing that changes when either vocabulary moves.
Frequently Asked Questions
Should unmapped categories fail the build or warn?
Fail, and fail at build time rather than at submission. A warning in a build log is a warning nobody reads until an archive is rejected weeks later, by which point the engineer who added the category has moved on. Failing immediately costs one line in the mapping table — a decision that takes seconds when the context is fresh — and makes it impossible to ship an archive that a consumer will refuse. The one nuance is the class-level fallback, which lets a new tier-2 category publish sensibly while still raising on an unknown tier-1 class.
Can I extend IMDF with my own categories?
Not in the category enumeration, and partly elsewhere. The unit and amenity category lists are closed, and a validator rejects anything outside them — that is the point of a standard. IMDF does allow alternate names and some extensibility on occupants, which is enough to carry an internal term as metadata so a curator can see what was merged. What you should not do is put internal semantics into a field the consumer will render, because either it is ignored or it appears somewhere unexpected.
How often does the mapping table change?
Rarely, and for two distinct reasons worth distinguishing in review. It changes when your internal taxonomy grows a tier-2 category, which is routine and usually a one-line addition. It changes when the standard revises its enumerations, which is infrequent and can be a breaking change for the consumer as well as for you. Versioning the mapping alongside the data — recording which mapping version produced which archive — is what makes an “it used to publish differently” conversation tractable.
Related
- POI Taxonomy & Classification — the internal vocabulary this maps from.
- Indoor Map Data Standards — the publishing boundary this mapping runs inside.
- Best Practices for Indoor POI Taxonomy — the three-tier design that makes this mapping possible at all.
This page is a companion to POI Taxonomy & Classification, part of the Indoor Mapping Architecture & Standards section.