## v1 ### Getting Started with MVF v1 # Getting Started with MVF v1 **Mappedin Venue Format (MVF)** is a GeoJSON based data format that represents a venue, so that it can be used programmatically. MVF contains geometry and location data associated with a venue, as seen in the Mappedin CMS. !GeoJSON exports - map desktop Mappedin’s highly accurate indoor map data can be exported as GeoJSON files for a variety of use cases. These GeoJSON based exports provide complete flexibility to build any indoor mapping experience. ## Exporting MVF file You can export the venue's map from Mappedin CMS. This export includes nodes and paths as well, allowing you to easily build wayfinding capabilities. This format can be used to build 2D mapping experiences or converted with a variety of 3D renderers, including Mappedin's. ## Coding with AI Mappedin provides an llms-mvfv1.txt file that can be used to help with coding when using Large Language Models (LLMs). ## Sample MVF Here's how an MVF export looks for one of the indoor venues: ```json { "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [-80.5315920413582, 43.52407304453391], [-80.53157331246412, 43.524069388237464] ] ] }, "properties": { "id": "5a8c81503e7e180413000000", "venue": "58347b71031d9c158b000001", "name": "Default" } } ] } ``` To play more and explore other venues' MVF exports, download a sample package: MVF Demo Files. ### MVF v1: Data Model # MVF v1: Data Model The Mappedin Venue Format consists of several data models that our SDKs use to display your indoor maps correctly: ### Venue A Venue is the most top level data model that encompasses all other data models | Property | Description | Optional | | --------- | ------------------------------------------------------------------------------ | -------- | | id | A unique string identifier | No | | name | A string that represents the name of the venue | No | | slug | A string that represents a more human-readable unique identifier for the venue | No | | buildings | A list of building ids that the venue is linked to | No | ### Building A building is a collection of levels assembled into a structure within a venue, such as an office tower or a theatre. A building's geometry is determined by the union of all of the level geometries within the physical building. You can have multiple buildings per venue. | Property | Description | Optional | | -------- | ------------------------------------------------- | -------- | | id | A unique string identifier | No | | name | A string that represents the name of the building | No | | venue | The venue id that the building is associated with | No | ### Level A level is a traversable level within a `Building` and is represented by a `Polygon` geometry. A level will never extend beyond the bounds of its building. | Property | Description | Optional | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | id | A unique string identifier | No | | name | A string that represents the name of the level | No | | building | The id of the `Building` that the level is contained in | No | | abbreviation | A string that represents an abbreviation of the level's name | No | | elevation | An integer that represents the elevation of the level relative to the ground, possible values are 0 (ground level), -n (underground level n) and +n (above ground level n) | No | ### Space A space is a traversable area such as a room that exists in a `Level`. Spaces are represented by a `Polygon` geometry or `Point` geometry. | Property | Description | Optional | | ---------- | ------------------------------------------------------------------------- | -------- | | id | A unique string identifier | No | | level | The id of the level that the space is contained within | No | | parent | The id of the parent `Space` that the current `Space` is contained within | Yes | | externalId | The external id field of the space (e.g. unit number) | Yes | ### Obstruction An obstruction is a non-traversable area (e.g. partition wall), meaning a path will never be created through an obstruction. Obstructions are represented by a `Polygon` geometry and may intersect with `Space`s | Property | Description | Optional | | ---------- | ------------------------------------------------------------------------------------- | -------- | | id | A unique string identifier | No | | level | The id of the level that the obstruction is contained within | No | | parent | The id of the parent `Obstruction` that the current `Obstruction` is contained within | Yes | | externalId | The external id field of the obstruction | Yes | ### Connection A connection is a mechanism that facilitates traversal between spaces, such as a staircase or an elevator within a building. Connections are linked by two different nodes. When creating a path, an accessible parameter can be provided that determines if a connection should be used for the path. Connections are represented by a `Polygon` geometry or a `Point` geometry. | Property | Description | Optional | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | id | A unique string identifier for the node this connection is on | No | | name | A string that represents the name of the connection (e.g. Escalator 1) | No | | level | The id of the level that the connection is contained within | No | | destinations | A list of id pairs in the format "vortex-node" where vortex is the id of this vortex and node is the id of the destination node on another floor | No | | type | Possible types consist of “stairs”, “elevator”, “escalator”, “moving-walkway”, “ramp” and “slide” | Yes | | weight | An integer that represents the cost associated with taking a connection, used in conjunction with the Connection's muliplier property when calculating the least "costly" path. | No | | multiplier | An integer that is multiplied against the weight to calculate the full cost associated with taking a connection | No | | accessible | A boolean that represents if the connection is wheelchair accessible or not | No | ### Location A location is a place of interest that is linked to a `Space`, such as a store or meeting room. Locations do not contain a geometry. | Property | Description | Optional | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | id | A unique string identifier | No | | name | A string that represents the name of the location | Yes | | spaces | A list of space ids that the location is linked to. In most cases a location will be associated with a single space id. However there are certain cases where there will be multiple space ids, such as when a location is on multiple levels. | No | | type | Possible values consist of “tenant" or "amenity" | No | | categories | A list of category ids that the location is linked to (e.g. Restaurant) | No | | externalId | The external id field of the location | No | | logo | An object that contains image urls for the logo of the location | No | | picture | An object that contains image urls for the picture of the location | No | | phone | An object that contains the phone numbers of the location | No | | email | A string that represents the email of the location | No | | social | An object that contains social media urls of the location | No | | hours | An array that contains the hours of operation of the location | No | ### Category A category is a group of locations with shared characteristics, such as Restaurants or Meeting Rooms. Categories are represented by a null geometry and are instead represented by their associated locations. | Property | Description | Optional | | -------- | ------------------------------------------------------------------ | -------- | | id | A unique string identifier | No | | name | A string that represents the name of the category | No | | picture | An object that contains image urls for the picture of the category | Yes | ### Node A node is a point on the map that is used to create a path from a starting location to a destination. Nodes may be linked to other nodes which is represented by its `neighbors` property. | Property | Description | Optional | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | id | A unique string identifier | No | | level | The id of the level that the node is contained within | No | | neighbors | A list of node ids that the node is linked to, allowing a path to be created from one node to another | No | | weight | An integer that represents the cost associated with taking a node, used in conjunction with the Node's muliplier property when calculating the least "costly" path. | No | | multiplier | An integer that is multiplied against the weight to calculate the full cost associated with taking a node | No | | accessible | A boolean that represents if the node is wheelchair accessible or not | No | ### MVF v1: Render with deck.gl # MVF v1: Render with deck.gl deck.gl is a popular open source framework for rendering maps and data with WebGL. In this guide, we'll demonstrate using Mappedin Venue Format (MVF) data to render the Mappedin Demo Mall with deck.gl layers. ## Setup > Although we recommend and use TypeScript in this guide, with small modifications you can adjust the code snippets to work with JavaScript as well. 1. Start a new vanilla Vite TypeScript project with - `yarn create vite mappedin-deckgl` - Select Vanilla and TypeScript - Then run `cd mappedin-deckgl && yarn` - `yarn dev` to start run the project. This will display a Vite welcome page 2. Download the MVF sample package 3. Extract the contents of `MVF_Demo_Files` folder to `/public/data/` in the project folder 4. Add deck.gl, community deck.gl types and GeoJSON types. To avoid adding React as a dependency, we'll import only the necessary the deck.gl submodules. ```bash yarn add @deck.gl/core @deck.gl/layers @danmarshall/deckgl-typings @types/geojson ``` 5. Create a `deckgl.d.ts` file for TypeScript definitions We're ready to begin writing. Start by clearing `src/main.ts` and add the following imports. ```ts title=main.ts import { Deck } from '@deck.gl/core'; import { GeoJsonLayer } from '@deck.gl/layers'; import { RGBAColor } from 'deck.gl'; import { Feature, FeatureCollection } from 'geojson'; ``` ## Loading MVF Data The `manifest.geojson` file in the MVF folder defines the structure and other files in the bundle. The MVF Data Model talks more about these files and their properties in detail. Right now we need only "Level", "Space" and "Node". Create a simple `fetch` helper to load GeoJSON data from the public folder. Parse the venue slug, data type, and level id to create the path to the file. ```ts async function loadData(venue: string, type: string, id: string): Promise { return (await fetch(`/data/${venue}/${type}/${id}.geojson`)).json(); } ``` The Mappedin Demo Mall features three levels and most of the data is divided into three files named after the level id. We can find the level id of "Lower Level" by looking at the `properties` of the level files and selecting the correct file name. For the purposes of this quick guide, we'll hardcode the slug and level id. Begin by fetching the data we need with these values using the `loadData` function we wrote earlier. ```ts const slug = 'mappedin-demo-mall'; const levelId = '5835ab589321170c11000000'; const levelData = await loadData(slug, 'level', levelId); const spacesData = await loadData(slug, 'space', levelId); const nodeData = await loadData(slug, 'node', levelId); ``` We now have three GeoJSON `FeatureCollections` representing the traversable areas and the nodes within our map. Next, we'll create our deck.gl layers. ## Creating Layers Let's transform the MVF data we just loaded into layers for deck.gl to render. To do this, we'll start by using deck.gl's GeoJsonLayer class. Create a new `GeoJsonLayer` for levels and give it the id "**level-layer**". Feed the `levelData.features` from MVF in as the data. Since this layer represents the background traversable area of the building floor, style it with a grey fill color and thin line width. ```ts const levelLayer = new GeoJsonLayer({ id: 'level-layer', data: levelData.features, getFillColor: [119, 119, 119, 255], getLineWidth: 0.1, }); ``` Our spaces layer represents the "rooms" in the venue, which are often assigned with a hex color code. Since deck.gl expects an RGB array for colors, write another short helper to convert our hexadecimal codes to RGB. ```ts function hexToRGB(hex: string): RGBAColor { return hex.match(/[0-9a-f]{2}/g)!.map((x) => parseInt(x, 16)) as RGBAColor; } ``` Now we can proceed in creating the "**spaces-layer**". It will take `spacesData.features` as the data and filtered for only "Polygon" geometry type. For the fill color, we'll access the color stored within each `Feature` object's properties. Use our newly created `hexToRGB()` to convert the color to RGB or fallback to white. ```ts const spacesLayer = new GeoJsonLayer({ id: 'spaces-layer', data: spacesData.features.filter((f) => f.geometry.type === 'Polygon'), getFillColor: (feature) => { return hexToRGB((feature as Feature).properties!.color) || [255, 255, 255, 255]; }, getLineWidth: 0.1, }); ``` Last, we'll create the layer for nodes. The "**node-layer**" takes the `nodeData.features` and an orange fill color with 40% opacity. ```ts const nodeLayer = new GeoJsonLayer({ id: 'node-layer', data: nodeData.features, getFillColor: [255, 120, 0, 102], getLineWidth: 0.1, }); ``` ## Rendering the Map Finally, we can initialize a new Deck object to render our layers on screen. Set the `initialViewState` to the approximate latitude and longitude for the Mappedin Demo Mall and enable the controller for interactivity. Then, add our three layers to the map in the order in which they are drawn. ```ts new Deck({ initialViewState: { latitude: 43.52014270934553, longitude: -80.53550722882292, zoom: 16, }, controller: true, layers: [levelLayer, spacesLayer, nodeLayer], }); ``` The final product can be seen in the CodeSandbox below and demonstrates a good starting point for working with MVF and deck.gl. The orange dots represent the nodes which can be used for pathfinding through the venue. You can find more information about GeoJSON data available in the MVF package on the MVF Data Model page. ## Additional Reading - Visualizing Warehouse Operations with Indoor Maps ### MVF v1: Render with Leaflet # MVF v1: Render with Leaflet As an extension of GeoJSON, the Mappedin Venue Format (MVF) is easy to integrate with various GIS tools and technologies. We use Typescript in this guide to help us with the GeoJSON data structure but the code works with small modifications in Javascript as well. In this guide, we will render the Mappedin Demo Mall using a popular web map library, Leaflet. !GeoJSON Exports - Render Leaflet 1 ## Setup 1. Start a new Vite Typescript -project (or JS tooling of your choice) with - `yarn create vite mappedin-leaflet` - Select Vanilla and Vanilla-ts - Then run `cd mappedin-leaflet && yarn` - `yarn dev` to start run the project. This will display a Vite welcome page 2. Download the MVF sample package 3. Extract the contents of `Demo Mall MVF` folder to to `data/mappedin-demo-mall/` in the project folder 4. Add Leaflet with `yarn add leaflet` 5. Install Leaflet and GeoJSON types as DevDependencies `yarn add -D @types/leaflet @types/geojson` Modify your `index.html` to the following ```html Leaflet MVF
``` Clear `src/main.ts` and add the following imports and styles. For `spaces`, we use a `StyleFunction` that returns `PathOptions` based on the GeoJSON feature's properties. ```ts import * as L from 'leaflet'; import * as GeoJSON from 'geojson'; const styles = { building: { color: '#BF4320', opacity: 0.5, fillOpacity: 1, fillColor: '#fafafa', dashArray: '5 10', }, level: { fillColor: '#777', fillOpacity: 1, color: 'black', weight: 1, }, spaces: (feature: any) => { return { color: 'black', weight: 1, opacity: 1, fillOpacity: 1, fillColor: feature.properties!.color || 'white', }; }, obstruction: { weight: 0, fillColor: 'black', fillOpacity: 1, }, node: { radius: 2, fillColor: '#BF4320', color: '#000', weight: 1, opacity: 1, fillOpacity: 0.4, }, }; ``` ## Loading MVF data We will load the data using `fetch`. `Manifest.geojson`-file in the MVF folder explains the structure and other files in the bundle. Data model documentation explains the files and their properties in detail. ```ts async function loadData(filename: string) { return (await fetch(`${filename}`)).json(); } ``` Mappedin Demo Mall features 3 levels and most of the data is divided into 3 files named after the level id. For this quick guide, we will hardcode our level id. . We can find the level id of "Lower Level" by looking at the `properties` of the level -files and selecting the correct file name. ```ts const levelId = '5835ab589321170c11000000'; const building = (await loadData('data/mappedin-demo-mall/building.geojson')) as GeoJSON.FeatureCollection; const level = (await loadData(`data/mappedin-demo-mall/level/${levelId}.geojson`)) as GeoJSON.FeatureCollection; const space = (await loadData(`data/mappedin-demo-mall/space/${levelId}.geojson`)) as GeoJSON.FeatureCollection; const obstruction = (await loadData( `data/mappedin-demo-mall/obstruction/${levelId}.geojson`, )) as GeoJSON.FeatureCollection; ``` ## Creating and drawing layers We create the building layer first so that we can use it to define the area where Leaflet displays the map. `getBounds()` calculates the boundaries needed to display the whole area described by the building polygon data. The map is drawn to the element with id `map`. ```ts const buildingLayer = L.geoJSON(building, { style: styles.building, }); const map = L.map('map', { maxBounds: buildingLayer.getBounds(), }); map.fitBounds(buildingLayer.getBounds()); ``` With the spaces, we filter the data to make sure that we only draw polygons, because it also includes `Point` geometry. ```ts const levelLayer = L.geoJSON(level, { style: styles.level }); const obstructionLayer = L.geoJSON(obstruction, { style: styles.obstruction }); const spaceLayer = L.geoJSON(space, { style: styles.spaces, filter: (feature) => { return feature.geometry.type === 'Polygon'; }, }); ``` Add layers to the map in the order in which they are drawn ```ts buildingLayer.addTo(map); levelLayer.addTo(map); obstructionLayer.addTo(map); spaceLayer.addTo(map); ``` This is how the map looks at the end. The orange dots represent nodes, which can be used for pathfinding through the venue. The MVF package comes with rich data about each `location` in the venue and all of that can be added on top of the base built in this tutorial.