Skip to content

2 Preprocesamiento

Javi edited this page May 5, 2025 · 3 revisions

Decodificación

Tras descargar los datos proporcionados en el Campus Virtual, el primer paso fue decodificar la información del dataframe inicial. De las dos columnas que había, tskafka indicaba el tiempo en milisegundos desde la época UNIX, que transformamos a formato estándar datetime para aumentar la legibilidad. La otra columna, message, contenía un mensaje codificado en base64, que pasamos a formato hexadecimal para después decodificarlo en función de los códigos proporcionados en el manual “The 1090 Megahertz Riddle”.

Apoyándonos en esta documentación, decodificamos los mensajes con funciones de pyModeS tras detectar el tipo de cada mensaje a partir del formato del enlace descendente. Había múltiples tipos de mensajes, cada uno con información distinta. Para recoger los distintos mensajes generamos un dataframe general con todas las posibles columnas, rellenando con nulos los valores que faltaban según el tipo de mensaje.

Para poder procesarlo después creamos una clase DataframeProcessor con distintos métodos estáticos que filtraban el conjunto de datos general según las necesidades de visualización, devolviendo dataframes más pequeños y específicos.

Anatomía de los mensajes

Los mensajes tienen pueden ser de cuatro tipos (identidad, altitud, ADS-B y Mode-S), codificado como ya hemos visto con el formato de enlace descendiente. Todos los mensajes tienen asociados el timestamp de emisión. Cada uno de estos tipos de mensaje proporcionan una información concreta, siendo común a todos ellos la ICAO, que es el identificador único de cada aeronave. Más concretamente:

  • Los de tipo identidad proporcionan el squack code, un código de cuatro dígitos utilizado en los transpondedores de las aeronaves para identificarlas y comunicarse con los controladores de tráfico aéreo.
  • Los de tipo altitud proporcionan la altitud barométrica de la aeronave.
  • Los de tipo ADS-B ofrecen información importante como la posición de la aeronave, la velocidad, el estado de vuelo o la categoría de turbulencia. Dentro de ellos hay más subtipos en función del campo typecode.
  • Los de tipo Mode-S proporcionan otra información relativa a la aeronave, incluyendo también datos meteorológicos. Dentro de ellos hay más subtipos en función del campo BDS.

Tanto los mensajes de tipo ADS-B como los de Mode-S pueden indicar el identificador único de vuelo, un campo llamado Callsign.

Para más detalles, ver el módulo decoder.py dentro de la carpeta preprocess.

Dificultades

Sin embargo, como cada tipo de mensaje proporcionaba una información incompleta, había ciertas filas que el código rellenaba de manera arbitraria y esto llevó a errores de coherencia en los datos. Para resolverlo, buscamos estas filas de outliers y las eliminamos del conjunto de datos, ya que la información de ese tipo de mensaje no era necesaria para esta entrega.

Uno de los principales problemas a los que nos hemos enfrentado en este proyecto ha sido el gran tamaño de los datos, tanto a la hora de procesar y decodificar el dataframe inicial como a la hora de representar las gráficas. Nuestros ordenadores no eran lo suficientemente potentes, así que utilizamos el ordenador de la facultad para decodificar los datos. Dividimos el conjunto en particiones para procesarlas, pero tuvimos problemas la primera vez que ejecutamos el código porque en cada partición saltaba un error distinto, y teníamos que modificar el código para acomodar cada caso. Había algunos datos que estaban almacenados como tuplas en vez de un solo dato, lo que hacía saltar el error al ejecutar el decodificador. Para solucionarlo dividimos el dataframe en más columnas, pero a medida que avanzaban las particiones iban saliendo nuevas tuplas de datos corruptos. La siguiente medida fue convertir esas columnas a objetos para evitar errores, y luego filtrar los datos corruptos y convertir la columna al tipo correcto después de decodificar todos los datos, manualmente. Además de esta conversión de tipos, los nulos se guardaron como tipo string, lo cual dificultaba el procesado y hacía que algunas columnas se guardaran como objeto, que también tuvimos que convertir al tipo correcto y marcar los nulos con su tipo correspondiente. Para facilitar todo este proceso creamos el módulo utilities.py con las funciones necesarias de conversión de tipos, procesado de nulos y creación de columnas auxiliares.

Estos problemas se agravaron especialmente al intentar procesar la semana completa debido a que la librería pandas no maneja bien grandes volúmenes de datos en términos de uso de memoria. Esto provoca que el entorno kernel se quede sin memoria disponible, lo que interrumpe la operación. Probamos usando la librería dask, pero hay métodos de pandas como merge_asof que no son compatibles con ella. Esto nos dificultó el proceso ya que eran necesarias esas funcionalidades específicas a la hora de representar las gráficas. Como solución, decidimos separar los datos en 10 particiones, asegurándonos de que los mensajes del mismo ICAO estuvieran en la misma partición. Aplicamos las funciones pertinentes a cada particición por separado y luego juntamos los resultados en un único dataframe, que era de menor tamaño y ya se podía cargar y representar sin sobrepasar la memoria del ordenador.

A la hora de representar los datos era necesario extraer la hora y el día de la semana a partir de la fecha, también con funciones de utilities.py.

Posiciones válidas

Creamos un módulo data_processor.py para determinar si la posición de un avión es válida o no. Según el manual, las posiciones dadas se pueden considerar no válidas si se cumple uno de los siguientes casos:

  • Si el avión se encuentra fuera del rango máximo del radar. Este rango no es fijo, pues depende, entre otras cosas, de la altitud del avión.
  • Si la distancia entre el avión y el radar es mayor de 180 millas náuticas (equivalen a unos 1800 km).

Cálculo de los tiempos de espera

Los mensajes asociados a los Callsigns (un identificador único de vuelo) no mostraban el estado de vuelo (flight status) como airborne, lo que complicó la correcta identificación de los vuelos activos. Para resolverlo, utilizamos la función merge_asof, combinando dos dataframes complementarios utilizando el ICAO como clave y relacionándolos por proximidad de tiempo.

Después, seleccionamos para cada vuelo el momento en el que despegaba y el momento en el que estaba en pista con velocidad cero y los restamos para obtener el tiempo de espera, que posteriormente convertimos a segundos.

Es preciso indicar que las funciones de ciertas librerías fallaron al intentar procesar el gran volumen de datos (> 3 GB). Para solucionarlo, implementamos un procesamiento por particiones, guardando los resultados de forma incremental en archivos Parquet separados, evitando así cargar todo el conjunto de datos en memoria al mismo tiempo. Un pequeño detalle a tener en cuenta es que todas las señales procedentes de una misma aeronave (misma ICAO) se destinaron a la misma partición para que, de este modo, los vuelos (identificados por el Callsign) también estuviesen en la misma. De este modo, no habría errores a la hora de calcular los tiempos de espera.

Clone this wiki locally