Skip to article frontmatterSkip to article content
Sensorjournalistikk

Visualisering i notebooks πŸ—ΊοΈ

Medieklyngen

NΓ₯r vi nΓ₯ har brukt litt tid pΓ₯ Γ₯ hente ut og analysere data med DuckDB, er neste steg Γ₯ jobbe visuelt med materialet gjennom visualisering. Det gjΓΈr vi i sΓ₯kalte notebooks – digitale notatbΓΈker som lar oss dokumentere hele arbeidsprosessen med bΓ₯de tekst og kode. Du kan kjΓΈre notebooks lokalt, for eksempel med Jupyter Notebook, men for enkelhetens skyld bruker vi Google Colab – en nettbasert lΓΈsning som sikrer at alle har samme utgangspunkt.

Hva er notebooks?ΒΆ

En notebook er et interaktivt verktΓΈy der du skriver og kjΓΈrer kode direkte i nettleseren. Tenk pΓ₯ det som en blanding av en tekstbehandler og en kode-editor, hvor du kan kombinere forklarende tekst, bilder og kode – alt i Γ©n og samme arbeidsflate. Det er et populΓ¦rt verktΓΈy innen datavitenskap, maskinlΓ¦ring, undervisning – og ikke minst, datajournalistikk.

De viktigste egenskapene:

  1. KjΓΈring av kode i smΓ₯ deler (celler)): Du kan kjΓΈre koden stykkevis, noe som gjΓΈr det enklere Γ₯ teste, feilsΓΈke og jobbe iterativt.
  2. Kombinasjon av tekst og kode: Du kan forklare hva du gjΓΈr underveis, noe som gjΓΈr prosessen mer forstΓ₯elig – bΓ₯de for deg selv og andre.
  3. Visualiseringer: Grafer, kart og diagrammer kan vises direkte i notatboken.
  4. Dokumentasjon: Hele arbeidsprosessen lagres, slik at du enkelt kan reprodusere resultatene. Perfekt hvis du for eksempel skal sende inn en SKUP-rapport.

I de neste ΓΈvelsene skal vi bruke notebooks, DuckDB, litt enkel Python, grafverktΓΈyet Plotly, og visualiseringsverktΓΈyet Lonboard. Det er flere nye verktΓΈy, men vi holder det pΓ₯ et overkommelig nivΓ₯. Har du ikke programmert fΓΈr, trenger du ikke bekymre deg – mange av kommandoene vil ligne pΓ₯ det du allerede har brukt i DuckDB.

Opprette en notebookΒΆ

Vi starter med Γ₯ opprette en tom notebook i Google Colab:

  1. GΓ₯ til Google Colab.

  2. Velg Fil β†’ Ny notatbok. Du fΓ₯r opp en tom notatbok som ser slik ut:

Figur 1: Tom notebook.

Legg til tekst- og kodecellerΒΆ

En notebook bestΓ₯r av to hovedtyper celler: tekst og kode.

Slik gΓ₯r du frem:

Legg til en tekstcelleΒΆ

  1. Klikk pΓ₯ Tekst-knappen i menylinjen.

Figur 2: Legg til ny tekstlinje.

  1. Skriv fΓΈlgende:
# Installer nΓΈdevendige biblioteker

Legg til en kodecelleΒΆ

  1. Klikk pΓ₯ Kode-knappen, og skriv inn:
!pip install lonboard==v0.9.3 duckdb plotly.express

Dette installerer Lonboard (versjon 0.9.3), DuckDB og Plotly i notatboken.

  1. Trykk pΓ₯ play-ikonet til venstre for cellen for Γ₯ kjΓΈre den. Du ser nΓ₯ at Colab laster ned og installerer verktΓΈyene.

Figur 3: FΓΈrste kodecelle installerer avhengigheter.

Last inn DuckDB og nΓΈdvendige modulerΒΆ

  1. Lag en ny tekstcelle med fΓΈlgende innhold:
# Last inn DuckDB og nΓΈdvendige tilleggsmoduler
  1. Deretter legger du inn fΓΈlgende kode i en ny kodecelle:
import duckdb
con = duckdb.connect()
con.sql('INSTALL spatial;')
con.sql('INSTALL httpfs;')
con.sql('INSTALL h3 FROM community;')
con.sql('LOAD spatial;')
con.sql('LOAD httpfs;')
con.sql('LOAD h3;')

Du vil kanskje kjenne igjen noen av kommandoene, selv om syntaksen nΓ₯ er litt annerledes. Kort fortalt er de skrevet i Python og sendes videre til DuckDB-biblioteket for Γ₯ kjΓΈres.

  1. KjΓΈr cellen med play-knappen. NΓ₯r alt er ferdig, vises et grΓΈnt avhukingssymbol som bekrefter at alt fungerte som det skulle.

Legg inn nΓΈkler til Medieklyngens serverΒΆ

Vi er snart klare til Γ₯ hente data fra Medieklyngens server.

  1. Start med en ny tekstcelle:
# Legg inn nΓΈkler til serveren
  1. Og sΓ₯ en ny kodecelle med dette innholdet:
con.sql("CREATE SECRET (TYPE R2, KEY_ID '9030e0f90a86af08b08b6e2a1222a778', SECRET '2fe64ae1c22869400f577bb9421602f0f81a83a2f658cea6bdd556f4fc65064b', ACCOUNT_ID 'bca3475a0f4afeb0640daafc17ec2b18');")
  1. Trykk pΓ₯ play. Hvis alt gikk bra, vil du se fΓΈlgende svar fra Colab:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Success β”‚
β”‚ boolean β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ true    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Gratulerer! Du er nΓ₯ logget pΓ₯ serveren og klar til Γ₯ hente og visualisere data.

Last inn og undersΓΈk dataΒΆ

NΓ₯ skal vi hente inn et datasett fra 20. mars 2025. Start med Γ₯ legge til en ny tekstcelle med fΓΈlgende tittel:

# Last inn data fra 20. mars 2025

Deretter legger du inn denne kodecellen:

con.sql("CREATE OR REPLACE TABLE FLIGHTS_20_03_2025 AS SELECT * FROM read_parquet('r2://medieklyngen-radar-data/adsb/history/*/*/*/*.parquet', hive_partitioning = true) WHERE year = 2025 AND month = 03 AND day = 20;")

Dette kan ta litt tid. Du ser fremdriften via den horisontale linjen under cellen. NΓ₯r den grΓΈnne avkrysningsboksen vises, er operasjonen fullfΓΈrt og du kan gΓ₯ videre.

Tell antall datapunkterΒΆ

Legg til en ny tekstcelle:

# Tell antall datapunkter

Og deretter en kodecelle:

con.sql("SELECT count(*) FROM FLIGHTS_20_03_2025")

Du fΓ₯r et svar som dette:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ count_star() β”‚
β”‚    int64     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚      4111738 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Over 4,1 millioner rader – et solid utgangspunkt!

Se pΓ₯ datastrukturenΒΆ

Legg inn en ny tekstcelle:

# Beskriv datastrukturen

Og legg deretter inn denne kodecellen:

con.sql("DESCRIBE FLIGHTS_20_03_2025").show(max_width=100)

Resultatet ser omtrent slik ut – og mye bΓΈr nΓ₯ vΓ¦re kjent stoff:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ column_name β”‚                          column_type                           β”‚  null   β”‚   key   β”‚ default β”‚  extra  β”‚
β”‚   varchar   β”‚                            varchar                             β”‚ varchar β”‚ varchar β”‚ varchar β”‚ varchar β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ dbFlags     β”‚ BIGINT                                                         β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ desc        β”‚ VARCHAR                                                        β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ icao        β”‚ VARCHAR                                                        β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ ownOp       β”‚ VARCHAR                                                        β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ r           β”‚ VARCHAR                                                        β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ reg_details β”‚ STRUCT(description VARCHAR, iso2 VARCHAR, iso3 VARCHAR, nati…  β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ t           β”‚ VARCHAR                                                        β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ timestamp   β”‚ TIMESTAMP                                                      β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ trace       β”‚ STRUCT(aircraft STRUCT(alert BIGINT, alt_geom BIGINT, baro_r…  β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ year        β”‚ BIGINT                                                         β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ day         β”‚ VARCHAR                                                        β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”‚ month       β”‚ VARCHAR                                                        β”‚ YES     β”‚ NULL    β”‚ NULL    β”‚ NULL    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 12 rows                                                                                                    6 columns β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Inspiser dataeneΒΆ

Neste steg er Γ₯ ta en nΓ¦rmere titt pΓ₯ selve innholdet i tabellen. Legg fΓΈrst inn en tekstcelle:

# Inspiser dataene
con.sql("SELECT * FROM FLIGHTS_20_03_2025").show(max_width=2500)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ dbFlags β”‚      desc       β”‚  icao   β”‚  ownOp  β”‚    r    β”‚                                    reg_details                                     β”‚    t    β”‚      timestamp      β”‚                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    trace                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    β”‚ year  β”‚   day   β”‚  month  β”‚
β”‚  int64  β”‚     varchar     β”‚ varchar β”‚ varchar β”‚ varchar β”‚      struct(description varchar, iso2 varchar, iso3 varchar, nation varchar)       β”‚ varchar β”‚      timestamp      β”‚                                   struct(aircraft struct(alert bigint, alt_geom bigint, baro_rate bigint, category varchar, emergency varchar, flight varchar, geom_rate bigint, gva bigint, ias bigint, mach double, mag_heading double, nac_p bigint, nac_v bigint, nav_altitude_fms bigint, nav_altitude_mcp bigint, nav_heading double, nav_modes varchar[], nav_qnh double, nic bigint, nic_baro bigint, oat varchar, rc bigint, roll double, sda bigint, sil bigint, sil_type varchar, spi bigint, squawk varchar, tas bigint, tat varchar, track double, track_rate double, true_heading double, "type" varchar, "version" bigint, wd bigint, ws bigint), altitude bigint, flags bigint, geometric_altitude bigint, geometric_vertical_rate bigint, ground_speed double, h3_15 varchar, indicated_airspeed bigint, lat double, lon double, on_ground boolean, roll_angle double, source varchar, "timestamp" varchar, track_degrees double, vertical_rate bigint)                                    β”‚ int64 β”‚ varchar β”‚ varchar β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': 0, 'alt_geom': 39025, 'baro_rate': 64, 'category': A3, 'emergency': NULL, 'flight': EZY83FE , 'geom_rate': 64, 'gva': 2, 'ias': 240, 'mach': 0.776, 'mag_heading': 101.78, 'nac_p': 9, 'nac_v': 1, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': 39008, 'nav_heading': 0.0, 'nav_modes': NULL, 'nav_qnh': 1013.6, 'nic': 8, 'nic_baro': 1, 'oat': NULL, 'rc': 186, 'roll': NULL, 'sda': 2, 'sil': 3, 'sil_type': perhour, 'spi': 0, 'squawk': 4633, 'tas': NULL, 'tat': NULL, 'track': 104.18, 'track_rate': NULL, 'true_heading': 104.21, 'type': adsb_icao, 'version': 2, 'wd': NULL, 'ws': NULL}, 'altitude': 39025, 'flags': 1, 'geometric_altitude': 39025, 'geometric_vertical_rate': 64, 'ground_speed': 485.8, 'h3_15': 8f1fa6a6ed849b6, 'indicated_airspeed': 240, 'lat': 50.625412, 'lon': 4.749635, 'on_ground': false, 'roll_angle': NULL, 'source': adsb_icao, 'timestamp': 2025-03-20 18:33:30, 'track_degrees': 104.2, 'vertical_rate': 64}                           β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 39025, 'flags': 1, 'geometric_altitude': 39025, 'geometric_vertical_rate': -32, 'ground_speed': 486.8, 'h3_15': 8f1fa0d335a6905, 'indicated_airspeed': 239, 'lat': 50.602795, 'lon': 4.889361, 'on_ground': false, 'roll_angle': 0.0, 'source': adsb_icao, 'timestamp': 2025-03-20 18:34:10.800000, 'track_degrees': 104.2, 'vertical_rate': 0}     β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 39025, 'flags': 0, 'geometric_altitude': 39025, 'geometric_vertical_rate': -32, 'ground_speed': 486.8, 'h3_15': 8f1fa0d33436702, 'indicated_airspeed': 239, 'lat': 50.602249, 'lon': 4.892964, 'on_ground': false, 'roll_angle': 0.0, 'source': adsb_icao, 'timestamp': 2025-03-20 18:34:11.900000, 'track_degrees': 104.2, 'vertical_rate': 0}     β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 39025, 'flags': 0, 'geometric_altitude': 39025, 'geometric_vertical_rate': 0, 'ground_speed': 486.8, 'h3_15': 8f1fa0d3341d233, 'indicated_airspeed': 240, 'lat': 50.6017, 'lon': 4.896361, 'on_ground': false, 'roll_angle': -0.3, 'source': adsb_icao, 'timestamp': 2025-03-20 18:34:12.990000, 'track_degrees': 104.2, 'vertical_rate': 0}        β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': 0, 'alt_geom': 39025, 'baro_rate': 0, 'category': A3, 'emergency': NULL, 'flight': EZY83FE , 'geom_rate': 0, 'gva': NULL, 'ias': 240, 'mach': 0.776, 'mag_heading': 102.3, 'nac_p': 9, 'nac_v': 1, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': 39008, 'nav_heading': 0.0, 'nav_modes': NULL, 'nav_qnh': 1013.6, 'nic': 8, 'nic_baro': 1, 'oat': NULL, 'rc': 186, 'roll': -0.18, 'sda': NULL, 'sil': 3, 'sil_type': perhour, 'spi': 0, 'squawk': 4633, 'tas': 436, 'tat': NULL, 'track': 104.15, 'track_rate': -0.03, 'true_heading': 104.77, 'type': adsb_icao, 'version': 2, 'wd': 280, 'ws': 53}, 'altitude': 39025, 'flags': 0, 'geometric_altitude': 39025, 'geometric_vertical_rate': 0, 'ground_speed': 486.8, 'h3_15': 8f1fa0d334e908c, 'indicated_airspeed': 240, 'lat': 50.601119, 'lon': 4.899977, 'on_ground': false, 'roll_angle': -0.2, 'source': adsb_icao, 'timestamp': 2025-03-20 18:34:13.960000, 'track_degrees': 104.2, 'vertical_rate': 0}                     β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 39025, 'flags': 0, 'geometric_altitude': 39025, 'geometric_vertical_rate': -32, 'ground_speed': 487.0, 'h3_15': 8f1fa0d15ab275e, 'indicated_airspeed': 240, 'lat': 50.596619, 'lon': 4.927874, 'on_ground': false, 'roll_angle': -0.7, 'source': adsb_icao, 'timestamp': 2025-03-20 18:34:22.160000, 'track_degrees': 104.3, 'vertical_rate': 0}    β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 39025, 'flags': 0, 'geometric_altitude': 39025, 'geometric_vertical_rate': 0, 'ground_speed': 487.0, 'h3_15': 8f1fa0d15376393, 'indicated_airspeed': 240, 'lat': 50.595486, 'lon': 4.93494, 'on_ground': false, 'roll_angle': -0.7, 'source': adsb_icao, 'timestamp': 2025-03-20 18:34:24.120000, 'track_degrees': 104.3, 'vertical_rate': 0}       β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 38975, 'flags': 1, 'geometric_altitude': NULL, 'geometric_vertical_rate': NULL, 'ground_speed': NULL, 'h3_15': 8f1fa01a5d44050, 'indicated_airspeed': NULL, 'lat': 50.511133, 'lon': 5.446918, 'on_ground': false, 'roll_angle': NULL, 'source': adsb_icao, 'timestamp': 2025-03-20 18:36:54.970000, 'track_degrees': NULL, 'vertical_rate': NULL}  β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': 0, 'alt_geom': NULL, 'baro_rate': 256, 'category': A3, 'emergency': NULL, 'flight': NULL, 'geom_rate': 0, 'gva': NULL, 'ias': 238, 'mach': 0.768, 'mag_heading': 102.83, 'nac_p': 8, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': 39008, 'nav_heading': 0.0, 'nav_modes': NULL, 'nav_qnh': 1013.6, 'nic': 8, 'nic_baro': 1, 'oat': NULL, 'rc': 186, 'roll': NULL, 'sda': NULL, 'sil': 2, 'sil_type': perhour, 'spi': 0, 'squawk': 4633, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': 105.5, 'type': adsb_icao, 'version': 0, 'wd': NULL, 'ws': NULL}, 'altitude': 39000, 'flags': 0, 'geometric_altitude': NULL, 'geometric_vertical_rate': 0, 'ground_speed': NULL, 'h3_15': 8f1fa01a5122270, 'indicated_airspeed': 238, 'lat': 50.509918, 'lon': 5.454206, 'on_ground': false, 'roll_angle': NULL, 'source': adsb_icao, 'timestamp': 2025-03-20 18:36:57.250000, 'track_degrees': NULL, 'vertical_rate': 256}                      β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ AIRBUS A-320NEO β”‚ 407568  β”‚ NULL    β”‚ G-UZHI  β”‚ {'description': general, 'iso2': GB, 'iso3': GBR, 'nation': United Kingdom}        β”‚ A20N    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 39000, 'flags': 0, 'geometric_altitude': NULL, 'geometric_vertical_rate': 0, 'ground_speed': NULL, 'h3_15': 8f1fa01a5154199, 'indicated_airspeed': 238, 'lat': 50.509318, 'lon': 5.457681, 'on_ground': false, 'roll_angle': NULL, 'source': adsb_icao, 'timestamp': 2025-03-20 18:36:58.090000, 'track_degrees': NULL, 'vertical_rate': 256}       β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       Β· β”‚     Β·           β”‚   Β·     β”‚  Β·      β”‚   Β·     β”‚                                      Β·                                             β”‚  Β·      β”‚          Β·          β”‚                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   Β·                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         β”‚    Β·  β”‚ Β·       β”‚ Β·       β”‚
β”‚       Β· β”‚     Β·           β”‚   Β·     β”‚  Β·      β”‚   Β·     β”‚                                      Β·                                             β”‚  Β·      β”‚          Β·          β”‚                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   Β·                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         β”‚    Β·  β”‚ Β·       β”‚ Β·       β”‚
β”‚       Β· β”‚     Β·           β”‚   Β·     β”‚  Β·      β”‚   Β·     β”‚                                      Β·                                             β”‚  Β·      β”‚          Β·          β”‚                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   Β·                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         β”‚    Β·  β”‚ Β·       β”‚ Β·       β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 429.8, 'h3_15': 8f1f2472daa542b, 'indicated_airspeed': 218, 'lat': 57.312744, 'lon': 9.889669, 'on_ground': false, 'roll_angle': -1.9, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:44.780000, 'track_degrees': 187.6, 'vertical_rate': 0}   β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 429.8, 'h3_15': 8f1f2472da86b9b, 'indicated_airspeed': 218, 'lat': 57.311577, 'lon': 9.889379, 'on_ground': false, 'roll_angle': -1.9, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:45.330000, 'track_degrees': 187.6, 'vertical_rate': 64}  β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 429.7, 'h3_15': 8f1f2472d068980, 'indicated_airspeed': 218, 'lat': 57.309994, 'lon': 9.888936, 'on_ground': false, 'roll_angle': -1.9, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:46.210000, 'track_degrees': 187.5, 'vertical_rate': 0}   β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': 0, 'alt_geom': 42475, 'baro_rate': 0, 'category': A2, 'emergency': none, 'flight': UNI132  , 'geom_rate': NULL, 'gva': 2, 'ias': 218, 'mach': 0.772, 'mag_heading': 187.73, 'nac_p': 10, 'nac_v': 1, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': 43008, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': 1013.6, 'nic': 8, 'nic_baro': 1, 'oat': NULL, 'rc': 186, 'roll': -2.11, 'sda': 2, 'sil': 3, 'sil_type': perhour, 'spi': 0, 'squawk': 7670, 'tas': 444, 'tat': NULL, 'track': 187.49, 'track_rate': -0.03, 'true_heading': 191.95, 'type': adsb_icao, 'version': 2, 'wd': 257, 'ws': 37}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 429.7, 'h3_15': 8f1f2472d05b9a1, 'indicated_airspeed': 218, 'lat': 57.308029, 'lon': 9.888468, 'on_ground': false, 'roll_angle': -2.1, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:47.140000, 'track_degrees': 187.5, 'vertical_rate': 0}                  β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 429.7, 'h3_15': 8f1f2472d765830, 'indicated_airspeed': 218, 'lat': 57.306038, 'lon': 9.88805, 'on_ground': false, 'roll_angle': -1.8, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:48.270000, 'track_degrees': 187.5, 'vertical_rate': 0}    β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 429.7, 'h3_15': 8f1f2472d7460d6, 'indicated_airspeed': 218, 'lat': 57.304501, 'lon': 9.887607, 'on_ground': false, 'roll_angle': -1.8, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:49.100000, 'track_degrees': 187.5, 'vertical_rate': 0}   β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 430.7, 'h3_15': 8f1f2472d60e923, 'indicated_airspeed': 217, 'lat': 57.302127, 'lon': 9.887075, 'on_ground': false, 'roll_angle': -1.9, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:50.250000, 'track_degrees': 187.5, 'vertical_rate': 0}   β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': 0, 'alt_geom': 42475, 'baro_rate': 0, 'category': A2, 'emergency': none, 'flight': UNI132  , 'geom_rate': NULL, 'gva': 2, 'ias': 217, 'mach': 0.772, 'mag_heading': 187.73, 'nac_p': 10, 'nac_v': 1, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': 43008, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': 1013.6, 'nic': 8, 'nic_baro': 1, 'oat': NULL, 'rc': 186, 'roll': -1.93, 'sda': 2, 'sil': 3, 'sil_type': perhour, 'spi': 0, 'squawk': 7670, 'tas': 442, 'tat': NULL, 'track': 187.47, 'track_rate': -0.03, 'true_heading': 191.95, 'type': adsb_icao, 'version': 2, 'wd': 261, 'ws': 36}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 430.7, 'h3_15': 8f1f2472d68986a, 'indicated_airspeed': 217, 'lat': 57.299753, 'lon': 9.886455, 'on_ground': false, 'roll_angle': -1.9, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:51.220000, 'track_degrees': 187.5, 'vertical_rate': 0}                  β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 430.7, 'h3_15': 8f1f24729d0686e, 'indicated_airspeed': 217, 'lat': 57.297775, 'lon': 9.885979, 'on_ground': false, 'roll_angle': -1.9, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:52.380000, 'track_degrees': 187.5, 'vertical_rate': 0}   β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”‚       0 β”‚ LEARJET 45      β”‚ 3cd568  β”‚ NULL    β”‚ D-CICU  β”‚ {'description': light 5.7 - 14 t MTOW, 'iso2': DE, 'iso3': DEU, 'nation': Germany} β”‚ LJ45    β”‚ 2025-03-20 00:00:00 β”‚ {'aircraft': {'alert': NULL, 'alt_geom': NULL, 'baro_rate': NULL, 'category': NULL, 'emergency': NULL, 'flight': NULL, 'geom_rate': NULL, 'gva': NULL, 'ias': NULL, 'mach': NULL, 'mag_heading': NULL, 'nac_p': NULL, 'nac_v': NULL, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': NULL, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': NULL, 'nic': NULL, 'nic_baro': NULL, 'oat': NULL, 'rc': NULL, 'roll': NULL, 'sda': NULL, 'sil': NULL, 'sil_type': NULL, 'spi': NULL, 'squawk': NULL, 'tas': NULL, 'tat': NULL, 'track': NULL, 'track_rate': NULL, 'true_heading': NULL, 'type': NULL, 'version': NULL, 'wd': NULL, 'ws': NULL}, 'altitude': 43000, 'flags': 0, 'geometric_altitude': 42475, 'geometric_vertical_rate': NULL, 'ground_speed': 430.7, 'h3_15': 8f1f24729da84ab, 'indicated_airspeed': 218, 'lat': 57.295843, 'lon': 9.885569, 'on_ground': false, 'roll_angle': -1.4, 'source': adsb_icao, 'timestamp': 2025-03-20 09:44:53.410000, 'track_degrees': 187.5, 'vertical_rate': -64} β”‚  2025 β”‚ 20      β”‚ 03      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ ? rows (>9999 rows, 20 shown)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               12 columns β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Antall fly per landΒΆ

La oss gjΓΈre en ΓΈvelse vi ogsΓ₯ har gjort tidligere i DuckDB: Vi teller antall fly per land.

Start med Γ₯ legge inn fΓΈlgende tekstcelle:

# Tell antall fly per land
sql = """

SELECT   reg_details.nation as land,
         COUNT(DISTINCT r) AS fly
FROM     FLIGHTS_20_03_2025
WHERE    LENGTH(reg_details.nation)
GROUP BY 1
ORDER BY 2 DESC

"""
con.sql(sql).show(max_rows=30)

Denne spΓΈrringen gir oss en oversikt over hvor mange ulike fly som er registrert i hvert land den dagen. Utvalget vises med maksimum 30 rader:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚         land         β”‚  fly  β”‚
β”‚       varchar        β”‚ int64 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Germany              β”‚   244 β”‚
β”‚ United Kingdom       β”‚   237 β”‚
β”‚ Netherlands          β”‚   227 β”‚
β”‚ Ireland              β”‚   152 β”‚
β”‚ Norway               β”‚   147 β”‚
β”‚ Malta                β”‚   113 β”‚
β”‚ Sweden               β”‚   106 β”‚
β”‚ United States        β”‚   100 β”‚
β”‚ Turkey               β”‚    96 β”‚
β”‚ Austria              β”‚    74 β”‚
β”‚ Poland               β”‚    64 β”‚
β”‚ France               β”‚    62 β”‚
β”‚ United Arab Emirates β”‚    56 β”‚
β”‚ Spain                β”‚    55 β”‚
β”‚ China                β”‚    51 β”‚
β”‚   Β·                  β”‚     Β· β”‚
β”‚   Β·                  β”‚     Β· β”‚
β”‚   Β·                  β”‚     Β· β”‚
β”‚ Australia            β”‚     3 β”‚
β”‚ Guernsey             β”‚     3 β”‚
β”‚ Isle of Man          β”‚     2 β”‚
β”‚ Taiwan               β”‚     2 β”‚
β”‚ Bahrain              β”‚     2 β”‚
β”‚ Georgia              β”‚     2 β”‚
β”‚ Bangladesh           β”‚     2 β”‚
β”‚ Kuwait               β”‚     2 β”‚
β”‚ Sri Lanka            β”‚     1 β”‚
β”‚ Uzbekistan           β”‚     1 β”‚
β”‚ Kenya                β”‚     1 β”‚
β”‚ Belarus              β”‚     1 β”‚
β”‚ Pakistan             β”‚     1 β”‚
β”‚ Azerbaijan           β”‚     1 β”‚
β”‚ Oman                 β”‚     1 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€
β”‚      65 rows (30 shown)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Resultatet er kjent, men mΓ₯ten vi kjΓΈrer spΓΈrringen pΓ₯ er litt ny. Vi starter med sql = """, og legger inn SQL-spΓΈrringen som en tekstblokk. PΓ₯ den mΓ₯ten oppretter vi en variabel som inneholder spΓΈrringen – noe som gjΓΈr det enkelt Γ₯ gjenbruke den senere, om vi ΓΈnsker. Her kjΓΈrer vi den bare Γ©n gang, ved Γ₯ sende variabelen inn i DuckDB-forbindelsen vΓ₯r med con.sql(sql). Vi ber samtidig om Γ₯ vise maksimalt 30 rader med show(max_rows=30).

Kanskje du, som meg, tenker at det minner litt vel mye om det vi gjorde direkte i DuckDB. Helt enig! NΓ₯ er det pΓ₯ tide Γ₯ ta visualiseringen ett hakk videre.

Visualiser fly per land i penere tabellΒΆ

Vi starter enkelt, med Γ₯ konvertere dataene til et format som er mer egnet for moderne nettleservisning. Dette gjΓΈr vi ved hjelp av Polars – et raskt og fleksibelt Python-verktΓΈy for databehandling.

# Visualiser fly per land i penere tabell
import polars as pl

pl.Config(set_tbl_rows=100)
flyperland = con.sql(sql).pl()
flyperland
landfly
β€œGermany”244
β€œUnited Kingdom”237
β€œNetherlands”227
β€œIreland”152
β€œNorway”147
β€œMalta”113
β€œSweden”106
β€œUnited States”100
β€œTurkey”96
β€œAustria”74
β€œPoland”64
β€œFrance”62
β€œUnited Arab Emirates”56
β€œSpain”55
β€œChina”51
β€œFinland”45
β€œDenmark”44
β€œPortugal”43
β€œQatar”37
β€œBelgium”34
β€œHungary”30
β€œRussia”23
β€œLatvia”22
β€œLiechtenstein”18
β€œCanada”15
β€œIndia”14
β€œIceland”13
β€œSingapore”11
β€œGreece”12
β€œEgypt”9
β€œJapan”9
β€œSaudi Arabia”8
β€œIsrael”7
β€œBulgaria”7
β€œCzech Republic”6
β€œSan Marino”6
β€œSerbia”6
β€œCroatia”6
β€œRomania”6
β€œJordan”5
β€œSlovakia”5
β€œItaly”5
β€œEthiopia”5
β€œEstonia”4
β€œThailand”4
β€œUkraine”4
β€œSouth Korea”4
β€œAustralia”3
β€œZimbabwe”3
β€œMorocco”3
β€œMalaysia”3
β€œGuernsey”3
β€œGeorgia”2
β€œTaiwan”2
β€œBahrain”2
β€œIsle of Man”2
β€œKuwait”2
β€œBangladesh”2
β€œOman”1
β€œAzerbaijan”1
β€œPakistan”1
β€œBelarus”1
β€œSri Lanka”1
β€œKenya”1
β€œUzbekistan”1

Her skjer det tre ting:

  1. Vi importerer Polars.

  2. Vi setter opp konfigurasjonen slik at tabellen kan vise opptil 100 rader.

  3. Vi bruker .pl() til Γ₯ hente resultatet fra DuckDB-spΓΈrringen som en Polars-tabell, og viser den ved Γ₯ referere til variabelen flyperland.

Resultatet er en langt mer leservennlig tabell enn den rΓ₯ tekstutskriften fra DuckDB. Du fΓ₯r oversikt over alle land, sortert etter antall fly – og du kan enkelt bruke dataene videre til grafer, kart og annen visualisering.

Lagre tabellen til CSVΒΆ

En av fordelene med Polars er hvor enkelt det er Γ₯ eksportere data til ulike formater. NΓ₯r du har hentet ut et datasett du vil ta vare pΓ₯ eller bruke videre, er det for eksempel veldig lett Γ₯ lagre det som en CSV-fil:

Legg til en tekstcelle:

# Lagre til CSV

Og deretter denne kodecellen:

flyperland.write_csv("flyperland.csv")

Hvor finner jeg filen?ΒΆ

Et godt spΓΈrsmΓ₯l! Til venstre i Google Colab-vinduet finner du en vertikal meny. Klikk pΓ₯ mappesymbolet nederst for Γ₯ Γ₯pne filvisningen. Om du ikke ser filen med en gang, kan du klikke pΓ₯ last inn pΓ₯ nytt-knappen (de to sirklene som danner en pil) for Γ₯ oppdatere visningen.

Figur 4: Filoversikten i Google Colab – klikk pΓ₯ mappen nederst, og last inn pΓ₯ nytt om nΓΈdvendig.

StolpediagramΒΆ

Nok tall og tabeller – nΓ₯ er det pΓ₯ tide med litt visuell magi! Og da passer det perfekt Γ₯ introdusere et nytt verktΓΈy pΓ₯ scenen: Plotly. Dette visualiseringsbiblioteket lar deg lage stilrene og interaktive grafer med minimale mengder kode.

La oss starte med Γ₯ lage et stolpediagram over fly per land:

# Visualiser som stolpediagram
import plotly.express as px

stolpe = px.bar(flyperland, x='land', y='fly', title="Fly per land")
stolpe

Figur 5: Fly per land visualisert som stolpediagram.

SΓ₯ enkelt – og sΓ₯ fint! Diagrammet er interaktivt, og du kan blant annet:

Vil du lagre hele grafen med all interaktiviteten intakt? Plotly har tenkt pΓ₯ det ogsΓ₯. Du kan enkelt lagre visualiseringen som en HTML-fil:

# Lagre til HTML
stolpe.write_html("stolpe.html")

Filen dukker opp i den samme filutforskeren som vi brukte da vi lagret CSV. Klikk pΓ₯ mappesymbolet til venstre, og husk Γ₯ trykke last inn pΓ₯ nytt hvis du ikke ser filen med Γ©n gang.

KakediagramΒΆ

Siden flyobservasjonene vΓ₯re utgjΓΈr en absolutt mengde, egner de seg ogsΓ₯ godt for visualisering som kakediagram. Det krever bare en liten endring fra stolpediagrammet, og vips – dataene blir til kake:

# Visualiser som kakediagram
kake = px.pie(flyperland, names='land', values='fly', title="Fly per land")
kake

Figur 6: Fly per land visualisert som (et litt sprengt) kakediagram.

Tada! Kakediagram, rett ut av ovnen. Men – dette ser kanskje litt sprengt ut?

NΓ₯r vi presser inn over 60 land med ulikt antall fly i Γ©n og samme sirkel, blir resultatet bΓ₯de visuelt rotete og vanskelig Γ₯ lese. Mange land har bare noen fΓ₯ fly, og det gir lite mening Γ₯ gi dem hver sin tynne kakebit.

Begrens detaljnivΓ₯et – grupper smΓ₯ landΒΆ

LΓΈsningen? Vi samler alle land med fΓ¦rre enn 100 observerte fly i en felles gruppe: Other countries. Det gjΓΈr vi ved Γ₯ legge inn litt enkel logikk i SQL-spΓΈrringen:

# Kakediagram - grupper smΓ₯ land
sql = """
WITH flight_counts AS (
    SELECT
        reg_details.nation as land,
        COUNT(DISTINCT r) AS fly
    FROM
        FLIGHTS_20_03_2025
    WHERE
        LENGTH(reg_details.nation) > 0
    GROUP BY
        reg_details.nation
)

SELECT
    CASE
        WHEN fly < 100 THEN 'Other countries'
        ELSE land
    END AS land,
    CAST(SUM(fly) AS BIGINT) AS fly
FROM
    flight_counts
GROUP BY
    1
ORDER BY
    2 DESC;

"""

flyperland = con.sql(sql).pl()

kake = px.pie(flyperland, names='land', values='fly', title="Fly per land")
kake

Her har vi bakt litt enkel programmeringslogikk inn i spΓΈrringen. Kort fortalt finner vi land som har under 100 observerte fly den aktuelle tidsperioden, og legger disse til i landet Other countries. Da ser vi umiddelbart at kakediagrammet bΓ₯de blir penere og mer lesbart.

Figur 7: Fly per land visualisert som (et litt penere) kakediagram.

Ved hjelp av CASE WHEN-logikken over slΓ₯r vi sammen alle smΓ₯nasjonene i Γ©n kategori. Resultatet? Et diagram som bΓ₯de ser bedre ut og er langt lettere Γ₯ lese.

WiderΓΈe-flyet som nΓΈdlandet i BergenΒΆ

Tirsdag 11. mars nΓΈdlandet et WiderΓΈe-fly i Bergen. Hendelsen var heldigvis udramatisk, men siden den skjedde like fΓΈr vΓ₯r andre kursΓΈkt, passet det godt Γ₯ bruke den som et ekte case. NΓ₯ skal vi jobbe oss gjennom hvordan man kan hente ut, analysere og visualisere flydata knyttet til en slik hendelse – pΓ₯ mΓ₯ter som kunne vΓ¦rt relevante i lokal nyhetsdekning.

  1. Vi starter med Γ₯ hente ut alle flydata for 11. mars 2025:
# Hent ut alle data fra 11. mars 2025
sql = """
CREATE TABLE flights_11_03_25 AS
      SELECT * FROM read_parquet('r2://medieklyngen-radar-data/adsb/history/*/*/*/*.parquet', hive_partitioning = true)
      WHERE year = 2025 AND month = 03 AND day = 11;
"""
con.sql(sql)
  1. Deretter filtrer vi datasettet pΓ₯ flyet LN-WDP i aktuell tidsperiode:
# Filtrer pΓ₯ LN-WDP i aktuell tidsperiode
sql = """
  SELECT r, trace.lon, trace.lat, trace.altitude, trace.ground_speed, trace.timestamp
  FROM flights_11_03_25
  WHERE r LIKE 'LN-WDP' AND trace.timestamp > '2025-03-11 07:00:23.710000' AND trace.timestamp < '2025-03-11 10:00:23.710000' AND trace.lat IS NOT NULL and trace.lon IS NOT NULL
  ORDER BY trace.timestamp;
"""
nodlanding = con.sql(sql).pl()
pl.Config(set_tbl_rows=10)
nodlanding
rlonlataltitudeground_speedtimestamp
β€œLN-WDP”5.2233660.287464null20.5β€œ2025-03-11 07:29:53.870000”
β€œLN-WDP”5.22345460.287189null19.5β€œ2025-03-11 07:29:56.830000”
β€œLN-WDP”5.22363160.286649null16.5β€œ2025-03-11 07:30:03.480000”
β€œLN-WDP”5.22385760.285976null16.5β€œ2025-03-11 07:30:12.620000”
β€œLN-WDP”5.22385260.285939null16.5β€œ2025-03-11 07:30:13.160000”
………………
β€œLN-WDP”5.22191560.282074500124.6β€œ2025-03-11 08:20:59.770000”
β€œLN-WDP”5.22172660.282623475124.6β€œ2025-03-11 08:21:00.850000”
β€œLN-WDP”5.22134760.283768450124.5β€œ2025-03-11 08:21:02.720000”
β€œLN-WDP”5.22125260.284077450124.5β€œ2025-03-11 08:21:03.480000”
β€œLN-WDP”5.22115460.28445450124.5β€œ2025-03-11 08:21:03.910000”

Resultatet viser alle datapunktene vi har om denne flyvningen, filtrert pΓ₯ kjennetegn (r) og tidsintervall. Ved Γ₯ bruke pl.Config(set_tbl_rows=10) fΓ₯r vi automatisk vist de fΓΈrste og siste 5 radene i tabellen – en praktisk mΓ₯te Γ₯ fΓ₯ et raskt inntrykk av datasettet pΓ₯.

HΓΈyde og fart over tidΒΆ

La oss se nΓ¦rmere pΓ₯ hvordan flyet beveget seg gjennom luftrommet, bΓ₯de i hΓΈyde og fart:

# Flyets hΓΈyde over tid
linje = px.line(nodlanding, x='timestamp', y='altitude', title="Flyets hΓΈyde over tid")
linje

Figur 8: Flyets hΓΈyde over tid.

# Flyets fart over tid
linje = px.line(nodlanding, x='timestamp', y='ground_speed', title="Flyets fart over tid")
linje

Figur 9: Flyets fart over tid.

Enkelt, oversiktlig og nyttig. Her fΓ₯r vi raskt et bilde av flyets bevegelser og endringer underveis. Og som vi ser – til tross for nΓΈdlandingen, ser dataene ganske udramatiske ut, akkurat som rapportert.

Visualisering pΓ₯ kartΒΆ

Grafene gir oss innsikt, men i hendelsesjournalistikk er det ofte kartet som virkelig skaper forstΓ₯else. Heldigvis kan vi enkelt plassere flyets rute direkte pΓ₯ et interaktivt kart med Plotly:

# Plott WiderΓΈe-flighten pΓ₯ et kart
kart = px.line_map(nodlanding, lat='lat', lon='lon', hover_data=['timestamp', 'altitude', 'ground_speed'])
kart.update_layout(height=540, width=960, map_zoom=6, map_center_lat = 59.5, map_center_lon = 6)
kart

Figur 10: WiderΓΈe-flyets rute plottet pΓ₯ kart.

Og der har vi det! Flyets ferd fra Bergen, via Rogaland – og tilbake til Bergen.

Selv om det krever litt mer kode enn de tidligere grafene, er det fortsatt overkommelig. Her bruker vi line_map for Γ₯ tegne ruten basert pΓ₯ geografiske koordinater. Ved Γ₯ legge til hover_data fΓ₯r vi i tillegg opp info om tidspunkt, hΓΈyde og fart nΓ₯r vi beveger musepekeren over ruten.

NΓ₯r du er fornΓΈyd med visualiseringen, kan du eksportere kartet som en PNG eller lagre det som HTML med full interaktivitet – perfekt for bruk i en nyhetsartikkel pΓ₯ nett.

Heatmaps med LonboardΒΆ

Vi var sΓ₯ vidt innom heatmaps da vi jobbet med sanntidsdata tidligere i kurset. Som du kanskje husker, er dette en utmerket mΓ₯te Γ₯ kartlegge flyaktivitet pΓ₯: Hvor er det mest trafikk, og hvordan fordeler bevegelsene seg i rom og tid?

Visualisering av slike mΓΈnstre krever ofte store datamengder – og da kommer Lonboard til sin rett. VerktΓΈyet er spesiallaget for Γ₯ hΓ₯ndtere store geografiske datasett, og genererer heatmaps pΓ₯ en bΓ₯de rask og imponerende mΓ₯te.

La oss bruke Lonboard til Γ₯ lage et heatmap av all tilgjengelig flytrafikk lΓΈrdag 8. mars 2025.

  1. Last inn data fra 8. mars:
# Last inn data fra 8. mars
sql = """
CREATE TABLE flights_08_03_2025 AS
      SELECT * FROM read_parquet('r2://medieklyngen-radar-data/adsb/history/*/*/*/*.parquet', hive_partitioning = true)
      WHERE year = 2025 AND month = 03 AND day = 08;
"""
con.sql(sql)
  1. GjΓΈr dataene klare for Lonboard. Vi lager en spΓΈrring som henter ut tidspunkt, posisjon og registreringsnummer, og gjΓΈr posisjonen om til et geometriobjekt (ST_POINT):
sql = """
SELECT trace.timestamp as Time, ST_POINT(trace.lon, trace.lat) as geom, r as Reg from flights_08_03_2025
GROUP BY ALL;
"""
query = con.sql(sql)
  1. NΓ₯ er alt klart! Vi bruker Lonboards HeatmapLayer og tegner det pΓ₯ et interaktivt kart:
from lonboard import Map, HeatmapLayer, PathLayer
layer = HeatmapLayer.from_duckdb(query, con)
m = Map(layer)
m

Og voilΓ  – du fΓ₯r opp et kart som viser hvor det var mest aktivitet i luftrommet denne dagen. De varme, rΓΈde omrΓ₯dene indikerer hΓΈy konsentrasjon av datapunkter, mens kjΓΈligere omrΓ₯der har mindre trafikk.

Figur 11: Heatmap av flytrafikk 8. mars 2025, generert med Lonboard.

Politihelikopternes bevegelserΒΆ

Det kan vΓ¦re nyttig Γ₯ se all flytrafikk pΓ₯ ett kart – men det er enda mer spennende nΓ₯r vi snevrer det inn. La oss undersΓΈke politihelikopternes bevegelser 8. mars 2025.

  1. Finn alle observasjoner av politihelikoptre:
# Observasjoner av politihelikoptre 8. mars 2025
sql = """
SELECT trace.timestamp as Time, ST_POINT(trace.lon, trace.lat) as geom, r as Reg from flights_08_03_2025
WHERE (r = 'LN-ORA' OR r = 'LN-ORB' or r = 'LN-ORC') GROUP BY ALL;
"""
query = con.sql(sql)
from lonboard import Map, HeatmapLayer, PathLayer
layer = HeatmapLayer.from_duckdb(query, con)
m = Map(layer)
m

Figur 12: Heatmap av politihelikoptre 8. mars 2025, generert med Lonboard.

Se der, ja! Vi ser tydelig en konsentrasjon av aktivitet i Oslo-omrΓ₯det. Men hvilket helikopter var i lufta – og nΓ₯r?

  1. Oversikt over aktivitet per time:
# Se hvilket politihelikopter som var i luften nΓ₯r
sql = """
SELECT r,
  EXTRACT(HOUR FROM CAST(trace.timestamp as timestamp)) AS hour_of_day,
  COUNT(*) AS num_points
FROM flights_08_03_2025 WHERE (r = 'LN-ORA' OR r = 'LN-ORB' or r = 'LN-ORC')
GROUP BY r, hour_of_day
ORDER BY hour_of_day;
"""
query = con.sql(sql)
query.pl()
rhour_of_daynum_points
β€œLN-ORC”7555
β€œLN-ORC”12417
β€œLN-ORA”141969
β€œLN-ORA”151316
β€œLN-ORA”19502
β€œLN-ORA”203520
β€œLN-ORA”21536

Her ser vi at LN-ORA og LN-ORC var aktive i lΓΈpet av dagen, mens LN-ORB enten var pΓ₯ bakken, utenfor dekning – eller ikke hadde transponderen aktivert.

  1. Visualiser bevegelsene til LN-ORA
# LN-ORA 8. mars 2025
sql = """
SELECT r,
  EXTRACT(HOUR FROM CAST(trace.timestamp as timestamp)) AS hour_of_day,
  COUNT(*) AS num_points, ST_POINT(trace.lon, trace.lat) as geom, trace.timestamp
FROM flights_08_03_2025 WHERE r = 'LN-ORA'
GROUP BY ALL
ORDER BY hour_of_day;
"""
query = con.sql(sql)
query.pl()
from lonboard import viz
viz(query, con=con)

Figur 13: Bevegelser for politihelikopteret LN-ORA, visualisert med Lonboard.

NΓ₯ ser vi alle registrerte bevegelser for LN-ORA denne dagen. Hovrer du med musepekeren over kartet, fΓ₯r du opp tidspunkter – og du kunne lett lagt til hΓΈyde, fart og andre datapunkter om ΓΈnskelig.

Visualisere et geografisk omrΓ₯deΒΆ

Tidligere i kurset har vi sett hvordan vi kan filtrere flydata basert pΓ₯ geografiske omrΓ₯der. NΓ₯ henter vi frem igjen H3-systemet, og bruker den til Γ₯ lage en visualisering av flytrafikken i en gitt sektor.

Ved Γ₯ velge en H3-sektor med opplΓΈsning 2 midt i SΓΈr-Norge, kan vi hente ut all flytrafikk i dette omrΓ₯det 8. mars 2025.

# Visualisere et geografisk omrΓ₯de
sql = """
SELECT r,
  EXTRACT(HOUR FROM CAST(trace.timestamp as timestamp)) AS hour_of_day,
  COUNT(*) AS num_points, ST_POINT(trace.lon, trace.lat) as geom, trace.timestamp
FROM flights_08_03_2025 WHERE h3_cell_to_parent(trace.h3_15, 2) = '82098ffffffffff'
GROUP BY ALL
ORDER BY hour_of_day;
"""
query = con.sql(sql)
query.pl()

Etter en kjapp runde med Polars fΓ₯r vi en oversikt over aktivitet i den valgte sektoren.

rhour_of_daynum_pointsbinary (truncated)timestamp
β€œB-304K”41b"\x00\x00...\x82o:N@"β€œ2025-03-08 04:17:45.440000”
β€œB-304K”41b"\x00\x00...\x95}W:N@"β€œ2025-03-08 04:17:46.250000”
β€œB-304K”41b"\x00\x00...\xee\xadH:N@"β€œ2025-03-08 04:17:46.800000”
β€œB-304K”41b"\x00\x00...\xe2\x03;:N@"β€œ2025-03-08 04:17:47.400000”
β€œB-304K”41b"\x00\x00...\xe6\x01,:N@"β€œ2025-03-08 04:17:48.110000”
……………
β€œLN-DYT”231b"\x00\x00...\xc4\xcc$O@"β€œ2025-03-08 23:19:17.280000”
β€œLN-DYT”231b"\x00\x00...\xb3!\xff$O@"β€œ2025-03-08 23:19:18.210000”
β€œLN-DYT”231b"\x00\x00...\xd3\x82\x17%O@"β€œ2025-03-08 23:19:18.710000”
β€œLN-DYT”231b"\x00\x00...\xe0(y%O@"β€œ2025-03-08 23:19:20.830000”
β€œLN-DYT”231b"\x00\x00...\xbfD\xbc%O@"β€œ2025-03-08 23:19:22.290000”

Heatmap over all trafikk i en gitt sektorΒΆ

Vi fortsetter med Γ₯ lage en Lonboard-heatmap av sektoren vΓ₯r:

# Lage heatmap av geografisk omrΓ₯de
layer = HeatmapLayer.from_duckdb(query, con)
m = Map(layer)
m

Figur 14: Heatmap av flyaktivitet i en H3-sektor.

Interessant! Vi kan nesten ane heksagonens konturer. Men vi gjΓΈr det enda tydeligere:

Viz-kart over all trafikk i en gitt sektorΒΆ

# Lage Viz-kart av geografisk omrΓ₯de
viz(query, con=con)

Figur 15: Viz-kart over geografisk omrΓ₯de.

Enkle linjer – og nΓ₯ ser vi tydelig H3-heksagonens form. Og som alltid med Viz-kart: du kan hovre med musepekeren for Γ₯ se detaljer som fly-ID og tidspunkter.

Visualisere all militær trafikk¢

NΓ₯ gjΓΈr vi en liten justering – og bruker samme metode for Γ₯ vise all militΓ¦rtrafikk i datasettet. Alt vi trenger er et filter pΓ₯ dbFlags = 8.

# Visualisere all militær trafikk
sql = """
SELECT r,
  EXTRACT(HOUR FROM CAST(trace.timestamp as timestamp)) AS hour_of_day,
  COUNT(*) AS num_points, ST_POINT(trace.lon, trace.lat) as geom, trace.timestamp
FROM flights_08_03_2025 WHERE dbFlags = 8
GROUP BY ALL
ORDER BY hour_of_day;
"""
query = con.sql(sql)
query.pl()
rhour_of_daynum_pointsgeom (truncated)timestamp
β€œN609GB”31b"\x00\x00...\xff\xe8\xcdH@"β€œ2025-03-08 03:45:40.800000”
β€œN609GB”31b"\x00\x00...\x30D\xceH@"
β€œN609GB”31b"\x00\x00...\xec-\xe5\xd0H@"β€œ2025-03-08 03:45:58.170000”
β€œN609GB”31b"\x00\x00...\xd5\x01\x10\xd1H@"β€œ2025-03-08 03:45:59.260000”
β€œN609GB”31b"\x00\x00...\xa0g\xb3\xd2H@"β€œ2025-03-08 03:46:09.070000”
……………
β€œVQ-BXG”191b"\x00\x00...\xb2\xb8\xff\x02J@"β€œ2025-03-08 19:09:23.110000”
β€œVQ-BXG”191b"\x00\x00...\x81\x02\xef\x02J@"β€œ2025-03-08 19:09:24.800000”
β€œVQ-BXG”191b"\x00\x00...\xc3*\xde\x02J@"β€œ2025-03-08 19:09:26.830000”
β€œVQ-BXG”191b"\x00\x00...\x3c\xc1\x02J@"β€œ2025-03-08 19:09:30.110000”
β€œVQ-BXG”191b"\x00\x00...\xeb\xfbp\x02J@"β€œ2025-03-08 19:09:39.060000”

Og jammen dukker det ikke opp en rekke kjente (og mindre kjente) militærfartøy!

Lage heatmap av all militær trafikk¢

# Lage heatmap av all militær trafikk
layer = HeatmapLayer.from_duckdb(query, con)
m = Map(layer)
m

Figur 16: Heatmap av all militær trafikk 8. mars 2025.

Her var det litt Γ₯ boltre seg i. La oss visualisere de samme dataene pΓ₯ et Viz-kart:

Viz-kart med detaljert infoΒΆ

# Lage Viz-kart av all militær trafikk
from lonboard import viz
viz(query, con=con)

Figur 17: Viz-kart over militær trafikk.

Her fΓ₯r vi den fulle oversikten – og du kan enkelt undersΓΈke hvilke fartΓΈy som var i lufta, hvor og nΓ₯r.

OppgaverΒΆ

NΓ₯ har du fΓ₯tt en grunnleggende innfΓΈring i bruk av notebooks, DuckDB, Polars, Plotly og Lonboard. Tid for litt praktisk trening: