Add coordinates to a CSV file
Geocode a list of addresses to x/y in WGS84 or UTM32 with datavask and id lookups, in plain Python or with pandas.
A map of customers, a route plan or an analysis by area needs coordinates on every address. If you have a CSV with street name, house number and postcode, you can add the columns x and y with two kinds of calls: datavask finds the address id, and a lookup by id gives the coordinates.
The coordinates belong to the access address (adgangsadresse), the entrance of the building. Every flat on the same staircase shares the same point, so you can geocode at building level without floor and door.
1. Find the access address with datavask
GET /datavask/adgangsadresser takes street name, house number and postcode (or the whole address in betegnelse) and returns the best match with its id:
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"
In the response, resultater[0].adresse.id is the access-address id. The response has the same shape as DAWA's datavask and contains no coordinates. Only take rows with kategori A or B. A C row gets empty coordinates so that a person can look at it.
2. Fetch the coordinates by id
GET /adgangsadresser takes several ids separated by |. With struktur=mini you get a flat object with x and y. Without srid they are longitude and latitude in WGS84 with eight decimals. With srid=25832 they are in UTM32 metres, which is handy for distances and GIS:
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"
The response does not come back in the order of the ids. Match on id, not on position. Keep to about 100 ids per call so the URL does not get too long.
To get both coordinate systems in one call, add format=csv. The CSV response always has the columns wgs84koordinat_længde, wgs84koordinat_bredde, etrs89koordinat_øst and etrs89koordinat_nord.
3. The whole flow with the csv module
The example reads two rows from a string, washes them and fetches the coordinates in WGS84 and UTM32 with one call per coordinate system. It only uses requests and the standard library:
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)
For a real file, replace io.StringIO(DATA) with open("kunder.csv", encoding="utf-8-sig"), split the id list into chunks of 100 and handle 429 as described in Limits, quotas and 429.
4. The same with pandas
With pandas it is handy to wash each unique address once, fetch the coordinates in chunks of 100 ids and merge the result back. Many CRM exports have the same address on several customers:
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())
Read every column as text (dtype=str). Otherwise codes with a leading zero, such as the municipality code 0101, lose the zero, and house numbers like 12A get mixed with numbers.
Which coordinate system?
srid | System | Unit | Use |
|---|---|---|---|
4326 (default) | WGS84 | degrees (longitude, latitude) | Web maps, GPS, Google Maps, Leaflet |
25832 | ETRS89 / UTM zone 32N | metres (east, north) | Distances, GIS, Danish map data |
Mind the order: x is longitude and y is latitude. Many tools want lat, lon, so swap them when you write to those. See Coordinates and srid.
Edge cases
- No coordinates. A few addresses have no access point. Then
xandyarenull. - Accuracy. The full response from
/adgangsadresser/{id}hasadgangspunkt.nøjagtighed:Ais measured,Bis calculated,Uis uncertain. Filter on it if precision matters to you. - Semicolon files. Danish Excel exports often use semicolons. Use
csv.DictReader(f, delimiter=";")orpd.read_csv(..., sep=";"). - Ids you already have. If you have address ids from an earlier wash, skip datavask and fetch the coordinates directly with
GET /adresser?id=a|b|c&struktur=mini.
Next steps
- Addresses in a radius or a polygon
- Clean a CSV export from your CRM
- More about geocoding: geocoding
Related guides
Markdown source Spotted an error? Tell us API-reference (Swagger)