Geocode Canadian addresses and places
You have text that names a place in Canada, typed by a person or sitting in a spreadsheet, and you need a point you can put on a map. This tutorial picks the right search for the input you have, reads how much to trust each answer, and runs a whole list at once.
unmap finds the place someone means, from open Canadian data, so you can put it on a map. It does not check that an address is deliverable, correct it to Canada Post's format, or confirm that a unit exists. If you need certified address verification for mailing, that is a different kind of product, and unmap is not one.
import { Unmap } from "@unmap/sdk";
const unmap = new Unmap({ key: "um_live_..." });
const results = await unmap.geocoder.search("17 Ave SW Calgary", { limit: 3 });The code is plain TypeScript. For the React, Next.js, Vue or Nuxt wiring, start from the framework recipes.
Where the answers come from
Six open sources are merged into one corpus: OpenStreetMap, OpenAddresses (Canada), the Statistics
Canada National Address Register, NRCan's Canadian Geographical Names Database, Who's On First and
GeoNames. Records that describe the same place are merged before indexing and one survives, and
source on a result names the one that did. English and French names sit on the same record.
Coverage stops at the border. A reverse or nearby lookup anchored outside Canada is a 422
outside_coverage, and a search never returns a result in another country.
Search free text
search takes the text as written and ranks by relevance. Naming a city in the query prefers it
without excluding anything else. When you know more about the reader, say so with the controls:
region excludes other provinces, bbox excludes everything outside a box, and focus only
prefers results near a point.
const results = await unmap.geocoder.search("main st", {
region: "NS",
focus: [-63.5752, 44.6488],
limit: 5,
});The difference between focus and the other two is the one to get right. focus reorders and
excludes nothing; region and bbox remove results, and when nothing survives you get [].
Use structured search when you have the parts
If your input is already split into columns, send the columns. A free-text engine has to guess which word is the city, and a guess that goes wrong becomes an ordinary search word.
const [hit] = await unmap.geocoder.structured({
address: "101 17 Ave SW",
city: "Calgary",
region: "AB",
});address and city decide which records are candidates. region and postalcode narrow them:
they exclude records that contradict them and keep records that are silent about them.
Read how good the answer is
Every result says how well it matches what you sent and how precisely it is placed, as two separate fields. Decide what your application accepts before you store a point.
import type { GeocodeResult } from "@unmap/sdk";
function isExactPoint(result: GeocodeResult): boolean {
return result.match_type === "exact" && result.precision === "point";
}match_type is about your input: exact, partial, or fallback when you gave a civic number
and the answer is the street or the city instead. precision is about the position: point is a
discrete address or place record in the source, not a rooftop accuracy claim, and locality is a
centroid. Nothing is interpolated, so an address that is not in any source comes back as the
street, marked fallback, rather than as an invented point. match_reasons names what did not
match.
Geocode a list
batch sends up to 100 queries in one request and answers in the same order. The client splits a
longer list into requests of 100 for you. Each non-blank query is one call, the same as sending it
on its own.
const rows = await unmap.geocoder.batch(["17 Ave SW Calgary", "Trois-Rivières", "ᐃᖃᓗᐃᑦ"], {
limit: 1,
});
for (const [best] of rows) {
console.log(best?.name, best?.match_type);
}A query with no match is [] in its slot, never a guess, so the result lines up with your rows.
For a CSV on your own machine, see Batch geocoding.
Where this gets the wrong answer
- It is not postal validation. A result tells you where a place is. It does not tell you that mail will arrive there.
- A postal code is not a lookup. A query that is only a postal code does not return the area
the code covers. Send it with the street, where it narrows the candidates, and expect
postalcode_not_verifiedwhen the source record has none. - Units are mostly not in the data. A query that names one usually comes back without it,
marked
unit_not_verified. Keep the unit in your own field. - A rural route answers with the community.
RR 2 Okotoksresolves to Okotoks, not to a mailbox. A query that is onlyRR 2is a400asking for the community. - A legal land description is not an address. Send
NW-25-24-1-W5to search as typed; see Look up an Alberta legal land description.
Related
- Geocoding API for every parameter and the result shape.
- Coverage for what the corpus holds and where it stops.
- Build an address autocomplete for suggestions as the reader types.