Visualización en tiempo real de vuelos sobre la península
Disponer de datos en tiempo real es una funcionalidad muy interesante para la toma de decisiones en ámbitos que requieren una respuesta inmediata, por ejemplo el control de la producción, la interacción web de los usuarios (juegos on line, marketing, ventas) o el análisis de indicadores bursátiles por mencionar solo algunos ejemplos.
En este artículo indicaremos como desplegar un informe que muestre sobre un mapa los aviones que sobrevuelan la península ibérica, identificando el nombre del vuelo, su velocidad, su altitud y otros datos de interés. Los datos se actualizarán periódicamente en tiempo real, por lo que tendremos una imagen verdadera de lo que está sobrevolando nuestro cielo. El proceso es muy sencillo. La complejidad más alta de este ejemplo reside en la implementación del Sensor, proceso ajeno a Power BI y que en nuestro caso hemos implementado para poder simular un escenario real.

El desarrollo del informe permitirá identificar los puntos fuertes i las limitaciones de los conjuntos de datos insertados ('Streaming Push Datasets') de Power BI que hemos utilizado.
Los elementos que integrarán este ejemplo son los siguientes:
- Fuente de datos: Hemos optado por los servicios de OpenSky Networks que ponen a nuestra disposición la información de tráfico aéreo que necesitamos. Los datos son accesibles para fines no comerciales vía API.
- Sensor: Aunque no se trate de un sensor como tal, necesitamos emular un dispositivo que capture los datos de nuestra Fuente de datos, los procese adecuando su formato, y los envíe al servicio de Streaming integrado en Power BI. En nuestro ejemplo, realizaremos todo ello mediante un script PHP que hospedaremos en un servidor local de desarrollo de WordPress. Si se desearán mostrar transacciones de compra en tiempo real en una web, el sensor equivaldría a la aplicación que leyera los datos de la tienda y los enviará a Power BI para su visualización.
- Planificador de tareas: Para controlar la frecuencia con la que nuestro sensor recoge y envía los datos, utilizaremos el planificador de tareas de Linux (cron), mediante el cual planificaremos la ejecución del script asociado al sensor cada 15 segundos de forma ininterrumpida. Dado que utilizamos un entorno de desarrollo, el impacto en el rendimiento del servidor no es crítico.
- Push Dataset: En el servicio de Power BI, activaremos un conjunto de datos en tiempo real bajo la modalidad Push (conjunto de datos insertados). Dicho conjunto de datos capturará la información que envíe el sensor a la frecuencia establecida. Microsoft ha diseñado este conjunto de datos para que capture información según la reciba. Este será el dataset sobre el que generaremos nuestro informe visual.
- Power BI Desktop: Mediante la aplicación de escritorio de Power BI desarrollaremos nuestro informe. El origen de datos al que nos conectaremos será el conjunto de datos insertado mencionado. El objeto visual que añadiremos al informe y que permitirá visualizar las rutas aéreas es 'Route Map', que puede añadirse a los visuales estándar desde la galería.
- Paneles/Dashboard: Una vez confeccionado el informe con Power BI Desktop, lo publicaremos en el servicio y crearemos un Panel en el que anclaremos los visuales de interés. Será en este panel donde veremos la actualización de las rutas de las aeronaves actualizándose en tiempo real.
Esquemáticamente, el flujo de los datos a través de los elementos mencionados es el siguiente:

El informe final y el panel de datos en tiempo real
Antes de continuar con los aspectos més técnicos del desarrollo, veamos una muestra del resultado final.
El informe:
Aunque podríamos haberlo desarrollado directamente en el servicio de Power BI, por comodidad hemos desarrollado el informe con Power BI Desktop para después publicarlo allí.
En general en Power BI los datos de un informe se actualizan dependiendo del tipo de datos y de la capacidad sobre la que estos se encuentran. La siguiente tabla de Microsoft es aclarativa al respecto:

Podemos ver que en la modalidad Push elegida, los informes no se refrescan con los datos (para que lo hagan debemos forzarlo manualmente). La actualización de los datos en tiempo real en este conjunto de datos se aplica solo a los iconos ('tiles') de los paneles. Así pues, para consumir visualizaciones de Power BI en tiempo real, deberemos hacerlo desde un Panel.
Entonces, ¿Por que hemos seleccionado un Dataset de tipo Push para nuestro ejemplo y no la modalidad 'LiveConnect'?
La respuesta es que queremos representar las rutas en un mapa (objetos visuales enriquecidos) y esto solo es posible hacerlo generando un informe asociado a un Dataset acumulativo como el elegido. La modalidad 'LiveConnect' no es acumulativa y actualmente presenta muchas limitaciones de visualización (únicamente permite tarjetas, visuales de líneas y barras).
El informe generado se muestra a continuación:

Posteriormente, para visualizar en tiempo real los cambios que se produzcan en los vuelos, es necesario consumir las visualizaciones generadas en el informe anclándolas a un Panel que generaremos en el servicio de Power BI. El resultado os lo mostramos con un video corto donde pueden observarse como se generan cambios con la cadencia programada de 15 segundos.
Y, ¿Por que mostramos el resultado en un video y no lo compartimos a través de un enlace directo al mismo?
El motivo de no compartiros el panel en vivo es una de las limitaciones del servicio. A día de hoy no es posible compartir de forma pública (si dentro de la organización) un informe o panel basado en datos en tiempo real.
Detalles del desarrollo:
Para quienes deseen replicar el ejemplo o simplemente ver el detalle del desarrollo, a continuación exponemos lo más relevante.
Fuente de datos:
Para capturar únicamente los vuelos sobre la península, OpenSky Networks nos proporciona un método de filtrado de llamada a la API, en la que indicando los límites de latitud y longitud máxima y mínima, su servicio nos retorna solo los vuelos que en ese instante se encuentran sobre dicha área. Para posicionar la península hemos tomado sus coordenadas más extremas.
$url='https://opensky-network.org/api/states/all?lamin=36.0025&lomin=-9.29916666666&lamax=43.7919444&lomax=3.33166666666';
La información que retorna la API está estructurada mediante un árbol 'json' con el siguiente formato:
{
"time": 1606566150,
"states": [
["341311", "P18 ", "Spain", 1606566150, 1606566150, -3.567, 40.4582, null, true, 8.23, 8.44, null, null, null, null, false, 0],
["4952c5", "TAP844 ", "Portugal", 1606566148, 1606566148, 1.4318, 41.325, 11574.78, false, 232.16, 92.03, 0, null, 11506.2, "5257", false, 0],
[...]
]
}
En la web de OpenSky Networks podéis ver el significado de cada campo.
Este formato de respuesta deberá ser procesado por el sensor para adecuarlo al formato requerido por Power BI, que es el siguiente. Notar en particular, la necesidad de adaptar el formato de fecha de UNIX epoch time a RFC 3339.
[
{
"icao24": "341311",
"callsign": "P18 ",
"origin_country": "Spain",
"longitude": -3.567,
"latitude": 40.4582,
"baro_altitude": null,
"on_ground": true,
"velocity": 8.23,
"vertical_rate": null,
"geo_altitude": null,
"timeframe": "2020-11-28T12:22:30+00:00"
},
{
"icao24": "4952c5",
"callsign": "TAP844 ",
"origin_country": "Portugal",
"longitude": 1.4318,
"latitude": 41.325,
"baro_altitude": null,
"on_ground": false,
"velocity": 232.16,
"vertical_rate": 0,
"geo_altitude": 11506.2,
"timeframe": "2020-11-28T12:22:30+00:00"
},
{
...
}
]
Sensor:
El código PHP que incorporaremos en nuestro servidor es la parte más compleja de este pequeño proyecto. Es el siguiente:
function CallAPI($method, $url, $data = false)
{
// Funcion de llamada a API mediante la libreria CURL
// Method: POST, PUT, GET etc
// Data: array("param" => "value") ==> index.php?param=value
// Referencia: https://stackoverflow.com/questions/9802788/call-a-rest-api-in-php/9802854#9802854
$curl = curl_init();
switch ($method)
{
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data)
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
break;
case "PUT":
curl_setopt($curl, CURLOPT_PUT, 1);
break;
default:
if ($data)
$url = sprintf("%s?%s", $url, http_build_query($data));
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl,CURLOPT_FAILONERROR,1);
$result = curl_exec($curl);
if(curl_errno($curl)){
exec( 'echo ' . curl_error($curl) . ' > /var/tmp/flightDataSensor_curl_error.txt');
$result='Error';
}
curl_close($curl);
return $result;
}
function colsFromArray(array $array, $keys) {
// Funcion para extraer las columnas de interés de un ARRAY asociativo
if (!is_array($keys)) $keys = [$keys];
$filter = function($k) use ($keys){
return in_array($k,$keys);
};
return array_map(function ($el) use ($keys,$filter) {
return array_filter($el, $filter, ARRAY_FILTER_USE_KEY );
}, $array);
}
function flight_data_sensor()
{
// SENSOR: extrae datos de vuelos de OpenSky y los envía al servicio de Power BI
// Establecemos el end point de power bi. El End Point se obtiene desde
// el servicio de Power BI al configurar el Dataset de Streaming
$powerbi_endpoint="<POWERBI END POINT>";
// Definimos la url de llamada a la API de OpenSky Network
// aplicando el filtro de coordenadas para obtener solo los vuelos sobre la península.
$url = 'https://opensky-network.org/api/states/all';
$data = array(
"lamin"=>"36.0025",
"lomin"=>"-9.29916666666",
"lamax"=>"43.7919444",
"lomax"=>"3.33166666666");
// Lanzamos una llamada GET de captura de datos contra OpenSky mediante curl
// y gestionamos la respuesta
$method='GET';
$opensky_response=CallAPI($method,$url,$data);
// Procesamos la respuesta adecuando el formato json entregado por
// OpenSky al formato requerido por Power BI.
// Aprovechamos el proceso para reducir los campos que enviaremos a Power BI
if ($opensky_response<>'Error') {
// Procesamos y enviamos los datos si no hay error
// Preparamos las etiquetas
$opensky_data = json_decode($opensky_response);
$array_keys=["icao24",
"callsign",
"origin_country",
"time_position",
"last_contact",
"longitude",
"latitude",
"baro_altitude",
"on_ground",
"velocity",
"true_track",
"vertical_rate",
"sensors",
"geo_altitude",
"squawk",
"spi",
"position_source",
"timestamp"
];
//Extraemos los grupos de datos de interés de la respuesta
//Hora UTC
$time_response_0=$opensky_data->time;
//Detalles de cada vuelo
$flight_data=$opensky_data->states;
// Adaptamos el formato de los datos a los requerimientos de Power BI
// Convertimos la marca de tiempo UNIX proporcionada en un formato datetime de php
$dt = new DateTime("@$time_response_0");
$time_response= $dt->format('Y-m-d H:i:s');
// Generamos un array asociativo de los detalles con las etiquetas
$my_associative_array=[];
foreach ($flight_data as $index) {
array_push($index, $time_response);
$my_associative_array[]=array_combine($array_keys,$index);
}
// Seleccionamos los parametros de interés (los que enviaremos a Power BI)
// para filtrarlos posteriormente
$newkeys=["icao24",
"callsign",
"origin_country",
"longitude",
"latitude",
"baro_altitude",
"on_ground",
"vertical_rate",
"velocity",
"geo_altitude",
"timestamp"
];
$my_associative_array=colsFromArray($my_associative_array,$newkeys);
// Crear objeto json a partir del array asociativo
$json_object=json_encode($my_associative_array);
// Enviamos datos al End point de Power BI
$method='POST';
$pbi_response=CallAPI($method,$powerbi_endpoint,$json_object);
}
}
Los comentarios incrustados en el código explican la función de cada bloque. Destacamos lo siguiente:
- End Point: Debéis sustituir este dato <POWERBI END POINT> por la 'url de inserción' del dataset de streaming que porporciona Power BI al crearlo.
- Formato de datos: Es muy importante transformar adecuadamente las datos del Sensor para que Power BI sea capaz de procesarlos. En particular el formato de fecha a nosotros nos generó algún quebradero de cabeza.
Planificador de tareas:
Cron es el planificador de tareas de Linux. Para crear una tarea programada bastará incorporar al fichero 'crontab' de linux una llamada al sensor: ''flight_data_sensor()". Dado que el formato de programación de la tarea limita la frecuencia mínima a 1 minuto, generaremos cuatro llamadas al Sensor con periodos de espera entre llamadas de 15 segundos. De este modo en cada minuto realizaremos 4 llamadas a intervalos de 15 segundos.
Para editar el fichero crontab, desde la consola:
crontab -e
En contenido que editaremos en el fichero será:
#call flight_data_sensor.php as root every minute
*/1 * * * * sudo php -r "require '[FULL-PATH]/flight_data_sensor.php'; flight_data_sensor();"
*/1 * * * * (sleep 15; sudo php -r "require '[FULL-PATH]/flight_data_sensor.php'; flight_data_sensor();")
*/1 * * * * (sleep 30; sudo php -r "require '[FULL-PATH]/flight_data_sensor.php'; flight_data_sensor();")
*/1 * * * * (sleep 45; sudo php -r "require '[FULL-PATH]/flight_data_sensor.php'; flight_data_sensor();")
donde sustituiremos [FULL-PATH] por la ruta completa donde ubiquemos el sensor.
Push dataset:
Power BI nos ofrece la posibilidad de trabajar con diferentes conjuntos de datos en Streaming: API o PubNub.
- API: Crea un dataset que queda a la espera de capturar datos en formato json. Nosotros somos los responsables de enviar al dataset estos datos mediante una petición GET contra el End Point ('url de inserción') del servicio. Hay dos tipos de datasets de steaming: acumulativos y no acumulativos. Los primeros crean una tabla que va acumulando los datos recibidos para facilitar un posterior análisis de datos históricos. La tabla se va llenando a medida que recibe datos. Los segundos simplemente capturan el dato recibido para posteriormente ser visualizado a través de un panel de forma dinámica. El dato se visualiza pero no se guarda, por lo que no es posible realizar análisis histórico.
- PubNub: Es el formato de comunicación de datos en tiempo real creado y soportado por la compañía con el mismo nombre. Esta compañía pone a disposición de desarrolladores diferentes aplicaciones y módulos para desarrollar servicios de datos en tiempo real a través de su propia API.
Como hemos mencionado, en este ejemplo utilizaremos la API de Power BI para datasets de streaming en su modalidad acumulativa.
Para crearlo, lo haremos desde el servicio de Power BI. Desde un área de trabajo accionaremos la opción "Conjunto de datos de streaming". Ello nos abrirá un panel lateral donde configuraremos el nombre del Dataset, así como los campos que enviaremos en cada llamada. Finalmente, activaremos la opción acumulativa ("Análisis del historial de datos") y copiaremos la url de inserción o End Point.


Para este tipo de datasets, Microsoft especifica las siguientes restricciones. Vemos que el rendimiento y la velocidad de ingesta de datos es más que suficiente para muchas aplicaciones y que la limitación más importante tiene que ver con la actualización de los objetos visuales (cuando se actualizan los datos se refresca el visual completo sin transiciones suaves y solo para los iconos o visualizaciones de los paneles).

Power BI Desktop:
Mediante Power BI desktop crearemos el informe base. Como origen de datos seleccionaremos "Conjunto de Datos de Power BI", y en las opciones que se nos mostrarán a continuación, elegiremos el dataset de streaming creado (es necesario iniciar sesión en el servicio). Automáticamente veremos la consulta con nuestros campos de datos, y a partir de este punto generaremos nuestro informe como si se tratara de un informe de Power BI más. Nos encontraremos sin embargo con algunas diferencias:
- No podremos incorporar otras fuentes de datos ni tablas adicionales (tampoco tablas manuales).
- No podremos manipular los datos con Power Query.
- No podremos guardar el informe como Template (pbit) y en consecuencia distribuirlo parametrizando el origen de datos.
Estas limitaciones tendrán mayor o menor importancia en función del uso que deseemos darle al informe, que recordemos no se consume como tal sino que sirve para posteriormente utilizarlo desde un Panel en el servicio de Power BI.
Para generar el informe hemos utilizado, entre otros, el objeto visual 'Route Map', incorporado desde la galería. Igualmente hemos creado algunas medidas y filtrado los datos en la visualización para mostrar solo los vuelos detectados en la última hora.
Ejemplos de medida DAX:
_LastRefreshTime =
VAR Resultado =
"Última actualización (UTC): " & NOW()
RETURN
Resultado
_LastDateRecovered =
// Ultimo dato de lectura de tiempo
Max(RealTimeData[timestamp])
_Current Velocity =
// Último dato de velocidad de un vuelo
// Utilizado en tooltips
IF(
HASONEVALUE(RealTimeData[icao24]),
LOOKUPVALUE(
RealTimeData[velocity],
RealTimeData[icao24],
SELECTEDVALUE(RealTimeData[icao24]),
RealTimeData[timestamp],
[_LastDateRecovered]
),
BLANK()
)
Filtros aplicados al informe:

Panel:
El último paso del proceso es preparar el informe para ser consumido. Para ello publicaremos el informe anterior en el servicio de Power BI, crearemos un panel y anclaremos los visuales de interés (PIN sobre el objeto gráfico del informe). Una vez en el panel, los datos se actualizan en tiempo real tal como habéis visto en el video. Bastará distribuirlo en la organización mediante las opciones de publicación: incrustar el panel o bien enviar su url. Quien desee consumirlo debe tener los permisos correspondientes y obviamente una cuenta en Power BI. A fecha de hoy, no es posible distribuir públicamente estos contenidos.
Conclusiones
A partir del desarrollo de un pequeño proyecto, hemos mostrado la capacidad de Power BI para generar informes de datos en tiempo real. Se trata de un modelo de consumo de información muy útil para cubrir determinadas necesidades empresariales del ámbito del Internet de las Cosas (IoT), proyectos de Marketing y Ventas, Gestión de Incidencias, Control de procesos productivos, etc.
Las principales ventajas que vemos en su uso son:
- Su facilidad y rapidez de implementación. Power BI es muy simple de configurar y usar. La complejidad estará presente únicamente en el proceso de generación de datos, donde según el tipo de proyecto se requerirá un mayor o menor esfuerzo de adaptación.
- Los límites de ingesta de datos no representan un problema para la mayor parte de proyectos de una PYME. En proyectos de BigData deberemos plantearnos otras alternativas.
- La frecuencia de actualización es muy flexible. Los dataset en streaming recogen y actualizan los visuales a la misma frecuencia a la que se le proporcionan.
- Podemos usar los objetos visuales disponibles para cualquier informe de Power BI.
- Power BI evoluciona a una velocidad de vértigo y considera en su roadmap evolutivo las peticiones de los usuarios. Muchas limitaciones actuales probablemente dejarán de serlo en el futuro.
Algunas de las restricciones que hemos observado en el desarrollo para el conjunto de datos acumulativo en tiempo real utilizado se han ido mencionando y son las siguientes:
- No es posible incorporar consultas adicionales a la consulta de de streaming. Así pues, solo podremos manejar como tabla la propia del dataset.
- No podremos utilizar Power Query para transformar los datos. El "Sensor" deberá entregar los datos limpios y tratados para ser usados. En cualquier caso podremos seguir generando medidas con DAX sobre nuestro conjunto de datos.
- La creación de Templates no es posible en informes generados con un dataset en streaming.
- Los refrescos de las visualizaciones en el panel no son suaves. El icono se recarga completamente en cada actualización.
- Los datasets acumulativos pueden acumular hasta 200 registros/filas de datos. Una vez se supera este umbral, los datos se borran atendiendo a su orden de entrada en secuencia FIFO (First-In-First-Out).
- La compartición de los paneles está limitada al ámbito de la organización.
¡Esperamos que este artículo os sea de utilidad!