geo

A geographic environment on the scene's canvas. Its in: names the boundary pack the environment is drawn over — geo:world is a world map on its own, with no child node needed — and every overlay inside it inherits that pack. The body holds those overlays: a choropleth joined to an @data dataset, marker points, great-circle arc routes, further region layers, and the parameters that drive them. The frame is a camera rather than a pair of domains — centre: (latitude first) and zoom: (web-map doubling levels) — and both are animatable with !cue.to, so one keyframe on each is a fly-to. The committed packs ship with the library and an attached .geojson travels with the document, so nothing is ever fetched from a tile server: a map renders offline, under both themes, and identically in ten years.

geoNode
A map zoomed on the Tasman Sea, Australia and New Zealand filled teal, with markers at Sydney and Auckland drawn over the route joining them.A map zoomed on the Tasman Sea, Australia and New Zealand filled teal, with markers at Sydney and Auckland drawn over the route joining them.
Writing with it:

#The geographic environment

geo is a map surface. Its implicit attribute is in, the boundary pack the environment is drawn over, so geo:world{ } is already a world map with nothing inside it. The committed packs are world (Natural Earth 110m countries) and six admin-1 sets — us-states, canada-provinces, australia-states, uk-counties, nz-regions and europe-admin1. Every overlay in the body inherits that pack unless it names its own. title captions the environment, projection chooses how the sphere is laid flat, and show-scale (on by default) says what a length on screen is worth on the ground.

A world map on the Equal Earth projection, titled Where the survey ran, with a marker at Paris.A world map on the Equal Earth projection, titled Where the survey ran, with a marker at Paris.
geo:world{
    marker(at: 48.85, 2.35; label: Paris)
}(
    title: Where the survey ran
    projection: equal-earth
)

Boundaries a pack does not carry — electorates, catchments, council wards — arrive as an attached .geojson file, and in names it the same way it names a pack: in: #wards, pointing at an @data declaration. Either way the geometry is drawn as SVG, exactly as a graph mark is, and nothing is fetched from a tile server — which is what lets a map render offline, under both themes, and identically in ten years.

The projection is a claim, not a decoration. equal-earth keeps areas honest and is the right world framing; mercator keeps angles honest and is the frame on which a great-circle route visibly bends; conic tunes its standard parallels to whatever the environment contains and is the right choice for one country; albers-usa carries Alaska and Hawaii in as insets; globe is the sphere itself, the far hemisphere behind the horizon. Choose the one whose distortion your claim can afford.

#centre, zoom and the fly-to

A geographic environment is framed by a camera rather than by a pair of domains: centre — latitude first — and zoom, in web-map doubling levels, where 1 fits the world's width and each level halves the span. Omit them both and the environment fits its own contents across the whole choreography, so a marker that travels stays framed at every point of its journey.

Both are animatable, and a !cue.to on each with the same anchor is the fly-to. A centre may also be written as #id naming a marker the environment already draws, so a map framed on Tokyo that also pins Tokyo states the place once; a keyframe onto a reference snaps rather than gliding, because a reference has no midpoint to interpolate through.

Three frames of a map flying from the whole world in to France and Germany, filled teal.Three frames of a map flying from the whole world in to France and Germany, filled teal.
geo#tour:world{
    region(in: world; only: France, Germany; fill: teal)
}(
    centre: 20, 0 !cue.to{50.5, 15}(at: 1; over: 900ms)
    zoom: 1 !cue.to{4}(at: 1; over: 900ms)
)

interactive: true hands the camera to the reader — scroll to zoom, drag to pan, double-click to reset. Exploration stays on the environment's subject: the zoom floor is the frame that fits its packs, markers and arcs, so a reader cannot lose the map. Their camera then overrides the authored one for the session, and continue: #that-environment carries it into a later environment. The scene's parameters do not travel with it — they are not the map's to hand over.

#Overlays

Everything drawn over the basemap is an overlay, and the four share opacity and hidden (declared but not drawn, and animatable, so hidden: true !cue.to{false}(at: 2) is a reveal). Cues wrap overlays exactly as they wrap graph marks. The attention cues do not reach this environment: a highlight ring and a spotlight are plot machinery, and a geographic environment admits only cue and cue.draw.

#Boundary layers

The environment's in: already draws its pack, so a region is for what a basemap cannot say: a subset in its own colours, a second pack layered over the first, an attached.geojson over a committed pack, or boundaries that arrive on a cue. only and except narrow it by name or ISO alpha-3 code, matched case-insensitively — France, DEU and Ivory Coast all read — and fill, stroke and width paint what is left.

A world map without Antarctica, with France, Germany and Italy filled teal.A world map without Antarctica, with France, Germany and Italy filled teal.
geo:world{
    region(in: world; except: Antarctica)
    region(in: world; only: France, Germany, Italy; fill: teal)
}

#Joining a dataset to regions

choropleth joins a @data column onto the pack's regions — the geographic sibling of the graph's heatmap. regions names the column of region names and values the column to shade by; join keys match pack names and short codes case-insensitively, and a region the join does not cover keeps the neutral land fill rather than disappearing.

values is a column of numbers, always: where along a scale each region sits. Which scale is colour — multicolour, temperature, or a single hue's own intensity — with range pinning the window. Where one scale will not do, colours names a column of them, a cell per row holding whatever colour would; rows sharing a cell are one group in the legend and labels says what to call it. That is the electoral map's own grammar — the winner names the colour, the margin says how solid it is — and it is the same sentence as the single-scale map, with the scale varying by row rather than being one for all.

geo:australia-states{
    choropleth(
        regions: #vote.state
        values: #vote.margin
        colours: #vote.colour
        labels: #vote.party
    )
}(
    title: Who led each state
)
A choropleth publishes derived min, max, mean and count over the values the join actually covered, so prose can cite the spread the shading spans instead of a number typed in beside it: give the mark an id — choropleth#gdp(…) — and read {{#gdp.max}}.

#Places

marker is a point on the earth, in two forms. Singly, at takes a coordinate, latitude first, with an optional label; either half may be an expression of the scene's parameters, so a marker rides a slider or a keyframe. Data-bound, lat and lon name columns and the one node draws every row — add size-by and symbol area scales with that column, which is the proportional-symbol map.

geo:world{
    marker(lat: #quakes.lat; lon: #quakes.lon; size-by: #quakes.magnitude; colour: red; opacity: 0.6)
}

#Great-circle routes

arc draws the great-circle path between two places — the shortest route over the earth, drawn the way the earth actually is. On a mercator environment the London–Tokyo arc bends far north of the straight line between them, and that one element is the whole projection-distortion lesson. Either end may be #id naming a marker in the same environment, and an anchored end follows its marker. An arc publishes derived distance in kilometres — the number the arc is.

Three frames of a Mercator world map with markers at London and Tokyo, the great-circle route between them drawing in beneath the markers.Three frames of a Mercator world map with markers at London and Tokyo, the great-circle route between them drawing in beneath the markers.
geo:world{
    cue.draw{
        arc#route(from: #london; to: #tokyo; label: {{round(#route.distance, 0)}} km)
    }(
        in: 1
        in-duration: 1200ms
    )
    marker#london(at: 51.47, -0.46; label: London)
    marker#tokyo(at: 35.55, 139.78; label: Tokyo)
}(
    projection: mercator
)

#Moving a map through time

A map moves through its data in one of two ways, and which one is a fact about the data. For a handful of slices, chain the columns: values is animatable, so #y1990.rate !cue.to{#y2000.rate}(at: 1) eases every region between its own two readings. Pin range when you do, or the ramp re-normalises each frame and the change erases itself.

For long-format data — one row per region per slice — a control bound over: the slice column is the better tool. It takes its range, its stops and its format from the data rather than being told them, so naming the column gives the scene a scrubber over exactly that span; marks carrying a slice: read themselves against it, interpolating between the two values that bracket wherever the reader is standing. Writing it as a cue.slider hands the scrubbing to the scene: the map runs its years from the anchor, and the step's own transport plays it.

geo:world{
    cue.slider#years:t(over: #rates.year; name: Year; at: 1; duration: 12s)
    choropleth(regions: #rates.country; values: #rates.rate; slice: #rates.year; range: [0, 220])
}

A slice column of NAMES rather than years works the same way and cannot be interpolated — there is nothing between Brazil and China — so the control for it is a selector, and the map shows the rows carrying the name the reader picked.

Parameters and cues gives the rule the choice actually turns on: a change belongs to a scrubber when one sentence holds across its whole range, and to a cue otherwise.

#Derived attributes

Two overlays compute something and publish it for reading: a choropleth's min, max, mean and count, and an arc's distance. A control publishes value, where its parameter is standing — the year, on one bound over: a column of them. A selector whose values are names publishes nothing, since a live value is a number. Reach one by member access on the node's id — #route.distance in an attribute, {{#route.distance}} in prose. Derived attributes covers how they behave.


#Attributes

inImplicit
The environment's basemap — the boundary pack it draws, and the one every overlay inside it inherits. world is Natural Earth 110m countries; the other six are admin-1 packs. It may instead be #id naming an attached .geojson dataset — in: #wards — which makes the attachment itself the boundaries. Normally written in the head, geo:world, the way code:python is.
worldus-statescanada-provincesaustralia-statesuk-countiesnz-regionseurope-admin1
Also written: basemapCan be written as shorthand: geo:value
names
For an attached .geojson only: the feature property holding each region's name, which is what a choropleth joins on. Absent, the first of name, NAME, title or id the features carry.
codes
For an attached .geojson only: a feature property holding a short join code — the custom equivalent of a pack region's ISO alpha-3.
simplify
For an attached .geojson only: Douglas–Peucker tolerance in degrees, thinning boundary detail as the file is read. 0 keeps every point; a large file wants about 0.01.
Default0
title
The caption set over the top-left of the environment. A map carries no axis labels to name its own subject, so unlike a graph's title: this one is visible content and not merely an accessible name. Rich text, so $maths$ and inline formatting render.
projection
How the sphere is laid flat — which decides what the map tells the truth about. equal-earth, the default, keeps AREAS honest and is the right world framing. mercator keeps ANGLES honest: the frame every slippy map uses, and the one on which a great-circle arc visibly bends away from the straight line. equirectangular plots latitude and longitude straight. conic is an Albers equal-area conic that tunes its standard parallels to whatever the environment contains — what national atlases use, and the right choice for a single country or region. albers-usa is the United States composite, the lower 48 on an equal-area conic with Alaska and Hawaii carried in as insets, so a US environment spends no width on the Pacific. globe is the sphere itself, orthographic, with the far hemisphere behind the horizon.
equal-earthmercatorequirectangularconicalbers-usaglobe
Defaultequal-earth
centreAnimatable
Where the camera looks: a coordinate written LATITUDE FIRST — centre: 48.85, 2.35 — or #id naming a marker this environment already draws, centre: #tokyo, so the place is stated once. Either half of a coordinate may be a parametised expression. Animatable: centre: 20, 0 !cue.to{50.5, 15}(at: 2; over: 900ms) pans the frame, and writing the same anchor on zoom: makes the two move as one fly-to. A keyframe targeting a #id snaps rather than gliding, because a reference has no midpoint to interpolate through. Omitted, the environment frames its own contents.
Also written: center
zoomAnimatable
Web-map doubling levels: 1 fits the world's width, and each level halves the span. Omitted, the zoom is AUTOMATIC — the environment fits what is inside it across the whole choreography, so a marker that travels stays framed at every point of its journey; writing zoom: 1 asks for exactly 1 instead. Parametised, and animatable with !cue.to.
interactive
Let the reader explore: scroll to zoom about the cursor, drag to pan, double-click to reset, with zoom and reset controls in the environment's toolbar. Exploration stays on the environment's subject — the zoom floor is the frame that fits its packs, markers and arcs, and panning cannot leave them behind. Once touched, the explored camera overrides the authored one for the rest of the session.
Defaultfalse
show-scaleAnimatable
The scale bar in the corner, saying what a length on the screen is worth on the ground. On by default, because every projection here changes scale across the frame and a reader has no other way to judge distance. It is measured at the middle of the environment, the only place the reading is honest. Turn it off for an environment where distance is beside the point.
Also written: scale
Defaulttrue
show-graticuleAnimatable
Draw the latitude and longitude grid over the environment. Animatable, so a cue can bring the grid in at the step where the projection itself becomes the subject.
Also written: graticule
Defaultfalse
graticule-spacing
Degrees between graticule lines. Does nothing without show-graticule:.
Default15
continue
Inherit an earlier geographic environment's state: continue: #that-environment. The reader's explored camera and their parameter values carry forward; every authored attribute is the new environment's own.

#Allowed content

#Allowed in

#Examples

The world on the Equal Earth projection, titled The world.The world on the Equal Earth projection, titled The world.
geo:world(title: The world)
The Australian states shaded by the winning party’s margin, red for Labor and blue for the Coalition, titled Who led each state.The Australian states shaded by the winning party’s margin, red for Labor and blue for the Coalition, titled Who led each state.
geo:australia-states{
    choropleth(regions: #vote.state; values: #vote.margin; colours: #vote.colour; labels: #vote.party)
}(
    title: Who led each state
)
Three frames of a map flying from the whole world in to France and Germany, filled teal.Three frames of a map flying from the whole world in to France and Germany, filled teal.
geo#tour:world{
    region(in: world; only: France, Germany; fill: teal)
}(
    centre: 20, 0 !cue.to{50.5, 15}(at: 1; over: 900ms)
    zoom: 1 !cue.to{4}(at: 1; over: 900ms)
)
The Canadian provinces on a conic projection, shaded by unemployment, Newfoundland and Labrador darkest.The Canadian provinces on a conic projection, shaded by unemployment, Newfoundland and Labrador darkest.
geo:canada-provinces{
    choropleth(regions: #d.province; values: #d.rate)
}(
    projection: conic
)

#Notes

  • An environment's own id is written on the node — geo#europe{…} — and continue: points back at it with #europe.
  • Cues wrap overlays exactly as they wrap graph marks, and cue.draw traces an arc along its length.
  • The cursor's coordinates, and the name of the region beneath it, read out in the corner of the environment.

Last updated