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

Mapping internal categories onto IMDF's closed enumeration A table of seven mappings. Office rooms map to the IMDF room category without loss. Meeting rooms and quiet rooms also map to room, which merges them and is lossy. Corridors map to walkway without loss. Gendered restrooms map to restroom, which is lossy at the category level although the gendered attribute survives separately. Service risers map to nonpublic, merging several internal categories. Stairs map to stairs without loss. Seven mappings, four of them lossy on purpose Internal class / category Internal IMDF unit category Lossy? room / office office room ● no room / meeting meeting room △ yes — merged room / quiet quiet room △ yes — merged corridor walkway ● no restroom / gendered gendered restroom △ yes — attribute survives service / riser riser nonpublic △ yes — merged stair stairs ● no Three internal categories collapse to IMDF `room`. That is correct, and it must be deliberate.

Loss is expected; silence is not. Three internal categories becoming one IMDF category is the standard doing its job — the failure is a mapping that happens by accident rather than by decision.

Internal vocabulary against published vocabulary A bar chart for one estate showing 44 distinct internal categories collapsing to 19 IMDF categories at publication, with zero unmapped categories because the build fails on any that are missing from the mapping table. 44 internal categories, 19 published, none unmapped 0 20 40 1 one estate's vocabulary distinct categories internal categories IMDF categories used unmapped (build fails)

Better than two to one, and that is healthy. The internal taxonomy serves search, booking and analytics; the published one serves a renderer, and it does not need to know a quiet room from a meeting room.

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

Handling an internal category the standard does not have One decision with four outcomes, resolved once in the mapping table. Where a near equivalent exists, map to it and record the original as an alternate name. Where several internal categories map to one standard category, accept the merge and keep the detail internal. Where one internal category would map to several standard ones, the internal model is too coarse and should be split. Where nothing suitable exists at all, fail the build rather than passing an unmapped value through. Four outcomes, and one of them is a bug in your own taxonomy An internal category has no IMDF equivalent. What now? decided once, in the mapping table, not per publish a near equivalent exists Map + record nearest category, original as alt name several map to one Accept the merge internal detail stays internal one maps to several Split internally !the internal model is too coarse genuinely nothing Fail the build never pass through an unmapped value

The third branch is the surprising one. If IMDF distinguishes stairs from escalators and your model does not, the standard has found a real gap — one that accessibility routing needs closed anyway.

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.

This page is a companion to POI Taxonomy & Classification, part of the Indoor Mapping Architecture & Standards section.