Skip to content

Look up an Alberta legal land description

Wells, leases, farms and rural properties on the Prairies are described by the Dominion Land Survey rather than by a street address: NW-25-24-1-W5 is the northwest quarter of section 25, township 24, range 1, west of the fifth meridian. This tutorial resolves a description like that to a place on the map, goes the other way from a point to its description, and draws the grid.

Needs the Legal Land add-on

Legal land search, the survey grid and survey identify need the legalLand capability, which the Legal Land, Energy and Agriculture add-ons all grant. On the dev plan it is free from localhost and preview origins, like every unmap add-on. From a production origin, a key without it gets 403: addon_required on a paid plan, dev_only on the dev plan. Civic search on the same key is unaffected, and a successful lookup is metered under legal-land, not geocode. See add-ons and metering.

live result
the code that produced it
import { Unmap } from "@unmap/sdk";

const unmap = new Unmap({ key: "um_live_..." });

const results = await unmap.geocoder.search("NW-25-24-1-W5", { limit: 1 });

The code is plain TypeScript. For the React, Next.js, Vue or Nuxt wiring, start from the framework recipes.

Send the description as typed

There is no separate endpoint. A well-formed description sent to search leaves the address path entirely and comes back as layer: "legal_land", with the parsed parts and a canonical spelling. Keeping the two apart is deliberate: ranked as an address, 25-24-1-W5 would match a street somewhere in Ontario.

const [cell] = await unmap.geocoder.search("NW-25-24-1-W5", { geometry: true });
 
if (cell?.layer === "legal_land") {
  console.log(cell.legal_land?.canonical, cell.legal_land?.unit); // "NW-25-24-1-W5" "qtr"
  console.log(cell.lng, cell.lat);
}

The spellings people actually type all resolve to the same cell: NW-25-24-1-W5, NW 25-24-1 W5 and NW25-24-1W5. Each level of the survey is its own unit:

You sendunitMeans
10-25-24-1-W5lsdLegal subdivision 10 of section 25
NW-25-24-1-W5qtrThe northwest quarter of section 25
25-24-1-W5secSection 25
24-1-W5twpTownship 24, range 1

legal_land.province comes back with the cell, so a Saskatchewan description such as SE-1-1-1-W3 says SK.

Draw the cell

bbox is the cell's bounds, and geometry is its polygon when you asked for it with geometry: true. Fit the map to one and draw the other.

import type { GeocodeResult } from "@unmap/sdk";
 
function showCell(cell: GeocodeResult) {
  const map = unmap.map;
  if (!map) return;
  if (cell.bbox) map.fitBounds(cell.bbox, { padding: 40 });
  if (!cell.geometry) return;
 
  // A Polygon or a MultiPolygon, as the survey row stores it: draw whichever arrives.
  const g = cell.geometry;
  const geometry =
    g.type === "Polygon"
      ? { type: "Polygon" as const, coordinates: g.coordinates as number[][][] }
      : { type: "MultiPolygon" as const, coordinates: g.coordinates as number[][][][] };
  map.addSource("cell", {
    type: "geojson",
    data: {
      type: "Feature",
      properties: {},
      geometry,
    },
  });
  map.addLayer({
    id: "cell-outline",
    type: "line",
    source: "cell",
    paint: { "line-color": "#FF3E9A", "line-width": 2 },
  });
}

Find the description under a point

The other direction is identify, not reverse geocoding: it asks which survey cells contain a point, and answers every level at once, from the legal subdivision up to the township.

const { results } = await unmap.identify({
  at: [-114.0719, 51.0447],
  layers: ["survey.dls"],
});
 
const lsd = results.find((r) => r.unit === "lsd");
console.log(lsd?.name); // "8-16-24-1-W5"

Wire it to a map click and a reader can point at a field and read its description, which is usually what they wanted the lookup for in the first place.

Draw the grid

The survey grid is a catalog overlay. Ask for it when you create the map and every level draws, with each cell labelled by its description.

const unmap = new Unmap({
  key,
  container: "map",
  center: [-114.0719, 51.0447],
  zoom: 12,
  overlays: ["survey.dls"],
});

Townships draw from far out; sections, quarters and legal subdivisions from zoom 9. When you want to choose which levels appear at which zoom, add the source yourself, as Layers shows.

Where this gets the wrong answer

  • A description that parses but is not in the table returns []. No centroid is invented for a cell that is not there.
  • Coverage depends on the system. DLS descriptions look up in Alberta, Saskatchewan, Manitoba and the BC Peace region, and NTS in British Columbia. FPS, Ontario concession and lot, and river lots parse but do not look up yet. Quebec and Atlantic cadastre are not sourced.
  • Structured search does not detect it. /geocode/structured treats NW-25-24-1-W5 as a street name. Send descriptions to search.
  • A cell locates land; it is not a survey. The polygon comes from the provincial open survey grid. It tells you where a description is. It does not establish a legal boundary.