---
title: "How to generate a map with data in real time?"
canonical: "https://onesaitplatform.refined.site/space/DOCT/2220799650/How%20to%20generate%20a%20map%20with%20data%20in%20real%20time%3F"
format: markdown
---
> Macro (toc)

# Introduction

Although most maps represent static data, you may find yourself wanting to represent dynamically-changing data, such as real-time data with the variable location of one or more entities.

This tutorial will explain how to get and process the raw data with the location of the buses of the city of Málaga to introduce them in an ontology using **FlowEngine,** which keeps the data updated to their last position, then generate a **Dashboard** that represents said data using the **[Onesait Cesium API](http://onesaitplatform.atlassian.net)**, showing in a secondary panel the information with the number of buses represented at that time.

## <u><span style="color: #003366">Versions</span></u>

When doing this tutorial, the following software versions have been used:

- <u>**Platform**</u>: 1.4.3-commando
- <u>**CesiumJS**</u>: 1.62
- <u>**Onesait Cesium API**</u>: beta 1.12

# Data collection  


The first step is to obtain the raw data of Málaga buses. These are available in [Málaga city's Open Data Portal](https://datosabiertos.malaga.eu/), specifically in the [EMT Bus Locations resource in real time](https://datosabiertos.malaga.eu/dataset/ubicaciones-de-autobuses-emt-en-tiempo-real).

![image](media://001c626d-3ac6-4be1-9b67-497022bfac69)

Among the different available options, there is the GeoJSON format, which will be the one that will be used to collect the data, transform it and ingest it in the modified ontology of its organizational structure.

> ✅ ### Analyzing the GeoJSON
> ✅ 
> ✅ By accessing the [GeoJSON service URL](https://datosabiertos.malaga.eu/recursos/transporte/EMT/EMTlineasUbicaciones/lineasyubicaciones.geojson), you can analyze its contents. By refreshing every minute, you will appreciate the change of the data.

#   
Ontology generation

The next step will be to put all that raw information in an ontology, adding the format that fits us most and configuring it so that it is updated with the most recent data.

Firstly, generate an ontology to store the information. This can be done by accessing the **Development menu> My Ontologies:**

![image](media://8b9f866c-d4c7-48a7-8bd5-990263274cf8)

In the **Ontology Manager,** click on the **Create** button:

![image](media://65f638c5-40af-480a-b176-ad8196e43d2e)

In the **Ontology configurator,** enter the requested fields:

![image](media://bcedcdbe-657c-4e54-b769-bbf699a90749)

Next, at the bottom of the page, in **Ontology Templates**, select the template **General > EmptyBase** template. Then, two new properties will be created:

- '<u>prop</u>': object type, required and unencrypted.
- '<u>geometry</u>': geometry type, required and unencrypted.

![image](media://6b82f310-3f8b-4701-96b2-99fdf00993eb)

> ❌ ### Restricted Property Names
> ❌ 
> ❌ Actually, the 'prop' field refers to properties, so you would expect that field to have been called 'properties'. However, due to the processing that will be carried out later with Digital Flow, the use of said name prevents the correct functioning of the tool, so to avoid this problem, we will start with a different name, but one that evokes the same meaning.


Once generated, click on the **Update Schema** button, and then on the **Schema Generator and JSON Instances** tab:

![image](media://a36100d2-b70e-4d79-abde-98f9d34b7194)

You will be shown the ontology scheme that is being created. By setting the **display option** to "Text", the ontology scheme can be viewed and edited.

![image](media://fb0dbada-95e0-48f1-819d-254aae134b31)

This schema will be modified to enter the following fields in the 'prop' object:

- '<u>codLine</u>': as string type.
- '<u>codBus</u>': as string type.
- '<u>codWay</u>': as string type.
- '<u>codInitialStop</u>': as string type.


> ℹ️ ### Ontology schema
> ℹ️ 
> ℹ️ In case you need it, you can download the complete ontology scheme below: 
> ℹ️ 
> ℹ️ ##### **Ontology schema**
> ℹ️ 
> ℹ️ ```javascript
> ℹ️ {
> ℹ️     "$schema": "http://json-schema.org/draft-04/schema#",
> ℹ️     "title": "ont_malaga_bus_real_time",
> ℹ️     "type": "object",
> ℹ️     "required": [
> ℹ️         "ont_malaga_bus_real_time"
> ℹ️     ],
> ℹ️     "properties": {
> ℹ️         "ont_malaga_bus_real_time": {
> ℹ️             "type": "string",
> ℹ️             "$ref": "#/datos"
> ℹ️         }
> ℹ️     },
> ℹ️     "datos": {
> ℹ️         "description": "Info ont_malaga_bus_real_time",
> ℹ️         "type": "object",
> ℹ️         "required": [
> ℹ️             "prop",
> ℹ️             "geometry"
> ℹ️         ],
> ℹ️         "properties": {
> ℹ️             "prop": {
> ℹ️                 "type": "object",
> ℹ️                 "properties": {
> ℹ️                     "codLine": {
> ℹ️                         "type": "string"
> ℹ️                     },
> ℹ️                     "codBus": {
> ℹ️                         "type": "string"
> ℹ️                     },
> ℹ️                     "codWay": {
> ℹ️                         "type": "string"
> ℹ️                     },
> ℹ️                     "codInitialStop": {
> ℹ️                         "type": "string"
> ℹ️                     }
> ℹ️                 },
> ℹ️                 "required": [
> ℹ️                     "codLine",
> ℹ️                     "codBus",
> ℹ️                     "codWay",
> ℹ️                     "codInitialStop"
> ℹ️                 ]
> ℹ️             },
> ℹ️             "geometry": {
> ℹ️                 "type": "object",
> ℹ️                 "required": [
> ℹ️                     "coordinates",
> ℹ️                     "type"
> ℹ️                 ],
> ℹ️                 "properties": {
> ℹ️                     "coordinates": {
> ℹ️                         "type": "array",
> ℹ️                         "items": [
> ℹ️                             {
> ℹ️                                 "type": "number",
> ℹ️                                 "maximum": 180,
> ℹ️                                 "minimum": -180
> ℹ️                             },
> ℹ️                             {
> ℹ️                                 "type": "number",
> ℹ️                                 "maximum": 90,
> ℹ️                                 "minimum": -90
> ℹ️                             }
> ℹ️                         ],
> ℹ️                         "minItems": 2,
> ℹ️                         "maxItems": 2
> ℹ️                     },
> ℹ️                     "type": {
> ℹ️                         "type": "string",
> ℹ️                         "enum": [
> ℹ️                             "Point"
> ℹ️                         ]
> ℹ️                     }
> ℹ️                 },
> ℹ️                 "additionalProperties": false
> ℹ️             }
> ℹ️         }
> ℹ️     },
> ℹ️     "description": "Real time buses of Málaga",
> ℹ️     "additionalProperties": true
> ℹ️ }
> ℹ️ ```

  
If everything has been carried out correctly, generating an instance of the scheme should give something similar to:

![image](media://efef7457-1377-4506-b7e7-0b67d32b3d5e)

Once this is achieved, the ontology is ready to be filled.

# Data intake by the ontology using FlowEngine

Unlike other ontologies whose purpose is the spatial representation of their information, the newly created ontology will be characterized because:

1. It will have many data.
2. The data must be periodically updated.
3. The amount of data will not be constant; it will vary according to the moment.

This implies that you will not be able to enter such data manually through the ontology CRUD and, since they have to be updated from time to time, it is unfeasible to update the information manually, so it is necessary to **automate the process of intake and update** of the ontology.

Therefore, you will use the **Digital Flows** tool, accessed from the menu **Development> My Digital Flows.**

![image](media://56d57180-e5d1-433c-944a-d0a17b1983a8)

From the **FlowEngine Manager** window, click on the **Create** button.

![image](media://bb91f219-3b2d-4281-9be0-524aa2dfcee9)

On the screen that opens, define a **name** for the flows domain:

![image](media://2a553a91-daf3-47c7-9fdf-dbaad32241b5)

Once this is done, the **FlowEngine** is created. Then, start it by clicking on the "play" button.

![image](media://e4c0c048-8d23-447b-90b6-fb485318a279)

With this, the Flow is executed, showing on the screen the CPU usage it has, along with its memory consumption. To edit it, click on the "eye" icon.

![image](media://f03a76e0-eca3-4c1f-b7d4-6d25e7431496)

  


This launches the FlowEngine configurator, where the necessary logic for data ingestion by the ontology will be generated.

![image](media://52ce7955-db7a-4dc6-bafb-94491943836d)

> ℹ️ ### More about FlowEngine
> ℹ️ 
> ℹ️ So you don't know what is this FlowEngine thingy? Maybe you would like to go deeper into this? We recommend our workshops: **[Workshop Ingest Flow Engine, Visor, Dashboard, API](https://onesaitplatform.refined.site/space/DOCT/2221610169)**.


Said workflow must perform the following steps: take the data from the GeoJSON, process them so that they take the ontology structure, put them into said ontology by previously deleting the data there (to keep only the most recent data), then repeat the process every minute, which is the time it takes for the data to update. This, translated into the model, would look like:

![image](media://c881c2fe-80ef-46c7-8381-6add4322082a)


> ℹ️ ### Flow code
> ℹ️ 
> ℹ️ You can download the code used to generate the workflow from the following drop-down:
> ℹ️ 
> ℹ️ ##### **JSON**
> ℹ️ 
> ℹ️ ```javascript
> ℹ️ [
> ℹ️     {
> ℹ️         "id": "50a50e60.9d951",
> ℹ️         "type": "tab",
> ℹ️         "label": "MalagaBusRealTime",
> ℹ️         "disabled": false,
> ℹ️         "info": ""
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "f67ca3a5.60c4",
> ℹ️         "type": "inject",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "Start",
> ℹ️         "topic": "",
> ℹ️         "payload": "",
> ℹ️         "payloadType": "date",
> ℹ️         "repeat": "60",
> ℹ️         "crontab": "",
> ℹ️         "once": false,
> ℹ️         "onceDelay": 0.1,
> ℹ️         "x": 110,
> ℹ️         "y": 60,
> ℹ️         "wires": [
> ℹ️             [
> ℹ️                 "d7abf32e.a23e3"
> ℹ️             ]
> ℹ️         ]
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "fb4cde38.d919e",
> ℹ️         "type": "debug",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "Inserted",
> ℹ️         "active": true,
> ℹ️         "tosidebar": true,
> ℹ️         "console": false,
> ℹ️         "tostatus": false,
> ℹ️         "complete": "payload",
> ℹ️         "x": 740,
> ℹ️         "y": 240,
> ℹ️         "wires": []
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "217fe597.b167fa",
> ℹ️         "type": "http request",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "Get GeoJSON",
> ℹ️         "method": "GET",
> ℹ️         "ret": "txt",
> ℹ️         "url": "https://datosabiertos.malaga.eu/recursos/transporte/EMT/EMTlineasUbicaciones/lineasyubicaciones.geojson",
> ℹ️         "tls": "",
> ℹ️         "timeoutMillis": "4000",
> ℹ️         "x": 440,
> ℹ️         "y": 60,
> ℹ️         "wires": [
> ℹ️             [
> ℹ️                 "de01687b.aa9068"
> ℹ️             ]
> ℹ️         ]
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "7593e426.04ab5c",
> ℹ️         "type": "function",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "GeoJSON to JSON Feature Collection",
> ℹ️         "func": "/** Generate the Feature Collection template */\nlet jsonTemplate = {\n    \"type\": \"FeatureCollection\",\n\t\"name\": \"malaga_bus_real_time\",\n\t\"features\": []\n}\n\n\n/** Incoming data */\nmsg.payload = JSON.parse(msg.payload)\n\n/** Settle the Feature Collection with the data from the GeoJSON */\nmsg.payload.forEach(geoJsonFeature => {\n\n    let feature = {\n        type: \"Feature\",\n        properties: {},\n        geometry: {\n            type: \"Point\",\n            coordinates: {}\n        }\n    }\n    \n    /** Settle the input properties */\n    feature[\"properties\"][\"codLine\"] = geoJsonFeature.properties.codLinea\n    feature[\"properties\"][\"codBus\"] = geoJsonFeature.properties.codBus\n    feature[\"properties\"][\"codWay\"] = geoJsonFeature.properties.sentido\n    feature[\"properties\"][\"codInitialStop\"] = geoJsonFeature.properties.codParIni\n    \n    /** Settle the input geometry */\n    feature[\"geometry\"][\"coordinates\"][0] = geoJsonFeature.geometry.coordinates[0]\n    feature[\"geometry\"][\"coordinates\"][1] = geoJsonFeature.geometry.coordinates[1]\n\n    /** Push the feature input to the feature collection */\n    jsonTemplate.features.push(feature)\n\n})\n\nmsg.payload = jsonTemplate\n\nreturn msg;",
> ℹ️         "outputs": 1,
> ℹ️         "noerr": 0,
> ℹ️         "x": 390,
> ℹ️         "y": 460,
> ℹ️         "wires": [
> ℹ️             []
> ℹ️         ]
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "640a69e5.58b018",
> ℹ️         "type": "function",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "Format JSON",
> ℹ️         "func": "let geojson = msg.payload\nlet array = []\n\n/** Iterate over each feature of the GeoJON */\ngeojson.forEach(feature => {\n\t/** Set an object to store the JSON feature properties */\n\tlet object = {}\n\n\t/** Define it properties, and settle it */\n\tobject.prop = {}\n\tobject.prop.codLine = feature.properties.codLinea\n\tobject.prop.codBus = feature.properties.codBus\n\tobject.prop.codWay = feature.properties.sentido\n\tobject.prop.codInitialStop = feature.properties.codParIni\n\n\t/** Set the geometry of the feature */\n\tobject.geometry = feature.geometry\n\n\t/** Format the geometry fields as numbers */\n\tobject.geometry.coordinates = [\n\t\tparseFloat(object.geometry.coordinates[0]),\n\t\tparseFloat(object.geometry.coordinates[1])\n\t]\n\n  /** Add the feature to the ontology */\n\tarray.push({ ont_malaga_bus_real_time: object })\n})\n\nreturn { payload: array }",
> ℹ️         "outputs": 1,
> ℹ️         "noerr": 0,
> ℹ️         "x": 340,
> ℹ️         "y": 180,
> ℹ️         "wires": [
> ℹ️             [
> ℹ️                 "aede1a52.e35888",
> ℹ️                 "e1a47587.f86318"
> ℹ️             ]
> ℹ️         ]
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "e1a47587.f86318",
> ℹ️         "type": "onesaitplatform-insert",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "Insert to Ontology",
> ℹ️         "ontology": "ont_malaga_bus_real_time",
> ℹ️         "authentication": "ZmpsYS5kZXY6STdnQTJaZW5ITEQ1clF6N3VFbGNzRWhMTVFDdVhxV0FMQk15bjF0aWhsND0=",
> ℹ️         "x": 550,
> ℹ️         "y": 180,
> ℹ️         "wires": [
> ℹ️             [
> ℹ️                 "fb4cde38.d919e"
> ℹ️             ]
> ℹ️         ]
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "de01687b.aa9068",
> ℹ️         "type": "json",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "JSON Stringify",
> ℹ️         "property": "payload",
> ℹ️         "action": "",
> ℹ️         "pretty": false,
> ℹ️         "x": 160,
> ℹ️         "y": 180,
> ℹ️         "wires": [
> ℹ️             [
> ℹ️                 "640a69e5.58b018"
> ℹ️             ]
> ℹ️         ]
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "d7abf32e.a23e3",
> ℹ️         "type": "onesaitplatform-query-static",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "Clear Ontology",
> ℹ️         "ontology": "ont_malaga_bus_real_time",
> ℹ️         "targetDB": "",
> ℹ️         "queryType": "native",
> ℹ️         "query": "db.ont_malaga_bus_real_time.remove();",
> ℹ️         "authentication": "ZmpsYS5kZXY6STdnQTJaZW5ITEQ1clF6N3VFbGNzRWhMTVFDdVhxV0FMQk15bjF0aWhsND0=",
> ℹ️         "x": 260,
> ℹ️         "y": 60,
> ℹ️         "wires": [
> ℹ️             [
> ℹ️                 "217fe597.b167fa"
> ℹ️             ]
> ℹ️         ]
> ℹ️     },
> ℹ️     {
> ℹ️         "id": "aede1a52.e35888",
> ℹ️         "type": "debug",
> ℹ️         "z": "50a50e60.9d951",
> ℹ️         "name": "Objects to insert",
> ℹ️         "active": true,
> ℹ️         "tosidebar": true,
> ℹ️         "console": false,
> ℹ️         "tostatus": false,
> ℹ️         "complete": "payload",
> ℹ️         "x": 540,
> ℹ️         "y": 240,
> ℹ️         "wires": []
> ℹ️     }
> ℹ️ ]
> ℹ️ ```

When you import the code into the **FlowEngine,** if everything is working correctly on the debugger screen, then a message should appear with the list of objects to be entered in the ontology (175 objects in the example), and another message with the text string of those 175 objects that are ingested in the ontology.

![image](media://1853319c-3848-446e-b0cd-fada3366eb84)

Using the **Query Tool**, you can verify that the ontology has correctly ingested those 175 objects and that, after one minute, the ontology has a similar number of records (remember that buses may appear or disappear depending on their availability), but never much greater than it or that, as time goes by, the number of records does not stop ascending (in the following image, taken a couple of minutes later, the number of records has increased to 178).

![image](media://19c61d61-af8c-49e1-98e7-a52263e5304e)

  
Having made the relevant checks, the ontology is ripe and ready for consumption.

# Consumption of the ontology as an API Rest service

To represent the ontology in Cesium, you need to serve this ontology as an **[entity collection](http://wiki.geojson.org/GeoJSON_draft_version_6#FeatureCollection)** JSON. To do that, an API REST service will be generated that will format the output according to the needs.

Navigate to the menu **Development > My APIs.**

![image](media://e5b65a6d-f228-4965-a4b5-ed2bae8825d8)

In the main page of **My APIs,** click the **User Tokens** button first.

![image](media://c77fb700-0a09-4443-8cf9-91419bc2b92b)

In the resulting window, **copy and save the alphanumeric code** that appears, because it will be used later to access the REST API service.

![image](media://f6d0046c-32f6-48bc-8306-92178e10659c)

Go back to **My APIs** page, and this time, click the **Create** button.

![image](media://c77fb700-0a09-4443-8cf9-91419bc2b92b)

In the API creation form, fill in the requested fields, where:

- ** Identification:** which will be the API's name. The name of the example is "api_malaga_bus_real_time".
- ** Version:** API versioning. This value is not editable.
- ** Category:** API theme characterization. For this tutorial, the "Other" option has been chosen.
- ** API Type:** the type of the API to create. In this case, choose the "Ontology as REST API" option.
- ** Ontology:** ontology from which the service will read and return the data.
- ** Requests per Seconds (QPS):** the number of requests per second to make against the API. Leave it disabled.
- ** Public:** option to define that the API is intended to be public for everyone or private for the user exclusively.
- ** EndPoint Base:** URL that the Rest API service will have.
- ** EndPoint Swagger:** Swagger URL of the Rest API service.
- ** Description:** a small descriptive text of the purpose of the API. The example is: "Call for the ontology".
- ** Meta-inf:** characterization tags the API.
- ** Image:** Image file to characterize the API. By default, it is left without a selection.

![image](media://2ab568d4-fa04-4842-aa99-b1f804abc4f3)

  


At the bottom of the API creator, under **Operations,** select the latest option, **Query (Custom)**, where a custom call to the ontology will be defined.

![image](media://a940456e-38ce-412d-b5a2-24ed1d49fe93)

In the pop-up window that will appear, the following data will be entered in the fields:

- **Method**: GET
- **Name**: getBuses
- **Query**: SELECT * FROM ont_malaga_bus_real_time
- **Query Configuration**: SQL
- **Description**: Get buses
- **Enable processing**: check the box. The code window that will appear is where the formatting of the ontology content will be carried out, to a JSON entity collection. To do this, enter the following code:

**Post-processing code**

```javascript
/** Get the data parsed as JSON */
var dataArray = JSON.parse(data);

/** Set an empty array */
var features = [];

/** Iterate over the parsed data */
dataArray.forEach(function(feature) {                 
  var jsonFeature = {
    "type": "Feature",
    "properties":{
      "codLine": feature.ont_malaga_bus_real_time.prop.codLine,
      "codBus": feature.ont_malaga_bus_real_time.prop.codBus,
      "codWay": feature.ont_malaga_bus_real_time.prop.codWay,
      "codInitialStop": feature.ont_malaga_bus_real_time.prop.codInitialStop
    },
    geometry: feature.ont_malaga_bus_real_time.geometry
  }
  
  /** Add the feature to the features array */
  features.push(jsonFeature)
});

/** Set the structure of the GeoJSON */
var geoJsonResult = {
  "type": "FeatureCollection",
  "name": "realTimeBusData",
  "features": features
};
 
/** Return the generated structure */
return (JSON.stringify(geoJsonResult));
```

Once the code has been entered, save the changes by clicking on the **Save Changes** button, then click on the **New** button to generate the REST API service. Once this is done, you will automatically return to **My APIs** page. From there, search the newly created API, and access its **Swagger** by clicking on the corresponding button.

![image](media://2f7dbbb6-3b23-441d-a886-9f7afe94e7ea)

When you click on the button, the **Platform API Manager** window will open. In the schema selector, select the option **HTTPS** by clicking on the **GET** button of the previously generated API service.

![image](media://da096df9-1be2-4108-b9bc-463762c39f6d)

This GET button will display the API invocation configurator. By clicking on the **Try it out** button, another small drop-down will be displayed. There, you must enter the User **Token** you previously obtained and saved.

![image](media://d6197327-8f5f-4479-a338-2f7005063d93)

By clicking on the **Execute** bar, if the Token entered is correct and the API Rest service is well defined, you should be able to see the JSON of the ontology entity collection.

![image](media://d2f2a854-a705-4fd8-b079-92af32c7284e)

### Be careful with the URLs!

CloudLab environment URLs have changed from www.onesaitplatform.online to [lab.onesaitplatform.com](http://lab.onesaitplatform.com). Although the web address has been updated in the code, the old address still appears in the images, so keep an eye out.

# Preparation of a map on a dashboard  


Having loaded the information of the ontology, and prepared the API REST service, the last step will be to generate a **Dashboard** containing a Gadget with a map that represents the location of the buses in real time, and another Gadget with information of the number of buses in service at that time. The resulting dashboard will be this:

> Macro (iframe)


> ℹ️ **Full-screen dashboard**
> ℹ️ 
> ℹ️ You can access the Dashboard full screen from **[this link](https://lab.onesaitplatform.com/controlpanel/dashboards/view/90f4a3bf-6530-47ca-aa40-f644caf26349/)**.


To create the Dashboard, access the menu **Visualization > My Dashboards.**

![image](media://c5777443-6480-4f73-b157-133f1036db8a)

In the My Dashboards page, click the **Create** button.

![image](media://73011e41-b122-4018-adf0-5e3ec0b3fe27)

In the Dashboard creation form, enter the requested data:

- ** Identification:** this will be the Dashboard's name. The name of the example is "dsh_demo_bus_malaga".
- ** Description:** a small descriptive text of the Dashboard's purpose. The example is: "Demo dashboard with real time bus location".
- ** Public:** option to define that the Dashboard is intended to be public for everyone or private for the user exclusively.
- ** Initial Style:** to preload an initial style on the Dashboard. By default, it is left empty.
- ** Image:** image file to characterize the dashboard. By default, it is left with no selection.
- ** Category:** dashboard classification category. It can be left unselected.
- ** Subcategory:** dashboard classification subcategory. It can also be left unselected.
- ** Global Styles, Libraries and Scripts:** in this section, you will import the Cesium style sheet and library, the library with the **[Onesait Cesium API](https://onesaitplatform.atlassian.net/wiki/spaces/DOCT/pages/2220800417)**, and the Dashboard styles. The content to be added is this:

  
**Global Styles, Libraries and Scripts**

```xml
<link rel='stylesheet' type='text/css' href='https://cesiumjs.org/Cesium/Build/Cesium/Widgets/widgets.css'>
<script src='https://cesiumjs.org/Cesium/Build/Cesium/Cesium.js'></script>
<script src='https://development-smartcities.cwbyminsait.com/web/Onesait_Cesium_API/onesait_cesium_api.js'></script>


<style>
  livehtml {
  	height: 100% !important;
    width: 100% !important;
  }
  
  .cesium-viewer-bottom {
      display: none !important;
  }
</style>
```

![image](media://fdb6c827-027e-408d-b43e-b8fb93f80382)

Once you have entered all the required information, click on the **New** button to create the Dashboard. This will automatically open the Dashboard, which will be empty. Add two gadgets of type **Template** (LiveHTML): one for the map, and the other for the descriptive text.

## Map gadget

![image](media://f975170c-7162-4aae-a67a-42e464c27b13)

Start by configuring the Gadget corresponding to the map, which, if we follow the example model, will be placed to the left of the Dashboard. To edit it, click on the **three-dot icon,** then select the **Edit** option.

![image](media://00205546-f4ac-46d4-ae65-924aaef7fea8)

Two code windows that appear. In the one titled **HTML Compiled & Synchronized** (the one on the left), add the following code:

### HTML Compiled & Synchronized

```xml
<!-- DIV container for the map -->
<div id="cesiumContainer"></div>
```

> ℹ️ ### About 'cesiumContainer'
> ℹ️ 
> ℹ️ 'cesiumContainer' is the name of the DIV in which Cesium will dynamically create the map viewer. If this DIV is to be called differently, you will need to modify references to the name in the variable 'viewer' in the JavaScript part.


In the **JS Compiled & Synchronized** window, enter this other code:

##### **JS Compiled & Synchronized**

```javascript
vm.initLiveComponent = function(){

  viewer = new Cesium.Viewer('cesiumContainer', {
    /** Bottom left widget */
    animation: false,
    /** Home widget button */
    homeButton: false,
    /** Basemap picker widget */
    baseLayerPicker: false,
    /** Full screen widget button */
    fullscreenButton: false,
    /** Geocoger input widget */
    geocoder: false,
    /** Basemap: CartoDB */
    imageryProvider: new Cesium.UrlTemplateImageryProvider({
      url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
    }),
    /** infoBox window **/
    infoBox: false,
    /** Useless widget button */
    navigationHelpButton: false,
    /** Force 3D scene always */
    scene3DOnly: true,
    /** Timeline widget */
    timeline: false,
    /** Green selector */
    selectionIndicator: false
  })

  /** Remove the HDR effect after Cesium 1.52 */
  viewer.scene.highDynamicRange = false

  /** Change background color to WHITE */
  viewer.scene.globe.baseColor = Cesium.Color.WHITE
  
}


vm.drawLiveComponent = function(newData, oldData){

}


vm.resizeEvent = function(){

}


vm.destroyLiveComponent = function(){

}
```

Once this is done, click on the **compile & synchronize** button to visualize the changes made.

![image](media://a01317db-ffaf-441e-8bad-e26a1cd1561c)

If no error has occurred, a dark background with a grey earth globe should be seen in the Gadget on the left. This means that both the Cesium library and the Onesait Cesium API have loaded correctly. The developer console will display a message explaining that the API has loaded correctly, also specifying the version in the running.

![image](media://b42e0ed5-fa05-495e-8678-bfe3fe6e5a4a)

Next, you will customize the generated viewer. Firstly, the view must be focused on the city of Málaga, the place where the data to be displayed is located. For this, edit the Gadget again with the viewer, and in the **JS Compiled & Synchronized** window, add the coordinates and altitude to which the camera will be placed as variables will be added. These variables will be defined at the top of the code, like this:

- **let startLongitude** = -4.420000
- **let startLatitude** = 36.719631
- **let startHeight** = 7000.0

  
Next, call the method **cameraToPosition().** It will move the viewer's camera to the coordinates and altitude specified in its input parameters. Invoke the method <u>**within **</u>the function **vm.initLiveComponent()** after the line 'viewer.scene.globe.baseColor = Cesium.Color.WHITE', as shown next:

##### **Method to move the camara to a given location**

```javascript
  /** Change background color to WHITE */
  viewer.scene.globe.baseColor = Cesium.Color.WHITE  

  /** Zoom to the initial extent */
  cameraToPosition(
      'zoomTo',
      'degrees',
      [startLongitude,
      startLatitude],
      startHeight
  )
```

> ℹ️ ### More on the method cameraToPosition()
> ℹ️ 
> ℹ️ You can find more information about the **cameraToPosition()** method in the **[Onesait Cesium API](https://onesaitplatform.refined.site/space/DOCT/2220800278/API+Onesait+Cesium)**.

  
After compiling and synchronizing the changes made, the Gadget viewer **should focus on** the city of Málaga:

![image](media://632d4857-316a-412e-bde8-a2ac28ee0adc)

Next, two passive functions will be called: one to allow the selection of entities when you click on them, called featureSelection(), and another that activates pop-up windows to show the property information of the selected points, called popup(). These functions will be configured as shown next:

##### **Other interesting functions**

```javascript
   /** Enable feature selection */
  featureSelection('simple', '#55ffff', 0.6)
  
  /** Enable popups */
  popup('single', 'table', 'allowZoom')
```


> ℹ️ ### More on the method featureSelection() and on cesiumPopup()
> ℹ️ 
> ℹ️ You can find more information about the method cameraToPosition() and featureSelection() in **[Onesait Cesium API](https://onesaitplatform.refined.site/space/DOCT/2220800278/API+Onesait+Cesium)**.

The next step will be to upload the ontology data to the map. To do this, the **createLayer()** method will be used, which will automatically load the ontology data on the map according to the properties defined as input parameters. However, before using this method, you need to obtain the ontology data. Those is served by the previously created API REST service. Because of this, you will start by doing the API service call. This will be done after the call to the cameraToPosition() method previously defined in the **JS Compiled & Synchronized** window of the Map Gadget.

##### **Call the REST API**

```
  /** Call the API Rest to create the layer from the ontology */
  jQuery.ajax({
      url:
          'https://www.onesaitplatform.online/api-manager/server/api/v1/api_malaga_bus_real_time/getBuses',
      headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          'X-OP-APIKey': 'Enter your personal token here'
      },
      type: 'GET',
      contentType: 'application/json',
      success: featureCollection => {

      }
  })
```

  


> ❌ ### Personal token 
> ❌ 
> ❌ Remember to add your own **Token** to the API call header; more specifically on **line 64,** where it reads *'Enter your personal token here'.*


Once the service is called, the JSON of the entity collection will be defined in the object called **featureCollection** in the response. It will be used as the input parameter in the **createLayer()** method. This method will be invoked within the call as:

##### **Method to load a point layer in the map**

```javascript
contentType: 'application/json',
success: featureCollection => {
	createLayer(
    	featureCollection,
        'entity',
        'point',
        'billboard',
        [simbBus,'fromDefaultBillboard',0.7],
        'showLayer',
        'allowPicking',
        'enablePopup'
    )
}
```


> ✅ ### Symbology to be used
> ✅ 
> ✅ You can define the symbology in a variable in the code header, after the declaration of longitude, latitude and altitude. Thus, if you want to quickly change the symbology, you only have to modify this variable.
> ✅ 
> ✅ For this example, the following symbology variable has been defined:
> ✅ 
> ✅ - **let simbBus** = 'https://www.onesaitplatform.online/controlpanel/files/5cc7e3c6f2df81000b8f4948?disposition=1'


> ℹ️ ### More on the method createLayer()
> ℹ️ 
> ℹ️ You can consult more information about the **createLayer()** method in the **[Onesait Cesium API](https://onesaitplatform.refined.site/space/DOCT/2220800278/API+Onesait+Cesium)**.

Having done that, save and synchronize the changes you made. Now the map should show the buses in service, representing them with the selected icon:

![image](media://4117cf61-73ee-44b6-9a5c-e971fe8687ec)

However, these buses are static, because the **featureCollection** object takes the ontology data in the moment the API call is invoked. For the data to be updated over time, you need to make a recursive call to the API to obtain the updated data, and to update the existing bus layer information. The latter will be done using the **updateLayer()** method.

Thus, after the call to the API Rest, include a **setInterval()** configured to one minute (or 60,000 milliseconds, which is the time it takes the service of the GeoJSON of the City of Málaga to update). Within it, add another call to the API REST, similar to the previous one, but this time instead of using the createLayer() method, use **updateLayer():**

##### **Repeat call to the REST API every minute**

```javascript
  /** Set an interval to update the layer created calling again the API Rest service */ 
  setInterval(() => {
      jQuery.ajax({
          url:
              'https://www.onesaitplatform.online/api-manager/server/api/v1/api_malaga_bus_real_time/getBuses',
          headers: {
              'Content-Type': 'application/json',
              Accept: 'application/json',
              'X-OP-APIKey': 'Enter your personal token here'
          },
          type: 'GET',
          contentType: 'application/json',
          success: featureCollection => {
            
              /** Update the layer with the new data */
              updateLayer(
                  'realTimeBusData',
                  'position',
                  featureCollection,
                  'codBus',
                  'billboard',
                  true
              )    
          }
      })
  }, 60000)
```


> ❌ ### Personal token 
> ❌ 
> ❌ Again, remember to add your own **Token** to the API call header; in this case on **line 120.**


> ℹ️ ### More on the method updateLayer()
> ℹ️ 
> ℹ️ You can consult more information about the **updateLayer()** method in the **[Onesait Cesium API](https://onesaitplatform.refined.site/space/DOCT/2220800278/API+Onesait+Cesium)**.

Save and synchronize the changes made. Now you should see **how the information on the map is updated every minute:**

![image](media://0ac18890-94bf-4fd8-9da7-ed8b8c6891b9)

##   
Information gadget

Now that the map is working, the next step will be to edit the second Gadget in the Dashboard to show some basic information of the map, that will serve to apply the interconnection between the Map Gadget and the Text Gadget. Therefore, edit the Gadget destined for the information (In the example, it corresponds to the one on the right), and enter the following code in the **HTML Compiled & Synchronized** window:

##### **HTML Compiled & Synchronized**

```xml
<style>
h1 { 
  font-size: 1.4em;
  margin-left: 2%;
  font-weight: bold;
  color: #2e6c99;
}

p {
  font-size: 0.8em;
  margin-left: 3%;
  margin-right: 3%;
}
</style>

<h1>Málaga Buses</h1>
<p>Dashboard with the real-time position of the buses of the Municipal Transport Company of Málaga.</p>
<p>The data update is carried out <span style="font-weight: bold;">every minute</span> (last update at <span class="updateTime">waiting data</span>).</p>
<p>Number of buses currently in service: <span class="counter" style="font-weight:bold;">waiting data</span>.</p>
```


By applying and synchronizing the changes made, the Gadget should look like this:

![image](media://29dbb06b-5c61-46a8-bab6-2b1ac931a065)

Looking carefully, you can see in the text two '**waiting data**'; one at the end of the second paragraph, and one at the end of the third one. The idea is to replace this static text string with dynamic data that is updated over time; in the first case, specifying the time of the last update, and in the second case, with the value of the memory of the number of buses on the map.

## Interconnection between Gadgets

The first case is easy, because it is only taking the timestamp at which the Rest API call was made and injecting the value in the** <span>** tag in the text string. Generate a function that, when it is executed, obtains the time information, formats it according to your interest, and injects it where appropriate. The code of this function itself is not relevant, but nevertheless bear in mind that the function will be executed from the map gadget, not from the current gadget, so **it must be defined and called in a somewhat special way.**

The method to obtain the time has been defined in the header of the **JS Compiled & Synchronized** window with the name of **lastUpdateTime()** as follows:

**JS Compiled & Synchronized**

```javascript
$scope.$root.lastUpdateTime = () => {

  /** Get the time */
  let date = new Date()
  let hours   = date.getHours()
  let minutes = date.getMinutes()
  let seconds = date.getSeconds()
  
  /** Format the time information */
  let time = '' + ((hours > 12) ? hours - 12 : hours)
  time  += ((minutes < 10) ? ':0' : ":") + minutes
  time  += ((seconds < 10) ? ':0' : ":") + seconds
  time  += (hours >= 12) ? ' p.m.' : ' a.m.'
  
  /** Change the inner HTML content to the time string */
  document.getElementsByClassName("updateTime")[0].innerHTML = time
}
```


> ❌ ### Defining and calling the methods
> ❌ 
> ❌ When defining the method, you must do so using '$scope.$root.methodName'. When the method is invoked, you must do it in the same way, calling it as '$scope.$root.methodName'.

Now, for the method to be executed, you need to edit the map Gadget code again, and call the method from the **JS Compiled & Synchronized** window. Specifically, you must call it twice: firstly, when the bus layer loads for the first time, then every time the data is updated. Therefore, the call should be placed after calling **createLayer()** on the one hand, and after calling **updateLayer()** on the other.

  
**Call the method lastUpdateTime() after createLayer()**

```javascript
              simbBus,
              simbBusSelected,
              0.8
          )
        
          /** Get the update time */
          $scope.$root.lastUpdateTime()
      }
  })
```

##### **Call the method lastUpdateTime() after updateLayer()**

```javascript
                  'codBus',
                  'billboard',
                  true
              )
           
            /** Get the update time */
            $scope.$root.lastUpdateTime()
              
          }
      })
  }, 60000)
```

Compile and synchronize the changes of both Gadgets. If everything is well defined, **the time at which the map data was last updated** should appear.

  


![image](media://17505b15-b2ee-4c7a-a2fa-39c5b250613b)

For the second case, define a new method in the Information Gadget. The method will take the value of the number of buses in the map as input parameter, and replace said value with the text string **'waiting data'** in the third paragraph from the tag identifier **<span>.** Edit this Gadget again, and define the new method under the one previously created one, with the name of **countBuses():**

##### **JS Compiled & Synchronized**

```javascript
$scope.$root.countBuses = (countValue) => {
  /** Change the inner HTML content to the nuber of buses */
  document.getElementsByClassName("counter")[0].innerHTML = countValue
}
```

Next, return to the map gadget, where the method will be called again from two places: in the first place, after executing **createLayer()**, to indicate how many buses have been created, and secondly after **updateLayer(),** to check, with each data update, how many buses are on the map.

In this case, you have to pass an input parameter to the method, which will be the value to be represented in the other Gadget. This value can be extracted in different ways. For this example, it has been calculated from the number of entities that exist in the **featureCollection** object when used in both **createLayer()** and **updateLayer(),** since a bus point will be created for each entity, so the value is valid for what you are looking for (as long as the starting data is correct, as in the case of this example).

The function call would therefore be:

##### **Call the method countBuses() after createLayer()**

```javascript
              simbBus,
              simbBusSelected,
              0.8
          )
        
          /** Get the update time */
          $scope.$root.lastUpdateTime()

		  /** Send the bus counter to the info gadget*/
          $scope.$root.countBuses(featureCollection.features.length)
      }
  })
```

##### **Call to the method countBuses() after updateLayer()**

```javascript
                  'codBus',
                  'billboard',
                  true
              )
           
            /** Get the update time */
            $scope.$root.lastUpdateTime()

		    /** Send the bus counter to the info gadget*/
            $scope.$root.countBuses(featureCollection.features.length)
              
          }
      })
  }, 60000)
```


Save and synchronize all the changes made in both Gadgets. **The value of the number of active buses** on the map should appear in the Information Gadget.

![image](media://f5f7351e-a9af-4d28-9e94-b1d6737af92f)

Once this is done, you only have to give the Dashboard some format (header colors, icons, sizes, etc.) to have it ready. In the case of the example, the following style settings were used:

- **Title**: Real time buses
- **Header Height**: 60
- **Logo Height**: 48
- **Header Color**: hsl(205, 54%, 39%)
- **Title Color**: hsl(0, 0%, 100%)
- **Icon Color**: hsl(206, 54%, 5%)
- **Page Color**: hsl(205, 54%, 39%)
- **Logo**: [http://www.canalmalaga.es/wp-content/uploads/2018/03/xAyuntamiento_Malaga-300x129.png.pagespeed.ic.f7GEoLmd4c.png](http://www.canalmalaga.es/wp-content/uploads/2018/03/xAyuntamiento_Malaga-300x129.png.pagespeed.ic.f7GEoLmd4c.png)

![image](media://bbcee2e6-bda4-45f9-a1dd-567f939bb422)

The final result, therefore, looks like this:


> Macro (iframe)

  
The complete codes used for each Gadget are the following ones:

##### **Map gadget - HTML Compiled & Synchronized**

```javascript
<!-- DIV container for the map -->
<div id="cesiumContainer"></div>
```

##### **Map gadget - JS Compiled & Synchronized**

```javascript
/** Set the initial extent */
let startLongitude = -4.420000
let startLatitude = 36.719631
let startHeight = 7000.0

/** Set the map symbology */
let simbBus = 'https://www.onesaitplatform.online/controlpanel/files/5cc7e3c6f2df81000b8f4948?disposition=1'
let simbBusSelected = 'https://www.onesaitplatform.online/controlpanel/files/5cd022d3f2df81000b8f494c?disposition=1'


vm.initLiveComponent = function(){

  viewer = new Cesium.Viewer('cesiumContainer', {
    /** Bottom left widget */
    animation: false,
    /** Home widget button */
    homeButton: false,
    /** Basemap picker widget */
    baseLayerPicker: false,
    /** Full screen widget button */
    fullscreenButton: false,
    /** Geocoger input widget */
    geocoder: false,
    /** Basemap: CartoDB */
    imageryProvider: new Cesium.UrlTemplateImageryProvider({
      url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
    }),
    /** infoBox window **/
    infoBox: false,
    /** Useless widget button */
    navigationHelpButton: false,
    /** Force 3D scene always */
    scene3DOnly: true,
    /** Timeline widget */
    timeline: false,
    /** Green selector */
    selectionIndicator: false
  })

  /** Remove the HDR effect after Cesium 1.52 */
  viewer.scene.highDynamicRange = false

  /** Change background color to WHITE */
  viewer.scene.globe.baseColor = Cesium.Color.WHITE
  
  /** Zoom to the initial extent */
  cameraToPosition(
      'zoomTo',
      'degrees',
      [startLongitude,
      startLatitude],
      startHeight
  )

  /** Enable feature selection */
  featureSelection('simple', '#55ffff', 0.6)
  
  /** Enable popups */
  popup('single', 'table', 'allowZoom')
 
  /** Call the API Rest to create the layer from the ontology */
  jQuery.ajax({
      url:
          'https://www.onesaitplatform.online/api-manager/server/api/v1/api_malaga_bus_real_time/getBuses',
      headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          'X-OP-APIKey': 'Enter your personal token here'
      },
      type: 'GET',
      contentType: 'application/json',
      success: featureCollection => {
          createLayer(
              featureCollection,
              true,
              true,
              'point',
              'entity',
              'billboard',
              simbBus,
              simbBusSelected,
              0.8
          )
        
          /** Send the bus counter to the info gadget*/
          $scope.$root.countBuses(featureCollection.features.length)
        
          /** Get the update time */
          $scope.$root.lastUpdateTime()
      }
  })

        
  /** Get the update time */
  $scope.$root.lastUpdateTime
  
  /** Set an interval to update the layer created calling again the API Rest service */ 
  setInterval(() => {
      jQuery.ajax({
          url:
              'https://www.onesaitplatform.online/api-manager/server/api/v1/api_malaga_bus_real_time/getBuses',
          headers: {
              'Content-Type': 'application/json',
              Accept: 'application/json',
              'X-OP-APIKey': 'Enter your personal token here'
          },
          type: 'GET',
          contentType: 'application/json',
          success: featureCollection => {
            
              /** Update the layer with the new data */
              updateLayer(
                  'realTimeBusData',
                  'position',
                  featureCollection,
                  'codBus',
                  'billboard',
                  true
              )
        
            /** Get the update time */
            $scope.$root.lastUpdateTime()
            
            /** Send the bus counter to the info gadget*/
            $scope.$root.countBuses(featureCollection.features.length)
              
          }
      })
  }, 60000)
  
}


vm.drawLiveComponent = function(newData, oldData){

}


vm.resizeEvent = function(){

}


vm.destroyLiveComponent = function(){

}
```

##### **Information gadget - HTML Compiled & Synchronized**

```javascript
<style>
h1 { 
  font-size: 1.4em;
  margin-left: 2%;
  font-weight: bold;
  color: #2e6c99;
}

p {
  font-size: 0.8em;
  margin-left: 3%;
  margin-right: 3%;
}
</style>

<h1>Autobuses de Málaga</h1>
<p>Dashboard con la posición en tiempo real de los autobuses de la Empresa Municipal de Transporte de Málaga.</p>
<p>La actualización de los datos se lleva a cabo <span style="font-weight: bold;">cada minuto</span> (última actualización a las <span class="updateTime">waiting data</span>).</p>
<p>Número de autobuses en servicio actualmente: <span class="counter" style="font-weight: bold;">waiting data</span>.</p>
```

##### **Information gadget - JS Compiled & Synchronized**

```javascript
$scope.$root.lastUpdateTime = () => {

  /** Get the time */
  let date = new Date()
  let hours   = date.getHours()
  let minutes = date.getMinutes()
  let seconds = date.getSeconds()
  
  /** Format the time information */
  let time = '' + ((hours > 12) ? hours - 12 : hours)
  time  += ((minutes < 10) ? ':0' : ":") + minutes
  time  += ((seconds < 10) ? ':0' : ":") + seconds
  time  += (hours >= 12) ? ' p.m.' : ' a.m.'
  
  /** Change the inner HTML content to the time string */
  document.getElementsByClassName("updateTime")[0].innerHTML = time
}

$scope.$root.countBuses = (countValue) => {
  /** Change the inner HTML content to the nuber of buses */
  document.getElementsByClassName("counter")[0].innerHTML = countValue
}

//This function will be call once to init components
vm.initLiveComponent = function(){
	
};

//This function will be call when data change. On first execution oldData will be null
vm.drawLiveComponent = function(newData, oldData){

};

//This function will be call on element resize
vm.resizeEvent = function(){

}

//This function will be call when element is destroyed
vm.destroyLiveComponent = function(){

};
```