## version-6.0 ### 3D Models # 3D Models > > !3D Model Mapper ## Adding 3D Models to a Map Adding 3D models to a map can be a great way to represent landmarks to help users find key locations. They could also be used to show the location of assets or represent furniture to provide a rich indoor layout. Mappedin JS supports models in Graphics Library Transmission Format (GLTF) and GL Transmission Format Binary (GLB) format. Models with nested meshes are not supported and should not be used. 3D Models can be added to the map using the MapView.Models.add() method. The `add` method requires a Coordinate to place the model and a URL of the model file. Optionally, the model's interactivity, rotation, scale and more can also be set using the `options` parameter, which accepts a TAddModelOptions. Models can be updated by calling the MapView.updateState() method. The following code sample demonstrates the `add` method. ```ts mapView.Models.add(coordinate, 'https://6cdkx7.csb.app/kiosk.glb', { interactive: true, rotation: [0, 0, 90], scale: [10, 10, 10], }); ``` ### Example Try adding some 3D Models to the map below. Clicking on the map will add a snake plant to the map. Clicking the plant removes it. ## Mappedin 3D Model Library The Mappedin 3D Assets Library is a collection of 3D models that can be used to represent landmarks, assets, and furniture on a map. It is optimized for use with Mappedin JS. These models are used in the 3D Model Mapper tool, which allows you to place models on a map and customize their appearance. ### Installation The Mappedin 3D Assets Library is available as an npm package available on https://www.npmjs.com/package/@mappedin/3d-assets. It can be installed using the following commands: **NPM**: ```bash npm install @mappedin/3d-assets ``` **Yarn**: ```bash yarn add @mappedin/3d-assets ``` ### Usage This package provides two ways to use the 3D assets: 1. Self-hosted GLB files (Recommended). 2. Direct base64 model imports. #### Self-hosted GLB files (Recommended) The `/binary` directory contains GLB files that can be self hosted on your own server. This method is recommended as it provides: - 30% smaller download size - No runtime overhead - Better caching control ```ts // Example usage with self-hosted GLB const coordinate = mapView.createCoordinate(45, -75); mapView.Models.add(coordinate, 'https://your-domain.com/assets/model.glb'); ``` #### Direct Base64 Imports For convenience, models can be imported directly as base64 strings. This method is easier to set up but comes with a larger bundle size. ```ts // Import specific models (supports tree-shaking) import { bed, chair } from '@mappedin/3d-assets/inline'; // Or import individual models import plant from '@mappedin/3d-assets/inline/plant_1'; // Usage with MapView const coordinate = mapView.createCoordinate(45, -75); mapView.Models.add(coordinate, bed); ``` This package supports tree-shaking when using direct imports. Only models explicitly imported will be included in the final bundle. ```ts // Only the bed model will be included in the bundle import { bed } from '@mappedin/3d-assets/inline'; ``` ### Example The following example showcases models from the Mappedin 3D Assets Library added to a map. These models were placed on the map using the 3D Model Mapper. ### Model List The Mappedin 3D Assets Library contains the following models. Each model's default blue color can be customized when adding it to the map. Bathtub Bed Bookshelf Box Cardboard Can Garbage Can Recycling Car Chair Computer Couch Couch Curved Couch Outward Curve Desk Desk Chair Dryer EV Charger Floor Lamp Fountain High Bench Hot Tub Kitchen Sink Kiosk Plant 1 Plant 2 Privacy Booth Refrigerator Round Table Self Checkout Shipping Container Shopping Shelves Stove Toilet Tree Pine Tree Pine Short Truck TV Vending Machine Washer Whiteboard Wood Stove ### Annotations # Annotations > > Map annotations add visual or textual elements to maps, providing extra information about specific locations or features. Map makers can choose from many annotations included in the Mappedin Editor to add to a map and access them using Mappedin JS. Annotations are organized into the following groups. > Note that these are just a few examples of annotations, each group contains many more. | Annotation Group | Examples | | ----------------- | -------------------------------------------- | | Access Features | Keybox, roof access | | Building Sides | Alpha, bravo, charlie, delta | | Equipment Rooms | Boiler Room, Sprinkler control room | | Fire Ratings | Fire and smoke wall 1 hour, firewall 3 hours | | Fire Safety | Fire Blanket, Fire Hose Reel | | General Systems | Emergency Generator, Smoke Control Panel | | Hazards | Biohazard, Explosive | | Safety | Accessible Elevator, Eyewash Station | | Utility Shutoff | Gas Valve, Main Water Valve | | Ventilation | Chimney, Exhaust Fan | | Water Connections | Sprinkler FDC, Public Hydrant | | Water Systems | Fire Pump, Pressurized Water Tank | Incorporating annotations help provide a safer space for all. It allows users to easily locate key elements in the map. !Mappedin JS v6 Annotations An app may choose to display all annotations, annotations of a specific group or no annotations at all. The following CodeSandbox lists each Annotation.type in text form that exist on the Mappedin Demo Office Map. It reads the annotations from each floor and groups them by their Annotation.group. ### Areas & Shapes # Areas & Shapes > > ## Areas An Area represents a logical region on the map. They do not represent a physical attribute like a wall or desk, but rather a region that can be used to trigger events, display information, or affect wayfinding. An area is made up of a polygon stored as a GeoJSON Feature, which can be accessed using Area.geoJSON. The Feature can be passed to other methods in Mappedin JS. Shapes.add() accepts an FeatureCollection and can be used to display one or more areas on the map. The code snippet below demonstrates how to use an `Area` to create a `Shape` and add it to the map. ```ts // Get the first area. const areas = mapData.getByType('area'); const areaGeoJSON = areas[0].geoJSON; // Create a FeatureCollection containing the Feature of the Area. const shapeFeatureCollection = { type: 'FeatureCollection', features: [ { type: areaGeoJSON.type, properties: areaGeoJSON.properties, geometry: areaGeoJSON.geometry, }, ], }; // Draw a shape of the area. mapView.Shapes.add(shapeFeatureCollection, { color: 'orange', altitude: 0.2, height: 0.1, opacity: 0.7, }); ``` Within the Mappedin Editor, it is possible to create an area and set it to be off limits for wayfinding. This means that the area will be excluded from the route calculation and directions will be rerouted around the area. This is useful for creating areas that are permanently off limits. At runtime, it is also possible to use an area as an exclusion zone for wayfinding. This is useful for creating areas that are temporarily off limits. Below is a code snippet that demonstrates how to use an `Area` to define a region that a wayfinding route should avoid at runtime. Refer to the Dynamic Routing section of the Wayfinding Guide for an interactive example that demonstrates clicking to set an exclusion zone. TDirectionZone is the type of the TGetDirectionsOptions.zones property that is passed to MapData.getDirections, MapData.getDirectionsMultiDestination() and MapData.getDistance(). These zones can be used to affect the route calculation by excluding a polygon from the route. The following code snippet demonstrates how to use an `Area` to define a region that a wayfinding route should avoid. ```ts //Get all areas. const areas = mapData.getByType('area'); // Get the maintenance area. const maintenanceArea = areas.find((area: any) => area.name === 'Maintenance Area'); const maintenanceGeoJSON = maintenanceArea.geoJSON; // Create directions that route around the area. const origin = mapData.getByType('object').find(obj => obj.name === 'I3'); const destination = mapData.getByType('door').find(obj => obj.name === 'Outbound Shipments 1'); if (origin && destination && shapeFeatureCollection) { const zoneFeature = { type: maintenanceGeoJSON.type, properties: maintenanceGeoJSON.properties, geometry: maintenanceGeoJSON.geometry, }; let zones = []; zones.push({ geometry: zoneFeature as Feature, cost: Infinity, floor: mapView.currentFloor, }); const directions = mapView.getDirections(origin, destination, { zones }); if (directions) { await mapView.Paths.add(directions.coordinates, { color: 'cornflowerblue', }); } } ``` ### Example The CodeSandbox below demonstrates the use of areas to adjust the route calculation. This map contains two areas. The first area is labelled `Forklift Area` and has been marked as off limits within the Mappedin Editor. The second area is labelled `Maintenance Area` and is passed into the `getDirections` function as an exclusion zone. ## Shapes The Shapes class draws 3 dimensional shapes on top of a map. The shapes are created using GeoJSON geometry, which could be a Polygon, MultiPolygon (array of polygons) or a LineString. Access the Shapes class through the MapView class using MapView.Shapes. Shapes are added by calling Shapes.add() and removed individually by calling Shapes.remove() and passing in the Shape object to be removed. All shapes can be removed at once by calling Shapes.removeAll(). The following code example adds a shape after the map is first loaded. Clicking on the map removes a shape if one is present and if not, adds the shape back to the map. ```ts let shape: = mapView.Shapes.add(shapeGeometry as any, { color: "red", altitude: 0.2, height: 2, opacity: 0.7, }); mapView.on("click", async (event) => { if (shape) { mapView.Shapes.remove(shape); shape = undefined; } else { shape = mapView.Shapes.add(shapeGeometry as any, { color: "red", altitude: 0.2, height: 2, opacity: 0.7, }); } }); ``` The code snippet above is implemented in the CodeSanbox below. Refer to the Security Camera Placement example in the Developer Showcase for a more advanced example that uses shapes to draw the field of view of security cameras. ### Blue Dot # Blue Dot The Blue Dot is a visual marker in mapping apps that shows a user's real-time location. It serves as a reference point, helping users identify their position and navigate efficiently. GPS, Wi-Fi, or other tracking technologies typically power the Blue Dot to ensure location accuracy. Mappedin JS provides a simple way to add a Blue Dot to a map. !Blue Dot ## Enable & Disable Blue Dot With Mappedin JS, an app can display a user's location by calling MapView.BlueDot.enable(). This will display a prompt for the user to allow or deny sharing their location with the web page. If permission is given, a device's geolocation is displayed on the map as a Blue Dot. The `enable` method accepts a TBlueDotOptions object as a parameter, which can be used to change the color of the Blue Dot, accuracy shading and heading indicator. It also allows a developer to enable Blue Dot debug logging, set the size of the Blue Dot, indicate whether it should watch the browser's geolocation for updates and to set the timeout value used to set the Blue Dot to inactive after a period of no location updates. When no longer required, the Blue Dot can be disabled using MapView.BlueDot.disable(). The following example demonstrates how to enable the Blue Dot, setting TBlueDotOptions parameters to customize the colors of the Blue Dot, set its timeout to 20 seconds and enable debug logging. ```ts mapView.BlueDot.enable({ color: 'tomato', debug: true, accuracyRing: { color: 'forestgreen', opacity: 0.1, }, heading: { color: 'aqua', opacity: 1, }, inactiveColor: 'wheat', timeout: 20000, }); ``` ## States The Blue Dot has a number of visual states that are used to convey information to the user. - When the Blue Dot position given to Mappedin JS has a high accuracy (accuracy value of 0), it is displayed as bright blue. !Blue Dot - A semi-transparent blue shadow is displayed underneath the Blue Dot to indicate accuracy range in meters. !Blue Dot Accuracy Range - A heading can be shown to indicate the direction the user is facing. !Blue Dot Heading - After the TBlueDotOptions.timeout has passed (default of 30 seconds), the Blue Dot is displayed as greyed. This indicates the user may have moved while no updates were received. !Blue Dot Timeout ## Follow Mode A user may pan the camera away and lose track of their Blue Dot position. An app may want to snap the camera to the user's location to reorient themselves. While this could be done using camera focus on, an app can also leverage Blue Dot's follow mode. Follow mode has multiple modes that are defined within TFollowMode, which are: - `position-only`: Camera position follows the Blue Dot's position. - `position-and-heading`: Camera position follows the Blue Dot's position. Camera bearing matches the Blue Dot's heading. - `position-and-path-direction`: Camera position follows the Blue Dot's position. Camera bearing is calculated based on the Navigation path. - `false`: Disables follow mode. ## Example Experiment with all Blue Dot states using the CodeSandbox below. ## Indoor Positioning Mappedin JS can use the browser's Geolocation API for position updates and display a Blue Dot on the map based on the given location. For indoor environments, an indoor positioning system may be required to provide an accurate location because satellite based positioning is not available indoors and cellular based positioning may not be accurate enough for indoor navigation. Mappedin JS can use the location provided from an indoor positioning system to display the Blue Dot. An example of an indoor positioning system is the Apple Maps Program, which provides a location service for indoor use. A Mappedin Map can be exported to IMDF format and imported into the Apple Maps Program. For more information refer to the Mappedin IMDF Export Page. For development purposes, indoor positions can be simulated using Chrome Developer Tools or by using pre-generated location data. The MapView.BlueDot.update() method can be used to update the Blue Dot's position on the map. It accepts a TBlueDotPositionUpdate that can set the `latitude`, `longitude`, `accuracy` `heading` and `floorOrFloorId` of the Blue Dot. All parameters of `TBlueDotPositionUpdate` are required, however it is possible to set individual parameters to override one or more coming from the browser's geolocation API. To do so provide values for parameters the app wishes to override and use `device` as the value of those parameters that should not be overridden. For example an app may wish to use the browser's Device Orientation API to provide the heading of the Blue Dot and leave the latitude and longitude to be provided by the browser's geolocation API. The following JSON object represents a TBlueDotPositionUpdate that overrides `heading` and uses the browser's geolocation API for the remaining parameters. ```JSON { "accuracy": 'device', "floorOrFloorId": 'device', "latitude": 'device', "longitude": 'device', "heading": 90 } ``` ## Events Blue Dot fires events that can be used to respond to changes to its position, state and errors. The data structure of these events are defined in TBlueDotEvents. The following events are available: - `blue-dot-position-update`: Fired when Blue Dot's position is updated. - `blue-dot-state-change`: Fired when Blue Dot's state changes. - `blue-dot-error`: Fired when an error occurs. ### Blue Dot Position Update The `blue-dot-position-update` event is fired when the Blue Dot's position is updated. It provides the coordinate, floor, accuracy and heading from the last position update. ```ts mapView.on('blue-dot-position-update', (e) => { console.log('blue-dot-position-update', e.coordinate, e.floor, e.accuracy, e.heading); }); ``` ### Blue Dot State Change The `blue-dot-state-change` event is fired when Blue Dot state changes. It provides the new state of the Blue Dot and the action that caused the state change. Actions are defined in TBlueDotAction and states in TBlueDotState. The states pertain to how the Blue Dot is displayed on the map. Refer to the Blue Dot States section above for images of each state. ```ts mapView.on('blue-dot-state-change', (e) => { console.warn('blue-dot-state-change', e.state, e.action); }); ``` ### Blue Dot Error The `blue-dot-error` event is fired when an error occurs. It provides the error as a GeolocationPositionError object. ```ts mapView.on('blue-dot-error', (e) => { console.error('blue-dot-error', e); }); ``` ## Generating Test Location Data The Mappedin Blue Dot Location Generator can be used to generate location data for testing. To create test data, load a map in the generator app and choose a Start and End space. Click Generate to generate the data and observe the Blue Dot on the map using the generated data for its position. Press the Download button to download the location data in JSON format. The data can be customized by specifying the parameters below: - **Venue Settings**: Add in a key, secret and map ID to load a different map. - **Environment**: Whether to load the map from Mappedin's North American or European environment. By default, maps exist in the North American environment. - **Jitter**: The amount of random variation in the position data from the navigation path. - **Accuracy**: The accuracy used in the data. In a real world scenario this value represents how accurate the indoor positioning system believes the result to be and is used to draw the light blue shading around the Blue Dot. - **Distance Between Updates**: The physical distance between each position update. Lower values will result in smoother movement of the Blue Dot. - **Start Space**: The space to start the Blue Dot's movement. - **End Space**: The space to end the Blue Dot's movement. - **Time between updates**: The time between each position update can be used to control the speed of the Blue Dot's movements. - **Path Near Radius**: Controls the size of the path shown on the map. ### Blue Dot Location Generator ### Building & Floor Selection # Building & Floor Selection > > ## Floor Selection When mapping a building with multiple floors, each floor has its own unique map. These are represented by the Floor class, which are accessed using MapData.getByType('floor'). The currently displayed Floor can be accessed using MapObject.floor. MapView displays one Floor at a time. !Mappedin-Web-SDK-v6-Level-Selector ### Video Walkthrough ### Changing Floors When initialized, MapView displays the Floor with the elevation that's closest to 0. This can be overridden by setting TShow3DMapOptions.initialFloor to a Floor or Floor.id and passing it to show3dMap(). ```ts // Setting the initial floor to Floor.id 'm_123456789'. const mapView = await show3dMap( document.getElementById('mappedin-map') as HTMLDivElement, mapData, { initialFloor: 'm_123456789', } ); ``` The Floor displayed in a MapView can also be changed during runtime by calling mapView.setFloor() and passing in a Floor or Floor.id. ```ts // Set the floor to Floor.id 'm_987654321'. mapView.setFloor(`m_987654321`); ``` ### Listening for Floor Changes Mappedin JS event system provides the ability to listen for floor changes on a MapView. The code below listens for the `floor-change` event and logs the new floor name to the console. ```ts // Listen for changes to the floor. mapView.on('floor-change', (event) => { console.log( "Floor changed to:", event?.floor.name, "in FloorStack:", event?.floor.floorStack.name ); }); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/functions/useEvent.html useEvent("floor-change", (event) => { console.log( "Floor changed to:", event?.floor.name, "in FloorStack:", event?.floor.floorStack.name ); }); ``` ## Building Selection Mappedin Maps can contain one to many buildings with each building having one to many floors. Each building has its floors organized into a FloorStack object. When multiple buildings are present, there are multiple FloorStack objects, one for each building. A FloorStack contains one to many Floor objects, each representing the map of each level of the building. The FloorStack object is accessed by using MapData.getByType('floor-stack'). ```ts // Get the FloorStack object. const floorStacks = mapData.getByType('floor-stack'); ``` ## Full Sample The following CodeSandbox uses select elements populated with all FloorStacks and Floors of the selected FloorStack. When a user selects a new floor from the select element, that floor is displayed in the MapView. Additionally, a `floor-change` event listener is implemented to update the select element and log the names of the current Floor and FloorStack name to the console. This is useful for scenarios where the user uses something other than the select element change the Floor, such as clicking on a Connection to another Floor when navigation is shown. ### Camera # Camera > > Controlling the view of the map allows apps to create visually rich experiences using Mappedin JS. This guide shows how to focus the map view on targets, move the camera and listen to camera events. !Mappedin JS v6 Camera > Note that the **MapView** class implements the **Camera** interface and exposes it as **MapView.Camera**. Use **MapView.Camera** to utilize **Camera**'s methods. ## Video Walkthrough ## Focus the Camera on Targets Mappedin JS has built in functionality to focus the camera on one ore more targets using a single or array of TCameraFocusOnTarget. When multiple targets are used, the SDK will ensure that all targets are visible. The following sample code acts on the click event to focus on the Space the user clicked on. Note that the Space must be set to interactive to allow it to be clickable. ```ts // Act on the click event to focus on the Space that was clicked. mapView.on('click', async (event) => { // Focus on the space that was clicked. mapView.Camera.focusOn(event.spaces[0]); }); ``` The CodeSandbox below allows you to test the Camera.focusOn capability. Click on a room to focus the camera on it. ## Controlling the Camera To Control the camera, set it with new pitch, bearing or zoom using Camera.set(). It accepts a TCameraTarget object that contains bearing, center coordinate, pitch and zoom level. These paraemters are all optional, allowing the camera to by moved by adjusting one or more of these values. Experiment with these camera controls using the CodeSandbox below. ### TCameraTarget - **bearing** - Bearing for the camera target in degrees. - **center** - Center `Coordinate` for the camera target. - **pitch** - Pitch for the camera target in degrees. - **zoomLevel** - Zoom level for the camera target in mercator zoom levels. ```ts mapView.Camera.set({ bearing: 30, pitch: 80, zoomLevel: 19.5, center: e.coordinate, }); ``` ## Animation The camera can also be animated into position using the same transforms described in the **Controlling the Camera** section above. This is done using the Camera.animateTo() method. Similar to Camera.set(), the `animateTo` method accepts a TCameraTarget object, but it is optional. This allows the camera to changed by adjusting its `bearing`, `pitch` or `zoomLevel` without specifying a new target to move to. In addition, `animateTo` also accepts TCameraAnimationOptions that allow setting of the duration of the animation in milliseconds and setting the easing curve. The next code snippet acts on the click event to animate the camera to the click position. It uses easing `ease-in-out` over 4000 milliseconds. ```ts // Act on the click event to animate to a new camera position. mapView.on('click', async (event) => { mapView.Camera.animateTo( { bearing: 30, pitch: 80, zoomLevel: 18, center: event.coordinate, }, { duration: 4000, easing: 'ease-in-out' }, ); }); ``` The types of easing anmiations are defined in TEasingFunction. The next CodeSandbox makes use of animates to fly the camera to all points of interest on the map. ### TEasingFunction - **Linear**: This function implies a constant rate of change. It means the animation proceeds at the same speed from start to end. There's no acceleration or deceleration, giving a very mechanical feel. - **Ease-in**: This function causes the animation to start slowly and then speed up as it progresses. Initially, there's gradual acceleration, and as the function moves forward, the rate of change increases. - **Ease-out**: Contrary to ease-in, ease-out makes the animation start quickly and then slow down towards the end. It begins with a faster rate of change and gradually decelerates. - **Ease-in-out**: This function combines both ease-in and ease-out. The animation starts slowly, speeds up in the middle, and then slows down again towards the end. It offers a balance of acceleration and deceleration. ## Resetting The Camera While there is no method in Mappedin JS to reset the camera to its default position, this can be easily built into an app. To do so, store the camera position after the map is shown and then use those values to reposition the camera at a later time. The code sample below stores the initial camera position. When a user clicks on a location it first focuses on that space. The next click will return the camera to its original position. ```ts let focused: boolean = false; //Display the default map in the mappedin-map div. const mapView = await show3dMap(document.getElementById('mappedin-map') as HTMLDivElement, mapData); const defaultCameraPosition: TCameraTarget = { bearing: mapView.Camera.bearing, pitch: mapView.Camera.pitch, zoomLevel: mapView.Camera.zoomLevel, center: mapView.Camera.center, }; // Set each space to be interactive and its hover color to orange. mapData.getByType('space').forEach((space) => { mapView.updateState(space, { interactive: true, hoverColor: '#f26336', }); }); // Act on the click event to focus on the Space that was clicked or reset the camera. mapView.on('click', async (event) => { if (focused) { // Reset the camera to its default position. mapView.Camera.set(defaultCameraPosition); focused = false; } else { // Focus on the space that was clicked. mapView.Camera.focusOn(event.spaces[0]); focused = true; } }); ``` ## Listening to Camera Events There are several camera events that can be acted on by setting a listener with mapView.on('camera-change'). The camera-change event contains a CameraTransform object. CameraTransform provides the bearing, center coordinate, pitch and zoom level. ```ts // Log camera change events to the console. mapView.on('camera-change', async (cam) => { console.log( 'Bearing: ' + cam.bearing + ', Pitch: ' + cam.pitch + ', Zoom Level: ' + cam.zoomLevel + ', Center: Lat:' + cam.center.latitude, +', Lon:' + cam.center.longitude, ); }); ``` The following CodeSandbox implements the code snippet above to print out camera change events to the console. ### Connections # Connections > > Map connections are pathways in multi-floor maps that enable vertical or horizontal movement between levels or spaces. They include stairs, elevators, and escalators, providing essential links for seamless navigation and spatial continuity across the map. The Connection includes details such as name, description, floors, coordinates and more. Connection contains an array of `coordinates`, with each `coordinate` representing the location of the connection on a specific floor. In the case of an elevator, each `coordinate` would have `latitude` and `longitude` values that are the same with different `floorIds`. Stairs may have `coordinates` with the same or different `latitude` and `longitude` values on each floor, depending on whether a building's stairs are vertically aligned. The following CodeSandbox loops through all `connections` and adds a Label using the Connection.name for each Connection that is connected to the currently displayed floor. Zoom into the map to view all connection labels. ### Dynamic Focus # Dynamic Focus > > 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. !Mappedin Dynamic Focus _Mappedin Dynamic Focus with Auto Focus Enabled_ ## 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: ```bash npm install @mappedin/dynamic-focus ``` With Yarn: ```bash 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. ```ts const mapView = await show3dMap(...); /** * Create a new Dynamic Focus controller that automatically updates the MapView. */ const df = 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()`. ```ts 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. ```ts 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. ```ts 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. ### Enterprise Data # Enterprise Data > > The Enterprise Data classes described in this guide are populated in Mappedin CMS, which requires an Enterprise Tier subscription. ## Enterprise Locations An EnterpriseLocation contains metadata about a location, such as its name, description, logo, phone number, social medial links, hours of operation and more. They can be accessed using the MapData.getByType() method as shown below. ```ts const allLocations = mapData.getByType('enterprise-location'); ``` Here is an example of the data contained within EnterpriseLocation. !enterprise-location-example-data This CodeSandbox below labels each location using its name. Click on the location label to display a Marker that shows the EnterpriseLocation name, logo and description. Click the map again to dismiss the Marker. ## Enterprise Categories An EnterpriseCategory groups one or more EnterpriseLocation. These allow similar locations to be sorted in a logical fashion. For example a mall may group locations into Food Court, Footwear and Women's Fashion. They can be accessed using the MapData.getByType() method as shown below. ```ts mapData.getByType('enterprise-category'); ``` Here is an example of the data contained within EnterpriseCategory. !enterprise-category-example-data EnterpriseCategory can contain one or more sub EnterpriseCategory that are accessed from its children accessor. EnterpriseCategory also contains a name, array of EnterpriseLocation and other metadata. The following CodeSandbox lists all EnterpriseCategory of the map and each EnterpriseLocation grouped within that category. ## Enterprise Venue The EnterpriseVenue class holds metadata bout the map, which includes the map name, supported languages, default language, top locations and more. It can be accessed using the MapData.getByType() method as shown below. ```ts mapData.getByType('enterprise-venue'); ``` Here is an example of the data contained within EnterpriseVenue. !enterprise-venue-example-data ## Enterprise Search The Search functionality allows users to search for locations, categories, and other points of interest within the venue. Here are two ways to enable search: 1. Enable Search on map initialization (Recommended): ```ts const mapData = await getMapData({ options, search: { enabled: true, }, }); ``` 2. Enable Search via method: ```ts await mapData.Search.enable(); ``` ### Search Query Use Search.query to search for locations based on a string input: - EnterpriseLocation: Specific places such as stores, restaurants, or washrooms. - EnterpriseCategory: Groups of locations, such as "Food Court" or "Electronics." - Places: Any main objects that can be searched for such as Space, Door, Point of Interest Search query returns a list of matching SearchResults based on the input string. SearchResults include information about the type of match, the score (relevance), and detailed metadata about the matching items. #### Example Search Query ```ts const results = await mapData.Search.query('Coffee Shop'); // Log the entire result object console.log(results); ``` #### Example Response ```ts { "places": [ { "type": "space", "match": { "coffee": ["description"], "shop": ["description"], "shopping": ["description"] }, "score": 19.5, "item": { "id": "s_ef330e70329183b4", "name": "Take Five Cafe", "type": "room", "floor": "f_8ca999c2cbcb6022", "center": { "latitude": 43.860466138841865, "longitude": -78.94643735347043, "floor": "f_8ca999c2cbcb6022" } } } ], "enterpriseLocations": [ { "type": "enterprise-location", "match": { "coffee": ["tags", "description"] }, "score": 24.3, "item": { "id": "el_094c671a5bc73fb7", "name": "Nespresso" } } ], "enterpriseCategories": [ { "type": "enterprise-category", "match": { "shop": ["locations.name"] }, "score": 9.4, "item": { "id": "ec_5cf9b7898619b951", "name": "Health & Beauty" } } ] } ``` #### Response Field Breakdown 1. Places: - `type`: Indicates the type of match (e.g., space). - `match`: Shows the fields that matched the query (e.g., description). - `score`: Relevance score of the match. - `item`: Includes id, name, type, floor, and center (coordinates). 2. Enterprise Locations: - `type`: enterprise-location. - `match`: Indicates fields matched (e.g., tags, description). - `item`: Contains id, name, and additional metadata. 3. Enterprise Categories: - `type`: enterprise-category. - `match`: Indicates query matches to category-related data. - `item`: Contains id and name. ### Search Suggestions Use Search.suggest to fetch real-time suggestions based on partial input. This is useful for creating an autocomplete feature for a search bar. #### Example Code ```ts // fetch suggestions for partial input "Coff" const suggestions = await mapData.Search.suggest('Coff'); // log the suggestions console.log(suggestions); // log suggestion names suggestions.forEach(suggestion => { console.log(`Suggestion: ${suggestion.suggestion}`); }); ``` #### Example Response Here’s a sample response for the query 'Coff': ```ts [ { suggestion: 'coffee', terms: ['coffee'], score: 7.558366632976704, }, ]; ``` #### Response Field Breakdown 1. `suggestion`: The suggested term or phrase (e.g., "coffee"). 2. `terms`: An array of individual terms that make up the suggestion. 3. `score`: A numerical value representing the relevance of the suggestion. #### Example Use Case: Implementing a Search Bar Below is an example of how to integrate the Search function into a search bar with real-time suggestions: ```ts // HTML (Assumed you have a simple input box and a results list) // // const searchBox = document.getElementById('searchBox'); const suggestionsList = document.getElementById('suggestionsList'); // listen for input events in the search box searchBox.addEventListener('input', async event => { const query = event.target.value; // fetch suggestions for the current input const suggestions = await mapData.Search.suggest(query); // clear previous suggestions suggestionsList.innerHTML = ''; // populate new suggestions suggestions.forEach(suggestion => { const listItem = document.createElement('li'); listItem.textContent = suggestion.name; suggestionsList.appendChild(listItem); }); }); ``` ### CodeSandbox Example This example demonstrates how to enhance user interactions with real-time search suggestions and visual map highlights. When a user selects a suggestion from the search box, the map dynamically zooms to the location, and a circle shape is displayed to highlight the area. The circle's size is adjustable and helps visually emphasize the searched location, improving the user experience. Features: 1. Search.suggest and Search.query 2. Camera animation to the selected location using Camera.animateTo. ## Events & Deals Events and deals created in Mappedin CMS can be retrieved using the @mappedin/events package. ### Installation To use the @mappedin/events package, you need to install it using npm or yarn. **Using npm** ```bash npm install @mappedin/events ``` **Using yarn** ```bash yarn add @mappedin/events ``` ### Usage The following example demonstrates how to load and display events on a map. ```ts import { show3dMap, Marker } from '@mappedin/mappedin-js'; import { EventsManager } from '@mappedin/events'; const mapData = await getMapData(...); // Create an EventManager to load and read event data const em = new EventsManager(mapData); // Use Promise.all() to reduce sequential load time const [mapView] = await Promise.all([show3dMap(...), em.load()]); // Read all events for (const event of em.events) { // Add event markers to all spaces that have events const target = event.location?.spaces[0]; mapView.Markers.add(target, `
${event.name}
`); } ``` ### Options The `EventsManager` constructor accepts an optional `options` parameter. The following options are available: ```ts /** * Options for the {@link EventsManager}. */ export type EventsManagerOptions = Partial<{ /** * The fields to fetch for each event. * * @default fields id, name, type, startDate, endDate, externalId, description, location, image, video */ fields: string[]; /** * Whether to include past or expired events. * * @default false */ includeExpired: boolean; }>; ``` ### Methods The `EventsManager` class provides the following methods: ```ts class EventsManager { /** * Whether load() has been called and successfully completed. */ get ready(): boolean; /** * All current and future events that have been loaded. Will be empty if load() has not been called. */ get events(): EventMetaData[]; /** * Load all the events for the map. */ async load(): void; /** * Get an event by ID. */ getById(id: string): EventMetaData | undefined; /** * Get an array of events attached to an EnterpriseLocation. */ getByLocationId(locationId: string): EventMetaData[]; /** * Clean up the EventsManager instance. */ destroy(): void; } ``` ### Getting Started # Getting Started > > This getting started guide demonstrates how to start a project using Mappedin JS. Following this guide results in a working demo with a map that you can interact with (zoom, pan, rotate etc). > Mappedin JS v6 is available as @mappedin/mappedin-js in NPM. ## Video Walkthrough ## Coding with AI Mappedin JS provides an llms-mappedin-js.txt file that can be used to help with coding when using Large Language Models (LLMs). ## Local Development For local development, start a project using Vite. Refer to the Vite Getting Started guide for setup details. Guides are written in TypeScript (JavaScript), as the SDK is written in Typescript and uses comprehensive types. ### 1. Create a Project > Note that Mappedin JS version 6 is currently in an beta state. Therefore you must append @beta when adding the Mappedin package. Failing to do so will add the current production release of version 5, which does not support maps in the Free, Plus, Advanced or Pro tiers. Run these shell commands to set up a new project and install Mappedin JS. ```bash yarn create vite mappedin-quickstart cd mappedin-quickstart yarn add @mappedin/mappedin-js@beta ``` ### 2. Update index.html, main.ts Modify & update the contents of `index.html` and `main.ts` to match the following. ```html title=index.html Mappedin JS v6 Getting Started
``` Create or update a file under the `src` directory called `main.ts`. Write the following contents to the file. ```ts title=src/main.ts import { getMapData, show3dMap } from '@mappedin/mappedin-js'; import '@mappedin/mappedin-js/lib/index.css'; // See Demo API key Terms and Conditions // https://developer.mappedin.com/docs/demo-keys-and-maps const options = { key: 'mik_yeBk0Vf0nNJtpesfu560e07e5', secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022', mapId: '65c0ff7430b94e3fabd5bb8c', }; async function init() { const mapData = await getMapData(options); const mapView = await show3dMap( document.getElementById('mappedin-map') as HTMLDivElement, mapData ); } init(); ``` ### 3. Run the Project To run the demo (with hotloading), use the following command: ```bash yarn run dev ``` The following CodeSandbox demonstrates what should be shown. This should result in a prompt showing the project being served at **http://127.0.0.1:5173** (default port). The 3D rendered map can be zoomed, panned and rotated via mouse or fingers. ## Create a Key & Secret ## Authenticating with a Bearer Token Mappedin JS can also be authenticated with a short lived bearer token. This can be used to prevent exposing an API key and secret by generating a token on the server side. The server can securely store the API key and secret, allowing the client to use the token to authenticate with Mappedin JS. To do so, make a request to the API Key REST endpoint sending the API key and secret. The following TypeScript example shows how to do this using a fetch request. Additional examples can found in the API Key REST documentation. ```ts async function getAccessToken(): Promise { // See Trial API key Terms and Conditions // https://developer.mappedin.com/docs/demo-keys-and-maps const response = await fetch('https://app.mappedin.com/api/v1/api-key/token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ key: 'YOUR_KEY', secret: 'YOUR_SECRET', }), }); const data: TokenResponse = await response.json(); return data.access_token; } ``` The API Key REST endpoint will return JSON that contains the bearer token. ```json { "access_token": "…", "expires_in": 172800 } ``` The bearer token can then be used to authenticate with Mappedin JS as shown in the following example. ```ts const options = { accessToken: 'your-access-token', mapId: 'your-map-id', }; const mapData = await getMapData(options); const mapView = await show3dMap(document.getElementById('mappedin-map') as HTMLDivElement, mapData); ``` ## Local Development without NPM While NPM is highly recommended for version control and to receive the latest bug fixes and features, Mappedin JS may also be imported from a CDN. In this example JSDelivr will be used to demonstrate importing the `mappedin-js` package. Mappedin JS is also available on unpkg. Please feel free to reach out if the SDK is not available on a preferred CDN. Import the SDK stylesheet in the document `` and any other necessary styles for the app. ```html ``` In the ``, include the element which the map will attach too and the script to asynchronously load the MapView. ```html
``` Putting it all together, the entire HTML document should resemble the following. ```html Mappedin JS v6 Getting Started with JSDelivr
``` With only the above document, a map will be rendered into the window. Just like with NPM, the 3D rendered map can be zoomed, panned and rotated via mouse or fingers. ## Offline Mode The examples in the Mappedin JS guides assume that the user is online and connected to the Internet. In some environments, this may not be possible. There are two methods available to store map data offline and use it to render a map. ### Offline Loading from TMVF Objects Offline loading of TMVF objects stored as JSON is appropriate for scenarios where Mappedin JS is used to initially download the map data using the getMapData() function. This data could be stored in a JSON file to be read from when the user is offline. The steps below outline how to do this. 1. Load the map using getMapData(). 2. Use MapData.toJSONBundle() to create a TMVF object. 3. Store the TMVF object. 4. When the user is offline, read the JSON file and use hydrateMapData() to create a MapData object. 5. Use the MapData object to render a map using show3dMap(). ### Offline Loading from MVF Files Offline loading of Mappedin Venue Format (MVF) files is appropriate for scenarios where the map data is not fetched using Mappedin JS. This approach is particularly useful in offline or low-connectivity environments—such as air gapped networks, cruise ships, or remote locations. It's also beneficial when you need full control over content distribution, for example, to meet internal security requirements or to ensure that a specific version of the map is always available, regardless of network conditions. An MVF file can be downloaded from the Mappedin Editor or fetched using Mappedin REST API endpoints. Instructions for downloading an MVF file can be found in the Getting Started with MVF v2 guide. The code snippet below demonstrates how to load a self hosted MVF file. ```ts import { show3dMap, parseMVF, unzipMVF, hydrateMapData } from '@mappedin/mappedin-js'; import './styles.css'; async function init() { //Fetch the MVFv2 file hosted in this CodeSandbox. const mvfResponse = await fetch('https://hjsgdv.csb.app/craft-show-demo-mvfv2.zip'); //Uncompress the zip file. const mvfBuffer = await mvfResponse.arrayBuffer(); const compressed = new Uint8Array(mvfBuffer); const raw = await unzipMVF(compressed); //Parse the MVF. const mvf = await parseMVF(raw); //Disable analytics which cannot be sent while offline. const mapData = await hydrateMapData(mvf, { analytics: { enabled: false, }, }); //Use the parsed MVF to show the map. const mapView = await show3dMap( document.getElementById('mappedin-map') as HTMLDivElement, mapData ); } init(); ``` The code snippet above is implemented in the following CodeSandbox. ## Debug Mode Use Debug Mode to get a closer look at how various map components behave and interact. Here's how to enable it: ### 1. Enable Debug Mode To activate the debug interface, call the following function in your code: ```ts mapView.enableDebug(); ``` enableDebug displays a panel on the right side of the map, revealing several tools and controls for direct interaction with the map’s elements. ### 2. Navigating the Debug Panel The debug panel provides access to a variety of controls: **Geometry Controls** - Adjust individual geometry settings, such as color, opacity, visibility. These controls make it easy to see how different elements respond to changes. **Interactivity** - Use toggles to turn interactivity on/off - Change colors and hover effects to highlight specific areas **Scene Controls** - Manage the overall scene settings, such as scaling, positioning, and group management. Toggle the visibility of groups or containers within the scene - Adjust padding, scale, and watermark positioning - Markers & Labels — Add, remove, or edit markers and labels directly through the panel **Camera Controls** - Fine-tune camera settings, including zoom levels, pitch, and center position - Set minimum and maximum zoom levels - Adjust the focus to a specific area or level !enable debug ### Images, Textures & Colors # Images, Textures & Colors > > !Images and Textures Images, textures and colors can enhance the fidelity of an indoor map. They can be used to add custom branding, highlight important features, or provide additional information to users. Images can be placed on any IAnchorable target on the map and given a `verticalOffset` to control the height at which the image is displayed. Textures can be applied to the top or sides of a MapObject, Space, Door or Wall. ## Requirements & Considerations JPEG and PNG images are supported for both images and textures. It's important to consider the size of all unique image files displayed on a map at one time. Using many unique images may cause instability on mobile devices with limited GPU memory. Mappedin JS will cache and reuse images that have the same URL, resulting in reduced memory usage. The following calculations illustrates how much memory is used for a given image: **Formula:** `width * height * 4 bytes/pixel = memory used` **512 x 512 Pixel Image:** `512px * 512px * 4 bytes/pixel = 1MB` **4096 x 4096 Pixel Image:** `4096px * 4096px * 4 bytes/pixel = 64MB` ## Creating Images for Spaces When creating images to be used as the floor of a space, it's important that the image dimensions match the space to avoid it leaking into adjacent spaces. An SVG export of the map can be useful to determine the correct dimensions. The following steps demonstrate how to create an image that matches a space's dimensions using Figma. Similar steps could be used in other image editing tools that support the SVG format. 1. Log into app.mappedin.com and select the map you want to create an image for. 2. Click on the **Download** button and choose `Download as SVG`. 3. Import the SVG into Figma (**File** > **Place Image**). 4. Select the geometry to create the image for by clicking near the center of it (not the edge). 5. Choose **File** > **Place** Image to place the image you want to use as the floor. 6. Click the geometry to fill. 7. Click **Export** (bottom right). 8. Choose format (PNG or JPEG), size and export. The exported image should match the dimensions of the space. ## Images The Images class is accessed through MapView.Images and used to add and remove images to and from the map. Images can be placed on any Door, Space, or Coordinate. TAddImageOptions is used to configure the image and specifies its width, height, rotation, vertical offset and whether the image is rotated to face the camera. The following example places an image on a Space named "Arena". ```ts const IMAGE_URL = 'https://x6ccjn.csb.app/arena-floor.png'; const arenaFloor = mapData.getByType('space').find((space) => space.name === 'Arena'); // Configure the images size and rotation. // Using an offset of 0.1 to place it just above the map's floor. // Use pixelsToMeters to convert pixels to meters. This value will vary based on the map size. const pixelsToMeters = 0.0617; const controller: TAddImageOptions = { width: 1014 * pixelsToMeters, height: 448 * pixelsToMeters, rotation: 121, verticalOffset: 1, flipImageToFaceCamera: false, }; // Add the default image to the arena floor. mapView.Images.add(arenaFloor, IMAGE_URL, controller); ``` The CodeSandbox implements the example above and provides a drop down, allowing a user to select a hockey, basketball or concert image to be displayed on the arena floor. ## Textures & Colors Walls, doors, floors and objects can be given textures or colors, which can be applied individually to the top or sides. The TGeometryState type is used to configure the texture and color, which is applied by calling MapView.updateState. When applying textures to walls it is important to set TShow3DMapOptions.shadingAndOutlines to false when calling show3dMap. This will prevent lines from being drawn between the top and side textures. Be sure to review the Requirements & Considerations section before applying textures. ### Doors The top and sides of a door can be given textures or colors. The example below demonstrates how to set the color of all interior and exterior doors. Doors can also be made visible on an individual basis by passing in a Door object to MapView.updateState(). ```ts //Make interior doors visible, sides brown and top yellow. mapView.updateState(DOORS.Interior, { visible: true, color: 'brown', topColor: 'yellow', opacity: 0.6, }); //Make exterior doors visible, sides black and top blue. mapView.updateState(DOORS.Exterior, { visible: true, color: 'black', topColor: 'blue', opacity: 0.6, }); ``` ### Objects Objects can be given textures or colors, which can be applied individually to the top and sides. The following example demonstrates how to set the texture of the side and color of the top of all objects. ```ts mapData.getByType('object').forEach((object) => { mapView.updateState(object, { texture: { url: 'https://example.com/object-side-texture.png', }, topColor: '#9DB2BF', }); }); ``` ### Spaces The floor of a space can be given a texture or color. When creating an image for the floor of a space, it's important that the image dimensions match the space to avoid it leaking into adjacent spaces. Refer to the Creating Images for Spaces section for more information. The following example demonstrates how to set the texture of the floor for all spaces. ```ts mapData.getByType('space').forEach((space) => { mapView.updateState(space, { topTexture: { url: FLOOR, }, }); }); ``` ### Walls The exterior and interior walls of a building can be targeted with different textures and colors. The following example demonstrates how to set the texture of an exterior wall and the colors of interior walls. Note that both types of walls support textures and colors. ```ts // Disable shadingAndOutlines to prevent lines from appearing between the top and side textures. const mapView = await show3dMap(document.getElementById('mappedin-map') as HTMLDivElement, mapData, { shadingAndOutlines: false, }); mapView.updateState(WALLS.Exterior, { texture: { url: 'https://example.com/wall-side-texture.png', }, topTexture: { url: 'https://example.com/wall-top-texture.png', }, }); mapView.updateState(WALLS.Interior, { color: '#526D82', topColor: '#27374D', }); ``` ### Example The CodeSandBox below demonstrates how to use colors and textures to customize the appearance of a map. Textures are applied to exterior walls, space floors and the sides of objects. Colors are applied to interior walls and the top of objects. A dark theme is also applied to the outdoor map. Refer to the Styles section in the Outdoor Map guide for more information on changing the outdoor map's style. ### Interactivity # Interactivity > > This guide explains how to enhance a map's interactivity by making components clickable. Interactivity can greatly improve the user experience and user engagement with a map. !Mappedin JS v6 Interactivity ## Video Walkthrough ## Interactive Spaces Spaces can be set to interactive to allow a user to click on them. When interactivity is enabled for a space, it enables a hover effect that highlights the space when the cursor is moved over the space. The following code enables interactivity for all spaces: ```ts // Set each space to be interactive. mapData.getByType('space').forEach((space) => { mapView.updateState(space, { interactive: true, }); }); ``` ## Hover Interactivity When interactivity is enabled for a space, it enables a hover effect that highlights the space when the cursor is moved over the space. The highlight color can also be customised to match the style of the map. The following code sample enables interactivity for all spaces on the map and sets the hover color to red. ```ts // Set each space to be interactive and its hover color to red. mapData.getByType('space').forEach((space) => { mapView.updateState(space, { interactive: true, hoverColor: 'red', }); }); ``` This CodeSandbox demonstrates interactive spaces with hover enabled. ## Interactive Labels Labels added to the map can be set to interactive to allow users to click on them and have the click event captured by an app. The code sample below adds a label to each space with a name and sets the label's interactivity to true. ```ts // Label all spaces with its space name and make them interactive. mapData.getByType('space').forEach((space) => { if (space.name) { mapView.Labels.add(space, space.name, { options: { interactive: true }}); } }); ``` ```tsx // Get all spaces with names const spaces = mapData.getByType('space').filter(space => space.name); // Label all spaces with its space name and make them interactive.å return ( <> {spaces.map(space => (