Find addresses in a radius or a polygon

Fetch every address inside a circle or a drawn area with cirkel and polygon, URL-encode the polygon correctly and page through large results.

3 min Level: Intermediate ✓ Code tested

A utility has to notify every household within 300 metres of a burst pipe. A municipality wants the addresses in a local-plan area. An estate agent wants to show every house in an area drawn on a map. These are spatial searches, and /adgangsadresser and /adresser have two filters for them: cirkel and polygon.

Use /adgangsadresser when you want one row per building entrance (one household per house, one point per staircase). Use /adresser when you need every flat.

Circle

The format is cirkel=longitude,latitude,radius_in_metres:

Shell#cirkel-curl
curl -s "https://api.danadresse.dk/adgangsadresser?cirkel=12.5683,55.6761,100&struktur=mini&per_side=5" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"
  • Results are sorted by distance, nearest first. The distance itself is not in the response.
  • The radius must be between 1 and 50,000 metres. Other values give 400 with the code invalid_cirkel.
  • With srid=25832 you give the centre in UTM32 metres: cirkel=724400,6175700,100&srid=25832. The coordinates in the response are then in metres too.
  • You can combine it with postnr, kommunekode, status and medtagnedlagte. Without status and medtagnedlagte you get current and provisional addresses.

Polygon

polygon takes the outer ring of a GeoJSON polygon as JSON: [[[lon,lat],[lon,lat],…]]. The ring needs at least three points, and it is good practice to close it by repeating the first point at the end.

The polygon contains [, ] and ,, so it has to be URL-encoded. Let your HTTP library do it:

Python#polygon-python
import json
import os
import requests

ring = [
    [12.5665, 55.6750], [12.5700, 55.6750],
    [12.5700, 55.6770], [12.5665, 55.6770],
    [12.5665, 55.6750],
]
polygon = json.dumps([ring], separators=(",", ":"))

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

alle, side, per_side = [], 1, 100
while side <= 5:
    r = s.get(
        "https://api.danadresse.dk/adgangsadresser",
        params={"polygon": polygon, "struktur": "mini", "per_side": per_side, "side": side},
        timeout=30,
    )
    r.raise_for_status()
    rows = r.json()
    alle.extend(rows)
    if len(rows) < per_side:
        break
    side += 1

print(f"{len(alle)} adgangsadresser i polygonet")
for a in alle[:3]:
    print(" ", a["betegnelse"])

With curl, use -G --data-urlencode and curl handles the encoding:

Shell#polygon-curl
curl -s -G "https://api.danadresse.dk/adgangsadresser" \
  --data-urlencode 'polygon=[[[12.5665,55.6750],[12.5700,55.6750],[12.5700,55.6770],[12.5665,55.6750]]]' \
  --data-urlencode "struktur=mini" \
  --data-urlencode "per_side=3" \
  -H "X-Api-Key: $DANADRESSE_API_KEY"

Rules for polygon:

  • Only the outer ring is used. Holes in the polygon (inner rings) are ignored.
  • Fewer than three points or invalid JSON gives 400 with the code invalid_polygon.
  • With srid=25832 the points are in UTM32 metres.
  • Results are sorted by id, so pages never overlap. They are not sorted by distance.
  • The polygon travels in the URL, and there is no POST variant. Simplify large polygons to a few hundred points so the URL stays under about 8 KB.

Page through the results

per_side is at most 1000 (default 100), and side starts at 1. There is no header with the total count, so keep going until you get a page with fewer rows than per_side.

On /adresser with cirkel or polygon, per_side counts addresses (flats), as on every other list. The addresses come building by building, with the nearest building first for cirkel.

JavaScript#cirkel-node
const url = new URL("https://api.danadresse.dk/adresser");
url.searchParams.set("cirkel", "12.5757,55.7326,40");
url.searchParams.set("struktur", "mini");
url.searchParams.set("per_side", "20");

const alle = [];
for (let side = 1; side <= 10; side++) {
  url.searchParams.set("side", String(side));
  const res = await fetch(url, { headers: { "X-Api-Key": process.env.DANADRESSE_API_KEY } });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const rows = await res.json();
  alle.push(...rows);
  if (rows.length < 20) break; // last page
}
console.log(`${alle.length} adresser i alt`);
console.log(alle.slice(0, 3).map((a) => a.betegnelse).join("\n"));

Straight onto a map

With format=geojson you get a FeatureCollection that Leaflet, MapLibre and QGIS read directly. crs names the coordinate system (EPSG:25832 with srid=25832), and properties has the same fields as the normal JSON response (shortened here):

JSON#geojson-svar
{
  "type": "FeatureCollection",
  "crs": { "type": "name", "properties": { "name": "EPSG:4326" } },
  "features": [
    {
      "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [12.56823601, 55.67611003] },
      "properties": {
        "id": "0a3f507a-ec03-32b8-e044-0003ba298018",
        "kvh": "01016152___3",
        "status": 1,
        "betegnelse": "Rådhuspladsen 3, 1550 København V"
      }
    }
  ]
}

See Formats: JSON, CSV, GeoJSON and NDJSON for the other formats.

Large areas

A circle or polygon may cover at most 50,000 access addresses (measured on its bounding rectangle). If the area is larger, the API answers 400 with the code area_too_large instead of returning an incomplete result. Split large areas into smaller pieces (for example one circle per postcode area or a grid of polygons), or add postnr or kommunekode, and remove duplicates on id afterwards. If you need every address in a municipality or a postcode, use the kommunekode or postnr filters instead.

Next steps

Related guides

Markdown source Spotted an error? Tell us API-reference (Swagger)

Try it with a free key

2,000 calls/month on the free plan. No credit card.

Create account →