Skip to Content
ExamplesFloor Management

Floor Management

Floor change demo: cycling through floors with visual selector

This guide documents the indoor APIs that control building and floor context:

  • getCurrentFloor
  • updateFloor
  • updateParentPlaceAndFloor
  • setParentPlace
  • removeParentPlace

It also explains what changes when the same map is used outdoors.

Indoor setup

import { initializeSDK, type MVXPlace } from "@mapvx/web-js" const sdk = initializeSDK("<YOUR_API_KEY>", { lang: "es" }) const map = sdk.createMap(document.getElementById("map") as HTMLElement, { center: { lat: 4.666918, lng: -74.053065 }, zoom: 18, parentPlaceId: "<PARENT_PLACE_ID>", onFloorChange: (newFloorId: string) => { console.log("Floor changed", newFloorId) }, onParentPlaceChange: (newParentPlaceId: string) => { console.log("Parent place changed", newParentPlaceId) }, })

Read the active floor

const currentFloorId = map.getCurrentFloor()

If the map has no parent place, this returns an empty string.

Switch floors inside the same building

map.updateFloor("<FLOOR_ID>", { onComplete: () => { console.log("Floor update finished") }, })

This automatically refreshes polygon filters, marker visibility, and route layers for the selected floor.

Understanding InnerFloor

Each parent place exposes an InnerFloor array sorted by index. The properties most relevant to a floor selector are:

PropertyTypeDescription
keystringUnique floor identifier used in all SDK calls
namestringFull display name (e.g. “Ground Floor”)
shortNamestring?Abbreviated label (e.g. “G”, “1F”, “B1”)
levelnumberNumeric level (negative for basements)
defaultFloorbooleanWhether this is the initial floor shown on entry
reachableFromGPSboolean?Whether GPS positioning works on this floor

Build a floor selector from a real parent place

Floor selector showing all innerFloors with levels and properties

const parentPlace = await sdk.getPlaceDetail("<PARENT_PLACE_ID>") parentPlace.innerFloors.forEach((floor) => { console.log(floor.key, floor.name, floor.level, floor.defaultFloor) }) function selectFloor(floorId: string): void { if (map.getCurrentFloor() === floorId) return map.updateFloor(floorId, { onComplete: () => { console.log("Now showing", floorId) }, }) }

Filter visible floors with remote configuration

Floor list filtered by enabledFloors from remote configuration

Not all floors in innerFloors should necessarily be shown to end users. The remote Configuration includes an enabledFloors array that declares which floor keys are visible for the current product deployment.

const parentPlace = await sdk.getPlaceDetail("<PARENT_PLACE_ID>") const config = await sdk.getConfiguration("<PARENT_PLACE_ID>", "directory") const allFloors = parentPlace.innerFloors // If enabledFloors is set, only show those; otherwise show all const visibleFloors = config.enabledFloors?.length > 0 ? allFloors.filter((floor) => config.enabledFloors.includes(floor.key)) : allFloors

Complete floor selector with filtering

Interactive floor selector demo with enabled/disabled floors

This example combines place data and remote config to render a floor selector that only shows the floors the deployment has enabled:

import { initializeSDK, type InnerFloor, type Configuration } from "@mapvx/web-js" const sdk = initializeSDK("<YOUR_API_KEY>", { lang: "es" }) async function buildFloorSelector(parentPlaceId: string): Promise<void> { const [parentPlace, config] = await Promise.all([ sdk.getPlaceDetail(parentPlaceId), sdk.getConfiguration(parentPlaceId, "directory"), ]) const allFloors = parentPlace.innerFloors const visibleFloors = config.enabledFloors?.length > 0 ? allFloors.filter((f) => config.enabledFloors.includes(f.key)) : allFloors const defaultFloor = visibleFloors.find((f) => f.defaultFloor) ?? visibleFloors[0] // Render buttons const container = document.getElementById("floor-selector")! container.innerHTML = "" visibleFloors.forEach((floor) => { const button = document.createElement("button") button.textContent = floor.shortName ?? floor.name button.dataset.floorKey = floor.key button.classList.toggle("active", floor.key === defaultFloor?.key) button.addEventListener("click", () => { selectFloor(floor.key) container.querySelectorAll("button").forEach((btn) => { btn.classList.toggle("active", btn.dataset.floorKey === floor.key) }) }) container.appendChild(button) }) } function selectFloor(floorId: string): void { if (map.getCurrentFloor() === floorId) return map.updateFloor(floorId, { onComplete: () => console.log("Now showing", floorId), }) }

Route-aware floor filtering

When a route is active, you may want the floor selector to only enable the floors the route passes through. Extract unique floor IDs from the route steps and disable the rest:

import { type MVXRoute, type InnerFloor } from "@mapvx/web-js" function getRouteFloors(route: MVXRoute, allFloors: InnerFloor[]): InnerFloor[] { const routeFloorKeys = new Set( route.steps .flatMap((step) => [step.startInsideFloor, step.endInsideFloor]) .filter(Boolean) as string[] ) return allFloors.map((floor) => ({ ...floor, // Attach a "disabled" flag for the UI to consume disabled: !routeFloorKeys.has(floor.key), })) } // After obtaining a route, update the floor selector const route = await map.addRoute(routeConfig) const floorsWithState = getRouteFloors(route, visibleFloors) floorsWithState.forEach((floor) => { const button = document.querySelector(`[data-floor-key="${floor.key}"]`) as HTMLButtonElement if (button) button.disabled = floor.disabled })

This pattern ensures users can only switch to floors that are part of their current navigation, reducing confusion in multi-floor buildings.

Switch building and floor together

Use this when a route or UI flow moves between indoor venues:

map.updateParentPlaceAndFloor("<NEW_PARENT_PLACE_ID>", "<FLOOR_ID>", { onComplete: () => { console.log("Building and floor ready") }, })

If floorId is omitted, the SDK falls back to the building’s default floor.

Set a parent place from an MVXPlace

This is useful when you already resolved the target building with getPlaceDetail.

const targetParentPlace: MVXPlace = await sdk.getPlaceDetail("<NEW_PARENT_PLACE_ID>") map.setParentPlace(targetParentPlace, true, () => { console.log("New style loaded for the building") })

Pass updateStyle: false only when the correct style is already loaded and you only need to update the internal context.

Leave indoor mode

map.removeParentPlace()

After this:

  • getCurrentFloor() returns an empty string
  • indoor layer filters are reset
  • floor-scoped markers stop depending on the previous building context

Outdoor behavior

Outdoor maps still support the same MapVXMap instance, but floor APIs become lightweight:

  • getCurrentFloor() is empty
  • updateFloor() does not have meaningful indoor context
  • removeParentPlace() is how you intentionally return to outdoor mode after an indoor flow
async function openIndoorBuilding(parentPlaceId: string): Promise<void> { const place = await sdk.getPlaceDetail(parentPlaceId) map.setParentPlace(place, true, () => { const defaultFloor = place.innerFloors.find((floor) => floor.defaultFloor)?.key if (defaultFloor) { map.updateFloor(defaultFloor) } }) }
Last updated on