Indoor mapping

GeoJSON for indoor mapping: where it breaks, and how to fix it

GeoJSON was designed for streets, parcels and countries. Point it at a building and it runs out of words: it has no floors, no rooms inside buildings and no local coordinates. None of that makes it the wrong format. It just means you need a few conventions, and here they are.

Updated 6 November 20269 min read

GeoJSON (RFC 7946) describes a geometry and a free-form properties object. That simplicity is why every map library reads it. It is also why indoor projects drift: each team invents its own way to say "this room is on floor 2 of building B", and no two files agree. Below are the six problems you hit first, with the fix most teams settle on.

1. Coordinates must be longitude and latitude

Floor plans come in millimetres or metres from a local origin. RFC 7946 is strict: coordinates are WGS 84 longitude and latitude, and the old crs member was removed from the spec. A plan in metres will either be rejected or land somewhere off the coast of West Africa, at 0°, 0°.

Fix: georeference the plan before you export it, which means placing, scaling and rotating it onto the real building. Keep 7 decimal places (about 1 cm). The spec itself notes that 6 decimals are already about 10 cm, which is enough for rooms but tight for desks.

TipIf your app needs the original local coordinates too, store the source units and transform in the collection's metadata, rather than inside each geometry.

2. There is no concept of floors

A position can have a third value, but RFC 7946 defines it as altitude or elevation, in metres. That isn't a floor number. Two floors of the same building have identical footprints, so in 2D they sit exactly on top of each other.

Fix: give every feature a level property and filter on it when you render.

  • Use an integer ordinal for logic: "level": 0 for the ground floor, 1 for the floor above, -1 for the basement.
  • Use a separate display name for people: "level_name": "Mezzanine".
  • This mirrors existing schemes. OpenStreetMap tags indoor features with level=*, and Apple's IMDF (an OGC Community Standard) uses an ordinal on its level features.

3. A FeatureCollection is flat

Buildings are trees: site → building → floor → room → desk. GeoJSON has one flat list of features. Nesting a FeatureCollection inside another isn't valid GeoJSON, and most libraries won't read it.

Fix: keep the list flat and express the tree with properties. Two common patterns:

  • IDs and parent references: give each feature a stable top-level id, and point children at their parent with "level_id" or "room_id". This is how IMDF links units to levels.
  • A path or group name: "group": "Level 1" or "group_path": ["Building A", "Level 1"]. It's simpler to read and filter, but harder to rename.

4. Overlapping shapes have no meaning

A desk sits inside a room, and a room sits inside a floor outline. GeoJSON happily stores overlapping polygons, but nothing in the file says which one is the room and which is furniture. Click handlers and area calculations then pick the wrong one.

Fix: add a category, and render in a fixed order.

  • "category": "room" | "corridor" | "desk" | "stair" | "door". OpenStreetMap uses indoor=room, indoor=corridor and indoor=area for the same idea.
  • Draw floor outlines first, rooms next, furniture and points last, and give small shapes priority on click.

5. Doors, walls and openings

Navigation needs to know where you can walk through. Walls are usually implied by the gaps between room polygons, and doors are often missing entirely.

Fix: model doors explicitly, either as Point features with "category": "door" and a "connects": ["room-12", "corridor-1"] list, or as short LineString features along the wall. IMDF calls these openings.

6. Holes, winding and styling

  • Holes: an atrium is a polygon with an inner ring. RFC 7946 asks for counter-clockwise outer rings and clockwise holes. Many tools ignore this, and most renderers cope, but validators will flag it.
  • Styling isn't part of GeoJSON. The informal simplestyle-spec keys (stroke, stroke-width, fill, fill-opacity, marker-color) are the closest thing to a convention. geojson.io and GitHub read them.

The fixes at a glance

ProblemPlain GeoJSONCommon conventionIn a FloorJSON export
Local coordinatesMust be lon/latGeoreference before exportPlaced on the map, exported as lon/lat
FloorsNot definedlevel + level_namegroup per floor; add level as a custom attribute
HierarchyFlat list onlyIDs and parent referencesgroup
What a shape isNot definedcategoryshape_type (geometry kind) + custom attributes
StylingNot definedsimplestyle-specstroke, fill, fill-opacity, marker-color
LabelsNot definedA name or label keyname, plus label, label_size, label_color
HolesInner ringsRFC 7946 winding orderOuter rings CCW; holes not supported yet

Which approach should you use?

  • Publishing to Apple Maps, or exchanging with venue platforms: use IMDF. It is GeoJSON-based but strict, with defined feature types (venue, building, level, unit, opening).
  • Contributing to OpenStreetMap: follow Simple Indoor Tagging.
  • Your own app, dashboard or website: plain GeoJSON with a small, consistent set of properties is the pragmatic choice. Start from the properties schema examples rather than inventing keys project by project.
FloorJSON colour panel styling three room polygons, showing outline and fill colours, fill opacity and outline width
Styling rooms in FloorJSON. Colours are exported as simplestyle-spec properties.

FAQ

Can GeoJSON store multiple floors of a building?Yes, as separate features with a level property such as "level": 1. GeoJSON has no built-in floor concept, so filter by that property when you render.
Should I use the third coordinate for floors?No. RFC 7946 defines the third value as altitude or elevation in metres. Use it for real heights if you need them, and a level property for floor numbers.
Can I nest a FeatureCollection inside another?Not in valid GeoJSON. Keep one flat FeatureCollection and express the hierarchy with ids and parent references, or with a group property.
Is IMDF better than plain GeoJSON?For Apple Maps and venue platforms, yes: it is a defined standard. For your own maps it is often more structure than you need, and plain GeoJSON with consistent properties is easier to start with.

Skip the hand-rolled properties

FloorJSON exports floor plans with a consistent set of properties (names, groups, colours and labels) so you start from a structure instead of inventing one.

Open FloorJSON