Geodesy
Distances and areas come from GeographicLib's Karney solver on the WGS84 ellipsoid, the same mathematics the benchmark checks every library against.
A WebGL2 GIS library that measures on the ellipsoid: Karney geodesy on WGS84, its own State Plane projections checked against NGS and Karney, true circular arcs, decluttered labels, id-buffer picking, and topology editing that keeps parcel fabrics sealed. 44 KB on the wire.
npm i @devtomorrow/sundial
Every button draws on the map. Keep an eye on it.
Distances and areas come from GeographicLib's Karney solver on the WGS84 ellipsoid, the same mathematics the benchmark checks every library against.
A DCEL fabric where a shared boundary is one edge pair, so gaps cannot form. Snapping, rule enforcement, exact undo. Arm the lab, hold Shift, and drag any shared corner.
The full editing plate: repair and rules from your own layers →
County deeds carry circular-arc boundaries; classic GeoJSON cannot express them. Sundial speaks every open format that can: OGC JSON-FG (the circular-arcs class), SQL/MM WKT as spatial databases emit it, and Esri JSON curveRings. One recorded arc stays one arc in all three, tessellated only for the screen at two centimeters, and exports back out the same way: byte-identical for any arc you have not edited, which the round-trip tests pin.
Sundial rasterizes its own signed-distance glyphs and declutters with a typed-column collision pass. Ten thousand labels, timed on your machine.
An id-buffer renders every feature's identity off-screen; hover reads it back asynchronously through a fence ring. Load the county fabric and point.
A fourteen-second scripted world tour with frame deltas sampled the whole way. Watch the map fly it and read your average.
import { SundialMap } from '@devtomorrow/sundial';
const map = new SundialMap({
container: document.getElementById('map'),
center: [-103.2497, 44.0525],
zoom: 16,
});
await map.whenSettled();
const deeds = map.addGeoJson(parcels, {
fill: { color: '#c9a13b', opacity: 0.2 },
outline: { color: '#c9a13b', widthPx: 1.5 },
});
const { addLabels } =
await import('@devtomorrow/sundial/labels');
addLabels(map, points.map((p, i) =>
({ lngLat: p, text: names[i], priority: 1 })));
map.on('click', (ev) => console.log(ev.feature));
const { whenGeodesyReady } =
await import('@devtomorrow/sundial');
const geo = await whenGeodesyReady();
geo.Inverse(34.05, -118.24, 51.5, -0.13);
const { EditWorkspace, curveFeaturesToInputs } =
await import('@devtomorrow/sundial/edit');
const ws = new EditWorkspace(
curveFeaturesToInputs(deeds), 44.08);
// moving a shared corner moves every deed on it
ws.moveVertex(ws.snap(lngLat, 6).node, target);
import { EditSession, SundialBackend, FeatureServiceBackend, DocumentBackend } from '@devtomorrow/sundial/sync';
import { createEditor, mountPeers } from '@devtomorrow/sundial/editor';
// Live, multi-user, with presence: the Sundial companion server.
const live = new EditSession(SundialBackend.open(base, datasetId, { peer, name }), { name });
const editor = createEditor(map, await live.open(), { session: live });
mountPeers(editor, document.getElementById('peers')!);
// Save-based: an OGC API Features collection, an ArcGIS layer, or one file.
const ogc = new EditSession(FeatureServiceBackend.ogcApi({ collectionUrl }));
const arcgis = new EditSession(FeatureServiceBackend.arcgis({ layerUrl, token }));
const file = new EditSession(new DocumentBackend({ url, format: 'json-fg' }));
Sundial competes on the Frontend GIS Benchmark as a house entry. Every figure above comes from the published methodology: same fixtures, same ladder, and a Karney reference computed live.
The pages the e2e suite drives, each case on a stage of its own, runnable by hand: ink truth (geometry reaches pixels), engine lifecycle (context loss, the device pool, resizing, picking, the settle contract) and arc ink (the GPU arc layer against an analytic circle, every device pixel).