JSON Schema Design for Indoor Maps

Indoor mapping pipelines require strict data contracts to bridge GIS extraction, wayfinding graph generation, and client-side SDK consumption. A poorly structured schema introduces silent topology breaks, coordinate drift, and cache poisoning that compound across deployment cycles. This guide details production-grade schema architecture, validation pipelines, and integration patterns for facilities engineers, GIS developers, and Python automation teams building toward a Production-Ready Indoor Map Deployment.

Foundational Schema Architecture & Spatial Primitives

Indoor maps operate as directed spatial graphs layered over architectural footprints. The JSON schema must enforce geometric validity, semantic consistency, and routing readiness without bloating payloads. Separating navigable topology (nodes, edges) from spatial boundaries (spaces, zones) enables conditional loading and reduces client-side memory pressure.

The following Draft 2020-12 schema enforces strict typing, explicit relationship constraints, and coordinate system declarations. It uses additionalProperties: false to prevent schema drift and applies regex patterns to guarantee identifier consistency across services.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "IndoorMapTopology",
  "type": "object",
  "required": ["version", "metadata", "nodes", "edges", "spaces"],
  "properties": {
    "version": {
      "type": "string",
      "pattern": "^\\d+\\.\\d+\\.\\d+$",
      "description": "Semantic version for cache busting and rollback tracking"
    },
    "metadata": {
      "type": "object",
      "required": ["building_id", "floor_level", "coordinate_system"],
      "properties": {
        "building_id": { "type": "string", "minLength": 1 },
        "floor_level": { "type": "number" },
        "coordinate_system": {
          "type": "string",
          "enum": ["local_xy", "EPSG:4326", "EPSG:3857"]
        },
        "datum_offset": {
          "type": "object",
          "properties": {
            "x": { "type": "number" },
            "y": { "type": "number" },
            "z": { "type": "number", "default": 0 }
          }
        }
      }
    },
    "nodes": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "x", "y", "z", "node_type"],
        "properties": {
          "id": { "type": "string", "pattern": "^N[0-9]+$" },
          "x": { "type": "number" },
          "y": { "type": "number" },
          "z": { "type": "number", "default": 0 },
          "node_type": {
            "type": "string",
            "enum": ["corridor", "door", "elevator", "stair", "point_of_interest"]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": false
      }
    },
    "edges": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "source", "target", "weight", "traversable"],
        "properties": {
          "id": { "type": "string", "pattern": "^E[0-9]+$" },
          "source": { "type": "string", "pattern": "^N[0-9]+$" },
          "target": { "type": "string", "pattern": "^N[0-9]+$" },
          "weight": { "type": "number", "minimum": 0 },
          "traversable": { "type": "boolean" },
          "accessibility": {
            "type": "string",
            "enum": ["wheelchair", "standard", "restricted"]
          },
          "direction": {
            "type": "string",
            "enum": ["forward", "bidirectional", "backward"]
          }
        },
        "additionalProperties": false
      }
    },
    "spaces": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["id", "boundary", "space_type"],
        "properties": {
          "id": { "type": "string", "pattern": "^S[0-9]+$" },
          "boundary": {
            "type": "array",
            "items": {
              "type": "array",
              "items": { "type": "number" },
              "minItems": 2,
              "maxItems": 2
            },
            "minItems": 3
          },
          "space_type": {
            "type": "string",
            "enum": ["room", "hallway", "stairwell", "elevator_shaft", "exterior"]
          },
          "properties": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "capacity": { "type": "integer", "minimum": 0 },
              "occupancy_status": { "type": "string", "enum": ["vacant", "occupied", "maintenance"] }
            }
          }
        },
        "additionalProperties": false
      }
    }
  },
  "additionalProperties": false
}

When defining spatial primitives, align coordinate expectations with established geospatial standards. The GeoJSON specification (IETF RFC 7946) provides a reliable baseline for polygon boundary ordering and coordinate precision, which prevents rendering artifacts in WebGL and native map SDKs.

Automated Validation & CI Gating

Schema compliance alone does not guarantee routing readiness. You must validate both structural correctness and graph topology before merging map updates. The following Python pipeline uses jsonschema for contract validation and networkx for topological integrity checks.

# validate_indoor_map.py
import json
import sys
import logging
from pathlib import Path
import jsonschema
from jsonschema import Draft202012Validator, RefResolver
import networkx as nx

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

SCHEMA_PATH = Path("schemas/indoor_map_topology.json")
MAP_PATH = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("maps/floor_2.json")

def load_schema():
    with open(SCHEMA_PATH, "r") as f:
        return json.load(f)

def validate_structure(data, schema):
    validator = Draft202012Validator(schema)
    errors = list(validator.iter_errors(data))
    if errors:
        for err in errors:
            logging.error(f"Schema violation at {list(err.absolute_path)}: {err.message}")
        raise ValueError("JSON Schema validation failed")
    logging.info("✅ JSON Schema validation passed")

def validate_topology(data):
    nodes = {n["id"] for n in data["nodes"]}
    edges = [(e["source"], e["target"], e["weight"]) for e in data["edges"] if e["traversable"]]
    
    G = nx.DiGraph()
    G.add_nodes_from(nodes)
    G.add_weighted_edges_from(edges)
    
    # Check for dangling edges referencing missing nodes
    missing_refs = set()
    for e in data["edges"]:
        if e["source"] not in nodes: missing_refs.add(e["source"])
        if e["target"] not in nodes: missing_refs.add(e["target"])
    if missing_refs:
        raise ValueError(f"Edges reference non-existent nodes: {missing_refs}")
    
    # Check for disconnected components in traversable graph
    if G.number_of_nodes() > 0:
        components = list(nx.weakly_connected_components(G))
        if len(components) > 1:
            logging.warning(f"⚠️ Traversable graph has {len(components)} disconnected components")
            
    logging.info("✅ Topology validation passed")

if __name__ == "__main__":
    try:
        with open(MAP_PATH, "r") as f:
            map_data = json.load(f)
        schema = load_schema()
        validate_structure(map_data, schema)
        validate_topology(map_data)
        logging.info("✅ All validations passed. Safe for deployment.")
    except Exception as e:
        logging.error(f"Validation pipeline failed: {e}")
        sys.exit(1)

Integrate this script into your CI pipeline to block merges that introduce broken routing graphs. The following GitHub Actions workflow gates map updates and enforces deterministic validation:

# .github/workflows/map-validation.yml
name: Indoor Map Validation & CI Gate
on:
  pull_request:
    paths: ['maps/**/*.json', 'schemas/**/*.json']

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install jsonschema networkx
      - name: Validate Map Topology
        run: python validate_indoor_map.py maps/floor_1.json
      - name: Validate Map Topology (Floor 2)
        run: python validate_indoor_map.py maps/floor_2.json

For teams managing complex API contracts across multiple microservices, Designing JSON schemas for indoor map APIs outlines how to modularize schema definitions using $ref and $dynamicRef for cross-service compatibility.

Versioning, Cache Invalidation, and Rollback Triggers

Indoor map payloads are frequently cached at the CDN, gateway, and client layers. When schema structures change or topology updates occur, stale caches cause routing failures and UI misalignment. Implement semantic versioning in the metadata.version field and compute a deterministic SHA-256 hash of the normalized JSON payload. Distribute this hash via HTTP ETag headers to trigger client-side invalidation only when the topology actually changes.

When deploying updates, pair schema version bumps with automated rollback triggers. If client telemetry reports a >2% navigation failure rate within 15 minutes of a new map release, your deployment orchestrator should automatically revert to the previous hash and invalidate the edge cache. Detailed Cache Invalidation Strategies cover TTL tuning, cache-busting headers, and progressive rollout patterns that minimize user disruption during facility renovations.

SDK Integration & Client-Side Consumption

Mobile and web SDKs consume indoor map payloads differently based on platform constraints. JavaScript/TypeScript clients benefit from pre-generated type definitions, while native iOS/Android clients often require binary serialization (e.g., FlatBuffers or Protobuf) for memory-constrained routing.

To bridge the gap between JSON contracts and client types, use quicktype or datamodel-code-generator in your build pipeline:

# Generate TypeScript types from schema
npx quicktype indoor_map_topology.json -o src/types/IndoorMap.ts --lang typescript --just-types

# Generate Python Pydantic models for backend routing services
datamodel-codegen --input indoor_map_topology.json --output models/indoor_map.py --output-model-type pydantic_v2.BaseModel

Client-side consumption should prioritize lazy loading. Fetch the metadata and nodes arrays first to initialize the routing engine, then stream edges and spaces as the user pans or zooms. Implement graph precomputation on the server side to avoid O(N²) Dijkstra runs on mobile devices. For comprehensive implementation details, review SDK Integration Patterns covering lazy graph hydration, WebGL tile mapping, and accessibility-aware routing weights.

Troubleshooting & Debugging Common Failures

Even with strict schemas, production deployments encounter edge cases. Use this diagnostic checklist to isolate and resolve common indoor mapping failures:

Symptom Root Cause Resolution
Silent routing loops Bidirectional edges with mismatched weights or missing direction constraints Enforce weight symmetry validation in CI; add direction enum to schema
Coordinate drift on zoom Mixed local_xy and EPSG:4326 nodes in the same payload Add CI assertion: metadata.coordinate_system must match all node/edge coordinate ranges
Client OOM crashes Unbounded additionalProperties or deeply nested metadata Set additionalProperties: false globally; cap metadata depth at 3 levels
Dangling POI markers point_of_interest nodes not connected to any traversable edge Run nx.isolates(G) in validation pipeline; reject maps with >0 isolated POIs
Cache poisoning after schema update Missing ETag rotation or stale CDN TTL Implement hash-based cache busting; force Cache-Control: no-cache during schema migrations

For advanced debugging, use jsonschema’s Draft202012Validator.iter_errors() to extract precise violation paths, and pair it with networkx’s nx.shortest_path() dry-runs to verify routing continuity before client deployment. The official jsonschema documentation provides extensive examples for custom validators and error formatting.