# Canadian census-division geography

The history maps use a frozen, simplified copy of Statistics Canada's 2021 census divisions. The GeoJSON contains all **293 complete divisions**; the map generator chooses and clips the southern portion for display. These are Canadian county equivalents, not municipalities or provinces.

## Official source and licence

- Product: [2021 Population Ecumene Boundary Files, reference guide](https://www150.statcan.gc.ca/n1/pub/92-159-g/92-159-g2021001-eng.htm), published February 9, 2022; geographic reference date January 1, 2021.
- Geometry: [Census Division Generalized Cartographic Boundary File, service layer 1](https://geo.statcan.gc.ca/geo_wa/rest/services/2021/Population_ecumene_boundary_files/MapServer/1). This is the complete division layer. The population ecumene mask is a different layer and was not used.
- Province names and abbreviations: [province/territory layer 0 of the same product](https://geo.statcan.gc.ca/geo_wa/rest/services/2021/Population_ecumene_boundary_files/MapServer/0), joined by `PRUID`.
- [Service metadata](https://geo.statcan.gc.ca/geo_wa/rest/services/2021/Population_ecumene_boundary_files/MapServer?f=pjson) identifies the [Open Government Licence – Canada](https://open.canada.ca/en/open-government-licence-canada). See also [Statistics Canada's attribution terms](https://www.statcan.gc.ca/en/terms-conditions/open-licence).

Attribution for this adaptation:

> Adapted from Statistics Canada, 2021 Population Ecumene Boundary Files, January 1, 2021. This does not constitute an endorsement by Statistics Canada of this product.

> Contains information licensed under the Open Government Licence – Canada.

The source is designed for small-scale thematic maps, approximately 1:20,000,000 to 1:25,000,000. Its generalized shorelines can differ from other boundary products. Coastal waters are removed by the source cartographic process. This is suitable for this national overview, not street-level or legal boundary identification.

## Snapshot and fields

`canada-census-divisions-2021-sources.json` records the exact queries, hashes, dependency versions, cleanup log and output integrity. Retrieved September 11, 2026. The dated service can still be maintained upstream; the checked-in asset and SHA-256 freeze the version used here. The repository pins this GeoJSON file to LF line endings so its byte hash is stable across Windows and Unix checkouts.

| File/response | SHA-256 |
| --- | --- |
| Official GeoJSON query response | `e4b1fbcfcb76c30b4fe359b45b39e4611265636187f4e63ca04bdc4dadf48887` |
| Official province attribute response | `3270555995e1ec3e75dbdd8e815a002b790f3a0d90e02d14d8152b7bd7ba8a7e` |
| Saved `canada-census-divisions-2021.geojson` | `9a7fe70a2be8052267f641e8b073bb4018a7b5406c3fc3b8cd1f48be12ff86d2` |

The saved asset has 293 features, 80,034 coordinate positions and 1,788,295 UTF-8 bytes (approximately 576 KB gzipped). Feature `id` equals the four-character `CDUID`; the combined map prefixes Canadian IDs with `CA-` to avoid collisions with U.S. FIPS codes. The renderer uses familiar province postal codes such as BC, ON and QC, derived from PRUID; the source snapshot preserves the original Statistics Canada abbreviations.

| Property | Meaning |
| --- | --- |
| `CDUID` | Official four-character census-division identifier, retained as a string |
| `DGUID` | Official 2021 dissemination geography identifier |
| `CDNAME`, `CDTYPE` | Official division name and type, including source accents |
| `PRUID` | Official two-character province/territory identifier |
| `LANDAREA` | Source land area in square kilometres; not the area of a viewport fragment |
| `provinceName`, `provinceAbbreviation` | English `PRENAME` and `PREABBR` joined from the official province layer; abbreviations such as `B.C.` are not postal codes |
| `centroid` | Derived full-region spherical centroid, `[longitude, latitude]`, rounded to six decimals |

## Geometry, centroids and partial divisions

The source CRS is **EPSG:3347, NAD83 / Statistics Canada Lambert**, with metre units. The query requests **EPSG:4326 longitude/latitude**, `maxAllowableOffset=0.01` degrees and five decimal places. This extra simplification keeps the asset small enough for a static build input.

ArcGIS's simplification and coordinate rounding leave some microscopic polygon components with zero or inverted spherical area. The build removes 101 such components and corrects three tiny hole windings. No entire census division is removed. The manifest logs every removed component. Removing these invalid components prevents globe-sized spherical fills; it is not a population or land-area filter.

The saved GeoJSON uses counterclockwise exterior rings and clockwise holes. **Reverse every ring before D3 spherical polygon processing.** Each stored centroid is computed with `d3.geoCentroid` on the cleaned, complete, unclipped division, before any display projection. The result is not a population centre, city coordinate, or centroid of the visible fragment.

Recommended display policy: include divisions that intersect the chosen southern-Canada viewport; clip only their rendered paths. A partially visible division keeps its complete-region centroid and one consistent nearest-team assignment. Do not recompute distances from the cropped polygon. A visible fragment can therefore be assigned using a centroid beyond the crop. Explain the cropped Canadian extent in the tool's methodology, and distinguish its 2021 boundaries from the fixed 2017 U.S. counties.

The shared history atlas implements that policy with 253 visible divisions, of which 29 cross a crop edge. It omits fragments smaller than 0.3 square SVG units. Counts refer to these displayed divisions, not all Canadian divisions. The crop is a rectangle in the shared Albers projection; it is not a single northern latitude. U.S. geometry retains 94% of the original display scale within the same 1000×630 canvas.

Representative full-region centroids:

| CDUID | Division | Longitude | Latitude |
| --- | --- | ---: | ---: |
| `5915` | Greater Vancouver, British Columbia | -122.834884 | 49.282144 |
| `3520` | Toronto, Ontario | -79.390408 | 43.726364 |
| `2466` | Montréal, Quebec | -73.681074 | 45.517877 |

## Reproduce the saved asset

Run the following as a Node ES module from the repository root. It downloads only the exact sources in the manifest, verifies all hashes, repeats the geometry cleanup and checks the resulting asset hash. A future upstream change fails the integrity check instead of silently altering the map.

```js
import { createHash } from 'node:crypto';
import { readFile, writeFile } from 'node:fs/promises';
import { createContext, runInContext } from 'node:vm';

const manifest = JSON.parse(await readFile('public/data/canada-census-divisions-2021-sources.json', 'utf8'));
const sha256 = text => createHash('sha256').update(text).digest('hex');
async function download(url, expectedHash) {
  const response = await fetch(url, { signal: AbortSignal.timeout(55000) });
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);
  const text = await response.text();
  if (sha256(text) !== expectedHash) throw new Error(`Integrity mismatch: ${url}`);
  return text;
}
const raw = await download(manifest.queryUrl, manifest.querySha256);
const provinceRaw = await download(manifest.provinceQueryUrl, manifest.provinceQuerySha256);
const context = createContext({});
for (const dependency of manifest.dependencies) {
  runInContext(await download(dependency.url, dependency.sha256), context, { timeout: 5000 });
}
const { d3 } = context;
const provinces = Object.fromEntries(JSON.parse(provinceRaw).features.map(f => [f.attributes.PRUID, f.attributes]));
const features = JSON.parse(raw).features.map(feature => {
  const p = feature.properties;
  const sourcePolygons = feature.geometry.type === 'Polygon'
    ? [feature.geometry.coordinates] : feature.geometry.coordinates;
  const coordinates = [];
  for (const sourcePolygon of sourcePolygons) {
    const polygon = sourcePolygon.map(ring => [...ring].reverse());
    for (let i = 1; i < polygon.length; i++) {
      if (d3.geoArea({ type: 'Polygon', coordinates: [polygon[i]] }) < 2 * Math.PI) polygon[i].reverse();
    }
    const area = d3.geoArea({ type: 'Polygon', coordinates: polygon });
    const exteriorArea = d3.geoArea({ type: 'Polygon', coordinates: [polygon[0]] });
    if (area <= 0 || area >= 2 * Math.PI || exteriorArea <= 0 || exteriorArea >= 2 * Math.PI) continue;
    coordinates.push(polygon);
  }
  if (!coordinates.length) throw new Error(`Empty division: ${p.CDUID}`);
  const geometry = { type: 'MultiPolygon', coordinates };
  const centroid = d3.geoCentroid(geometry).map(value => Number(value.toFixed(6)));
  for (const polygon of coordinates) for (const ring of polygon) ring.reverse();
  return {
    type: 'Feature', id: p.CDUID,
    properties: {
      ...p, provinceName: provinces[p.PRUID].PRENAME,
      provinceAbbreviation: provinces[p.PRUID].PREABBR, centroid,
    },
    geometry,
  };
}).sort((a, b) => a.id.localeCompare(b.id));
if (features.length !== 293 || new Set(features.map(f => f.id)).size !== 293) throw new Error('Invalid division count');
const output = JSON.stringify({ type: 'FeatureCollection', name: 'Statistics Canada 2021 generalized census divisions', features }) + '\n';
if (sha256(output) !== manifest.output.sha256) throw new Error('Output integrity mismatch');
await writeFile(manifest.output.path, output, 'utf8');
```
