Skip to article frontmatterSkip to article content
Kom i gang med analyse av flytrafikkdata

Visualisering i notebooks 🗺️

Medieklyngen

Visualisering i notebooks

Med de grunnleggende ferdighetene på plass, skal vi nå fortsette med å utforske dataene våre gjennom visualisering. Til dette skal vi bruke notebooks, enten i Google Colab eller Jupyter Notebook. I kombinasjon med DuckDB og verktøyet Lonboard har vi en kraftig kombinasjon av verktøy å eksperimentere med. Spenn fast beltet, så tar vi av!

Hva er notebooks?

En notebook er et interaktivt verktøy hvor du kan skrive og kjøre kode direkte i nettleseren. Det er som en kombinasjon av en tekstbehandler og en kode-editor, hvor du kan skrive forklarende tekst, legge til bilder og utføre koder samtidig. Dette gjør det veldig praktisk for datavitenskap, maskinlæring, undervisning, eksperimentering og selvfølgelig datajournalistikk.

Hovedfunksjonene til en notebook er:

  1. Kjøre kode i små deler (celler): Du kan kjøre koden bit for bit i stedet for hele programmet på én gang, noe som gjør det enklere å teste og feilsøke.

  2. Tekst og kode sammen: Du kan skrive forklarende tekst i tillegg til koden, slik at det er lett å forstå hva som skjer.

  3. Visualiseringer: Du kan lage grafer og diagrammer direkte i notatboken for å visualisere resultater.

  4. Dokumentasjon: Du får samtidig dokumentert arbeidsprosessen din, slik at du enkelt kan replikere resultatene fra grunnen av senere.

I de kommende øvelsene skal vi benytte notebooks, DuckDB, enkel Python-kode og visualiseringsverktøyet Lonboard. Her er det flere nye verktøy i bruk, men vi vil holde øvelsen på et relativt enkelt nivå. Selv om du ikke har programmert før, frykt ikke – vi vil bruke kommandoer som er gjenkjennelige fra DuckDB.

De første stegene

La oss begynne med å opprette en tom notebook. Den ser slik ut:

Figur 1: Tom notebook.

Deretter begynner vi å legge inn celler, som finnes i to typer: tekst og kode. Tekstcellene benyttes til å dokumentere det du gjør, mens kodecellene kjører koden. De første linjene vi legger inn, er et par såkalte avhengigheter (i tillegg fjerner vi en modul som kan skape problemer):

!pip uninstall malloy --y
!pip install lonboard==0.9.3 duckdb==1.0.0

(Hvis du vil, kan du legge en tekstcelle over denne kodecellen, for eksempel med tittelen Installer avhengigheter.)

Trykk på play-knappen til venstre i cellen for å kjøre koden.

Deretter laster vi inn DuckDB i en ny celle. Du vil kanskje kjenne igjen noen av kommandoene, selv om de har en litt annen syntaks her:

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;')

Trykk play.

Så legger vi inn nøkler til databasen i en ny celle:

con.sql("CREATE SECRET (TYPE R2, KEY_ID '9030e0f90a86af08b08b6e2a1222a778', SECRET '2fe64ae1c22869400f577bb9421602f0f81a83a2f658cea6bdd556f4fc65064b', ACCOUNT_ID 'bca3475a0f4afeb0640daafc17ec2b18');")

Last inn data

Vi fortsetter med å hente et datasett, denne gang for dagene 1., 2. og 3. september 2024. Legg inn følgende kommando i en ny kode-celle:

con.sql("CREATE OR REPLACE TABLE FLIGHTS_09_01_02_2024 AS SELECT * FROM read_parquet('r2://medieklyngen-radar-data/adsb/history/*/*/*/*.parquet', hive_partitioning = true) WHERE year = 2024 AND month = 09 AND (DAY = 01 OR DAY = 02 OR DAY = 03);")

Dette vil ta litt tid. Når fremdriftsindikatoren er ferdig, kan du kjøre neste celle for å ta en titt på dataene:

con.sql("DESCRIBE FLIGHTS_09_01_02_2024")

Dette vil se slik ut: Figur 2: En titt på datastrukturen.

Dette er nyttig, men vi kan gå enda mer i dybden:

con.sql("SELECT * FROM FLIGHTS_09_01_02_2024").show(max_width=2500)

Nå begynner det å bli spennende: Figur 3: En titt på dataene.

Til slutt, la oss telle hvor mange datapunkter vi har å jobbe med i dette settet:

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

Fant du svaret? Det skal ligge et lite stykke over antallet innbyggere i Norge.

Heatmap-visualisering

Så langt har vi repetert mye av det samme som vi gjorde i DuckDB direkte på kommandolinjen. Notebooks-metoden gir oss imidlertid spennende muligheter når det kommer til visualisering. La oss begynne med å lage et heatmap, og hva passer vel bedre enn å kombinere dette med politihelikopterjakt?

Norge har i dag tre politihelikoptre, med kjennemerkene LN-ORA, LN-ORB og LN-ORC. De opererer under kallesignalene HEP01, HEP02 og HEP03. Alle helikoptrene er stasjonert utenfor Oslo, men flyr over hele landet ved behov.

Når vi skal prøve å finne dem i datasettet, kan det være lurt å begynne i Oslo-området, der sjansen er størst for å finne dem.

Politihelikopternes bevegelser i Oslo

Vi begynner med å finne H3-sektoren til Oslo i passende oppløsning her: https://wolf-h3-viewer.glitch.me/

Ved zoom-nivå 3 ser vi at heksagonens ID er 830999fffffffff.

Dermed kjører vi følgende spørring i en ny kode-celle:

sql = """

SELECT timestamp as Time, ST_POINT(trace.lon, trace.lat) as geom, r as Reg from FLIGHTS_09_01_02_2024
WHERE (r = 'LN-ORA' OR r = 'LN-ORB' or r = 'LN-ORC') AND h3_cell_to_parent(trace.h3_15, 3) = '830999fffffffff'
  ORDER BY trace.timestamp

"""

from lonboard import Map, HeatmapLayer
query = con.sql(sql)
layers = HeatmapLayer.from_duckdb(query, con)
n = Map(layers)
n

Og der har vi dem: Alle registrerte politihelikopterflygninger over Oslo mellom 1. og 3. september 2024.

Figur 4: Politihelikoptertrafikken over sentrale Oslo mellom 1. og 3. september 2024.

Fallskjermflyet på Voss

La oss flytte oss noe vestover, til Voss. Her har det i årevis vært en konflikt om den lokale flyplassen og den lokale fallskjermklubbens videre skjebne. Et av argumentene mot flyplassen er støyen som flyene genererer, særlig siden de ofte flyr over de samme områdene.

La oss ta en titt på datasettet for de samme dagene, og se om vi kan få et inntrykk av flytrafikken der. Vi kjører følgende spørring, som henter ut alle posisjonene fra fallskjermflyet med kjennetegnet D-FLOC i perioden:

sql = """

SELECT timestamp as Time, ST_POINT(trace.lon, trace.lat) as geom, r as Reg from FLIGHTS_09_01_02_2024
WHERE r = 'D-FLOC' AND trace.lon IS NOT NULL and trace.lat IS NOT NULL
  ORDER BY trace.timestamp

"""
con.sql(sql)

from lonboard import Map, HeatmapLayer
query = con.sql(sql)
layers = HeatmapLayer.from_duckdb(query, con)
n = Map(layers)
n

Dette gir oss denne heatmapen: Figur 5: Fallskjermflyet D-FLOCs bevegelser over Voss mellom 1. og 3. september 2024.

Brødsmulevisualisering

Mens heatmapen gir en god pekepinn på mengden aktivitet i et område, kan en detaljert brødsmulesti gi oss et mer spesifikt bilde. La oss prøve denne spørringen:

sql = """

SELECT trace.timestamp as Time, ST_POINT(trace.lon, trace.lat) as geom, r as Reg, trace.lon as Lon, trace.lat as Lat, trace.altitude as Alt from FLIGHTS_09_01_02_2024
WHERE r = 'D-FLOC' AND trace.lon IS NOT NULL and trace.lat IS NOT NULL
  ORDER BY trace.timestamp

"""
con.sql(sql)

query = con.sql(sql)

from lonboard import viz
viz(query, con=con)

Dette gir oss følgende visualisering: Figur 6: Detaljert visning av fallskjermflyet D-FLOCs bevegelser over Voss mellom 1. og 3. september 2024.

Identifikasjon av fartøy under en viss høyde

La oss nå undersøke hvilke andre fly som kan ha bidratt til støyen i området rundt flyplassen. Vi filtrerer på fly under 12.000 fot i området ved hjelp av H3-heksagonen 84098c5ffffffff med zoomnivå 4:

sql = """

SELECT DISTINCT r as callsign from FLIGHTS_09_01_02_2024
WHERE h3_cell_to_parent(trace.h3_15, 4) = '84098c5ffffffff' AND (trace.altitude <= 12000)
  ORDER BY trace.timestamp

"""

con.sql(sql)

Dette gir oss en liste over disse fartøyene:

Fallskjermflyet D-FLOC ligger øverst på listen, men vi ser også helikoptre som LN-OXP. Apropos helikoptre, hadde det ikke vært spennende å kartlegge alle helikoptre vi har registrert i denne perioden over hele Norge?

Visualisering av helikopterturer

Ved hjelp av et wildcard kan vi enkelt filtrere ut alle helikoptre på kartet. Vi kjører denne spørringen for å se alle helikopterturer:

sql = """
SELECT trace.timestamp as Time, ST_POINT(trace.lon, trace.lat) as geom, r as Reg, trace.lon as Lon, trace.lat as Lat, trace.altitude as Alt from FLIGHTS_09_01_02_2024
WHERE r LIKE 'LN-O%'
"""

con.sql(sql)

query = con.sql(sql)

from lonboard import viz
viz(query, con=con)

Her er resultatet: Figur 7: Alle registrerte helikopterturer i dekningsområdet mellom 1. og 3. september 2024.

Visualisering av militær trafikk

Som vi så tidligere, kan vi identifisere militære fartøy ved hjelp av dbFlags-attributten. Vi kan noen ganger også finne militære fartøy på en annen måte, som vi skal se på nå:

sql = """
SELECT reg_details.description,
       COUNT(DISTINCT r)
FROM   FLIGHTS_09_01_02_2024
GROUP BY 1
ORDER BY 2 DESC;
"""
con.sql(sql)

Her er resultatet: Figur 8: Oversikt over observerte fartøy i perioden.

Her ser vi en liste over fartøystyper, inkludert NATO AWACS. Awacs er det karakteristiske overvåkingsflyet med en stor antenne montert på skroget. La oss tegne opp flyets rute på et kart:

sql = """
SELECT trace.timestamp as Time, ST_POINT(trace.lon, trace.lat) as geom, r as Reg, trace.lon as Lon, trace.lat as Lat, trace.altitude as Alt from FLIGHTS_09_01_02_2024
WHERE reg_details.description == 'NATO AWACS'
"""
con.sql(sql)

query = con.sql(sql)

from lonboard import viz
viz(query, con=con)

Her er resultatet:

Figur 9: AWACS på tur i Norge.

Oppgaver

Nå har du fått en helt grunnleggende innføring i bruk av notebooks, DuckDB og visualiseringsverktøyet Lonboard. La oss friske opp kunnskapen med noen kjappe oppgaver: