---
title: "¿Cómo generar un mapa con datos en tiempo real?"
canonical: "https://onesaitplatform-es.refined.site/space/DOC/2216691763/%C2%BFC%C3%B3mo%20generar%20un%20mapa%20con%20datos%20en%20tiempo%20real%3F"
format: markdown
---
> Macro (toc)

# Introducción

Aunque la mayoría de los mapas representan datos estáticos, se puede dar el caso de querer representar datos que cambien dinámicamente, como pueden ser datos en tiempo real con la localización variable de una o más entidades.

En este tutorial se explicará cómo obtener y tratar los datos en bruto con la localización de los autobuses de la ciudad de Málaga para introducirlos en una ontología mediante **FlowEngine**, que mantenga los datos actualizados a su última posición, y generar un **Dashboard** que represente dichos datos haciendo uso del **[API de Cesium de Onesait](https://onesaitplatform.atlassian.net/wiki/spaces/DOC/pages/2216692252)**, indicado en un panel secundario la información con el número de autobuses representados en dicho momento.

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

A la hora de realizar este tutorial, se han usado las siguientes versiones de software:

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

# Obtención de datos

El primer paso consiste en obtener los datos en bruto de los autobuses de Málaga. Estos se encuentran disponibles en el [Portal de Datos Abiertos de la ciudad de Málaga](https://datosabiertos.malaga.eu/), concretamente en el recurso de [Ubicaciones de autobuses EMT en tiempo real](https://datosabiertos.malaga.eu/dataset/ubicaciones-de-autobuses-emt-en-tiempo-real).

![image](media://0c4706f4-e7a2-4b22-9fdd-42e0a590a3b2)

De entre las distintas opciones disponibles, se encuentra el formato GeoJSON, que será el que se utilizará para recoger los datos, transformarlos e ingestarlos en la ontología modificado su estructura organizativa.

> ✅ ### Analizando el GeoJSON
> ✅ 
> ✅ Accediendo a la [URL del servicio del GeoJSON](https://datosabiertos.malaga.eu/recursos/transporte/EMT/EMTlineasUbicaciones/lineasyubicaciones.geojson), se puede analizar el contenido del mismo. Refrescando cada minuto se podrá apreciar el cambio de los datos.

#   
Generación de la ontología

El siguiente paso consistirá en meter toda esa información en bruto en una ontología, añadiendo el formato que más te interese y configurando la misma para que se actualice con los datos más recientes.

En primer lugar, genera una ontología para que almacene la información. Esto se hará accediendo al menú de **Desarrollo > Mis Ontologías**:

![image](media://fd4c2ac7-6316-46cf-bf2c-07794030e7d2)

En el **Gestor de Ontologías**, pulsa en el botón de **Crear**:

![image](media://59f00c74-2cdf-4b04-a987-88494c83b286)

En el **configurador de la Ontología**, introduce los campos solicitados:

![image](media://f9f84fc2-30a5-4493-8316-168dc7e27d4e)

Seguidamente, en la parte inferior de la página, en **Plantillas de Ontología**, se seleccionará la plantilla **General > EmptyBase**. A continuación se creará dos nuevas propiedades:

- '<u>prop</u>': de tipo objeto, requerido y sin encriptar.
- '<u>geometry</u>': de tipo geometry, requerido y sin encriptar.

![image](media://b64300e2-b6f7-400d-aa8c-61686aa19e9b)

> ❌ ### Nombres de propiedades restringidos
> ❌ 
> ❌ En realidad, el campo 'prop' hace referencia a propiedades, por lo que cabría esperar que dicho campo se hubiese llamado 'properties'. Sin embargo, debido al procesado que se llevará posteriormente con Digital Flow, el uso de dicho nombre impide el correcto funcionamiento de la herramienta, por lo que para evitar dicho problema se parte de un nombre diferente, pero que evoque el mismo significado.


Una vez generados, pulsa en el botón de **Actualizar Esquema**, y seguidamente en la pestaña de **Generador de Esquema e Instancias JSON**:

![image](media://fc1170a7-019c-454e-9e2a-0da41e0b7c28)

Se te mostrará el esquema de la ontología que se está creando. Activando la **opción de visualización** a "Texto", podrás ver y editar el esquema de la ontología.

![image](media://d61bec79-1550-4c26-8bac-854835c4a508)

Este esquema se modificará para introducir los siguientes campos en el objeto de 'prop':

- '<u>codLine</u>': como tipo string.
- '<u>codBus</u>': como tipo string.
- '<u>codWay</u>': como tipo string.
- '<u>codInitialStop</u>': como tipo string.


> ℹ️ ### Esquema de la ontología
> ℹ️ 
> ℹ️ Por si lo necesitas, a continuación puedes descargar el esquema de la ontología al completo: 
> ℹ️ 
> ℹ️ ##### **Esquema de ontología**
> ℹ️ 
> ℹ️ ```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
> ℹ️ }
> ℹ️ ```


Si todo se ha llevado a cabo correctamente, generando una instancia del esquema debería obtenerse algo similar a:

![image](media://a1df5541-7d63-4416-9434-f9a190f37dce)

Conseguido esto, la ontología está lista para ser rellenada.

# Ingesta de datos por parte de la ontología mediante FlowEngine

A diferencia de otras ontologías cuya finalidad sea la representación espacial de su información, la ontología recién creada se va a caracterizar porque:

1. Va a contar con muchos datos.
2. Los datos se tienen que actualizar periódicamente.
3. La cantidad de datos no va a ser constante; variará según el momento.

Esto implica va a implicar que no va a ser posible introducir dichos datos manualmente mediante el CRUD de ontología, y puesto que se tienen que actualizar cada cierto tiempo, es inviable el ir actualizando la información a mano, por lo que es necesario **automatizar el proceso de ingesta y actualización** de la ontología.

Por ello, se va a hacer uso de la herramienta de **Digital Flows**, a la cual se accederá desde el menú **Desarrollo > Mis Digital Flows**.

![image](media://625f7357-f112-4087-82ea-bd3ac1dc53b8)

Desde la ventana del **Gestor de FlowEngine**, se pulsará en el botón de **Crear**.

![image](media://91bd1822-7e75-4ed9-bf4e-febc1b092cdf)

En la pantalla que se abre, define un **nombre** para el dominio del flujo:

![image](media://1ade048b-0642-41bc-9abb-c2190ec1839c)

Hecho esto, queda creado el **FlowEngine**. A continuación, hay que arrancarlo pulsando sobre el botón de "play".

![image](media://8c10ef54-3a9a-4329-8d9d-87430a8a3278)

Con ello se ejecuta el Flujo, mostrando en pantalla el uso de CPU que tiene así como su consumo de memoria. Para poder editarlo, pulsa sobre el icono del "ojo".

![image](media://75c5df2d-2335-4b76-8f78-a269d2e834ba)

  


Con ello se lanza el configurador del FlowEngine, en donde se generará la lógica necesaria para la ingesta de datos por parte de la ontología.

![image](media://462a1278-168d-452a-a2b2-2bcd4608e29a)

> ℹ️ ### Más sobre FlowEngine
> ℹ️ 
> ℹ️ ¿No sabes qué es esto del FlowEngine? ¿O te gustaría profundizar algo más en esto? Te recomendamos nuestro taller de **[Workshop Ingesta Flow Engine, Visor, Dashboard, API](https://onesaitplatform-es.refined.site/space/DOC/2216105742/Workshops+y+Tutoriales)**.


Dicho flujo de trabajo deberá realizar los siguientes pasos: coger los datos del GeoJSON, procesarlos para que tomen la estructura de la ontología, meterlos en dicha ontología eliminando previamente los datos que hayan (para mantener únicamente los datos más recientes), y repetir el proceso cada minuto, que es el tiempo que tardan los datos en actualizarse. Esto traducido al modelo quedaría tal como:

![image](media://fd757dae-7377-4cda-8bfa-b6dd96f3b6a8)


> ℹ️ ### Código del flujo
> ℹ️ 
> ℹ️ El código utilizado para generar el flujo de trabajo lo puedes descargar desde el siguiente desplegable:
> ℹ️ 
> ℹ️ ##### **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": []
> ℹ️     }
> ℹ️ ]
> ℹ️ ```


Importando el código al **FlowEngine**, si todo funciona correctamente en la pantalla del *debugger,* debería aparecer un mensaje con el listado de objetos que se van a introducir en la ontología (175 objetos en el ejemplo), y otro mensaje con la cadena de texto de esos 175 objetos que se ingestan en la ontología.

![image](media://f89c3f59-b324-46a3-bc13-93fc6b8d9ca9)

Mediante la herramienta de **Query Tool**, puedes comprobar que la ontología ha ingestado correctamente esos 175 objetos, y que, pasado un minuto, la ontología tiene una cantidad de registros similar (hay que recordar que pueden aparecer o desaparecer autobuses según su disponibilidad), pero nunca mucho mayor ni que conforme pase el tiempo el número de registros no deje de ascender (en la siguiente imagen, tomada un par de minutos después, el número de registros ha aumentado a 178).

![image](media://46a38fe6-20e7-4c57-a997-e53a5781cf34)

  


Hechas las comprobaciones pertinentes, la ontología queda preparada y lista para su consumo.

# Consumo de ontología como un servicio API Rest

Para poder representar la ontología en Cesium, es necesario servir dicha ontología como un JSON de **[colección de entidades](http://wiki.geojson.org/GeoJSON_draft_version_6#FeatureCollection)**. Para ello, genera un servicio API Rest que formatee la salida según las necesidades.

Nevaga hasta el menú de **Desarrollo > Mis APIs**.

![image](media://dc832070-ac50-4ab1-a687-f0ea4f139406)

En la página principal de** Mis APIs**, pulsa en un primer momento en el botón de **Tokens de usuario**.

![image](media://0b9f955e-251a-4bd0-a88e-0a92c2e55fab)

En la ventana resultante, **copia y guarda el código alfanumérico** que aparezca, pues se utilizará más adelante para poder acceder al servicio del API Rest.

![image](media://3e0cfc2c-b4bc-43c1-a2a4-18794366f5a7)

Regresa a la página de **Mis APIs**, y pulsa esta vez en el botón de **Crear**.

![image](media://0b9f955e-251a-4bd0-a88e-0a92c2e55fab)

En el formulario de creación del API, rellena los campos solicitados, donde:

- **Identification**: que será el nombre del API. El nombre del ejemplo es "api_malaga_bus_real_time".
- **Versión**: el versionado del API. Este valor no es editable.
- **Category**: caracterización temática del API. Para este tutorial se ha escogido la opción de "Other".
- **API Type**: el tipo de API a crear. En este caso, se escogerá la opción de "Ontology as REST API".
- **Ontology**: ontología a partir de la cual el servicio leerá y devolverá los datos.
- **Requests per Seconds (QPS)**: el número de peticiones por segundo a realizar contra la API. Se deja desactivado.
- **Public**: opción para definir que se quiere que el API sea público para todo el mundo o privado para el usuario en exclusiva.
- **EndPoint Base**: URL que tendrá el servicio API Rest.
- **EndPoint Swagger**: URL del Swagger del servicio API Rest.
- **Description**: un pequeño texto descriptivo de la finalidad del API. El del ejemplo es: "Call for the ontology".
- **Meta-inf**: etiquetas de caracterización el API.
- **Image**: archivo de imagen para caracterizar el API. Por defecto se deja sin seleccionar ninguna.

En la parte inferior del creador del API, en **Operaciones**, selecciona la última opción de todas, **Query (Custom)**, en donde se definirá una llamada personalizada a la ontología.

![image](media://21ee8408-cb24-4e40-a707-8c5c12d95708)

En la ventana emergente que aparecerá, introduce en los campos los siguientes datos:

- **Method**: GET
- **Name**: getBuses
- **Query**: SELECT * FROM ont_malaga_bus_real_time
- **Query Configuration**: SQL
- **Description**: Get buses
- **Enable processing**: activa la casilla. La ventana de código que aparecerá es donde se llevará a cabo el formateo del contenido de la ontología a un JSON de colección de entidades. Para ello, introduce el siguiente código:

**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));
```

Una vez introducido el código, salva los cambios pulsando en el botón **Salvar Cambios**, y seguidamente pulsa en el botón de **Nuevo**, para generar el servicio API Rest. Hecho esto, volverás automáticamente a la página de **Mis APIs**. Desde ella, busca el API recién creado, y accede a su **Swagger** pulsando en el correspondiente botón.

![image](media://a3a967aa-25d9-4421-bb01-9188d7ce6a7c)

Al pulsar en el botón, se abrirá la ventana del **Gestor de APIs de la Plataforma**. En el selector de esquemas, elige la opción de **HTTPS**, pulsando en el botón de **GET** del servicio API generado previamente.

![image](media://8925c4a5-bf69-4c84-abb5-96043a802f6f)

Este botón de GET desplegará el configurador de invocación del API. Pulsando en el botón de **Try it out**, se mostrará otro pequeño desplegable en donde deberás introducir el **Token** de usuario antes obtenido y guardado.

![image](media://551524c6-a500-4cee-824b-fbaeed56fc7c)

Pulsando sobre la barra de **Ejecutar**, si el Token introducido es correcto y el servicio API Rest está bien definido, deberías poder ver el JSON de la colección de entidades de la ontología.

![image](media://b7e68f6f-6c55-4fe5-93e6-74b2daf670e4)

### ¡Ojo con las URLs!

Las URLs del entorno de CloudLab han cambiado de www.onesaitplatform.online a [lab.onesaitplatform.com](http://lab.onesaitplatform.com). Aunque en el código se ha actualizado la dirección web, en las imágenes aun aparece la dirección antigua, por lo que estate atento.

# Preparación de un mapa en un dashboard

Cargada la información de la ontología, y preparado el servicio API Rest, el último paso consistirá en generar un **Dashboard** que contenga un Gadget con un mapa que represente la localización de los autobuses en tiempo real, y otro Gadget con información del número de autobuses en servicio en dicho momento. El Dashboard resultante será el siguiente:

> Macro (iframe)

  


> ℹ️ **Dashboard a pantalla completa**
> ℹ️ 
> ℹ️ Puedes acceder al Dashboard a pantalla completa desde **[este enlace](https://lab.onesaitplatform.com/controlpanel/dashboards/view/90f4a3bf-6530-47ca-aa40-f644caf26349/)**.


Para crear el Dashboard, accede al menú **Visualizaciones > Mis Dashboards**.

![image](media://6c2eab54-fa3c-4d06-b7a9-d7a442f45958)

En la página de Mis Dashboards, pulsa en el botón de **Crear**.

![image](media://8aa0e085-c5e6-4218-983e-ccc13e10dfba)

En el formulario de creación del Dashboard, introduce los datos solicitados:

- **Identification**: que será el nombre del Dashboard. El nombre del ejemplo es "dsh_demo_bus_malaga".
- **Description**: un pequeño texto descriptivo de la finalidad del Dashboard. El del ejemplo es: "Demo dashboard with real time bus location".
- **Public**: opción para definir que se quiere que el Dashboard sea público para todo el mundo o privado para el usuario en exclusiva.
- **Initial Style**: para precargar un estilo inicial en el Dashboard. Por defecto se deja vacío.
- **Image**: archivo de imagen para caracterizar el Dashboard. Por defecto se deja sin seleccionar ninguna.
- **Category**: categoría de clasificación del Dashboard. Se puede dejar sin seleccionar nada.
- **Subcategory**: subcategoría de clasificación del Dashboard. Se puede dejar también sin seleccionar nada.
- **Global Styles, Libraries and Scripts**: en esta sección se importarán la hoja de estilo y biblioteca de Cesium, la biblioteca con el **[Onesait Cesium API](https://onesaitplatform-es.refined.site/space/DOC/2216692252/API+Onesait+Cesium)**, y los estilos del Dashboard. El contenido a añadir es:

**Global Styles, Libraries and Scripts**

```xml
<!-- CSS Styles -->
<link rel='stylesheet' type='text/css' href='https://lab.onesaitplatform.com/web/cesium/Cesium1.60/Widgets/widgets.css'>
<link rel='stylesheet' type='text/css' href='https://lab.onesaitplatform.com/web/Onesait_Cesium_API/onesait_cesium_api_styles.css'>

<!-- Scripts and libraries -->
<script src='https://cesiumjs.org/releases/1.62/Build/Cesium/Cesium.js'></script>
<script src='https://lab.onesaitplatform.com/web/Onesait_Cesium_API/onesait_cesium_api_b1-12.min.js'></script>


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


![image](media://d88f8d5a-7889-4c74-966b-6204a4357fe0)

Una vez introducida toda la información requerida, pulsa en el botón de **Nuevo** para crear el Dashboard. Esto abrirá automáticamente el Dashboard, el cual se encontrará vacío. Añade dos gadgets del tipo **Template** (LiveHTML); uno para el mapa, y otro para el texto descriptivo.

##   
** Gadget de mapa**

![image](media://aebacf3c-b84b-4e1c-a38e-d4e201333bc7)

Empieza configurando el Gadget correspondiente al mapa, que por seguir el modelo del ejemplo, se situará a la izquierda del Dashboard. Para editarlo, pulsa en el **icono de los tres puntos**, y después selecciona la opción de **Editar**.

![image](media://b49eb255-b963-468f-ac5f-952d0ec7c454)


De las dos ventanas de código que aparecen, en la de **HTML Compiled & Synchronized** (la de la izquierda), añade el siguiente código:

### HTML Compiled & Synchronized

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


> ℹ️ ### Acerca de 'cesiumContainer'
> ℹ️ 
> ℹ️ 'cesiumContainer' es el nombre del DIV en el cual Cesium creará dinámicamente el visor del mapa. Si se quiere llamar de otra manera a este DIV, es necesario modificar también el nombre al que hace referencia la variable 'viewer' en la parte de JavaScript.

En la ventana de **JS Compiled & Synchronized**, introduce este otro código:

##### **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(){

}
```


Hecho esto, pulsa el botón de **compilar y sincronizar**, para poder visualizar los cambios realizados.

![image](media://b62e0153-6812-4893-ba96-b3518af16d05)

Si no se ha producido ningún error, deberías ver en el Gadget de la izquierda un fondo oscuro con un globo terráqueo de color gris, lo que significa que tanto la biblioteca de Cesium como el Onesait Cesium API se han cargado correctamente. En los avisos de la consola de desarrollador aparecerá un mensaje indicando que el API se ha cargado correctamente, indicando a su vez la versión en ejecución.

![image](media://131bfd29-83d5-4568-a887-bf65388eadfd)

A continuación, personalizarás el visor generado. En primer lugar, se centrará la vista en la ciudad de Málaga, que es el lugar en donde se encuentran los datos a mostrar. Para ello, edita nuevamente el Gadget con el visor, y en la ventana de **JS Compiled & Synchronized**, añade las coordenadas y altitud a la que situar la cámara como variables. Dichas variables se definirán en la parte superior del código, de la siguiente forma:

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

Seguidamente, llama al método **cameraToPosition()**, el cual desplazará la cámara del visor a las coordenadas y altitud que se le indique como parámetros de entrada. Invoca al método <u>**dentro**</u> de la función de **vm.initLiveComponent()**, después de la línea 'viewer.scene.globe.baseColor = Cesium.Color.WHITE', tal como se expone a continuación:

##### **Método para mover la cámara a una posición dada**

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

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

> ℹ️ ### Más sobre el método cameraToPosition()
> ℹ️ 
> ℹ️ En el **[Onesait Cesium API](https://onesaitplatform-es.refined.site/space/DOC/2216692252/API+Onesait+Cesium)** puedes consultar más información sobre el método de **cameraToPosition()**.


Compilando y sincronizando los cambios realizados, el visor del Gadget **debería de enfocar** a la ciudad de Málaga:

![image](media://201f9c56-8b2d-4b5a-93d1-7b0bc4148fa3)


Seguidamente, se llamarán dos funciones pasivas: una para permitir la selección de entidades cuando se pinche sobre ellas, denominada featureSelection(), y otra que active las ventanas emergentes -o popups- para mostrar la información de propiedades de los puntos seleccionados, denominada popup(). Dichas funciones se configurarán tal como se muestra a continuación:

##### **Otras funciones interesantes**

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


> ℹ️ ### Más sobre el método featureSelection() y cesiumPopup()
> ℹ️ 
> ℹ️ En el **[Onesait Cesium API](https://onesaitplatform-es.refined.site/space/DOC/2216692252/API+Onesait+Cesium)** puedes consultar más información sobre el método de **cameraToPosition()** y **featureSelection()**.

El siguiente paso será cargar los datos de la ontología al mapa. Para ello, se hará uso del método de **createLayer()**, el cual cargará automáticamente en el mapa los datos de la ontología según las propiedades que se le definan como parámetros de entrada. Sin embargo, antes de utilizar este método, es necesario obtener los datos de la ontología, los cuales son servidos por el servicio API Rest antes creado. Por ello, empieza haciendo la llamada al servicio del API. Esto se hará después de la llamada al método de cameraToPosition() previamente definido en la ventana de **JS Compiled & Synchronized** del Gadget del mapa.

##### **Llamada al API Rest**

```
  /** Call the API Rest to create the layer from the ontology */
  jQuery.ajax({
      url:
          'https://lab.onesaitplatform.com/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 => {

      }
  })
```


> ❌ ### Token personal
> ❌ 
> ❌ Recuerda añadir tu propio **Token** en la cabecera de la llamada del API; más concretamente en la **línea 64**, donde pone '*Enter your personal token here*'.

Una vez llamado el servicio, el JSON de la colección de entidades se encontrará definido en el objeto llamado **featureCollection** de la respuesta, que será el que se utilizará como parámetro de entrada en el método de **createLayer()**. Dicho método se invocará dentro de la llamada como:

##### **Método para cargar una capa de puntos en el mapa**

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


> ✅ ### Simbología a utilizar
> ✅ 
> ✅ Puedes definir la simbología en una variable situada en la cabecera del código, tras la declaración de la longitud, latitud y altitud. Así, si quieres cambiar la simbología rápidamente, sólo tendrás que modificar esta variable.
> ✅ 
> ✅ Para este ejemplo, se ha definido la siguiente variable de simbología:
> ✅ 
> ✅ - **let simbBus** = 'https://lab.onesaitplatform.com/controlpanel/files/5cc7e3c6f2df81000b8f4948?disposition=1'


> ℹ️ ### Más sobre el método createLayer()
> ℹ️ 
> ℹ️ En el **[Onesait Cesium API](https://onesaitplatform-es.refined.site/space/DOC/2216692252/API+Onesait+Cesium)** puedes consultar más información sobre el método de **createLayer()**.

Hecho esto, salva y sincroniza los cambios llevados a cabo. Deberías ver en el mapa los autobuses en servicio representados con el icono seleccionado:

![image](media://f0a191a6-695f-4100-8057-69851c20e053)

Sin embargo, estos autobuses se encuentran estáticos, pues el objeto de **featureCollection** toma los datos de la ontología en el momento en el que se invoca la llamada al API. Para que se actualicen los datos con el tiempo, necesitas hacer una llamada recursiva al API para obtener los datos actualizados, y actualizar la información de la capa ya existentes de autobuses. Esto último se hará mediante el método de **updateLayer()**.

Por ello, tras la llamada al API Rest, incluye un **setInterval()** configurado a un minuto (o 60.000 milisegundos, que es el tiempo que tarda en actualizarse el servicio del GeoJSON del Ayuntamiento de Málaga), en cuyo interior se añadirá otra llamada al API Rest similar a la anterior, pero esta vez en vez de usar el método de createLayer(), usa **updateLayer()**:

##### **Repetir la llamada al API Rest cada minuto**

```javascript
  /** Set an interval to update the layer created calling again the API Rest service */ 
  setInterval(() => {
      jQuery.ajax({
          url:
              'https://lab.onesaitplatform.com/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)
```


> ❌ ### Token personal
> ❌ 
> ❌ Recuerda nuevamente el añadir tu propio **Token** en la cabecera de la llamada del API; en este caso en la **línea 120**.


> ℹ️ ### Más sobre el método updateLayer()
> ℹ️ 
> ℹ️ En el **[Onesait Cesium API](https://onesaitplatform-es.refined.site/space/DOC/2216692252/API+Onesait+Cesium)** puedes consultar más información sobre el método de **updateLayer()**.


Salvando y sincronizando los cambios realizados, debería verse **cómo se va actualizando la información en el mapa cada minuto**:

![image](media://f18f8b02-7e01-4523-982f-581eba7ffc0e)

## Gadget de información

Ahora que el mapa está funcionando, el siguiente paso será el editar el segundo Gadget del Dashboard para que muestre algún tipo de información básica del mapa, que sirva para aplicar la interconexión entre el Gadget del mapa y el Gadget de texto. Edita, por tanto, el Gadget destinado a la información (que en el ejemplo corresponde con el de la derecha), y en la ventana de **HTML Compiled & Synchronized** introduce el siguiente código:

##### **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>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>
```


Aplicando y sincronizando los cambios realizados, el Gadget debería de verse tal como:

![image](media://d443b6c1-9ab7-4532-b350-55ee2c7d6a74)

Fijándose con cuidado, se puede apreciar en el texto la existencia de dos '**waiting data**'; uno al final del segundo párrafo, y otro al final del tercero. La idea es sustituir esa cadena de texto estática por datos dinámicos, que se actualicen con el tiempo; en el primer caso indicando la hora de la última actualización, y en el segundo caso con el valor del recuerdo del número de autobuses en el mapa.

## Interconexión entre Gadgets

El primer caso es sencillo, pues no es más que sacar la hora a la que se ha hecho la llamada al API Rest e inyectar el valor en la etiqueta **<span>** de la cadena de texto. Se generará pues una función que, cuando se ejecute, obtenga la información de la hora, la formatee según interese y la inyecte donde interesa. El código de dicha función en sí no es relevante, pero sin embargo hay que tener en cuenta que la función será ejecutada desde el Gadget del mapa, no desde el Gadget actual, por lo que **hay que definirla y llamarla de un modo algo especial**.

El método para obtener la hora se ha definido en la cabecera de la ventana de **JS Compiled & Synchronized** con el nombre de **lastUpdateTime()** de la siguiente forma:

**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
}
```


> ❌ ### Definición y llamada a los métodos
> ❌ 
> ❌ A la hora de definir el método, tienes que hacerlo mediante '$scope.$root.nombreMétodo'. Cuando se invoque el método, tienes que hacerlo del mismo modo, llamándolo como '$scope.$root.nombreMétodo'.

Ahora, para que se ejecute el método, tienes que volver a editar el código del Gadget del mapa, y llamar al método desde la ventana de **JS Compiled & Synchronized**. Concretamente tienes que llamarla dos veces: primero cuando se carga la capa de autobuses por primera vez, y posteriormente cada vez que se actualicen los datos. Por tanto, debes situar la llamada después de llamar a **createLayer()** por un lado, y después de llamar a **updateLayer()** por el otro.

##### **Llamada al método de lastUpdateTime() tras createLayer()**

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

##### **Llamada al método de lastUpdateTime() tras updateLayer()**

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


Compila y sincroniza los cambios de ambos Gadgets. Si todo está bien definido, debería de aparecer **la hora a la que se actualizó por última vez** los datos del mapa.

![image](media://1c946de1-e06b-4c52-8b65-cdcc481fca42)

Para el segundo caso, define en el Gadget de información un nuevo método que tome como parámetro de entrada el valor del número de autobuses en el mapa, y sustituya dicho valor por la cadena de texto '**waiting data**' del tercer párrafo a partir del identificador de su etiqueta **<span>**. Edita de nuevo dicho Gadget, y define el nuevo método bajo el creado previamente con el nombre de **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
}
```

Después, vuelve al Gadget del mapa, en donde se llamará al método nuevamente desde dos sitios; en un primer lugar tras ejecutar el **createLayer()**, para que indique cuantos autobuses se han creado, y por segunda vez tras el **updateLayer()**, para ir controlando con cada actualización de datos cuantos autobuses hay en el mapa.

En este caso, tienes que pasar un parámetro de entrada al método, que será el valor a representar en el otro Gadget. Este valor se puede extraer de diversas maneras. Para este ejemplo, se ha calculado a partir del número de entidades que existen en el objeto de **featureCollection** a la hora de usarse tanto en **createLayer()** como en **updateLayer()**, ya que por cada entidad se creará un punto de autobús, por lo que el valor es válido para lo que se busca (siempre y cuando los datos de partida sean correctos, como es en el caso de este ejemplo).

La llamada a la función sería por tanto:

##### **Llamada al método de countBuses() tras 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)
      }
  })
```

##### **Llamada al método de countBuses() tras 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)
```


Guarda y sincroniza todos los cambios llevados a cabo en ambos Gadgets. Debería aparecer en el Gadget de información** el valor del número de autobuses activos** en el mapa.

![image](media://0f9bb1d7-9c94-4b6a-91b9-c07cacca80b6)

Hecho esto, sólo queda dar un poco de formato al Dashboard (colores de cabecera, iconos, tamaños, etc.) para tenerlo listo. En el caso del ejemplo, se usó la siguiente configuración de estilos:

- **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://80eff4e2-62dc-46b3-9de4-c53aeb411b54)

El resultado final queda por tanto del siguiente modo:


> Macro (iframe)

Los códigos completos utilizados para cada Gadget son los siguientes:

##### **Gadget del mapa - HTML Compiled & Synchronized**

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

  
**Gadget del mapa - JS Compiled & Synchronized**

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

/** Set the root */
let onesaitPlatform = window.location.protocol + '//' + window.location.hostname

/** Set the map symbology */
let simbBus = onesaitPlatform + '/controlpanel/files/5cc7e3c6f2df81000b8f4948?disposition=1'
let simbBusSelected = onesaitPlatform + '/controlpanel/files/5cd022d3f2df81000b8f494c?disposition=1'
let simbCluster = onesaitPlatform + '/controlpanel/files/5ce53ea21e27aa000b1cb239?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(
      'zoom',
      [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:
      onesaitPlatform + '/api-manager/server/api/v1/api_malaga_bus_real_time/getBuses',
      headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          'X-OP-APIKey': 'a659ec4ab8654902ba4780abda010bf5'
      },
      type: 'GET',
      contentType: 'application/json',
      success: featureCollection => {
        
          createLayer(
              featureCollection,
              'entity',
              'point',
              'billboard',
              [simbBus,
              'fromDefaultBillboard',
              0.7],
              'showLayer',
              'allowPicking',
              'enablePopup'
          	)
        
          /** 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:
          onesaitPlatform + '/api-manager/server/api/v1/api_malaga_bus_real_time/getBuses',
          headers: {
              'Content-Type': 'application/json',
              Accept: 'application/json',
              'X-OP-APIKey': 'a659ec4ab8654902ba4780abda010bf5'
          },
          type: 'GET',
          contentType: 'application/json',
          success: featureCollection => {
            
              /** Update the layer with the new data */
              updateLayer(
                  'realTimeBusData',
                  'position',
                  featureCollection,
                  'codBus',
                  'billboard',
                  true
              )
            
            /** Send the bus counter to the info gadget*/
            $scope.$root.countBuses(featureCollection.features.length)
        
            /** Get the update time */
            $scope.$root.lastUpdateTime()
              
          }
      })
  }, 60000)
}


vm.drawLiveComponent = function(newData, oldData){

}


vm.resizeEvent = function(){

}


vm.destroyLiveComponent = function(){

}
```

##### **Gadget de información - 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>
```

##### **Gadget de información - 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(){

};
```