Skip to content

Repository files navigation

The topographic logo: three contour rings on paper folded along one diagonal crease, on a graphite tile

topographic

npm: topographic npm: topographic-react License: Apache 2.0

Topographic terrain rendered with WebGL. Generate a land or turn a real place into a topographic terrain.

Open the playground · Docs · npm: topographic · npm: topographic-react

Everest and the Khumbu valley in 3D, drawn as pale contour lines from real elevation data, the camera swinging slowly around the block

With a coding agent, install the topographic skill and ask for a terrain:

npx skills add yahyabedirhan/topographic

By hand:

npm install topographic
npx skills add yahyabedirhan/topographic

The second line installs the topographic skill for your coding agent.

topographic draws terrain as contour lines, from real elevation data or from a shape and a seed. The same settings draw the same land on every screen.

Each example below shows its code beside the terrain it draws.

Real places

Name a place, and topographic draws it from real elevation data.

import { createTopography } from 'topographic';

createTopography(el, {
  location: 'everest',
});
Everest and the Khumbu valley in 3D as pale contour lines on dark paper

Ten places are built in.

Istanbul, with the Bosphorus between the two shores
istanbul
Cyprus, the whole island
cyprus
The Matterhorn above Zermatt
matterhorn
Geirangerfjord between steep walls
geirangerfjord
The Grand Canyon from rim to rim
grand-canyon
Yosemite Valley, Half Dome and El Capitan
yosemite
Torres del Paine, granite towers over lakes
torres-del-paine
Mount Fuji, the cone and its lakes
fuji
Everest above the Khumbu valley
everest
Kilimanjaro over the plains
kilimanjaro

In the playground, open Map, frame any other area and click Apply. Its Map menu also holds about 100 more places to search, listed in docs/places.md. Export then gives you the code for it.

Generated shapes

Without a place, topographic generates the land. A shape sets its form, and every seed is a new land of that form. Each step below adds to the one before.

createTopography(el, {
  shape: 'drifting-lobes',
  layout: { anchor: 1 },
});
Soft contour rings around one summit in the centre, pale lines on dark paper
createTopography(el, {
  shape: 'granite',
  seed: 21,
  layout: { anchor: 1 },
});
Rougher rings of the granite shape around a summit lower in the frame
createTopography(el, {
  shape: 'granite',
  seed: 21,
  layout: { anchor: 1 },
  terrain: { peaks: 3, terracing: 0.85 },
});
The same land with two summits, the rings grouped into stepped bands
createTopography(el, {
  shape: 'granite',
  seed: 21,
  layout: { anchor: 1 },
  terrain: { peaks: 3, terracing: 0.85 },
  mode: '3d',
  elevation: 0.45,
  camera: { elevation: 42, radius: 2.6 },
  theme: { paper: '#120d07', ink: '#e0a24a' },
});
The two summits raised in 3D, amber lines on dark brown paper

Every picture here also sets dark paper and a light ink, theme: { paper: '#0b0b0d', ink: '#cfd8ea' }, and brighter lines, which the code leaves out.

2D and 3D

The same land draws as a flat contour map or as a 3D terrain. Here the Grand Canyon goes from 2D to 3D, then takes a new look and more lines.

createTopography(el, {
  location: 'grand-canyon',
  mode: '2d',
  lines: { count: 24 },
});
The Grand Canyon from above as a flat contour map, pale lines on dark paper
createTopography(el, {
  location: 'grand-canyon',
  mode: '3d',
  lines: { count: 24 },
});
The same canyon raised in 3D on a block
createTopography(el, {
  location: 'grand-canyon',
  mode: '3d',
  lines: { count: 24 },
  theme: { paper: '#efe6d2', ink: '#1f2a44' },
});
The canyon in 3D with dark navy lines on beige paper
createTopography(el, {
  location: 'grand-canyon',
  mode: '3d',
  lines: { count: 60, indexEvery: 5 },
  theme: { paper: '#efe6d2', ink: '#1f2a44' },
});
The same view with many more lines, the canyon walls dense with navy contours

In 3D, camera sets the angle and the distance, and camera.motion moves it: orbit, push, flyover or drone. The guide lists every 3D setting.

Usage

In plain HTML, use the web component:

<script type="module">import 'topographic/element';</script>

<topographic-view location="fuji" mode="3d"></topographic-view>

In JavaScript, createTopography draws into an element and returns the instance:

import { createTopography } from 'topographic';

const topography = createTopography(document.querySelector('#terrain'), { shape: 'long-ridge' });
topography.update({ terrain: { peaks: 3 } });

In React, install topographic-react too:

npm install topographic topographic-react
import { Topography } from 'topographic-react';

<Topography location="yosemite" />

The terrain fills the element you give it. The guide shows how to size it and how to put content over it.

A background can also blend between scenes, each with its own shape and line colour. A new scene fades in, and with scenes set, a theme change fades between light and dark:

const topography = createTopography(el, {
  scenes: [{ shape: 'drifting-lobes' }, { shape: 'long-ridge', ink: 'rgb(98% 50% 13%)' }],
  scene: 0,
});
topography.update({ scene: 1 });

The guide covers scenes, the dark amount, the centre column, the maxFps cap and setOptions. In the playground, Install and Export give you this code with your settings filled in.

Images and video

snapshot() draws a PNG at any size, or any part of the view. record() draws a 3D video as MP4 or WebM. Both draw off screen, so the page does not change while they run.

const png = await topography.snapshot({ width: 1920, height: 1080 });
const clip = await topography.record({ seconds: 6, motion: 'orbit', format: 'mp4' });

AI agents

The topographic skill teaches your coding agent the settings, the places and the code:

npx skills add yahyabedirhan/topographic

The playground has plain-text docs for AI tools at llms.txt. In browsers that support WebMCP, an agent can use the playground directly: change the settings, choose a place, take a screenshot, record a video and get the code. Without WebMCP, an agent can open a link with URL parameters, or call the same tools through a small stub; llms.txt shows how.

The guide lists every setting with its range and default, and covers colours and themes, server rendering and the WebMCP tools.

How it works

How topographic fits together: the web component, the React component and the playground call one public API, which also takes an optional heightfield. Settings resolve from the params table, shapes and looks, and one WebGL renderer draws them as a 2D contour shader or a 3D terrain pass, whose frames record() encodes as video.

The web component, the React component and the playground all use the same code. One renderer draws both 2D and 3D.

One terrain, two views: settings, or an optional heightfield, feed one shared contour field. In 2D a fragment shader paints its lines on every pixel. In 3D a mesh is lifted by the same field, painted with the same lines and seen through a moving camera.

2D and 3D use the same land and the same lines. 3D adds height and a camera.

How a real place becomes a terrain in the playground: the Map popover gives a bounding box, and the playground worker calls the library's topographic/locations. A named place loads its built-in grid with loadLocation(). Any other area goes through fetchLocation(), which fetches Terrarium elevation tiles, stitches and resamples them to a 256 by 256 grid, scales the relief and smooths it. The region hook hands the heightfield to the renderer with setHeightfield().

A real place starts as elevation tiles. They are stitched, smoothed and turned into a grid of heights, which the renderer draws. The low-level design explains each part.

Limits

  • An area framed on the map in the playground can be up to 300 km wide.
  • record() draws the 3D view only, up to 12 seconds a clip.
  • topographic needs WebGL. Without it, the element shows a plain colour.
  • It stops drawing when it is off screen, and draws one still frame for people who turn on reduced motion. A scene fade runs to its end first.
  • Scenes blend in 2D only, up to 8 at a time.

Contributing

Issues and pull requests are welcome. You need Node.js 20 or later.

npm install
npm run dev        # the playground, live
npm test           # unit tests
npm run build      # packages and the playground
npm run smoke      # headless Chrome checks (set CHROME=/path/to/chrome)

scripts/render-readme-assets.mjs draws the images in this README, the npm pages and the social cards with the library itself. scripts/readme-shots.mjs retakes the playground screenshots in assets/screenshots/.

Credits

The code structure was inspired by Paper Shaders. Height data comes from Mapzen Terrain Tiles on AWS Open Data. Map images come from OpenFreeMap, OpenMapTiles and OpenStreetMap.

Privacy

The npm packages collect nothing. The playground site sends cookieless analytics and error reports; PRIVACY.md lists every event and how to opt out.

License

The code is licensed under the Apache License 2.0. See LICENSE and NOTICE. If you use or adapt it, please credit Yahya Bedirhan Pak with a link to yahyapak.dev.

The project name, the logo in assets/images/logo/, the screenshots in assets/screenshots/, the images in apps/playground/public/readme/ and assets/social/, and the social cards apps/playground/public/og.png and og-docs.png are not covered by this license and are all rights reserved.

Releases

Packages

Contributors

Languages