# Tilføj koordinater til en CSV-fil

> Geokod en liste af adresser til x/y i WGS84 eller UTM32 med datavask og opslag på id, i ren Python eller med pandas.
>
> https://danadresse.dk/guides/geokod-csv · verified 2026-11-02
Et kort over kunder, en ruteplan eller en analyse pr. område kræver koordinater på hver adresse. Har du en CSV med vejnavn, husnummer og postnummer, kan du tilføje kolonnerne `x` og `y` med to slags kald: datavask finder adressens id, og et opslag på id'et giver koordinaterne.

Koordinaterne hører til adgangsadressen, altså bygningens indgang. Alle lejligheder i samme opgang har derfor samme punkt, og du kan geokode på bygningsniveau uden etage og dør.

## 1. Find adgangsadressen med datavask

`GET /datavask/adgangsadresser` tager vejnavn, husnummer og postnummer (eller hele adressen i `betegnelse`) og returnerer det bedste match med id:

```bash id=geo-curl run=live expect="adgangsadresser"
curl -s -G "https://api.danadresse.dk/datavask/adgangsadresser" \
  --data-urlencode "vejnavn=Strandvejen" \
  --data-urlencode "husnr=100" \
  --data-urlencode "postnr=2900" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"
```

I svaret er `resultater[0].adresse.id` adgangsadressens id. Svaret har samme form som DAWA's datavask og indeholder ingen koordinater. Tag kun rækker med `kategori` A eller B. En C-række får tomme koordinater, så et menneske kan se på den.

## 2. Hent koordinaterne på id

`GET /adgangsadresser` tager flere id'er adskilt af `|`. Med `struktur=mini` får du et fladt objekt med `x` og `y`. Uden `srid` er de længde- og breddegrad i WGS84 med otte decimaler. Med `srid=25832` er de i UTM32-meter, som er praktisk til afstande og GIS:

```bash id=geo-ids run=live expect="6175755"
curl -s -G "https://api.danadresse.dk/adgangsadresser" \
  --data-urlencode "id=0a3f507c-0624-32b8-e044-0003ba298018|0a3f507a-ec01-32b8-e044-0003ba298018" \
  --data-urlencode "struktur=mini" \
  --data-urlencode "srid=25832" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"
```

Svaret kommer ikke i samme rækkefølge som id'erne. Slå op på `id`, ikke på position. Hold dig til omkring 100 id'er pr. kald, så URL'en ikke bliver for lang.

Vil du have begge koordinatsystemer i ét kald, så tilføj `format=csv`. CSV-svaret har altid kolonnerne `wgs84koordinat_længde`, `wgs84koordinat_bredde`, `etrs89koordinat_øst` og `etrs89koordinat_nord`.

## 3. Hele flowet med csv-modulet

Eksemplet læser to rækker fra en tekststreng, vasker dem og henter koordinaterne i WGS84 og UTM32 med ét kald pr. koordinatsystem. Det bruger kun `requests` og standardbiblioteket:

```python id=geo-csv run=live expect="x_utm"
import csv
import io
import os
import sys
import requests

DATA = """kundenr,vejnavn,husnr,postnr
1001,Strandvejen,100,2900
1002,Rådhuspladsen,1,1550
"""

s = requests.Session()
s.headers["X-Api-Key"] = os.environ["DANADRESSE_API_KEY"]
API = "https://api.danadresse.dk"

rows = list(csv.DictReader(io.StringIO(DATA)))
for row in rows:
    r = s.get(f"{API}/datavask/adgangsadresser", timeout=10, params={
        "vejnavn": row["vejnavn"], "husnr": row["husnr"], "postnr": row["postnr"]})
    r.raise_for_status()
    svar = r.json()
    ok = svar["kategori"] in ("A", "B") and svar["resultater"]
    row["kategori"] = svar["kategori"]
    row["adgangsadresseid"] = svar["resultater"][0]["adresse"]["id"] if ok else ""

ids = [r["adgangsadresseid"] for r in rows if r["adgangsadresseid"]]

def punkter(srid):
    r = s.get(f"{API}/adgangsadresser", timeout=10, params={
        "id": "|".join(ids), "struktur": "mini", "srid": srid, "per_side": len(ids)})
    r.raise_for_status()
    return {a["id"]: (a["x"], a["y"]) for a in r.json()}

wgs84, utm = punkter(4326), punkter(25832)
for row in rows:
    aid = row["adgangsadresseid"]
    row["lon"], row["lat"] = wgs84.get(aid, ("", ""))
    row["x_utm"], row["y_utm"] = utm.get(aid, ("", ""))

out = csv.DictWriter(sys.stdout, fieldnames=list(rows[0].keys()))
out.writeheader()
out.writerows(rows)
```

Til en rigtig fil skal du udskifte `io.StringIO(DATA)` med `open("kunder.csv", encoding="utf-8-sig")`, dele id-listen op i bidder af 100 og håndtere `429` som beskrevet i [Grænser, kvoter og 429](/guides/graenser-og-429).

## 4. Samme med pandas

Med pandas er det praktisk at vaske de unikke adresser én gang, hente koordinaterne i bunker af 100 id'er og flette resultatet tilbage. Mange CRM-eksporter har den samme adresse på flere kunder:

```python id=geo-pandas run=syntax
import os
import pandas as pd
import requests

s = requests.Session()
s.headers["X-Api-Key"] = os.environ["DANADRESSE_API_KEY"]

df = pd.read_csv("kunder.csv", dtype=str, encoding="utf-8-sig")
unikke = df[["vejnavn", "husnr", "postnr"]].drop_duplicates()

def vask(r):
    svar = s.get("https://api.danadresse.dk/datavask/adgangsadresser", timeout=10,
                 params={"vejnavn": r.vejnavn, "husnr": r.husnr, "postnr": r.postnr}).json()
    ok = svar["kategori"] in ("A", "B") and svar["resultater"]
    return pd.Series({"kategori": svar["kategori"],
                      "adgangsadresseid": svar["resultater"][0]["adresse"]["id"] if ok else None})

unikke = unikke.join(unikke.apply(vask, axis=1))

ids = unikke["adgangsadresseid"].dropna().unique().tolist()
punkt = {}
for i in range(0, len(ids), 100):
    bunke = ids[i:i + 100]
    r = s.get("https://api.danadresse.dk/adgangsadresser", timeout=30, params={
        "id": "|".join(bunke), "struktur": "mini", "per_side": len(bunke)})
    r.raise_for_status()
    punkt.update({a["id"]: (a["x"], a["y"]) for a in r.json()})
unikke["x"] = unikke["adgangsadresseid"].map(lambda i: punkt.get(i, (None, None))[0])
unikke["y"] = unikke["adgangsadresseid"].map(lambda i: punkt.get(i, (None, None))[1])
df = df.merge(unikke, on=["vejnavn", "husnr", "postnr"], how="left")
df.to_csv("kunder_geokodet.csv", index=False)
print(df["kategori"].value_counts())
```

Læs alle kolonner som tekst (`dtype=str`). Ellers mister koder med foranstillet nul, som kommunekoden `0101`, deres nul, og husnumre som `12A` blandes med tal.

## Hvilket koordinatsystem?

| `srid` | System | Enhed | Brug |
|---|---|---|---|
| `4326` (standard) | WGS84 | grader (længde, bredde) | Webkort, GPS, Google Maps, Leaflet |
| `25832` | ETRS89 / UTM zone 32N | meter (øst, nord) | Afstande, GIS, danske kortdata |

Bemærk rækkefølgen: `x` er længdegrad og `y` er breddegrad. Mange værktøjer vil have `lat, lon`, så byt om, når du skriver til dem. Se [Koordinater og srid](/guides/koordinater-srid).

## Kanttilfælde

- **Ingen koordinater.** Nogle få adresser har ikke et adgangspunkt. Så er `x` og `y` `null`.
- **Nøjagtighed.** Det fulde svar fra `/adgangsadresser/{id}` har `adgangspunkt.nøjagtighed`: `A` er målt, `B` er beregnet, `U` er usikkert. Filtrer på den, hvis præcisionen betyder noget for dig.
- **Semikolon-filer.** Danske Excel-eksporter bruger tit semikolon. Brug `csv.DictReader(f, delimiter=";")` eller `pd.read_csv(..., sep=";")`.
- **Allerede kendte id'er.** Har du adresse-id'er fra en tidligere vask, så spring datavask over og hent koordinaterne direkte med `GET /adresser?id=a|b|c&struktur=mini`.

## Næste skridt

- [Adresser i en radius eller et polygon](/guides/adresser-i-radius-og-polygon)
- [Rens en CSV-fil fra CRM'et](/guides/rens-crm-csv)
- Mere om geokodning: [geokodning](/geokodning)
