Indoor & Levels
WemapMap renders multi-level (indoor) livemaps and, when indoor is enabled in
your livemap, drives floor selection automatically.
Automatic building & level selection
After core.init(), WemapMap reads indoor.enable and indoor.minZoom from
the livemap snippet. When indoor is enabled it fetches buildings for the viewport
on movement (debounced), selects the building nearest the viewport centre, and
switches to that building's default level. Below indoor.minZoom there is no
current building.
There is nothing to wire up for a standard indoor livemap:
import { core, type Building } from '@wemap/core';
import { WemapMap } from '@wemap/map';
await core.init({ emmid, token });
const map = new WemapMap({ container: 'map' });
map.onBuildingChange((building: Building | null) => {
// e.g. rebuild a floor switcher from building?.levels
});
Override snippet indoor settings when needed:
const map = new WemapMap({
container: 'map',
indoor: { enable: true, minZoom: 16 },
});
Display a level
setLevel(level) rewrites the filter of every level-aware layer so only
features on that level (plus level-agnostic features) stay visible. A feature is
kept when:
- its
levelproperty equals the requested level, or - it declares a
min_level/max_levelrange containing the level, or - it has no
levelproperty at all.
map.setLevel(1);
const unsubscribe = map.onLevelChange((level) => console.log('now on', level));
setLevel is idempotent (it never stacks filters), and the active level is
re-applied automatically when the style is swapped.
Built-in level switcher
For an easy way to display a floor switcher, drop in the opt-in LevelControl. It renders one
button per level of the current building (top floor first), reflects the active
level, and shows nothing when no indoor building is in view:
import { WemapMap, LevelControl } from '@wemap/map';
const map = new WemapMap({ container: 'map' });
await map.whenReady();
map.addControl(new LevelControl(map)); // defaults to 'bottom-right'
It reuses maplibre's own control styling (from maplibre-gl.css), so no extra
stylesheet is required.
Make your own layers level-aware
Give a runtime layer a level property on its features and wire it into
setLevel, either when adding it:
map.addSource('my-src', { type: 'geojson', data });
map.addLayer({ id: 'my-rooms', type: 'fill', source: 'my-src' }, { indoor: true });
…or by registering a layer you added through the raw instance
(unregisterIndoorLayer restores the original filter):
map.maplibre.addLayer(myLevelAwareLayer);
map.registerIndoorLayer(myLevelAwareLayer.id);
Use getLevelProperty(level) to build the GeoJSON properties for a feature at
a given floor.
Automatic pose-driven level sync
When indoor is enabled, two rules run automatically (no configuration):
- Building selection — when the active building changes (pan), the map
switches to that building's
default_level. - Pose level — when a user pose reports a new floor, the pose is inside the active building's footprint, and the level exists on the building, the map follows.
There is no auto level change when no building is selected, and a manual
setLevel() (e.g. a floor picker) sticks until one of these triggers fires. See
the User location guide for the pose stream.
For the full API, see the Map API reference.