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.
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.
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 send | unit | Means |
|---|---|---|
10-25-24-1-W5 | lsd | Legal subdivision 10 of section 25 |
NW-25-24-1-W5 | qtr | The northwest quarter of section 25 |
25-24-1-W5 | sec | Section 25 |
24-1-W5 | twp | Township 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/structuredtreatsNW-25-24-1-W5as 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.
Related
- Geocoding API for the systems table and the error body.
- Layers for identify and the survey overlays.
- Overlay components for
legal-land-search, this search box as a copy-in component.