## data-sync ### Airport Data Objects # Airport Data Objects ## Airport Data The Mappedin system has objects that can be synced that are specific to airports: Check-In Counter, Baggage Claim, Security Wait Times, Flight and Parking. These objects provide additional capabilities to Mappedin Web users, such as: - Searching for their gate by flight number - Integrating security wait times into estimated journey times - Finding the check-in counter for their airline - Viewing available parking ### Check-In Counter The airline occupying a specific set of check-in counters may vary over the course of a day. A `check-in` is a special type of location that may only be active for a set period in the day. To handle this, Mappedin can run a higher frequency sync to an airline `check-in` endpoint that returns current active check-ins to ensure counters are accurately associated with the correct airlines on the map at all times. | Property | Description | Type | Importance | | ----------- | -------------------------------------------------------------------------------------------------------------- | --------------- | ---------- | | name | Airline name | `string` | Required | | externalId | Unique identifier of the location entry | `string` | Required | | type | Differentiates check-in locations from regular locations. Use value `check-in` to specify a check-in location. | `string` | Required | | polygon | Unique identifier(s) of related check-in counter polygon(s) in the map | `Array` | Required | | logo | URL to airline logo image | `string` | Required | | airportData | Contains an object called `airlineCode` that is a string that contains the airline code. | `object` | Optional | Click to view a JSON example. ```json [ { "name": "Airline Name", "externalId": "location_id_1", "type": "check-in", "polygons": ["check-in-counter-1A", "check-in-counter-1B", "check-in-counter-1C"], "logo": "http://www.example.com/Example-Airline-Logo.jpg", "airportData": { "airlineCode": "AC" } } ] ``` ### Baggage Claim For airports, a Location Object may contain additional properties that are specific to a baggage claim | Property | Description | Type | Importance | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | | name | Airline name. | `string` | Required | | type | Differentiates check-in locations from regular locations. Use value `baggage-claim` to specify a baggage claim location. | `string` | Required | | venue | Name of the venue to be displayed | `string` | Required | | airportData | Contains an object called `baggageClaimId` that is an array of strings, each representing the ID of a baggage claim. The IDs are formatted as `sectorName;claimUnit` and must match the values in the flight data API provided to Mappedin. | `object` | Optional | Click to view a JSON example. ```json { [ { "name": "Baggage Claim 1 (Terminal 3)", "type": "baggage-claim", "venue": "venue1", "airportData": { "baggageClaimId": ["3;1", "3"] } } ] } ``` ### Security Wait Times On a Mappedin Map, a connection is used to represent a path between two nodes. Security checkpoints are represented as connections that allow passage in only one direction and have a wait time, representing the time it takes for a traveller to pass through it. | Property | Description | Type | Importance | | ---------- | --------------------------------------------- | -------- | ---------- | | externalId | Unique identifier of the connection | `string` | Required | | waitTime | Wait time in minutes for the given connection | `number` | Required | Click to view a JSON example. ```json [ { "externalId": "SEC1", "waitTime": 5 }, { "externalId": "SEC2", "waitTime": 15 }, { "externalId": "SEC3", "waitTime": 9 } ] ``` ### Flight Flight data allows a user to perform a search using their airline name or flight code. The search results can show them additional details and provide directions to the gate of departure or arrival. | Property | Description | Type | Importance | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ---------- | | id | Unique identifier | `string` | Required | | airlineCode | Airline code | `string` | Required | | airlineName | Airline name | `string` | Required | | airportName | Airport name | `string` | Required | | sectorName | Terminal name or number | `string` | Required | | primaryGate | Gate number | `string` | Required | | claimUnit | Baggage claim number | `string` | Required | | leg | Arrival or departure leg | `A` or `D` | Required | | flightStatus | Object containing status & color. Color can be set using CSS colors names `DarkGoldenRod`, as Hex strings `#0000FF` or the rgb values `rgb(255,0,0)`. | status: `string`, color: `string` | Required | | flightNumber | Flight number | `string` | Required | | scheduledTimeLocal | Scheduled time of arrival or departure in ISO 8601 format | `string` | Required | | estimatedTimeLocal | Estimated time of arrival or departure in ISO 8601 format | `string` | Optional | Click to view a JSON example. ```json { "id": "2024-AL-123", "airlineCode": "AL", "airlineName": "Airline", "airportName": "Airport", "sectorName": "Domestic", "primaryGate": "50", "claimUnit": "8" "leg": "A", "flightStatus": { "status": "DEPARTED", "color": "#2266FF" }, "flightNumber": "AL123", "scheduledTimeLocal": "2024-04-03T09:00:00.000", "estimatedTimeLocal": "2024-04-03T09:15:00.000", "checkinId": "POLY-123", "claimUnitId": "POLY-456" } ``` ### Parking Using the parking object allows users to see information about parking lots. They can check for things such as the parking lot size, number of free spaces and whether the parking lot is open or closed. | Property | Description | Type | Importance | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ---------- | | id | Unique identifier | `string` | Required | | name | Parking lot name | `string` | Required | | externalId | Unique identifier of the location for the parking lot entrance | `string` | Optional | | freeSpaces | Number of free parking spaces | `number` | Required | | totalSpaces | Total number of parking spaces | `number` | Required | | freeEVSpaces | Free electric vehicle charging spaces | `number` | Optional | | status | Status of the parking lot | `string` | Required | | color | Color for the status pill and parking lot background. Color can be set using CSS colors names `DarkGoldenRod`, as Hex strings `#0000FF` or the rgb values `rgb(255,0,0)`. | `string` | Required | | lastUpdatedDate | Last updated time in local time in ISO 8601 format | `string` | Required | | isClosed | Whether the parking lot is closed | `boolean` | Optional | Click to view a JSON example. ```json { "id": "PA", "name": "Parking A", "externalId": "POLY-123", "freeSpaces": 200, "totalSpaces": 300, "freeEVSpaces": 10, "status": "OPEN", "color": "#33AC30", "lastUpdatedDate": "2024-04-03T13:36:07.983" "isClosed": false, } ``` ### Common Data Objects # Common Data Objects ## Data The Mappedin system has four types of objects that can be synced that are common across all verticals: Venues, Locations, Categories and Events. Mappedin products (like Mappedin Dynamic Directory and Mappedin Web) require certain properties on these objects to be populated in order to function correctly. Some other properties are optional, but can enhance the user experience. Others still can just be stored, either for potential use in a future version of a product, or to be available in the SDKs for a custom app you build. Descriptions for the what the object types are and what fields can be synced are as follows: ### Venue A Venue is defined as a whole property (like a Mall, or a Hospital) that has Locations inside of it. This is an optional object and not required for a data sync to be setup. It should have the following properties: | Property | Description | Type | Importance | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------- | | name | Name of the venue to be displayed | `string` | Required | | externalId | Unique identifier of the venue | `string` | Required | | address | Street address of the venue | `string` | Optional | | city | City name | `string` | Optional | | state | State or province | `string` | Optional | | postal | Postal code or zip code | `string` | Optional | | telephone | Phone number | `string` | Optional | | website | URL to venue's website | `string` | Optional | | operationHours | Venue opening hours in OpeningHoursSpecification format (including holiday information) | `object` | Optional | | propertyImage | URL of an image of the property | `string` | Optional | Click to view sample of an ideal JSON ```json [ { "name": "Mappedin Demo Mall", "externalId": "mappedin-demo-mall", "address": "460 Phillip St #300", "city": "Waterloo", "state": "Ontario", "postal": "N2L 5J2", "telephone": "519-594-0102", "website": "http://www.mappedin.com", "operationHours": [ { "@type": "OpeningHoursSpecification", "opens": "10:00", "closes": "23:00", "dayOfWeek": ["Sunday"] }, { "@type": "OpeningHoursSpecification", "opens": "09:00", "closes": "22:00", "dayOfWeek": [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] }, { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Saturday"], "validFrom": "2018-06-30", "validThrough": "2018-06-30", "opens": "00:00", "closes": "00:00" } ] } ] ``` ### Location A Location is an individual place of interest (like a store, restaurant, tenant, or amenity) in a Venue. A location can be tied to multiple physical locations. For example, all the restrooms might be modeled as a single location. Multiple Starbucks might be modeled as one location or several. An Anchor may span multiple floors. Locations should have the following properties: **Required:** - A unique alpha-numeric ID that will not change on subsequent syncs (Tenant ID) - A unit ID or equivalent that links to a space in the mall - Name - Location Hierarchy (if applicable) - If multiple locations are to be attached to the same physical space (Sears and Sears Optical, for example), a way to determine which is the main one (Sears) **Highly Recommended:** - Description - Logo image - Categories **Optional:** - Parent location if this location is the child of another location (tenant ID of the parent location) - Type (ie, `Store` vs `Restaurant`) - State (if the location is `closed temporarily`, `new` or `coming soon`) - Hours (especially if different from Mall hours), in OpeningHoursSpecification - Search keywords (tags) - Phone Number - URLs to social media accounts (Twitter, Facebook, Instagram) - A Website URL for a tenant's own website, or a Details URL that links to information on the current site Contact name and email - URL friendly name (must be unique per venue, for use in the URL when deep linking in to Mappedin Web) - URL for a header image | Property | Description | Type | Importance | | -------------- | --------------------------------------------------------------------------------------------------------- | --------------- | ---------- | | name | Name of the location to be displayed | `string` | Required | | externalId | Unique identifier of the location | `string` | Required | | polygons | Unit ID of the polygon to attach the location to | `Array` | Required | | description | Description of the location | `string` | Optional | | language | If there are other languages than the default | string | Optional | | telephone | Phone number | `string` | Optional | | operationHours | Opening hours for the location for each day as well as special hours | `Array` | Optional | | links | Webpage links for the location. Each link requires a URL and has an optional label | `Array` | Optional | | logoKey | URL for location logo | `string` | Optional | | categories | Category IDs of the location | `Array` | Optional | | social | Social media links | `object` | Optional | | tags | List of search tags for the location | `Array` | Optional | | parent | The `externalId` of the parent location | `string` | Optional | | states | Status of location, `coming-soon`, `new`, `closed-temporarily` or `pop-up` | `Array` | Optional | | gallery | Links to pictures of the location. Each item requires an image URL. Caption and embedded URL are optional | `Array` | Optional | | headerImage | URL for a header image. Displayed at the top of the profile card on Mappedin Web. | `string` | Optional | _Note: We've often found that Amenities (washrooms, ATMs, etc) are not managed in our partner's internal CMS. In that case Amenities are managed entirely within the Mappedin CMS. If you do have complete amenity information in your system, we can sync it like another type of Location. Amenities require a name, amenity type, icon, and a reference to any places they exist on the map._ Click to view sample of an ideal JSON ```json [ { "name": "Example Store", "externalId": "tenant_id_1", "parent": "parent_tenant_id", "description": "The best place to buy your favourite things - Guaranteed.", "detailsUrl": "http://www.example.com/StoreDetails", "categories": ["category_id_1", "category_id_2"], "polygons": ["space_id_matching_CAD_1"], "logo": "http://www.example.com/Example-Store-Logo-filename-that-changes-if-the-file-changes.jpg", "phone": "(555) 555-5555", "social": { "facebook": "https://www.facebook.com/ExampleStore", "twitter": "https://twitter.com/ExampleStore", "instagram": "https://instagram.com/ExampleStore", "website": "https://www.examplestore.com" }, "tags": "clothes, things, brand name, shoes", "states": [ { "type": "coming-soon", "start": "2019-01-01", "end": "2025-01-01" } ], "operationHours": [ { "@type": "OpeningHoursSpecification", "opens": "10:00", "closes": "23:00", "dayOfWeek": ["Sunday"] }, { "@type": "OpeningHoursSpecification", "opens": "09:00", "closes": "22:00", "dayOfWeek": [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] }, { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Saturday"], "validFrom": "2018-06-30", "validThrough": "2018-06-30", "opens": "00:00", "closes": "00:00" } ], "headerImage": "http://www.example.com/Example-Header-Image-filename-that-changes-if-the-file-changes.jpg", } ] ``` ### Categories A Location can belong to one or more Categories, and Categories can have a parent/child hierarchy. **Highly Recommended:** - Picture (Full colour image for Dynamic Directory) - Logo (Pictograph for use in Mappedin Web) | Property | Description | Type | Importance | | ----------- | --------------------------------------------------------------------------- | --------------- | ---------- | | name | Name of the category to be displayed | `string` | Required | | externalId | Unique, durable identifier for the category which is used for the locations | `string` | Required | | language | If there are other languages than the default | `string` | Optional | | iconKey | URL to logo | `string` | Optional | | pictureKey | URL to picture | `string` | Optional | | parents | An array containing a single externalId of the parent category | `Array` | Optional | Click to view sample of an ideal JSON ```json [ { "name": "Clothing", "externalId": "category_id_1" }, { "name": "Fashion", "externalId": "category_id_2", "language": "en", "iconKey": "http://www.example.com/Fashion-filename-that-changes-if-the-file-changes.jpg", "parents": ["category_id_1"] } ] ``` ### Events An Event is a thing that happens for a period of time, either once or with some recurrence. It can optionally be associated with a location, or just present at the Venue in general. This is an optional object and not required for a data sync to be setup. **Required:** - Name - Durable ID - Start date - End date **Highly Recommended:** - Image - Show date (if you want to show it before it's live) **Optional:** - Location ID, corresponding to a Tenant ID that matches an existing Location For recurring events, if the rules are more complicated than "weekly at the same time", we require an iCalendar string. See rrule or similar for help generating these. | Property | Description | Type | Importance | | ----------- | ------------------------------------------------------------------------ | -------- | ---------- | | name | Name of the event / promotion | `string` | Required | | externalId | Unqiue identifer for the event | `string` | Required | | type | Type of event such as `event` or `promotion` | `string` | Required | | startDate | Unix timestamp of event start date | `number` | Required | | endDate | Unix timestamp of event end date | `number` | Required | | showDate | Unix timestamp of when to start displaying the event | `number` | Optional | | location | Location the event is taking place; using the externalId of the location | `string` | Optional | | description | Description of the event | `string` | Optional | | imageKey | URL to image of event or promotion | `string` | Optional | Click to view sample of an ideal JSON ```json [ { "name": "Event", "externalId": "vN7m3kH9rU1Mxxz", "location": "NqtdVzVkfrCkDtODwnWb", "type": "event", "startDate": "1577854816800", "endDate": "1609347616800", "showDate": "1577854816800", "description": "Sample of an event", "imageKey": "http://www.example.com/Example-event-image-filename-that-changes-if-the-file-changes.jpg" }, { "name": "Promotion", "externalId": "QZd6cuI7raKDFiD", "type": "promotion", "startDate": "1577854816800", "endDate": "1609347616800" } ] ``` #### Overlay Mappedin's Digital Directory also supports the option for Ad Overlays. In order to sync Ad Overlays, there are two main differences from other events. The event `type` needs to be set as `ad` or `ad overlay`, in order to differentiate it from other event types, and an url to an image is required. Learn more about event types. | Property | Description | Type | Importance | | --------- | ---------------------------------- | -------- | ---------- | | name | Name of the ad | `string` | Required | | type | Set as 'ad' or 'ad overlay' | `string` | Required | | startDate | Unix timestamp of event start date | `number` | Required | | endDate | Unix timestamp of event end date | `number` | Required | | imageKey | URL to image of Ad Overlay | `string` | Required | Click to view sample of an ad overlay ```json { "name": "Ad Overaly", "type": "ad", "startDate": 1643691600000, "endDate": 1651377600000, "imageKey": "https://www.example.com/" } ``` ### X\_ properties Venue, Location, Category and Event can all have extra, optional properties not part of the standard Mappedin data model. These are referred to as `x_ properties`, because they are all prefixed with `x_` when they are accessed from the object. If you are building an application with our SDKs and need access to certain properties present in your CMS, they can potentially be added via x\_ properties. Work with your Mappedin Developer Relations contact to determine if this is appropriate, and what the data might look like. ### Geodata Sync # Geodata Sync Enterprise customers with existing indoor map data may wish to import this into Mappedin, either as a one-time import forming the basis of a customer facing map, or as part of a recurring sync. This can be done by converting the data into the Mappedin Venue Format version 3 (MVFv3). Please use the Mappedin Demo Airport MVFv3 as a reference. ## Overview An MVFv3 is a zip file that contains implementations of one or more MVF Extensions. Each extension defines a set of JSON and GeoJSON files that can or must be included, as well as the shape of the data and how it links to other extensions. Unlike many other geospatial formats, MVF is optimized to represent entire venues in a single package, indoors and outdoors. This includes building geometry, points of interest, outdoor features, and the pathing throughout. This means where possible data is grouped into separate files per floor. Further details on MVFv3, including how to use it, can be found in the MVF v3 Specification. The rest of this document will outline what is needed for an import into the Mappedin CMS. ## Special Notes ### Identifiers Many extensions will include arrays of objects with an `id` property, which is a string that looks like `_`, like `f_67a24d8a241d315f309f2436`. These identifiers are used to link objects together, both within and between extensions. All identifiers MUST: - Be unique within the current MVF it is a part of. So a given MVF can only contain one floor with the id `f_1`, but MVFs representing different venues could each have their own `f_1`. - Be durable between imports. So if a venue is synced once with an `f_1`, the next time the sync is run, there should still be an `f_1` as long as that floor still exists. Non-durable identifiers will cause any manually managed references in non-imported extensions to break. For example, if floor identifiers changed every sync, and pathing was being done manually in CMS, every sync would delete the entire pathing network. If an object has different properties but fundamentally represents the same object, it should have the same identifier. Eg, a "First floor" gets an expansion and is now called "Main Floor" but still covers the old area, that is still `f_1`. In addition, any identifier referenced by an object in the MVF (for example, a `location` attaching to a `locationCategory`) MUST be present in the MVF itself. If not, the import will fail. In cases where the class of data is wholly managed in CMS, before submission the latest version of that extension should be downloaded from Mappedin and bundled into the MVF to be re-imported. ### Details Many extensions will include objects with definitions for an optional `details` property. This is a standard MVF concept, and `details` can always contain the same properties. On some extensions, `details` is actually required, usually along with the `name` property (see Locations). Example details ```json "details": { // The name of the object "name": "Joe's Coffee", // A description of the object "description": "Hot drinks at Joe's.", // An identifier that links the object to another system. // For example, a customer may have their own CMS containing location metadata, // and this would be the id of the location in that system. // In Mappedin products, the externalId is typically used as part of the URL // when generating deep links into that object, so it should be a stable and unique identifier. "externalId": "JOES-COFFEE-1", // Not always used, but if an object has an "icon"-like representation // (which is different from a logo or photograph) it can be included here. "icon": "https://cdn.mappedin.com/images/mappedin-demo-airport/icon.png", // An alternative name for an object, typically used in a space constrained environment. // For example, "Lobby" is often "L". "shortName": "JC", }, ``` ### GeoJSON All geojson files must be valid RFC-7946 compliant GeoJSON. In particular, this means the coordinate system must be WGS84 (EPSG:4326). This is also true of any other coordinates provided in non-geojson files. Any distances will be in meters. Extensions will provide additional constraints on particular geojson files, such as mandatory entries in the `properties` object. They may also limit collections to certain types of geometry, such as `Polygon` or `MultiPolygon`. Note that geometry in the MVFv3 exported by Mappedin after an import is done will NOT be identical to the original geometry. In particular, some rounding may occur, and any "Multi" geometries will be converted to their singular instances. Line strings will also be inflated into Polygons according to the style of the venue. ### Validation All MVFv3s must meet the spec, meaning the data must be in the correct format, and all references must be valid within the MVF. The `@mappedin/mvf` TypeScript package provides convenient functions to test, validate, and even create MVFv3 zip files. It is recommended to use this package as part of any MVFv3 build processes. Below are options for common scenarios. #### Online validator A manual check can be performed using the MVFv3 validator online tool. This operates locally in your browser, and is just a convenience front end for the `@mappedin/mvf` package. #### Post build step as part of a non-Node pipeline If the MVFv3 is being built in an environment other than Node, the `@mappedin/mvf` package can still be used as part of a post build step. ```js #!/usr/bin/env node import fs from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; import { createCMSMVFv3Parser } from '@mappedin/mvf/preset-cms'; const arg = process.argv[2]; if (!arg) { console.error('No file provided'); process.exit(1); } const filePath = path.resolve(process.cwd(), arg); let data; try { const buf = await fs.readFile(filePath); data = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); } catch (err) { console.error(err); process.exit(1); } const parser = createCMSMVFv3Parser().build().unwrap(); const mvfResult = await parser.decompress(data); if (mvfResult.isErr()) { console.error(mvfResult.error.message); process.exit(2); } const verifyResult = await parser.verify(mvfResult.value); if (verifyResult.isErr()) { console.error(JSON.stringify(verifyResult.error, null, 2)); process.exit(3); } process.exit(0); ``` #### Integration tests when building an MVF in a Node application When building an MVFv3 in a Node application, the `@mappedin/mvf` package can be used to write unit and integration tests. In the CMS MVFv3 parser, most extensions are OPTIONAL, meaning that it is possible to start with tests that build a manifest, then add floors, then geometry, etc. ```ts import { createCMSMVFv3Parser } from '@mappedin/mvf/preset-cms'; const mvfParser = createCMSMVFv3Parser().build().unwrap(); describe('MVFv3 bundling', () => { it('should build a valid MVFv3', () => { const mvf = buildMVFv3(venueData); expect(mvfParser.parse(mvf).error?.First()).toBeUndefined(); expect(mvfParser.verify(mvf).error).toBeNull(); }); }); ``` Note: In this environment, the parser also provides a `compress` method that will take an MVFv3 JavaScript object in memory and return a Uint8Array of the zip file. ## Mandatory Extensions While an MVFv3 may have many extensions, only a subset are used for importing into the Mappedin CMS. These are the mandatory extensions. ### Core The Core extension is required and must be present. It contains the manifest, the floors, and the geometry for each floor. #### Manifest The manifest is a GeoJSON file that contains the metadata for the venue. It must be present and must be named `manifest.geojson`. It must contain a FeatureCollection with a single feature. Example: ```json // manifest.geojson { "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-114.0056954137981, 51.13358028945705] }, "properties": { // Required, but not currently validated by the import. "contents": [ { "type": "file", "name": "manifest.geojson" }, { "type": "file", "name": "floors.geojson" }, { "type": "folder", "name": "geometry", "children": [ { "type": "file", "name": "f_67a24d8a241d315f309f2436.geojson" } ] }, { "type": "file", "name": "nodes.json" } ], // Required "name": "Mappedin Demo - Airport", // Required by the spec, but not currently used by the import "time": "2025-09-05T23:45:17.446Z", // Required. Must always be `3.0.0` "version": "3.0.0", // Optional. Used if the venue is usually displayed such that North is not "up". // 90 degrees would mean East is "up". "naturalBearing": 0.1543915198848822, // Optional for single language venues, required for multi-language venues. "language": "en", // Optional if sycing only one venue, required if syncing multiple venues. // Provided my Mappedin. AKA the venue slug. "mapId": "mappedin-demo-airport", // Optional. The FloorId of the default floor to display when the venue is loaded. // For multi-building venues, this SHOULD be the outdoor floor. "defaultFloor": "f_67a24d8a241d315f309f2436", // Optional, will be ignored by the import and derived by the geometry "tzid": "America/Edmonton" } } ] } ``` #### Floors Floors are stored in the `floors.geojson` file. It must be present and must be named `floors.geojson`. It must contain a FeatureCollection with a single feature for each floor. The feature must have a `Polygon` or `MultiPolygon` geometry which describes the outline of the floor. This will be used to render the outline of the floor when viewed in stacked map mode. The most important property of the feature is the `id` property, which must be a string beginning with `f_` and be unique within the MVF. MOST other extensions will reference a floor by its ID in some way, often in a file named after the floor ID (see Geometry section below for an example). Example: ```json // floors.geojson { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { // Required. Must be a string beginning with `f_` and be unique within the MVF. // Many other extensions will reference this ID, often in a file named by the ID. "id": "f_67a24d8a241d315f309f2436", // Required. This is an ordinal value with 0 being the ground floor. // In a single-building MVF, elevation must be unique within the MVF. // In a multi-building MVF that implements the floorStack extension, // elevation must be unique within the floorStack instead. "elevation": 0, // Optional. See the section on Details for more information. // A name, short name, and externalId will be automatically generated if not provided. // For shortName, consider what the button on the elevator would say. "details": { "externalId": "MAP-RIZD7TRZ", "name": "First Floor", "shortName": "F1" }, // Optional. If a floor has another name it can be put here. // This name is typically shown near, but with less emphasis than, the actual name. "subtitle": "Arrivals", }, "geometry": { "type": "MultiPolygon", "coordinates": [ // Snipped for brevity, must be valid WGS84 GeoJSON coordinates ] } }, ] } ``` #### Geometry Geometry are the fundamental building blocks of the MVF. They are stored in the `geometry` folder. Each file must be named after the floor ID it is associated with, and must be a GeoJSON file. It must contain a FeatureCollection with every feature of the floor. This includes any walls, polygons for rooms, points that locations or elevators represent, etc. These objects will be heavily referenced by other extensions. Example: ```json // geometry/f_67a24d8a241d315f309f2436.geojson { "features": [ { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [-114.00868338785048, 51.13111184629685], [-114.00869812368491, 51.13108800537968], [-114.00873495449434, 51.13109592381163], [-114.008720270649, 51.13111972993556], [-114.00868338785048, 51.13111184629685] ] ] }, "properties": { "id": "g_67a24e03241d315f309f37a6", // Typically, externalId is the only property provided, if any. // This is often the space identifier in a customer's location management system. "details": { "externalId": "ROOM-123" } } } ] } ``` ### Floor Stacks In a multi-building MVF, the `Floor Stacks` extension is required. In a single building MVF, it is optional, unless the Facade extension is also present, or if there are outdoor features like parking lots, when it is then required. It groups floors together into buildings, and provides basic metadata about those buildings. It must be present and must be named `floor-stacks.json`. It must contain an array of objects with a `id` property and a `floors` property. The `id` property must be a string beginning with `fs_` and be unique within the MVF. The `floors` property must be an array of floor IDs. A floor can only be in one floor stack, and the elevation of the floors inside a stack must be unique. They do not need to be contiguous. Also see the Outdoors extension below. Example: ```json // floor-stacks.json [ { // Required, must be a string beginning with `fs_` and be unique within the MVF. "id": "fs_67a24d8a241d315f309f2431", // Optional, a standard details object. "details": { "externalId": "MG-N6HS75U6", "name": "Domestic Parking", "shortName": "P1" }, // Required, an array of floor IDs belonging to this stack. "floors": [ "f_67a24d8a241d315f309f2437", "f_67a24d8a241d315f309f2438", ], // Optional, the floor ID of the default floor to display when the stack is loaded. "defaultFloor": "f_67a24d8a241d315f309f2437" }, ] ``` In a single building MVF with no Outdoors extension, a Floor Stack will be created automatically if not supplied. ### Outdoors In a multi-building MVF, or a single building MVF with the Facade extension,the `Outdoors` extension is required. It must be present and must be named `outdoors.json`, which contains a single object with a `floors` array containing the floor IDs of the outdoor floors. It describes the floors that represent the outdoor area of a venue. It can have multiple levels, such as when Arrivals and Departures are accessible from "outside" but are stacked on top of each other. When implementing the Facade extension, the geometry that describes the outer shell (or facade) of the building should be included on an outdoor floor. Other outdoor features, such as parking lots or outdoor amenities should be included on an outdoor floor rather than outside on the first floor. Example: ```json // outdoors.json { // Required, an array of floor IDs belonging to the outdoors. // Elevation must be unique within the outdoor floors "floors": [ "f_67a24d8a241d315f309f2436" ] } ``` ### CMS The `CMS` extension represents several sub extensions that contain data required by the Mappedin CMS. All CMS sub extensions will be present inside of the `cms` folder. The `layers` extension is required for geometry imports, but others are optional. #### Layers The `layers` extension is required for geometry imports. There must be a `cms/layers` folder with a `f_.json` file for each floor. Each file contains an object with a mapping of `geometryId` to `layer name`. The standard layer names are: - Floor - Walls - Non Public - Entrance - Connections - Parking Garage - Retails - Security Area - Zones - Check In Counters - Washrooms - Gates - Obstructions - Services - Inner Wall - Baggage Carousels A geometry of any type other than `Point` or `MultiPoint` MUST be mapped to a layer, or the import will fail. Example: ```json // cms/layers/f_67a24d8a241d315f309f2442.json { "g_67a24e03241d315f309f348d": "Zones", "g_67a24e03241d315f309f348e": "Connections", "g_67a24e03241d315f309f348f": "Connections", "g_67a24e03241d315f309f3490": "Connections", "g_67a24e03241d315f309f3491": "Connections", "g_67a24e03241d315f309f3492": "Connections", "g_67a24e03241d315f309f3493": "Connections", "g_67a24e03241d315f309f3494": "Connections", "g_67a24e03241d315f309f3495": "Connections", "g_67a24e03241d315f309f3496": "Connections", "g_67a24e03241d315f309f3497": "Connections", "g_67a24e03241d315f309f3498": "Non Public", "g_67a24e03241d315f309f3499": "Non Public", "g_67a24e03241d315f309f349a": "Floor", "g_67a24e03241d315f309f34e4": "Wall", "g_67a24e03241d315f309f34e5": "Wall", "g_67a24e03241d315f309f34e8": "Inner Wall", "g_67a24e03241d315f309f34e9": "Inner Wall", "g_67a24e03241d315f309f34ea": "Inner Wall", "g_67a24e03241d315f309f34ee": "Connections", "g_67a24e03241d315f309f34ef": "Connections", "g_67a24e03241d315f309f34f0": "Connections", "g_67a24e03241d315f309f34f1": "Connections", "g_67a24e03241d315f309f34f7": "Connections", "g_67a24e03241d315f309f34f8": "Connections", "g_67a24e03241d315f309f34f9": "Connections", "g_67a24e03241d315f309f3503": "Non Public", "g_67a24e03241d315f309f3504": "Non Public", "g_67a24e03241d315f309f3506": "Void", "g_67a24e03241d315f309f3507": "Non Public", "g_67a24e03241d315f309f3508": "Non Public", "g_67a24e03241d315f309f3509": "Floor", "g_67a24e03241d315f309f3729": "Retails" }, ``` ## Optional Extensions The following extensions are optional, but highly recommended if the data is available. More details on each extension can be found in the MVF v3 Specification page. - Connections - Defines elevators, stairs, and other connections between floors - Facade - Defines the outer shell of the building. Used heavily in multi-building mode in the Mappedin SDK - Locations - Defines POI data for the venue ### Facade The `facade` extension maps polygons on the outdoor map to the building (floor stack) they represent. This is used heavily in features such as Dynamic Focus to peel back the outer shell of the building to reveal the interior. A simple facade might just be a single polygon that represents all the geometry of the default floor of the building merged together, but more complex facades with multiple polygons can also be supported. The facade extension requires a `facade` folder, which contains `f_.json` files for each floor. Each file contains an array of objects with a `floorStackId` property that references the floor stack they represent, and a `geometryIds` property that is an array of geometry IDs that represent the facade on that floor. If there are multiple outdoor floors, there should be a facade file for each one. Example: ```json // facade/f_67a24d8a241d315f309f244c.json [ { "floorStackId": "fs_67a24d8a241d315f309f2435", "geometryIds": [ "g_67a24e03241d315f309f3768" ] } ] ``` Reminder: The **file name** is the floor the facade geometry is **on**, and the geometry Ids must be geometry on that floor. The **floorStackId** is the building the geometry **represents**, and will be other floors. ### Connections & Navigation Flags The `connections` extension defines elevators, stairs, and other connections between floors. For it to be used as part of a continuous import, it is essential that that a connection's entrances and exits map to the right geometries on the right floors. If Connections is provided, Navigation Flags is required (see below). Example: ```json // connections.json [ { // Required, must be a string beginning with `c_` and be unique within the MVF. "id": "c_67a24e01241d315f309f2fb1", // Required, must be one of "elevator", "stairs", "escalator", "door", "travelator", or "ramp". // See the Connections specification for more details. "type": "elevator", // Required, an array of Flagged Geometry Anchors (see the Navigation Flags extension). // These are the points a person can ENTER the connection. From there, they can leave at any exit. // These are objects with a geometryId, the floorId it is on, and a flags array. // For most use cases, the flags array will be [1] if the entrance is "accessible" for wheelchair // users, and [0] if it is not. "entrances": [ { "geometryId": "g_67a24e01241d315f309f2636", "floorId": "f_67a24d8a241d315f309f2437", "flags": [1] }, { "geometryId": "g_67a24e01241d315f309f265f", "floorId": "f_67a24d8a241d315f309f2438", "flags": [1] }, { "geometryId": "g_67a24e01241d315f309f26bf", "floorId": "f_67a24d8a241d315f309f2439", "flags": [1] }, { "geometryId": "g_67a24e01241d315f309f272e", "floorId": "f_67a24d8a241d315f309f243a", "flags": [1] }, ], // Required, an array of Flagged Geometry Anchors (see the Navigation Flags extension). // These are the points a person can EXIT the connection. They must enter at an entrance. These // do not need to be symmetrical, meaning you can have a stairwell that you can enter from the // second floor without being able to exit there. "exits": [ { "geometryId": "g_67a24e01241d315f309f2636", "floorId": "f_67a24d8a241d315f309f2437", "flags": [1] }, { "geometryId": "g_67a24e01241d315f309f265f", "floorId": "f_67a24d8a241d315f309f2438", "flags": [1] }, { "geometryId": "g_67a24e01241d315f309f26bf", "floorId": "f_67a24d8a241d315f309f2439", "flags": [1] }, { "geometryId": "g_67a24e01241d315f309f272e", "floorId": "f_67a24d8a241d315f309f243a", "flags": [1] } ], // Required, must be positive. The cost (in meters) to enter the connection. // i.e., the distance it's worth walking to avoid using this connection. Often very high for // elevators, and very low for escalators, to encourage use of higher throughput escalators. "entryCost": 5000, // Required, must be >= 1. The value to multiply the difference in elevation by to add to // the cost to use the connection. Often low for elevators, high for stairs, medium for escalators. // A person has to wait a bit for an elevator, but once they are on it's much faster to travel // through high numbers of floors compared to escalators, and stairs are even worse. "floorCostMultiplier": 1, // Optional, a standard details object. "details": { "name": "DPG-Elevator 1", "externalId": "CON-8E5939QH" }, } ] ``` If unsure about the cost values, use the values in the example MVF, or ask your Mappedin representative. NOTE: Connections can reference both Point and Polygon geometries. Polygons are preferred if available, since that renders better, but Points are supported. #### Navigation Flags For the purposes of geodata import, it is sufficient to provide this static file: ```json // navigationFlags.json { "accessible": { "index": 0, "bit": 0, "details": { "name": "Accessible", "description": "Accessible routes indicate that they can be used by people with mobility issues, in particular wheelchair users." } }, "outdoors": { "index": 0, "bit": 1, "details": { "name": "Outdoors", "description": "Outdoors routes indicate that can be exposed to the elements." } } } ``` For more information, see the Navigation Flags specification. ### Locations The `locations` extension defines POI data for the venue. It is how stores, restrooms, etc are defined. It also includes categories, which are used to group locations together. For customers that wish to do an one time geodata import, but want an on-going POI sync, providing an API similar to the one outlined in Common Data Objects is recommended. Providing an MVF will require a fully integral, up to date import MVF every time, which is equivalent to a full geodata sync. #### Locations.json The Locations aspect of the Locations extension comes in the form of a `locations.json` file, which contains an array of `Location` objects. Example: ```json // locations.json [ { // Required, must be a string beginning with `loc_` and be unique within the MVF. "id": "loc_67a24e09241d315f309f3869", // Required, a standard details object except for name, which is now also required "details": { "name": "Who's Who In The Zoo", "description": "Any child or “child at heart” will be delighted with the selection of stuffed animals, dolls, wind-ups and battery-operated gizmos filling the shelves of Who’s Who in the Zoo. Their boutique is designed specifically for flight lovers and offers a wide selection of airplane and spacecraft models.", "externalId": "405" }, // Required, but can be empty. An array of geometry anchors. Each geometry anchor must have a // `geometryId` property, which is the ID of the geometry on the floor, and a `floorId` property, // which is the ID of the floor. // // These are the physical places this location can be found. If a location is in a room, it should // be anchored to Polygon geometry. If it's more of a point in space, like an AED, it should be // anchored to a Point geometry. // // NOTE: A location can be attached to many geometries, on many floors, and many locations can be // attached to the same geometry. "geometryAnchors": [ { "geometryId": "g_67a24e03241d315f309f313e", "floorId": "f_67a24d8a241d315f309f243e" } ], // Required, but can be empty. An array of category IDs this location belongs to. // See location-categories.json below. "categories": [ "lcat_67a24e08241d315f309f3820", "lcat_67a24e08241d315f309f383d" ], // Required, but can be empty. An array of image objects. Each image object must have a `url` // property, which is the full URL of the image. Pictures are usually photographs of the // location. See Logo for the brand logo. "images": [{ "url": "https://cdn.mappedin.com/5a3827a74bd0ef04d9000000/0d0a9e3afb1f4e662387d3502e76c35cb7785c74.png" }], // Required, but can be empty. An array of objects with a `url` property, which is the full URL of the link, // and a `label` property describing the link. "links": [{ "url": "https://www.whoswhointhezoo.com/collection/offers/", "label": "Offers" }], // Required, but can be empty. An array of social media objects. Each social media object must have a `url` // property, which is the full URL of the social media profile, and `name` which is the name of the social // media platform. // For a geodata import, must be one of "facebook", "twitter", or "instagram". "social": [], // Required, but can be empty (which will be interpreted as using the venue's opening hours). // This is an array of objects meeting a more permissive OpeningHoursSpecification schema.org type. // See https://docs.mappedin.com/mvf/v3/latest/types/_mappedin_mvf.locations.OpeningHoursSpecification.html "openingHours": [ { "@type": "OpeningHoursSpecification", "opens": "07:30", "closes": "20:00", "dayOfWeek": [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] } ], // Optional. If present, must be an object with a `url` property, which is the full URL of the // website, and a `label` property, which should always be "Website" (other values will be ignored). "website": { "label": "Website", "url": "http://whoswhointhezoo.com/" }, // Optional. If present, must be a string representing the (public) phone number of the location "phone": "403-291-7037", // Optional, but highly recommended. If present, must be a string representing the logo image URL. // This is usually displayed on the card for the location in Mappedin Solutions, and should be // square if possible. "logo": "https://cdn.mappedin.com/5a3827a74bd0ef04d9000000/0d0a9e3afb1f4e662387d3502e76c35cb7785c74.png" // Optional. If an SDK customer needs to pass additional data to the SDK, this is the place // to do it. Talk to your Mappedin representative if you need to use this. "extra": { }, } ] ``` NOTE: For any image URLs, in a recurring sync, the image will ONLY be updated if the URL has changed. Meaning, if the URL is something like `https://example.com/store-123.png`, and that image is re-uploaded to the same URL, it will not be changed in the Mappedin system after a sync. #### Location Categories The Location Categories aspect of the Locations extension comes in the form of a `location-categories.json` file, which contains an array of `LocationCategory` objects. Example: ```json // location-categories.json [ { // Required, must be a string beginning with `lcat_` and be unique within the MVF. "id": "lcat_67a24e08241d315f309f3833", // Required, a standard details object except for name, which is now also required "details": { "name": "Travel Essentials", "externalId": "CAT-XZGGCK75" }, // Optional, but can be empty. A string representing the ID of the parent category. // Best practice is not to have deeply nested parent/child relationships. "parent": "lcat_67a24e08241d315f309f3820", // Optional. If an SDK customer needs to pass additional data to the SDK, this is the place // to do it. Talk to your Mappedin representative if you need to use this. "extra": { }, } ] ``` Here is the standard recommended location categories for Airport venues: ```json // location-categories.json [ { "id": "lcat_1", // Or Restrooms/Toilets as appropriate "details": { "name": "Washrooms" } }, { "id": "lcat_2", "details": { "name": "Checkin" } }, { "id": "lcat_3", "details": { "name": "Security" } }, { "id": "lcat_4", "details": { "name": "Gates" } }, { "id": "lcat_5", "details": { "name": "Food & Drinks" } }, { "id": "lcat_6", "details": { "name": "Shops" } }, { "id": "lcat_7", "details": { "name": "Services" } }, { "id": "lcat_8", "details": { "name": "Lounges" } }, { "id": "lcat_9", "details": { "name": "Baggage Claim" } }, { "id": "lcat_10", "details": { "name": "Parking" } }, { "id": "lcat_11", "details": { "name": "ATM/Exchange" } }, { "id": "lcat_12", // Shuttles, rideshare pickups, taxi stands, etc "details": { "name": "Transportation*" } }, { "id": "lcat_13", "details": { "name": "Connections" } }, { "id": "lcat_14", // A location for each terminal (if applicable), who's geometry points to the facade for that // terminal on the outdoor map. "details": { "name": "Terminals" } }, { "id": "lcat_15", "details": { "name": "Hotel" } }, { "id": "lcat_16", "details": { "name": "Art" } } ] ``` ### Getting Started # Getting Started The following guide shows how to setup map data so that it can be synced with with Mappedin CMS. ## Overview For customers with an existing central CMS, a data sync can be set up to treat that as the single source of truth. The Mappedin CMS will still be required to make any structural changes to the maps, using Mappedin's powerful Map Editor, and to manage location types not present in your system (typically: amenities). All other location, category, and event data can come from your CMS. To do that, your CMS needs to provide an API that meets our requirements. ## API To synchronize with your system, Mappedin requires access to a REST API returning JSON data. Mappedin will run the sync once a day typically. In the future, syncing a venue in response to a webhook may be available. ## Images Mappedin can sync Images from your system in a number of places (Store logos being the primary example). In order to use an image, the sync platform needs a programmatic way to know if the image has changed. Ideally this would be the name of the image (different name is interpreted as a different image), but we can also use, in order of preference: a last changed date property, a hash of the image stored in your API along side it, or an eTag on the image resource itself. Please ensure that images conform to the MIME type of `image` and it's subtypes `jpeg`, `png` or `svg+xml` For best results, please supply an SVG or 512px resolution PNG/JPGs, with a minimal amount of padding and no larger than 15MBs.