Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Sensorjournalistikk

Analyse med DuckDB 🦆

Medieklyngen

Introduksjon

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, DuckLake 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 sensorene som deler data med fellesskapet. Alle sensorene som henter ut rådata fra luften, sender disse dataene inn til en server som løpende vasker og bearbeider dem. Deretter lagres dataene i en datasjø noen ganger i minuttet. Datasjøen vi benytter i Medieklyngen, er DuckLake – en datasjø-standard som integrerer tett med DuckDB.

I DuckLake lagres dataene i formatet Parquet, eller parkett på godt norsk. Ja, som i tregulvet. I motsetning til radbaserte formater som CSV og Excel, lagrer Parquet-formatet dataene 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. Dette gir betydelige ytelsesforbedringer sammenlignet med radbaserte formater – og er ofte 10-100 ganger raskere for analytiske spørringer.

Parquet-filene i DuckLake 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 for DuckDB å effektivt filtrere og hente ut spesifikke data, og forbedrer spørringsytelsen ved at DuckDB 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 i tillegg til 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 (Structured Query Language). 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;

Her henter man ut alle kolonner fra tabellen flights, hvor høyde over havet er større enn 10.000 (fot).


Forberedelser

Nå skal vi sette opp DuckDB og koble oss til Medieklyngens datasjø. Under går vi gjennom stegene systematisk. De følgende instruksjonene forutsetter at du har installert DuckDB på maskinen din som beskrevet i kapitlet Tekniske forberedelser. 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.

Oppstart

  1. Lag en tom mappe på valgfri plassering på datamaskinen din.

  2. Start Terminal (Mac og Linux) eller PowerShell (Windows), og naviger til mappen du akkurat opprettet.

  3. Start DuckDB med følgende kommando:

duckdb sensorkurs-andre-samling.db

Dette starter DuckDB med en tom databasefil kalt sensorkurs-andre-samling.db. Filen lagres i mappen du kjører kommandoen fra.

DuckDB svarer med et prompt som ser omtrent slik ut:

DuckDB v1.4.1 (Andium) b390a7c376
Enter ".help" for usage hints.
D 

Installer og last inn utvidelser

  1. Installer følgende utvidelser ved å skrive inn disse kommandoene i prompten (dette gjøres kun ved første gangs kjøring):

-- Installer utvidelser
INSTALL httpfs;
INSTALL postgres;
INSTALL ducklake;
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):

-- Last inn utvidelser
LOAD httpfs;
LOAD postgres;
LOAD ducklake;
LOAD spatial;
LOAD h3;
  1. Legg inn tilgangsnøkler til serveren som lagrer de historiske dataene:

-- Legg inn tilgangsnøkkel
CREATE SECRET (
    TYPE R2,
    KEY_ID '9030e0f90a86af08b08b6e2a1222a778',
    SECRET '2fe64ae1c22869400f577bb9421602f0f81a83a2f658cea6bdd556f4fc65064b',
    ACCOUNT_ID 'bca3475a0f4afeb0640daafc17ec2b18'
);

DuckDB bekrefter med:

┌─────────┐
│ Success │
│ boolean │
├─────────┤
│ true    │
└─────────┘
  1. Logg på datasjø (DuckLake):

-- Logg på datasjø
ATTACH 'ducklake:postgres:dbname=mcn_ducklake host=ep-fragrant-unit-a20fur0h.eu-central-1.aws.neon.tech user=fra_signaler_til_skup_h2_2025 password=npg_TLH6IjXdAW9q sslmode=require' AS mcn
    (DATA_PATH 'r2://mcn-ducklake', READ_ONLY);

Gratulerer! Du er nå koblet til Medieklyngens datasjø og klar til å begynne å utforske data.


Last inn data i DuckDB

Nå begynner det spennende! La oss starte med å laste ned alle kjente flybevegelser for en spesifikk dag, for eksempel 1. november 2025. Kjør følgende kommando:

SELECT * FROM mcn.airtraces WHERE timestamp >= '2025-11-01 00:00:00' and timestamp < '2025-11-02 00:00:00';

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 datasjø, pakker dem ut og importerer dem til databasen. Når det er ferdig, ser det forhåpentligvis først slik ut:

100% ▕████████████████████████████████████████████████████████████▏

Deretter listes det ut en tabell i terminalen som trolig minner om dette:

┌──────────────────────┬─────────┬──────────────┬─────────┬───┬─────────┬─────────┬───────┐
│      timestamp       │  icao   │ registration │ squawk  │ … │ is_pia  │ is_ladd │ flags │
│ timestamp with tim…  │ varchar │   varchar    │ varchar │   │ boolean │ boolean │ int32 │
├──────────────────────┼─────────┼──────────────┼─────────┼───┼─────────┼─────────┼───────┤
│ 2025-11-01 01:00:0…  │ ADA699  │ N979RP       │ 1405    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 4BA9EF  │ TC-JOO       │ 2247    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ AB6FBF  │ N836MH       │ 2013    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ ABFDD1  │ N872AN       │ 0551    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 4CAD94  │ EI-SCD       │ 0201    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 48C1A8  │ SP-RNI       │ 3567    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 4BA9EF  │ TC-JOO       │ 2247    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 478A54  │ NULL         │ NULL    │ … │ NULL    │ NULL    │  NULL │
│ 2025-11-01 01:00:0…  │ 4080E2  │ G-JZDG       │ 3257    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ AB6FBF  │ N836MH       │ 2013    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ ADA699  │ N979RP       │ 1405    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 48C1A8  │ SP-RNI       │ 3567    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 478A54  │ NULL         │ NULL    │ … │ NULL    │ NULL    │  NULL │
│ 2025-11-01 01:00:0…  │ 4CAD94  │ EI-SCD       │ 0201    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 4BA9EF  │ TC-JOO       │ 2247    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 4080E2  │ G-JZDG       │ 3257    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ ADA699  │ N979RP       │ 1405    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 48C1A8  │ SP-RNI       │ 3567    │ … │ false   │ false   │     0 │
│ 2025-11-01 01:00:0…  │ 478A6F  │ NULL         │ NULL    │ … │ NULL    │ NULL    │  NULL │
│ 2025-11-01 01:00:0…  │ 478A80  │ NULL         │ NULL    │ … │ NULL    │ NULL    │  NULL │
│          ·           │   ·     │  ·           │  ·      │ · │  ·      │  ·      │     · │
│          ·           │   ·     │  ·           │  ·      │ · │  ·      │  ·      │     · │
│          ·           │   ·     │  ·           │  ·      │ · │  ·      │  ·      │     · │
│ 2025-11-01 20:06:1…  │ 4D2529  │ 9H-WNC       │ 4321    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 45D961  │ OY-VKA       │ 2357    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 406532  │ G-EUYM       │ 6216    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4ACA87  │ SE-RTG       │ 1161    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4ACAB2  │ SE-RUR       │ 2354    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 478A3E  │ NULL         │ NULL    │ … │ NULL    │ NULL    │  NULL │
│ 2025-11-01 20:06:1…  │ 478A45  │ NULL         │ NULL    │ … │ NULL    │ NULL    │  NULL │
│ 2025-11-01 20:06:1…  │ AC72F0  │ N901AN       │ 3451    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4ACA6D  │ SE-RSM       │ 6404    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4D2529  │ 9H-WNC       │ 4321    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 48C2A2  │ SP-RZC       │ 2276    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4CA81D  │ EI-DTM       │ 1000    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 702086  │ S2-AJS       │ 5221    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4AC9F3  │ SE-ROS       │ 0202    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 406532  │ G-EUYM       │ 6216    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 45AC52  │ OY-KBR       │ 0412    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 407CB1  │ G-RUKE       │ 6217    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4D24D6  │ 9H-WDX       │ 4322    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4ACA87  │ SE-RTG       │ 1161    │ … │ false   │ false   │     0 │
│ 2025-11-01 20:06:1…  │ 4D2529  │ 9H-WNC       │ 4321    │ … │ false   │ false   │     0 │
├──────────────────────┴─────────┴──────────────┴─────────┴───┴─────────┴─────────┴───────┤
│ 3839743 rows (3.84 million rows, 40 shown)                         82 columns (7 shown) │
└─────────────────────────────────────────────────────────────────────────────────────────┘

Du har gjennomført din første spørring i DuckDB – gratulerer!

La oss bryte den opp:

Deretter ble det skrevet ut en tabell til terminalen. Hvis vi ser på nederste linje i denne, ser vi at DuckDB forteller at den akkurat har returnert 3,84 millioner rader, hvorav 40 er synlige på skjermen (de tyve første, og de tyve siste i resultatet). Videre ser vi at det er 82 kolonner, med antallet som er synlig på skjermen i parentes.

Akkurat hvor mange kolonner som vises hos deg, avhenger av oppløsningen på skjermen din.

Vi har altså hentet ned 3,84 millioner rader, og som vi så tok det noe tid å fullføre spørringen. Det er fordi vi også har hentet hele 82 kolonner per rad, noe som tilsammen utgjør nærmere 314 millioner felt med data. Det er en betydelig mengde. Er alle kolonnene like relevante? Svaret er nei. Og DuckDB har tenkt på dette. Mens data i tekstfiler og regneark er lagret radvis, er dataene i DuckLake som sagt lagret kolonnevis. I tillegg er de komprimert på en måte som gjør det enkelt å hente ut bare de dataene vi trenger. Da går spørringen vår raskere, og vi blir mer effektive. Det skal vi se på straks. Men først, la oss se nærmere på kolonnene med data vi har til rådighet i datasettet vårt. Og til det trenger vi såkalte punktum-kommandoer.

Punktum-kommandoer i DuckDB

Nå har vi allerede sett at vi ikke har plass til å liste ut hele tabellen i et skjermbilde, men det finnes en løsning for dette. Hvis vi har behov for å se flere felter, kan vi endre hvordan dataene listes ut med en såkalt punktum-kommando. For å få oversikt over disse, skriver vi .help i DuckDBs prompt. Da lister DuckDB ut en meny med alternativer.

Her er det mye å velge i. Men vi vet heldigvis hva vi leter etter, og finner kommandoen .mode. Skriv .help mode for å vise de ulike alternativene:

.mode MODE ?TABLE?       Set output mode
   MODE is one of:
     ascii     Columns/rows delimited by 0x1F and 0x1E
     box       Tables using unicode box-drawing characters
     csv       Comma-separated values
     column    Output in columns.  (See .width)
     duckbox   Tables with extensive features
     html      HTML  code
     insert    SQL insert statements for TABLE
     json      Results in a JSON array
     jsonlines Results in a NDJSON
     latex     LaTeX tabular environment code
     line      One value per line
     list      Values delimited by "|"
     markdown  Markdown table format
     quote     Escape answers as for SQL
     table     ASCII-art table
     tabs      Tab-separated values
     tcl       TCL list elements
     trash     No output

DuckDB viser oss nå ulike måter å visualisere dataene på. La oss først prøve alternativet line:

.mode line

Et dataeksempel

Deretter kjører vi spørringen på nytt, men denne gangen med en liten justering. Ser du den?

SELECT * FROM mcn.airtraces WHERE timestamp >= '2025-11-01 00:00:00' and timestamp < '2025-11-02 00:00:00' LIMIT 1;

Den lille endringen vi har gjort, er å legge inn LIMIT 1 helt til slutt i spørringen. Da returnerer DuckDB kun én eneste rad. Og siden vi har aktivert .mode line, får vi nå se alle kolonnene denne ene raden med data inneholder – pent skrevet ut i en vertikal liste:

                                        timestamp = 2025-11-01 01:00:01.021+01
                                             icao = ADA699
                                     registration = N979RP
                                           squawk = 1405
                                           flight = RPA4430
                                             type = E170
                                         category = A3
                                             year = 2005
                                      description = EMBRAER ERJ-170-100
                                            owner = REPUBLIC AIRWAYS INC
                                         latitude = 42.234696
                                        longitude = -71.213752
                              altitude_barometric = 10600
                               altitude_geometric = 9875
                                        on_ground = false
                                     ground_speed = 290.6
                              indicated_air_speed = NULL
                                    true_airspeed = NULL
                                             mach = NULL
                                            track = 264.07
                                 calculated_track = NULL
                                       track_rate = NULL
                                 magnetic_heading = NULL
                                     true_heading = NULL
                                             roll = NULL
                                  barometric_rate = 64
                                   geometric_rate = NULL
                    navigation_integrity_category = 8
navigation_integrity_category_barometric_altitude = 1
                            radius_of_containment = 186
                 navigation_accuracy_for_position = 10
                 navigation_accuracy_for_velocity = 2
                           source_integrity_level = 3
                      source_integrity_level_type = perhour
                      geometric_vertical_accuracy = 2
                          system_design_assurance = 2
                                          nav_qnh = 991.2
                          outside_air_temperature = NULL
                            total_air_temperature = NULL
                                   wind_direction = NULL
                                       wind_speed = NULL
                                 nav_altitude_mcp = 10016
                                 nav_altitude_fms = NULL
                                      nav_heading = NULL
                               nav_mode_autopilot = true
                                    nav_mode_vnav = true
                                nav_mode_alt_hold = false
                                nav_mode_approach = false
                                    nav_mode_lnav = false
                                    nav_mode_tcas = true
                                        emergency = NULL
                                            alert = 0
                     surveillance_identifier_code = 0
                                     message_type = adsb_icao
                                 mlat_derived_lat = false
                                 mlat_derived_lon = false
                                 mlat_derived_nic = false
                                  mlat_derived_rc = false
                                 tisb_derived_lat = false
                                 tisb_derived_lon = false
                                 tisb_derived_nic = false
                                  tisb_derived_rc = false
                                 flydenity_nation = United States
                            flydenity_description = general
                                   flydenity_iso2 = US
                                   flydenity_iso3 = USA
                                             h3_1 = 812a3ffffffffff
                                             h3_2 = 822a37fffffffff
                                             h3_3 = 832a33fffffffff
                                             h3_4 = 842a339ffffffff
                                             h3_5 = 852a339bfffffff
                                            h3_15 = 8f2a339a27152d0
                                             rssi = -11.1
                          rough_receiver_latitude = NULL
                         rough_receiver_longitude = NULL
                                      receiver_id = 6d23022f-adf4-4782
                              recent_receiver_ids = [6d23022f-adf4-4782]
                                      is_military = false
                                   is_interesting = false
                                           is_pia = false
                                          is_ladd = false
                                            flags = 0

Før vi glemmer det, endrer vi visningen tilbake til standardvisningen duckbox – den første tabellen vi så:

.mode duckbox

Kolonner i datasettet

Når vi nå har sett litt på feltene vi har tilgjengelig i datasettet, la oss dykke dypere i hva de gjør. Her er en fullstendig oversikt:

KolonnenavnBeskrivelse
timestampTidspunkt for når ADS-B-meldingen ble mottatt (med millisekundpresisjon)
icao24-biters ICAO flyadresse (heksadesimal)
registrationFlyets registreringsnummer (halenummer)
squawkTransponder squawk-kode (4-sifret oktal)
flightFlightnummer eller kallesignal
typeFlytypekode
categoryFlykategori (A0-A7, B0-B7, C0-C7, etc.)
yearFlyets produksjonsår
descriptionFlybeskrivelse/modell
ownerFlyeier/operatør
latitudeFlyets breddegrad i desimalgrader
longitudeFlyets lengdegrad i desimalgrader
altitude_barometricBarometrisk høyde i fot over havet
altitude_geometricGeometrisk høyde i fot over havet
on_groundSann hvis flyet er på bakken, usann hvis luftbårent
ground_speedBakkefart i knop
indicated_air_speedIndikert lufthastighet i knop
true_airspeedSann lufthastighet i knop
machMach-tall (hastighet i forhold til lydhastighet)
trackBane over bakken i grader (0-359)
calculated_trackBeregnet banevinkel i grader
track_rateEndringshastighet for bane i grader per sekund
magnetic_headingMagnetisk kurs i grader (0-359)
true_headingSann kurs i grader (0-359)
rollFlyets rullevinkel i grader (positiv = høyrekrengning)
barometric_rateBarometrisk vertikalhastighet i fot per minutt
geometric_rateGeometrisk vertikalhastighet i fot per minutt
navigation_integrity_categoryNavigation Integrity Category (NIC) - indikator for posisjonsnøyaktighet
navigation_integrity_category_barometric_altitudeNIC for barometrisk høyde
radius_of_containmentInneslutningsradius i meter - posisjonsusikkerhet
navigation_accuracy_for_positionNavigation Accuracy Category for Position (NACp)
navigation_accuracy_for_velocityNavigation Accuracy Category for Velocity (NACv)
source_integrity_levelSource Integrity Level (SIL) - indikator for dataintegritet
source_integrity_level_typeSIL-type (perhour, unknown, perflight)
geometric_vertical_accuracyGeometrisk vertikal nøyaktighet (GVA) i fot
system_design_assuranceSystem Design Assurance (SDA) nivå
nav_qnhNavigasjons-QNH trykk i hektopascal
outside_air_temperatureUtelufttemperatur i grader Celsius
total_air_temperatureTotal lufttemperatur i grader Celsius
wind_directionVindretning i grader (0-359)
wind_speedVindhastighet i knop
nav_altitude_mcpNavigasjonshøyde fra Mode Control Panel i fot
nav_altitude_fmsNavigasjonshøyde fra Flight Management System i fot
nav_headingNavigasjonskurs i grader (0-359)
nav_mode_autopilotSann hvis autopilot-modus er aktiv
nav_mode_vnavSann hvis vertikal navigasjonsmodus er aktiv
nav_mode_alt_holdSann hvis høydeholdmodus er aktiv
nav_mode_approachSann hvis innflygingsmodus er aktiv
nav_mode_lnavSann hvis lateral navigasjonsmodus er aktiv
nav_mode_tcasSann hvis Traffic Collision Avoidance System-modus er aktiv
emergencyNødsituasjonsstatus (none, general, lifeguard, minfuel, nordo, unlawful, downed, etc.)
alertVarselflagg (0=ingen varsel, 1=varsel)
surveillance_identifier_codeSurveillance Identifier Code (SPI)
message_typeType ADS-B-melding mottatt
mlat_derived_latSann hvis breddegrad ble utledet fra multilaterasjon
mlat_derived_lonSann hvis lengdegrad ble utledet fra multilaterasjon
mlat_derived_nicSann hvis NIC ble utledet fra multilaterasjon
mlat_derived_rcSann hvis inneslutningsradius ble utledet fra multilaterasjon
tisb_derived_latSann hvis breddegrad ble utledet fra TIS-B
tisb_derived_lonSann hvis lengdegrad ble utledet fra TIS-B
tisb_derived_nicSann hvis NIC ble utledet fra TIS-B
tisb_derived_rcSann hvis inneslutningsradius ble utledet fra TIS-B
flydenity_nationFlyets nasjonalitet fra Flydenity-berikelse
flydenity_descriptionFlybeskrivelse fra Flydenity-berikelse
flydenity_iso22-bokstavers landskode fra Flydenity-berikelse
flydenity_iso33-bokstavers landskode fra Flydenity-berikelse
h3_1H3 geospatial indeks ved oppløsning 1 (grov)
h3_2H3 geospatial indeks ved oppløsning 2
h3_3H3 geospatial indeks ved oppløsning 3
h3_4H3 geospatial indeks ved oppløsning 4
h3_5H3 geospatial indeks ved oppløsning 5
h3_15H3 geospatial indeks ved oppløsning 15 (fin)
rssiMottatt signalstyrkeindikator i dBFS
rough_receiver_latitudeOmtrentlig mottaker-breddegrad i desimalgrader
rough_receiver_longitudeOmtrentlig mottaker-lengdegrad i desimalgrader
receiver_idUnik identifikator for mottakeren som behandlet denne meldingen
recent_receiver_idsArray av nylige mottaker-IDer som behandlet dette flyet
is_militarySann hvis flyet er flagget som militært
is_interestingSann hvis flyet er flagget som interessant
is_piaSann hvis flyet er flagget som PIA (Position Information Available)
is_laddSann hvis flyet er flagget som LADD (Low Altitude Detection and Display)
flagsBitfelt som inneholder diverse flyflagg

Dette er et betydelig antall kolonner å forholde seg til – 82 i alt, for å være helt nøyaktig. Men selv om mange av disse kan være relevante under gitte omstendigheter, er det et mye mindre antall vi typisk forholder oss til. La oss konsentere oss om de viktigste i første omgang.

De viktigste kolonnene

Noen kolonner er viktigere enn andre. La oss ta en titt på dem her:

KolonnenavnBeskrivelse
timestampTidspunkt for når ADS-B-meldingen ble mottatt (med millisekundpresisjon)
icao24-biters ICAO flyadresse (heksadesimal)
registrationFlyets registreringsnummer (halenummer)
squawkTransponder squawk-kode (4-sifret oktal)
flightFlightnummer eller kallesignal
latitudeFlyets breddegrad i desimalgrader
longitudeFlyets lengdegrad i desimalgrader
altitude_barometricBarometrisk høyde i fot over havet
on_groundSann hvis flyet er på bakken, usann hvis luftbårent
ground_speedBakkefart i knop
trackBane over bakken i grader (0-359)

Her har vi en mer oversiktlig liste med kolonner. Og hvis vi bytter ut stjernen (*) i spørringen vår med de feltene vi ønsker å hente, ser det slik ut:

SELECT timestamp, icao, registration, squawk, flight, latitude, longitude, altitude_barometric, on_ground, ground_speed, track 
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' and timestamp < '2025-11-02 00:00:00';

Vi får et resultat som minner om dette:

┌────────────────────────────┬─────────┬──────────────┬─────────┬─────────┬──────────────┬───────────────┬─────────────────────┬───────────┬──────────────┬──────────────┐
│         timestamp          │  icao   │ registration │ squawk  │ flight  │   latitude   │   longitude   │ altitude_barometric │ on_ground │ ground_speed │    track     │
│  timestamp with time zone  │ varchar │   varchar    │ varchar │ varchar │ decimal(9,6) │ decimal(10,6) │        int32        │  boolean  │ decimal(6,1) │ decimal(5,2) │
├────────────────────────────┼─────────┼──────────────┼─────────┼─────────┼──────────────┼───────────────┼─────────────────────┼───────────┼──────────────┼──────────────┤
│ 2025-11-01 01:00:01.021+01 │ ADA699  │ N979RP       │ 1405    │ RPA4430 │    42.234696 │    -71.213752 │               10600 │ false     │        290.6 │       264.07 │
│ 2025-11-01 01:00:01.171+01 │ 4BA9EF  │ TC-JOO       │ 2247    │ THY6549 │    51.968544 │      4.425999 │               35000 │ false     │        496.3 │       118.53 │
│ 2025-11-01 01:00:01.188+01 │ AB6FBF  │ N836MH       │ 2013    │ DAL174  │    42.226976 │    -71.137944 │               33000 │ false     │        549.8 │        52.46 │
│ 2025-11-01 01:00:01.348+01 │ ABFDD1  │ N872AN       │ 0551    │ AAL70   │    42.308576 │    -71.933608 │               38975 │ false     │        570.2 │        51.62 │
│ 2025-11-01 01:00:01.348+01 │ 4CAD94  │ EI-SCD       │ 0201    │ SAS2679 │    58.758820 │     16.893340 │               18125 │ false     │        386.4 │        34.35 │
│ 2025-11-01 01:00:01.484+01 │ 48C1A8  │ SP-RNI       │ 3567    │ RYR57YL │    51.693740 │      4.843783 │               38000 │ false     │        386.7 │       285.29 │
│ 2025-11-01 01:00:01.657+01 │ 4BA9EF  │ TC-JOO       │ 2247    │ THY6549 │    51.967988 │      4.427647 │               35000 │ false     │        496.3 │       118.53 │
│ 2025-11-01 01:00:01.771+01 │ 478A54  │ NULL         │ NULL    │ BR044   │    60.299824 │      5.226697 │                NULL │ true      │          0.0 │         NULL │
│ 2025-11-01 01:00:01.876+01 │ 4080E2  │ G-JZDG       │ 3257    │ EXS8KN  │    52.167508 │      6.337228 │               38000 │ false     │        396.1 │       304.09 │
│ 2025-11-01 01:00:02.088+01 │ AB6FBF  │ N836MH       │ 2013    │ DAL174  │    42.228288 │    -71.135600 │               33000 │ false     │        549.8 │        52.46 │
│ 2025-11-01 01:00:02.088+01 │ ADA699  │ N979RP       │ 1405    │ RPA4430 │    42.234604 │    -71.215184 │               10600 │ false     │        290.6 │       264.07 │
│ 2025-11-01 01:00:02.161+01 │ 48C1A8  │ SP-RNI       │ 3567    │ RYR57YL │    51.694016 │      4.842224 │               38000 │ false     │        386.7 │       285.29 │
│ 2025-11-01 01:00:02.292+01 │ 478A54  │ NULL         │ NULL    │ BR044   │    60.299824 │      5.226698 │                NULL │ true      │          0.0 │         NULL │
│ 2025-11-01 01:00:02.427+01 │ 4CAD94  │ EI-SCD       │ 0201    │ SAS2679 │    58.760424 │     16.895466 │               18100 │ false     │        386.4 │        34.35 │
│ 2025-11-01 01:00:02.584+01 │ 4BA9EF  │ TC-JOO       │ 2247    │ THY6549 │    51.966844 │      4.431000 │               35000 │ false     │        496.3 │       118.53 │
│ 2025-11-01 01:00:02.93+01  │ 4080E2  │ G-JZDG       │ 3257    │ EXS8KN  │    52.168440 │      6.334991 │               38000 │ false     │        396.1 │       304.09 │
│ 2025-11-01 01:00:03.113+01 │ ADA699  │ N979RP       │ 1405    │ RPA4430 │    42.234420 │    -71.217376 │               10600 │ false     │        290.6 │       264.07 │
│ 2025-11-01 01:00:03.133+01 │ 48C1A8  │ SP-RNI       │ 3567    │ RYR57YL │    51.694520 │      4.839032 │               38000 │ false     │        386.7 │       285.29 │
│ 2025-11-01 01:00:03.226+01 │ 478A6F  │ NULL         │ NULL    │ BR071   │    60.298976 │      5.226556 │                NULL │ true      │          0.0 │         2.81 │
│ 2025-11-01 01:00:03.26+01  │ 478A80  │ NULL         │ NULL    │ BR088   │    60.299000 │      5.226461 │                NULL │ true      │          0.0 │         NULL │
│             ·              │   ·     │  ·           │  ·      │   ·     │        ·     │          ·    │                  ·  │  ·        │           ·  │           ·  │
│             ·              │   ·     │  ·           │  ·      │   ·     │        ·     │          ·    │                  ·  │  ·        │           ·  │           ·  │
│             ·              │   ·     │  ·           │  ·      │   ·     │        ·     │          ·    │                  ·  │  ·        │           ·  │           ·  │
│ 2025-11-01 20:06:15.369+01 │ 4D2529  │ 9H-WNC       │ 4321    │ WZZ68WL │    59.955736 │      7.539557 │               26250 │ false     │        435.8 │       301.39 │
│ 2025-11-01 20:06:15.381+01 │ 45D961  │ OY-VKA       │ 2357    │ VKG1523 │    60.271088 │      5.361423 │                5625 │ false     │        282.3 │        30.20 │
│ 2025-11-01 20:06:15.402+01 │ 406532  │ G-EUYM       │ 6216    │ BAW773  │    59.134416 │      9.834503 │               30450 │ false     │        326.3 │       215.39 │
│ 2025-11-01 20:06:15.471+01 │ 4ACA87  │ SE-RTG       │ 1161    │ NSZ5341 │    62.409120 │      6.190898 │               13350 │ false     │        293.0 │       195.03 │
│ 2025-11-01 20:06:15.471+01 │ 4ACAB2  │ SE-RUR       │ 2354    │ SAS7470 │    62.811400 │      7.171122 │                7625 │ false     │        299.7 │        38.36 │
│ 2025-11-01 20:06:15.533+01 │ 478A3E  │ NULL         │ NULL    │ BR022   │    60.293620 │      5.225988 │                NULL │ true      │          0.2 │         NULL │
│ 2025-11-01 20:06:15.533+01 │ 478A45  │ NULL         │ NULL    │ BR029   │    60.294904 │      5.230960 │                NULL │ true      │          0.2 │         NULL │
│ 2025-11-01 20:06:15.553+01 │ AC72F0  │ N901AN       │ 3451    │ AAL1411 │    42.782808 │    -71.602560 │               36000 │ false     │        355.0 │       224.09 │
│ 2025-11-01 20:06:15.56+01  │ 4ACA6D  │ SE-RSM       │ 6404    │ SAS74M  │    59.168264 │      6.273667 │               20100 │ false     │        384.0 │        59.83 │
│ 2025-11-01 20:06:15.56+01  │ 4D2529  │ 9H-WNC       │ 4321    │ WZZ68WL │    59.956376 │      7.537663 │               26200 │ false     │        435.8 │       301.39 │
│ 2025-11-01 20:06:15.591+01 │ 48C2A2  │ SP-RZC       │ 2276    │ RYR80AM │    52.223512 │      4.854889 │               39000 │ false     │        498.0 │        93.57 │
│ 2025-11-01 20:06:15.591+01 │ 4CA81D  │ EI-DTM       │ 1000    │ EJU49DK │         NULL │          NULL │                2525 │ false     │        198.0 │       246.09 │
│ 2025-11-01 20:06:15.591+01 │ 702086  │ S2-AJS       │ 5221    │ BBC202  │    51.702988 │      5.390279 │               37000 │ false     │        512.8 │       110.91 │
│ 2025-11-01 20:06:15.609+01 │ 4AC9F3  │ SE-ROS       │ 0202    │ SAS131  │    58.479904 │     16.475239 │               38000 │ false     │        354.5 │       213.38 │
│ 2025-11-01 20:06:15.727+01 │ 406532  │ G-EUYM       │ 6216    │ BAW773  │    59.133912 │      9.833862 │               30450 │ false     │        326.3 │       215.39 │
│ 2025-11-01 20:06:15.73+01  │ 45AC52  │ OY-KBR       │ 0412    │ SAS33G  │    64.152756 │     12.470032 │               38000 │ false     │        385.1 │       200.36 │
│ 2025-11-01 20:06:15.74+01  │ 407CB1  │ G-RUKE       │ 6217    │ RUK3248 │    57.924360 │      5.222936 │               39000 │ false     │        485.7 │        59.16 │
│ 2025-11-01 20:06:15.855+01 │ 4D24D6  │ 9H-WDX       │ 4322    │ WZZ99TX │    58.488876 │      8.600346 │               34325 │ false     │        445.8 │       298.98 │
│ 2025-11-01 20:06:15.856+01 │ 4ACA87  │ SE-RTG       │ 1161    │ NSZ5341 │    62.408568 │      6.190592 │               13375 │ false     │        293.0 │       195.03 │
│ 2025-11-01 20:06:15.888+01 │ 4D2529  │ 9H-WNC       │ 4321    │ WZZ68WL │    59.956880 │      7.535958 │               26175 │ false     │        435.8 │       301.39 │
├────────────────────────────┴─────────┴──────────────┴─────────┴─────────┴──────────────┴───────────────┴─────────────────────┴───────────┴──────────────┴──────────────┤
│ 3839743 rows (3.84 million rows, 40 shown)                                                                                                                  11 columns │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Antallet rader er identisk med tidligere, men vi ser at det nå er 11 kolonner i stedet for 82. Og hvis du har en litt stor skjerm, ser du kanskje alle på en gang.

En nærmere titt på kolonnene

La oss se på dataene vi nå har hentet frem. Vi legger merke til timestamp helt til venstre, som indikerer når dataene ble fanget opp av serveren med millisekundpresisjon. Vi har foreløpig ikke spesifisert sorteringen på dataene, men det kommer vi til. Kolonnen icao er selvforklarende. Det viser oss flyets ICAO-nummer, også kjent som hex-kode.

Når vi kommer til registration, er det en ting det er verdt å merke seg. Vi ser at noen av radene er markert med NULL. Det er DuckDBs måte å fortelle oss at kolonnen er tom i den aktuelle raden. Det er rett og slett ikke registrert noen verdi her, men det er helt normalt. Luftfartøyene sender nemlig ikke ut alle parameterne i samtlige sendinger.

Når vi beveger oss videre, ser vi kolonner for både squawk-koder og flight. Vi legger også merke til de essensielle feltene latitude (breddegrad), longitude (lengdegrad), samt altitude_barometric (barometrisk høyde i fot). Disse tre feltene sammen gir oss det vi trenger for å kunne fastslå fartøyets posisjon i tre dimensjoner.

Kolonnen on_ground er en såkalt boolean, eller en bolsk verdi på norsk. Det betyr at den enten er sann (true) eller usann (false). Og du har kanskje gjettet det allerede? Hvis on_ground er true, står fartøyet på bakken. Hvis verdien er false, er det i luften.

Til slutt legger vi merke til ground_speed, som indikerer fartøyets hastighet over bakken i knop, og track, som indikerer kursen.


Finn et spesifikt fartøy

Datapunktene vi har sett på til nå ble tilfeldig valgt fra en gitt dato. Vanligvis vil vi lete etter spesifikke fartøy, for eksempel et politihelikopter, eller se på trafikken i et bestemt geografisk område. I Norge har vi for tiden tre politihelikoptere, med registreringsnumrene LN-ORA, LN-ORB, og LN-ORC. La oss sjekke om vi finner et av dem i dataene fra den aktuelle dagen vår, 1. november 2025. Vi starter med en litt endret spørring:

SELECT timestamp, icao, registration 
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND registration = 'LN-ORC' 
LIMIT 1;

Dette filtrerer datasettet på registration (halenummer), slik at vi kun får data for LN-ORC, og begrenser treffet til ett datapunkt.

┌───────────────────────────┬─────────┬──────────────┐
│         timestamp         │  icao   │ registration │
│ timestamp with time zone  │ varchar │   varchar    │
├───────────────────────────┼─────────┼──────────────┤
│ 2025-11-01 15:09:47.93+01 │ 478058  │ LN-ORC       │
└───────────────────────────┴─────────┴──────────────┘

Her var det treff på første forsøk! Det ser ut til at det tredje politihelikopteret har vært ute og fløyet. Her ba vi kun om timestamp, icao og registration i spørringen vår (SELECT timestamp, icao, registration ), og det er også alt vi får i retur. Men som den observante leser kanskje har sett, har vi nå sikret oss ICAO-nummeret til dette helikopteret. Nå kan vi gjøre en ny spørring på den aktuelle datoen, med ICAO-nummeret som filter:

SELECT timestamp, icao, registration, squawk, flight, latitude, longitude, altitude_barometric, on_ground, ground_speed, track 
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND icao = '478058' 
ORDER BY timestamp ASC;

La du merke til noe nytt her? Vi har lagt til en ny kommando på slutten, nemlig ORDER BY timestamp ASC. Denne kommandoen signaliserer til DuckDB at vi ønsker å sortere resultatet stigende etter timestamp. La oss se på resultatet:

┌────────────────────────────┬─────────┬──────────────┬─────────┬─────────┬──────────────┬───────────────┬─────────────────────┬───────────┬──────────────┬──────────────┐
│         timestamp          │  icao   │ registration │ squawk  │ flight  │   latitude   │   longitude   │ altitude_barometric │ on_ground │ ground_speed │    track     │
│  timestamp with time zone  │ varchar │   varchar    │ varchar │ varchar │ decimal(9,6) │ decimal(10,6) │        int32        │  boolean  │ decimal(6,1) │ decimal(5,2) │
├────────────────────────────┼─────────┼──────────────┼─────────┼─────────┼──────────────┼───────────────┼─────────────────────┼───────────┼──────────────┼──────────────┤
│ 2025-11-01 15:09:47.93+01  │ 478058  │ LN-ORC       │ NULL    │ NULL    │    59.849184 │     10.956116 │                2800 │ false     │        134.4 │       313.19 │
│ 2025-11-01 15:09:51.748+01 │ 478058  │ LN-ORC       │ NULL    │ NULL    │    59.850768 │     10.952706 │                2750 │ false     │        133.0 │       313.17 │
│ 2025-11-01 15:09:54.859+01 │ 478058  │ LN-ORC       │ 5151    │ NULL    │    59.852096 │     10.949982 │                2725 │ false     │        133.0 │       313.17 │
│ 2025-11-01 15:09:55.788+01 │ 478058  │ LN-ORC       │ 5151    │ NULL    │    59.852508 │     10.949066 │                2700 │ false     │        133.7 │       312.88 │
│ 2025-11-01 15:09:56.771+01 │ 478058  │ LN-ORC       │ 5151    │ NULL    │    59.852960 │     10.948065 │                2700 │ false     │        134.4 │       313.19 │
│ 2025-11-01 15:10:01.855+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.855164 │     10.943390 │                2625 │ false     │        136.6 │       312.33 │
│ 2025-11-01 15:10:02.888+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.855576 │     10.942474 │                2600 │ false     │        136.6 │       312.33 │
│ 2025-11-01 15:10:05.946+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.856856 │     10.939636 │                2575 │ false     │        137.2 │       313.23 │
│ 2025-11-01 15:10:06.492+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.857100 │     10.939163 │                2575 │ false     │        137.2 │       313.23 │
│ 2025-11-01 15:10:07.094+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.857336 │     10.938689 │                2575 │ false     │        137.9 │       313.53 │
│ 2025-11-01 15:10:08.023+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.857800 │     10.937742 │                2550 │ false     │        137.9 │       313.53 │
│ 2025-11-01 15:10:08.623+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.858000 │     10.937256 │                2550 │ false     │        137.9 │       313.53 │
│ 2025-11-01 15:10:10.152+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.858636 │     10.935848 │                2550 │ false     │        138.0 │       312.94 │
│ 2025-11-01 15:10:21.951+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.863724 │     10.925079 │                2500 │ false     │        135.1 │       312.90 │
│ 2025-11-01 15:10:24.025+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.864644 │     10.923157 │                2500 │ false     │        134.4 │       313.79 │
│ 2025-11-01 15:10:41.393+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.864644 │     10.923157 │                2500 │ false     │        134.4 │       313.79 │
│ 2025-11-01 15:10:43.469+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.864644 │     10.923157 │                2500 │ false     │        134.4 │       313.79 │
│ 2025-11-01 15:10:45.216+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.875068 │     10.907719 │                2500 │ false     │        129.0 │       327.67 │
│ 2025-11-01 15:10:45.598+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.875256 │     10.907435 │                2500 │ false     │        129.0 │       327.67 │
│ 2025-11-01 15:10:46.307+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.875580 │     10.907056 │                2500 │ false     │        129.0 │       327.67 │
│             ·              │   ·     │   ·          │  ·      │   ·     │        ·     │         ·     │                  ·  │   ·       │          ·   │          ·   │
│             ·              │   ·     │   ·          │  ·      │   ·     │        ·     │         ·     │                  ·  │   ·       │          ·   │          ·   │
│             ·              │   ·     │   ·          │  ·      │   ·     │        ·     │         ·     │                  ·  │   ·       │          ·   │          ·   │
│ 2025-11-01 15:11:32.293+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897184 │     10.873260 │                2500 │ false     │        134.1 │       287.35 │
│ 2025-11-01 15:11:32.782+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897228 │     10.872676 │                2500 │ false     │        133.2 │       284.35 │
│ 2025-11-01 15:11:33.439+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897320 │     10.871919 │                2500 │ false     │        133.2 │       284.35 │
│ 2025-11-01 15:11:33.819+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897368 │     10.871445 │                2500 │ false     │        131.6 │       281.40 │
│ 2025-11-01 15:11:35.785+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897508 │     10.869232 │                2500 │ false     │        127.5 │       274.95 │
│ 2025-11-01 15:11:36.332+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897552 │     10.868500 │                2500 │ false     │        127.5 │       274.95 │
│ 2025-11-01 15:11:36.826+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897552 │     10.868042 │                2500 │ false     │        126.1 │       271.82 │
│ 2025-11-01 15:11:37.153+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897552 │     10.867467 │                2500 │ false     │        126.1 │       271.82 │
│ 2025-11-01 15:11:37.752+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897508 │     10.866943 │                2500 │ false     │        125.0 │       269.08 │
│ 2025-11-01 15:11:38.137+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897508 │     10.866486 │                2500 │ false     │        125.0 │       269.08 │
│ 2025-11-01 15:11:38.68+01  │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897508 │     10.865753 │                2500 │ false     │        124.3 │       266.31 │
│ 2025-11-01 15:11:41.795+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897232 │     10.862457 │                2500 │ false     │        121.4 │       258.60 │
│ 2025-11-01 15:12:05.554+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897232 │     10.862457 │                2500 │ false     │        109.7 │       257.36 │
│ 2025-11-01 15:12:07.083+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.894108 │     10.835645 │                2500 │ false     │        108.7 │       257.24 │
│ 2025-11-01 15:12:17.842+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.892928 │     10.825653 │                2475 │ false     │        104.1 │       256.10 │
│ 2025-11-01 15:12:24.888+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.892016 │     10.818878 │                2475 │ false     │        104.4 │       253.30 │
│ 2025-11-01 15:12:25.926+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.891876 │     10.818124 │                2475 │ false     │        103.1 │       250.16 │
│ 2025-11-01 15:12:36.798+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.889408 │     10.809979 │                2475 │ false     │         93.4 │       232.39 │
│ 2025-11-01 15:12:38.495+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.888848 │     10.808937 │                2500 │ false     │         90.5 │       225.90 │
│ 2025-11-01 15:12:45.425+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.886616 │     10.805717 │                2500 │ false     │         88.4 │       217.65 │
├────────────────────────────┴─────────┴──────────────┴─────────┴─────────┴──────────────┴───────────────┴─────────────────────┴───────────┴──────────────┴──────────────┤
│ 74 rows (40 shown)                                                                                                                                          11 columns │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

74 rader med posisjoner! Vi legger merke til at de første radene har nullverdier i kolonnene squawk og flight. Dette er ganske typisk når et fartøy kommer inn i dekningsområdet.

Vi ser at det fortsatt er 40 rader som vises, men det totale antallet 74 er i dette tilfellet så lavt at det burde gå greit å vise det på skjermen. Er det mulig? Ja, det er det. Her kommer punktumkommandoen .maxrows til nytte. La oss teste:

.maxrows 100

Så kjører vi den samme spørringen på nytt, og plutselig ser vi alle datapunktene på skjermen!

SELECT timestamp, icao, registration, squawk, flight, latitude, longitude, altitude_barometric, on_ground, ground_speed, track 
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND icao = '478058' 
ORDER BY timestamp ASC;
┌────────────────────────────┬─────────┬──────────────┬─────────┬─────────┬──────────────┬───────────────┬─────────────────────┬───────────┬──────────────┬──────────────┐
│         timestamp          │  icao   │ registration │ squawk  │ flight  │   latitude   │   longitude   │ altitude_barometric │ on_ground │ ground_speed │    track     │
│  timestamp with time zone  │ varchar │   varchar    │ varchar │ varchar │ decimal(9,6) │ decimal(10,6) │        int32        │  boolean  │ decimal(6,1) │ decimal(5,2) │
├────────────────────────────┼─────────┼──────────────┼─────────┼─────────┼──────────────┼───────────────┼─────────────────────┼───────────┼──────────────┼──────────────┤
│ 2025-11-01 15:09:47.93+01  │ 478058  │ LN-ORC       │ NULL    │ NULL    │    59.849184 │     10.956116 │                2800 │ false     │        134.4 │       313.19 │
│ 2025-11-01 15:09:51.748+01 │ 478058  │ LN-ORC       │ NULL    │ NULL    │    59.850768 │     10.952706 │                2750 │ false     │        133.0 │       313.17 │
│ 2025-11-01 15:09:54.859+01 │ 478058  │ LN-ORC       │ 5151    │ NULL    │    59.852096 │     10.949982 │                2725 │ false     │        133.0 │       313.17 │
│ 2025-11-01 15:09:55.788+01 │ 478058  │ LN-ORC       │ 5151    │ NULL    │    59.852508 │     10.949066 │                2700 │ false     │        133.7 │       312.88 │
│ 2025-11-01 15:09:56.771+01 │ 478058  │ LN-ORC       │ 5151    │ NULL    │    59.852960 │     10.948065 │                2700 │ false     │        134.4 │       313.19 │
│ 2025-11-01 15:10:01.855+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.855164 │     10.943390 │                2625 │ false     │        136.6 │       312.33 │
│ 2025-11-01 15:10:02.888+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.855576 │     10.942474 │                2600 │ false     │        136.6 │       312.33 │
│ 2025-11-01 15:10:05.946+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.856856 │     10.939636 │                2575 │ false     │        137.2 │       313.23 │
│ 2025-11-01 15:10:06.492+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.857100 │     10.939163 │                2575 │ false     │        137.2 │       313.23 │
│ 2025-11-01 15:10:07.094+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.857336 │     10.938689 │                2575 │ false     │        137.9 │       313.53 │
│ 2025-11-01 15:10:08.023+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.857800 │     10.937742 │                2550 │ false     │        137.9 │       313.53 │
│ 2025-11-01 15:10:08.623+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.858000 │     10.937256 │                2550 │ false     │        137.9 │       313.53 │
│ 2025-11-01 15:10:10.152+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.858636 │     10.935848 │                2550 │ false     │        138.0 │       312.94 │
│ 2025-11-01 15:10:21.951+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.863724 │     10.925079 │                2500 │ false     │        135.1 │       312.90 │
│ 2025-11-01 15:10:24.025+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.864644 │     10.923157 │                2500 │ false     │        134.4 │       313.79 │
│ 2025-11-01 15:10:41.393+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.864644 │     10.923157 │                2500 │ false     │        134.4 │       313.79 │
│ 2025-11-01 15:10:43.469+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.864644 │     10.923157 │                2500 │ false     │        134.4 │       313.79 │
│ 2025-11-01 15:10:45.216+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.875068 │     10.907719 │                2500 │ false     │        129.0 │       327.67 │
│ 2025-11-01 15:10:45.598+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.875256 │     10.907435 │                2500 │ false     │        129.0 │       327.67 │
│ 2025-11-01 15:10:46.307+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.875580 │     10.907056 │                2500 │ false     │        129.0 │       327.67 │
│ 2025-11-01 15:10:46.689+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.875808 │     10.906769 │                2500 │ false     │        129.0 │       327.67 │
│ 2025-11-01 15:10:48.169+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.876496 │     10.905853 │                2500 │ false     │        128.5 │       328.04 │
│ 2025-11-01 15:10:48.601+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.876792 │     10.905541 │                2500 │ false     │        128.5 │       328.04 │
│ 2025-11-01 15:10:52.155+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.878468 │     10.903457 │                2500 │ false     │        128.5 │       328.04 │
│ 2025-11-01 15:10:59.14+01  │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.882080 │     10.899078 │                2500 │ false     │        129.3 │       328.28 │
│ 2025-11-01 15:10:59.467+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.882264 │     10.898804 │                2500 │ false     │        129.3 │       328.28 │
│ 2025-11-01 15:11:09.519+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.887312 │     10.892565 │                2500 │ false     │        130.7 │       328.13 │
│ 2025-11-01 15:11:11.648+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.888432 │     10.891145 │                2500 │ false     │        131.2 │       327.76 │
│ 2025-11-01 15:11:12.685+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.888988 │     10.890482 │                2475 │ false     │        131.2 │       327.76 │
│ 2025-11-01 15:11:13.12+01  │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.889220 │     10.890198 │                2500 │ false     │        131.6 │       328.36 │
│ 2025-11-01 15:11:14.869+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.890104 │     10.889061 │                2500 │ false     │        131.6 │       328.36 │
│ 2025-11-01 15:11:15.633+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.890528 │     10.888588 │                2475 │ false     │        132.4 │       328.59 │
│ 2025-11-01 15:11:16.233+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.890848 │     10.888209 │                2500 │ false     │        132.4 │       328.59 │
│ 2025-11-01 15:11:19.292+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.892388 │     10.886315 │                2500 │ false     │        133.1 │       327.26 │
│ 2025-11-01 15:11:19.893+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.892712 │     10.885841 │                2500 │ false     │        134.0 │       325.95 │
│ 2025-11-01 15:11:20.876+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.893176 │     10.885083 │                2500 │ false     │        135.0 │       323.13 │
│ 2025-11-01 15:11:21.375+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.893432 │     10.884705 │                2475 │ false     │        135.0 │       323.13 │
│ 2025-11-01 15:11:21.86+01  │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.893688 │     10.884326 │                2475 │ false     │        135.6 │       320.09 │
│ 2025-11-01 15:11:22.297+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.893876 │     10.883947 │                2475 │ false     │        135.6 │       320.09 │
│ 2025-11-01 15:11:22.842+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.894156 │     10.883473 │                2475 │ false     │        137.2 │       316.77 │
│ 2025-11-01 15:11:23.334+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.894340 │     10.883095 │                2475 │ false     │        137.2 │       316.77 │
│ 2025-11-01 15:11:24.81+01  │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.894944 │     10.881683 │                2475 │ false     │        137.2 │       316.77 │
│ 2025-11-01 15:11:25.3+01   │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.895216 │     10.881134 │                2475 │ false     │        138.4 │       310.02 │
│ 2025-11-01 15:11:25.791+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.895356 │     10.880676 │                2475 │ false     │        138.4 │       310.02 │
│ 2025-11-01 15:11:26.283+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.895584 │     10.880035 │                2500 │ false     │        138.4 │       307.37 │
│ 2025-11-01 15:11:26.829+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.895740 │     10.879496 │                2500 │ false     │        138.7 │       304.72 │
│ 2025-11-01 15:11:27.375+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.895924 │     10.879117 │                2475 │ false     │        138.7 │       304.72 │
│ 2025-11-01 15:11:27.704+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.896040 │     10.878571 │                2500 │ false     │        138.2 │       301.39 │
│ 2025-11-01 15:11:28.413+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.896272 │     10.877930 │                2500 │ false     │        138.2 │       301.39 │
│ 2025-11-01 15:11:28.74+01  │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.896360 │     10.877472 │                2500 │ false     │        138.2 │       298.05 │
│ 2025-11-01 15:11:29.34+01  │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.896528 │     10.876844 │                2500 │ false     │        138.2 │       298.05 │
│ 2025-11-01 15:11:29.832+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.896636 │     10.876282 │                2500 │ false     │        137.0 │       294.13 │
│ 2025-11-01 15:11:30.325+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.896764 │     10.875707 │                2500 │ false     │        137.0 │       294.13 │
│ 2025-11-01 15:11:31.417+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897004 │     10.874451 │                2500 │ false     │        135.4 │       290.31 │
│ 2025-11-01 15:11:32.293+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897184 │     10.873260 │                2500 │ false     │        134.1 │       287.35 │
│ 2025-11-01 15:11:32.782+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897228 │     10.872676 │                2500 │ false     │        133.2 │       284.35 │
│ 2025-11-01 15:11:33.439+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897320 │     10.871919 │                2500 │ false     │        133.2 │       284.35 │
│ 2025-11-01 15:11:33.819+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897368 │     10.871445 │                2500 │ false     │        131.6 │       281.40 │
│ 2025-11-01 15:11:35.785+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897508 │     10.869232 │                2500 │ false     │        127.5 │       274.95 │
│ 2025-11-01 15:11:36.332+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897552 │     10.868500 │                2500 │ false     │        127.5 │       274.95 │
│ 2025-11-01 15:11:36.826+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897552 │     10.868042 │                2500 │ false     │        126.1 │       271.82 │
│ 2025-11-01 15:11:37.153+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897552 │     10.867467 │                2500 │ false     │        126.1 │       271.82 │
│ 2025-11-01 15:11:37.752+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897508 │     10.866943 │                2500 │ false     │        125.0 │       269.08 │
│ 2025-11-01 15:11:38.137+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897508 │     10.866486 │                2500 │ false     │        125.0 │       269.08 │
│ 2025-11-01 15:11:38.68+01  │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897508 │     10.865753 │                2500 │ false     │        124.3 │       266.31 │
│ 2025-11-01 15:11:41.795+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897232 │     10.862457 │                2500 │ false     │        121.4 │       258.60 │
│ 2025-11-01 15:12:05.554+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.897232 │     10.862457 │                2500 │ false     │        109.7 │       257.36 │
│ 2025-11-01 15:12:07.083+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.894108 │     10.835645 │                2500 │ false     │        108.7 │       257.24 │
│ 2025-11-01 15:12:17.842+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.892928 │     10.825653 │                2475 │ false     │        104.1 │       256.10 │
│ 2025-11-01 15:12:24.888+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.892016 │     10.818878 │                2475 │ false     │        104.4 │       253.30 │
│ 2025-11-01 15:12:25.926+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.891876 │     10.818124 │                2475 │ false     │        103.1 │       250.16 │
│ 2025-11-01 15:12:36.798+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.889408 │     10.809979 │                2475 │ false     │         93.4 │       232.39 │
│ 2025-11-01 15:12:38.495+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.888848 │     10.808937 │                2500 │ false     │         90.5 │       225.90 │
│ 2025-11-01 15:12:45.425+01 │ 478058  │ LN-ORC       │ 5151    │ HEP03   │    59.886616 │     10.805717 │                2500 │ false     │         88.4 │       217.65 │
├────────────────────────────┴─────────┴──────────────┴─────────┴─────────┴──────────────┴───────────────┴─────────────────────┴───────────┴──────────────┴──────────────┤
│ 74 rows                                                                                                                                                     11 columns │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Spennende! Men hvordan kan vi verifisere at dette faktisk er politihelikopteret? Nå vet vi jo allerede hva halenummeret er, så i akkurat dette tilfellet er saken relativt grei. Men hva om vi ikke var helt sikker på hva vi hadde å gjøre med her? Da kunne vi ha lagt til et par kolonner til med praktisk informasjon. Se denne spørringen:

SELECT timestamp, icao, registration, type, description, flydenity_nation, flydenity_description 
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND icao = '478058' 
LIMIT 1;
┌───────────────────────────┬─────────┬──────────────┬─────────┬───────────────┬──────────────────┬───────────────────────┐
│         timestamp         │  icao   │ registration │  type   │  description  │ flydenity_nation │ flydenity_description │
│ timestamp with time zone  │ varchar │   varchar    │ varchar │    varchar    │     varchar      │        varchar        │
├───────────────────────────┼─────────┼──────────────┼─────────┼───────────────┼──────────────────┼───────────────────────┤
│ 2025-11-01 15:09:47.93+01 │ 478058  │ LN-ORC       │ A169    │ AGUSTA AW-169 │ Norway           │ helicopters           │
└───────────────────────────┴─────────┴──────────────┴─────────┴───────────────┴──────────────────┴───────────────────────┘

Perfekt! Nå vet vi at dette er et norskregistrert helikopter av typen Agusta AW-169.

Analyser fartøy per registreringsland

La oss bygge videre på spørringene vi har sett på tidligere. Vi har allerede brukt denne strukturen:

SELECT timestamp, icao, registration, type, description, flydenity_nation, flydenity_description 
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND icao = '478058' 
LIMIT 1;

I datastrukturen legger vi merke til flydenity_nation, som er et felt som angir registreringslandet til de sporede fartøyene. La oss se hvilke land som er representert i datasettet på den aktuelle dagen vår, og hvor mange fly som er registrert per land:

SELECT flydenity_nation,
       COUNT(DISTINCT icao)
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00'
GROUP BY 1
ORDER BY 2 DESC;

Vi får dette resultatet:

┌──────────────────────┬──────────────────────┐
│   flydenity_nation   │ count(DISTINCT icao) │
│       varchar        │        int64         │
├──────────────────────┼──────────────────────┤
│ NULL                 │                  929 │
│ United States        │                  809 │
│ United Kingdom       │                  342 │
│ Germany              │                  218 │
│ Netherlands          │                  214 │
│ Ireland              │                  185 │
│ Sweden               │                  136 │
│ Turkey               │                  123 │
│ Malta                │                  121 │
│ Norway               │                  111 │
│ China                │                   88 │
│ United Arab Emirates │                   68 │
│ Poland               │                   62 │
│ France               │                   60 │
│ Qatar                │                   59 │
│ Canada               │                   49 │
│ Austria              │                   47 │
│ Finland              │                   47 │
│ Denmark              │                   38 │
│ Russia               │                   37 │
│   ·                  │                    · │
│   ·                  │                    · │
│   ·                  │                    · │
│ Brazil               │                    2 │
│ Mexico               │                    2 │
│ Serbia               │                    2 │
│ Bulgaria             │                    2 │
│ Malaysia             │                    2 │
│ San Marino           │                    2 │
│ Isle of Man          │                    2 │
│ Bangladesh           │                    2 │
│ Kenya                │                    2 │
│ Sri Lanka            │                    2 │
│ Pakistan             │                    2 │
│ Georgia              │                    1 │
│ Algeria              │                    1 │
│ Mauritius            │                    1 │
│ Indonesia            │                    1 │
│ Venezuela            │                    1 │
│ Turkmenistan         │                    1 │
│ Moldova              │                    1 │
│ Israel               │                    1 │
│ Dominican Republic   │                    1 │
├──────────────────────┴──────────────────────┤
│ 72 rows (40 shown)                2 columns │
└─────────────────────────────────────────────┘

Hvordan fungerer denne spørringen?

I spørringen over har vi introdusert et par nye konsepter. La oss bryte det ned:

Den mest kompliserte delen her er COUNT(DISTINCT icao). COUNT er en innebyggd funksjon i DuckDB, som lar oss gjøre en telleoperasjon direkte i spørringen. Nøkkelordet DISTINCT signaliserer at vi er ute etter unike ICAO-nummer. Vi henter altså ikke ut alle ICAO-numre i alle radene våre her, men vi teller hvor mange unike det finnes.

For å illustrere forskjellen:

Etter hvert skal vi se på enda mer avanserte funksjoner vi kan skrive direkte inn i SQL-spørringer, men allerede nå aner vi konturene av noe kraftfullt.

Tolkning av resultatet

La oss se på resultatet. Vi ser at det er 72 rader, hvorav 71 er nasjoner. Hvordan kan dette ha seg? På toppen troner nemlig NULL. Dataene i kolonnen er hentet fra en ekstern database som heter Flydenity, og det er en del fly som ikke er registrert med nasjon her.

Hvis vi ønsker å kun vise registrerte land, kan vi filtrere bort NULL-verdiene ved å legge til betingelsen IS NOT NULL:

SELECT flydenity_nation,
       COUNT(DISTINCT icao)
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00'
  AND flydenity_nation IS NOT NULL
GROUP BY 1
ORDER BY 2 DESC;
┌──────────────────────┬──────────────────────┐
│   flydenity_nation   │ count(DISTINCT icao) │
│       varchar        │        int64         │
├──────────────────────┼──────────────────────┤
│ United States        │                  809 │
│ United Kingdom       │                  342 │
│ Germany              │                  218 │
│ Netherlands          │                  214 │
│ Ireland              │                  185 │
│ Sweden               │                  136 │
│ Turkey               │                  123 │
│ Malta                │                  121 │
│ Norway               │                  111 │
│ China                │                   88 │
│ United Arab Emirates │                   68 │
│ Poland               │                   62 │
│ France               │                   60 │
│ Qatar                │                   59 │
│ Canada               │                   49 │
│ Finland              │                   47 │
│ Austria              │                   47 │
│ Denmark              │                   38 │
│ Russia               │                   37 │
│ Portugal             │                   35 │
│    ·                 │                    · │
│    ·                 │                    · │
│    ·                 │                    · │
│ Bulgaria             │                    2 │
│ Sri Lanka            │                    2 │
│ Pakistan             │                    2 │
│ Kenya                │                    2 │
│ Serbia               │                    2 │
│ Isle of Man          │                    2 │
│ San Marino           │                    2 │
│ Brazil               │                    2 │
│ Malaysia             │                    2 │
│ Mexico               │                    2 │
│ Bangladesh           │                    2 │
│ Algeria              │                    1 │
│ Indonesia            │                    1 │
│ Georgia              │                    1 │
│ Turkmenistan         │                    1 │
│ Mauritius            │                    1 │
│ Moldova              │                    1 │
│ Venezuela            │                    1 │
│ Dominican Republic   │                    1 │
│ Israel               │                    1 │
├──────────────────────┴──────────────────────┤
│ 71 rows (40 shown)                2 columns │
└─────────────────────────────────────────────┘

IS NOT NULL er SQL-syntaks for å sjekke at en kolonne har en verdi (altså ikke er tom). Dette er nyttig når vi kun vil arbeide med komplette data.

Liste ut fartøy basert på nasjonalitet

La oss ta utgangspunkt i de flyene som har kjent nasjonalitet. Basert på dette resultatet kan vi nå endre spørringen til å få en oversikt over alle unike fartøy fra en spesifikk nasjon, for eksempel Russland:

SELECT DISTINCT icao,
  registration,
  flydenity_nation
FROM mcn.airtraces
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND flydenity_nation = 'Russia'
ORDER BY 2;

Denne spørringen ber om unike ICAO-nummer fra vår aktuelle dag hvor fartøyet er registrert i Russland, sortert etter registreringsnummer. Resultatet er som følger:

┌─────────┬──────────────┬──────────────────┐
│  icao   │ registration │ flydenity_nation │
│ varchar │   varchar    │     varchar      │
├─────────┼──────────────┼──────────────────┤
│ 150657  │ RA-67159     │ Russia           │
│ 151DF9  │ RA-73209     │ Russia           │
│ 151E0C  │ RA-73228     │ Russia           │
│ 151E15  │ RA-73237     │ Russia           │
│ 151E17  │ RA-73239     │ Russia           │
│ 151E39  │ RA-73273     │ Russia           │
│ 151E3B  │ RA-73275     │ Russia           │
│ 151E53  │ RA-73299     │ Russia           │
│ 151E66  │ RA-73318     │ Russia           │
│ 151E6B  │ RA-73323     │ Russia           │
│ 151E6D  │ RA-73325     │ Russia           │
│ 151EFA  │ RA-73466     │ Russia           │
│ 151F0C  │ RA-73484     │ Russia           │
│ 151F0D  │ RA-73485     │ Russia           │
│ 151FB8  │ RA-73656     │ Russia           │
│ 151FB9  │ RA-73657     │ Russia           │
│ 151FBD  │ RA-73661     │ Russia           │
│ 151FC0  │ RA-73664     │ Russia           │
│ 151FF0  │ RA-73712     │ Russia           │
│ 151FF3  │ RA-73715     │ Russia           │
│ 151FF5  │ RA-73717     │ Russia           │
│ 151FFF  │ RA-73727     │ Russia           │
│ 152001  │ RA-73729     │ Russia           │
│ 15200A  │ RA-73738     │ Russia           │
│ 152029  │ RA-73769     │ Russia           │
│ 152040  │ RA-73792     │ Russia           │
│ 15204B  │ RA-73803     │ Russia           │
│ 15205F  │ RA-73823     │ Russia           │
│ 152064  │ RA-73828     │ Russia           │
│ 152077  │ RA-73847     │ Russia           │
│ 155BE3  │ RA-89059     │ Russia           │
│ 155BE5  │ RA-89061     │ Russia           │
│ 155C1A  │ RA-89114     │ Russia           │
│ 155C26  │ RA-89126     │ Russia           │
│ 155C2C  │ RA-89132     │ Russia           │
│ 155C3C  │ RA-89148     │ Russia           │
│ 155C51  │ RA-89169     │ Russia           │
├─────────┴──────────────┴──────────────────┤
│ 37 rows                         3 columns │
└───────────────────────────────────────────┘

Her ser vi 37 unike russiskregistrerte fly som ble observert 1. november 2025. Legg merke til at vi denne gangen bruker DISTINCT i SELECT-delen for å sikre at hvert fly kun vises én gang, selv om det kan ha blitt observert mange ganger i løpet av dagen.


Geografiske søk

Nå har vi sett på hvordan vi kan hente ut informasjon om et spesifikt luftfartøy, og fartøy registrert i ulike nasjoner. Men hva om vi ikke vet akkurat hva vi ser etter, og har behov for å jobbe i en bestemt region?

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 Uber.

Forberedelser: Lag en lokal tabell

Før vi gjør geografiske søk, må vi legge til et viktig steg. Til nå har vi jobbet direkte mot Medieklyngens datasjø, noe som fungerer fint for enkle operasjoner. Men når vi skal gjøre mer avanserte geografiske analyser med spatial-funksjoner, må vi laste dataene ned lokalt først. Siden DuckLake er en relativt ny løsning, er ikke alle DuckDBs funksjoner støttet direkte i datasjøen enda.

Vi laster ned dataene slik:

CREATE TABLE flights_02_11_25 AS
    SELECT timestamp, icao, registration, squawk, flight, latitude, longitude, 
           altitude_barometric, on_ground, ground_speed, track 
    FROM mcn.airtraces 
    WHERE timestamp >= '2025-11-02 00:00:00' 
      AND timestamp < '2025-11-03 00:00:00' 
    ORDER BY timestamp ASC;

Som vi ser, er spørringen tilnærmet identisk til dem vi kjenner godt fra tidligere. To ting er endret:

Dataene lagres i den lokale tabellen flights_02_11_25, som bor i filen vi spesifiserte da vi startet opp DuckDB tidligere (sensorkurs-andre-samling.db) på din datamaskin. Nå kan vi spørre på dataene helt lokalt i fortsettelsen.

Administrere lokale tabeller

Etter hvert som vi jobber, kan den lokale databasefilen på maskinen din bli fylt opp med en rekke tabeller. For å se hvilke tabeller som finnes, kan vi kjøre følgende kommando:

SHOW TABLES;

Da får vi opp en liste med de lokale tabellene våre:

┌──────────────────┐
│       name       │
│     varchar      │
├──────────────────┤
│ flights_02_11_25 │
└──────────────────┘

Punkt og radius

Når man vurderer måter å avgrense et søk geografisk på, dukker kanskje passeren opp i tankene. 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.

La oss si at vi vil søke etter alle fly innenfor en radius på 1 kilometer fra Bergen lufthavn, Flesland. Funksjonen ST_Distance_Sphere bruker haversine-formelen som tar høyde for jordens kurvatur og gir oss storsirkelavstanden – den korteste avstanden mellom to punkter målt langs jordoverflaten. Se følgende spørring:

SELECT timestamp, icao, registration, longitude, latitude
FROM flights_02_11_25
WHERE ST_Distance_Sphere(
    ST_Point(latitude, longitude), 
    ST_Point(60.293389, 5.218056)  -- Fleslands koordinater
) <= 1000; -- meter
┌────────────────────────────┬─────────┬──────────────┬───────────────┬──────────────┐
│         timestamp          │  icao   │ registration │   longitude   │   latitude   │
│  timestamp with time zone  │ varchar │   varchar    │ decimal(10,6) │ decimal(9,6) │
├────────────────────────────┼─────────┼──────────────┼───────────────┼──────────────┤
│ 2025-11-02 00:00:01.092+01 │ 478A3A  │ NULL         │      5.231410 │    60.294812 │
│ 2025-11-02 00:00:02.941+01 │ 478A3A  │ NULL         │      5.231405 │    60.294808 │
│ 2025-11-02 00:00:02.941+01 │ 478A88  │ NULL         │      5.231481 │    60.295016 │
│ 2025-11-02 00:00:03.109+01 │ 478A3A  │ NULL         │      5.231386 │    60.294800 │
│ 2025-11-02 00:00:03.109+01 │ 478A88  │ NULL         │      5.231479 │    60.295016 │
│ 2025-11-02 00:00:03.512+01 │ 478A3A  │ NULL         │      5.231380 │    60.294796 │
│ 2025-11-02 00:00:04.522+01 │ 478A44  │ NULL         │      5.231331 │    60.294864 │
│ 2025-11-02 00:00:04.522+01 │ 478A3A  │ NULL         │      5.231363 │    60.294800 │
│ 2025-11-02 00:00:06.5+01   │ 478A88  │ NULL         │      5.231479 │    60.295016 │
│ 2025-11-02 00:00:06.5+01   │ 478A3A  │ NULL         │      5.231380 │    60.294796 │
│ 2025-11-02 00:00:06.938+01 │ 478A3A  │ NULL         │      5.231363 │    60.294800 │
│ 2025-11-02 00:00:06.938+01 │ 478A88  │ NULL         │      5.231481 │    60.295016 │
│ 2025-11-02 00:00:06.938+01 │ 478A44  │ NULL         │      5.231339 │    60.294868 │
│ 2025-11-02 00:00:07.053+01 │ 478A3A  │ NULL         │      5.231339 │    60.294800 │
│ 2025-11-02 00:00:08.639+01 │ 478A44  │ NULL         │      5.231331 │    60.294864 │
│ 2025-11-02 00:00:08.639+01 │ 478A3A  │ NULL         │      5.231331 │    60.294796 │
│ 2025-11-02 00:00:08.639+01 │ 478A88  │ NULL         │      5.231479 │    60.295016 │
│ 2025-11-02 00:00:09.497+01 │ 478A88  │ NULL         │      5.231481 │    60.295016 │
│ 2025-11-02 00:00:09.497+01 │ 478A3A  │ NULL         │      5.231339 │    60.294800 │
│ 2025-11-02 00:00:10.826+01 │ 478A3A  │ NULL         │      5.231331 │    60.294796 │
│             ·              │   ·     │  ·           │          ·    │        ·     │
│             ·              │   ·     │  ·           │          ·    │        ·     │
│             ·              │   ·     │  ·           │          ·    │        ·     │
│ 2025-11-02 23:59:51.827+01 │ 478A3A  │ NULL         │      5.231363 │    60.294744 │
│ 2025-11-02 23:59:51.827+01 │ 478A88  │ NULL         │      5.231386 │    60.294984 │
│ 2025-11-02 23:59:53.083+01 │ 478A3A  │ NULL         │      5.231356 │    60.294740 │
│ 2025-11-02 23:59:53.083+01 │ 478A44  │ NULL         │      5.231062 │    60.294796 │
│ 2025-11-02 23:59:53.083+01 │ 478A45  │ NULL         │      5.230747 │    60.294928 │
│ 2025-11-02 23:59:53.748+01 │ 478A88  │ NULL         │      5.231405 │    60.294984 │
│ 2025-11-02 23:59:54.284+01 │ 478A88  │ NULL         │      5.231386 │    60.294984 │
│ 2025-11-02 23:59:56.156+01 │ 478A88  │ NULL         │      5.231405 │    60.294984 │
│ 2025-11-02 23:59:56.156+01 │ 478A3A  │ NULL         │      5.231380 │    60.294752 │
│ 2025-11-02 23:59:56.252+01 │ 478A45  │ NULL         │      5.230747 │    60.294928 │
│ 2025-11-02 23:59:57.081+01 │ 478A45  │ NULL         │      5.230747 │    60.294912 │
│ 2025-11-02 23:59:57.081+01 │ 478A44  │ NULL         │      5.231102 │    60.294788 │
│ 2025-11-02 23:59:57.191+01 │ 478A3A  │ NULL         │      5.231380 │    60.294760 │
│ 2025-11-02 23:59:57.275+01 │ 478A44  │ NULL         │      5.231086 │    60.294784 │
│ 2025-11-02 23:59:57.756+01 │ 478A45  │ NULL         │      5.230743 │    60.294912 │
│ 2025-11-02 23:59:58.896+01 │ 478A44  │ NULL         │      5.231111 │    60.294784 │
│ 2025-11-02 23:59:59.039+01 │ 478A44  │ NULL         │      5.231102 │    60.294788 │
│ 2025-11-02 23:59:59.505+01 │ 478A3A  │ NULL         │      5.231386 │    60.294752 │
│ 2025-11-02 23:59:59.505+01 │ 478A45  │ NULL         │      5.230747 │    60.294912 │
│ 2025-11-02 23:59:59.876+01 │ 478A3A  │ NULL         │      5.231380 │    60.294752 │
├────────────────────────────┴─────────┴──────────────┴───────────────┴──────────────┤
│ 142136 rows (40 shown)                                                   5 columns │
└────────────────────────────────────────────────────────────────────────────────────┘

La oss bryte ned denne spørringen. Første delen er kjent stoff, men etter WHERE begynner det å balle på seg. Her bruker vi geo-funksjonen ST_Distance_Sphere, 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 1000 meter fra det geografiske punktet 5.218056, 60.293389 (Bergen lufthavn, Flesland)».


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.

La oss søke etter alle fly i et område rundt Bergen:

SELECT timestamp, icao, registration, longitude, latitude
FROM flights_02_11_25
WHERE (longitude BETWEEN 5.2 AND 5.4) AND (latitude BETWEEN 60.2 AND 60.4);
┌────────────────────────────┬─────────┬──────────────┬───────────────┬──────────────┐
│         timestamp          │  icao   │ registration │   longitude   │   latitude   │
│  timestamp with time zone  │ varchar │   varchar    │ decimal(10,6) │ decimal(9,6) │
├────────────────────────────┼─────────┼──────────────┼───────────────┼──────────────┤
│ 2025-11-02 00:00:01.092+01 │ 478A3A  │ NULL         │      5.231410 │    60.294812 │
│ 2025-11-02 00:00:02.941+01 │ 478A3A  │ NULL         │      5.231405 │    60.294808 │
│ 2025-11-02 00:00:02.941+01 │ 478A88  │ NULL         │      5.231481 │    60.295016 │
│ 2025-11-02 00:00:03.109+01 │ 478A3A  │ NULL         │      5.231386 │    60.294800 │
│ 2025-11-02 00:00:03.109+01 │ 478A88  │ NULL         │      5.231479 │    60.295016 │
│ 2025-11-02 00:00:03.512+01 │ 478A3A  │ NULL         │      5.231380 │    60.294796 │
│ 2025-11-02 00:00:04.522+01 │ 478A44  │ NULL         │      5.231331 │    60.294864 │
│ 2025-11-02 00:00:04.522+01 │ 478A3A  │ NULL         │      5.231363 │    60.294800 │
│ 2025-11-02 00:00:06.5+01   │ 478A88  │ NULL         │      5.231479 │    60.295016 │
│ 2025-11-02 00:00:06.5+01   │ 478A3A  │ NULL         │      5.231380 │    60.294796 │
│ 2025-11-02 00:00:06.938+01 │ 478A3A  │ NULL         │      5.231363 │    60.294800 │
│ 2025-11-02 00:00:06.938+01 │ 478A88  │ NULL         │      5.231481 │    60.295016 │
│ 2025-11-02 00:00:06.938+01 │ 478A44  │ NULL         │      5.231339 │    60.294868 │
│ 2025-11-02 00:00:07.053+01 │ 478A3A  │ NULL         │      5.231339 │    60.294800 │
│ 2025-11-02 00:00:08.639+01 │ 478A44  │ NULL         │      5.231331 │    60.294864 │
│ 2025-11-02 00:00:08.639+01 │ 478A3A  │ NULL         │      5.231331 │    60.294796 │
│ 2025-11-02 00:00:08.639+01 │ 478A88  │ NULL         │      5.231479 │    60.295016 │
│ 2025-11-02 00:00:09.497+01 │ 478A88  │ NULL         │      5.231481 │    60.295016 │
│ 2025-11-02 00:00:09.497+01 │ 478A3A  │ NULL         │      5.231339 │    60.294800 │
│ 2025-11-02 00:00:10.826+01 │ 478A3A  │ NULL         │      5.231331 │    60.294796 │
│             ·              │   ·     │  ·           │          ·    │        ·     │
│             ·              │   ·     │  ·           │          ·    │        ·     │
│             ·              │   ·     │  ·           │          ·    │        ·     │
│ 2025-11-02 23:59:51.827+01 │ 478A3A  │ NULL         │      5.231363 │    60.294744 │
│ 2025-11-02 23:59:51.827+01 │ 478A88  │ NULL         │      5.231386 │    60.294984 │
│ 2025-11-02 23:59:53.083+01 │ 478A3A  │ NULL         │      5.231356 │    60.294740 │
│ 2025-11-02 23:59:53.083+01 │ 478A44  │ NULL         │      5.231062 │    60.294796 │
│ 2025-11-02 23:59:53.083+01 │ 478A45  │ NULL         │      5.230747 │    60.294928 │
│ 2025-11-02 23:59:53.748+01 │ 478A88  │ NULL         │      5.231405 │    60.294984 │
│ 2025-11-02 23:59:54.284+01 │ 478A88  │ NULL         │      5.231386 │    60.294984 │
│ 2025-11-02 23:59:56.156+01 │ 478A88  │ NULL         │      5.231405 │    60.294984 │
│ 2025-11-02 23:59:56.156+01 │ 478A3A  │ NULL         │      5.231380 │    60.294752 │
│ 2025-11-02 23:59:56.252+01 │ 478A45  │ NULL         │      5.230747 │    60.294928 │
│ 2025-11-02 23:59:57.081+01 │ 478A45  │ NULL         │      5.230747 │    60.294912 │
│ 2025-11-02 23:59:57.081+01 │ 478A44  │ NULL         │      5.231102 │    60.294788 │
│ 2025-11-02 23:59:57.191+01 │ 478A3A  │ NULL         │      5.231380 │    60.294760 │
│ 2025-11-02 23:59:57.275+01 │ 478A44  │ NULL         │      5.231086 │    60.294784 │
│ 2025-11-02 23:59:57.756+01 │ 478A45  │ NULL         │      5.230743 │    60.294912 │
│ 2025-11-02 23:59:58.896+01 │ 478A44  │ NULL         │      5.231111 │    60.294784 │
│ 2025-11-02 23:59:59.039+01 │ 478A44  │ NULL         │      5.231102 │    60.294788 │
│ 2025-11-02 23:59:59.505+01 │ 478A3A  │ NULL         │      5.231386 │    60.294752 │
│ 2025-11-02 23:59:59.505+01 │ 478A45  │ NULL         │      5.230747 │    60.294912 │
│ 2025-11-02 23:59:59.876+01 │ 478A3A  │ NULL         │      5.231380 │    60.294752 │
├────────────────────────────┴─────────┴──────────────┴───────────────┴──────────────┤
│ 190905 rows (40 shown)                                                   5 columns │
└────────────────────────────────────────────────────────────────────────────────────┘

Denne metoden er litt mer lettlest enn den forrige. Her ber vi om alle datapunkter som ligger mellom lengdegradene 5.2 og 5.4, samt mellom breddegradene 60.2 og 60.4.


Sammenligning av geografiske søkemetoder

Vi har nå sett på to ulike måter å søke geografisk i flydata. Før vi går videre til den tredje metoden (H3), la oss sammenligne:

MetodeBeste brukFordelerUlemper
Punkt og radiusSøk rundt et spesifikt sted (f.eks. en flyplass eller landingsområde)Enkel å forstå, presis sirkulær avgrensningKan være treg på store datasett
AvgrensningsboksSøk i rektangulære områderRask, enkel SQL-syntaksRektangulær form passer ikke alle bruksområder
H3 (kommer nå)Effektive søk og aggregeringer over store områderMeget rask, hierarkisk, god for analyserKrever forståelse av heksagonsystemet

I tillegg til disse metodene 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 nå utforske H3 – en svært effektiv metode for geografiske søk.


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 og i ulike oppløsninger.

Hvorfor heksagoner?

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.

Oppløsninger og hierarki

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.

H3 i Medieklyngens datasjø

Det er mulig å oversette vanlige koordinater til H3-heksagoner direkte i DuckDB ved hjelp av innebygde funksjoner. I Medieklyngens datasjø har vi imidlertid allerede gjort denne jobben, for å sikre mest mulig effektive spørringer.

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


Bruk av H3 i praksis

  1. Finn H3-sektoren du ønsker å søke i. Til dette finnes det flere åpne løsninger, for eksempel H3 Viewer.

  2. Finn området du ønsker å undersøke, for eksempel Bergen. Etter hvert som du zoomer inn på kartet, 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. Dennes ID kopieres da til utklippsboken din.

  4. Kjør følgende spørring, tilpasset med din egen heksagon-ID og oppløsning. Oppløsningen spesifiserer du der det nå står 4 (etter h3_cell_to_parent), mens heksagon-ID-en settes til slutt på samme linje.

SELECT registration, latitude, longitude, timestamp
FROM mcn.airtraces
WHERE timestamp >= '2025-11-02 00:00:00' 
  AND timestamp < '2025-11-03 00:00:00'
  AND h3_cell_to_parent(h3_15, 4) = '84099e9ffffffff';
┌──────────────┬──────────────┬───────────────┬────────────────────────────┐
│ registration │   latitude   │   longitude   │         timestamp          │
│   varchar    │ decimal(9,6) │ decimal(10,6) │  timestamp with time zone  │
├──────────────┼──────────────┼───────────────┼────────────────────────────┤
│ B-303Z       │    58.303976 │      8.286438 │ 2025-11-02 05:06:52.133+01 │
│ B-303Z       │    58.303344 │      8.284843 │ 2025-11-02 05:06:52.625+01 │
│ B-303Z       │    58.301788 │      8.280945 │ 2025-11-02 05:06:53.991+01 │
│ B-303Z       │    58.301148 │      8.279350 │ 2025-11-02 05:06:54.537+01 │
│ B-303Z       │    58.300624 │      8.278107 │ 2025-11-02 05:06:55.071+01 │
│ B-303Z       │    58.299928 │      8.276276 │ 2025-11-02 05:06:55.63+01  │
│ B-303Z       │    58.297896 │      8.271199 │ 2025-11-02 05:06:57.431+01 │
│ B-303Z       │    58.297304 │      8.269781 │ 2025-11-02 05:06:57.871+01 │
│ B-303Z       │    58.295504 │      8.265289 │ 2025-11-02 05:06:59.457+01 │
│ B-303Z       │    58.294968 │      8.264022 │ 2025-11-02 05:06:59.837+01 │
│ B-303Z       │    58.293092 │      8.259415 │ 2025-11-02 05:07:01.517+01 │
│ B-303Z       │    58.291176 │      8.254669 │ 2025-11-02 05:07:03.059+01 │
│ B-303Z       │    58.290528 │      8.253036 │ 2025-11-02 05:07:03.735+01 │
│ B-303Z       │    58.289016 │      8.249315 │ 2025-11-02 05:07:05.074+01 │
│ B-303Z       │    58.288328 │      8.247543 │ 2025-11-02 05:07:05.61+01  │
│ B-303Z       │    58.287644 │      8.245771 │ 2025-11-02 05:07:06.21+01  │
│ B-303Z       │    58.287048 │      8.244353 │ 2025-11-02 05:07:06.718+01 │
│ B-303Z       │    58.286452 │      8.242936 │ 2025-11-02 05:07:07.155+01 │
│ B-303Z       │    58.285996 │      8.241695 │ 2025-11-02 05:07:07.632+01 │
│ B-303Z       │    58.306368 │      8.292374 │ 2025-11-02 05:06:50.114+01 │
│   ·          │        ·     │          ·    │             ·              │
│   ·          │        ·     │          ·    │             ·              │
│   ·          │        ·     │          ·    │             ·              │
│ LN-WDN       │    58.177356 │      8.049774 │ 2025-11-02 23:29:50.621+01 │
│ LN-WDN       │    58.177912 │      8.050507 │ 2025-11-02 23:29:51.769+01 │
│ LN-WDN       │    58.165392 │      8.033936 │ 2025-11-02 23:29:28.067+01 │
│ LN-WDN       │    58.165672 │      8.034302 │ 2025-11-02 23:29:28.667+01 │
│ LN-WDN       │    58.165952 │      8.034668 │ 2025-11-02 23:29:29.049+01 │
│ LN-WDN       │    58.166200 │      8.034993 │ 2025-11-02 23:29:29.596+01 │
│ LN-WDN       │    58.166508 │      8.035400 │ 2025-11-02 23:29:30.142+01 │
│ LN-WDN       │    58.166784 │      8.035767 │ 2025-11-02 23:29:30.634+01 │
│ LN-WDN       │    58.168464 │      8.037872 │ 2025-11-02 23:29:33.747+01 │
│ LN-WDN       │    58.171072 │      8.041351 │ 2025-11-02 23:29:38.662+01 │
│ LN-WDN       │    58.174256 │      8.045625 │ 2025-11-02 23:29:44.67+01  │
│ LN-WDN       │    58.174760 │      8.046334 │ 2025-11-02 23:29:45.707+01 │
│ LN-WDN       │    58.175264 │      8.046936 │ 2025-11-02 23:29:46.527+01 │
│ LN-WDN       │    58.175536 │      8.047308 │ 2025-11-02 23:29:47.185+01 │
│ LN-WDN       │    58.176848 │      8.049042 │ 2025-11-02 23:29:49.641+01 │
│ LN-WDN       │    58.178472 │      8.051239 │ 2025-11-02 23:29:52.805+01 │
│ LN-WDN       │    58.179016 │      8.052004 │ 2025-11-02 23:29:53.897+01 │
│ LN-WDN       │    58.181580 │      8.055459 │ 2025-11-02 23:29:58.976+01 │
│ LN-WDN       │    58.184060 │      8.058746 │ 2025-11-02 23:30:03.837+01 │
│ LN-WDN       │    58.185132 │      8.060120 │ 2025-11-02 23:30:05.914+01 │
├──────────────┴──────────────┴───────────────┴────────────────────────────┤
│ 10683 rows (40 shown)                                          4 columns │
└──────────────────────────────────────────────────────────────────────────┘

Aggregering med H3

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 registration, COUNT(icao) as count
FROM mcn.airtraces
WHERE timestamp >= '2025-11-02 00:00:00' 
  AND timestamp < '2025-11-03 00:00:00'
  AND h3_4 = '84099e9ffffffff'
GROUP BY registration
ORDER BY count DESC;
┌──────────────┬───────┐
│ registration │ count │
│   varchar    │ int64 │
├──────────────┼───────┤
│ LN-PFG       │   694 │
│ SE-RUP       │   530 │
│ LN-FGH       │   413 │
│ B-303Z       │   393 │
│ PH-HXE       │   380 │
│ SE-RST       │   322 │
│ 9H-MRT       │   312 │
│ G-DBCG       │   311 │
│ SE-RZE       │   309 │
│ SE-RZZ       │   302 │
│ M-ABGS       │   289 │
│ LN-WDN       │   280 │
│ SE-ROT       │   258 │
│ LN-NII       │   250 │
│ SE-RYC       │   245 │
│ C-FNOI       │   241 │
│ A7-BTB       │   238 │
│ OY-KBO       │   238 │
│ C-FRSO       │   237 │
│ SE-RPG       │   229 │
│   ·          │    ·  │
│   ·          │    ·  │
│   ·          │    ·  │
│ G-TTOB       │   113 │
│ EI-SII       │   113 │
│ LN-WDP       │   111 │
│ PH-EZM       │   110 │
│ TC-LHF       │   110 │
│ G-EUUI       │   102 │
│ 9H-WNV       │    99 │
│ EI-SIZ       │    92 │
│ LN-WDU       │    91 │
│ OY-KBR       │    90 │
│ PH-EZO       │    82 │
│ LN-ENN       │    82 │
│ PH-EXL       │    72 │
│ G-RUKJ       │    68 │
│ OH-LKN       │    56 │
│ SE-NAD       │    54 │
│ OY-SWO       │    52 │
│ A7-BAC       │    51 │
│ PH-EXU       │    42 │
│ LN-RKT       │     2 │
├──────────────┴───────┤
│ 55 rows    2 columns │
│ (40 shown)           │
└──────────────────────┘

Her får vi en oversikt over fartøyene som er observert i den aktuelle sektoren, sortert etter antall meldinger.


Oppsummering: Når bruker vi hvilken metode?

Nå har vi sett på tre ulike måter å søke geografisk i flydata. Her er en oppsummering av når du bør bruke hvilken:

MetodeBruk når...Eksempel
Punkt og radiusDu søker rundt et spesifikt sted med kjent avstand«Alle fly innenfor 5 km fra Bergen lufthavn»
AvgrensningsboksDu har et rektangulært område definert av koordinater«Alle fly mellom disse fire hjørnepunktene»
H3Du skal gjøre effektive søk eller aggregeringer over større områder«Alle fly i Bergen-regionen gruppert etter antall observasjoner»

I praksis vil du ofte bruke:


Oppgaver


Interessante fartøy og skjulte data

Mens mange av sporingsdataene vi har sett på så langt er offentlig tilgjengelig i tjenester som Flightradar24 og Radarbox, er det ekstra spennende med informasjon som er vanskeligere tilgjengelig. Noen fly ønsker ikke å bli sporet offentlig – men sender fortsatt ut ADS-B-signaler som vi kan fange opp. Andre fly er militære, eller klassifisert som «interessante» av ulike årsaker.

Her kommer kolonnen flags til unnsetning. Feltet inneholder informasjon om spesielle egenskaper ved fartøyet som ikke alltid er synlige i offentlige sporingstjenester. Dataene her stammer fra en database som oppdateres periodisk på serveren.

Hva forteller flags-kolonnen oss?

Kolonnen flags kan inneholde følgende informasjon:

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 journalistisk informasjon – for eksempel kan vi finne militære fly som opererer i et område, eller identifisere fly som aktivt prøver å skjule seg fra offentlig sporing.

Et praktisk eksempel først

La oss si at vi finner et fly med flags = 12. Hva betyr det?

Hvis vi ser i binærrepresentasjon:

12 = 8 + 4
   = LADD + PIA

Dette flyet har altså både LADD-beskyttelse OG et midlertidig PIA-nummer. Det prøver aktivt å skjule seg fra offentlig sporing på to måter samtidig. Spennende!

Men vent litt – hvordan kan én kolonne inneholde informasjon om flere egenskaper samtidig? Man kunne tenke at hvert fartøy kun kan ha ett av disse flaggene. Men sannheten er at hvert fartøy kan ha flere flagg satt samtidig, gjennom en smart teknisk løsning kjent som bitwise-operasjoner.

La oss se hvordan dette fungerer.


Hvordan fungerer 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 flags, består den egentlig bare av en sekvens av 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 representere flere egenskaper samtidig.

Oversikt over flaggverdier

FlaggtypeDesimalverdiBinærverdiBetydning
Militært10001Bit 1 er satt
Interessant20010Bit 2 er satt
PIA40100Bit 3 er satt
LADD81000Bit 4 er satt

Eksempel på kombinasjon

Hvis et fartøy både er markert som PIA og LADD, får det:

  0100 (PIA = 4)
+ 1000 (LADD = 8)
-------
  1100 (= 12)

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

Andre eksempler:


Hvordan finner vi ut hvilke flagg som er satt?

For å sjekke om et bestemt flagg er satt, bruker vi en bitvis AND-operasjon. Ampersand-tegnet (&) er operatoren vi bruker i SQL for dette. Den sammenligner to tall bit for bit og returnerer 1 bare hvis begge bitene er 1.

Slik fungerer &-operatoren

Eksempel:

flags = 12:  1100
LADD = 8:    1000
           ------
Resultat:    1000  (= 8, som er større enn 0)

Derfor kan vi bruke flags & 8 != 0 for å sjekke om LADD-flagget er satt.

Et annet eksempel:

flags = 12:  1100
Militært=1:  0001
           ------
Resultat:    0000  (= 0, militært er IKKE satt)

Her får vi 0, fordi det militære flagget ikke er aktivt i flags = 12.


Søke etter fly med spesifikke flagg

Nå som vi forstår teorien, la oss se hvordan vi bruker dette i praksis.

SELECT DISTINCT icao, registration, flags
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND flags & 8 != 0
ORDER BY registration;

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å.

Dette gir oss:

┌─────────┬──────────────┬───────┐
│  icao   │ registration │ flags │
│ varchar │   varchar    │ int32 │
├─────────┼──────────────┼───────┤
│ AE10BB  │ 01-0192      │    10 │
│ 477FF2  │ 02           │    10 │
│ 478125  │ 0265         │    10 │
│ 47812A  │ 0276         │    10 │
│ AE119C  │ 03-3119      │    10 │
│ AE1458  │ 06-6154      │    10 │
│ AE145E  │ 06-6160      │    10 │
│ AE146F  │ 07-7177      │    10 │
│ AE49C6  │ 09-9210      │    10 │
│ 4A8199  │ 102004       │    10 │
│ AE5BFD  │ 16-03090     │    10 │
│ AE03F1  │ 165379       │    10 │
│ 478114  │ 5607         │    10 │
│ ADFF0C  │ 60-0335      │    10 │
│ AE062B  │ 60-0353      │    10 │
│ AE0562  │ 85-0005      │    10 │
│ AE057D  │ 87-0032      │    10 │
│ AE0CFC  │ 87-24594     │    10 │
│ AE07DA  │ 93-0600      │    10 │
│ AE07DE  │ 93-0604      │    10 │
│ AE07F3  │ 97-0041      │    10 │
│ 06A01C  │ A7-HJJ       │    10 │
│ 06A27A  │ A7-MAM       │    10 │
│ 7101E3  │ HZ-MF2       │    10 │
│ 485920  │ PH-GOV       │    10 │
│ 43C5EE  │ ZM420        │    10 │
│ 43C1C6  │ ZZ176        │    10 │
│ 43C6F3  │ ZZ330        │    10 │
│ A038E6  │ NULL         │    10 │
│ A12257  │ NULL         │    10 │
├─────────┴──────────────┴───────┤
│ 30 rows              3 columns │
└────────────────────────────────┘

Her ser vi at alle luftfartøyene har flags = 10, som betyr at det har både LADD (8) og Interessant (2) satt.

Det samme prinsippet gjelder for de andre flaggene:

-- Alle fartøy klassifisert som militære
SELECT DISTINCT icao, registration, flags
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND flags & 1 != 0
ORDER BY registration;

-- Alle fartøy klassifisert som interessante
SELECT DISTINCT icao, registration, flags
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND flags & 2 != 0
ORDER BY registration;

-- Alle fartøy med PIA
SELECT DISTINCT icao, registration, flags
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND flags & 4 != 0
ORDER BY registration;

Hvis du vil hente ut fartøy som har enten LADD eller PIA (eller begge), gjør du slik:

SELECT DISTINCT icao, registration, flags
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND flags & (8 + 4) != 0
ORDER BY registration;

Her bruker vi (8 + 4) som tilsvarer 12 i binær: 1100. Dette sjekker om minst ett av de to flaggene er satt.

En enklere tilnærming: Boolske kolonner

Synes du bitwise-konseptet er i overkant avansert? Det er helt forståelig – det er tross alt journalistikk og ikke teknisk utvikling vi fokuserer på her. Derfor har det nylig kommet en utvidelse i Medieklyngens datasjø som lar deg søke frem interessante fartøy på en enklere måte.

I tillegg til flags-kolonnen finnes det nå også egne boolske kolonner for hvert flagg:

FlaggtypeKolonneMulige verdier
Militærtis_militarytrue eller false
Interessantis_interestingtrue eller false
PIAis_piatrue eller false
LADDis_laddtrue eller false

Dette gjør spørringene mye mer lesbare og intuitive:

SELECT DISTINCT icao, registration, flags
FROM mcn.airtraces 
WHERE timestamp >= '2025-11-01 00:00:00' 
  AND timestamp < '2025-11-02 00:00:00' 
  AND is_interesting
ORDER BY registration;

Nødkoder og nødsituasjoner

Som vi så tidligere, kan piloter melde fra om ulike situasjoner ved å sette spesielle squawk-koder i transponderen. Disse kodene er standardiserte signaler som kommuniserer flyets status til lufttrafikkontrollen og andre fly. La oss se om vi finner noen slike koder i våre historiske data.

Hva er squawk-koder?

En squawk-kode er en firesifret kode som flyet sender ut via transponderen. De fleste squawk-koder er rutinemessige identifikatorer tildelt av lufttrafikkontrollen, men noen koder har spesielle betydninger:

Squawk-kodeBetydningAlvorlighetsgrad
7700Generell nødsituasjonKritisk
7600Radiokommunikasjon sviktetAlvorlig
7500Kapring (hijacking)Kritisk

I dette kapittelet skal vi se nærmere på kode 7700, som indikerer en generell nødsituasjon. Dette kan være alt fra tekniske problemer til medisinske nødssituasjoner om bord.


Søke etter nødkoder i dataene

La oss undersøke om det har vært noen nødsituasjoner i våre data. Vi velger en dag fra oktober 2024 hvor vi vet det skjedde noe interessant.

Steg 1: Finn fly med nødkode

Først søker vi etter alle fly som har sendt ut squawk-kode 7700:

SELECT DISTINCT registration, MIN(timestamp) as first_seen
FROM mcn.airtraces
WHERE timestamp >= '2024-10-15 00:00:00' 
  AND timestamp < '2024-10-16 00:00:00'
  AND squawk = '7700'
GROUP BY registration
ORDER BY first_seen DESC;
┌──────────────┬───────────────────────────┐
│ registration │        first_seen         │
│   varchar    │ timestamp with time zone  │
├──────────────┼───────────────────────────┤
│ 9H-VISTA     │ 2024-10-15 17:49:20.89+02 │
└──────────────┴───────────────────────────┘

Interessant! Vi har funnet ett fly med aktivert nødkode denne dagen: 9H-VISTA.

Steg 2: Undersøk flyets bevegelser

La oss se nærmere på dette flyets posisjon og høyde mens nødkoden var aktiv:

SELECT registration, timestamp, latitude, longitude, altitude_barometric
FROM mcn.airtraces
WHERE timestamp >= '2024-10-15 00:00:00' 
  AND timestamp < '2024-10-16 00:00:00'
  AND squawk = '7700'
  AND registration = '9H-VISTA'
ORDER BY timestamp ASC;

Dette gir oss en detaljert tidslinje:

┌──────────────┬───────────────────────────┬──────────────┬───────────────┬─────────────────────┐
│ registration │         timestamp         │   latitude   │   longitude   │ altitude_barometric │
│   varchar    │ timestamp with time zone  │ decimal(9,6) │ decimal(10,6) │        int32        │
├──────────────┼───────────────────────────┼──────────────┼───────────────┼─────────────────────┤
│ 9H-VISTA     │ 2024-10-15 17:49:20.89+02 │    52.066315 │      4.629288 │               13675 │
│ 9H-VISTA     │ 2024-10-15 17:49:37.06+02 │    52.087326 │      4.630280 │               13400 │
│ 9H-VISTA     │ 2024-10-15 17:49:47.49+02 │    52.100473 │      4.630973 │               13225 │
│ 9H-VISTA     │ 2024-10-15 17:50:00.16+02 │    52.116577 │      4.631882 │               12975 │
│ 9H-VISTA     │ 2024-10-15 17:50:15.5+02  │    52.135803 │      4.632874 │               12725 │
│ 9H-VISTA     │ 2024-10-15 17:50:21.35+02 │    52.142899 │      4.633179 │               12625 │
│ 9H-VISTA     │ 2024-10-15 17:50:27.08+02 │    52.149819 │      4.633562 │               12525 │
│ 9H-VISTA     │ 2024-10-15 17:50:29.1+02  │    52.152328 │      4.633636 │               12450 │
│ 9H-VISTA     │ 2024-10-15 17:50:35.88+02 │    52.160294 │      4.634018 │               12350 │
│ 9H-VISTA     │ 2024-10-15 17:50:42.86+02 │    52.168671 │      4.634476 │               12225 │
│ 9H-VISTA     │ 2024-10-15 17:50:49.86+02 │    52.177002 │      4.634857 │               12100 │
│ 9H-VISTA     │ 2024-10-15 17:50:52.75+02 │    52.180573 │      4.635010 │               12050 │
│ 9H-VISTA     │ 2024-10-15 17:50:57.83+02 │    52.186478 │      4.635239 │               11975 │
│ 9H-VISTA     │ 2024-10-15 17:51:00.72+02 │    52.189853 │      4.635446 │               11900 │
│ 9H-VISTA     │ 2024-10-15 17:51:08.42+02 │    52.198883 │      4.635849 │               11775 │
│ 9H-VISTA     │ 2024-10-15 17:51:13.89+02 │    52.205262 │      4.636152 │               11675 │
│ 9H-VISTA     │ 2024-10-15 17:51:17.25+02 │    52.209091 │      4.636383 │               11625 │
│ 9H-VISTA     │ 2024-10-15 17:51:20.93+02 │    52.213257 │      4.636612 │               11575 │
│ 9H-VISTA     │ 2024-10-15 17:51:23.22+02 │    52.216049 │      4.636688 │               11525 │
│ 9H-VISTA     │ 2024-10-15 17:51:28.36+02 │    52.221771 │      4.636993 │               11450 │
│    ·         │             ·             │        ·     │          ·    │                  ·  │
│    ·         │             ·             │        ·     │          ·    │                  ·  │
│    ·         │             ·             │        ·     │          ·    │                  ·  │
│ 9H-VISTA     │ 2024-10-15 18:02:00.02+02 │    52.454177 │      4.751430 │                2150 │
│ 9H-VISTA     │ 2024-10-15 18:02:09.19+02 │    52.447678 │      4.751434 │                2150 │
│ 9H-VISTA     │ 2024-10-15 18:02:16.18+02 │    52.442734 │      4.751205 │                2150 │
│ 9H-VISTA     │ 2024-10-15 18:02:34.97+02 │    52.429230 │      4.749756 │                2100 │
│ 9H-VISTA     │ 2024-10-15 18:03:01.13+02 │    52.411743 │      4.746933 │                1775 │
│ 9H-VISTA     │ 2024-10-15 18:03:13.31+02 │    52.404971 │      4.746015 │                1600 │
│ 9H-VISTA     │ 2024-10-15 18:03:21.83+02 │    52.400782 │      4.745544 │                1500 │
│ 9H-VISTA     │ 2024-10-15 18:03:30.95+02 │    52.396266 │      4.745466 │                1425 │
│ 9H-VISTA     │ 2024-10-15 18:03:35.59+02 │    52.393985 │      4.745466 │                1400 │
│ 9H-VISTA     │ 2024-10-15 18:03:38.76+02 │    52.392356 │      4.745466 │                1375 │
│ 9H-VISTA     │ 2024-10-15 18:03:44.88+02 │    52.389267 │      4.745407 │                1300 │
│ 9H-VISTA     │ 2024-10-15 18:03:51.76+02 │    52.385745 │      4.745152 │                1275 │
│ 9H-VISTA     │ 2024-10-15 18:04:01.75+02 │    52.380661 │      4.744644 │                1150 │
│ 9H-VISTA     │ 2024-10-15 18:04:03.5+02  │    52.379694 │      4.744524 │                1125 │
│ 9H-VISTA     │ 2024-10-15 18:04:09.18+02 │    52.376854 │      4.744289 │                1075 │
│ 9H-VISTA     │ 2024-10-15 18:04:14.69+02 │    52.374023 │      4.743958 │                1025 │
│ 9H-VISTA     │ 2024-10-15 18:04:18.63+02 │    52.372012 │      4.743740 │                 975 │
│ 9H-VISTA     │ 2024-10-15 18:04:26.27+02 │    52.367935 │      4.743423 │                 900 │
│ 9H-VISTA     │ 2024-10-15 18:04:47.79+02 │    52.356697 │      4.742327 │                 675 │
│ 9H-VISTA     │ 2024-10-15 18:05:03.25+02 │    52.348597 │      4.741699 │                 525 │
├──────────────┴───────────────────────────┴──────────────┴───────────────┴─────────────────────┤
│ 53 rows (40 shown)                                                                  5 columns │
└───────────────────────────────────────────────────────────────────────────────────────────────┘

Steg 3: Analyser dataene

La oss se nærmere på hva disse dataene forteller oss:

Observasjon 1: Synkende høyde

Hvis vi følger kolonnen altitude_barometric, ser vi et tydelig mønster:

Dette indikerer at flyet er i ferd med å lande.

Observasjon 2: Geografisk posisjon

La oss undersøke hvor flyet befinner seg. Kopier de siste koordinatene 52.348597, 4.741699 og lim dem inn i Google Maps.

Funn: Flyet er rett ved Schiphol lufthavn i Amsterdam! Den lave høyden og nødkoden sammen forteller oss at dette mest sannsynlig er en nødlanding.


Hva skjedde egentlig?

Med dataene vi nå har samlet, kan vi konkludere:

  1. Fly: 9H-VISTA (registrert i Malta, kjennetegn på «9H»)

  2. Tidspunkt: 15. oktober 2024, rundt kl. 17.49-18.05

  3. Situasjon: Nødkode 7700 aktivert ved ca. 13 675 fot

  4. Utfall: Flyet landet på Schiphol, Amsterdam (høyde sank til 525 fot)

  5. Varighet: Nødkoden var aktiv i ca. 15 minutter

Et kjapt Google-søk forteller oss at dette er et privatfly operert av VistaJet. Flyet landet med nødkoden aktiv 15. oktober 2024 på Schiphol i Amsterdam.

Journalistisk oppfølging

Hadde dette vært et fly i Norge, kunne dette vært utgangspunkt for en journalistisk undersøkelse:

Med ADS-B-data har vi dokumentasjon på eksakt tidspunkt, posisjon og høydeforløp – verdifullt underlagsmateriale for en sak.


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.

Oversikt over eksportformater

FormatBeste brukFordelerUlemper
CSVTabelldata, regneark, enkle graferUniverselt støttet, lett å åpne i ExcelMangler støtte for komplekse strukturer
JSONProgrammatisk behandling, API-erStøtter hierarkisk data, fleksibeltVanskeligere å lese manuelt
GeoJSONKartvisualiseringer, geografisk analysePerfekt for kart, inneholder geometriStørre filstørrelse

La oss se på hvert format i detalj.


CSV

CSV (Comma-Separated Values) er det mest tilgjengelige formatet og fungerer utmerket når vi ønsker å analysere data i regnearkprogrammer som Excel eller Numbers. Det er et enkelt, tekstbasert format der verdier skilles med komma, noe som gjør det lett å åpne i en rekke verktøy.

Når bruker vi CSV?

CSV er spesielt godt egnet til:

Begrensninger

CSV har begrensninger når det gjelder å håndtere komplekse datastrukturer eller geografiske koordinater utover enkle latitude/longitude-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.

Når bruker vi JSON?

JSON egner seg godt for:

Fordeler og ulemper

Til forskjell fra CSV kan JSON håndtere hierarkiske 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.

Når bruker vi GeoJSON?

GeoJSON er ideelt for:

Fordeler og ulemper

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.


Praktisk eksempel: Widerøe-nødlanding

For å demonstrere de ulike eksportformatene, tar vi utgangspunkt i en faktisk hendelse: En Widerøe-nødlanding på Bergen lufthavn Flesland i juli 2025. Vi skal eksportere dataene i alle tre formater og se hvordan de kan brukes videre.

Flyet vi skal se på:


Eksport til CSV

CSV er det enkleste formatet, perfekt for å åpne dataene i Excel eller lage grafer i Everviz eller Datawrapper.

COPY (
  SELECT timestamp, icao, registration, longitude, latitude, 
         altitude_barometric, ground_speed
  FROM mcn.airtraces
  WHERE timestamp >= '2025-07-11 07:40:00'
    AND timestamp < '2025-07-11 08:09:00'
    AND icao = '4785BF'
  ORDER BY timestamp
) TO 'ln-wdi.csv' WITH (HEADER);

Filen ln-wdi.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.

Hva kan vi gjøre med CSV-filen?


Eksport til JSON

La oss prøve det samme med JSON. Det er bare en minimal endring som skal til – vi bytter ut filendelsen:

COPY (
  SELECT timestamp, icao, registration, longitude, latitude, 
         altitude_barometric, ground_speed
  FROM mcn.airtraces
  WHERE timestamp >= '2025-07-11 07:40:00'
    AND timestamp < '2025-07-11 08:09:00'
    AND icao = '4785BF'
  ORDER BY timestamp
) TO 'ln-wdi.json';

Figur 2: JSON-filen åpnet i teksteditoren Sublime.

JSON Lines-format

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. Hver linje er et selvstendig JSON-objekt.

Eksempel på innhold:

{"timestamp":"2025-07-11 05:43:21.918+00","icao":"4785BF","registration":"LN-WDI","longitude":5.188293,"latitude":60.345808,"altitude_barometric":2700,"ground_speed":179.2}
{"timestamp":"2025-07-11 05:43:22.449+00","icao":"4785BF","registration":"LN-WDI","longitude":5.188097,"latitude":60.346224,"altitude_barometric":2725,"ground_speed":180.2}
{"timestamp":"2025-07-11 05:43:22.982+00","icao":"4785BF","registration":"LN-WDI","longitude":5.188009,"latitude":60.346712,"altitude_barometric":2725,"ground_speed":180.2}
{"timestamp":"2025-07-11 05:43:23.435+00","icao":"4785BF","registration":"LN-WDI","longitude":5.187901,"latitude":60.34706,"altitude_barometric":2750,"ground_speed":180.2}

JSONL er like enkelt å tolke programmatisk som standard JSON, og er mer effektivt for store datasett. Du kan lese mer om formatet på jsonlines.org.

Hva kan vi gjøre med JSON-filen?


Eksport til GeoJSON

For eksport av geografiske data som skal visualiseres på kart, bruker vi GeoJSON. Dette er litt mer avansert fordi vi må konvertere våre geografiske punkter til en linje (LineString) som viser flyets rute.

Enkel GeoJSON-eksport (punkter)

La oss først se på den enkleste måten – eksportere hvert datapunkt individuelt:

COPY (
  SELECT timestamp, registration, ST_POINT(longitude, latitude) AS geom
  FROM mcn.airtraces
  WHERE timestamp >= '2025-07-11 07:40:00'
    AND timestamp < '2025-07-12 08:09:00'
    AND icao = '4785BF'
  GROUP BY ALL
  ORDER BY timestamp
) TO 'ln-wdi-punkter.geojson' WITH (FORMAT gdal, DRIVER 'GeoJSON');

Dette gir oss individuelle punkter på kartet.

Avansert GeoJSON-eksport (linje)

For å vise flyets rute som en sammenhengende linje, bruker vi en mer avansert spørring:

COPY (
  SELECT registration, ST_MakeLine(ARRAY_AGG(ST_POINT(longitude, latitude) ORDER BY timestamp)) AS geom
  FROM mcn.airtraces
  WHERE timestamp >= '2025-07-11 07:40:00'
    AND timestamp < '2025-07-12 08:09:00'
    AND icao = '4785BF'
  GROUP BY ALL
) TO 'ln-wdi-linje.geojson' WITH (FORMAT gdal, DRIVER 'GeoJSON');

Figur 3: GeoJSON-filen åpnet i visualiseringsverktøyet GeoJSON.io.

Hva kan vi gjøre med GeoJSON-filen?


Valg av format: En beslutningstabell

Ikke sikker på hvilket format du skal velge? Bruk denne tabellen:

Jeg vil...Bruk format
Åpne i Excel og lage graferCSV
Analysere i Python eller JavaScriptJSON
Visualisere på kartGeoJSON
Lage en tidsserie-graf (høyde, hastighet)CSV
Vise flyets rute på et kartGeoJSON (linje)
Importere i DatawrapperCSV (for grafer) eller GeoJSON (for kart)
Videre prosessering i R eller PythonJSON eller CSV

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 4: 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 5: 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 6: 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 7: 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 8: 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.

DuckDBs grafiske grensesnitt

Når vi nå har trent ferdighetene i kommandolinjearbeid, er det på tide å introdusere en ny og kanskje hakket mer intuitiv måte å jobbe med DuckDB på: DuckDBs grafiske grensesnitt. Dette kjører i nettleseren din, og lar deg organisere spørringene visuelt i celler. Hvorfor startet vi ikke i den enkle enden og introduserte dette med en gang, tenker du kanskje. Det er flere årsaker til det. For det første er DuckDBs grafiske grensesnitt relativt nyutviklet, og ikke helt stabilt. Visse store spørringer kjører og kjører uten å bli ferdig, og statusindikatorene er ikke helt på plass ennå. For det andre introduserer det grafiske grensesnittet noen funksjoner som er litt på siden av kjernen i DuckDB, og som kan virke forvirrende før man har den grunnleggende syntaksen på plass. Og for det tredje er det lettere å gå fra kommandolinjen, når man har konseptene her på plass, til det grafiske grensesnittet enn omvendt.

Figur 9: DuckDBs grafiske grensesnitt.

Oppstart

Hvis DuckDB allerede kjører, stopp den først ved å skrive .exit i DuckDBs kommandolinje. Deretter starter vi verktøyet opp igjen med følgende kommando som er nesten helt identisk til den vi har brukt til nå:

duckdb sensorkurs-andre-samling.db --ui

DuckDB-kommandolinjen dukker opp i terminalen som tidligere, men nå lastes også nettleseren vår med DuckDBs grafiske grensesnitt. Her kan vi legge inn kommandoer og kjøre dem i sekvens.

Opprett ny notebook

Når DuckDBs grensesnitt er lastet, er neste steg å legge til en såkalt Notebook. Dette konseptet skal vi lære mer om senere i kurset, men i denne omgangen er det tilstrekkelig å se på notebooks som mapper hvor du kan lagre spørringer i en rekkefølge og kjøre dem gjentatte ganger.

Du oppretter en ny notebook ved å finne overskriften Notebooks oppe til venstre i grensesnittet, og deretter klikke +. Da åpnes en ny notebook med standardnavnet Untitled Notebook. Denne kan du endre navn på til det du ønsker ved å markere Untitled Notebook i den store kolonnen til høyre fra Notebooks-overskriften, og deretter endre teksten til det du selv ønsker. Lagre ved å trykke Enter.

Legg inn og kjør spørringer i celler

Neste steg er å legge inn spørringer i fornuftig store bolker. Dette gjør vi gjennom noe som kalles celler, eller Cells. La oss ta utgangspunkt i de første kommandoene vi kjørte i denne modulen.

  1. Trykk Add Cell-knappen i den høyre kolonnen, og kopier og deretter lim inn følgende spørring i cellen du akkurat opprettet:

-- Installer utvidelser
INSTALL httpfs;
INSTALL postgres;
INSTALL ducklake;
INSTALL spatial;
INSTALL h3 FROM community;
  1. Deretter trykker vi Add Cell igjen, og legger inn neste spørring:

-- Last inn utvidelser
LOAD httpfs;
LOAD postgres;
LOAD ducklake;
LOAD spatial;
LOAD h3;
  1. Og så neste:

-- Legg inn tilgangsnøkkel
CREATE SECRET (
    TYPE R2,
    KEY_ID '9030e0f90a86af08b08b6e2a1222a778',
    SECRET '2fe64ae1c22869400f577bb9421602f0f81a83a2f658cea6bdd556f4fc65064b',
    ACCOUNT_ID 'bca3475a0f4afeb0640daafc17ec2b18'
);
  1. Og så neste:

-- Logg på datasjø
ATTACH 'ducklake:postgres:dbname=mcn_ducklake host=ep-fragrant-unit-a20fur0h.eu-central-1.aws.neon.tech user=fra_signaler_til_skup_h2_2025 password=npg_TLH6IjXdAW9q sslmode=require' AS mcn
    (DATA_PATH 'r2://mcn-ducklake', READ_ONLY);

Notebooks lar oss selv velge om vi vil kjøre cellene én for én mens vi jobber dem frem, eller når alle er skrevet inn. Oppe til venstre i hver celle finner du en play-knapp. Når du trykker på denne, kjører du spørringen i den aktuelle cellen. Prøv å gjøre dette med den øverste cellen i den valgte notebooken.

Du kan også kjøre alle cellene i sekvens, i rekkefølgen topp til bunn. Det gjør du ved å trykke Shift + Enter.

La oss prøve å legge inn og kjøre den aller første dataspørringen vi gjorde i dette kapittelet i en ny celle:

SELECT * FROM mcn.airtraces WHERE timestamp >= '2025-11-01 00:00:00' and timestamp < '2025-11-02 00:00:00';

Vi ser en sirkel som spinner, og deretter lastes dataene inn i en grafisk tabell. Stilig! Vi kan nå scrolle nedover i tabellen, og nye data vil vises. Vi kan også scrolle horisontalt, slik at vi får oversikt over alle kolonnene.

Figur 10: Et resultat av en spørring vist i DuckDBs grafiske grensesnitt.

Statistikk om datasettet

Hvis du klikker i cellen der du kjørte spørringen over, dukker det opp en kolonne helt til høyre i skjermbildet. Denne inneholder informasjon og statistikk om denne spørringen. Helt øverst ser vi at det står 3,839,743 Rows, 82 Columns, som betyr at resultatet vårt inneholder 3,8 millioner rader og 82 kolonner. I tabellen under kan vi klikke oss inn på hver kolonne i datasettet vårt, og få opp informasjon om distribusjon av verdier, høyeste og laveste verdi (evt. tidligste og seneste verdi der dette er relevant), telling av de vanligste verdiene, med mer. Hvis vi holder musepekeren i ro over et felt, får vi en forklaring på hva vi ser i det aktuelle feltet. Kolonnen helt til høyre i tabellen viser antallet nullverdier i datasettet, som også kan være svært nyttig.

Praktisk bruk av det grafiske grensesnittet

DuckDBs grafiske grensesnitt er som nevnt relativt nytt, og flere funksjoner vil bli implementert etter hvert. Men du kan kjøre alle de samme spørringene i grensesnittet som på kommandolinjen, og du kan til og med bruke de to modusene om hverandre. Omfattende spørringer har en tendens til å bli stående og kverne lenge i det grafiske grensesnittet, mens kommandolinjen gjerne er mer effektiv. Dette vil trolig endre seg over tid, men det er uansett ikke noe i veien for å bruke begge modusene om hverandre.

Oppsummering

I dette kapittelet har vi sett på DuckDB som et kraftfullt verktøy for å analysere store mengder historiske flydata. La oss oppsummere de viktigste punktene:

Hva er DuckDB?

Medieklyngens datasjø

Grunnleggende SQL-operasjoner

Geografiske søkemetoder

Punkt og radius:

Avgrensningsboks:

H3 (heksagonsystemet):

Interessante fartøy

Flags-systemet (bitwise):

Boolske kolonner (enklere):

Nødkoder og nødsituasjoner

Med SQL kan du analysere nødlandinger i detalj – høydefall, posisjon over tid, og varigheten av hendelsen.

Dataeksport og visualisering

Tre hovedformater:

Verktøy for visualisering:

DuckDBs grafiske grensesnitt

Journalistisk nytte

Med DuckDB kan du:

Viktige læringspunkter

Med denne kunnskapen i bagasjen er du klar til å utforske lufttrafikken og avdekke historier som ligger gjemt i millioner av datapunkter!

Oppgaver