3D Models
## 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 b…
Unclaimed Agent
Are you the maintainer? Claim this agent to manage its listing and increase its trust score.
## 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 samples demonstrate adding a 3D model to the map. ```ts mapView.Models.add(coordinate, 'https://yourDomain.com/models/yourModel.glb', { interactive: true, rotation: [0, 0, 90], scale: [10, 10, 10], }); ``` ```tsx <Model target={coordinate} options={{ scale: 2, rotation: [0, 0, 90], opacity: 1, }} url={"https://yourDomain.com/models/yourModel.glb"} /> ``` ### 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 at 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 ## Using 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. ## Annotation Icons The Mappedin Editor and Viewer use a set of SVG icons to represent annotations. The CodeSandbox below demonstrates using these icons and placing them as Markers on the map. The `annotation-icons.ts` file in the example contains an array with all icon ids, category, uri and SVG image. ### 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<Polygon>, 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", (event: TClickPayload) => { 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-error`: Fired when an error occurs. - `blue-dot-follow-change`: Fired when the Blue Dot's follow mode changes. - `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 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 MapView.currentFloor. !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 IFocusable. 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. Refer to the Interactivity guide for more information on how to set a space to interactive. ```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 parameters 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: '#4248FF', }); }); // 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 and multi-building maps that enable vertical or horizontal movement between levels, spaces or buildings. 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. 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: ```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 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 }); ``` ```tsx import { DynamicFocus } from "@mappedin/dynamic-focus"; import { MapView, useMapData, useMap } from "@mappedin/react-sdk"; function MyDynamicFocus() { const { mapView } = useMap(); const dynamicFocus = new DynamicFocus(mapView, { autoFocus: true, setFloorOnFocus: true, }); return null; } export default function App() { // See Demo API key Terms and Conditions // https://developer.mappedin.com/docs/demo-keys-and-maps const { mapData } = useMapData({ key: "mik_yeBk0Vf0nNJtpesfu560e07e5", secret: "mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022", mapId: "682e13a2703478000b567b66", }); return mapData ? ( <MapView mapData={mapData}> <MyDynamicFocus /> </MapView> ) : null; } ``` 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. ## 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. ```ts // 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); } }); ``` ```tsx // Act on the facades-in-view-change event to update floor visibility. const [facadesInView, setFacadesInView] = useState<Set<string>>(new Set()); useEvent("facades-in-view-change", (event) => { const { facades } = event; const newFacadesInView = new Set<string>(); if (facades.length > 0) { for (const facade of facades) { newFacadesInView.add(facade.id); } const primaryFacade = facades[0]; const primaryFloor = floorToShowByBuilding.get(primaryFacade.floorStack.id) ?? primaryFacade.floorStack.defaultFloor; mapView.setFloor(primaryFloor); } setFacadesInView(newFacadesInView); }); ``` ### 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. ```ts // 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, }); }); }); } ``` ```tsx // Act on the floor-change event to update the level selector. useEvent("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. ### 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) // <input type="text" id="searchBox" placeholder="Search here..."> // <ul id="suggestionsList"></ul> 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, `<div>${event.name}</div>`); } ``` ### 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 <!DOCTYPE html> <html> <head> <title>Mappedin JS v6 Getting Started</title> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" charset="UTF-8" /> <style> * { margin: 0; padding: 0; } html, body { width: 100%; height: 100%; } #mappedin-map { height: 100%; width: 100%; position: relative; } </style> </head> <body> <div id="mappedin-map"></div> <script type="module" src="src/main.ts"></script> </body> </html> ``` 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'; // 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<string> { // 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 <style> * { margin: 0; padding: 0; } html, body { width: 100%; height: 100%; } #mappedin-map { height: 100%; width: 100%; position: relative; } </style> ``` In the ``, include the element which the map will attach too and the script to asynchronously load the MapView. ```html <div id="mappedin-map"></div> <script type="module"> import { getMapData, show3dMap, } from 'https://cdn.jsdelivr.net/npm/@mappedin/mappedin-js@beta/lib/esm/index.js'; // See Demo API key Terms and Conditions // https://developer.mappedin.com/web/docs/demo-keys-and-maps const options = { key: 'mik_yeBk0Vf0nNJtpesfu560e07e5', secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022', mapId: '65c0ff7430b94e3fabd5bb8c', }; getMapData(options).then(async mapData => { const mapView = await show3dMap(document.getElementById('mappedin-map'), mapData); }); </script> ``` Putting it all together, the entire HTML document should resemble the following. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <style> * { margin: 0; padding: 0; } html, body { width: 100%; height: 100%; } #mappedin-map { height: 100%; width: 100%; position: relative; } </style> <title>Mappedin JS v6 Getting Started with JSDelivr</title> </head> <body> <div id="mappedin-map"></div> <script type="module"> import { getMapData, show3dMap, } from 'https://cdn.jsdelivr.net/npm/@mappedin/mappedin-js@beta/lib/esm/index.js'; // See Demo API key Terms and Conditions const options = { key: 'mik_yeBk0Vf0nNJtpesfu560e07e5', secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022', mapId: '65c0ff7430b94e3fabd5bb8c', }; getMapData(options).then(async mapData => { const mapView = await show3dMap(document.getElementById('mappedin-map'), mapData); }); </script> </body> </html> ``` 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 => ( <Label key={space.externalId} target={space} text={space.name} // label text options={{ interactive: true, }} // makes the label interactive /> ))} </> ); ``` After enabling interactivity, click events on the label can be captured using Mapview.on('click') when using Mappedin JS or useEvent('click') when using Mappedin SDK for React. The TEvents object passed into the `on` or `useEvent` method contains an array of labels that were clicked. The following code sample captures the click event and checks if the user clicked on a label. If they did, it logs the id of the label that was clicked and removes it from the map. ```ts // Act on the click event to log the id of the label that was clicked and then remove it. mapView.on('click', (event: TClickPayload) => { if (event?.labels?.length > 0) { console.log('Clicked on label: ' + event.labels[0].id); mapView.Labels.remove(event.labels[0]); } }); ``` ```tsx // Act on the click event to log the id of the label that was clicked and then remove it. useEvent('click', (event: TClickPayload) => { if (event?.labels?.length > 0) { console.log('Clicked on label: ' + event.labels[0].id); mapView.Labels.remove(event.labels[0]); } }); ``` ## Interactive Markers Markers are another component that can be set to interactive, allowing them to be clickable and have an app act on the click event. The `interactive` property of a Marker can be set to `true`, `false` or `pointer-events-auto`. - `false` - The Marker is not interactive. - `true` - The Marker is interactive and the click event is captured by the `mapView.on('click')` event handler. - `pointer-events-auto` - The Marker is interactive and mouse events are passed to the Marker's HTML content. The code sample below adds a marker to each space with a name and sets the marker's interactivity to true. ```ts // Add a marker on each space with a name and make the markers interactive. mapData.getByType('space').forEach((space) => { if (space.name) { mapView.Markers.add(space, `<div>${space.name}</div>`, { interactive: true, }); } }); ``` After enabling interactivity, click events on the marker can be captured using Mapview.on('click') when using Mappedin JS or useEvent('click') when using Mappedin SDK for React. The TEvents object passed into the `on` or `useEvent` method contains an array of markers that were clicked. The following code sample captures the click event and checks if the user clicked on a marker. If they did, it logs the id of the marker that was clicked and removes it from the map. ```ts // Act on the click event to log the id of the marker that was clicked and then remove it. mapView.on('click', (event: TClickPayload) => { if (event?.markers?.length > 0) { console.log('Clicked on marker: ' + event.markers[0].id); mapView.Markers.remove(event.markers[0]); } }); ``` ```tsx useEvent('click', (event: TClickPayload) => { if (event?.markers?.length > 0) { console.log('Clicked on marker: ' + event.markers[0].id); mapView.Markers.remove(event.markers[0]); } }); ``` ## Handling Click and Hover Events Mappedin JS enables capturing and responding to click and hover events through its event handling system. After enabling interactivity, click or hover events on the space can be captured using MapView.on('click') or MapView.on('hover') when using Mappedin JS or useEvent('click') or useEvent('hover') when using Mappedin SDK for React. The TEvents `click` and `hover` events pass TClickPayload that contains the objects the user clicked or hovered on. It allows developers to obtain information about user interactions with various elements on the map, such as: 1. **coordinate** - A Coordinate object that contains the latitude and longitude of the point where the user clicked. 2. **facades** - An array of Facade objects that were clicked. 3. **floors** - An array of Floor objects. If the map contains multiple floors, floors under the click point are included. 4. **labels** - An array of Label objects that were clicked. 5. **markers** - An array of Marker objects that were clicked. 6. **models** - An array of Model objects that were clicked. 7. **objects** - An array of MapObject objects that were clicked. 8. **paths** - An array of Path objects that were clicked. 9. **pointerEvent** - A PointerEvent object that contains the pointer event data. 10. **shapes** - An array of Shape objects that were clicked. 11. **spaces** - An array of Space objects that were clicked. Use `Mapview.on('click')` or `Mapview.on('hover')` to capture click or hover events using Mappedin JS or `useEvent('click')` or `useEvent('hover')` to capture click or hover events using Mappedin SDK for React as shown below. ```ts mapView.on('click', (event: TClickPayload) => { console.log("The user clicked!"); }); mapView.on('hover', (event: THoverPayload) => { console.log("The user hovered!"); }); ``` ```tsx useEvent('click', (event: TClickPayload) => { console.log("The user clicked!"); }); useEvent('hover', (event: THoverPayload) => { console.log("The user hovered!"); }); ``` The following code sample captures the click event and checks if the user clicked on a space. If they did, it creates a label with the space name. If the click was not on a space, it creates a label with the latitude and longitude the user clicked on. ```ts // Act on the click event to create a label with the space name that was clicked. // If the space has no name, use the coordinate. mapView.on('click', (event: TClickPayload) => { const spaceName = event?.spaces?.[0]?.name; if (spaceName) { mapView.Labels.add(event.coordinate, spaceName); } else { mapView.Labels.add(event.coordinate, 'Clicked: Lat: ' + event.coordinate.latitude + ' Lon: ' + event.coordinate.longitude); } }); ``` The next CodeSanbox uses interactive markers and the `click` event. When a marker is clicked, it is removed. ### Labels # Labels > > Labels display text and images anchored to specific points on a map. They rotate, show, or hide based on priority and zoom level, providing information about location names, points of interest, and more. Effective labels help apps convey additional information, such as room names, points of interest, main entrances, or other useful contextual details. !Mappedin JS v6 Labels > Note that the MapView class implements the Labels interface and exposes it as MapView.Labels. Use MapView.Labels to utilize Labels' methods. ## Video Walkthrough ## Displaying All Labels > This is an experimental feature that may undergo significant changes, including breaking changes, renaming, or complete removal in future versions. Do not rely on this API in production environments. To display all labels that have been added to the source map, use the __EXPERIMENTAL__all() method of the Labels interface. The following code sample demonstrates use of the `__EXPERIMENTAL__all()` method: ```ts // Add all the labels to the map. mapView.Labels.__EXPERIMENTAL__all() ``` To remove all Labels, use the removeAll() method. ```ts // Remove all the labels from the map. mapView.Labels.removeAll(); ``` ## Adding & Removing Individual Labels Labels can be added individually to a map by calling Labels.add(). A label can be added to any IAnchorable target. Refer to the Labels.add() documentation for the complete list of targets. The following code sample adds a label to each space that has a name: ```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, { 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 => ( <Label key={space.externalId} target={space} text={space.name} // label text options={{ interactive: true }} // makes the label interactive /> ))} </> ); ``` Labels can be removed by using the Labels.remove(label) method, passing in the label to be removed as shown below: ```ts // Remove a label. mapView.Labels.remove(label); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/classes/default.Labels.html#remove mapView.Labels.remove(label); ``` ## Interactive Labels Labels added to the map can be set to interactive to allow users to click on them. For more information, refer to the Interactive Labels section of the Interactivity Guide. ## Label Icons Icons can be added to labels in `SVG`, `PNG`, `JPEG` and `WebP` formats. Icons are clipped in a circle to prevent overflow. Three clipping methods of `contain`, `fill` and `cover` can be set in the TLabelAppearance.iconFit parameter with `contain` being the default. | Fill | Contain | Cover (default) | | ----------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------- | | !Floating Label fill | !Floating Label contain | !Floating Label cover | The TLabelAppearance.iconPadding property sets the amount of space between the icon and the border. The icon may shrink based on this value. | `padding: 0` | `padding: 10` | | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | | !Floating Label fill with 0 padding | !Floating Label fill with 10 padding | Label icons can be configured to be shown or hidden based on the current zoom level using TLabelAppearance.iconVisibleAtZoomLevel. A value below 0 will result in the icon always being hidden. Setting this value to 0 ensures icons show up at maxZoom (fully zoomed in) and 1 configures them to always be displayed. ## Label Appearance Labels can have their appearance styled to match the visual theme of an app or to make groups of labels easily distinguishable from one another. The following type declaration of TLabelAppearance describes these customisable attributes. | Option | Type | Description | Default | |--------|------|-------------|---------| | `margin` | `number` | Margin around the label text and pin in pixels. This will affect label density. Minimum is 6px. | 6 | | `maxLines` | `number` | Number of lines to display when text spans multiple lines. | 2 | | `textSize` | `number` | Text size in pixels | 11.5 | | `maxWidth` | `number` | Maximum width of text in pixels. | 150 | | `lineHeight` | `number` | Line height sets the height of a line box. It's commonly used to set the distance between lines of text. | 1.2 | | `color` | ColorString | A ColorString for the label text and pin. | `#333` | | `outlineColor` | ColorString | A ColorString for the outline around the label text and pin. | `white` | | `textColor` | ColorString | A ColorString representing just the text color. Defaults to the same as `color`. | - | | `textOutlineColor` | ColorString | A ColorString representing just the text outline. Defaults to the same as `outlineColor`. | - | | `pinColor` | ColorString | A ColorString representing just the pin color. Defaults to the same as `color`. | - | | `pinOutlineColor` | ColorString | A ColorString representing just the pin outline. Defaults to the same as `outlineColor`. | - | | `pinColorInactive` | ColorString | A ColorString representing just the pin color when the label is inactive. Defaults to the same as `pinColor`. | - | | `pinOutlineColorInactive` | ColorString | A ColorString representing just the pin outline when the label is inactive. Defaults to the same as `pinOutlineColor`. | - | | `icon` | `string` | An icon to be placed inside the label pin. Can be an SVG string or a path to a PNG or JPEG. | - | | `iconSize` | `number` | Size of the icon in pixels. Requires `icon` to be set. | 20 | | `iconScale` | `number` \| Interpolation | Scale the icon uniformly. Specify a number or an Interpolation object. | 1 | | `iconPadding` | `number` | Padding between the icon and the marker's border in pixels. | 2 | | `iconFit` | `'fill'` \| `'contain'` \| `'cover'` | How the icon should fit inside the marker. Options: `fill` (stretch to fill), `cover` (maintain aspect ratio and fill), `contain` (maintain aspect ratio and fit inside). | `cover` | | `iconOverflow` | `'visible'` \| `'hidden'` | Whether the icon should overflow the circle of the marker. Options: `visible`, `hidden`. | `hidden` | | `iconVisibleAtZoomLevel` | `number` | Defines the zoom level at which the icon becomes visible. Infinity ensures never visible, -Infinity ensures always visible. | -Infinity | These CodeSandbox examples shows how to customize label appearances: - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin JS The code sample below demonstrates how to use some label styling options. It specifies an SVG icon to use and sets the icon and text color. Labels are added to each space with a name. ```tsx // SVG image for the label's icon. const icon = `<svg width="92" height="92" viewBox="-17 0 92 92" fill="none" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0)"> <path d="M53.99 28.0973H44.3274C41.8873 28.0973 40.7161 29.1789 40.7161 31.5387V61.1837L21.0491 30.7029C19.6827 28.5889 18.8042 28.1956 16.0714 28.0973H6.5551C4.01742 28.0973 2.84619 29.1789 2.84619 31.5387V87.8299C2.84619 90.1897 4.01742 91.2712 6.5551 91.2712H16.2178C18.7554 91.2712 19.9267 90.1897 19.9267 87.8299V58.3323L39.6912 88.6656C41.1553 90.878 41.9361 91.2712 44.669 91.2712H54.0388C56.5765 91.2712 57.7477 90.1897 57.7477 87.8299V31.5387C57.6501 29.1789 56.4789 28.0973 53.99 28.0973Z" fill="white"/> <path d="M11.3863 21.7061C17.2618 21.7061 22.025 16.9078 22.025 10.9887C22.025 5.06961 17.2618 0.27124 11.3863 0.27124C5.51067 0.27124 0.747559 5.06961 0.747559 10.9887C0.747559 16.9078 5.51067 21.7061 11.3863 21.7061Z" fill="white"/> </g> <defs> <clipPath id="clip0"> <rect width="57" height="91" fill="white" transform="translate(0.747559 0.27124)"/> </clipPath> </defs> </svg>`; // Define colors for labels const colors = ['blue', 'pink', 'green', 'orange', 'tomato', 'gray']; // Label all spaces with its space name and add styling using a custom color and SVG icon. mapData.spaces.forEach((space, index) => { if (space.name) { const color = colors[index % colors.length]; mapView.Labels.add(space, space.name, { appearance: { color: color, icon: icon }, }); } }); ``` ```tsx // SVG image for the label's icon. const icon = `<svg width="92" height="92" viewBox="-17 0 92 92" fill="none" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0)"> <path d="M53.99 28.0973H44.3274C41.8873 28.0973 40.7161 29.1789 40.7161 31.5387V61.1837L21.0491 30.7029C19.6827 28.5889 18.8042 28.1956 16.0714 28.0973H6.5551C4.01742 28.0973 2.84619 29.1789 2.84619 31.5387V87.8299C2.84619 90.1897 4.01742 91.2712 6.5551 91.2712H16.2178C18.7554 91.2712 19.9267 90.1897 19.9267 87.8299V58.3323L39.6912 88.6656C41.1553 90.878 41.9361 91.2712 44.669 91.2712H54.0388C56.5765 91.2712 57.7477 90.1897 57.7477 87.8299V31.5387C57.6501 29.1789 56.4789 28.0973 53.99 28.0973Z" fill="white"/> <path d="M11.3863 21.7061C17.2618 21.7061 22.025 16.9078 22.025 10.9887C22.025 5.06961 17.2618 0.27124 11.3863 0.27124C5.51067 0.27124 0.747559 5.06961 0.747559 10.9887C0.747559 16.9078 5.51067 21.7061 11.3863 21.7061Z" fill="white"/> </g> <defs> <clipPath id="clip0"> <rect width="57" height="91" fill="white" transform="translate(0.747559 0.27124)"/> </clipPath> </defs> </svg>`; // Define colors for labels const colors = ['blue', 'pink', 'green', 'orange', 'tomato', 'gray']; // Get all spaces with names const spaces = mapData.getByType('space').filter(space => space.name); return ( <> {spaces.map((space, index) => { const color = colors[index % colors.length]; // Cycle through colors const icon = `<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="${color}" /></svg>`; return ( <Label key={space.externalId} target={space} text={space.name} // Label text options={{ appearance: { color: color, icon: icon, // Custom SVG icon }, interactive: true, // Make the label interactive }} /> ); })} </> ); ``` ## Label Ranking Ranking can be added to labels to control which label will be shown when more than one label occupies the same space. The label with the highest rank will be shown. If labels do not overlap, ranking will have no effect. Rank values are `low`, `medium`, `high`, and `always-visible` and are defined in TCollisionRankingTier. The code below adds labels where a user clicks on the map. The label rank is cycled with each click. If the user clicks and adds multiple labels in the same location, the label with the highest rank is shown and lower ranking labels are hidden. ```ts const RANKS: TCollisionRankingTier[] = ['low', 'medium', 'high', 'always-visible']; let currentRank = 0; // Act on the click event and add a label with a cycling rank. Observe how higher ranking labels are shown when they collide with lower ranking labels. mapView.on('click', (event: TClickPayload) => { currentRank++; // There are 4 possible ranks. If rank exceeds 3, reset it to 0. if (currentRank > 3) { currentRank = 0; } // Add a label with the current rank at the clicked coordinate. mapView.Labels.add(event.coordinate, 'Label Rank ' + RANKS[currentRank], { rank: RANKS[currentRank], }); }); ``` ```tsx const [labels, setLabels] = useState< { coordinate: Mappedin.Coordinate; text: string; rank: Mappedin.TCollisionRankingTier; }[] >([]); const RANKS: Mappedin.TCollisionRankingTier[] = ['low', 'medium', 'high', 'always-visible']; const [currentRank, setCurrentRank] = useState(0); // Handle map clicks to add a label with a cycling rank useEvent('click', event => { const newRank = (currentRank + 1) % RANKS.length; // Cycle through ranks setCurrentRank(newRank); const newLabel = { coordinate: event.coordinate, text: `Label Rank ${RANKS[newRank]}`, rank: RANKS[newRank], }; setLabels(prevLabels => [...prevLabels, newLabel]); }); return ( <> {labels.map((label, index) => ( <Label key={index} // Use index as a fallback key for simplicity target={label.coordinate} // Add the label to the clicked coordinate text={label.text} options={{ rank: label.rank, // Set the rank for the label }} /> ))} </> ); ``` The CodeSandbox examples below demonstrates how to add labels with a cycling rank. Click on the map to add a new label, and observe how the rank cycles with each click: - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin React ## Enabling and Disabling Labels Labels can be dynamically enabled or disabled using `mapView.updateState()` if using Mappedin JS or the `enabled` option in the React SDK. When a label is disabled, it will be hidden from view but remain in the map's memory. This is useful for managing label visibility based on conditions like zoom level or user interaction, without the overhead of repeatedly adding and removing labels. For Mappedin JS, use `mapView.getState()` to check a label's current state, which returns the label's current properties including its enabled status. Here's an example on how to enable/disable labels on click: ```ts let coordinate; let label; mapView.on("click", (event: TClickPayload) => { // first click - add label if (!label) { console.log("Adding label at:", event.coordinate); coordinate = event.coordinate; label = await mapView.Labels.add( coordinate, "Click to Toggle!", { interactive: true, enabled: true } ); } else { // get current state of label const labelState = mapView.getState(label); console.log("Current Label State:", labelState); // toggle enabled state const newState = !labelState.enabled; mapView.updateState(label, { enabled: newState }); console.log(`Label is now ${newState ? "enabled" : "disabled"}`); } }); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/types/default.TAddLabelOptions.html const [isEnabled, setIsEnabled] = useState(false); return ( <Label target={space} text="Dynamic Label" options={{ enabled: isEnabled // label will be hidden when false }} /> ); ``` Here are two CodeSandbox examples that demonstrate how to enable/disable labels on mouse clicks: - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin JS A common use case is showing different sets of labels based on zoom level: ```ts // init secondary labels - hidden until zoomed in secondaryLabels = mapData .getByType("space") .filter((space: Space) => space.name !== "") .slice(40, 60) .map((space: Space) => ({ target: space, text: "Secondary: " + space.name, options: { interactive: true, enabled: true }, labelInstance: undefined, })); // add secondary labels, then immediately disable them secondaryLabels.forEach(async (label) => { label.labelInstance = await mapView.Labels.add( label.target, label.text, label.options ); // store ref and disable initially mapView.updateState(label.labelInstance, { enabled: false }); }); // handle camera change to toggle secondary labels mapView.on("camera-change", (transform) => { if (transform.zoomLevel < 19) { secondaryLabels.forEach((label) => { if (label.labelInstance) { mapView.updateState(label.labelInstance, { enabled: false }); } }); } else { secondaryLabels.forEach((label) => { if (label.labelInstance) { mapView.updateState(label.labelInstance, { enabled: true }); } }); } }); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/types/default.TAddLabelOptions.html const [secondaryLabels, setSecondaryLabels] = useState( spaces.slice(5).map(space => ({ target: space, text: space.name, enabled: false })) ); useEvent('camera-change', (transform) => { setSecondaryLabels(labels => labels.map(label => ({ ...label, enabled: transform.zoomLevel >= 20 })) ); }); return ( <> {/* primary labels - always visible */} {spaces.slice(0, 5).map(space => ( <Label key={space.id} target={space} text={space.name} options={{ enabled: true }} /> ))} {/* secondary labels - conditionally visible */} {secondaryLabels.map(label => ( <Label key={label.target.id} target={label.target} text={label.text} options={{ enabled: label.enabled }} /> ))} </> ); ``` Here are two CodeSandbox examples that demonstrate how to enable/disable labels based on camera zoom levels: - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin React ### Location Profiles & Categories # Location Profiles & Categories > > A Location refers to something that represents a physical position on the map, such as an annotation, area, connection (e.g. stairs or elevator), door, point of interest or space. Locations may have a LocationProfile attached to them. The profiles contain information about the location such as its name, description, social media links, opening hours, logo and more. A LocationProfile can have zero to many LocationCategory attached to it. A LocationCategory may have a child or parent category. For example, the parent `Food & Drink` LocationCategory has children such as `Bar`, `Cafe`, `Food Court` and `Mediterranean Cuisine`. ## LocationProfile A LocationProfile can be accessed by using the `locationProfiles` property of a location. For example, use Space.locationProfiles to access every LocationProfile of a space. Every LocationProfile can also be access using the MapData.getByType() method as shown below. ```ts const locationProfiles = mapData.getByType('location-profile'); ``` ## LocationCategory To access the LocationCategory of a LocationProfile, use the LocationProfile.categories property. Note that a LocationCategory can have either parent or child categories. These are accessible using the LocationCategory.parent and LocationCategory.children properties respectively. Every LocationCategory can also be access using the MapData.getByType() method as shown below. ```ts const locationCategories = mapData.getByType('location-category'); ``` ## Example The following CodeSandbox example demonstrates how to show every LocationCategory that contains a LocationProfile and some LocationProfile details. Both the parent and child LocationCategory name is displayed in the headers. The `LocationProfile.name`, `LocationProfile.description` and `LocationProfile.logo` properties are used to display the name, description and logo of the restaurant. Note how the LocationProfiles for restaurants exist in multiple LocationCategories, having both a category called `Restaurant` and another that describes the type of cuisine they serve, such as `Italian Cuisine`. ### Mappedin MapLibre Overlay # Mappedin MapLibre Overlay > > Mappedin JS makes use of MapLibre to render the outdoor portion of the map. The SDK exposes this layer through MapView.OutoorMap and can be used to add additional layers as described in the Outdoor Map Guide. It is also possible to utilize the opposite architecture, by adding a MapView to a MapLibre Map. This is accomplished by using the MappedinMapLibreOverlay class. It allows a developer to easily add a Mappedin indoor map to an existing MapLibre based app. ## Example The following example demonstrates the use of MappedinMapLibreOverlay. A MapLibre Map is created, using the indoor map's center point as it's initial position. Once the MapLibre map is loaded, a MappedinMapLibreOverlay is added using the MapLibre addControl method. When the map is clicked, the current MappedinMapLibreOverlay is removed and a new one added to show an indoor map of a different building. The map is then moved to the center point of the new indoor map. ```ts // See Trial API key Terms and Conditions // /docs/demo-keys-and-maps const KEY = 'mik_yeBk0Vf0nNJtpesfu560e07e5'; const SECRET = 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022'; // Get MapData for 2 maps. const maps = await Promise.all([ await getMapData({ key: KEY, secret: SECRET, mapId: '660c0c3aae0596d87766f2da', }), await getMapData({ key: KEY, secret: SECRET, mapId: '660c0c6e7c0c4fe5b4cc484c', }), ]); let currentOverlay: MappedinMapLibreOverlay; // Instantiate a MapLibreMap and center it on the first indoor map's center point. // Use Mappedin's default outdoor style. const map = new MapLibreMap({ container: 'mappedin-map', style: 'https://tiles-cdn.mappedin.com/styles/mappedin/style.json', center: [maps[0].mapCenter.longitude, maps[0].mapCenter.latitude], zoom: 18, transformRequest: (url: string) => { return { url: url, headers: { 'x-mappedin-tiles-key': maps[0].outdoorViewToken, }, }; }, }); // Once the MapLibreMap is loaded, add a MappedinMapLibreOverlay to display // the indoor map. map.on('load', () => { currentOverlay = createMapLibreOverlay(maps[0]); map.addControl(currentOverlay); currentOverlay.on('loaded', async ({ mapView }: { mapView: MapView }) => { mapView.auto(); map.flyTo({ center: [maps[0].mapCenter.longitude, maps[0].mapCenter.latitude], curve: 1.42, }); }); }); let i = 1; // When the map is clicked, fly to the other indoor map's center point // and display it. map.on('click', async () => { const mapData = maps[i]; // Remove the existing MappedinMapLibreOverlay. if (currentOverlay) { map.removeControl(currentOverlay); } currentOverlay = createMapLibreOverlay(mapData); // Add the other indoor map. map.addControl(currentOverlay); currentOverlay.on('loaded', async ({ mapView }: { mapView: MapView }) => { mapView.auto(); map.flyTo({ center: [mapData.mapCenter.longitude, mapData.mapCenter.latitude], curve: 1.42, }); }); i++; if (i >= maps.length) { i = 0; } }); ``` ### Markers # Markers > > Mappedin JS allows adding and removing Marker on a map. Markers are elements containing HTML that Mappedin JS anchors to any IAnchorable target. They are automatically rotated and repositioned when the camera moves. !Mappedin JS v6 Markers > Note that the MapView class implements the Markers interface and exposes it as MapView.Markers. Use MapView.Markers to utilize Markers' methods. ## Video Walkthrough ## Creating Markers Markers are added to the map by referencing a target that can be a Door, Space, Coordinate or any IAnchorable target. The following code sample adds a marker to a coordinate. ```ts mapView.Markers.add(coordinate, '<div>This is a Marker!</div>'); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/functions/Marker.html // Get all spaces with names const spaces = mapData.getByType('space').filter(space => space.name); // Create markers for all spaces with its space name and make them interactive return ( <> {spaces.map(space => ( <Marker key={space.externalId} target={space} text={space.name} // marker text options={{ interactive: true }} /> ))} </> ); ``` ## Placement Marker placement is set using the `options` argument of Markers.add(). Options accepts a TAddMarkerOptions value, which has a member named `placement`. When provided, the point of the Marker described by the `placement` is placed next to the target. For example, using `center` will place the Marker's center at the target. Both a single placement and an array of placements can be provided. When an array is provided, the Marker is placed at the first target that has space available to display the Marker. Placement positions are defined in TMarkerPlacement which contains 9 values. The placement points are as follows, with the default being center. ### TMarkerPlacement - `center` - `top` - `left` - `bottom` - `right` - `top-left` - `top-right` - `bottom-left` - `bottom-right` Marker content can be styled using CSS that references the placement of the marker. This can allow the creation of a tooltip or bubble-style marker that points to its target. This is done by using the CSS Selector: ```css .mappedin-marker[data-placement='<PLACEMENT>']; ``` Where `` is the placement position. Here is an example of adding a triangle pointer to the Marker's target of `left`: ```CSS .marker:before { content: ''; width: 0; height: 0; top: calc(50% - 10px); left: -10px; z-index: 1; position: absolute; border-bottom: 10px solid transparent; border-top: 10px solid transparent; z-index: -1; } .mappedin-marker[data-placement="left"] .marker:before { left: auto; right: -5px; border-left: 10px solid #333333; } ``` Here are two CodeSandbox examples that demonstrate how to use markers with custom placements: - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin React ## Removing Markers Markers can be removed individually by using the Markers.remove(marker) method, passing in the marker to be removed as shown below. ```ts mapView.Markers.remove(marker); ``` ```ts // https://docs.mappedin.com/react/v6/latest/classes/default.Markers.html#remove useEvent("click", (event: TClickPayload) => { // if a marker is clicked, remove it if (event.markers.length > 0) { const clickedMarkerId = event.markers[0].id; mapView.Markers.remove(event.markers[0]); } }) ``` To remove all markers from a map, call Markers.removeAll(). ```ts mapView.Markers.removeAll(); ``` ```ts // https://docs.mappedin.com/react/v6/latest/classes/default.Markers.html#removeall mapView.Markers.removeAll(); ``` The CodeSandbox examples below demonstrates how to add and remove markers interactively. Click on a room to place a marker, and click on an existing marker to remove it. - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin JS ## Marker Rank Ranking can be added to markers to control which marker will be shown when more than one marker occupies the same space. The marker with the highest rank will be shown. If markers do not overlap, ranking will have no effect. Rank values `low`, `medium`, `high` and `always-visible` as defined in TCollisionRankingTier. The code below adds markers where a user clicks on the map. The marker rank is cycled with each click. If the user clicks and adds multiple markers in the same location, the marker with the highest rank is shown and lower ranking markers are hidden. ```ts const RANKS = ['medium', 'high', 'always-visible'] as const; let rank = 0 as number; // Act on the click event and add a marker with a cycling rank. Observe how higher ranking markers are shown when they collide with lower ranking markers. mapView.on('click', (event: TClickPayload) => { // TCollisionRankingTier contains 3 values. If rank exceeds 2, reset it to 0. const markerTemplate = ` <div> <style> .marker { display: flex; align-items: center; background-color: #fff; max-height: 64px; border: 2px solid grey; padding: 4px 12px; font-weight: bold; font-family: sans-serif; } .marker img { max-width: 64px; max-height: 32px; object-fit: contain; margin-right: 12px; } </style> <div class="marker"> <p>Marker Rank: ${RANKS[rank]}</p> </div> </div>`; // Add a marker with the current rank at the clicked coordinate. mapView.Markers.add(event.coordinate, markerTemplate, { rank: RANKS[rank], }); rank++; if (rank === RANKS.length) { rank = 0; } }); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/functions/Marker.html const [markers, setMarkers] = useState<{ coordinate: Mappedin.Coordinate; text: string; rank: Mappedin.TCollisionRankingTier; }[]>([]); // define available ranks and current rank state const RANKS: Mappedin.TCollisionRankingTier[] = ['medium', 'high', 'always-visible']; const [currentRank, setCurrentRank] = useState(0); // handle map clicks to add a marker with a cycling rank useEvent('click', (event: TClickPayload) => { if (!mapView) return; // cycle through rank values const newRank = (currentRank + 1) % RANKS.length; setCurrentRank(newRank); // create a new marker with the current rank const newMarker = { coordinate: event.coordinate, text: `Marker Rank ${RANKS[newRank]}`, rank: RANKS[newRank], }; // add new marker to state setMarkers((prevMarkers) => [...prevMarkers, newMarker]); }); // render markers using the Marker component return ( <> {markers.map((marker, index) => ( // note: <Marker> component generates a .mappedin-marker class <Marker key={index} target={marker.coordinate} options={{ interactive: true, rank: marker.rank }} > <div style={{ borderRadius: '10px', backgroundColor: '#fff', padding: '5px', boxShadow: '0px 0px 1px rgba(0, 0, 0, 0.25)', fontFamily: 'sans-serif', textAlign: 'center' }}> <p style={{ margin: 0, fontSize: '14px' }}> {marker.text} </p> <p style={{ margin: 0, fontSize: '10px', color: '#666' }}> Rank: {marker.rank} </p> </div> </Marker> ))} </> ); ``` ## Moving Markers Markers can be repositioned after they have been added to a map. There are two ways of doing this: - Markers.setPosition() instantly repositions a Marker - Markers.animateTo() animates a Marker to a new position The Marker's position can be set to a Coordinate, Space or Door. The `animateTo` method takes an `options` parameter of TAnimationOptions that defines the animation duration and TEasingFunction, which can be `linear`, `ease-in`, `ease-out` or `ease-in-out`. - **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. The code samples below displays a custom Marker when the map is first clicked. When the map is clicked again, the Marker is animated to the clicked location using the default animation options. ```ts let marker: Marker; mapView.on('click', (event: TClickPayload) => { if (marker) { mapView.Markers.animateTo(marker, event.coordinate); } else { marker = mapView.Markers.add( event.coordinate, 'Marker', { interactive: true, placement: 'right' } ); } }); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/functions/AnimatedMarker.html export default function AnimatedMarkers() { const { mapData } = useMap(); const [markerCoordinate, setMarkerCoordinate] = useState<Mappedin.Coordinate | null>( mapData?.mapCenter || null ); // handle map clicks to update the marker's coordinate useEvent("click", (event: TClickPayload) => { setMarkerCoordinate(event.coordinate); }); // render <AnimatedMarker/> with updated coordinates return ( <> {markerCoordinate && ( <AnimatedMarker target={markerCoordinate} duration={1000} options={{ interactive: true, anchor: "right" }} > <img src="https://k2mgpc.csb.app/logo.png" alt="Marker" style={{ width: "25px", position: "relative", top: "-10px", left: "-13px", }} /> </AnimatedMarker> )} </> ); } ``` The CodeSandbox examples below demonstrates how to display and animate a custom marker. Click around the map to animate the marker to the clicked location using default animation options: - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin React ## Enabling and Disabling Markers Markers can be dynamically enabled or disabled using MapView.updateState() if using Mappedin JS or the `enabled` option in the React SDK. When a marker is disabled, it will be hidden from view but remain in the map's memory. This is useful for managing marker visibility based on conditions like zoom level or user interaction, without the overhead of repeatedly adding and removing markers. For Mappedin JS, use MapView.getState() to check a marker's current state, which returns the marker's current properties including its enabled status. Here's an example on how to enable/disable markers on click: ```ts let coordinate; let marker: Marker; mapView.on("click", (event: TClickPayload) => { // first click - add marker if (!marker) { console.log("Adding marker at:", event.coordinate); coordinate = event.coordinate; marker = mapView.Markers.add( coordinate, "<div class='markerContainer'>Enabled Marker!</div>" ); } else { // get current state of marker const markerState = mapView.getState(marker); console.log("Current Marker State:", markerState); // toggle enabled state const newState = !markerState.enabled; mapView.updateState(marker, { enabled: newState }); console.log(`Marker is now ${newState ? "enabled" : "disabled"}`); } }); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/types/default.TAddMarkerOptions.html const [isEnabled, setIsEnabled] = useState(true); const space = mapData?.getByType("space")[14]; useEvent("click", (event: TClickPayload) => { setIsEnabled(!isEnabled); }); return ( <Marker target={space} options={{ enabled: isEnabled, interactive: true }} > <div className="markerContainer"> Click to enable/disable marker </div> </Marker> ); ``` Here are two CodeSandbox examples that demonstrate how to enable/disable markers on mouse clicks: - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin JS A common use case is showing different sets of markers based on zoom level: ```ts // create two sets of markers from spaces const primaryMarkers = mapData .getByType("space") .filter((space: Space) => space.name !== "") .slice(0, 40) .map((space: Space) => mapView.Markers.add( space, `<div class="markerContainer">Primary: ${space.name}</div>`, { interactive: true, enabled: true } ) ); // add & store secondary markers with references const secondaryMarkers = mapData .getByType("space") .filter((space: Space) => space.name !== "") .slice(40, 60) .map(async (space: Space) => { const marker = await mapView.Markers.add( space, `<div class="markerContainer markerSecondary">Secondary: ${space.name}</div>`, { interactive: true } ); marker.markerInstance = markerObject; // store reference mapView.updateState(marker, { enabled: false }); // init hide secondary markers }); // update marker visibility based on zoom level mapView.on("camera-change", (transform: CameraTransform) => { if (transform.zoomLevel < 19) { secondaryMarkers.forEach((marker) => { if (marker.markerInstance) { mapView.updateState(marker.markerInstance, { enabled: false }); } }); } else { secondaryMarkers.forEach((marker) => { mapView.updateState(marker.markerInstance, { enabled: true }); }); } }); ``` ```tsx // https://docs.mappedin.com/react/v6/latest/types/default.TAddMarkerOptions.html const spaces = mapData.getByType('space') const [secondaryMarkers, setSecondaryMarkers] = useState( spaces.slice(5).map(space => ({ target: space, text: space.name, enabled: false })) ); useEvent('camera-change', (transform) => { setSecondaryMarkers(markers => markers.map(marker => ({ ...marker, enabled: transform.zoomLevel >= 20 })) ); }); return ( <> {/* primary markers - always visible */} {spaces.slice(0, 5).map(space => ( <Marker key={space.id} target={space} text={space.name} options={{ enabled: true }} /> ))} {/* secondary markers - conditionally visible */} {secondaryMarkers.map(marker => ( <Marker key={marker.target.id} target={marker.target} text={marker.text} options={{ enabled: marker.enabled }} /> ))} </> ); ``` Here are two CodeSandbox examples that demonstrate how to enable/disable markers based on camera zoom levels: - Mappedin JS - Mappedin React #### CodeSandbox - Mappedin React ## Marker Clusters When many markers are close together, they can overlap and become hard to distinguish. `Mappedin.MarkerCluster` groups nearby markers into a single "cluster" marker, helping to keep the map clean and easy to navigate. This improves map readability and gives users a clearer view of what’s on the map at different zoom levels. As users zoom in, clusters break apart to reveal individual markers, and as they zoom out, markers combine into clusters. Mappedin JS supports marker clustering through the @mappedin/marker-cluster package. ### Installation To use marker clustering, first install the @mappedin/marker-cluster package: ```bash yarn add @mappedin/marker-cluster ``` ### Basic Usage Here's an example of how to implement marker clustering with Mappedin JS. - Markers are added to various spaces on the map with associated images. - MarkerCluster is initialized with a custom cluster function that defines how cluster markers should appear. - Markers are added to the cluster to enable clustering behavior based on proximity. ```ts import { MarkerCluster, ClusterInformation } from '@mappedin/marker-cluster'; // keep track of each marker's custom data const markerProperties: Map<Marker, { src: string; name: string }> = new Map(); // add markers to map mapData.getByType('space').forEach(space: Space => { const randomImage = images.next().value; const marker = mapView.Markers.add(space, markerTemplate(randomImage.src), { rank: 'always-visible', }); markerProperties.set(marker, { src: randomImage.src, name: randomImage.name }); }); // create a cluster marker from grouped markers const clusterFn = (cluster: ClusterInformation) => { const markers = cluster.markers.map(m => markerProperties.get(m)!); return mapView.Markers.add(cluster.center, clusterMarkerTemplate(markers), { rank: 'always-visible', }); }; // create cluster manager and add markers const cluster = new MarkerCluster(mapView, clusterFn, { algorithmOptions: { threshold: 150, // distance in pixels to cluster }, }); for (const [marker] of markerProperties) { cluster.add(marker); } ``` #### CodeSandbox Example This CodeSandbox shows animal image markers clustered together when zoomed out. Zoom out and out to see clusters form and disperse dynamically. ### Migration Guide # Migration Guide Mappedin JS version 6.0 is a major release that includes a number of changes to the SDK. Mappedin JS v6 uses a GeoJSON-based rendering engine, rebuilt from the ground up. It works in unison with MapLibre which enables outdoor base maps as well as data visualization features. By using GeoJSON as the core data format, v6 can integrated with existing external data sources. This guide explains the steps needed to migrate from version 5. ## Initialization Changes The options for getting a map have changed. `getVenue()` has been replaced by `getMapData()`. v5 Initialization ```ts const options: TGetVenueOptions = { venue: 'mappedin-demo-mall', clientId: '5eab30aa91b055001a68e996', clientSecret: 'RJyRXKcryCMy4erZqqCbuB1NbR66QTGNXVE0x3Pg6oCIlUR1', }; async function init() { const venue = await getVenue(options); const mapView = await showVenue(element, venue); } ``` v6 Initialization ```ts const options = { key: '5eab30aa91b055001a68e996', secret: 'RJyRXKcryCMy4erZqqCbuB1NbR66QTGNXVE0x3Pg6oCIlUR1', mapId: 'mappedin-demo-mall', }; async function init() { const mapData = await getMapData(options); const mapView = await show3dMap(element, mapData); } ``` ## Component Access `Mappedin.` has been replaced by `MapData.getByType()` For example: In v5 access nodes using `Mappedin.nodes`, which contains an array of `Node` objects. In v6 access nodes using `MapData.getByType('node')`, which returns an array of `Node` objects. `Mappedin.getCollectionItemById` is replaced by `mapData.getById(, id)` The following classes have been redesigned. Refer to the chart below for a mapping of similar classes. | v5 Component | v6 Component | | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | MappedinPolygon | Space | | MappedinMapGroup | FloorStack | | MappedinMap | Floor | | MappedinCoordinate | Coordinate | | MappedinNode | Node | MapView.state no longer exists. These states are now available within their respective classes BlueDot.state and StackedMaps.state. ## UI Components FloatingLabels have been replaced with Labels. FloatingLabels.labelAllLocations() has been replaced with Labels.all(). `Tooltips` have not been carried over from v5 and Markers should be used instead. ## Updating UI Components `setPolygonColor`, `clearPolygonColor` and other methods of changing the state of UI objects on the map is now performed using MapView.updateState(), which also supports `initial` for returning properties to their original state. Camera.animate() has been renamed to Camera.animateTo(). Markers.animate() has been renamed to Markers.animateTo(). ## Interactivity MapView.addInteractivePolygonsForAllLocations() has been replaced by updating the state of each space to be interactive as shown below. ```ts // Set each space to be interactive. mapData.getByType('space').forEach(space => { mapView.updateState(space, { interactive: true, }); }); ``` Event names passed from `MapView.on()` have been changed: | v5 Event | v 6 Event | | --------------------------------------------------------------- | --------------------- | | `E_SDK_EVENT.CLICK` | `click` | | `E_SDK_EVENT.MAP_CHANGED , E_SDK_EVENT.MAP_CHANGED_WITH_REASON` | `floor-change` | | `E_SDK_EVENT.OUTDOOR_VIEW_LOADED` | `outdoor-view-loaded` | ## Stacked maps StackedMaps APIs have been simplified to make them easier to use. | v5 Method | v6 Method | | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | StackedMaps.showOverview() | StackedMaps.expand() | | StackedMaps.disable() | StackedMaps.collapse() | | StackedMaps.scrollToMap() | MapView.setFloor() | | StackedMaps.zoomIntoMap() | MapView.setFloor() followed by StackedMaps.collapse() with a duration of 0 | ## Map Metadata Classes containing enterprise map metadata have been renamed. | v5 Component | v6 Component | | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | MappedinCategory | EnterpriseCategory | | MappedinLocation | EnterpriseLocation | | MappedinVenue | EnterpriseVenue | ## Wayfinding & Directions MappedinNavigatable.directionsTo is replaced by MapData.getDirections. MapData.getDirections() only supports getting directions between two locations. For multi destination directions, use MapData.getDirectionsMultiDestination(). Journey is now Navigation. v5 Multi Destination Directions ```ts const directions = getDirections(start, [dest1, dest2]); ``` v6 Multi Destination Directions ```ts const directions = getDirectionsMultiDestination(start, [dest1, dest2]); ``` ## Camera v5 Camera.tilt in radians is replaced by v6 Camera.pitch in degrees. `Camera.minPitch` and `Camera.maxPitch` are now available to set the minimum and maximum pitch values. v5 Camera.rotation in radians is replace in v6 by Camera.bearing in degrees Clockwise rotation is now positive. v5 `Camera.zoom`, `Camera.minZoom` and `Camera.maxZoom` used meters from ground level. In v6 these now use mercator zoom level units. The following camera methods and accessors have been renamed. | v5 Method | v6 Method | | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | | Camera.position() | Camera.center() | | Camera.setSafeAreaInsets() | Camera.setScreenOffsets() | | Camera.getSafeAreaInsets() | Camera.screenOffsets() | | MapView.Camera.on(...) | MapView.on(...) | | E_CAMERA_EVENT.USER_INTERACTION_START | user-interaction-start | | E_CAMERA_EVENT.USER_INTERACTION_END | user-interaction-end | | E_CAMERA_EVENT.CHANGED | camera-change | ## Search OfflineSearch has been moved to MapData.search(). The results returned in SearchResult are more structured: ```ts const results = mapData.Search.query('levis'); // results { places: SearchResultPlaces[]; enterpriseLocations?: SearchResultEnterpriseLocations[]; enterpriseCategories?: SearchResultEnterpriseCategory[]; } ``` ## Multi Language The methods to work with maps in multiple languages have been changed. ```ts // Specify a non default language when getting map data. const options = { key: 'key', secret: 'secret', mapId: 'mapId', language: 'en', }; // Get supported languages. const supportedLanguages = mapData.getByType('enterprise-venue')[0].languages; // Change languages await mapData.changeLanguage('es'); ``` ## Blue Dot Blue Dot events have moved from BlueDot.on() to MapView.on(). v5 ```ts const blueDotChangeHandler = ({ map, nearestNode, position, bearing }) => { // Do something with the new values }; mapView.BlueDot.on(E_BLUEDOT_EVENT.POSITION_UPDATE, blueDotChangeHandler); ``` v6 ```ts // Position update mapView.on('blue-dot-position-update', e => { console.log('blue-dot-position-update', e.coordinate, e.floor, e.accuracy, e.heading); }); // State change mapView.on('blue-dot-state-change', e => { console.warn('blue-dot-state-change', e.state, e.action); }); // Error mapView.on('blue-dot-error', e => { console.error('blue-dot-error', e); }); ``` BlueDot.updateBearing() functionality has been moved to the BlueDot.update() method and is provided using the `heading` property. In v6, to remove the heading unset it in the `update` method as shown below, otherwise it will use the heading provided from the browser. ```ts BlueDot.update({ heading: undefined }); ``` PositionUpdater has been removed. The same functionality can be achieved by calling `BlueDot.enable({ watchBrowserPosition: false })` and then calling the BlueDot.update() method. Blue Dot rotation mode was set when enabling Blue Dot in v5 using TEnableBlueDotOptions.useRotationMode. In v6, this is set by calling BlueDot.follow and setting the TFollowMode. The v5 behavior of TEnableBlueDotOptions.allowImplicitFloorLevel has changed. In v5 it assumed the user was always on the default map if no level update was provided by the indoor positioning provider. In v6, if the floor cannot be determined Blue Dot will render on every floor by default. If the position update has a `floorLevel`, it will only render on that floor. To force it to render on all floors set `floorOrFloorId` to `undefined`: ```ts BlueDot.update({ floorOrFloorId: undefined }); ``` Blue Dot smoothing has not been carried over from v5. ## Dynamic Focus In Mappedin JS v5, the Dynamic Focus controller was always available as part of the MapView. Now, Dynamic Focus is a separate package which must be installed and initialized separately. With NPM: ```bash npm install @mappedin/dynamic-focus ``` With Yarn: ```bash yarn add @mappedin/dynamic-focus ``` Enabling Dynamic Focus: v5 ```ts const mapView = await showVenue(document.getElementById('mappedin-map')!, venue, { dynamicFocus: { baseMap: defaultMap, indoorsFullyVisibleZoomLevel: 18, setMapAtZoomLevel: 18, buildingFullyVisibleZoomLevel: 17, preloadDefaultMaps: true, }, }); ``` v6 ```ts // v6 - enable on instantiation const mapView = await show3dMap(...); const dynamicFocus = new DynamicFocus(mapView, { setFloorOnFocus: true, autoFocus: true }); // v6 - after instantiation dynamicFocus.updateState({ setFloorOnFocus: true, autoFocus: true }) ``` Disabling Dynamic Focus: v5 ```ts mapView.DynamicFocus.setMapOnUserInteraction = false; ``` v6 ```ts dynamicFocus.updateState({ autoFocus: false }); ``` Setting the default floor: v5 ```ts mapView.DynamicFocus.setDefaultMapForMapGroup(mapGroup, map); mapView.DynamicFocus.resetDefaultMapForMapGroup(mapGroup); ``` v6 ```ts dynamicFocus.setDefaultFloorForStack(floorStack, floor); dynamicFocus.resetDefaultFloorForStack(floorStack); ``` Setting the current floor: v5 - not implemented. v6 ```ts dynamicFocus.setCurrentFloorForStack(floorStack, floor); ``` Manually trigger focus: v5 ```ts mapView.DynamicFocus.update(); ``` v6 ```ts dynamicFocus.focus(); ``` ### Outdoor Map # Outdoor Map > > The outdoor map shown around the Mappedin indoor map can be manipulated to show or hide additional information. It can be used to add GeoJSON based geometry, images, deck.gl layers and more. The outdoor map is accessed using MapView.Outdoor.map, which returns a `maplibregl.Map` object. MapLibre GL JS is a TypeScript library that uses WebGL to render interactive maps from vector tiles in a browser. Mappedin JS makes use of it to display the outdoor map. By providing access to the MapLibre map used to draw the outdoor map, Mappedin JS enables developers to draw on and manipulate the outdoor map. Developers can use most capabilities of MapLibre GL JS with key exceptions being Interactivity and Camera, which are not available. > User touch and click events are handled by Mappedin JS and are not propagated to the outdoor map layer. Camera movement is also handled by Mappedin JS and cannot be manipulated using MapLibre controls. The remainder of this guide demonstrates a few examples of what could be achieved with the outdoor map. Please refer to the MapLibre GL JS documentation and MapLibre GL JS Examples for comprehensive instructions and demonstrations of what is possible with MapLibre. ## Styles Styles can be applied to the outdoor map to change its colors. Mappedin provides five pre-built styles to choose from. - Default: https://tiles-cdn.mappedin.com/styles/mappedin/style.json - Honeycrisp: https://tiles-cdn.mappedin.com/styles/honeycrisp/style.json - Fresh Mint: https://tiles-cdn.mappedin.com/styles/freshmint/style.json - Night Blue: https://tiles-cdn.mappedin.com/styles/midnightblue/style.json - Starlight: https://tiles-cdn.mappedin.com/styles/starlight/style.json To change the style used by the Outdoor map, pass the URL of the style in the outdoorView object of TShow3DMapOptions as shown below. ```ts const mapView = await show3dMap( document.getElementById('mappedin-map') as HTMLDivElement, mapData, { outdoorView: { style: 'https://tiles-cdn.mappedin.com/styles/freshmint/style.json', }, } ); ``` The outdoor style can also be changed at runtime using mapView.Outdoor.setStyle(). ```ts mapView.Outdoor.setStyle('https://tiles-cdn.mappedin.com/styles/freshmint/style.json'); ``` The CodeSandbox below demonstrates changing the Outdoor style while optionally changing the indoor style by changing the view. ## Adding Raster or Vector Maps Mappedin JS's outdoor map uses a road map to provide users with a reference point and to give them context of the area around buildings. An app may wish to display a different type of map, such as a satellite image or weather patterns. Mappedin JS allows an app to add additional map sources. Vector, raster and raster-dem sources can be used from providers such as Google, Azure, MapTiler or mapbox. ### Satellite Outdoor Map The following example demonstrates the use of a raster satellite image source from MapTiler. > The key used in this example is locked for use on developer.mappedin.com and the provided CodeSandbox. To customise this example, create a free MapTiler key at https://www.maptiler.com/ ### Adding a Raster Source The following code snippet adds a raster satellite map from MapTiler. The `addLayer` method passes `mappedin` as the name of the layer to draw below. This ensures that the layer is drawn under the indoor map. Remove it if you need to have your source rendered on top of the indoor map. ```ts // Get an instance of the outdoor map. const outdoorMap = mapView.Outdoor.map!; // On load of the outdoor map, add a satellite tiles layer outdoorMap.on('load', () => { try { // Add a raster tile source for satellite imagery from MapTiler. // Get your own free MapTiler key at https://www.maptiler.com/ outdoorMap.addSource('satelite', { type: 'raster', tiles: [ 'https://api.maptiler.com/maps/satellite/{z}/{x}/{y}@2x.jpg?key=<REPLACE_WITH_YOUR_KEY>', ], tileSize: 512, attribution: '© MapTiler © OpenStreetMap contributors', }); // Add a raster layer using the satellite source. // Have it show below the mappedin layer, which holds the indoor map. outdoorMap.addLayer( { id: 'satelite', type: 'raster', source: 'satelite', paint: {}, }, 'mappedin' ); } catch (error) { console.error('Error adding tile source or layer:', error); } }); ``` ### Adding a Vector Source The following code snippet adds a contour vector map from MapTiler. The `addLayer` method passes `mappedin` as the name of the layer to draw below. This ensures that the layer is drawn under the indoor map. Remove it if you need to have your source rendered on top of the indoor map. ```ts // Get an instance of the outdoor map. const outdoorMap = mapView.Outdoor.map!; // On load of the outdoor map, add a satellite tiles layer outdoorMap.on('load', () => { try { // Add a vector tile source for satellite imagery from MapTiler. // Get your own free MapTiler key at https://www.maptiler.com/ outdoorMap.addSource('contours', { type: 'vector', url: 'https://api.maptiler.com/tiles/contours/tiles.json?key=<REPLACE_WITH_YOUR_KEY>', }); // Add a raster layer using the satellite source. // Have it show below the mappedin layer, which holds the indoor map. outdoorMap.addLayer( { id: 'terrain-data', type: 'line', source: 'contours', 'source-layer': 'contour', layout: { 'line-join': 'round', 'line-cap': 'round', }, paint: { 'line-color': '#ff69b4', 'line-width': 1, }, }, 'mappedin' ); } catch (error) { console.error('Error adding tile source or layer:', error); } }); ``` ## Drawing GeoJSON Geometry on the Outdoor Map MapView.Outdoor.map allows drawing GeoJSON based geometry on top of the outdoor map using MapLibre. The following example demonstrates drawing a LineString on the outdoor map that represents an area under construction that should be avoided. Labels are drawn using Mappedin JS. ## 3D Buildings Outdoor Street Maps (OSM), which are used to create the outdoor map contain height and shape information for many buildings. This data can be used to render 3D buildings on the outdoor map. The example below demonstrates how to do this. A `3d-building` layer is added, which extrudes buildings based on their height in OSM. Then a filter is applied to the `3d-building` layer to avoid drawing the 3D building located at the indoor map location, preventing the indoor map from being obscured. ```ts import { useMap } from '@mappedin/react-sdk'; import { useRef } from 'react'; export function ThreeDBuildings() { const { mapView } = useMap(); const stylesheetId = useRef(''); // Get an instance of the MapLibre outdoor map. const map = mapView.Outdoor.map; // Once the outdoor map is loaded, load the 3D buildings. map.on('load', () => { const layers = map.getStyle().layers; const buildingLayer = layers.find((layer: { id: string }) => layer.id === 'building'); const color = buildingLayer?.paint?.['fill-color']; //Add the 3d-building layer above the mappedin layer. map.addLayer( { id: '3d-building', source: 'osm', 'source-layer': 'building', type: 'fill-extrusion', minzoom: 15, paint: { 'fill-extrusion-color': color, 'fill-extrusion-height': ['get', 'render_height'], 'fill-extrusion-base': ['get', 'render_min_height'], }, }, 'mappedin' ); }); // Filter the indoor map's building from the 3D buildings that // are drawn. This prevents the indoor map from being covered. map.on('styledata', e => { if ( map && e && e.dataType === 'style' && e.type === 'styledata' && map.getLayer('3d-building') ) { // If we can find the id we'll use it, otherwise assume it has changed. const newStylesheetId = e?.style?.stylesheet?.id; if (!newStylesheetId || newStylesheetId !== stylesheetId.current) { stylesheetId.current = newStylesheetId; map.setFilter('3d-building', map.getFilter('building')); } } }); return null; } ``` The CodeSandbox below shows this example in action. ## Using deck.gl In addition to using MapLibre methods, deck.gl can also be used to draw on the outdoor map using `deck.MapboxOverlay`. The next example shows how to take positional information in GeoJSON format that represents the off site positions of shopping carts and displays them on the map using a deck.gl `ScatterplotLayer`. ## Drawing an Image on the Outdoor Map Images can be drawn on top of the outdoor map. They are positioned using latitude and longitude coordinates that begin on the top left position and continue clockwise. The example below draws an image of a black parking symbol. The green parking symbol uses an image on a Marker created with Mappedin JS. Right click on the map and move to rotate it to observe how the image added to the outdoor map remains static while the image added to a Marker rotates with the map to always use the same perspective. The Marker also remains a constant size when the map is zoomed in and out while the image gets smaller and larger. ### Points of Interest # Points of Interest > > Points of Interest (POIs) are specific locations or features on a map that users find useful or informative. POIs serve as landmarks or markers, highlighting key places, services, or objects to enhance the map's utility and user experience. They are contained in the PointOfInterest class, which contains a coordinate, name, description, images, links and the floor the point of interest exists on. All of these elements are configured in the Mappedin Editor. !Mappedin JS v6 POIs ## Video Walkthrough PointOfInterest.name could be used to create labels to show users helpful information. The code sample below creates a label at the location of each PointOfInterest and uses its name as the label text. It also verifies that the PointOfInterest exists on the current floor being shown in the MapView. ```ts // Iterate through each point of interest and label it. for (const poi of mapData.getByType('point-of-interest')) { // Label the point of interest if it's on the map floor currently shown. if (poi.floor.id === mapView.currentFloor.id) { mapView.Labels.add(poi.coordinate, poi.name); } } ``` ```tsx // Get all points of interest const pois = mapData.getByType('point-of-interest'); // Create labels for each point of interest return ( <> {pois.map(poi => ( <Label key={poi.externalId} target={poi} text={poi.name} /> ))} </> ); ``` The following CodeSandbox demonstrates one possible use for PointOfInterest. It labels each PointOfInterest and uses Camera.animateTo() to create a flyby of each one. Reload the CodeSandbox to restart the animation. ### Release Notes # Release Notes ## System Requirements Mappedin JS is designed to work on a variety of devices, such as desktop & laptop computers, kiosks, smartphones and tablets. Hardware requirements such as CPU and memory will vary depending the size of the map and the display's size and resolution. A minimum of 2 GB available memory is recommended. The following requirements must also be met. **Graphics Hardware Requirements** - WebGL 2.0 Support **Supported Desktop Browsers** - Chrome 51+ - Safari 10+ - Edge 125+ - Firefox 115+ - Opera 190+ **Supported Mobile Browsers** - Chrome 127+ - Safari 14.0+ - Samsung Internet 25+ - Firefox for Android 127+ - Opera Mobile 80+ --- ## v6.0.0-rc.0 _August 7, 2025_ > This is the first release candidate for Mappedin JS v6. This version introduces significant breaking changes as the SDK transitions from beta to release candidate status. ### ⚠️ Breaking Changes #### show3dMap - The `TShow3dMapOptions` key `multiFloorView.floorHeight` has been renamed to `multiFloorView.floorGap`. ```ts // ❌ Before const mapView = await show3dMap(el, mapData, { multiFloorView: { floorHeight: 10, }, }); // ✅ After const mapView = await show3dMap(el, mapData, { multiFloorView: { floorGap: 10, }, }); ``` - Multi-floor View is now enabled by default. ```ts // ❌ Before const mapView = await show3dMap(el, mapData, { multiFloorView: { enabled: true, }, }); // ✅ After const mapView = await show3dMap(el, mapData); ``` - 2D occlusion is now enabled by default and the option has been removed from `show3dMap()`. ```ts // ❌ Before const mapView = await show3dMap(el, mapData, { occlusion: { enabled: true, }, }); // ✅ After const mapView = await show3dMap(el, mapData); ``` #### Paths - `getDirections()` is now asynchronous. ```ts // ❌ Before const directions = mapView.getDirections(...); // ✅ After const directions = await mapView.getDirections(...); ``` - Replaced `pathOptions.nearRadius`, `pathOptions.farRadius`, `pathOptions.nearZoom`, and `pathOptions.farZoom` with a unified `width` option. ```ts // ❌ Before mapView.Paths.add(path, { nearRadius: 0.5, farRadius: 1, nearZoom: 16, farZoom: 18 }); // ✅ After mapView.Paths.add(path, { width: 1 }); ``` #### Markers - Marker `anchor` property renamed to `placement`. ```ts // ❌ Before mapView.Markers.add( space, `<div>${...}</div>`, { anchor: 'left', }, ); // ✅ After mapView.Markers.add( space, `<div>${...}</div>`, { placement: 'left', }, ); ``` - Marker `dynamicResize` option is now enabled by default. ```ts // ❌ Before const marker = mapView.Markers.add(space, `<div>${...}</div>`, { dynamicResize: true, }); // ✅ After const marker = mapView.Markers.add(space, `<div>${...}</div>`); ``` #### Labels - Label `appearance` options have been flattened and simplified. ```ts // ❌ Before mapView.Labels.add(target, 'label', { appearance: { text: { foregroundColor: 'white', backgroundColor: 'black', }, marker: { foregroundColor: { active: 'white', inactive: 'white', }, backgroundColor: { active: 'black', inactive: 'black', }, }, }, }); // ✅ After mapView.Labels.add(target, 'label', { appearance: { color: 'white', outlineColor: 'black', }, }); ``` - `Labels.all()` has been renamed to `Labels.__EXPERIMENTAL__all()` to clearly indicate experimental status. ```ts // ❌ Before mapView.Labels.all(); // ✅ After mapView.Labels.__EXPERIMENTAL__all(); ``` - `auto()` methods have been renamed to `__EXPERIMENTAL__auto()`. ```ts // ❌ Before mapView.auto(); // ✅ After mapView.__EXPERIMENTAL__auto(); ``` #### Events - Click event keys are now all optional, with the exception of `coordinate` and `pointerEvent`. ```ts mapView.on('click', event => { const { spaces, objects, floors } = event; spaces.forEach(() => {}); objects.forEach(() => {}); floors.forEach(() => {}); // etc }); // ✅ After mapView.on('click', event => { const { spaces, objects, floors } = event; if (spaces) { spaces.forEach(() => {}); } if (objects) { objects.forEach(() => {}); } if (floors) { floors.forEach(() => {}); } // etc }); ``` - Hover event keys are now all optional, with the exception of `coordinate`. ```ts // ❌ Before mapView.on('hover', event => { const { spaces, objects, floors } = event; spaces.forEach(() => {}); objects.forEach(() => {}); floors.forEach(() => {}); // etc }); // ✅ After mapView.on('hover', event => { const { spaces, objects, floors } = event; if (spaces) { spaces.forEach(() => {}); } if (objects) { objects.forEach(() => {}); } if (floors) { floors.forEach(() => {}); } // etc }); ``` #### MapLibre Overlay - The experimental `createMapLibreOverlay()` function has been removed and will be published under a separate `@mappedin/maplibre-overlay` package. ```ts // ❌ Before import { createMapLibreOverlay } from '@mappedin/mappedin-js'; // ✅ After import { createMapLibreOverlay } from '@mappedin/maplibre-overlay'; ``` #### Visibility - Setting `opacity: 0` no longer implicitly sets `visible: false`. Use the `visible` property explicitly to remove an element from the scene. ```ts // ❌ Before mapView.updateState(space, { opacity: 0 }); mapView.getState(space).visible; // false // ✅ After mapView.updateState(space, { opacity: 0 }); mapView.getState(space).visible; // true mapView.updateState(space, { visible: false }); ``` #### Camera - Camera transform values are now rounded for stability and to reduce floating-point precision errors. ```ts // ❌ Before transform.center.latitude; // 43.52041666666667891234 transform.center.longitude; // -79.3827777777778123456 transform.zoomLevel; // 18.123456789012345 // ✅ After transform.center.latitude; // 43.5204167 (7 decimals) transform.center.longitude; // -79.3827778 (7 decimals) transform.zoomLevel; // 18.12346 (5 decimals) new CameraTransform(camera, { precision: -1 }); // Get raw values if needed ``` #### States - Improved the types and input validation for `getState` and `updateState`. As a result, many of the types have changed. ```ts // ❌ Before import type { TGetState, TUpdateState, TUpdateStates, // Removed TDoorsState, TFacadeState, TFloorState, TGeometryState, TImageState, TLabelState, TMarkerState, TModelState, UpdateModelState, // Removed TPathState, TPathUpdateState, TShapeState, TShapeUpdateState, Text3DState, // Removed UpdatableText3DState, // Removed TWallsState, TWallsUpdateState, } from '@mappedin/mappedin-js'; // ✅ After import type { TUpdateState, TGetState, TDoorsState, TDoorsUpdateState, // Added TFacadeState, TFacadeUpdateState, // Added TFloorState, TFloorUpdateState, // Added TGeometryState, TGeometryUpdateState, // Added TImageState, TImageUpdateState, // Added TLabelState, TLabelUpdateState, // Added TMarkerState, TMarkerUpdateState, // Added TModelState, TModelUpdateState, // Replaces UpdateModelState TPathState, TPathUpdateState, // Added TShapeState, TShapeUpdateState, // Added TText3DState, // Replaces Text3DState TText3DUpdateState, // Replaces UpdatableText3DState TWallsState, TWallsUpdateState, // Added } from '@mappedin/mappedin-js'; ``` #### BlueDot - The `BlueDot` API has been removed and will be published under a separate `@mappedin/blue-dot` package. ```ts // ❌ Before import { show3dMap } from '@mappedin/mappedin-js'; const mapView = await show3dMap(...); mapView.BlueDot.enable(); // ✅ After import { show3dMap } from '@mappedin/mappedin-js'; import { BlueDot } from '@mappedin/blue-dot'; const mapView = await show3dMap(...); new BlueDot(mapView).enable(); ``` ### Features - Added `navigationFlags` getter to `Node`. - Added floor-independent BlueDot positioning. - Upgraded MapLibre to v5. ### Fixes - Fixed `side` not working in `updateState()`. - Removed `tabindex` from canvas and attribution. - Fixed transparency fighting for overlapping Paths. - Fixed Path animation delays. - Fixed outlines persisting when polygon is `visible: false`. - Fixed loading MVF v3 for enterprise. - Fixed shading only working on elevation 0. - Fixed Mappedin attribution showing when `outdoorView` is disabled. ## v6.0.1-beta.53 _July 21, 2025_ **Features** - Adds `lineOfSight` as an option to `MapData.Query.nearest()` to avoid returning results obstructed by walls. - Adds `disableWorkers` as an option to `show3dMap()`. It is highly recommended not to disable web workers unless absolutely necessary, such as an environment with strict CSP rules. ## v6.0.1-beta.52 _July 21, 2025_ **Changes** - Label `iconSizeInterpolation` has been replaced with `iconScale`. ```ts // Before mapView.Labels.add(space, 'label', { appearance: { marker: { iconSizeInterpolation: { maxZoomLevel: 18, minZoomLevel: 17, maxScale: 2, minScale: 1, } } } }); // After mapView.Labels.add(space, 'label', { appearance: { marker: { iconScale: { on: 'zoom-level', input: [17, 18], output: [1, 2], easing: 'linear' // optional } } } }); ``` - `MapView.getMapData()` now returns the MapData directly instead of an object containing a single key value pair. ```ts const mapData = mapView.getMapData(); // Before mapData: { [string]: MapData, } // After mapData: MapData ``` - The `name` property across map elements no longer falls back to external id. ```ts // Before spaceWithoutName.externalId === 'id'; spaceWithoutName.name === 'id'; // After spaceWithoutName.externalId === 'id'; spaceWithoutName.name === ''; ``` **Features** - Improved performance and memory usage. - Improved type inference in `MapData.Query.nearest()`. - Added Facade as an option to `MapView.animateState()`. - Added an `exclude` array property to `MapData.Query.nearest()`. - Added `MapView.options` property to return the initialization options passed into `show3dMap()`. - Added `subtitle` property to Floor. - Added `'unknown'` as a possible type with `MapData.getById()` which will return the first map element found with that id. - Added option to disable token prefetching. - Added support for updating Path altitude. - Added `pitch` and `bearing` to `MapView.Camera.getFocusOnTransform()`. **Fixes** - Fixed Path color when animation is layered over a `visibleThroughGeometry` Path. - Fixed `center` property falling back to `[0, 0]` if a center coordinate is not provided in the data. Center will now attempt to be calculated before falling back. - Fixed 2D occlusion on outdoor Floors fighting with basements and lower levels. - Fixed behavior when setting a color to an invalid color string. - Fixed BlueDot accuracy ring being interactive. - Fixed Marker flickering. - Fixed crash when updating the state of an invalid geometry. - Fixed invalid map Objects being created. - Fixed getDirections throwing when given an invalid start and end. - Fixed BlueDot receiving position updates from far outside the bounds of the map. - Fixed `insetPadding.type` being required. - Fixed `unsafe-eval` content security policy issues. ## v6.0.1-beta.51 _June 25, 2025_ **Features** - Added fallback language loading for `getMapData()`. **Fixes** - Fixed focusing on a LocationProfile. - Fixed performance regression with outlines. ## v6.0.1-beta.50 _June 23, 2025_ **Features** - Added `MapView.tweenGroup` which returns the TweenJS Group for controlling animations created with `MapView.tween()` and `MapView.animateState()`. - Added the `interactive` state option for Shapes. **Fixes** - Fixed `initialFloor` not working in Multi-floor View. - Fixed Paths with `interactive: true` not being interactive. ## v6.0.1-beta.49 _June 19, 2025_ **Features** - Added `altitude` state to Space and MapObject. - Added `visible` and `height` state to Shape. **Fixes** - Fixed walls and MapObjects not loading in MVF v3. - Fixed Floors with invisible geometry being detected in click events. - Fixed EnterpriseLocation `instances` not being translated. ## v6.0.1-beta.48 _June 18, 2025_ **Features** - Added `center` property to Floor. - Included Roboto font in bundle for developers with strict CSP. **Fixes** - Fixed error running Mappedin JS in Node without access to `window`. - Fixed `animateState()` setting all state values every frame. - Fixed `getScreenCoordinateFromCoordinate()` not respecting floor altitude. - Fixed layers under geometry check running forever if nothing is found. - Fixed `lowPriorityPin` showing for `always-visible` Markers. - Fixed vertical paths not facing the camera. - Fixed some Labels overlapping on map load. ## v6.0.1-beta.47 _June 11, 2025_ **Features** - Added the option to toggle the visibility of the watermark. - Added an option to make paths visible through geometry. - Added icon size interpolation for labels. - Added an option to override connection weight while getting directions. - Added `MapView.preloadFloors()` to pre load specified floors before they come visible. - Added `hidden` property to `EnterpriseLocation`. **Fixes** - Fixed `BlueDot` not being visible across all floors. ## v6.0.1-beta.46 _June 4, 2025_ **Changes** - Restored the MVF v3 support which was temporarily removed in v6.0.1-beta.45. ## v6.0.1-beta.45 _June 3, 2025_ **Changes** - Reverted the MVF v3 support added in v6.0.1-beta.44 due to an error when importing Mappedin JS in CodeSandbox. ## v6.0.1-beta.44 _June 3, 2025_ **Changes** - The experimental feature `MapView.DynamicFocus` has been removed. Going forward, new versions of Dynamic Focus will be published under a separate package @mappedin/dynamic-focus. **Features** - Added `setWorkerUrl()` and `MapView.Text3D.disableWorker()` for handling strict CSP. - Added fields to `MapView.updateState(floor, {...})` for managing visibility of geometry, footprints, 2D elements, and occlusion. - Improved `MapView.auto()` to only label Spaces with names. - Added `lowPriorityPin` to Markers which shows a small circle when space is limited. - Added `MapView.animateState()` to transition between states. - Added options to fetch or hydrate an MVF v3. **Fixes** - Fixed a warning about SAS tokens which could be logged sporadically for enterprise maps. - Fixed issues with the exported types for `getMapData()` and the React SDK. - Fixed clicks in multi-floor view returning the coordinates on the wrong floor. ## v6.0.1-beta.43 _May 21, 2025_ **Fixes** - Fixed `Labels.all()` failing to label maps with new data. - Fixed a regression with model interactivity. - Fixed doors which have been disabled still being navigable. - Fixed SAS token fetching for multi-building maps. ## v6.0.1-beta.42 _May 16, 2025_ **Changes** - Facades now have their own state independent from the spaces that they're made of. Updating the opacity of a facade will no longer overwrite the opacity of its spaces. ## v6.0.1-beta.41 _May 15, 2025_ **Features** - Added new properties to `TAddPathOptions` for `nearZoomLevel` and `farZoomLevel`. - Added support for multi-floor view with enterprise maps. - Added support for `Floor` in `updateState()`. **Fixes** - Fixed cases where Stacked Maps would zoom into the wrong map. - Fixed a potential error when Markers did not have an anchor strategy selected. - Fixed 2D occlusion for Markers and Labels placed outside a floor. - Fixed missing CSS. - Fixed missing types for `setScreenOffsets()`. ## v6.0.1-beta.40 _May 8, 2025_ **Features** - Added `resize` event which is published when the map container resizes. - Improved the behavior of focusing on a floor. The floor should now fit better within the camera frame. - Added `icon` getter to `LocationProfile`. - Improved Stacked Maps `auto` vertical distance calculation. **Fixes** - Fixed a regression in inactive Label marker size. - Fixed the collider of the inactive Label being too large. - Fixed `takeScreenshot()` sometimes firing before the map is ready. - Fixed floors getting stuck in a partially transparent state in Dynamic Focus. - Fixed Dynamic Focus firing every time a render occurs. - Fixed injected CSS not loading. - Fixed geometry outlines not working when altitude is not 0. - Fixed Labels and Markers being misaligned after map container is resized. ## v6.0.1-beta.39 _April 23, 2025_ **Changes** - The path arrow animation has been slowed down from 1ms to 3ms. **Features** - Added support for rendering enterprise polygons which have been split via edge offsets. - Added enterprise Connection names. - Improved Label stabilization. - Improved Model position, rotation, and scale properties for more precise control. - Improved `MapData.Query.nearest()` so that it only returns navigable nodes. **Fixes** - Fixed image flipping instability when viewed at exactly 90 or 270 degree rotation. - Fixed cases where the previous path may not be cleared from navigation. - Fixed cases where floors may overlap in Dynamic Focus. - Fixed cases where the wrong name was returned from Hyperlinks. - Fixed an issue where labels would not appear on floor change until an additional render occurred. - Fixed an issue whereby Facades could disappear despite not being in focus. - Fixed accessible connections in non-enterprise venues. - Fixed Text3D drawing despite not having enough room. ## v6.0.1-beta.38 _April 15, 2025_ **Changes** - React SDK versions are now published under the "Latest" tag on NPM. It is recommended to remove the `beta` tag from `package.json` and avoid using it in future installations. - The CSS styles for the SDK are now automatically loaded when `show3dMap()` is called. This can be disabled by passing the flag `injectStyles: false`. - `iconVisibilityThreshold` has been replaced with `iconVisibleAtZoomLevel` in label appearance. **Features** - Added `getScreenCoordinateFromCoordinate()` to MapView. - Added touch screen controls including double tap to zoom and two finger tap to zoom out. - Added `segments` property to the Path class which gives you a portion of the path on a floor. - Added `iconOverflow` option for label appearance. - Added `interactive` flag for the watermark. - Added `getAccessToken()` and `getSasToken()` functions to MapData. - Added `categories` property to EnterpriseLocation. - Added detached text areas loaded from enterprise data. - Added `flipImagesToFaceCamera` to `TShow3dMapOptions`. - Added `flipImageToFaceCamera` as an option for Spaces. - Added `verticalOffset` as an option to `createCoordinate()`. - Added automatic loading of Mappedin CSS styles. - Added `blueDot` boolean to `'click'` event. - Added localization for the Floors and FloorStacks. - Added `iconVisibleAtZoomLevel` to label appearance. **Fixes** - Fixed `getById('connection', id)` returning the wrong data. - Fixed a regression in label icon loading. - Fixed a crash when map element width or height are 0. - Fixed `getFocusOnTransform()` triggering a `'camera-change'` event. - Fixed textures not being loaded from data. - Fixed directions failing to identify a door in the instructions. - Fixed `setFloor` de-syncing DynamicFocus. - Fixed parity between `'floor-change-start'` and `'floor-change'`. - Fixed unnecessary warnings when `layoutId` is not set. - Fixed enterprise textures not blending with the polygon color. - Fixed edge weights in enterprise navigation. - Fixed outlines creating diagonal lines across some polygons. - Fixed pan bounds not scaling based on map size. - Fixed some paths rendering at the wrong elevation. - Fixed textures being applied to all polygons with the same style. - Fixed cases where label density would be really low. - Fixed images and labels z-fighting when outlines are turned off. - Fixed label icon size updates requiring an extra render to take affect. - Fixed labels being too jumpy during pan actions. - Fixed connection node neighbours being excluded on the current floor. - Fixed a crash that could occur when accessing LocationProfile properties with an old MVF. - Fixed issues loading inline SVGs in label icons. - Fixed path not drawing in stacked maps. - Fixed image meshes being visible before the image has fully loaded. ## v6.0.1-beta.37 _March 27, 2025_ **Features** - Added `space` getter to `Node`. - Added `Camera.interactions.enable()`, `Camera.interactions.disable()`, and `Camera.interactions.set()`. - Added support for specifying enterprise map perspectives via the `viewId`. - Added `screenOffsets` to `show3dMap()` to set the initial camera padding. - Added support for nodes with `preventSmoothing` flag. - Added `shortName` getter to `FloorStack`. - Added `dynamic-focus-change` event. - Added `extra` getter to `Connection`. - Added `website` getter to `LocationProfile`. - Added `setMapToDeparture` option to `Navigation.draw()` - Added support for enterprise map textures. - Improved rendering of enterprise maps. **Fixes** - Fixed how Stacked Maps `"auto"` distance between floors handles `includedFloors`. - Fixed connections with less than 2 nodes appearing in navigation. - Fixed floors disappearing in Stacked Maps after `Navigation.draw()`. - Fixed rendering defect of geometry `topColor` on some devices. - Fixed map data details should fallback to `LocationProfile`. - Fixed pan bounds breaking in Stacked Maps. - Fixed `minZoom` calculation from pan bounds. - Fixed floor state modification during Stacked Maps. - Fixed `screenOffsets` not being respected. - Fixed `focusOn()` when floor is shifted below altitude 0. - Fixed extra `FloorStack`s being rendered at the same time. - Fixed `connection` getter on `LocationProfile`. - Fixed connections not relevant to the path being included. ## v6.0.1-beta.36 _March 18, 2025_ **Fixes** - Fixed cases where enterprise maps were not choosing the shortest route. - Fixed cases where outdoor multi-polygon building footprints were not being hidden. ## v6.0.1-beta.35 _March 17, 2025_ **Fixes** - Fixed cases where directions included unnecessary instructions. - Fixed rendering artifacts on some devices when color is applied to geometry. ## v6.0.1-beta.34 _March 13, 2025_ **Features** - Added `lowDpi` flag to `TShow3dMapOptions` for improved performance on large displays. - Added `picture` property to `EnterpriseCategory`. - Added types for `LocationProfile` and `LocationCategory`. **Fixes** - Fixed floor footprints rendering at the wrong altitude in multi-floor view. - Fixed cases of navigation choosing a longer route than expected. ## v6.0.1-beta.33 _March 7, 2025_ **Features** - Added `openingHours` getter to LocationProfile. - Added support for `Camera.focusOn()` and `Camera.getFocusOnTransform()` when the geometry is not yet rendered. - Added `verticalOffset` to Coordinate. - Added `"auto"` as an option for StackedMaps `distanceBetweenFloors`. - Added `color` to Model state as a shorthand to update known materials. - Added `naturalBearing` getter to MapData and automatically oriented the map to this bearing. - Added `MapView.takeScreenshot()` to screenshot the 3D scene. **Fixes** - Fixed image flipping on certain initial bearings. - Fixed `visible: false` not working for Models. - Fixed incorrect gaps between floors in multi-floor view. - Fixed cases where OutdoorView was disabled because a token was not provided. - Fixed `onMVFParsed` not firing in `hydrate()`. - Fixed camera animations not being interruptible by default. - Fixed issues preventing draft map images from being fetched. - Fixed incorrect click Coordinate being returned in multi-floor view. - Fixed the `minZoomLevel` calculation on load when map is in portrait orientation. ## v6.0.1-beta.32 _March 3, 2025_ **Features** - Added `shortName` property to Floor. - Added MapView events for `pre-render` and `post-render`. - Added `backgroundColor` and `backgroundAlpha` to global state. - Added getters to return the selected `departure` and `desination` from the Directions. - Added an option to `show3dMap()` to enable Label and Marker occlusion. - Added `location-profile` and `location-category` map data. **Fixes** - Fixed some 3D models not being placed at their coordinate. ## v6.0.1-beta.31 _February 26, 2025_ **Features** - Added new options to `Models.add()` and `updateState()` for `materials` and `verticalOffset`. - Added `Camera.getFocusOnTransform()` to directly return the necessary camera transform to focus on a target. **Fixes** - Fixed `topColor` throwing a warning when set to `'initial'`. - Fixed initial polygon and shape altitude being doubled. ## v6.0.1-beta.30 _February 24, 2025_ **Fixes** - Improved performance of multi-destination directions. - Removed the default limit on search results. ## v6.0.1-beta.29 _February 19, 2025_ **Fixes** - Fixed marker rank not being respected. ## v6.0.1-beta.28 _February 11, 2025_ **Fixes** - Reverts the lighting change from v6.0.1-beta.27. ## v6.0.1-beta.27 _February 11, 2025_ **Changes** - Increased light intensity to improve contrast and appearance of 3D models. - Updated the default hover color from `#ff0000` to `#f6efff`. **Fixes** - Fixed a potential crash when building footprints were MultiPolygon. ## v6.0.1-beta.26 _February 10, 2025_ **Features** - Added a `.geoJSON` property in select map data classes to access the underlying GeoJSON. - Added an `interruptible ` boolean to the camera options. - Improved `updateState` and `getState` to align their properties. - Improved Labels to dynamically respond to height and visibility changes in the Space that they're attached to. - Improved the hiding of building footprints under the map geometry. ## v6.0.1-beta.25 _February 4, 2025_ **Features** - Added support for setting opacity and height of walls with `updateState()`. - Added support for Facades in `updateState()`. - Added a new `Text3D` API to MapView for labelling enterprise Spaces. **Fixes** - Fixed the rotation of the BlueDot heading to always take the shortest path. ## v6.0.1-beta.24 _January 29, 2025_ **Features** - Added `MapData.Query.nearest()` to return the nearest feature to a point on the map. - Added Facade class representing the exterior geometry of a FloorStack. **Fixes** - Fixed an issue causing Labels to overlap. ## v6.0.1-beta.23 _January 24, 2025_ **Features** - Improved the collision detection of Labels. They should now be more stable and adjust their position around the anchor less often. - Added `Area` map data type. **Fixes** - Fixed `EnterpriseLocation` instances not being translated. ## v6.0.1-beta.22 _January 14, 2025_ **Features** - Added support for loading private images via SAS token. **Fixes** - Fixed issue loading map when `language` is set in `getMapData()`. - Fixed colliders that extend off viewport being non-interactive. ## v6.0.1-beta.21 _January 9, 2025_ **Fixes** - Fixed custom `baseAuthUri` not being set. - Fixed `EnterpriseLocation.instances` not being populated. ## v6.0.1-beta.20 _January 8, 2025_ **Changes** - Renamed `watchBrowserPosition` to `watchDevicePosition` in `BlueDot.enable()`. **Features** - Expanded the possible max zoom level from 22 to 24. The default remains 22. - Added a `language` option to `getMapData()` to specify the initial map language. - Added `instances` property to `EnterpriseLocation`. **Fixes** - Fixed a type error when passing a Door to `updateState()`. - Fixed an error which could be thrown when rapidly adding and removing Markers or Labels. - Fixed an issue causing all connection types to show a stairs icon. ## v6.0.1-beta.19 _December 16, 2024_ **Features** - Added `Navigation.floorStacks` getter. **Fixes** - Fixed `Outdoor.visible` returning incorrect values. - Fixed `Outdoor.setOpacity()` failing when called directly after `show3dMap()`. - Fixed a crash which could occur when `Camera.focusOn()` is passed undefined values. - Improved the error message thrown when container height and/or width are 0. ## v6.0.1-beta.18 _December 11, 2024_ **Changes** - The Mappedin Web SDK has been renamed Mappedin JS for clarity and consistency with the `@mappedin/mappedin-js` package. **Features** - Enabled `BlueDot.update()` to set `heading`, `accuracy`, or `floorOrFloorId` prior to receiving an initial position. **Fixes** - Fixed the appearance of Labels when text is on top of the pin. ## v6.0.1-beta.17 _November 29, 2024_ **Changes** - **[Breaking]** Marker interactivity has been adjusted. The `interactive` option now accepts 3 values: - `false`: The Marker is not interactive and browser pointer events are disabled. (default) - `true`: The Marker will register in the SDK `'click'` and `'hover'` events. Browser pointer events are disabled. - `'pointer-events-auto'`: The Marker will not register in SDK events. Browser pointer events are enabled. **Features** - Added `Navigation.setActivePath()`, `Navigation.setActivePathByDirections()` and `Navigation.setActivePathIndex()` for multi-destination routes. - Added options for `StackedMaps.expand()` and `StackedMaps.collapse()` and new functions to control multi-floor views. - Added a `nodes` getter for a enterprise Connections. - Added `BlueDot.follow()` to attach the camera to the BlueDot. - Added a `target` getter for Labels, Markers, and other map elements. - **(React SDK)** Improved handling of `Marker` and `Label` options prop updates. **Fixes** - Fixed issue when rendering older MVFs causing Floor names to be an empty string. - Fixed missing types in documentation. ## v6.0.1-beta.16 _November 26, 2024_ **Changes** - **[Breaking]** Camera bearing will now always rotate clockwise from north. Previously, this rotation was counterclockwise. - **[Breaking]** Renamed `floor` to `floorOrFloorId` in `BlueDot.update()`. This property now accepts either a Floor object or a string floor ID. **Features** - Added the option to provide an ordered list for the Marker `anchor` property. The Marker will be placed in the first available position in this list. - Added `blue-dot-state-change`, `blue-dot-position-update` and `blue-dot-error` events. - Added support for custom `departure`, `destination`, and `connection` Markers in `Navigation.draw()`. - Added support for Shapes in `MapView.updateState()`. - Added `environment` option in `getMapData()`. **Fixes** - Fixed `Camera.focusOn()` padding. - Fixed issue with flattened path `nearRadius` and `farRadius`. ## v6.0.1-beta.15 _November 15, 2024_ **Fixes** - Fixed Path `nearRadius` and `farRadius` options not being respected. - Fixed BlueDot position updates failing when heading is `null`. - Fixed Navigation `createMarkers` options not being respected. - Fixed Marker animations only updating during camera events. ## v6.0.1-beta.14 _November 14, 2024_ **Changes** - `getDirections()` has been reverted to single destination only. Multi-destination directions can now be generated using `getDirectionsMultiDestination()`. **Features** - Added `accessible` flag to connections. - Added `BlueDot.update()` method to set the BlueDot position. - Added `interactive` boolean option for paths. Interactive paths will appear in hover and click events. **Fixes** - Fixed TypeScript return types for `getDirections()`. ## v6.0.1-beta.13 _November 8, 2024_ **Features** - Added support for hydrating a map with language packs included. - Added appearance states for BlueDot. - Added Outdoor.setOpacity(). - Added support for multi-destination navigation. - Added externalId to data objects. - Added getDirections() to MapData. **Fixes** - Fixed a regression causing map to go out of sync on devices with high device pixel ratio. ## v6.0.1-beta.12 _October 30, 2024_ **Fixes** - Fixed issue causing multi-floor `Navigation.draw()` to show duplicate markers on destination floors. ## v6.0.1-beta.11 _October 29, 2024_ **Changes** `MapView.Paths.add()` has been changed. It will now return a `Path` directly, while previous it returned `Promise`. This should make it easier to remove a path before the animation completes. ```ts const path = mapView.Paths.add(...); mapView.Paths.remove(path); ``` The draw animation can now be awaited by accessing `Path.animation`. ```ts // Before const path = await mapView.Paths.add(...); // Now const path = mapView.Paths.add(...); await path.animation; ``` **Features** - Added support for directions smoothing on enterprise maps. - Added support to remove the watermark for enterprise customers. - Enabled backend analytics. **Fixes** - Fixed `MapView.Outdoor.setStyle()` not always hiding the building footprint after the style changes. - Fixed auth0 tokens always requesting enterprise map data. - Fixed `MapView.Camera.focusOn()` not respecting `bearing: 0`. ## v6.0.1-beta.10 _October 25, 2024_ **Features** - Added `MapView.getState(WALLS.Interior)` and `MapView.getState(WALLS.Exterior)` - Added support for MVF language packs. - Added support for more image types in Label icons. - Added Connection-based instructions to directions. **Fixes** - Fixed cases of Path z-fighting. ## v6.0.1-beta.9 _October 18, 2024_ **Features** - Added `Outdoor.show()` and `Outdoor.hide()` methods. - Added automatic zoom bounds based on map size to prevent the camera zooming out too far. - `getMapData()` will now render enterprise maps without any extra flags. - Added options to `Navigation.draw()` to set departure and destination marker colors. - Added `FloorStack`s to group floors in a single building. **Fixes** - Fixed the reversed latitude and longitude in enterprise connections. ## v6.0.1-beta.8 _October 11, 2024_ **Fixes** - Included `MapView.Images` API in the documentation. - Fixed `MapData.getByType('connection')` returning an empty array with enterprise maps. ## v6.0.1-beta.7 _October 10, 2024_ **Changes** - Prefixed enterprise `Location`, `Category`, and `Venue` to be `EnterpriseLocation`, `EnterpriseCategory`, and `EnterpriseVenue`. **Features** - Added Search API with `MapData.Search.query()` and `MapData.Search.suggest()`. - Added vertical paths between floors in Stacked Maps. ## v6.0.1-beta.6 _October 10, 2024_ **Fixes** - `Navigation.clear()` or `Navigation.draw()` will now cancel a previous draw in progress. - Included `Venue`, `Location`, and `Category` in documentation. ## v6.0.1-beta.5 _October 9, 2024_ **Features** - Added image texture properties to `updateState()`. - Added `MapView.enableDebug()` for inspecting map elements. - Added validation for new Labels and Markers. - Added `MapView.Images` API for adding images to the map. - Added support for multi-languages and enterprise data. - Added support for visible doors. ```ts import { DOORS } from '@mappedin/mappedin-js'; mapView.updateState(DOORS.Interior, { visible: true }); mapView..updateState(DOORS.Exterior, { visible: true }); ``` **Fixes** - Fixed a false warning when outdoor view is disabled. - Fixed the `Floor.name` value in enterprise maps. - Fixed the camera panning action during zoom while in Stacked Maps. ## v6.0.1-beta.4 _September 19, 2024_ **Features** - Added ability to update opacity of individual geometries. - Added ability to update height of individual geometries. - Geometry outline color now updates when geometry color changes. - Added option to set background color and opacity for map. - Added caching for access token requests. - Added cleanup when run in overlay mode. - Added customization options to `BlueDot.enable()`. - Added support for rendering images from the mapData. **Fixes** - Fixed the watermark collider not updating position. - Fixed CORS errors with outdoor tiles. - Fixed issue with `updateState` not updating the Label icon. - Fixed issue with resizing the canvas breaking click and hover events. ## v6.0.1-beta.3 _September 13, 2024_ **Features** - (React SDK) Added `onLoad` prop to most components which returns the instance. **Fixes** - (React SDK) Fixed overlapping Markers rendering in React StrictMode. ## v6.0.1-beta.2 _September 11, 2024_ We've published an additional package @mappedin/react-sdk. This SDK exports React components and hooks in addition to all the same functionality as the @mappedin/mappedin-js package. **Features** - Added the `Space.doors` getter which returns all `Door`s associated with a `Space`. **Fixes** - Fixed `Camera.focusOn()` not respecting the global tilting limits. - Fixed `Camera.focusOn()` not respecting the pan bounds of the map. ## v6.0.1-beta.1 _September 5, 2024_ **Changes** - The default `minZoomLevel` has been changed to 12 from its previously approximate 13.03 value. The default `maxZoomLevel` remains 22. The documentation has been updated to show these values. - Renamed `CustomGeometry` to `Shapes`. **Features** - Improved `Camera.focusOn()` to contain the bounding box of the space or object. - `Camera.focusOn()` now accepts a Floor. **Fixes** - Fixed the `initialFloor` option not being respected. - Fixed Markers and Labels rendering over top of the watermark. - Fixed issues with initial camera positioning and state. - Fixed issues with camera projection after adjusting the min and max zoom levels. - Fixed cursor state not updating after camera animations complete. ## v6.0.1-beta.0 _August 26, 2024_ Mappedin JS v6 is now in beta. While many of the core features have been implemented, we’re continuing to rapidly iterate and improve the product. We welcome all customer feedback. **Features** - Added warnings when a focusOn() animation is outside of the camera boundaries. **Fixes** - Resolved issues with setting the camera state during initialization and runtime. ## v6.0.1-alpha.38 _August 21, 2024_ **Features** - Added BlueDot as an experimental option. **Fixes** - Resolved issues with ultrawide monitors with device pixel ratio >= 2. ## v6.0.1-alpha.37 _August 19, 2024_ **Features** - Added options to dynamically control routing by creating zones with path cost. **Fixes** - Fixed issue where interactive spaces would sometimes remain clickable after floor change. ## v6.0.1-alpha.36 _August 14, 2024_ **Features** - Added optional animated path arrows. ## v6.0.1-alpha.35 _August 12, 2024_ **Features** - Added an `isInView()` function to the MapView, which can detect if a 3D element is within the current viewport. - Added the array of clicked floors to the `MapView.on('click')` payload. **Fixes** - Fixed `shadingAndOutlines: false` to properly disable outlines. - Fixed Markers always rendering beneath Labels. Markers will now be on top. - Fixed the watermark rendering behind 3D paths. - Fixed cases where `Outdoor.setStyle()` would not hide the outdoor geometry. - Fixed issues with updating the style of exterior walls. - Fixed `MVFStyle` and `MVFStyleCollection` types not being exported. ## v6.0.1-alpha.34 _August 2, 2024_ **Changes** - **[Breaking]** Previously, setting `outdoorView.token` to a falsey value would disable outdoor view. This is no longer the case. To disable outdoor view, set `outdoorView.enabled: false` in `show3dMap` options. - **[Breaking]** Removed the `updateZoomLimits` property of `focusOn`. Zoom levels will now be clamped between the min and max zoom of the SDK. - Label images will no longer fade in and out as they reposition. **Features** - Added a new exported function, `createMapLibreOverlay()`. This can be used to add Mappedin indoor maps to Maplibre via `addControl()`. - Added new options to control the gradient shading on the sides of geometry. - Added a new API for adding and removing 3D Models. - Added a new API for adding and removing custom geometry. - Added the `outdoorView.enabled` flag to turn on or off the outdoor map. - Added toggle-able visibility state to geometry. - Added a "Provide Feedback" link in the bottom right corner. This will enable Mappedin to gather feedback and improve the product. **Fixes** - Fixed an issue where `MapView.Labels.all()` would prevent polygon hover color from being set. - Fixed an issue preventing label icons from loading. - Fixed a Label rendering issue on Intel Iris GPUs. - Fixed an issue with the request URL for MVF `viewId`s. ## v6.0.1-alpha.33 _July 24, 2024_ **Changes** - **[Breaking]** `addPath` now returns a promise, allowing the animation to be awaited. - **[Breaking]** Style changes will now fire the `outdoor-style-loaded` event, instead of firing the `outdoor-view-loaded` event. The latter will only be fired on initial load. - Improved camera controls and interactions with multi-finger touch. Multi-finger touch will no longer trigger a click event, and releasing a finger will no longer abruptly end camera movement. **Features** - The Maplibre map is now exposed via `Outdoor.map` for advanced usage. - Added support for geometry textures when provided in the map data. - Added a light option for the Mappedin watermark. - The map geometry can be updated during runtime by passing a new style to `Style.setFromStyleCollection`. - Added `viewId` property to `getMapData` to specify the editor Map View to fetch. **Fixes** - Fixed cases where a simplified path would cut corners too sharply. ## v6.0.1-alpha.32 _July 17, 2024_ **Features** - `focusOn` will now accept an Annotation as the target. **Fixes** - Fixed an issue causing `hiddenLayersUnderGeometry` to be ignored on some maps. - Fixed an issue where multi-floor paths could draw a straight line from start to finish, ignoring connections. ## v6.0.1-alpha.31 _July 15, 2024_ **Features** - Added a "low" option for marker and label rank. **Fixes** - Fixed an issue where `layersHiddenByGeometry` would fail if a layer name was not present in the outdoor style. - Fixed an issue where marker z-index wasn't 3D accurate. The marker nearest to the camera will now be on top. ## v6.0.1-alpha.30 _July 8, 2024_ **Fixes** - Duplicate marker and label IDs are now rejected. - Fixed an issue preventing the outdoor-view-loaded event from firing. - Fixed a crash which could occur when the map has no interior walls. ## v6.0.1-alpha.29 _July 4, 2024_ **Features** - `getMapData` now optionally accepts an `accessToken` string in place of `key` and `secret`. ## v6.0.1-alpha.28 _July 2, 2024_ **Fixes** - Resolves an issue where maps with multiple styles for spaces and obstructions would fail to load all geometry. ## v6.0.1-alpha.27 _July 2, 2024_ **Fixes** - Resolved an issue where iOS would darken the canvas after the user has \* moved to another tab. - Resolved an issue where applying a new outdoor style could add layers above the indoor map. - Duplicate entity IDs in the data are now rejected. ## v6.0.1-alpha.26 _June 28, 2024_ This update introduces a completely new SDK architecture enabling faster load times, less memory consumption, real time 2D colliders, and more. Please reach out if there are any unexpected changes after upgrading to v6.0.1-alpha.26. **Changes** - **[Breaking]** Increased the default `iconSize` of label icons from 10 pixels to 20 pixels. - **[Breaking]** Decreased the default `iconPadding` of label icons from 4 pixels to 2 pixels. - **[Breaking]** `Camera.animate` has been renamed to `Camera.animateTo`. - **[Breaking]** `maxZoomLevel` and `minZoomLevel` setters have been replaced with `setMaxZoomLevel` and `setMinZoomLevel` methods. - **[Breaking]** The `priority` property for labels and markers has been renamed `rank` in `updateState`. - **[Breaking]** The elements in the DOM have shifted. It is not recommended to rely on or modify the Mappedin elements in the DOM. | Element | Before | After | | ----------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | Map Canvas | Top level | The map canvas is now a child of `` | | Attribution | Top level | Attribution elements are now children of `` | | Colliders | Children of `` | Colliders are now children of `` | **Features** - Colliders such as labels and markers adjust position in real time during camera events. - Initial camera bearing, pitch, and zoomLevel can be set in the options for `show3dMap`. ```ts await show3dMap(document.getElementById('mappedin-map'), mapData, { bearing: 45, pitch: 45, zoomLevel: 19, }); ``` - `Labels.all` will automatically use the icon for spaces and points of interest if one has been set in the editor. - The color of the top of the walls can now be set independently of the rest of the wall. ```ts await show3dMap(document.getElementById('mappedin-map'), mapData, { wallTopColor: '#000000', }); ``` - The ‘click’ event returns a new property, `pointerEvent`, which can be used to differentiate between mouse clicks. ```ts mapView.on('click', async (event) => { const [space] = event.spaces; if (!space) return; switch (event.pointerEvent.button) { case 0: const { coordinate } = event; const label = space.name || `Clicked: Lat: ${coordinate.latitude} Lon: ${coordinate.longitude}`; mapView.Labels.add(coordinate, label); break; case 2: mapView.Camera.focusOn(space); break; } }); ``` - Outdoor layers below indoor geometry can be hidden using the `outdoorView.layersHiddenByGeometry` option for `show3dMap`. **Fixes** - Fixed loading of geometry styles. - Reduced memory consumption. - Reduced bundle size. ## v6.0.1-alpha.25 _June 13, 2024_ - Version bump only. ## v6.0.1-alpha.24 _June 12, 2024_ **Features** - Marker position can now be updated instantly using `setPosition` or over time using `animateTo`. **Changes** - `outdoorViewToken` and `outdoorViewStyle` have been deprecated in favor of `outdoorView.token` and `outdoorView.style` on `TShow3DMapOptions`. They will be removed in a future release. ```ts outdoorView = { token: '' style: '' } ``` - Multi-finger touch will no longer count as a click event. **Fixes** - Fixed a case where some generic styles were being exported. ## v6.0.1-alpha.23 _May 22, 2024_ **Features** - Customize the outdoor style using the `outdoorViewStyle` property of `TShow3DMapOptions`. The property expects a URL to a style specification conforming to the Maplibre Style Spec. **Changes** - The wall shading now darkens the color instead of setting it grey. Shading is now noticeable on all objects. | Before | After | | ------------------------------------------------------------------- | ------------------------------------------------------------------ | | !Shading Before | !Shading After) | - The watermark should no longer disappear at some zoom levels. As a side effect, the default sizing of the watermark has changed. | Before | After | | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | Sizing was based on vertical height of the screen. !Before Watermark | Sizing is now 150px wide by default. !After Watermark | ## v6.0.1-alpha.22 _May 15, 2024_ **Changes** - **[Breaking]** The type of `TCameraAnimationOptions.easing` has been changed to `EasingCurve`. The accepted values of this property remain the same. - `Markers.removeAll()` now has a return type of `Marker[]`. ## v6.0.1-alpha.21 _May 13, 2024_ **Changes** - **[Breaking]** The default value of label iconFit has been changed to `"cover"`. Previously, the default value was `"fill"`. **Fixes** - Fixed an issue causing the `onMVFParsed` callback of `getMapData` to never fire. - Fixed an issue with watermark visibility. ## v6.0.1-alpha.20 _May 2, 2024_ **Features** - Added new options to customize the location of the watermark. The watermark `padding` option now accepts an object with `top`, `right`, `bottom`, `left` to fine-tune it. - **(Breaking)** Added support for JPEG and PNG images in Floating Label icons. In addition to this, icons are now clipped in a circle to prevent overflow. New appearance options have been added to customize the icons. ```ts mapView.Labels.add(space, space.name, { appearance: { marker: { iconSize: 20, iconFit: 'fill', iconPadding: 4, icon: 'http://www.example.com/dog.jpeg', }, }, }); ``` The new `iconFit` property determines how the icon should be scaled within the circle. `'fill'` is now default. | Fill (default) | Contain | Cover | | ----------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------- | | !Floating Label fill | !Floating Label contain | !Floating Label cover | The `padding` property sets the amount of space between the icon and the border. The icon may shrink based on this value. | `padding: 0` | `padding: 10` | | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | | !Floating Label fill with 0 padding | !Floating Label fill with 10 padding | ## v6.0.1-alpha.19 _April 30, 2024_ **Features** - It is now possible to specify the radius of the raycast for directions smoothing. ```ts mapView.getDirections(departure, destination, { smoothing: { radius: 3 }, // 3 metre radius }); ``` By default, radius is set to 1.8 metres. **Fixes** - Fixed a race condition when doing `mapView.Navigation.clear()` directly after calling `mapView.Navigation.draw(directions)`. ## v6.0.1-alpha.18 _April 25, 2024_ **Fixes** - Fixed an issue where directions may be missing distances between instructions. ## v6.0.1-alpha.17 _April 23, 2024_ **Fixes** - Resolved an issue with installing the package from NPM. ## v6.0.1-alpha.16 _April 23, 2024_ - Version bump only. ## v6.0.1-alpha.15 _April 19, 2024_ **Fixes** - Connection weights were not being properly accounted for, causing all connection types to be prioritized equally. This has been corrected. - Fixed `SafeAreaInsets` not working when focusing on a single point. ## v6.0.1-alpha.14 _April 12, 2024_ - Version bump only ## v6.0.1-alpha.13 _April 10, 2024_ **Features** - The MapView contains a new controller called `Exporter`. With Exporter, the rendered map can be converted to GLTF. ```ts const gltf = await mapView.Exporter.getCurrentSceneGLTF(); ``` ## v6.0.1-alpha.12 _April 2, 2024_ **Fixes** - The navigation path should no longer traverse through walls. - Fixed an issue with the watermark. ## v6.0.1-alpha.11 _March 26, 2024_ **Fixes** - Resolved an issue where some maps didn't render exterior walls. In cases where the map is missing styles for exterior walls, they will default to the indoor wall style. ## v6.0.1-alpha.10 _March 25, 2024_ - Version bump only. ## v6.0.1-alpha.9 _March 19, 2024_ - Version bump only. ## v6.0.1-alpha.8 _March 18, 2024_ **Fixes** - Halved memory usage during map initial load. - The outline effect no longer retains line thickness while zooming out with inertia. - Labels render correctly now on Intel integrated GPUs. ## v6.0.1-alpha.7 _March 12, 2024_ **Changes** - The default color of the exit connection navigation button is now blue instead of purple. **Fixes** - The `focusOn` animation no longer resets the min and max zoom levels. - If directions have less than 2 coordinates, navigation will now warn and prevent drawing the path. - The `Coordinate` class is now data layer agnostic, making it easier to test and create directly. ## v6.0.1-alpha.5 _February 28, 2024_ **Features** - All map data objects now accurately include hyperlink metadata under the property `links`. **Fixes** - The default `type` for map data objects is now `object`, instead of `unknown`. This will no longer warn of unknown object kind. ## v6.0.1-alpha.4 _February 27, 2024_ **Fixed** - The polygon outline opacity is now interpolated as the camera zooms out. This improves the outline appearance at the furthest zoom level. ## v6.0.1-alpha.3 _February 27, 2024_ We're testing a new version of Mappedin JS and we'd love to hear your feedback! Mappedin JS v6 alpha adds first class support for maps built with app.mappedin.com. Please note that breaking changes will occur. We encourage you to contact us with any issues or feedback, and regularly check this page for detailed release notes and API changes. ### Spaces # Spaces > > A Space represents an area enclosed by walls, such as a hall or room. Spaces can be Interactive and have Labels and Markers added to them. Spaces can also be customized with a color, texture and hover color. ## Spaces Example The example below loops through all spaces and sets the color and hover color to a random color and makes them interactive. Hover over a space to see its color shown in the top left color field. Right clicking on any space will reset the spaces colors to default. Left clicking will randomly set new colors. Refer to the Textures & Colors section for more information on how to set the texture or color of a space. ```ts mapData.getByType('space').forEach((space) => { // Generate a random color const hexColor = '#' + Math.floor(Math.random() * 16777215) .toString(16) .padStart(6, '0'); // Apply it to each space mapView.updateState(space, { color: hexColor, // Set the space's color to a random color interactive: true, // Make the space interactive (clickable and hoverable) hoverColor: hexColor, // Set the same value as hover color }); }); ``` ## Doors By default, a Door is not visible and drawn as a gap in a wall. An app can set the `visible` property to `true` to make the door visible. Other properties of a door can also be set, such as color, texture, interactivity and more. Refer to TDoorsState for the complete list of properties. Refer to the Textures & Colors section for more information on how to set the texture or color of a door. Doors are grouped into DOORS.Interior and DOORS.Exterior doors, based on whether they are on an interior or exterior wall. ```ts //Make interior doors visible and brown. mapView.updateState(DOORS.Interior, { visible: true, color: '#5C4033', opacity: 0.6, }); //Make exterior doors visible and black. mapView.updateState(DOORS.Exterior, { visible: true, color: 'black', opacity: 0.6, }); ``` The screenshot below shows brown interior and black exterior doors with labels added to depict the status of the door. To try the full interactive example, load the Door Status example in the Mappedin Showcase. !Visible Doors ### Stacked Maps # Stacked Maps > > Stacked maps consist of individual layers that represent different floors of a multi-story building. These layers are conceptually or digitally stacked to create a complete view of the structure, allowing users to visualize, navigate, and interact with multiple floors in an integrated way. !Stacked Maps Stacked Maps can be enabled by calling StackedMaps.expand() and disabled by calling StackedMaps.collapse(). The current state of Stacked Maps can be accessed using StackedMaps.state, which can be either `expanded` or `collapsed`. MapView.on() fires the event `stacked-maps-state-change` when the state of the Stacked Maps changes. Stacked Maps style can be customized by passing a TExpandOptions object to StackedMaps.expand(). This allows for customization of the distance between floors, which floors are shown in the stack and whether the `mapView.on('floor-change')` event is fired and MapView.currentFloor is updated when the user changes the elevation of the camera to center a new floor. The sample code below shows the implementation of a button to toggle the Stacked Maps view on and off and sets the distance between floors to 30 meters. ```ts enableButton.onclick = () => { if (mapView.StackedMaps.state === 'collapsed') { enableButton.innerText = 'Disable'; mapView.StackedMaps.expand({ distanceBetweenFloors: 30 }); } else { enableButton.innerText = 'Enable'; mapView.StackedMaps.collapse(); } }; ``` Try the code above in the CodeSandbox below, which enables and disables Stacked Maps and listens for the `stacked-maps-state-change` event. ### Using React # Using React > > Mappedin publishes a version of the JS package which is compatible with React applications. The package can be found here on NPM @mappedin/react-sdk. The Mappedin React SDK exports convenient JSX components to render and manipulate the 3D map. Additionally, everything included in the @mappedin/mappedin-js is exported from the React SDK under the default export. When using `@mappedin/react-sdk`, do not install or import `@mappedin/mappedin-js`. The Mappedin SDK for React includes Mappedin JS. Including both can cause build conflicts or runtime errors due to duplicated or mismatched dependencies. If `@mappedin/mappedin-js` was previously added, remove it from `package.json` and `node_modules`. Types from `@mappedin/mappedin-js` are included in the `@mappedin/react-sdk` package and can be accessed via `import Mappedin from '@mappedin/react-sdk';` using the `Mappedin` namespace. Example: ```ts import Mappedin from '@mappedin/react-sdk'; const spaces: Mappedin.Space[] = mapData.getByType('space'); ``` ## 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 Run these shell commands to set up a new project and install Mappedin JS with React. With Yarn ```sh yarn create vite mappedin-quickstart --template react-ts cd mappedin-quickstart yarn add @mappedin/react-sdk ``` With NPM ```sh npm create vite@latest mappedin-quickstart -- --template react-ts cd mappedin-quickstart npm add @mappedin/react-sdk ``` ### 2. Update index.html, App.tsx Modify & update the contents of `index.html` to match the following. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Mappedin JS v6 Getting Started</title> <style> * { margin: 0; padding: 0; } html, body { width: 100%; height: 100%; } #root { height: 100%; width: 100%; position: relative; } </style> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html> ``` Modify & update the contents of `App.tsx` file under the src directory to match the following. ```tsx import React from 'react'; import { MapView, useMapData, useMap, Label } from '@mappedin/react-sdk'; function MyCustomComponent() { const { mapData } = useMap(); return mapData.getByType('space').map(space => { return <Label target={space.center} text={space.name} />; }); } export default function App() { // See Demo API key Terms and Conditions // https://developer.mappedin.com/docs/demo-keys-and-maps const { isLoading, error, mapData } = useMapData({ key: 'mik_yeBk0Vf0nNJtpesfu560e07e5', secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022', mapId: '65c0ff7430b94e3fabd5bb8c', }); if (isLoading) { return <div>Loading...</div>; } if (error) { return <div>{error.message}</div>; } return mapData ? ( <MapView mapData={mapData}> <MyCustomComponent /> </MapView> ) : null; } ``` ### 3. Run the Project To run the demo (with hotloading), use the following command: With Yarn ```sh yarn run dev ``` With NPM ```sh npm run dev ``` 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. ## MapView The `MapView` component contains the DOM element of the 3D map and the context wrapper for control of the map. Components which reference the 3D map must be children of the `MapView` component to have access to the context. The Mappedin CSS must be imported alongside the MapView. ```tsx import React from 'react'; import { MapView, useMapData, useMap, Label } from '@mappedin/react-sdk'; function MyCustomComponent() { const { mapView, mapData } = useMap(); return mapData.getByType('space').map(space => { return <Label target={space.center} text={space.name} />; }); } export default function App() { // See Demo API key Terms and Conditions // https://developer.mappedin.com/docs/demo-keys-and-maps const { isLoading, error, mapData } = useMapData({ key: 'mik_yeBk0Vf0nNJtpesfu560e07e5', secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022', mapId: '65c0ff7430b94e3fabd5bb8c', }); if (isLoading) { return <div>Loading...</div>; } if (error) { return <div>{error.message}</div>; } return mapData ? ( <MapView mapData={mapData} style={{ width: '650px', height: '650px' }}> <MyCustomComponent /> </MapView> ) : null; } ``` ## useMapData and useMap The `useMapData` hook fetches and hydrates new Map Data from Mappedin servers. It is recommended to only do this once at the beginning of the application to limit the amount of network requests for the user. It returns an object containing `isLoading`, `error`, and `mapData`. These can be used to monitor the state of the fetch and display feedback for the user. ```tsx // See Demo API key Terms and Conditions // https://developer.mappedin.com/docs/demo-keys-and-maps const { isLoading, error, mapData } = useMapData({ key: 'mik_yeBk0Vf0nNJtpesfu560e07e5', secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022', mapId: '65c0ff7430b94e3fabd5bb8c', }); ``` The `useMap` hook returns an object containing the `mapView` and `mapData` once the 3D map has been rendered. Once the MapData has been initially fetched and passed to the ``, `useMap` should be used to reference the hydrated data. `useMap` must be used in a child component of the ``. ```tsx function MyCustomComponent() { const { mapView, mapData } = useMap(); return mapData.getByType('space').map(space => { return <Label target={space.center} text={space.name} />; }); } ``` ## useEvent The `useEvent` hook enables subscribing to interactive map events and triggering a callback when they occur. For example, the following snippet subscribes to the `click` event and updates state using the click coordinate. `Label`s are rendered based on the updated state. The `click` event contains all the interactive elements that the click action passed through. > See full JavaScript guides about Interactivity and the Camera for more information about events. ```tsx function MyCustomComponent() { const { mapView, mapData } = useMap(); const [labels, setLabels] = useState([]); useEvent('click', event => { setLabels(prevLabels => [ ...prevLabels, { target: event.coordinate, text: 'Hello, World!' }, ]); }); return labels.map((label, idx) => <Label key={idx} {...label} />); } ``` Other useful events include `hover`, `camera-change`, and `floor-change`. The `hover` event is very similar to the `click` event and will contain all the interactive elements that are underneath the mouse cursor. ```tsx useEvent('hover', event => { console.log('hover', event); }); ``` The `camera-change` event fires when the camera is adjusted. It contains the new `bearing`, `pitch`, `zoomLevel` and camera `center` coordinate. ```tsx useEvent('camera-change', event => { console.log('camera-change', event.bearing, event.pitch, event.zoomLevel, event.center); }); ``` The `floor-change` event fires whenever the building floor is changed. It contains the `floor` and the `reason` that the change occurred. ```tsx useEvent('floor-change', event => { console.log('floor-change', event.floor, event.reason); }); ``` ## Optimizing User Interface Performance To optimize performance and avoid unnecessary re-renders, it's important to use React's memoization techniques. Rendering a MapView can be resource-intensive, especially when dealing with complex maps that include many components. Ensure that components only re-render when necessary, and avoid patterns that trigger cascading re-renders in sub-components. Avoid defining component properties inline—this can lead to new references on every render. Instead, define them as constants, memoized callbacks (useCallback), or memoized values (useMemo) when appropriate. ## Labels > Refer to Labels guide for more information and examples The `Label` component renders a2D point of interest label on the map. Typically, they are used to show the name of each `Space` in the building. Labelling each Space can be achieved by iterating through `mapData.getByType('space')` and returning a Label for each item. ```tsx function MyCustomComponent() { const { mapData } = useMap(); // Get all spaces with names const spaces = mapData.getByType('space').filter(space => space.name); const options = { interactive: true }; return ( <> {spaces.map(space => ( <Label key={space.externalId} target={space} text={space.name} // label text options={options} // makes the label interactive /> ))} </> ); } ``` ### Dynamic Visibility Labels can be dynamically enabled or disabled using the `enabled` option. When a label is disabled, it will be hidden from view but remain in the map's memory: ```tsx const [isEnabled, setIsEnabled] = useState(false); return ( <Label target={space} text="Dynamic Label" options={{ enabled: isEnabled, // label will be hidden when false }} /> ); ``` The CodeSandbox examples below demonstrates how to dynamically enable and disable Labels based on user zoom level. Zoom in and out to show secondary labels: #### CodeSandbox - Dynamic Label visibility on zoom ## Markers > Refer to Markers guide for more information and examples The `Marker` component creates a DOM element to a coordinate on the map. React components can be provided as children of a `Marker` with their own state and interactivity. The `interactive` property of a Marker can be set to `true`, `false` or `pointer-events-auto`. - `false` - The Marker is not interactive. - `true` - The Marker is interactive and the click event is captured by the `useEvent("click", (event) => {});` event handler. - `pointer-events-auto` - The Marker is interactive and mouse events are passed to the Marker's HTML content. ```tsx function MyCustomComponent() { const { mapData } = useMap(); // Get all spaces with names const spaces = mapData.getByType('space').filter(space => space.name); const options = { interactive: true }; return ( <> {spaces.map(space => ( <Marker key={space.externalId} target={space} options={options}> <div style={{ borderRadius: '10px', backgroundColor: '#fff', padding: '5px', boxShadow: '0px 0px 1px rgba(0, 0, 0, 0.25)', fontFamily: 'sans-serif', }} > {space.name} </div> </Marker> ))} </> ); } ``` ### Animated Markers The `AnimatedMarker` component allows for smooth transitions when updating marker positions: ```tsx function AnimatedMarkers() { const { mapData } = useMap(); const [markerCoordinate, setMarkerCoordinate] = useState<Mappedin.Coordinate | null>( mapData?.mapCenter || null ); // Handle map clicks to update the marker's coordinate useEvent('click', event => { setMarkerCoordinate(event.coordinate); }); const options = { interactive: true, anchor: 'right' }; return ( <> {markerCoordinate && ( <AnimatedMarker target={markerCoordinate} duration={1000} options={options}> <div>Animated Marker</div> </AnimatedMarker> )} </> ); } ``` The CodeSandbox examples below demonstrates how to display and animate a custom marker. Click around the map to animate the marker to the clicked location using default animation options: #### CodeSandbox - Animate Markers ### Dynamic Visibility Like labels, markers can be dynamically enabled or disabled using the `enabled` option: ```tsx const [isEnabled, setIsEnabled] = useState(false); return ( <Marker target={space} options={{ enabled: isEnabled, // marker will be hidden when false }} > <div>Dynamic Marker</div> </Marker> ); ``` ## Paths Paths can be drawn from A to B using the `Path` component. The `Path` component requires directions which must be created between two `mapData` objects. The following example creates a `Path` from the first space in the data to the second. > See full JavaScript guide on Wayfinding for more information. ```tsx function MyCustomComponent() { const { mapData, mapView } = useMap(); const space1 = mapData.getByType('space')[0]; const space2 = mapData.getByType('space')[1]; const directions = mapView.getDirections(space1, space2); const options = { color: 'blue' }; return directions ? <Path coordinate={directions.coordinates} options={options} /> : null; } ``` ## Navigation The `Navigation` component appears similar to the `Path` but with extra functionality to handle floor changes, start and end Markers, and styling. > See full JavaScript guide on Wayfinding for more information. ```tsx function MyCustomComponent() { const { mapData, mapView } = useMap(); const space1 = mapData.getByType('space')[0]; const space2 = mapData.getByType('space')[1]; const directions = mapView.getDirections(space1, space2); return directions ? <Navigation directions={directions} /> : null; } ``` ## Shapes The `Shapes` component takes a GeoJSON feature collection and renders it to the map in 3D. ```tsx function MyCustomComponent() { return ( <Shape geometry={featureCollection} style={{ color: "red", altitude: 0.2, height: 2, opacity: 0.7, }} /> ); } const featureCollection = { type: "FeatureCollection", features: [ { type: "Feature", properties: {}, geometry: { type: "Polygon", coordinates: [ ..., // Some valid GeoJSON coordinates ], }, }, ], }; ``` ## Models The `Model` component adds an instanced GLB/GLTF model to the map. In the example below, a model is added to each space that contains the word "Multipurpose" in its name. > See full JavaScript guide on 3D Models for more information. ```tsx function MyCustomComponent() { const { mapData } = useMap(); const multiPurposeSpaces = mapData .getByType('space') .filter(space => space.name.includes('Multipurpose')); const modelUrl = 'https://myDomain.com/myModel.glb'; const options = { scale: [1, 1, 1], rotation: [90, 0, 0], opacity: 0.5, }; return multiPurposeSpaces.map((space: Mappedin.Space) => { return ( <Model key={space.id} target={space.center} url={modelUrl} options={options} onLoad={models => console.log('Model loaded', models)} /> ); }); } ``` ## Full Featured Example The following CodeSandbox implements all the features mentioned above. ### Views # Views > > A `view` is a copy of a map that allows a map editor to toggle the visibility of a layer or individual items in a layer on and off and choose a map theme. It is similarly named but should not be confused with the `MapView` class, which is used to show a map. Views allow the same map to be used for many use cases, such as: - Public use for wayfinding - Use one of many light or dark themes - With safety annotations for safety and security personnel - Showing private areas for maintenance and custodial staff - Displaying only the floor plan for leasing prospects - Highlighting delivery doors for delivery drivers - and many more... ## Get a View ID A `viewId` is used by Mappedin JS to download specific view. To get a `viewId`: 1. Log into the Mappedin Editor. 2. Click on the `Developers` tab. 3. Select the desired map from the map drop down. 4. Select the desired view from the view drop down. 5. The `viewId` will be shown in the code snippet on the page. ## Display a View To display a specific view of a map, specify the `viewId` of the view using the TGetMapDataSharedOptions object that is passed into the getMapData() method as shown below. ```ts const options = { key: KEY, secret: SECRET, mapId: MAP_ID, viewId: viewId, }; const mapData = await getMapData(options); ``` ## View Example The following example demonstrates how an app could display and switch between multiple views. Use the drop down menu to switch between the available views. ### Wayfinding # Wayfinding > > Wayfinding is the process of navigating through environments using spatial cues and instructions to efficiently reach a destination. Mappedin JS provides the tools needed to guide users from one location to another. It allows drawing a path between points on the map as well as creation of textual turn-by-turn directions for navigation. !Mappedin JS v6 Navigation ## Video Walkthrough ## Getting Directions Directions form the core of wayfinding. Mappedin has path finding capabilities that allow it to generate directions from one target to another. These directions are used for drawing paths and providing the user with textual turn-by-turn directions. Generate directions by calling MapData.getDirections() and pass in targets for the origin and destination. These targets are wrapped in a TNavigationTarget, which can contain various types of targets. MapData.getDirections() can accept a single TNavigationTarget or an array of them for the origin and destination. If an array is used, Mappedin JS will choose the targets that are closest to each other. An example of where this could be used is when a user asks for directions to a washroom. There may be many Spaces named Washroom. The app can pass all washroom spaces to `getDirections` and receive directions to the nearest one. An array of destinations is also used when creating `multisegment` directions, which are directions that include multiple legs. Directions for some maps may appear jagged or not smooth. This is due to the SDK attempting to find the shortest path through the map's geometry. Directions can be smoothed by setting TGetDirectionsOptions.smoothing to true in the `TGetDirectionsOptions` passed to MapData.getDirections(). ## Drawing Navigation When a user needs to get from point A to point B, drawing a path on the map helps them to navigate to their destination. It can help them to visualize the route they'll need to take, like a good treasure map. Navigation is a helper class to display wayfinding easily on the map. Functionality of Navigation could be replicated by drawing the paths using Paths and adding well designed markers at connection points. > Note that the MapView class includes the Navigation class and exposes it as MapView.Navigation. Use MapView.Navigation to utilize Navigation's methods. Navigation.draw() allows for easily drawing multiple components that make up a wayfinding illustration. It shows a human figure to mark the start point, a path with animated directional arrows, pulses in the direction of travel and a pin to mark the destination. Each of these components can be customized to match an app's style. The following sample uses the default navigation settings to navigate from the Cafeteria to the Gymnasium. ```ts // Get the spaces for the first and second spaces to navigate to and from. const firstSpace = mapData.getByType('space').find(s => s.name === 'Cafeteria'); const secondSpace = mapData.getByType('space').find(s => s.name === 'Gymnasium'); // Ensure that the spaces exist. if (firstSpace && secondSpace) { const directions = mapData.getDirections(firstSpace, secondSpace); if (directions) { // Add a path from the first space to the second space. mapView.Navigation.draw(directions); } } ``` The navigation visualization can be customized by passing in a TNavigationOptions object to Navigation.draw(). The following example demonstrates how to customize the navigation visualization, using the `pathOptions`, `markerOptions` and `createMarkers` properties. ```ts let navigationOptions: TNavigationOptions = { pathOptions: { displayArrowsOnPath: true, animateArrowsOnPath: true, accentColor: '#ffffff', }, markerOptions: { departureColor: '#228b22', destinationColor: '#ff6347', }, createMarkers: { departure: (instruction: TDirectionInstruction) => { const marker = mapView.Markers.add(instruction.coordinate, '<div>Departure</div>'); return marker; }, destination: (instruction: TDirectionInstruction) => { const marker = mapView.Markers.add(instruction.coordinate, '<div>Destination</div>'); return marker; }, connection: (instruction: TDirectionInstruction) => { const marker = mapView.Markers.add(instruction.coordinate, '<div>Connection</div>'); return marker; }, }, }; mapView.Navigation.draw(directions, navigationOptions); ``` ## Navigating From a Click Event A navigation start or end point may originate from a user clicking on the map. It is possible to create a start and or end point using the click event and accessing its TEvents payload. The code shown below uses the click position as a starting point to create directions. `nearRadius` and `farRadius` are described in the Path Styles section later in this guide. ```ts mapView.on('click', async event => { const clickedLocation = event.coordinate; const destination = mapData.getByType('space').find(s => s.name === 'Gymnasium'); // If the destination is found, navigate to it. if (destination) { //Ensure that directions could be generated (user clicked on a navigable space). const directions = mapData.getDirections(clickedLocation, destination); if (directions) { // Navigate from the clicked location to the gymnasium. mapView.Navigation.draw(directions, { pathOptions: { nearRadius: 1, farRadius: 1, }, }); } } }); ``` ## Multi-Floor Wayfinding Using Navigation, no additional work is needed to provide wayfinding between floors. Whenever a user needs to switch a floor, an interactive tooltip with an icon indicating the type of Connection (such as an elevator or stairs) will be drawn. By clicking or tapping the tooltip, the map view switches to the destination floor. The following CodeSandbox gives an example of multi floor navigation. ## Multi-Destination Wayfinding Multi-segment directions are directions that include multiple legs. They can be created by passing an array of destinations to MapData.getDirectionsMultiDestination(). ```ts // Pass an array of destinations to create multi-segment directions. const directions = mapData.getDirectionsMultiDestination(departure, destinations); ``` The following CodeSandbox demonstrates how to create multi-segment directions and draw them using Navigation. ## Wayfinding Using Accessible Routes When requesting directions, it is important to consider the needs of the user. Users with mobility restrictions may require a route that avoids stairs and escalators and instead uses ramps or elevators. The getDirections method accepts TGetDirectionsOptions, which allows specifying whether an accessible route should be returned. By default, the shortest available route is chosen. The following code demonstrates how to request directions that make use of accessible routes. ```ts const directions = await mapData.getDirections(space1, space2, { accessible: true }); ``` ## Drawing a Path While Navigation provides a complete start and end navigation illustration, it may be desired to draw just the path. This can be done using `Paths`. > Note that the MapView class implements the Paths interface and exposes it as MapView.Paths. Use MapView.Paths to utilize Paths methods. Paths can be drawn from one coordinate to another using Paths.add(). If using just two coordinates, the path will be drawn straight between the two points. This may work for some scenarios, but in most cases an app will need to show the user their walking path, going through doors and avoiding walls and other objects. Such a path of coordinates can be created by calling the MapData.getDirections() method, passing in a start and end TNavigationTarget. Note that a Space requires an entrance to be used as a target. The width of the path is set using the `nearRadius` and `farRadius` parameters. These values are in meters. `nearRadius` is the path width used at the lowest zoom level closest to the ground and `farRadius` is used at the highest zoom level. Additional path styles are outlined later in this guide in the Path Styles section. ```ts // Get the directions between two spaces. const directions = mapData.getDirections(startSpace, endSpace); // Draw a path using the directions' coordinates array to show the route. path = mapView.Paths.add(directions.coordinates, { nearRadius: 0.5, farRadius: 0.5, }); ``` The following CodeSandbox draws a path from one location to another. Click on a room to set the departure point, then click another room to set the destination. When a path is shown, clicking anywhere will remove it. ## Removing Paths There are two methods that can be used to remove paths. Paths.remove() accepts a path to remove and is used to remove a single path. To remove all paths, Paths.removeAll() can be used. ```ts // Remove a single path. mapView.Paths.remove(path); ``` ```ts // Remove all paths mapView.Paths.removeAll(); ``` ## Path Styles Mappedin JS offers many options to customise how paths appear to the user. Path animations, color, width and many more options can be set using TAddPathOptions. ## Dynamic Routing When generating directions, Mappedin JS provides the most direct route possible between the origin and destination. There can be scenarios when this is not desired, such as when there there is an obstruction like a spill that requires cleaning or where maintenance crews are active. The MapData.getDirections() method accepts a `zones` parameter that denotes areas that could be avoided. Zones are defined using TDirectionZone and contain a `cost`, `floor` and `geometry`. `cost` represents the additional cost for navigation through the zone and can range from from 0 to Infinity. A cost of 0 will make the zone free to navigate through. A zone cost of Infinity will block passage through the zone. Multiple zones occupying the same location they are added together, along with the cost from the map. The SDK may route directions through a zone if all other routes have a higher cost. `floor` represents the floor for the zone. If not specified the zone blocks all floors. `geometry` is held within a Feature that contains a MultiPolygon or Polygon. The following CodeSandbox demonstrates the use of zones to alter directions. Left click to add a zone with a cost of 10. Right click to add a zone with a cost of Infinity. Middle click to clear all zones. Try stacking multiple zones on top of each other and observe how the lowest cost route is used. ## Turn-by-Turn Directions Turn-by-Turn directions are a set of text instructions describing the route the user needs to take. An example is "Turn right and go 10 meters". These could be shown to the user in a list to give them an overview with all steps they need to take, or the current instruction could be displayed along with a map showing the next leg of their journey. The code sample assembles these actions together and: - Gets directions between the start and end TNavigationTarget. - Draws a path using the directions' coordinates. - Creates a Marker with textual turn-by-turn instructions for each leg in the journey. Refer to the Marker Guide for more information on using Markers. ```ts // Get directions from the first space to the second space. const directions = mapData.getDirections(firstSpace, secondSpace); // Add a path from the first space to the second space. mapView.Paths.add(directions.coordinates); // Add markers for each direction instruction. directions.instructions.forEach((instruction: TDirectionInstruction) => { const markerTemplate = ` <div class="marker"> <p>${instruction.action.type} ${instruction.action.bearing ?? ''} in ${Math.round( instruction.distance )} meters.</p> </div>`; mapView.Markers.add(instruction.coordinate, markerTemplate, { rank: 4, }); }); ``` The following CodeSandbox demonstrates how to create and display turn-by-turn directions using Markers. ## api-reference ### Mappedin JS # Mappedin JS ## Latest Version Mappedin JS v6.0.1-beta.53 ## Previous Versions v6.0.1-beta.52v6.0.1-beta.51v6.0.1-beta.50v6.0.1-beta.49v6.0.1-beta.48v6.0.1-beta.46v6.0.1-beta.45v6.0.1-beta.44v6.0.1-beta.43v6.0.1-beta.42v6.0.1-beta.41v6.0.1-beta.40v6.0.1-beta.39v6.0.1-beta.38v6.0.1-beta.37v6.0.1-beta.36v6.0.1-beta.35v6.0.1-beta.34v6.0.1-beta.33v6.0.1-beta.32v6.0.1-beta.31v6.0.1-beta.30v6.0.1-beta.29v6.0.1-beta.28v6.0.1-beta.27v6.0.1-beta.26v6.0.1-beta.25v6.0.1-beta.24v6.0.1-beta.23v6.0.1-beta.22v6.0.1-beta.20v6.0.1-beta.18v6.0.1-beta.17v6.0.1-beta.16v6.0.1-beta.15v6.0.1-beta.14v6.0.1-beta.13v6.0.1-beta.12v6.0.1-beta.11v6.0.1-beta.10v6.0.1-beta.9v6.0.1-beta.8v6.0.1-beta.7v6.0.1-beta.6v6.0.1-beta.5v6.0.1-beta.4v6.0.1-beta.3v6.0.1-beta.2v6.0.1-beta.1v6.0.1-beta.0v6.0.1-alpha.38v6.0.1-alpha.37v6.0.1-alpha.36v6.0.1-alpha.35v6.0.1-alpha.34v6.0.1-alpha.33v6.0.1-alpha.32v6.0.1-alpha.31v6.0.1-alpha.30v6.0.1-alpha.29v6.0.1-alpha.28v6.0.1-alpha.27v6.0.1-alpha.26v6.0.1-alpha.25v6.0.1-alpha.24v6.0.1-alpha.23v6.0.1-alpha.22v6.0.1-alpha.21v6.0.1-alpha.20v6.0.1-alpha.19v6.0.1-alpha.18v6.0.1-alpha.17v6.0.1-alpha.16v6.0.1-alpha.15v6.0.1-alpha.14v6.0.1-alpha.13v6.0.1-alpha.12v6.0.1-alpha.10v6.0.1-alpha.9v6.0.1-alpha.8v6.0.1-alpha.7v6.0.1-alpha.5v6.0.1-alpha.4v6.0.1-alpha.3v6.0.0-rc.0 ### React # React ## Latest Version Mappedin React SDK v6.0.1-beta.53 ## Previous Versions v6.0.1-beta.52v6.0.1-beta.51v6.0.1-beta.50v6.0.1-beta.49v6.0.1-beta.48v6.0.1-beta.46v6.0.1-beta.45v6.0.1-beta.44v6.0.1-beta.43v6.0.1-beta.42v6.0.1-beta.41v6.0.1-beta.40v6.0.1-beta.39v6.0.1-beta.38v6.0.1-beta.37v6.0.1-beta.36v6.0.1-beta.35v6.0.1-beta.34v6.0.1-beta.33v6.0.1-beta.32v6.0.1-beta.31v6.0.1-beta.30v6.0.1-beta.29v6.0.1-beta.28v6.0.1-beta.27v6.0.1-beta.26v6.0.1-beta.25v6.0.1-beta.24v6.0.1-beta.23v6.0.1-beta.22v6.0.1-beta.20v6.0.1-beta.18