Skip to main content
Version: 6.0

Dynamic Focus

Mappedin JS version 6 is currently in a beta state while Mappedin perfects new features and APIs. Open the v6 release notes to view the latest changes.
Using Mappedin JS with your own map requires a Pro license. Try a demo map for free or refer to the Pricing page for more information.

A single venue may include multiple nearby buildings, each contained within a FloorStack. Dynamic Focus enables seamless exploration across these buildings by revealing indoor content as the camera pans over each structure. There are two ways to implement Dynamic Focus.

1. Dynamic Focus Package

The first approach is to use the Mappedin Dynamic Focus package @mappedin/dynamic-focus, which is a standalone package that can be used in conjunction with Mappedin JS. It provides a simple API for implementing Dynamic Focus, allowing automatic switching between building facades and building maps as they move into and out of focus when the map is panned. The Dynamic Focus package will trigger the same facades-in-view-change and floor-change events as the Implementing Dynamic Focus using MapView method, allowing the app to listen for these events and update the map view accordingly, such as showing and hiding markers or labels when focus is transitioned to a new building.

2. Dynamic Focus using MapView.updateState

The second approach is to use the MapView.updateState() method to update the map view when the camera pans over a building. This approach is more flexible and allows for more control over Dynamic Focus behavior, but it requires more code to implement. This can allow for custom behaviour, such as showing the the building map of different elevations. This can be useful when floors of nearby buildings don't share the same elevation, such as when level 2 of one building is aligned with level 3 of another building. It could also allow for showing and hiding markers or labels when focus is transitioned to a new building.

Mappedin Dynamic Focus

Mappedin Dynamic Focus with Auto Focus Enabled

Implementing the Mappedin Dynamic Focus Package

The Mappedin Dynamic Focus package offers a simple API for implementing Dynamic Focus. If custom behaviour is required refer to the Implementing Dynamic Focus using MapView section.

Installation

Dynamic focus is provided as a separate package that works in conjunction with Mappedin JS. To install the @mappedin/dynamic-focus package from npm, run the following command:

With NPM:

npm install @mappedin/dynamic-focus

With Yarn:

yarn add @mappedin/dynamic-focus

Usage

The Dynamic Focus controller is instantiated with a reference to the MapView instance. The following example demonstrates how to create a new Dynamic Focus controller and enables auto focus to automatically transition the currently visible map from one building to the next as the camera pans over it.

import { getMapData, show3dMap } from '@mappedin/mappedin-js';
import { DynamicFocus } from '@mappedin/dynamic-focus';

// Refer to the Getting Started Guide for more information on how to initialize the map.
const mapView = await show3dMap(...);

// Create a new Dynamic Focus controller that automatically updates the MapView.
const dynamicFocus = new DynamicFocus(mapView, { autoFocus: true, setFloorOnFocus: true });

Dynamic Focus has a number of options that can be enabled either at instantiation or at any time via updateState().

dynamicFocus.updateState({
autoFocus: true, // whether to automatically focus on camera move
indoorZoomThreshold: 18, // the zoom level at which the indoors is revealed
outdoorZoomThreshold: 17, // the zoom level at which the indoors are hidden
setFloorOnFocus: true, // whether to automatically call setFloor during focus
indoorAnimationOptions: {
// options for the animation to fade out the facades
duration: 150,
},
outdoorAnimationOptions: {
// options for the animation to fade in the facades
duration: 150,
},
});

Auto Focus

Dynamic Focus' auto focus provides default behavior to transition the currently visible map from one building to the next as the camera pans over it. The default behavior can be overridden by setting the autoFocus option to false, allowing the developer to control the focus manually.

const mapView = await show3dMap(...);

/**
* Create a new Dynamic Focus controller that automatically updates the MapView.
*/
const dynamicFocus = new DynamicFocus(mapView, { autoFocus: true, setFloorOnFocus: true });

/**
* Disable automatic updates - now the app needs to manually call `focus()` to update the view.
*/
dynamicFocus.updateState({ autoFocus: false });

/**
* Manually trigger a focus update to show/hide facades and update floor visibility.
* This shows the default Floor in the FloorStack that is currently centered on the map.
*/
dynamicFocus.focus();

/**
* Clean up the controller and restore default floor visibility behavior
*/
dynamicFocus.destroy();

Setting FloorStack Default Floor

The default floor is the Floor that is visible when focus is transitioned to a new FloorStack (building). The default Floor for a FloorStack can be set by calling setDefaultFloorForStack() with the floor stack and floor ID. The default floor can be reset by calling resetDefaultFloorForStack() with the floor stack.

const dynamicFocus = new DynamicFocus(mapView);
dynamicFocus.setDefaultFloorForStack(floorStack, floor);
dynamicFocus.resetDefaultFloorForStack(floorStack);

Interactive Example

The following CodeSandbox demonstrates how to use Dynamic Focus to transition between floors and buildings of a map with multiple buildings (FloorStacks). Zoom in and move the map around to observe Dynamic Focus auto focus behavior.

Implementing Dynamic Focus using MapView

Dynamic Focus can be implemented using the MapView.updateState method. This approach is more flexible and allows for full customization of Dynamic Focus behavior, but it requires more code to implement. For a simple implementation of Dynamic Focus, refer to the Mappedin Dynamic Focus Package section.

Implementing Dynamic Focus using MapView.updateState requires the app to switch the visibility of a building Facade and Floor when the camera pans over a building. The Facade represents the look of the building from the outside, with it's roof on. The Floor represents the look of the building from the inside, showing the indoor map.

Listen for Changes to Facades In view

The facades-in-view-change event is emitted when the facades in view change. This event is emitted when the camera pans over a building. The event provides the list of facades in view. The app can use this event to update the visibility of the facades and floors. The first Facade in the array contains the one that is most centered in view.

// Act on the facades-in-view-change event to update floor visibility.
let facadesInView = new Set<string>();
mapView.on("facades-in-view-change", (event) => {
const { facades } = event;

facadesInView.clear();

if (facades.length > 0) {
for (const facade of facades) {
facadesInView.add(facade.id);
}
const primaryFacade = facades[0];
const primaryFloor =
floorToShowByBuilding.get(primaryFacade.floorStack.id) ??
primaryFacade.floorStack.defaultFloor;
mapView.setFloor(primaryFloor);
}
});

Listen for Changes to the Floor

The floor-change event is emitted when the floor changes, for example when the user selects a new floor from a floor selector or when they click on a navigation marker to go up or down stairs or an elevator. The event provides the new floor. The app can use this event to update the visibility of the facades and floors.

// Act on the floor-change event to update the level selector.
mapView.on("floor-change", (event) => {
const { floor: newFloor } = event;
elevation = newFloor.elevation;
console.log("Elevation: " + elevation);
updateFloorsToShow();

mapData.getByType("floor-stack").forEach((fs) => {
if (fs.facade) {
if (
facadesInView.has(fs.facade.id) ||
fs.id === newFloor.floorStack.id
) {
openFacade(fs.facade);
} else {
closeFacade(fs.facade);
}
}
});

console.log(
"Floor changed to:",
event?.floor.name,
"in building:",
event?.floor.floorStack.name
);
});

// Open the building's facade and show its indoor map.
function openFacade(facade: Facade) {
if (animationsByFacade.has(facade.id)) {
animationsByFacade.get(facade.id)?.cancel();
}
// first, show the floor we want to see
showFloors(facade.floorStack);
if (mapView.getState(facade)!.opacity === 0) {
// already animated out
return;
}
// then, animate the facade out
const animation = mapView.animateState(
facade,
{
opacity: 0,
},
{
duration: animationDuration,
}
);
animationsByFacade.set(facade.id, animation);
animation.then(() => {
animationsByFacade.delete(facade.id);
});
}

// Close the building's facade and hide its indoor map.
function closeFacade(facade: Facade) {
if (animationsByFacade.has(facade.id)) {
animationsByFacade.get(facade.id)?.cancel();
}
if (mapView.getState(facade)!.opacity === 1) {
// already animated in
return;
}

// first, animate the facade in
const animation = mapView.animateState(
facade,
{
opacity: 1,
},
{
duration: animationDuration,
}
);
animationsByFacade.set(facade.id, animation);
animation.then(() => {
animationsByFacade.delete(facade.id);
// then, hide the other floors
facade.floorStack.floors.forEach((floor) => {
mapView.updateState(floor, {
visible: false,
});
});
});
}

Interactive Example

The following CodeSandbox demonstrates how to implement Dynamic Focus using the MapView.updateState method. Zoom in and move the map around to observe Dynamic Focus behavior.