Skip to article frontmatterSkip to article content
Sensorjournalistikk

Analyse med DuckDB πŸ¦†

Medieklyngen

Sanntidsdata, som vi allerede har sett pΓ₯, er et svΓ¦rt nyttig verktΓΈy for Γ₯ orientere seg om ΓΈyeblikksbildet – hva som skjer her og nΓ₯. PΓ₯ samme mΓ₯te er det fint Γ₯ kunne spole tilbake i tid for Γ₯ se hvordan situasjonen sΓ₯ ut for fem, ti og femten minutter siden – eller i gΓ₯r, forrige uke og kanskje i fjor sommer.

Samtidig er det situasjoner hvor vi kanskje vet hva vi ser etter, eller hvor vi skal se, men ikke nΓΈdvendigvis vet nΓ₯r det riktige tidspunktet er. Vi har kanskje hex-koden til et fly vi vet har passert over Norge, men ikke tidspunktet. Hvor har politihelikopteret flydd den siste uken? Og hvilke helikoptre har blitt observert i et gitt geografisk omrΓ₯de?

For Γ₯ kunne jobbe effektivt med denne typen spΓΈrsmΓ₯l og problemstillinger, trenger vi et verktΓΈy som kan hΓ₯ndtere store mengder historiske data effektivt. Til dette skal vi bruke DuckDB.

Hva er DuckDB?ΒΆ

DuckDB er en lettvekts, relasjonell analytisk database optimalisert for analyse av store datamengder. Den er designet med fokus pΓ₯ ytelse, enkelhet og fleksibilitet, noe som gjΓΈr den svΓ¦rt velegnet for datajournalistisk analyse – for eksempel av store skips- og flytrafikkdatasett.

DuckDB kjΓΈrer lokalt pΓ₯ datamaskinen din, noe som betyr at du ikke trenger Γ₯ sette opp en kompleks server for Γ₯ hΓ₯ndtere dataene. Hvis du allerede har gjort de tekniske forberedelsene tidligere i dette kompendiet, har du alt du trenger pΓ₯ datamaskinen din for Γ₯ komme i gang.

DuckDB kan integreres direkte med verktΓΈy som Python og R, noe som gir oss muligheten til Γ₯ kombinere SQL-spΓΈrringer med andre dataanalyseverktΓΈy og biblioteker. Dette er svΓ¦rt nyttig for Γ₯ lage datadrevne historier som involverer statistisk analyse eller visualiseringer. Og hvis du trives i Excel, er det enkelt Γ₯ eksportere et filtrert datasett direkte til dette verktΓΈyet.

DuckDB, Parquet og historiske dataΒΆ

SΓ₯ hvordan fΓ₯r vi historiske data inn i DuckDB? I disse ΓΈvelsene tar vi utgangspunkt i Medieklyngens ADS-B-server, som tilrettelegger dataene i et format som enkelt lar seg importere inn i DuckDB uten ytterligere bearbeiding.

Medieklyngens ADS-B-server lagrer alle data fra radarene som deler data med fellesskapet. Hver natt genereres optimaliserte databasefiler med siste dΓΈgns bevegelser. Filene lagres i det standardiserte Parquet-formatet, som er utviklet for Γ₯ hΓ₯ndtere store datamengder effektivt gjennom optimalisering og komprimering.

I motsetning til radbaserte formater som CSV og Excel, lagrer Parquet data kolonnevis. Dette gir flere fordeler ved analyse av store datasett, siden det blir raskere Γ₯ hente ut og komprimere spesifikke kolonner. For fly- og skipsdata, som ofte inneholder mange attributter som posisjon, hastighet og hΓΈyde, gjΓΈr Parquet det enkelt Γ₯ trekke ut og behandle relevante felter, noe som gir betydelige ytelsesforbedringer sammenlignet med radbaserte formater.

Parquet-filene er hive-partisjonert, noe som betyr at de er delt inn i mapper basert pΓ₯ Γ₯r, mΓ₯ned og dag. Dette gjΓΈr det enklere Γ₯ filtrere og hente ut spesifikke data, og forbedrer spΓΈrringsytelsen ved at man kun leser de nΓΈdvendige partisjonene i stedet for hele datasettet.

Parquet-filer kan leses av mange ulike verktΓΈy og programmeringssprΓ₯k, noe som gir stor fleksibilitet. For eksempel kan Parquet-filer behandles i verktΓΈy som Apache Spark, Hadoop, Presto, Amazon Athena og DuckDB. Dette gir en robust og skalerbar lΓΈsning som er klargjort for fremtidig vekst i datamengdene. Den gode nyheten er at du ikke trenger Γ₯ forholde deg til denne kompleksiteten med mindre du ΓΈnsker Γ₯ gΓ₯ i dybden pΓ₯ et senere tidspunkt.

SpΓΈrresprΓ₯ket SQLΒΆ

DuckDB benytter det veletablerte spΓΈrresprΓ₯ket SQL. SQL har rΓΈtter tilbake til 1970-tallet, da det ble utviklet av IBM for Γ₯ hΓ₯ndtere data i relasjonsdatabaser. SprΓ₯ket er intuitivt bygget opp og relativt enkelt Γ₯ lΓ¦re seg pΓ₯ et grunnleggende nivΓ₯.

I DuckDBs SQL-dialekt bruker du enkle kommandoer for Γ₯ hente, manipulere og analysere data fra tabeller. Den grunnleggende strukturen bestΓ₯r av kommandoer som SELECT, som brukes for Γ₯ hente spesifikke kolonner fra en tabell, og FROM, som angir hvilken tabell (datakilde) du henter data fra. Du kan ogsΓ₯ bruke WHERE for Γ₯ filtrere dataene basert pΓ₯ bestemte kriterier.

Skjematisk forklart:

SELECT [kolonner]
FROM [tabell]
WHERE [betingelser];

For eksempel kan en spΓΈrring se slik ut:

SELECT * FROM flights WHERE altitude > 10000;

Analyse i DuckDBΒΆ

Min erfaring er at det er enklere Γ₯ forstΓ₯ logikken i SQL ved Γ₯ utfΓΈre spΓΈrringene selv – fΓΈrst helt enkelt, deretter bygge pΓ₯ med mer avanserte filtre og sorteringer. Men fΓΈrst mΓ₯ vi logge inn og sette opp alt. Her er det bare Γ₯ Β«hamre innΒ» linje for linje, sΓ₯ kommer vi snart til det spennende og nyttige. De fΓΈlgende instruksjonene forutsetter at du har installert DuckDB pΓ₯ maskinen din. Hvis du har lastet ned DuckDB som en fil i en mappe i stedet for Γ₯ installere det, er det viktig at du navigerer til riktig mappe fΓΈr du begynner Γ₯ kjΓΈre disse kommandoene.

ForberedelserΒΆ

  1. Lag en tom mappe pΓ₯ valgfri plassering pΓ₯ datamaskinen din.
  2. Start Terminal (Mac og Linux) eller Poweshell (Windows), og naviger til mappen du akkurat opprettet.
  3. Start DuckDB med fΓΈlgende kommando:
duckdb sensorkurs-tirsdag-11-03-25.db

Dette starter DuckDB med en tom databasefil kalt sensorkurs-tirsdag-11-03-25.db. Filen lagres i mappen du kjΓΈrer kommandoen fra.

DuckDB svarer med et prompt som ser omtrent slik ut:

v1.2.1 8e52ec4395
Enter ".help" for usage hints.
D

Installer og last inn utvidelser og innstillingerΒΆ

  1. Installer fΓΈlgende utvidelser ved Γ₯ skrive inn disse kommandoene i prompten (dette gjΓΈres kun ved fΓΈrste gangs kjΓΈring):
INSTALL httpfs;
INSTALL spatial;
INSTALL h3 FROM community;
  1. Last inn utvidelsene du akkurat installerte ved hjelp av fΓΈlgende kommandoer (dette mΓ₯ gjΓΈres hver gang, eller automatiseres med et script, se under):
LOAD httpfs;
LOAD spatial;
LOAD h3;
  1. Legg inn tilgangsnΓΈkler til serveren som lagrer de historiske dataene:
CREATE SECRET (
    TYPE R2,
    KEY_ID '9030e0f90a86af08b08b6e2a1222a778',
    SECRET '2fe64ae1c22869400f577bb9421602f0f81a83a2f658cea6bdd556f4fc65064b',
    ACCOUNT_ID 'bca3475a0f4afeb0640daafc17ec2b18'
);

DuckDB bekrefter med:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Success β”‚
β”‚ boolean β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ true    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Last inn data i DuckDBΒΆ

NΓ₯ begynner moroa! La oss starte med Γ₯ laste ned alle kjente flybevegelser for en spesifikk dag, for eksempel lΓΈrdag 8. mars 2025. KjΓΈr fΓΈlgende kommando:

CREATE TABLE flights_08_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 = 08;

Dette oppretter en ny tabell i databasen kalt flights_08_03_25 og fyller den med data fra alle Parquet-filer som samsvarer med datoen 8. mars 2025.

NΓ₯r du trykker Enter for Γ₯ kjΓΈre kommandoen, tar det litt tid fΓΈr du fΓ₯r respons. DuckDB laster ned dataene fra Medieklyngens server, pakker dem ut og importerer dem til databasen. NΓ₯r det er ferdig, ser det forhΓ₯pentligvis slik ut:

100% β–•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–

Tell antall posisjoner i datasettetΒΆ

NΓ₯ har vi hentet ned dataene for en dag, men hvor mye data dreier det seg om? La oss telle dem:

SELECT count(*) FROM flights_08_03_25;

Dette gir fΓΈlgende svar:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  count_star()  β”‚
β”‚     int64      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    2805044     β”‚
β”‚ (2.81 million) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2,81 millioner posisjoner! Det gir oss litt Γ₯ jobbe med.

Bli kjent med datastrukturenΒΆ

La oss ta en nΓ¦rmere titt pΓ₯ dataene og den underliggende strukturen ved Γ₯ kjΓΈre en enkel spΓΈrring. LIMIT 1 begrenser oss til ett treff – det fΓΈrste:

SELECT * FROM flights_08_03_25 LIMIT 1;
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ dbFlags β”‚       desc       β”‚  icao   β”‚ … β”‚ year  β”‚   day   β”‚  month  β”‚
β”‚  int64  β”‚     varchar      β”‚ varchar β”‚   β”‚ int64 β”‚ varchar β”‚ varchar β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    0    β”‚ BOEING 777-200LR β”‚ 040168  β”‚ … β”‚ 2025  β”‚ 08      β”‚ 03      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1 rows                                          12 columns (6 shown) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

En Boeing 777-200LR! Og et ICAO-nummer samt en dato. Men vi merker oss ogsΓ₯ at det stΓ₯r 12 columns (6 shown). Standardvisningen har ikke plass til Γ₯ vise alt.

Vi kan endre visningen med en dot-kommando kalt .mode. Skriv .help mode for Γ₯ vise de ulike alternativene.

La oss fΓΈrst prΓΈve alternativet duckbox:

.mode duckbox

Deretter kjΓΈrer vi spΓΈrringen pΓ₯ nytt:

SELECT * FROM flights_08_03_25 LIMIT 1;

...og ser at vi nΓ₯ fΓ₯r med alle kolonnene.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ dbFlags β”‚       desc       β”‚  icao   β”‚  ownOp  β”‚    r    β”‚     reg_details      β”‚    t    β”‚      timestamp      β”‚            trace             β”‚ year  β”‚   day   β”‚  month  β”‚
β”‚  int64  β”‚     varchar      β”‚ varchar β”‚ varchar β”‚ varchar β”‚ struct(description…  β”‚ varchar β”‚      timestamp      β”‚ struct(aircraft struct(ale…  β”‚ int64 β”‚ varchar β”‚ varchar β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    0    β”‚ BOEING 777-200LR β”‚ 040168  β”‚ NULL    β”‚ ET-AWE  β”‚ {'description': ge…  β”‚ B77L    β”‚ 2025-03-08 00:00:00 β”‚ {'aircraft': {'alert': 0, …  β”‚ 2025  β”‚ 08      β”‚ 03      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Men ser vi egentlig alt? Se pΓ₯ kolonnene reg_details og trace. Her ser vi at det ligger datastrukturer som fortsatt blir forkortet. Dette er fordi dataene er i JSON-format. Hvis du tar en titt pΓ₯ de ulike tilgjengelige visningene med .help mode, finner vi json som et valg. La oss prΓΈve dette og kjΓΈre spΓΈrringen pΓ₯ nytt:

.mode json
SELECT * FROM flights_08_03_25 LIMIT 1;

NΓ₯ ser vi den fullstendige strukturen – for ett av de flere millioner datapunktene fra denne dagen.

[{"dbFlags":0,"desc":"BOEING 777-200LR","icao":"040168","ownOp":null,"r":"ET-AWE","reg_details":"{'description': general, 'iso2': ET, 'iso3': ETH, 'nation': Ethiopia}","t":"B77L","timestamp":"2025-03-08 00:00:00","trace":"{'aircraft': {'alert': 0, 'alt_geom': 16600, 'baro_rate': 832, 'category': NULL, 'emergency': none, 'flight': NULL, 'geom_rate': 832, 'gva': 2, 'ias': 319, 'mach': 0.648, 'mag_heading': 51.15, 'nac_p': 11, 'nac_v': 2, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': 25008, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': 1013.0, 'nic': 8, 'nic_baro': 1, 'oat': NULL, 'rc': 186, 'roll': NULL, 'sda': 2, 'sil': 3, 'sil_type': perhour, 'spi': 0, 'squawk': 7122, 'tas': NULL, 'tat': NULL, 'track': 52.37, 'track_rate': NULL, 'true_heading': 56.23, 'type': adsb_icao, 'version': 2, 'wd': NULL, 'ws': NULL}, 'altitude': 16450, 'flags': 1, 'geometric_altitude': 16600, 'geometric_vertical_rate': 832, 'ground_speed': 407.8, 'h3_15': 8f1fa0af699899b, 'indicated_airspeed': 319, 'lat': 50.670364, 'lon': 6.125528, 'on_ground': false, 'roll_angle': NULL, 'source': adsb_icao, 'timestamp': 2025-03-08 07:04:04.840000, 'track_degrees': 52.4, 'vertical_rate': 832}","year":2025,"day":"08","month":"03"}]

FormatΓ©r JSON-data for bedre oversiktΒΆ

JSON kan vΓ¦re vanskelig Γ₯ lese i sin rΓ₯ form. For en bedre oversikt kan vi bruke et gratis verktΓΈy for Γ₯ formatere dataene:

JSON Formatter

En rask copy-paste senere, og dataene er langt mer oversiktlige.

[
   {
      "dbFlags":0,
      "desc":"BOEING 777-200LR",
      "icao":"040168",
      "ownOp":null,
      "r":"ET-AWE",
      "reg_details":"{'description': general, 'iso2': ET, 'iso3': ETH, 'nation': Ethiopia}",
      "t":"B77L",
      "timestamp":"2025-03-08 00:00:00",
      "trace":"{'aircraft': {'alert': 0, 'alt_geom': 16600, 'baro_rate': 832, 'category': NULL, 'emergency': none, 'flight': NULL, 'geom_rate': 832, 'gva': 2, 'ias': 319, 'mach': 0.648, 'mag_heading': 51.15, 'nac_p': 11, 'nac_v': 2, 'nav_altitude_fms': NULL, 'nav_altitude_mcp': 25008, 'nav_heading': NULL, 'nav_modes': NULL, 'nav_qnh': 1013.0, 'nic': 8, 'nic_baro': 1, 'oat': NULL, 'rc': 186, 'roll': NULL, 'sda': 2, 'sil': 3, 'sil_type': perhour, 'spi': 0, 'squawk': 7122, 'tas': NULL, 'tat': NULL, 'track': 52.37, 'track_rate': NULL, 'true_heading': 56.23, 'type': adsb_icao, 'version': 2, 'wd': NULL, 'ws': NULL}, 'altitude': 16450, 'flags': 1, 'geometric_altitude': 16600, 'geometric_vertical_rate': 832, 'ground_speed': 407.8, 'h3_15': 8f1fa0af699899b, 'indicated_airspeed': 319, 'lat': 50.670364, 'lon': 6.125528, 'on_ground': false, 'roll_angle': NULL, 'source': adsb_icao, 'timestamp': 2025-03-08 07:04:04.840000, 'track_degrees': 52.4, 'vertical_rate': 832}",
      "year":2025,
      "day":"08",
      "month":"03"
   }
]

Dette viser oss den fullstendige strukturen pΓ₯ dataene lagret i Parquet-formatet. Vi ser mange datafelter, men ogsΓ₯ en del nullverdier (NULL), noe som er ganske vanlig. Blant feltene vi bΓΈr merke oss:

Under feltet trace finner vi en ny understruktur med interessante data:

Finn et spesifikt fartΓΈyΒΆ

Datapunktet vi har sett pΓ₯ til nΓ₯ ble tilfeldig valgt. Vanligvis vil vi lete etter spesifikke fartΓΈy, for eksempel et politihelikopter. I Norge har politihelikoptrene registreringsnumrene LN-ORA, LN-ORB, og LN-ORC. La oss sjekke om vi finner et av dem i datasettet:

SELECT * FROM flights_08_03_25 WHERE r = 'LN-ORA' LIMIT 1;

Dette filtrerer pΓ₯ r (registrering), slik at vi kun fΓ₯r data for LN-ORA, og begrenser treffet til ett datapunkt.

[{"dbFlags":0,"desc":"AGUSTA AW-169","icao":"479c1e","ownOp":null,"r":"LN-ORA","reg_details":"{'description': helicopters, 'iso2': NO, 'iso3': NOR, 'nation': Norway}","t":"A169","timestamp":"2025-03-08 00:00:00","trace":"{'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': 1050, 'flags': 5, 'geometric_altitude': 1300, 'geometric_vertical_rate': 1216, 'ground_speed': 80.5, 'h3_15': 8f0999771432244, 'indicated_airspeed': NULL, 'lat': 59.809338, 'lon': 10.855534, 'on_ground': false, 'roll_angle': NULL, 'source': adsb_icao, 'timestamp': 2025-03-08 14:05:42.490000, 'track_degrees': 75.6, 'vertical_rate': 1216}","year":2025,"day":"08","month":"03"}]

Se der, ja! Her var det treff pΓ₯ fΓΈrste forsΓΈk. Det ser ut til at det fΓΈrste politihelikopteret har vΓ¦rt ute Γ₯ flydd. La oss Β«forfineΒ» dataene litt:

[
  {
    "dbFlags": 0,
    "desc": "AGUSTA AW-169",
    "icao": "479c1e",
    "ownOp": null,
    "r": "LN-ORA",
    "reg_details": "{'description': helicopters, 'iso2': NO, 'iso3': NOR, 'nation': Norway}",
    "t": "A169",
    "timestamp": "2025-03-08 00:00:00",
    "trace": "{'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': 1050, 'flags': 5, 'geometric_altitude': 1300, 'geometric_vertical_rate': 1216, 'ground_speed': 80.5, 'lat': 59.809338, 'lon': 10.855534, 'on_ground': false, 'timestamp': 2025-03-08 14:05:42.490000, 'track_degrees': 75.6, 'vertical_rate': 1216}"
  }
]

Vi ser umiddelbart at dette er et helikopter av typen Augusta AW-169, og at det har ICAO-adressen 479c1e. Det er mange nullverdier her, men vi ser at bΓ₯de GPS-posisjon, geometrisk hΓΈyde og bakkefart er oppgitt. Det er en god start. Men hva om vi ΓΈnsker ikke bare Γ₯ se ett enkelt datapunkt, men alle punktene fra dette helikopteret i det aktuelle datasettet? La oss prΓΈve med en litt mer omfattende spΓΈrring:

SELECT * FROM flights_08_03_25 WHERE r = 'LN-ORA';

Ved Γ₯ fjerne filteret LIMIT 1, fΓ₯r vi mye mer data. Men hva om vi kunne lagre disse dataene til en fil? I DuckDB kan vi bruke COPY():

COPY (
   SELECT * FROM flights_08_03_25 WHERE r = 'LN-ORA'
   ) TO 'ln-ora.json';

Denne filen kan vi nΓ₯ Γ₯pne i en nettleser eller Notisblokk. For Γ₯ fΓ₯ filen mer hΓ₯ndterlig:

COPY (
  SELECT r, trace.altitude,
     trace.ground_speed,
     trace.lon,
     trace.lat,
     trace.timestamp
   FROM flights_08_03_25
   WHERE r = 'LN-ORA'
   ORDER BY trace.timestamp
   ) TO 'ln-ora.csv' WITH (HEADER);

NΓ₯ kan du Γ₯pne filen i Excel. Vi har hentet ut registreringsnummer, bakkefart, koordinater (lat og lon) og tidspunkt.

Vis fartΓΈy pr. landΒΆ

I datastrukturen legger vi merke til at nation er et felt som angir registreringslandet til de sporede fartΓΈyene. La oss se hvilke land som er representert i datasettet, og hvor mange fly som er registrert per land:

.mode duckbox

SELECT   reg_details.nation,
         COUNT(DISTINCT r)
FROM     flights_08_03_25
WHERE    LENGTH(reg_details.nation)
GROUP BY 1
ORDER BY 2 DESC;

I spΓΈrringen over har vi introdusert et par nye konsepter. Vi begynner med Γ₯ velge ut feltet nation, som ligger under noden reg_details i datastrukturen. Deretter kjΓΈrer vi en funksjon som teller antall unike fartΓΈy i datasettet og kobler dette antallet mot hver nasjon. Til slutt grupperer vi visningen pΓ₯ land og sorterer etter antall fly per land, synkende.

Vi ser at det er 69 land representert, og at Β«United KingdomΒ» ligger pΓ₯ toppen med hele 239 unike observerte fly. Et neste steg kan vΓ¦re Γ₯ se pΓ₯ fly fra et spesifikt land. La oss prΓΈve oss pΓ₯ Russland med denne spΓΈrringen:

SELECT r, trace.altitude,
   trace.ground_speed,
   trace.lon,
   trace.lat,
   reg_details.nation,
   trace.timestamp
FROM flights_08_03_25
WHERE reg_details.nation = 'Russia'
ORDER BY trace.timestamp;

Ser man det! Her har vi fire unike posisjoner:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    r     β”‚ altitude β”‚ ground_speed β”‚    lon    β”‚    lat    β”‚ nation  β”‚         timestamp          β”‚
β”‚ varchar  β”‚  int64   β”‚    double    β”‚  double   β”‚  double   β”‚ varchar β”‚          varchar           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ RA-73716 β”‚    32000 β”‚        402.1 β”‚ 21.901337 β”‚ 59.239197 β”‚ Russia  β”‚ 2025-03-08 19:39:50.160000 β”‚
β”‚ RA-73716 β”‚    32025 β”‚        403.8 β”‚ 21.549114 β”‚ 59.149366 β”‚ Russia  β”‚ 2025-03-08 19:41:38.690000 β”‚
β”‚ RA-73716 β”‚    32025 β”‚        403.8 β”‚ 21.546178 β”‚ 59.148622 β”‚ Russia  β”‚ 2025-03-08 19:41:39.610000 β”‚
β”‚ RA-73716 β”‚    32000 β”‚        404.3 β”‚ 21.447112 β”‚ 59.119108 β”‚ Russia  β”‚ 2025-03-08 19:42:11.010000 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Kanskje ikke det mest revolusjonerende funnet til Γ₯ begynne med, men leter vi lenge nok, dukker det plutselig opp interessante ting.

NΓΈdkoderΒΆ

Vi husker fra tidligere at piloter kan melde fra om nΓΈdsituasjoner ved Γ₯ sette spesielle squawk-koder i transponderen. La oss se om vi finner noen slike koder i dataene.

  1. Vi begynner med Γ₯ laste inn et litt bredere datasett, med data fra en nesten helt tilfeldig valgt dag i oktober 2024.
CREATE TABLE flights_15_10_24 AS
   SELECT * FROM read_parquet('r2://medieklyngen-radar-data/adsb/history/*/*/*/*.parquet', hive_partitioning = true)
   WHERE year = 2024 AND month = 10 AND day = 15;
  1. Deretter kjΓΈrer vi fΓΈlgende spΓΈrring for Γ₯ se etter posisjoner med squawk satt til 7700, som indikerer en nΓΈdsituasjon:
SELECT DISTINCT r, FIRST(trace.timestamp) as timestamp
       FROM  flights_15_10_24 WHERE trace.aircraft.squawk::INT = 7700
       GROUP BY 1;
9H-VISTA  2024-10-15 15:49:20.890000
  1. Her fΓ₯r vi opp ett treff! La oss bygge ut spΓΈrringen til ogsΓ₯ Γ₯ inneholde koordinater og hΓΈyde:
SELECT r, trace.timestamp, trace.lat, trace.lon, trace.altitude
      FROM  flights_15_10_24 WHERE trace.aircraft.squawk::INT = 7700
      GROUP BY ALL
      ORDER BY 2 ASC;
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    r     β”‚         timestamp          β”‚    lat    β”‚   lon    β”‚ altitude β”‚
β”‚ varchar  β”‚          varchar           β”‚  double   β”‚  double  β”‚  int64   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 9H-VISTA β”‚ 2024-10-15 15:49:20.890000 β”‚ 52.066315 β”‚ 4.629288 β”‚    13150 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:49:37.060000 β”‚ 52.087326 β”‚  4.63028 β”‚    12875 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:49:47.490000 β”‚ 52.100473 β”‚ 4.630973 β”‚    12700 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:00.160000 β”‚ 52.116577 β”‚ 4.631882 β”‚    12475 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:15.500000 β”‚ 52.135803 β”‚ 4.632874 β”‚    12225 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:21.350000 β”‚ 52.142899 β”‚ 4.633179 β”‚    12125 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:27.080000 β”‚ 52.149819 β”‚ 4.633562 β”‚    12025 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:29.100000 β”‚ 52.152328 β”‚ 4.633636 β”‚    11975 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:35.880000 β”‚ 52.160294 β”‚ 4.634018 β”‚    11875 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:42.860000 β”‚ 52.168671 β”‚ 4.634476 β”‚    11750 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:49.860000 β”‚ 52.177002 β”‚ 4.634857 β”‚    11625 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:52.750000 β”‚ 52.180573 β”‚  4.63501 β”‚    11575 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:50:57.830000 β”‚ 52.186478 β”‚ 4.635239 β”‚    11500 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:00.720000 β”‚ 52.189853 β”‚ 4.635446 β”‚    11450 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:08.420000 β”‚ 52.198883 β”‚ 4.635849 β”‚    11325 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:13.890000 β”‚ 52.205262 β”‚ 4.636152 β”‚    11225 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:17.250000 β”‚ 52.209091 β”‚ 4.636383 β”‚    11175 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:20.930000 β”‚ 52.213257 β”‚ 4.636612 β”‚    11125 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:23.220000 β”‚ 52.216049 β”‚ 4.636688 β”‚    11075 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:28.360000 β”‚ 52.221771 β”‚ 4.636993 β”‚    11000 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:30.110000 β”‚  52.22406 β”‚ 4.637146 β”‚    10950 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:34.280000 β”‚ 52.228771 β”‚ 4.637329 β”‚    10875 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:51:40.810000 β”‚ 52.236191 β”‚ 4.637604 β”‚    10775 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:52:08.170000 β”‚  52.26709 β”‚ 4.638443 β”‚    10325 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:52:55.960000 β”‚ 52.319781 β”‚ 4.639448 β”‚     9525 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:54:08.700000 β”‚ 52.398605 β”‚  4.64325 β”‚     8250 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:54:13.730000 β”‚ 52.404053 β”‚ 4.643555 β”‚     8125 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:54:19.850000 β”‚ 52.410977 β”‚ 4.643921 β”‚     7925 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:55:58.310000 β”‚ 52.520737 β”‚ 4.650116 β”‚     5925 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:57:28.100000 β”‚ 52.592484 β”‚ 4.745387 β”‚     4275 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:58:53.190000 β”‚ 52.581848 β”‚ 4.860611 β”‚     2875 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 15:58:58.430000 β”‚ 52.577362 β”‚ 4.860458 β”‚     2775 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:00:18        β”‚ 52.526001 β”‚ 4.786606 β”‚     1925 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:02:00.020000 β”‚ 52.454177 β”‚  4.75143 β”‚     1900 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:02:09.190000 β”‚ 52.447678 β”‚ 4.751434 β”‚     1900 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:02:16.180000 β”‚ 52.442734 β”‚ 4.751205 β”‚     1900 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:02:34.970000 β”‚  52.42923 β”‚ 4.749756 β”‚     1875 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:03:01.130000 β”‚ 52.411743 β”‚ 4.746933 β”‚     1525 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:03:13.310000 β”‚ 52.404971 β”‚ 4.746015 β”‚     1350 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:03:21.830000 β”‚ 52.400782 β”‚ 4.745544 β”‚     1250 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:03:30.950000 β”‚ 52.396266 β”‚ 4.745466 β”‚     1175 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:03:35.590000 β”‚ 52.393985 β”‚ 4.745466 β”‚     1150 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:03:38.760000 β”‚ 52.392356 β”‚ 4.745466 β”‚     1125 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:03:44.880000 β”‚ 52.389267 β”‚ 4.745407 β”‚     1050 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:03:51.760000 β”‚ 52.385745 β”‚ 4.745152 β”‚     1000 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:04:01.750000 β”‚ 52.380661 β”‚ 4.744644 β”‚      900 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:04:03.500000 β”‚ 52.379694 β”‚ 4.744524 β”‚      875 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:04:09.180000 β”‚ 52.376854 β”‚ 4.744289 β”‚      825 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:04:14.690000 β”‚ 52.374023 β”‚ 4.743958 β”‚      775 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:04:18.630000 β”‚ 52.372012 β”‚  4.74374 β”‚      725 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:04:26.270000 β”‚ 52.367935 β”‚ 4.743423 β”‚      650 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:04:47.790000 β”‚ 52.356697 β”‚ 4.742327 β”‚      425 β”‚
β”‚ 9H-VISTA β”‚ 2024-10-15 16:05:03.250000 β”‚ 52.348597 β”‚ 4.741699 β”‚      275 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 53 rows                                                       5 columns β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. Hvis vi ser nΓ¦rmere pΓ₯ kolonnen altitude, ser vi at denne faller med tiden, fΓΈr den ender opp pΓ₯ kun 275 fot. Interessant!

  2. La oss finne ut hvor dette er. Kopier koordinatene 52.348597, 4.741699 og lim dem inn i Google Maps.

  1. Vi ser at flyet befinner seg rett ved Schiphol-lufthavnen i Amsterdam. Det kan se ut som om flyet er pΓ₯ finalen av det som kanskje er en nΓΈdlanding. La oss utvide spΓΈrringen med flere kolonner.
SELECT r, trace.timestamp, trace.lat, trace.lon, trace.altitude, "desc", t, trace.aircraft.category, trace.aircraft.flight 
       FROM  flights_15_10_24 WHERE trace.aircraft.squawk::INT = 7700
       GROUP BY ALL
       ORDER BY 2 DESC;
  1. Et kjapt Google-sΓΈk forteller oss at dette er et privatfly eid av Vista Jet. Flyet landet med nΓΈdkoden 7700 aktiv 15. oktober 2024 pΓ₯ Schiphol i Amsterdam. Hadde dette vΓ¦rt i Norge, kunne det vΓ¦rt spennende Γ₯ undersΓΈke saken videre.

  2. Vi kan ogsΓ₯ ta med oss rΓ₯dataene videre til et kartverktΓΈy eller visualisere flyets fart og hΓΈyde over tid i en graf dersom vi ΓΈnsker Γ₯ presentere disse dataene til sluttbrukere.

Interessante fartΓΈyΒΆ

Mens mange av sporingsdataene vi har sett pΓ₯ sΓ₯ langt er offentlig tilgjengelig i tjenester som Flightradar24 og Radarbox, er det selvsagt ekstra spennende med informasjon som er vanskeligere tilgjengelig. Her kommer feltet dbFlags til unnsetning. Dataene her stammer fra en database som oppdateres periodisk pΓ₯ serveren. La oss hente inn de tilgjengelige dataene fra hele mars 2025, og deretter kjΓΈre en ny spΓΈrring:

CREATE TABLE flights_03_25 AS
   SELECT * FROM read_parquet('r2://medieklyngen-radar-data/adsb/history/*/*/*/*.parquet', hive_partitioning = true)
   WHERE year = 2025 AND month = 03;

.maxrows 400
SELECT r, dbFlags AS flag
     FROM   flights_03_25 WHERE dbFlags > 0
     GROUP BY ALL
     ORDER BY 2 DESC
     LIMIT 1000;

Vi fΓ₯r opp en liste over fartΓΈy som er flagget i databasen som spesielle.

LADD og PIA husker vi fra tidligere. LADD er flagget som skal signalisere til Flightradar24 og andre tjenester at det aktuelle fartΓΈyet bΓΈr filtreres ut fra offentlig visning. PIA er et midlertidig ICAO-nummer som man kan sΓΈke om Γ₯ fΓ₯ innvilget fra amerikanske luftfartsmyndigheter.

Disse flaggene kan gi oss svΓ¦rt verdifull informasjon. Det logiske er kanskje Γ₯ tenke pΓ₯ at hvert fartΓΈy kun kan ha ett av disse flaggene. Da kunne vi ha kjΓΈrt fΓΈglende spΓΈrring for Γ₯ fΓ₯ ut alle fartΓΈy som har LADD-flagget satt:

SELECT r, dbFlags
     FROM   flights_03_25 WHERE dbFlags = 8
     GROUP BY ALL
     ORDER BY 2 DESC
     LIMIT 1000;

Men sannheten er at hvert fartΓΈy kan ha alle disse flaggene satt pΓ₯ en gang, gjennom en lur teknisk lΓΈsning kjent som bitwise.

Alt av data i en datamaskin lagres som biter – det vil si 0-er og 1-ere. NΓ₯r vi snakker om en heltallsverdi, som dbFlags, bestΓ₯r den egentlig bare av en rekke slike biter. Her er et forenklet eksempel:

Binærverdi: 1000  →  desimalverdi: 8

Hver posisjon i dette binΓ¦rtallet kan representere et β€œja/nei”-flagg. Og siden vi kan slΓ₯ sammen flere slike, kan ett enkelt tall romme flere tilstander samtidig.

FlaggtypeDesimalverdiBinærverdi
Militært10001
Interessant20010
PIA40100
LADD81000

Hvis et fartΓΈy bΓ₯de er markert som PIA og LADD, fΓ₯r det:

4 + 8 = 12  →  Binært: 1100

Med andre ord: dbFlags = 12 forteller oss at begge disse to flaggene er satt.

Hvordan finner vi ut hvilke flagg som er satt?ΒΆ

For Γ₯ sjekke om et bestemt flagg er slΓ₯tt pΓ₯ i en gitt verdi, bruker vi en bitvis AND-operasjon. I SQL kan det se slik ut:

SELECT * FROM flights_03_25 WHERE dbFlags & 8 != 0;

Denne spΓΈrringen returnerer alle rader der LADD-flagget er satt. Vi sjekker altsΓ₯ om biten for tallet 8 (som tilsvarer 1000 binΓ¦rt) er skrudd pΓ₯.

Det samme prinsippet gjelder for de andre flaggene:

-- Alle militære fartøy
SELECT * FROM flights_03_25 WHERE dbFlags & 1 != 0;

-- Alle klassifisert som interessante
SELECT * FROM flights_03_25 WHERE dbFlags & 2 != 0;

Kombinasjoner? Null problemΒΆ

Hvis du vil hente ut fartΓΈy som enten har LADD eller PIA, gjΓΈr du slik:

SELECT * FROM flights_03_25 WHERE dbFlags & (8 + 4) != 0;

Hvis vi gΓ₯r tilbake til utgangsspΓΈrringen lenger oppe, er det slik den skal se ut hvis vi vil hente ut alle fartΓΈy med LADD-flagget satt:

SELECT r, dbFlags
     FROM   flights_03_25 WHERE dbFlags & 8 != 0
     GROUP BY ALL
     ORDER BY 2 DESC
     LIMIT 1000;

Ønsker vi Γ₯ bygge ut spΓΈrringen til Γ₯ gi oss alle fartΓΈy som har bΓ₯de PIA (4) og LADD (8) satt, ser det slik ut:

SELECT r, dbFlags
     FROM   flights_03_25 WHERE dbFlags & (8 + 4) != 0
     GROUP BY ALL
     ORDER BY 2 DESC
     LIMIT 1000;

Tekniske flaggΒΆ

Siden vi akkurat har dykket ned i Bitwise, passer det bra Γ₯ se pΓ₯ et annet flagg i datastrukturen som ogsΓ₯ benytter seg av det samme konseptet: trace.flags.

Her er det kanskje flagget 2 som er av stΓΈrst interesse, da det kan hjelpe oss Γ₯ identifisere starten pΓ₯ en flygning. La oss prΓΈve:

SELECT r,
        trace.timestamp AS flight_start,
FROM   flights_08_03_25
WHERE  r = 'SE-ROI' and trace.flags & 2 != 0
ORDER BY trace.timestamp;
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    r    β”‚        flight_start        β”‚
β”‚ varchar β”‚          varchar           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ SE-ROI  β”‚ 2025-04-01 06:28:48.060000 β”‚
β”‚ SE-ROI  β”‚ 2025-04-01 08:50:32.850000 β”‚
β”‚ SE-ROI  β”‚ 2025-04-01 15:39:22.160000 β”‚
β”‚ SE-ROI  β”‚ 2025-04-01 16:45:08.970000 β”‚
β”‚ SE-ROI  β”‚ 2025-04-01 18:31:13.950000 β”‚
β”‚ SE-ROI  β”‚ 2025-04-01 22:06:13.600000 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Voila, her har vi en (estimert) oversikt over fartΓΈyet SE-ROIs avanger 8. mars 2025, i UTC-tid.

SΓΈk i geografiske sektorerΒΆ

DuckDB har en utvidelse kalt spatial som lar oss gjΓΈre geografiske spΓΈrringer. Kort fortalt gjΓΈr denne modulen DuckDB i stand til Γ₯ hΓ₯ndtere koordinatsystemer. Vi begynner med Γ₯ se pΓ₯ tradisjonelle former for geografiske sΓΈk, fΓΈr vi utforsker det enkle og geniale H3-rammeverket for geografisk sΓΈk, utviklet av ingen ringere enn Uber.

La oss starte med Γ₯ hente inn data fra en ny dato, nemlig 16. mars 2025.

CREATE TABLE flights_16_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 = 16;

Punkt og radiusΒΆ

NΓ₯r man vurderer mΓ₯ter Γ₯ avgrense et sΓΈk geografisk pΓ₯, dukker kanskje passeren opp i tankene? Eller? I den analoge verdenen starter vi med et geografisk utgangspunkt pΓ₯ kartet, setter passernΓ₯len pΓ₯ ΓΈnsket posisjon og mΓ₯ler opp en radius. Denne metoden kan vi enkelt replikere i DuckDB. Se fΓΈlgende spΓΈrring:

SELECT r, trace.lon, trace.lat, trace.timestamp
FROM flights_16_03_25
WHERE ST_DWithin(ST_Point(trace.lon, trace.lat), ST_Point(5.332982, 60.385434), 0.01);

Det var en munnfull! FΓΈrste delen av spΓΈrringen er kjent stoff, men etter WHERE begynner det Γ₯ balle pΓ₯ seg. Her bruker vi geo-funksjonen ST_DWithin, som filtrerer gjennom hvert datapunkt i tabellen og sjekker om det befinner seg innenfor vΓ₯rt forhΓ₯ndsdefinerte omrΓ₯de.

Filteret vi har satt opp, er: «Alle datapunkter innenfor 0,01 grad fra det geografiske punktet 5.332982, 60.385434». Men hva betyr 0,01 grad? Funksjonen forholder seg til geografiske grader, og 0,01 grad tilsvarer omtrent 1 kilometer. Dette kan være et godt utgangspunkt. Alternativt kan vi prøve 0,1 grader, som tilsvarer rundt 11 kilometer.

AvgrensningsboksΒΆ

En annen mΓ₯te Γ₯ definere et geografisk omrΓ₯de pΓ₯ er Γ₯ tegne en avgrensningsboks ved hjelp av fire geografiske koordinater: to lengdegrader og to breddegrader. Ved Γ₯ definere disse som punkter pΓ₯ et kart og trekke en tenkt linje mellom dem, fΓ₯r vi en sektor i form av et kvadrat eller et rektangel vi kan bruke som et filter.

SELECT r, trace.lon, trace.lat, trace.timestamp
FROM flights_16_03_25
WHERE (trace.lon BETWEEN 5.9 AND 6) AND (trace.lat BETWEEN 59 AND 59.1);

Denne metoden er litt mer lettlest enn den forrige. Her ber vi om alle datapunkter som ligger mellom lengdegradene 5.9 og 6.0, samt mellom breddegradene 59.0 og 59.1.

I tillegg til avgrensningsbokser og punkt-og-radius-metoden er det mulig Γ₯ gjΓΈre komplekse sΓΈk i omrΓ₯der definert via polygoner. For eksempel kan man sΓΈke etter alle datapunkter innenfor et spesifikt fylke. Siden dette er mer spesialisert og mindre relevant for fly- og skipstrafikkdata, kommer vi ikke til Γ₯ gΓ₯ i dybden pΓ₯ dette her. I stedet skal vi utforske en annen, svΓ¦rt effektiv metode for geografiske sΓΈk: H3.

H3ΒΆ

H3 er et geografisk rutenett-system utviklet av Uber for Γ₯ effektivisere og forenkle geografiske analyser. Systemet deler jordkloden inn i sekskantede celler, kjent som heksagoner, i motsetning til tradisjonelle rutemΓΈnstre basert pΓ₯ kvadrater. Hver heksagon har en unik identifikator (H3 ID) og kan representere geografiske omrΓ₯der med hΓΈy presisjon.

H3-rammeverket gir en mer naturlig tilnΓ¦rming til romlige analyser sammenlignet med kvadratiske rutenett, som ofte introduserer skjevheter pΓ₯ grunn av jordens krumning. En annen fordel med heksagoner er at de overlapper i svΓ¦rt liten grad, i motsetning til for eksempel sirkler.

H3 opererer med 15 ulike opplΓΈsninger. Lavere opplΓΈsninger dekker stΓΈrre omrΓ₯der, mens hΓΈyere opplΓΈsninger gir mer detaljerte inndelinger. Dette gjΓΈr det mulig Γ₯ skalere geografiske analyser etter behov – for eksempel kan man undersΓΈke et helt kontinent med grov opplΓΈsning eller et spesifikt nabolag med hΓΈy detaljgrad.

Et heksagon i H3 kan ogsΓ₯ grupperes med sine Β«foreldreΒ» pΓ₯ hΓΈyere nivΓ₯er. Dette betyr at man enkelt kan aggregere data for stΓΈrre omrΓ₯der ved Γ₯ slΓ₯ sammen flere smΓ₯ heksagoner til stΓΈrre enheter. Slike hierarkiske strukturer er nyttige for analyser pΓ₯ tvers av forskjellige geografiske skalaer.

Det er mulig Γ₯ oversette vanlige koordinater til H3-heksagoner direkte i DuckDB ved hjelp av innebygde funksjoner. PΓ₯ Medieklyngens ADS-B-server har vi imidlertid allerede gjort denne jobben, for Γ₯ sikre mest mulig effektive spΓΈrringer.

Vi installerte og lastet inn H3-utvidelsen i starten av dette kapittelet med fΓΈlgende kommandoer:

INSTALL h3 FROM community;
LOAD h3;

Med dette tillegget pΓ₯ plass, er det bare Γ₯ gΓ₯ i gang.

  1. Det fΓΈrste vi mΓ₯ gjΓΈre, er Γ₯ finne H3-sektoren til omrΓ₯det vi ΓΈnsker Γ₯ sΓΈke i. Til dette finnes det flere Γ₯pne lΓΈsninger, for eksempel denne: H3 Viewer.
  2. Finn omrΓ₯det du ΓΈnsker Γ₯ undersΓΈke, for eksempel Kristiansand. Etter hvert som du zoomer inn, vil du se at rutenettet av heksagoner endrer stΓΈrrelse. Velg et passende utsnitt hvor ett heksagon dekker omrΓ₯det du vil analysere. Noter Current H3 resolution ΓΈverst til hΓΈyre i skjermbildet.
  3. Klikk pΓ₯ ΓΈnsket heksagon. Denne kopieres da til utklippsboken din.
  4. KjΓΈr fΓΈlgende spΓΈrring, eventuelt tilpasset med din egen heksagon-ID og opplΓΈsning. OpplΓΈsningen spesifiserer du der det nΓ₯ stΓ₯r 4 (etter trace.h3_15), mens heksagon-ID-en settes til slutt pΓ₯ samme linje.
CREATE TABLE flights_04_08_24 AS
   SELECT * FROM read_parquet('r2://medieklyngen-radar-data/adsb/history/*/*/*/*.parquet', hive_partitioning = true)
   WHERE year = 2024 AND month = 08 AND day = 04;

SELECT r, trace.lat, trace.lon, timestamp
    FROM flights_04_08_24
    WHERE h3_cell_to_parent(trace.h3_15, 4) = '84099e9ffffffff';
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    r    β”‚    lat    β”‚   lon    β”‚      timestamp      β”‚
β”‚ varchar β”‚  double   β”‚  double  β”‚      timestamp      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ HB-JMD  β”‚ 58.188714 β”‚ 8.422119 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚  58.19067 β”‚ 8.420746 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.192532 β”‚ 8.419373 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.194626 β”‚  8.41792 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.196686 β”‚ 8.416502 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.198444 β”‚ 8.415253 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.201796 β”‚ 8.412872 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.203565 β”‚ 8.411682 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.205246 β”‚ 8.410477 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.207149 β”‚ 8.409119 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.215389 β”‚ 8.403351 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.217422 β”‚ 8.401883 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.219482 β”‚ 8.400465 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.221497 β”‚ 8.399048 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.223236 β”‚ 8.397807 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.227127 β”‚ 8.395149 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚  58.23117 β”‚ 8.392273 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚  58.25061 β”‚ 8.378581 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.254679 β”‚ 8.375702 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ HB-JMD  β”‚ 58.264176 β”‚ 8.369019 β”‚ 2024-08-04 00:00:00 β”‚
β”‚   Β·     β”‚     Β·     β”‚     Β·    β”‚          Β·          β”‚
β”‚   Β·     β”‚     Β·     β”‚     Β·    β”‚          Β·          β”‚
β”‚   Β·     β”‚     Β·     β”‚     Β·    β”‚          Β·          β”‚
β”‚ SP-LSE  β”‚ 58.239166 β”‚ 8.104278 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.250931 β”‚ 8.074951 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.253422 β”‚ 8.068726 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.271988 β”‚ 8.022589 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.273998 β”‚ 8.017548 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.276749 β”‚ 8.010717 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.284622 β”‚ 7.991136 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.285812 β”‚ 7.988212 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚  58.28829 β”‚ 7.981934 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚   58.2895 β”‚ 7.979004 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.291763 β”‚ 7.973328 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.292953 β”‚ 7.970404 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.294016 β”‚ 7.967651 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.295226 β”‚ 7.964722 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.297833 β”‚ 7.958221 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.299043 β”‚ 7.955292 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.320876 β”‚ 7.901184 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.322159 β”‚  7.89793 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚ 58.325159 β”‚ 7.890472 β”‚ 2024-08-04 00:00:00 β”‚
β”‚ SP-LSE  β”‚  58.32758 β”‚ 7.884521 β”‚ 2024-08-04 00:00:00 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 25477 rows (40 shown)                      4 columns β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

For Γ₯ fΓ₯ en rask oversikt over hvor mange ganger hvert fartΓΈy er observert i den valgte sektoren, kan vi bruke fΓΈlgende spΓΈrring:

SELECT r, COUNT(trace.timestamp) AS count
    FROM flights_04_08_24
    WHERE h3_cell_to_parent(trace.h3_15, 4) = '84099e9ffffffff'
    GROUP BY 1
    ORDER BY count DESC;
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚    r    β”‚ count β”‚
β”‚ varchar β”‚ int64 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€
β”‚ LN-ORA  β”‚ 11722 β”‚
β”‚ LN-PFD  β”‚   574 β”‚
β”‚ LN-WDK  β”‚   571 β”‚
β”‚ SE-RPE  β”‚   481 β”‚
β”‚ LN-OOX  β”‚   471 β”‚
β”‚ LN-BOG  β”‚   398 β”‚
β”‚ PH-EXW  β”‚   398 β”‚
β”‚ SE-RUC  β”‚   320 β”‚
β”‚ PH-EZX  β”‚   280 β”‚
β”‚ LN-NGM  β”‚   276 β”‚
β”‚ LN-OOU  β”‚   261 β”‚
β”‚ A6-BNG  β”‚   253 β”‚
β”‚ CS-CLA  β”‚   251 β”‚
β”‚ EI-SIU  β”‚   238 β”‚
β”‚ SE-RSO  β”‚   231 β”‚
β”‚ OY-TCG  β”‚   224 β”‚
β”‚ HA-LXI  β”‚   218 β”‚
β”‚ SE-RPF  β”‚   218 β”‚
β”‚ PH-EXP  β”‚   208 β”‚
β”‚ ES-ATC  β”‚   207 β”‚
β”‚   Β·     β”‚     Β· β”‚
β”‚   Β·     β”‚     Β· β”‚
β”‚   Β·     β”‚     Β· β”‚
β”‚ JY-BAC  β”‚    67 β”‚
β”‚ LN-WDN  β”‚    62 β”‚
β”‚ JY-BAB  β”‚    61 β”‚
β”‚ LN-WDI  β”‚    60 β”‚
β”‚ F-GRHF  β”‚    54 β”‚
β”‚ OY-RCM  β”‚    51 β”‚
β”‚ SE-DMR  β”‚    50 β”‚
β”‚ D-AGBB  β”‚    40 β”‚
β”‚ N909AD  β”‚    33 β”‚
β”‚ SE-ROI  β”‚    26 β”‚
β”‚ LN-NII  β”‚    21 β”‚
β”‚ HB-JMD  β”‚    20 β”‚
β”‚ TC-LLR  β”‚    17 β”‚
β”‚ LN-YHA  β”‚    12 β”‚
β”‚ SE-RGV  β”‚    11 β”‚
β”‚ TF-PLA  β”‚    11 β”‚
β”‚ TF-ISO  β”‚    11 β”‚
β”‚ SE-RSC  β”‚     8 β”‚
β”‚ OH-LKL  β”‚     4 β”‚
β”‚ LN-WDM  β”‚     1 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     93 rows     β”‚
β”‚   (40 shown)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Denne spΓΈrringen teller antall registreringer per fartΓΈy i den valgte sektoren. Hvis vi ΓΈnsker mer detaljerte data, kan vi utvide spΓΈrringen for Γ₯ hente ut hΓΈyde, bakkefart og posisjon:

SELECT r, trace.altitude,
  trace.ground_speed,
  ST_POINT(trace.lon, trace.lat) geom,
  trace.timestamp
FROM flights_04_08_24
WHERE h3_cell_to_parent(trace.h3_15, 4) = '84099e9ffffffff' AND r = 'LN-ORA' 
ORDER BY trace.timestamp;

Denne spΓΈrringen gir oss en detaljert tidsserie over fartΓΈyets bevegelser i den valgte sektoren.

SELECT r, trace.altitude,
  trace.ground_speed,
  ST_POINT(trace.lon, trace.lat) geom,
  trace.timestamp
FROM flights_04_08_24
WHERE h3_cell_to_parent(trace.h3_15, 4) = '84099e9ffffffff' AND r = 'LN-ORA' AND trace.timestamp > '2024-08-04 13:11:23.710000' AND trace.timestamp < '2024-08-04 16:40:02.77000'
ORDER BY trace.timestamp;
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    r    β”‚ altitude β”‚ ground_speed β”‚            geom            β”‚         timestamp          β”‚
β”‚ varchar β”‚  int64   β”‚    double    β”‚          geometry          β”‚          varchar           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ LN-ORA  β”‚     2475 β”‚         NULL β”‚ POINT (8.176663 58.294006) β”‚ 2024-08-04 13:11:28.130000 β”‚
β”‚ LN-ORA  β”‚     2500 β”‚        125.6 β”‚ POINT (8.132541 58.266632) β”‚ 2024-08-04 13:12:29.570000 β”‚
β”‚ LN-ORA  β”‚     2500 β”‚        124.8 β”‚ POINT (8.131165 58.265805) β”‚ 2024-08-04 13:12:31.480000 β”‚
β”‚ LN-ORA  β”‚     2500 β”‚        124.8 β”‚ POINT (8.124212 58.261459) β”‚ 2024-08-04 13:12:41.370000 β”‚
β”‚ LN-ORA  β”‚     2500 β”‚         NULL β”‚ POINT (8.077332 58.232241) β”‚ 2024-08-04 13:13:48.270000 β”‚
β”‚ LN-ORA  β”‚     2500 β”‚         NULL β”‚ POINT (8.075958 58.231356) β”‚ 2024-08-04 13:13:50.290000 β”‚
β”‚ LN-ORA  β”‚     2500 β”‚        123.4 β”‚ POINT (8.075306 58.230972) β”‚ 2024-08-04 13:13:51.170000 β”‚
β”‚ LN-ORA  β”‚     2500 β”‚        124.1 β”‚ POINT (8.066711 58.225584) β”‚ 2024-08-04 13:14:03.450000 β”‚
β”‚ LN-ORA  β”‚     2575 β”‚        123.3 β”‚ POINT (8.063965 58.223969) β”‚ 2024-08-04 13:14:07.390000 β”‚
β”‚ LN-ORA  β”‚     2800 β”‚        121.9 β”‚ POINT (8.053986 58.218694) β”‚ 2024-08-04 13:14:21.420000 β”‚
β”‚ LN-ORA  β”‚     3275 β”‚         96.2 β”‚ POINT (8.043676 58.214401) β”‚ 2024-08-04 13:14:36.330000 β”‚
β”‚ LN-ORA  β”‚     3550 β”‚         91.2 β”‚ POINT (8.038094 58.212112) β”‚ 2024-08-04 13:14:45.120000 β”‚
β”‚ LN-ORA  β”‚     3600 β”‚         89.0 β”‚ POINT (8.037598 58.211898) β”‚ 2024-08-04 13:14:46        β”‚
β”‚ LN-ORA  β”‚     3625 β”‚         87.6 β”‚ POINT (8.03714 58.211665)  β”‚ 2024-08-04 13:14:46.920000 β”‚
β”‚ LN-ORA  β”‚     3675 β”‚         87.6 β”‚ POINT (8.036233 58.211334) β”‚ 2024-08-04 13:14:48.400000 β”‚
β”‚ LN-ORA  β”‚     3675 β”‚         87.6 β”‚ POINT (8.035967 58.211243) β”‚ 2024-08-04 13:14:48.780000 β”‚
β”‚ LN-ORA  β”‚     3725 β”‚         84.8 β”‚ POINT (8.035436 58.210968) β”‚ 2024-08-04 13:14:49.760000 β”‚
β”‚ LN-ORA  β”‚     3775 β”‚         84.8 β”‚ POINT (8.03421 58.210501)  β”‚ 2024-08-04 13:14:51.840000 β”‚
β”‚ LN-ORA  β”‚     3800 β”‚         83.4 β”‚ POINT (8.033664 58.210281) β”‚ 2024-08-04 13:14:52.770000 β”‚
β”‚ LN-ORA  β”‚     3825 β”‚         82.0 β”‚ POINT (8.033386 58.210175) β”‚ 2024-08-04 13:14:53.150000 β”‚
β”‚   Β·     β”‚       Β·  β”‚           Β·  β”‚             Β·              β”‚             Β·              β”‚
β”‚   Β·     β”‚       Β·  β”‚           Β·  β”‚             Β·              β”‚             Β·              β”‚
β”‚   Β·     β”‚       Β·  β”‚           Β·  β”‚             Β·              β”‚             Β·              β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.5 β”‚ POINT (8.274261 58.225025) β”‚ 2024-08-04 15:47:33.300000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.6 β”‚ POINT (8.277401 58.22699)  β”‚ 2024-08-04 15:47:37.230000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.6 β”‚ POINT (8.27873 58.227814)  β”‚ 2024-08-04 15:47:39.140000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.0 β”‚ POINT (8.280059 58.228683) β”‚ 2024-08-04 15:47:41        β”‚
β”‚ LN-ORA  β”‚     2200 β”‚        135.0 β”‚ POINT (8.280856 58.229187) β”‚ 2024-08-04 15:47:41.930000 β”‚
β”‚ LN-ORA  β”‚     2200 β”‚        135.0 β”‚ POINT (8.281565 58.229645) β”‚ 2024-08-04 15:47:42.860000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.0 β”‚ POINT (8.282185 58.230057) β”‚ 2024-08-04 15:47:43.570000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        134.3 β”‚ POINT (8.282867 58.230472) β”‚ 2024-08-04 15:47:44.720000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.1 β”‚ POINT (8.284223 58.23143)  β”‚ 2024-08-04 15:47:46.570000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.1 β”‚ POINT (8.284666 58.231705) β”‚ 2024-08-04 15:47:47.120000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.2 β”‚ POINT (8.285729 58.232391) β”‚ 2024-08-04 15:47:48.590000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.9 β”‚ POINT (8.28717 58.233312)  β”‚ 2024-08-04 15:47:50.500000 β”‚
β”‚ LN-ORA  β”‚     2200 β”‚        135.9 β”‚ POINT (8.287944 58.233856) β”‚ 2024-08-04 15:47:51.380000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.9 β”‚ POINT (8.288299 58.234039) β”‚ 2024-08-04 15:47:51.980000 β”‚
β”‚ LN-ORA  β”‚     2175 β”‚        135.9 β”‚ POINT (8.289368 58.234801) β”‚ 2024-08-04 15:47:53.450000 β”‚
β”‚ LN-ORA  β”‚     2200 β”‚        135.9 β”‚ POINT (8.294403 58.238106) β”‚ 2024-08-04 15:48:00.280000 β”‚
β”‚ LN-ORA  β”‚     2200 β”‚        135.9 β”‚ POINT (8.296007 58.23912)  β”‚ 2024-08-04 15:48:02.300000 β”‚
β”‚ LN-ORA  β”‚     2200 β”‚        135.9 β”‚ POINT (8.298222 58.240585) β”‚ 2024-08-04 15:48:05.140000 β”‚
β”‚ LN-ORA  β”‚     2200 β”‚        135.9 β”‚ POINT (8.298931 58.241089) β”‚ 2024-08-04 15:48:06.400000 β”‚
β”‚ LN-ORA  β”‚     2200 β”‚        135.9 β”‚ POINT (8.302031 58.243103) β”‚ 2024-08-04 15:48:10.600000 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 8076 rows (40 shown)                                                              5 columns β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Eksport av dataΒΆ

NΓ₯r vi eksporterer data fra DuckDB for videre analyse, er det viktig Γ₯ velge riktig format basert pΓ₯ hva vi ΓΈnsker Γ₯ gjΓΈre med informasjonen. De tre mest relevante formatene i denne sammenhengen er CSV, JSON og GeoJSON, som hver har sine styrker og svakheter avhengig av bruksomrΓ₯det.

CSVΒΆ

CSV (Comma-Separated Values) er det mest tilgjengelige formatet og fungerer utmerket nΓ₯r vi ΓΈnsker Γ₯ analysere data i regnearkprogrammer som Excel. Det er et enkelt, tekstbasert format der verdier skilles med komma, noe som gjΓΈr det lett Γ₯ Γ₯pne i en rekke verktΓΈy. I Datawrapper kan vi for eksempel laste opp en CSV-fil for Γ₯ lage interaktive grafer og diagrammer, mens Everviz gir muligheter for videre visualisering. CSV er spesielt godt egnet til numeriske analyser og tabellbasert behandling, men har begrensninger nΓ₯r det gjelder Γ₯ hΓ₯ndtere komplekse datastrukturer eller geografiske koordinater utover enkle lat/lon-verdier.

JSONΒΆ

Hvis vi trenger et format som stΓΈtter mer strukturert og hierarkisk data, er JSON (JavaScript Object Notation) et bedre valg. JSON er mye brukt i nettbaserte lΓΈsninger og API-er, fordi det er lett Γ₯ tolke i programmeringssprΓ₯k som Python og JavaScript. Dette formatet egner seg godt for videre databehandling og analyse i programmeringsmiljΓΈer og gir fleksibilitet nΓ₯r vi skal hΓ₯ndtere store og sammensatte datasett. Til forskjell fra CSV kan JSON hΓ₯ndtere nestede datastrukturer, men det kan vΓ¦re vanskeligere Γ₯ lese manuelt uten et spesialisert verktΓΈy.

GeoJSONΒΆ

NΓ₯r vi jobber med geografiske data, er GeoJSON et naturlig valg. Dette formatet er en utvidelse av JSON, spesialtilpasset geospatiale data, slik at det kan lagre informasjon om punkter, linjer, polygoner og andre geografiske objekter. GeoJSON er ideelt for kartvisualiseringer og kan brukes i verktΓΈy som GeoJSON.io for en rask visuell fremstilling eller i mer avanserte lΓΈsninger som MapTiler og GIS-programmer som QGIS. Fordi GeoJSON kan inneholde bΓ₯de attributter og geometriske data i samme fil, er det spesielt nyttig nΓ₯r vi ΓΈnsker Γ₯ visualisere flytrafikk, for eksempel ved Γ₯ plotte posisjonene til et spesifikt fly i lΓΈpet av en dag. Sammenlignet med CSV er GeoJSON mer detaljert, men dette fΓΈrer ogsΓ₯ til stΓΈrre filstΓΈrrelser og mer kompleksitet.

Dataeksport i DuckDBΒΆ

Valget av eksportformat avhenger av hva slags analyse vi ΓΈnsker Γ₯ utfΓΈre. Skal vi jobbe med tall og enkle tabeller, er CSV det mest effektive. Trenger vi et fleksibelt format for videre programmering og datahΓ₯ndtering, er JSON bedre egnet. NΓ₯r det kommer til geografiske analyser og kartvisualiseringer, er GeoJSON uten tvil det beste valget. Uansett hvilket format vi velger, finnes det verktΓΈy som kan hjelpe oss med Γ₯ fΓ₯ mest mulig ut av dataene – enten det er Excel, Datawrapper, GeoJSON.io eller spesialiserte GIS-systemer.

La oss se pΓ₯ hvordan vi kan eksportere dataene i disse tre formatene fra DuckDB. Vi tar utgangspunkt i en (litt forenklet) versjon av vΓ₯r forrige spΓΈrring, men med en liten endring: Vi bruker COPY, som rammer inn spΓΈrringen vΓ₯r i parenteser.

CSVΒΆ

COPY (
  SELECT r, trace.altitude, trace.ground_speed, trace.lon, trace.lat, trace.timestamp
  FROM flights_04_08_24
  WHERE h3_cell_to_parent(trace.h3_15, 4) = '84099e9ffffffff'
    AND r = 'LN-ORA'
    AND trace.timestamp > '2024-08-04 13:11:23.710000'
    AND trace.timestamp < '2024-08-04 16:40:02.770000'
  ORDER BY trace.timestamp
) TO 'ln-ora.csv' WITH (HEADER);

Filen ln-ora.csv dukker nΓ₯ opp i mappen der du startet DuckDB, og kan Γ₯pnes i valgfritt program – for eksempel Excel eller Numbers.

Figur 1: CSV-data Γ₯pnet i Numbers pΓ₯ Mac.

JSONΒΆ

La oss prΓΈve det samme med JSON. Det er bare en ΓΈrliten justering som skal til.

COPY (
  SELECT r, trace.altitude, trace.ground_speed, trace.lon, trace.lat, trace.timestamp
  FROM flights_04_08_24
  WHERE h3_cell_to_parent(trace.h3_15, 4) = '84099e9ffffffff'
    AND r = 'LN-ORA'
    AND trace.timestamp > '2024-08-04 13:11:23.710000'
    AND trace.timestamp < '2024-08-04 16:40:02.770000'
  ORDER BY trace.timestamp
) TO 'ln-ora.json';

NΓ₯r vi Γ₯pner JSON-filen i en teksteditor, ser vi at den ikke har standard JSON-format. Dette er JSONL (JSON Lines), et format velegnet for store datamengder i sekvens. JSONL er like enkelt Γ₯ tolke programmatisk som standard JSON. Du kan lese mer om formatet pΓ₯ jsonlines.org.

GeoJSONΒΆ

For eksport av geografiske data som punkter, linjer og polygoner bruker vi GeoJSON. For Γ₯ eksportere vΓ₯re geografiske punkter som en linje (LineString), benytter vi en aggregeringsfunksjon i spΓΈrringen:

COPY (
  SELECT r, ST_MakeLine(ARRAY_AGG(ST_POINT(trace.lon, trace.lat) ORDER BY trace.timestamp)) AS geom
  FROM flights_04_08_24
  WHERE h3_cell_to_parent(trace.h3_15, 4) = '84099e9ffffffff'
    AND r = 'LN-ORA'
    AND trace.timestamp > '2024-08-04 13:11:23.710000'
    AND trace.timestamp < '2024-08-04 16:40:02.770000'
  GROUP BY r
) TO 'ln-ora.geojson' WITH (FORMAT gdal, DRIVER 'GeoJSON');

Visualisering av geodataΒΆ

NΓ₯r dataene er eksportert som CSV, JSON eller GeoJSON, er det enkelt Γ₯ visualisere dem i grafer eller kart. For grafer anbefaler vi det norske verktΓΈyet everviz, som bygger pΓ₯ teknologien fra Highsoft – et selskap fra Vik i Sogn. Datawrapper er ogsΓ₯ et godt alternativ. NΓ₯r vi lager grafer, er CSV det beste utgangsformatet.

GeoJSON.ioΒΆ

Hvis du vil visualisere data pΓ₯ et kart, er gratisverktΓΈyet GeoJSON.io et godt sted Γ₯ starte. La oss teste det ut:

  1. GΓ₯ til GeoJSON.io
  2. Klikk pΓ₯ Open, bla til mappen der DuckDB er startet fra, og velg en fil i GeoJSON-format fra datamaskinen din.
  3. Filen lastes inn i grensesnittet, og du kan utforske den nΓ¦rmere pΓ₯ kartet.

Figur 2: Data visualisert i GeoJSON.io.

DatawrapperΒΆ

Datawrapper er et mye brukt verktΓΈy blant datajournalister for Γ₯ visualisere data i grafer og kart. Med Datawrapper kan du enkelt importere data i GeoJSON-format og legge til punkter (features) og linjer (linestrings) som et lag over kartet.

Mange norske redaksjoner har tilgang til Datawrapper. Hvis du ikke jobber i en av dem, kan du likevel teste ut funksjonaliteten gratis.

NΓ₯ har du fΓ₯tt en grunnleggende innfΓΈring i noen av mulighetene som ligger i DuckDB. Ved Γ₯ kombinere disse funksjonene kan du utfΓΈre avanserte sΓΈk – begrenset kun av fantasien. La oss prΓΈve noen oppgaver:

  1. Γ…pne Datawrappers veiviser for kart
  2. Velg Locator Map. Du fΓ₯r nΓ₯ opp en firestegs prosess.
  3. Under Add markers huker du av for Import line and area markers nederst i skjermbildet.
  4. Klikk Import markers from file, bla til GeoJSON-filen din, og trykk OK.

Figur 3: Last opp GeoJSON-filen i Datawrapper.

  1. Klikk Fit map view to markers for Γ₯ tilpasse kartet til dataene dine.
  2. For Γ₯ endre farge pΓ₯ dataene, trykk pΓ₯ den horisontale markΓΈren Add region as area marker.

Figur 4: Endre farge pΓ₯ dataene.

  1. Klikk More options, og kryss av for Show exact line (slower loading time).
  2. Endre eventuelt Marker name.
  3. NΓ₯r du er fornΓΈyd, trykk Proceed.
  4. I steget Design maps velger du kartdesign og kan aktivere mΓ₯lestokk, utheve en region og mer.

Figur 5: Velg kartdesign.

  1. NΓ₯r du er fornΓΈyd, trykk Proceed.
  2. I steg tre legger du til tittel, beskrivelse, kilde og eventuelle kommentarer.
  3. NΓ₯r du er fornΓΈyd, trykk Proceed.

Figur 6: Legg inn tittel, beskrivelse, kilde og eventuelle kommentarer.

  1. NΓ₯ er du nesten i mΓ₯l! PΓ₯ neste skjermbilde klikker du pΓ₯ Publish now. Deretter fΓ₯r du en embed-kode som du kan bruke til Γ₯ legge kartet inn i en artikkel.

OppgaverΒΆ