How to Define an Indoor CRS for Multi-Building Campuses
This page covers the one technique that keeps a campus routable end to end — collapsing every building’s private drawing frame onto a single surveyed plane — and it sits under Indoor Coordinate Reference Systems within the broader Indoor Mapping Architecture & Standards reference. When that plane is shared, a corridor in Building A and a skybridge into Building B agree on what “one metre north” means; when it is not, the routing graph silently fragments at every building boundary.
Concept Definition
A campus-scale indoor CRS is a single facility-local Cartesian frame, metric, orthogonal, and anchored to one surveyed origin, into which every building’s geometry is transformed before it reaches a spatial database or routing graph. The origin is fixed to a permanent reference — a surveyed geodetic control marker, a structural column-grid intersection, or a high-precision GNSS point — and never moves once published.
Each building arrives in its own local frame, carrying its own drawing unit, (0,0) anchor, and User Coordinate System rotation. Unifying them is a per-building 2D similarity (Helmert) transform — translation, rotation, and a single uniform scale — solved from surveyed control points, plus a shared vertical datum so floor levels stack consistently. The transform is valid only when its residual error is small; an affine transform is the fallback when a building’s drawing carries non-uniform scale (different X and Y units), at the cost of one extra degree of freedom. Three invariants must hold across the whole campus:
- Consistent horizontal datum — all CAD/BIM exports project to a common local tangent plane or state-plane system before the per-building offset is applied.
- Deterministic vertical datum — floor elevations resolve to one vertical reference (NAVD88, EGM2008, or a surveyed site benchmark) with
Z = 0at the ground-floor finished floor level. - Scale preservation — legacy export artifacts (1:100 vs 1:50 mismatches) are absorbed by explicitly solving for uniform scale, never by eyeballing.
Minimal Working Example
The core technique is solving the per-building similarity transform from at least three non-collinear control points and reporting the residual RMS in metres. This self-contained routine returns the 3×3 affine matrix and the RMS, the single number that gates whether the building may be published.
import logging
from typing import Tuple
import numpy as np
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
def solve_campus_helmert(
source: np.ndarray, target: np.ndarray
) -> Tuple[np.ndarray, float]:
"""Solve a 2D similarity (Helmert) transform mapping a building's local
frame onto the surveyed campus plane; return (3x3 affine, RMS in metres)."""
if source.shape[0] < 3:
raise ValueError("Need >=3 non-collinear control points for scale+rotation.")
src_c = source - source.mean(axis=0)
tgt_c = target - target.mean(axis=0)
a = np.sum(src_c[:, 0] * tgt_c[:, 0] + src_c[:, 1] * tgt_c[:, 1])
b = np.sum(src_c[:, 0] * tgt_c[:, 1] - src_c[:, 1] * tgt_c[:, 0])
denom = np.sum(src_c ** 2)
if denom == 0:
raise ValueError("Degenerate control points: source points coincide.")
scale = np.hypot(a, b) / denom
theta = np.arctan2(b, a)
rot = scale * np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
trans = target.mean(axis=0) - rot @ source.mean(axis=0)
fitted = (rot @ source.T).T + trans
rms = float(np.sqrt(np.mean(np.sum((fitted - target) ** 2, axis=1))))
logging.info("scale=%.6f theta=%.4f rad RMS=%.4f m", scale, theta, rms)
return np.vstack([np.column_stack([rot, trans]), [0, 0, 1]]), rms
Call it once per building with that building’s surveyed control points (source = local drawing coordinates, target = campus-plane coordinates), then refuse to publish any building whose RMS exceeds the threshold below.
Parameter & Threshold Reference
| Parameter | Type | Default / Threshold | Notes |
|---|---|---|---|
source, target |
np.ndarray shape (N, 2) |
— | Paired control points in metres; N ≥ 3, non-collinear. |
| Control points per building | int |
3 minimum, 5+ recommended |
Extra points let RMS detect a bad pick; collinear sets are rejected. |
max_rms_threshold |
float |
0.15 m |
Publish gate. Above this, the transform is unfit for routing. |
| Horizontal datum | EPSG / string | local tangent plane or state plane | Common across all buildings before per-building offset. |
| Vertical datum | string | NAVD88 / EGM2008 / site benchmark |
One reference campus-wide; mixing them stacks floors wrong. |
Ground-floor Z |
float |
0.0 m at FFL |
Logical level indices (B1, L1, L2) map to physical Z-offsets. |
| Portal coordinate tolerance | float |
0.0 m (exact parity) |
Skybridge/tunnel endpoints must be byte-identical in both graphs. |
| Z-plane bbox overlap | float |
0.3 m horizontal max |
Larger overlap between adjacent floor levels signals datum misalignment. |
| Transform model | enum | helmert | affine |
Use affine only when X and Y scales genuinely differ. |
Common Errors & Fixes
RuntimeError: RMS 0.83m exceeds threshold 0.15m — almost always a scale artifact: one building’s drawing was exported at 1:50 while the campus grid assumes 1:100, so its geometry is uniformly stretched. Because the Helmert solver returns scale explicitly, inspect it — a value near 2.0 or 0.5 confirms a unit/scale mismatch rather than a survey error. Re-export the offending DWG at the correct scale (or pre-multiply its coordinates) and re-solve; do not raise the threshold to force a pass.
Stairwell routing failures / “elevator shaft clipping” — the horizontal transform is clean but the vertical datum is not, so two floor levels occupy overlapping Z ranges. Flag the geometry whose Z falls outside its building envelope before ingestion:
import logging
def flag_z_outliers(features: list[dict], z_min: float, z_max: float) -> list[str]:
"""Return ids of features whose elevation escapes the building envelope."""
bad = [f["id"] for f in features
if not (z_min <= f["properties"]["z"] <= z_max)]
if bad:
logging.warning("Z-datum misaligned: %d feature(s) outside envelope", len(bad))
return bad
If this returns more than ~5% of a building’s features, the vertical datum is offset from the horizontal CRS — cross-reference laser-scanned point clouds against the CAD baseline to find the systematic shift, and resolve floor indices the way Converting CAD Elevations to Indoor Z-Levels describes.
Graph disconnection at a skybridge or tunnel — both buildings solve cleanly in isolation, yet the routing graph splits into two components. The cause is a portal node whose coordinates differ by a few centimetres between the two building datasets. Cross-building transitions need dedicated portal nodes whose coordinates are exactly shared, expressed in the campus GeoJSON envelope so both graphs snap to one vertex:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "portal-skybridge-A3-B2",
"geometry": { "type": "Point", "coordinates": [1042.50, 2080.75] },
"properties": { "kind": "skybridge", "z": 7.20, "level_id": "L2",
"connects": ["bldg-A", "bldg-B"] }
}
]
}
Integration Point
This transform is the contract every downstream stage assumes. Geometry only reaches it after vector ingestion — the SVG/DWG parsing workflows deliver a unit-aligned FeatureCollection, and the campus CRS then anchors it to the surveyed datum. Vertical resolution is handed to Level Mapping & Z-Axis Logic, which turns clustered Z-planes into routing-ready level IDs. Once geometry is on the shared plane, Z-axis routing weights and the exact-parity portal nodes above feed the wayfinding engine, while Fallback Routing Architectures rely on consistent grid alignment so dead-reckoning corridor vectors stay valid when RTLS or BLE positioning drops out. Publish the result through the envelope defined in JSON Schema Design for Indoor Maps so the campus plane and its POI placements stay interoperable across SDKs.
Related
- Converting CAD Elevations to Indoor Z-Levels
- Best Practices for Indoor POI Taxonomy
- Fallback Routing Architectures
This page sits under the Indoor Coordinate Reference Systems collection, part of the Indoor Mapping Architecture & Standards reference.