FloorMap SDK
Installation
NPM
You might need npm login
You will need to be logged in to install a dependency. Execute npm login
and use the username, password, and e-mail found in 1Password under npmjs (dev-team).
npm install @wework/floormap-sdk
yarn add @wework/floormap-sdk
And import the SDK with:
import * as FloorMap from '@wework/floormap-sdk'
UMD
By using the UMD format, You can access the SDK via FloorMap variable.
<script type="text/javascript" src="floormap-sdk.min.js"></script>
In JavaScript
const manager = new FloorMap.Manager({
})
const floorMap = manager.createFloorMap(target, {
})
Please see example/sample
for UMD usage
Getting started
Authentication
Before create and render a floor map, you need to authenticate with MilkyWay service by creating Manager
object and providing a credential.
To request for an appId/appSecret, kindly email tech-sg@wework.com with the subject Request for FloorMap SDK credentials and a brief explanation of its intended purpose.
const manager = new FloorMap.Manager({
appId: ,
appSecret: ,
baseUrl: 'https://occupancy-api.phoenix.dev.wwrk.co/v2',
})
Then, use .authenticate
function to start authenticating
manager
.authenticate()
.then(mwAccessToken => {
})
.catch(e => {
})
Spaceman JWT Token
Spaceman JWT Token is supported directly by the SDK. You can pass Spaceman JWT while constructing manager instance.
const manager = new FloorMap.Manager({
appId: ,
appSecret: ,
baseUrl: 'https://occupancy-api.phoenix.dev.wwrk.co/v2',
spacemanToken:
})
Store result mwAccessToken
for futher usage
authenticate
function resolves mwAccessToken
object. You can store the access token object for further use and provide the token object next time you're creating a manager.
manager.authenticate().then(mwAccessToken => {
localStorage.setItem('MW_TOKEN_STORAGE_KEY', JSON.stringify(mwAccessToken))
})
const mwAccessToken = JSON.parse(localStorage.getItem('MW_TOKEN_STORAGE_KEY'))
const manager = new FloorMap.Manager({
mwAccessToken: mwAccessToken,
})
After this point, the manager instance is ready to create and render the floormap.
Create a floor map
First, create an empty HTML element for the map to render itself.
<body>
<section id="container"></section>
</body>
Next, Use manager instance to create and render a floormap and provide target element.
const target = document.getElementById('container')
const floorMap = manager.createFloorMap(target, options)
FloorMap options
options.backgroundColor
- Background color of the mapoptions.deskLayout
- Show desk layoutoptions.deskInteractable
- Allow desk/chair to be interactable
After creating a floor map, Call render
function with buildingId
and floorId
to render a floor map into the screen (if floorId
is omitted, the lowest floor of the building will be rendered)
floorMap
.render({
buildingId: 'b308b94c-bca6-4318-b906-1c148f7ca183',
})
.then(result => {
console.log(result)
})
.catch(e => {
console.log(e.data)
console.log(e.options)
})
Final source code
<!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" />
<title>FloorMap</title>
<style>
body {
margin: 0;
padding: 0;
}
#main {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="../../dist/floormap-sdk.js"></script>
<script>
async function renderMap() {
const target = document.getElementById('container')
let currentId = null
const manager = new FloorMap.Manager({
appId: ,
appSecret: ,
baseUrl: 'https://occupancy-api.phoenix.dev.wwrk.co/v2',
})
await manager.authenticate()
const floorMap = manager.createFloorMap(target, {
backgroundColor: '#303030',
})
floorMap.render({
buildingId: 'b308b94c-bca6-4318-b906-1c148f7ca183',
})
}
renderMap()
</script>
</body>
</html>
Map Event
You can subscribe to user interaction and data event on the map by using addEventlistener
and removeEventListener
to remove a listener on the floor map instance.
floorMap.addEventListener('onmouseover', event => {})
floorMap.addEventListener('onmouseout', event => {})
floorMap.addEventListener('onclick', event => {})
floorMap.addEventListener('onmousemove', event => {})
floorMap.addEventListener('datachange', event => {})
floorMap.addEventListener('onerror', event => {})
floorMap.addEventListener('onrender', event => {})
An event object will contain:
type
- Event typepayload
- An informations of the interactiondata
- Space data, If an interaction is on space/object
Example payload
{
"type": "onclick",
"payload": {
"id": "cdc9c84e-d092-11e7-9d13-0642b0acf810",
"point": { "x": 14.237798863250575, "y": 74.47726859122804, "z": -3 },
"mousePos": { "x": 530, "y": 203 }
},
"data": {
}
}
Styling
You can manipulate the style of space, table, and chair by using functions on floorMap instance:
Apply style
Apply style to a given space uuid
applyStyle({ id: string, style: object, key: string }, completion?: function)
example
floorMap.applyStyle({ id: 'space-uuid-1', style: { color: 'red' }, key: 'highlight' })
floorMap.applyStyle([
{ id: 'space-uuid-1', style: { color: 'red' }, key: 'highlight' },
{ id: 'space-uuid-2', style: { color: 'red' }, key: 'highlight' },
])
Revert style
revertStyle({ id: string, key: string }, completion?: function)
Revert style with matched key to a given space uuid
example
floorMap.revertStyle({ id: 'space-uuid-1', key: 'highlight' })
floorMap.revertStyle([
{ id: 'space-uuid-1', key: 'highlight' },
{ id: 'space-uuid-2', key: 'highlight' },
])
Reset style
Reset all styles to a given space uuid
resetStyle({ id: string }, completion?: function)
example
flooMap.resetStyle({ id: 'space-uuid-1' })
flooMap.resetStyle([{ id: 'space-uuid-1' }, { id: 'space-uuid-2' }])
Style is Stack
Calling applyStyle
will push style object into the stack. If we call applyStyle
with a different key, the later style will be placed on top of the stack of overwrite property in previous items in the stack.
Also, we call applyStyle
with a key that already exists in the stack, that style with the same key will be replaced with new style instead of merging and stay in the current position in the stack instead of bumping to the top
For example:
floorMap.applyStyle({ id: spaceUUID, style: { color: 'red', opacity: 0.8 }, key: 'somekey' })
floorMap.applyStyle({ id: spaceUUID, style: { opacity: 0.5 }, key: 'otherkey' })
floorMap.applyStyle({ id: spaceUUID, style: { color: 'red', opacity: 1.0 }, key: 'somekey' })
floorMap.revertStyle({ id: spaceUUID, key: 'somekey' })
floorMap.resetStyle({ id: spaceUUID })
Combine event and style to create a user interaction feedback
floorMap.addEventListener('onmouseover', event => {
const { payload, data } = event
floorMap.applyStyle({ id: payload.id, style: { color: 'aqua' }, key: 'HOVER' })
})
floorMap.addEventListener('onmouseout', event => {
const { payload, data } = event
floorMap.applyStyle({ id: payload.id, style: {}, key: 'HOVER' })
})
Map lifecycle hooks
Map lifecycle gives an opportunity to you to start loading data along with when the map starts loading data, modify physical data or apply a style to an object before the map start to render an object into the screen.
onLoad -> didLoad -> onRender -> didRender
Lifecycle function will get called with the following parameters:
building
A building objectfloor
A floor objectoptions
Passing options from .render
and .load
errors
If any errors occurred
onLoad
onLoad hook allows you to prepare your data while map starts loading their data. You can make the map to be waiting for your data to be loaded before starting rendering into the screen by returning promise from the function.
Example
const unsubscribe = floorMap.onLoad(({ building, floor, spaces, objects, options, errors }) => {
})
If a promise gets returned from the function, the map will wait until the promise gets resolved before start rendering.
const unsubscribe = floorMap.onLoad(({ building, floor, spaces, objects, options, errors }) => {
return fetch()
})
didLoad
didLoad
will get called when the map finished loading the data (including returned promises from onLoad
function), and the Promise from onLoad(s) has been resolved. You can use this function to modify the physical data in the map.
Example
const unsubscribe = floorMap.didLoad(({ building, floor, spaces, objects, options, errors }) => {
floorMap.updateData(spaceeUUID, { roomType: 'Private Large Office' })
})
onRender
onRender
will get called during the map is preparing to render object for rendering into the screen, but not yet rendered into the screen. This function gives you an opportunity to apply a style to spaces.
Example
const unsubscribe = floorMap.onRender(({ building, floor, spaces, objects, options, errors }) => {
floorMap.applyStyle({
id: spaceUUID,
style: { color: 'aqua' },
key: 'occupancy-style',
})
})
didRender
didRender
will get called when the map finished rendering objects into the screen. You can add custom overlay into the map on this lifecycle (We will talk about map overlay in next section)
Example
const unsubscribe = floorMap.didRender(({ building, floor, spaces, objects, options, errors }) => {
const imageOverlay = new ImageOverlay(noteIcon, {
style: {
width: 3,
height: 3,
},
})
imageOverlay.spaceUUID = spaceUUID
floorMap.addObject(imageOverlay)
})
onError
Get a call when an error occurred during loading or rending
Example
const unsubscribe = floorMap.onError(({ options, errors }) => {
floorMap.updateData(spaceeUUID, { roomType: 'Private Large Office' })
})
Unsubscribe
Call returned function to remove a listener.
const unsubscribe = floorMap.onLoad(() => {})
unsubscribe()
Map Data
Preload Building / Floor Data
Pre-load and cache building/floor data in local. This function won't render floormap into the screen
floorMap.load(options: Object): Promise<Result>
Options:
buildingId
- Physical / System Building UUIDfloorId
- Physical / System Floor UUIDautoLoadFloor
- Auto load lowerest floor in building if floorId
is omittedskipCache
- Skip local building/floor cache in the SDK
Result:
building
- Building objectfloor
- Floor objectspaces
- Space objects inside floorobjects
- Objects (Chair, Table) inside floor
Render Floormap
Load and render floormap into the screen
floorMap.render(options: Object): Promise<Result>
Options:
buildingId
- Physical / System Building UUIDfloorId
- Physical / System Floor UUIDautoLoadFloor
- Auto load lowerest floor in building if floorId
is omittedskipCache
- Skip local building/floor cache in the SDK
Result:
building
- Building objectfloor
- Floor objectspaces
- Space objects inside floorobjects
- Objects (Chair, Table) inside floor
Reload Map
Reload and re-render current floor
floorMap.reload(options: Object): Promise
Options:
skipCache
: Skip local building/floor cache in the SDK
Get Spaces
Returns all spaces and objects in current floor (if floorId is omitted)
floorMap.getSpaces(floorId: string?): Space|SpaceObject[]
Update Data
Update Space/SpaceObject data by its uuid. This will trigger on('datachange')
event.
floorMap.updateData(uuid: string, data: object?)
Map State
Current Building
Returns current building
floorMap.getCurrentBuilding(): Building
Current Floor
Returns current floor
floorMap.getCurrentBuilding(): Floor
Current floor map state
Returns current status of the floor map
floorMap.getCurrentState(): string
State values:
IDLE
- Floor map is ready to renderLOADING
- Loading dataRENDERING
- Rendering mapRENDERED
- Floor map is displayed on the screenTERMINATED
- Floor map is terminated from teardown callERROR
- Have an error during loading/rendering
Camera control
Fit Content
Fit camera to the map content
floorMap.fitContent({ padding: number })
Example
floorMap.fitContent({ padding: 20 })
setZoomLevel
Set the zoom level
floorMap.setZoomLevel(zoomLevel: number)
getZoomLevel
Get current zoom level of the map.
floorMap.getZoomLevel(): number
setCenter
Set the camera rotation view angle
floorMap.setCenter({ x: number, y:number, z: number })
Example
floorMap.setCenter({ x: 2, y: 0, z: 5 })
setRotation
Rotate the map
floorMap.setRotation(veticalDegree: number, horozontalDegree: number)
rotationDegree
Vetical rotation degreepolarAngle
Horizontal rotation degree
Example
floorMap.setRotation(rotationDegree, polarAngle)
Object Overlay
ImageOverlay
const imageOverlay = new FloorMap.ImageOverlay(imageUrl, {
style: {
width: 3,
height: 3,
},
})
imageOverlay.spaceUUID = '[PHYSICAL_SPACE_UUUD]'
floorMap.addObject(imageOverlay)
TextOverlay
const textOverlay = new FloorMap.TextOverlay('FloorMapSDK!!')
textOverlay.position = { x: 0, y: 0, z: 0 }
textOverlay.scalar = 2
textOverlay.style = {
color: '#000000',
fontFamily: 'Arial, Helvetica, sans-serif',
textAlign: 'center',
fontWeight: 'normal',
fontStyle: 'normal',
}
floorMap.addObject(overlay)
LineOverlay
const lineOverlay = new FloorMap.LineOverlay()
lineOverlay.style = {
color: '#303030',
}
lineOverlay.addPoint({ x: 100, y: 100 })
lineOverlay.addPoint({ x: 100, y: 200 })
floorMap.addObject(lineOverlay)
lineOverlay.updatePoint(1, { x: 100, y: 300 })
lineOverlay.removePoint(1, { x: 100, y: 300 })
floorMap.updateObject(lineOverlay)
PolygonOverlay
const polygon = new FloorMap.PolygonOverlay(
[
{ x: x - 10, y: y - 10 },
{ x: x + 10, y: y - 10 },
{ x: x + 10, y: y + 10 },
{ x: x - 10, y: y + 10 },
],
{ style: { color: 'aqua', opacity: 0.6 } }
)
polygon.style = { color = '#000000', outline, opacity = 1 }
polygon.interactable = true
floorMap.addObject(polygon)
FloorMapGL Render Object
ImageOverlay
, TextOverlay
, LineOverlay
, and PloygonOverlay
are the abstractions of floormap.gl object. That means we can add floormap.gl's object using .addObject
function as well.
floorMap.addObject({
id: '001',
tags: ['level 3'],
type: 'MESH',
style: {
color: 'rgb(155, 255, 55)',
side: 'FRONT',
texture: {
img: '',
repeat: 0.5
}
outline: {
color: 'rgb(255, 0, 0)',
width: 0.2,
only: false
}
},
points: [
{ x: -1, y: -1 },
{ x: 1, y: -1 },
{ x: 1, y: 1 },
{ x: -1, y: 1 }
],
geometryTranslate: {x: -1, y: -1, z: 0},
interactable: true,
visible: true,
extrude: 2,
position: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1, z: 1 }
})
The documentation on floormap.gl render object can be found on Floormap.GL repository
Add Object
floorMap.addObject(overlay)
floorMap.addObject([overlay, overlay2])
Remove Object
FloorMap overlay/object can be removed by calling .removeObject
with object or id
floorMap.removeObject(overlay)
floorMap.removeObject(id)
floorMap.removeObject([overlay1, overlay2])
floorMap.removeObject([id1, id2])
Overlay Positioning
To position an overlay on the map, we can set position
property.
const imageOverlay = new ImageOverlay(imageUrl)
imageOverlay.position = { x: 100, y: 100 }
floorMap.addObject(imageOverlay)
In case you want to add an overlay into the specific space/room. Instead of manually calculating the position of a room, we can omit position value and assign spaceUUID
to an overlay. The floor map will calculate the center position of the room and assign it to the overlay.
const imageOverlay = new ImageOverlay(imageUrl)
imageOverlay.spaceUUID = 'SPACE_UUID'
floorMap.addObject(imageOverlay)
Session
getAccessToken
Returns an access token of the current session
Example
const mwAccessTokenObj = manager.session.getAccessToken()
setAccessToken
Set new access token to the current session
Example
manager.session.setAccessToken(mwAccessTokenObj)
setSpacemanToken
Set new spaceman JWT token and re-authenticate
Example
const mwAccessTokenObj = await manager.session.setSpacemanToken(jwt)
Separate of concern with extension
Create a extension
We can use lifecycle hooks like onLoad
pre-loading our data and apply a style with applyStyle
base on data we have. In some case, we want to separate to logic of data loading and styling from main application logic, make it reusable, plug-n-play, or even distribute our logic as a standalone package, and This is where the extension come to play.
To create an extension, create a class and extend the Extension
base class. Then override required functions.
getId() - required
- ID/Name of the extension, the name has to be unique across all extensiongetDescription()
- Description of the extensiononRegister()
- This function will get called when register the extension to a maponRemove()
- This function will get called when unregister the extension from a mapgetDataById(spaceUUID: string)
- Provide additional data for to space/object based on UUID - Deprecated Use getDataBySpace insteadgetDataBySpace(space: Space)
- Provide additional data for to space/object
Inside extension class, you can access the map that extension has been registered to via getMap()
. This allows you to listen and fetch database on the map lifecycle such as onLoad
and onRender
.
In the example, We're going to create CityWeatherExtension to fetch weather database on the current location of the building.
class CityWeatherExtension extends Extension {
constructor() {
super()
this.data = undefined
this.currentCity = ''
}
getId() {
return 'cityweather'
}
getDescription() {
return 'Awesome City Weather: Provide Weather information based on building location'
}
onRegister() {
this.unsubscribeOnLoad = this.getMap().onLoad(async ({ building }) => {
if (this.currentCity === building.city) {
return
}
this.currentCity = building.city
const res = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${this.currentCity}`)
const data = await res.json()
this.data = data
return
})
}
onRemove() {
this.data = undefined
this.unsubscribeOnLoad()
}
getDataBySpace(space) {
return `Room ${space.number}: ${this.getTemperature()}F`
}
getTemperature() {
return this.data.main.temp
}
}
Register extension
Register extension by calling registerExtension
on FloorMap instance
const weatherExtension = new CityWeatherExtension()
floorMap.registerExtension(weatherExtension)
Now when you switch a building, the extension will fetch the weather data for the user. Also when you click on any space, the event payload will include the data from the extension (which come from getDataById).
floorMap.addEventListener('onclick', event => {
if (event.data) {
console.log(event.data.cityweather)
}
})
Access extension instance through floormap
floorMap.extensions[extensionId]
floorMap.getExtension(extensionId)
floormap.extensions.cityweather
floormap.getExtension('cityweather')
Remove extension
Remove extension from floormap
floorMap.removeExtension(weatherExtension)
floorMap.removeExtension('cityweather')
Built-in extensions
Please see: https://github.com/WeConnect/floormap-extensions
API References
We provide API references documentation generated via jsdoc
# Clone
git clone git@github.com:WeConnect/floormap-sdk.git
cd floormap-sdk
yarn jsdoc
After that, the API doc will be in the docs
folder.
Demo application
React application
git clone git@github.com:WeConnect/floormap-sdk.git
cd floormap-sdk
yarn install
Open example/occupancy/index.js
, then edit appId
and appSecret
To request for an appId/appSecret, kindly email tech-sg@wework.com with the subject Request for FloorMap SDK credentials and a brief explanation of its intended purpose.
Then start the demo application
yarn start:demo
Open http//localhost:3000/
Plain Javascript application
git clone git@github.com:WeConnect/floormap-sdk.git
cd floormap-sdk
yarn install
The plain demo version can be found in example/sample
. Open index.html
file.