---
id: "packages/active-geo/layers-and-measures"
title: "Layers and measures"
description: "Area, point and context layers, measures per object, and the fill and size encodings."
url: "/packages/active-geo/layers-and-measures"
locale: "en"
revision: "970854d2e967632f"
---

# Layers and measures

**Example: Sites sized by their staff**

Client: Runs entirely in your browser. Source: `examples/geo/points.tsx`.

```tsx
// apps/docs/examples/geo/points.tsx
'use client';

import {useEffect, useState} from 'react';

import {
  englishGeoTranslator,
  GeoMap,
  type GeoAreaFeature,
  type GeoContextGeometry,
} from '@workspace/active-geo';
import {
  loadWorld50m,
  world50m,
} from '@workspace/active-geo/collections/world-50m';

const BENELUX = new Set(['NLD', 'BEL', 'LUX']);

// Invented sites, sized by their staff.
const SITES = [
  {key: 'ams', name: 'Amsterdam', latitude: 52.37, longitude: 4.9, staff: 120},
  {key: 'rtm', name: 'Rotterdam', latitude: 51.92, longitude: 4.48, staff: 64},
  {key: 'utr', name: 'Utrecht', latitude: 52.09, longitude: 5.12, staff: 38},
  {key: 'ein', name: 'Eindhoven', latitude: 51.44, longitude: 5.47, staff: 45},
  {key: 'bru', name: 'Brussels', latitude: 50.85, longitude: 4.35, staff: 72},
  {key: 'ant', name: 'Antwerp', latitude: 51.22, longitude: 4.4, staff: 51},
  {key: 'lux', name: 'Luxembourg', latitude: 49.61, longitude: 6.13, staff: 19},
];

/**
 * Point symbols over context geometry, sized by a count, with a legend, a
 * tooltip and selection (click, or Enter on a focused point).
 */
export default function SitesMap() {
  const [outline, setOutline] = useState<GeoAreaFeature[] | null>(null);
  useEffect(() => {
    let current = true;
    void loadWorld50m().then((loaded) => {
      if (current) setOutline(loaded.filter((f) => BENELUX.has(String(f.key))));
    });
    return () => {
      current = false;
    };
  }, []);
  if (!outline) {
    return <p className="text-muted-foreground text-sm">Loading the map…</p>;
  }
  return (
    // The map takes the width of its container (a ResizeObserver);
    // in a centring layout the host gives it one.
    <div className="w-full">
      <GeoMap
        title="Sites in the Benelux"
        projection="conic-equal-area"
        locale="en-GB"
        t={englishGeoTranslator}
        attribution={world50m.attribution}
        layers={[
          {
            type: 'context',
            id: 'benelux',
            title: 'Benelux',
            // Context, not the frame: the Netherlands of Natural Earth
            // include the Caribbean islands, so the sites frame the map.
            fit: false,
            geometries: outline.flatMap((feature): GeoContextGeometry[] =>
              feature.geometry ? [feature.geometry] : [],
            ),
          },
          {
            type: 'point',
            id: 'sites',
            title: 'Sites',
            measures: [
              {id: 'staff', label: 'Staff', semantics: {kind: 'count'}},
            ],
            marker: {size: {measure: 'staff'}},
            points: SITES.map(({staff, ...site}) => ({
              ...site,
              values: {staff},
            })),
          },
        ]}
      />
    </div>
  );
}
```

## Layers

| `type` | Holds | Interactive |
| --- | --- | --- |
| `area` | `features`: `{key, name?, geometry, values?, details?}` | yes |
| `point` | `points`: `{key, latitude, longitude, name?, values?, details?}` | yes |
| `context` | `geometries`: areas and lines | no |

Area values can be inline (`values`) or joined by key from separate records (`data`), never both.

## Measures

A layer declares its measures: `{id, label, semantics?}` for a number (with the family's value
semantics: `count`, `money`, `percent`, …) or `{id, label, kind: 'category', categories}`. A value
per object is a number, a category id, missing (`null` or absent) or withheld
(`{suppressed: true}`): a withheld value is never shown as a number.

## Encodings

- **Fill** of areas (and points): `{measure, scale}` with a classed scale (`threshold`,
  `quantize`, `category` or `diverging`), or a constant colour. End classes are open.
- **Size** of points: `{measure, domainMax?, maxRadius?}`: the area of a circle is proportional to
  the value; zero, missing and withheld draw a hollow marker.

The legend explains every encoding that needs it, including the special states (no value,
withheld).

## Text

Tooltip, labels and the accessible description read the same fields: `'name'`, `{measure}`,
`{encoding: 'fill'}`, `{detail}`. Labels on areas and points are placed so they do not collide.
