Dronezoner Data API v3.0
Seneste dataopdatering
Henter…
Data-status
Planlagt opdatering
Dagligt kl. 03:00 CET
Antal filer
14 endpoints (9 dronezoner + 5 naturområder)

Om dette API

Alle URLs er faste og ændres ikke — du kan trygt pege din applikation direkte på dem. Data stammer fra Trafikstyrelsens officielle datasæt og publiceres i flere geodata-formater. KML-filer er opdelt efter geometritype (Point / Polygon), og KMZ polygon-filer er yderligere opdelt efter farve for maksimal kompatibilitet med Google Earth og andre KML-klienter.

To selvstændige datasæt — 14 filer i alt

API'et giver adgang til to separate datasæt med hver sin kilde og sit opdateringsforløb. De to datasæt er uafhængige og kan downloades enkeltvis.

Datasæt Zonekategorier Geometrityper Antal filer
Dronezoner Rød (flyvesikring), Orange (opmærksomhed), Blå (sikring) Point + Polygon 9
Naturområder Grøn (beskyttede naturområder, aktiv/inaktiv) Polygon 5
TOTAL 14

Datasæt: Dronezoner (RØD · ORANGE · BLÅ)

Indhold og lagstruktur

Datasættet indeholder geografiske dronezoner inddelt i tre kategorier (rød, orange, blå) med hver to lagtyper — en signatur (Point) og et område (Polygon). Alle lag er inkluderet i samtlige downloadformater. Udgåede zoner filtreres automatisk fra ved den daglige opdatering.

Lag Geometri Antal features

Data der ikke er inkluderet

Følgende datatyper indgår ikke i dette datasæt og skal hentes hos de respektive dataejere:

NOTAM'er og ICAO-kort — hentes hos Naviair (naviair.dk), som er dataejer for luftfartsinformation.

Veje og jernbaner — hentes hos Kortforsyningen / Styrelsen for Dataforsyning og Infrastruktur (dataforsyningen.dk).

Tilgængelige endpoints — Dronezoner

GET GeoJSON — Komplet datasæt .geojson

Alle dronezoner i ét GeoJSON-dokument. Velegnet til web-applikationer, Leaflet, Mapbox, OpenLayers og lignende kortbiblioteker.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data
import requests, json

URL = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data"

response = requests.get(URL)
response.raise_for_status()

drone_zones = response.json()
print(f"Hentet {len(drone_zones['features'])} dronezoner")

with open("dronezoner.geojson", "w", encoding="utf-8") as f:
    json.dump(drone_zones, f, ensure_ascii=False, indent=2)
const res = await fetch(
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data"
);
const data = await res.json();
console.log(`Hentet ${data.features.length} dronezoner`);

// Leaflet eksempel
L.geoJSON(data).addTo(map);
# Download til fil
curl -o dronezoner.geojson \
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data"

# Pretty-print
curl -s "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data" | jq .
using var client = new HttpClient();
var url = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data";

var json = await client.GetStringAsync(url);
await File.WriteAllTextAsync("dronezoner.geojson", json);
GET KML — Point-features .kml

Alle punkt-signaturer (lufthavne, heliporte, opmærksomhedspunkter m.fl.) i KML-format. Velegnet til Google Earth, Google Maps og andre KML-kompatible klienter.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/19b7580f09d04433b4ce6d2884feeb33/data
import requests

URL = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/19b7580f09d04433b4ce6d2884feeb33/data"
response = requests.get(URL)
response.raise_for_status()

with open("dronezoner_point.kml", "wb") as f:
    f.write(response.content)
print("KML Point downloaded")
curl -o dronezoner_point.kml \
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/19b7580f09d04433b4ce6d2884feeb33/data"
GET KML — Polygon-features .kml

Alle polygon-zoner (restriktionszoner, opmærksomhedsområder, sikringskritiske områder) i KML-format.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/878250cbf2504a0db98c49e9a06a25cf/data
GET KMZ — Point-features (komprimeret) .kmz

Komprimeret udgave af KML Point. Mindre filstørrelse, ideel til distribution og Google Earth.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/e2359c1f89e749c3855efe277ffd0e32/data
GET KMZ — Polygon RØD (flyvesikringskritiske) .kmz

Komprimeret KML med røde polygon-zoner — flyvesikringskritiske områder. Separate farvefiler giver bedre kompatibilitet med Google Earth og enklere integration.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/67d686c594624c6ba1b6b208972bf612/data
GET KMZ — Polygon ORANGE (opmærksomhedsområder) .kmz

Komprimeret KML med orange polygon-zoner — opmærksomhedsområder.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/2073d3ca285247168679452286bc43b7/data
GET KMZ — Polygon BLÅ (sikringskritiske) .kmz

Komprimeret KML med blå polygon-zoner — sikringskritiske områder.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/5df3b2a6ae5a463f88c998db2d042b19/data
GET GeoPackage — Komplet datasæt .gpkg

OGC-standard SQLite-baseret format. Understøtter vektor- og rasterdata. Ideel til QGIS og moderne GIS-software.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/048f825f32e3444d8989a6701f477eec/data
import requests
import geopandas as gpd

URL = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/048f825f32e3444d8989a6701f477eec/data"
response = requests.get(URL)
response.raise_for_status()

with open("dronezoner.gpkg", "wb") as f:
    f.write(response.content)

gdf = gpd.read_file("dronezoner.gpkg")
print(f"Indlæst {len(gdf)} dronezoner, CRS: {gdf.crs}")
curl -o dronezoner.gpkg \
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/048f825f32e3444d8989a6701f477eec/data"

# Vis info med GDAL
ogrinfo dronezoner.gpkg
GET Shapefile — Komplet datasæt .zip

ESRI Shapefile i ZIP-format (.shp, .shx, .dbf m.fl.). Industristandard — kompatibel med alle GIS-systemer.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/6eaf92665c0244c7b5b2f56f805d8717/data
import requests, zipfile
from io import BytesIO
import geopandas as gpd

URL = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/6eaf92665c0244c7b5b2f56f805d8717/data"
response = requests.get(URL)
response.raise_for_status()

with open("dronezoner_shp.zip", "wb") as f:
    f.write(response.content)

with zipfile.ZipFile(BytesIO(response.content)) as z:
    z.extractall("dronezoner_shp")
    print(f"Udpakket: {z.namelist()}")
curl -o dronezoner_shapefile.zip \
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/6eaf92665c0244c7b5b2f56f805d8717/data"

unzip dronezoner_shapefile.zip -d dronezoner_shp/

Datasæt: Naturområder (GRØN)

Indhold og lagstruktur

Datasættet viser beskyttede naturområder med droneflyvningsrestriktioner. Datasættet indeholder polygon-geometrier med både aktive og sæsonmæssigt inaktive zoner. KML/KMZ-filerne inkluderer visuel aktiv/inaktiv-styling.

Lag Geometri Aktive Inaktive Total

Tilgængelige endpoints — Naturområder

GET GeoJSON — Naturområder .geojson

Alle naturområder i ét GeoJSON-dokument. Indeholder både aktive og inaktive zoner med attributter for status og sæsonperiode.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/ff657943724944faaf19807380f5e24a/data
import requests, json

URL = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/ff657943724944faaf19807380f5e24a/data"

response = requests.get(URL)
response.raise_for_status()

nature_zones = response.json()
print(f"Hentet {len(nature_zones['features'])} naturområder")

# Filtrer aktive/inaktive
active = [f for f in nature_zones['features']
          if f['properties'].get('Status') == 'Aktiv']
print(f"Heraf {len(active)} aktive zoner")

with open("naturomraader.geojson", "w", encoding="utf-8") as f:
    json.dump(nature_zones, f, ensure_ascii=False, indent=2)
const res = await fetch(
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/ff657943724944faaf19807380f5e24a/data"
);
const data = await res.json();
console.log(`Hentet ${data.features.length} naturområder`);

// Leaflet eksempel med farve efter status
L.geoJSON(data, {
  style: f => ({
    color: f.properties.Status === 'Aktiv' ? '#27ae60' : '#95a5a6',
    fillOpacity: f.properties.Status === 'Aktiv' ? 0.3 : 0.1
  })
}).addTo(map);
# Download til fil
curl -o naturomraader.geojson \
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/ff657943724944faaf19807380f5e24a/data"

# Tæl features
curl -s "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/ff657943724944faaf19807380f5e24a/data" \
  | jq '.features | length'
using var client = new HttpClient();
var url = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/ff657943724944faaf19807380f5e24a/data";

var json = await client.GetStringAsync(url);
await File.WriteAllTextAsync("naturomraader.geojson", json);
GET KML — Naturområder Polygon .kml

Alle naturområder i KML-format med visuel aktiv/inaktiv-styling. Aktive zoner vises med udfyldt grøn farve, inaktive med gennemsigtig grå.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/92fa2e08ace1421eac58d2868091c146/data
GET KMZ — Naturområder Polygon (komprimeret) .kmz

Komprimeret KML med alle naturområder. Aktiv/inaktiv-styling bevaret. Mindre filstørrelse, ideel til Google Earth.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/451143b893224b1a9561cf1236997a63/data
GET GeoPackage — Naturområder .gpkg

Naturområder i OGC GeoPackage-format. Ideel til QGIS og moderne GIS-software.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/3b8fb69058af464c9bcef2ca3e11b875/data
import requests
import geopandas as gpd

URL = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/3b8fb69058af464c9bcef2ca3e11b875/data"
response = requests.get(URL)
response.raise_for_status()

with open("naturomraader.gpkg", "wb") as f:
    f.write(response.content)

gdf = gpd.read_file("naturomraader.gpkg")
print(f"Indlæst {len(gdf)} naturområder, CRS: {gdf.crs}")
curl -o naturomraader.gpkg \
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/3b8fb69058af464c9bcef2ca3e11b875/data"

ogrinfo naturomraader.gpkg
GET Shapefile — Naturområder .zip

Naturområder i ESRI Shapefile-format (ZIP). Kompatibel med alle GIS-systemer.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/95f75b5c5a4f425493d602ca6f006835/data

Tjek for opdateringer (metadata)

Metadata-endpoint

Alle items eksponerer metadata via ?f=json. Feltet modified (epoch millisekunder) angiver hvornår filen sidst blev opdateret på serveren.
Herunder er to integrationsmønstre — vælg det der passer til jeres behov.
GET Mønster 1 — Valider at data er fra i dag Anbefalet

Anbefalet til produktionssystemer. Scriptet henter metadata fra ArcGIS Online og bruger feltet modified (epoch millisekunder) til at afgøre om data er opdateret i dag (dansk tid, CET/CEST).

Bemærk: Feltet modified er det eneste pålidelige tidsstempel til maskinelt tjek. Andre felter som description og snippet indeholder også opdateringsdato i klartekst, men er beregnet til menneskeligt aflæsning — brug altid modified til automatisering.

Hvis data ikke er fra i dag, returneres en fejl — enten fordi jeres job kører før kl. 03:00 CET, eller fordi det planlagte opdateringsscript ikke har kørt. På den måde undgår I at bruge forældet data uden at vide det.

Exit codes — kort opsummering
Exit codeBetydningHandling
0Data er fra i dagFilen downloades og gemmes
1Data er IKKE fra i dagIngen download — WARNING/ERROR logges
2Metadata-kald fejledeIngen download — netværksfejl
Forventet adfærd
SituationResultatExit code
Data er opdateret i dag✓ Download og brug data0
Jeres job kører før kl. 03:00⚠ Data er fra i går — endnu ikke opdateret1
Opdateringsscriptet fejlede✗ Data er forældet (X dage) — undersøg1
Metadata-kald fejler✗ Netværksfejl — retry eller alarm2
https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a?f=json
"""
Dronezone data-freshness guard
Tjekker om data er opdateret I DAG ved at læse feltet 'modified'
fra ArcGIS Online item-metadata (?f=json).

'modified' er et epoch-timestamp i millisekunder (fx 1771552890000)
og angiver hvornår filen sidst blev opdateret på serveren.
Det er det eneste felt der skal bruges til maskinelt freshness-tjek.
(Felterne 'description' og 'snippet' indeholder samme dato i klartekst,
men er til menneskeligt aflæsning — brug IKKE dem til automatisering.)

Exit codes (standard for CI/CD og scheduled tasks):
  0 = Data er fra i dag — alt OK, fil downloadet
  1 = Data er IKKE fra i dag — forældet (WARNING eller ERROR)
  2 = Metadata-kald fejlede — netværksfejl, retry eller alarm
"""
import sys
import logging
import requests
from datetime import datetime, timezone, timedelta

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s"
)
log = logging.getLogger("dronezone")

META_URL = (
    "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/"
    "content/items/980697acd04d4a9bb1fd34bbefab924a?f=json"
)
DATA_URL = (
    "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/"
    "content/items/980697acd04d4a9bb1fd34bbefab924a/data"
)

CET = timezone(timedelta(hours=1))   # Vinter: CET (UTC+1)
# CET = timezone(timedelta(hours=2)) # Sommer: CEST (UTC+2)

OUTPUT_FILE = "dronezoner.geojson"
# ↑ VIGTIGT: Erstat med stien hvor JERES system gemmer filen, fx:
#   Windows:  r"C:\Dronekortdata\dataFraDronezoner\dronezoner.geojson"
#   Linux:    "/opt/dronedata/dronezoner.geojson"
#   macOS:    "/Users/dit-navn/dronedata/dronezoner.geojson"

def check_and_download():
    # 1. Hent metadata
    try:
        resp = requests.get(META_URL, timeout=30)
        resp.raise_for_status()
        meta = resp.json()
    except requests.RequestException as e:
        log.error(f"Kunne ikke hente metadata: {e}")
        sys.exit(2)  # Netværksfejl

    # 2. Parse modified timestamp
    modified_ms = meta.get("modified")
    if not modified_ms:
        log.error("Feltet 'modified' mangler i metadata")
        sys.exit(2)

    modified_dt = datetime.fromtimestamp(modified_ms / 1000, tz=CET)
    today = datetime.now(tz=CET).date()

    log.info(f"Senest opdateret: {modified_dt.strftime('%d/%m %Y kl. %H:%M')} CET")

    # 3. Tjek om data er fra i dag
    if modified_dt.date() != today:
        days_old = (today - modified_dt.date()).days
        if days_old == 1:
            log.warning(
                "DATA IKKE OPDATERET I DAG. "
                "Seneste data er fra i går. "
                "Sandsynlig årsag: Jeres job kører før kl. 03:00, "
                "eller det daglige opdateringsscript har endnu ikke kørt."
            )
        else:
            log.error(
                f"DATA ER FORÆLDET — {days_old} dage gammelt! "
                f"Seneste opdatering: {modified_dt.strftime('%d/%m %Y')}. "
                "Det planlagte opdateringsscript kan være fejlet. "
                "Kontakt Trafikstyrelsen hvis problemet fortsætter."
            )
        sys.exit(1)  # Data er IKKE frisk

    # 4. Data er fra i dag — download
    log.info("✓ Data er opdateret i dag — downloader...")
    try:
        data_resp = requests.get(DATA_URL, timeout=60)
        data_resp.raise_for_status()
    except requests.RequestException as e:
        log.error(f"Download fejlede: {e}")
        sys.exit(2)

    with open(OUTPUT_FILE, "wb") as f:
        f.write(data_resp.content)

    log.info(f"✓ Gemt {OUTPUT_FILE} ({len(data_resp.content):,} bytes)")
    sys.exit(0)  # Alt OK


if __name__ == "__main__":
    check_and_download()
/**
 * Dronezone data-freshness guard (Node.js / Deno / Bun)
 *
 * Læser feltet 'modified' (epoch ms) fra ArcGIS Online metadata.
 * Det er det eneste felt der skal bruges til maskinelt freshness-tjek.
 * ('description' og 'snippet' indeholder dato i klartekst, men er til
 * menneskeligt aflæsning — brug dem IKKE til automatisering.)
 *
 * Exit codes: 0 = data fra i dag (OK), 1 = forældet, 2 = netværksfejl
 */
const META_URL =
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a?f=json";
const DATA_URL =
  "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data";

async function checkAndDownload() {
  // 1. Hent metadata
  let meta;
  try {
    const res = await fetch(META_URL);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    meta = await res.json();
  } catch (e) {
    console.error(`[ERROR] Kunne ikke hente metadata: ${e.message}`);
    process.exit(2);
  }

  // 2. Tjek modified-dato i dansk tid (CET = UTC+1)
  const modifiedMs = meta.modified;
  const modified = new Date(modifiedMs);

  // Konverter til CET-dato (tilføj 1 time for UTC+1)
  const cetOffset = 1; // Skift til 2 for sommertid
  const modCET = new Date(modifiedMs + cetOffset * 3600000);
  const nowCET = new Date(Date.now() + cetOffset * 3600000);

  const modDate = modCET.toISOString().slice(0, 10);
  const todayDate = nowCET.toISOString().slice(0, 10);

  console.log(`[INFO] Senest opdateret: ${modified.toISOString()} (CET: ${modDate})`);

  // 3. Valider freshness
  if (modDate !== todayDate) {
    const daysOld = Math.floor((nowCET - modCET) / 86400000);
    if (daysOld <= 1) {
      console.warn(
        `[WARNING] Data er fra i går. ` +
        `Opdateringsscriptet har sandsynligvis endnu ikke kørt (kl. 03:00 CET).`
      );
    } else {
      console.error(
        `[ERROR] Data er ${daysOld} dage gammelt! ` +
        `Det planlagte opdateringsscript kan være fejlet.`
      );
    }
    process.exit(1);
  }

  // 4. Data er frisk — download
  console.log("[INFO] ✓ Data er fra i dag — downloader...");
  const data = await fetch(DATA_URL);
  const fs = await import("fs/promises");
  const buffer = Buffer.from(await data.arrayBuffer());
  await fs.writeFile("dronezoner.geojson", buffer);
  console.log(`[INFO] ✓ Gemt dronezoner.geojson (${buffer.length.toLocaleString()} bytes)`);
  process.exit(0);
}

checkAndDownload();
#!/usr/bin/env bash
# dronezone_freshness_guard.sh
#
# Læser feltet 'modified' (epoch ms) fra ArcGIS Online metadata.
# Det er det eneste felt der skal bruges til maskinelt freshness-tjek.
# ('description'/'snippet' indeholder klartekst-dato, men kun til mennesker.)
#
# Exit codes: 0 = data fra i dag (OK), 1 = forældet, 2 = netværksfejl
set -euo pipefail

META_URL="https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a?f=json"
DATA_URL="https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data"

# Hent modified timestamp (epoch ms)
MODIFIED_MS=$(curl -sf "$META_URL" | jq -r '.modified')

if [ -z "$MODIFIED_MS" ] || [ "$MODIFIED_MS" = "null" ]; then
  echo "[ERROR] Kunne ikke hente metadata" >&2
  exit 2
fi

# Konverter til sekunder og sammenlign med i dag (CET)
MODIFIED_SEC=$((MODIFIED_MS / 1000))
MODIFIED_DATE=$(TZ="Europe/Copenhagen" date -d "@$MODIFIED_SEC" +%Y-%m-%d)
TODAY=$(TZ="Europe/Copenhagen" date +%Y-%m-%d)

echo "[INFO] Senest opdateret: $(TZ='Europe/Copenhagen' date -d "@$MODIFIED_SEC" '+%d/%m %Y kl. %H:%M') CET"

if [ "$MODIFIED_DATE" != "$TODAY" ]; then
  DAYS_OLD=$(( ( $(date -d "$TODAY" +%s) - $(date -d "$MODIFIED_DATE" +%s) ) / 86400 ))
  if [ "$DAYS_OLD" -le 1 ]; then
    echo "[WARNING] Data er fra i går. Script kører muligvis før kl. 03:00." >&2
  else
    echo "[ERROR] Data er ${DAYS_OLD} dage gammelt! Opdateringsscript kan være fejlet." >&2
  fi
  exit 1
fi

echo "[INFO] ✓ Data er fra i dag — downloader..."
curl -sf -o dronezoner.geojson "$DATA_URL"
echo "[INFO] ✓ Gemt dronezoner.geojson"
exit 0
// DronezoneFreshnessGuard.cs
//
// Læser feltet 'modified' (epoch ms) fra ArcGIS Online metadata.
// Det er det eneste felt der skal bruges til maskinelt freshness-tjek.
// ('description'/'snippet' indeholder klartekst-dato, men kun til mennesker.)
//
// Exit codes: 0 = data fra i dag (OK), 1 = forældet, 2 = netværksfejl
using System.Text.Json;

var metaUrl = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a?f=json";
var dataUrl = "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a/data";

using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };

// 1. Hent metadata
JsonDocument meta;
try {
    var json = await client.GetStringAsync(metaUrl);
    meta = JsonDocument.Parse(json);
} catch (Exception ex) {
    Console.Error.WriteLine($"[ERROR] Metadata-kald fejlede: {ex.Message}");
    Environment.Exit(2);
    return;
}

// 2. Parse modified
var modifiedMs = meta.RootElement.GetProperty("modified").GetInt64();
var modifiedUtc = DateTimeOffset.FromUnixTimeMilliseconds(modifiedMs);
var cet = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
var modifiedCet = TimeZoneInfo.ConvertTime(modifiedUtc, cet);
var todayCet = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, cet);

Console.WriteLine($"[INFO] Senest opdateret: {modifiedCet:dd/MM yyyy 'kl.' HH:mm} CET");

// 3. Tjek freshness
if (modifiedCet.Date != todayCet.Date) {
    var daysOld = (todayCet.Date - modifiedCet.Date).Days;
    if (daysOld <= 1)
        Console.Error.WriteLine("[WARNING] Data er fra i går. Script kører muligvis før kl. 03:00.");
    else
        Console.Error.WriteLine($"[ERROR] Data er {daysOld} dage gammelt!");
    Environment.Exit(1);
    return;
}

// 4. Download
Console.WriteLine("[INFO] ✓ Data er fra i dag — downloader...");
var data = await client.GetByteArrayAsync(dataUrl);
await File.WriteAllBytesAsync("dronezoner.geojson", data);
Console.WriteLine($"[INFO] ✓ Gemt dronezoner.geojson ({data.Length:N0} bytes)");
Environment.Exit(0);
GET Mønster 2 — Sammenlign med lokal kopi Alternativ

Sammenligner serverens modified-tidsstempel med din lokale fils ændringstid. Downloader kun hvis serveren har nyere data. OBS: Dette mønster fortæller ikke om data faktisk er fra i dag — brug Mønster 1 hvis det er vigtigt.

https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a?f=json
import os
import requests
from datetime import datetime, timezone

META_URL = (
    "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/"
    "content/items/980697acd04d4a9bb1fd34bbefab924a?f=json"
)
DATA_URL = (
    "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/"
    "content/items/980697acd04d4a9bb1fd34bbefab924a/data"
)
LOCAL_FILE = "dronezoner.geojson"
# ↑ VIGTIGT: Erstat med stien til JERES lokale fil, fx:
#   Windows:  r"C:\Dronekortdata\dataFraDronezoner\dronezoner.geojson"
#   Linux:    "/opt/dronedata/dronezoner.geojson"
#   macOS:    "/Users/dit-navn/dronedata/dronezoner.geojson"

meta = requests.get(META_URL).json()
modified_dt = datetime.fromtimestamp(meta["modified"] / 1000, tz=timezone.utc)

print(f"Server-data opdateret: {modified_dt.isoformat()}")

if os.path.exists(LOCAL_FILE):
    local_mtime = datetime.fromtimestamp(
        os.path.getmtime(LOCAL_FILE), tz=timezone.utc
    )
    if modified_dt > local_mtime:
        print("→ Ny data tilgængelig — downloader...")
        r = requests.get(DATA_URL)
        with open(LOCAL_FILE, "wb") as f:
            f.write(r.content)
    else:
        print("→ Lokal kopi er allerede opdateret.")
else:
    print("→ Ingen lokal kopi fundet — downloader...")
    r = requests.get(DATA_URL)
    with open(LOCAL_FILE, "wb") as f:
        f.write(r.content)
# Hent modified-tidspunkt
curl -s "https://trafikstyrelsen.maps.arcgis.com/sharing/rest/content/items/980697acd04d4a9bb1fd34bbefab924a?f=json" \
  | jq '{modified: .modified, title: .title}'