/ Technical Architecture Overview

OrthoMap — Technical Architecture

A WebGL2 map rendering framework specialized for orthographic (globe view) projection
Worker-based parallel pipeline · OffscreenCanvas · Gint / GeoPBF / AltPBF integration

Table of Contents
  1. Architecture Overview
  2. Orthographic Projection & Interaction
  3. Worker Communication Protocol
  4. Base Layer — WebGL2 Globe Rendering + Tiles
  5. BorderLines Layer — Lightweight Gint Line Rendering
  6. Accessories Layer — Canvas2D Overlay
  7. Gint Layer — Full WebGL2 Vector Rendering
  8. State Management & Gadgets

1. Architecture Overview

OrthoMap keeps only projection math, event handling, and layer management on the main thread, delegating all rendering to OffscreenCanvas + Web Workers. Each layer runs on its own independent Worker, achieving zero rendering blockage on the main thread.

Canvas2D Accessories stars · constellations · night · globe minimap · scale · clock (border.js Worker)
↑ overlay (topmost)
WebGL2 BorderLines graticule · country borders · maritime boundaries · geographic lines (gintBorder.js Worker)
↑ vector overlay
WebGL2 OrthoMapGL base globe texture + slippy tiles (base.js Worker)
↕ user-added layers (createLayer / createRemoteLayer)
Worker Gint Layer arbitrary GeoPBF → WebGL2 rendering · hover picking · polygon fill (gint.js Worker)

Initialization completes in three sequential steps.

// index.js — entry point
const map = await orthoMap({
	target: d3.select("#container"),  // defaults to body
	baseName: "osm.street",
	center: [139.7, 35.7],
	zoom: 4,
	accessories: { stars: { maxCount: 500 } },
	gadgets: false,
});

// Internal sequence:
// 1. orthographic(map, opts)  — projection, events, interaction setup
// 2. createLayers(map, opts)  — spawn Workers, create 3 layers
// 3. createGadgets(map)       — register UI components

2. Orthographic Projection & Interaction

2.1 Scale ↔ Zoom Conversion

D3's geoOrthographic uses a scale value representing the globe radius in screen pixels. OrthoMap converts this to the standard web map zoom level.

scale = 2zoom × (256 / 2π)
zoom = log2(scale / (256 / 2π))
256 / 2π ≈ 40.74 (based on 256 px tile size)

Below zoom 5.5 (threshold), only the base globe image is shown; above it, slippy tiles are composited on top.

2.2 Versor Quaternion — Pan, Zoom, and Rotation

Mouse drag and pinch gestures are built on D3's zoom events, using versors (unit quaternions) for geometrically correct sphere-surface tracking.

// Drag start: convert touch point to Cartesian 3D on the sphere
v0 = cartesian(proj.invert(pt0));  // [lng,lat] → [x,y,z]
q0 = versor(r0 = proj.rotate());  // current rotation as quaternion

// During drag: compose minimal-arc rotation as quaternion product
const v1 = cartesian(proj.rotate(r0).invert(pt));
const q1 = multiply(q0, delta(v0, v1));
proj.rotate(rotation(q1));

During a two-finger pinch, the angular difference between fingers is composed as an additional quaternion, enabling simultaneous Z-axis rotation (map tilt).

Ctrl + Wheel: Cursor-Centered Z-Rotation

Holding Ctrl or Meta while scrolling rotates the globe around the point under the cursor. The 3D sphere vector [x,y,z] at the cursor position is computed and used as the rotation axis, producing a quaternion that is composed with the current rotation. This is a physically grounded rotation about an actual point on the sphere, not a simple camera spin.

2.3 flyToFeature / zoomToFeature

Both functions use a monotonically incrementing flyTicket counter so that any new interaction immediately cancels an in-progress animation.

FunctionAlgorithmCharacteristic
flyToFeature d3.interpolateZoom (zoom out → travel → zoom in) + linear easing Draws a smooth arc through the air for long-distance navigation
zoomToFeature 2-phase: ① rotate to destination at threshold zoom, ② zoom in on arrival Travels at tile-free zoom level, then zooms in — minimizes tile requests
// zoomToFeature — 2-phase composition
// Phase 1: travel capped at zoom 5.5 (travelMs ≈ distance × 2500 ms)
const sTrav = min(s1, zval2scale(5.5));
// Phase 2: zoom in over 2000 ms after arrival (600 ms overlap)
proj.scale(s0 + (sTrav - s0) * tRotEased          // travel phase
			 + (s1  - sTrav) * easeCubicInOut(tZoom)); // zoom phase

2.4 Event System

Built on D3 Dispatch, the API distinguishes between user-registered (anonymous) and system-registered (named) handlers.

// User event registration — auto-numbered namespace management
const handler = map.onClick(e => console.log(e.lng, e.lat));
handler.destroy(); // unregister

// Available events:
// Enter / Move / Leave / Drop / Click / ContextMenu
// Drawing / Drawn / Resize / Change / LoadStart / LoadEnd

// Move / Click carry elevation from AltPBF integration
// e = { lng, lat, alt, x, y, shiftKey, metaKey }

3. Worker Communication Protocol

Each layer transfers canvas ownership to its Worker via OffscreenCanvas.transferControlToOffscreen() and communicates with the main thread exclusively through postMessage. The entire render loop runs inside the Worker.

Main Thread → Worker
init — offscreen / dpr / workers
set — data upload (per cmd)
drawing — scale / rotate / panning
drawn — commit render after pan ends
move — cursor coordinates
resize — width / height
destroy — terminate Worker
Worker → Main Thread
{action:"done", type:"init"}
{action:"done", type:"set"}
{action:"done", type:"resize"}
{action:"identify", featureId}
{action:"click", featureId, lng, lat}
{action:"redraw"} — after context restored
Separation of Drawing and Drawn

drawing is a per-frame "immediate render" command fired continuously during pan and zoom — aimed at fast, lower-quality output (no new tile fetches). drawn is a "commit render" command sent once when the pointer stops, triggering expensive work such as high-quality FBO rendering and picking buffer updates.

When a set message contains binary render data, it is sent as a Transferable for zero-copy transfer.

// Transferable transfer only when rawBuffers is present
if (prop?.rawBuffers) {
	const { rawBuffers, ...rest } = prop;
	worker.postMessage({ type: "set", cmd, data, prop: rest, rawBuffers }, rawBuffers);
}

4. Base Layer — WebGL2 Globe Rendering + Tiles

The base.js Worker maps a full-globe equirectangular image (8192×4096) onto the sphere as a texture, then composites slippy tiles on top as the user zooms in — a two-tier design.

4.1 GPU-Side Orthographic Texture Sampling

The fragment shader back-projects each pixel's screen coordinate to a sphere coordinate and samples the equirectangular texture directly, eliminating any CPU-side projection or vertex buffer.

// GLSL Fragment Shader (baseFs) — orthographic inverse projection
float x = (gl_FragCoord.x - translate.x) / scale;
float y = (translate.y  - gl_FragCoord.y) / scale;
float rho = sqrt(x*x + y*y);
if (rho <= 1.0) {                          // inside globe circle only
	float c = asin(rho);
	float lng = atan(x * sin(c), rho * cos(c));
	float lat = asin(y * sin(c) / rho);
	applyRotation(rx, ry, rz, lng, lat);   // apply user rotation
	vec2 uv = vec2((lng + pi) / (2.0*pi), (lat + pi/2.0) / pi);
	outColor = texture(u_image, uv);
} else { discard; }                       // discard fragments outside globe
Texture Wrap Modes

The base texture uses REPEAT on the S axis (longitude) and CLAMP_TO_EDGE on T (latitude), preventing seams when rendering across the ±180° antimeridian. Tile textures use CLAMP_TO_EDGE on both axes to avoid bleeding between adjacent tiles.

4.2 Tile Loading Pipeline

A Worker pool (4 parallel by default) fetches tile images by URL, converts them to ImageBitmap, and uploads them as GL textures via createTileTexture.

// Tile priority: load tiles closest to screen center first
ents.sort((a, b) => a[4] - b[4])  // [4] = distance from center
	.forEach(([x, y, zo, pos, dist]) => {
		// zo = polar LOD offset (from Y2T/Y4T tables)
		// fall back to cached z-1, z-2, z-3 if preferred zoom not available
		for (let dz of [0, 1, 2, 3]) {
			const z = Z0 - (zo + dz); if (z < 0) continue;
			const img = TileTub.get(name);
			if (img) { /* remap UV to parent tile region and draw */ return; }
		}
	});

Y2T / Y4T tables: High-latitude tiles appear very small on screen due to Mercator distortion. These constant tables define a per-zoom LOD offset (zo) that causes polar tiles to be loaded at 1–2 zoom levels lower, avoiding unnecessary high-resolution fetches.

4.3 Available Base Maps

NameBase Globe ImageTile SourceMaxZoom
whiteEarthwhiteEarth.webpnone7
google.streetnaturalEarth.webpGoogle Maps (lyrs=m)22
google.satellitegoogle.satellite.webpGoogle Maps (lyrs=s)22
osm.streetnaturalEarth.webpOpenStreetMap JP19
osm.satelliteosm.satellite.webpBing Maps (quadkey index)19
cyberjapan.stdnaturalEarth.webpGSI Japan (Japan extent) + OSM (outside)19
Firefox Workaround

Firefox cannot reliably use createImageBitmap(blob) results as WebGL textures. A UA-detected workaround renders through an intermediate OffscreenCanvas and re-converts with transferToImageBitmap().

5. BorderLines Layer — Lightweight Gint Line Rendering

gintBorder.js is a lightweight variant of the Gint renderer that implements line drawing only. It has no hover picking, polygon fill, or FBOs, and draws multiple fixed-style datasets in a single pass.

5.1 Four Datasets × Four Styles

// 4 Gint datasets (loaded via GeoPBF)
const geoNames = [
	"ne_110m_graticules_10",               // 10° graticule grid
	"ne_50m_admin_0_boundary_lines_land",   // country borders
	"ne_50m_admin_0_boundary_lines_maritime_indicator", // maritime boundaries
	"ne_50m_geographic_lines",              // equator, tropics, etc.
];

// Per-dataset draw styles (dash values in screen pixels)
const BORDER_GL_STYLES = [
	{ color: [1.0, 1.0, 1.0, 0.6], lineWidth: 1.0, dash: [0, 0] }, // graticule (solid)
	{ color: [1.0, 1.0, 1.0, 1.0], lineWidth: 1.0, dash: [4, 2] }, // country borders
	{ color: [0.5, 0.5, 1.0, 0.8], lineWidth: 0.8, dash: [4, 2] }, // maritime
	{ color: [1.0, 1.0, 1.0, 0.6], lineWidth: 0.8, dash: [4, 2] }, // geographic lines
];

5.2 Screen-Pixel-Constant Dash Pattern

Dash lengths maintain a constant visual size regardless of zoom level. The vertex shader computes a cumulative arc distance as v_dist_base = vertex_index × scale × sin(1°), giving a pixel-space distance that stays visually consistent as the zoom changes.

5.3 Alpha Compositing (blendFuncSeparate)

To prevent semi-transparent alpha from accumulating where lines overlap, the color and alpha channels are blended separately.

gl.blendFuncSeparate(
	gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,  // RGB: standard alpha blend
	gl.ONE,       gl.ONE_MINUS_SRC_ALPHA   // A: preserve source alpha
);

5.4 Horizon-Crossing Polygon Handling (VS_STENCIL)

The stencil-pass vertex shader does not collapse back-hemisphere vertices (z < 0) to NDC origin. Instead it pushes them outward to the horizon circle (radius = u_scale). This keeps fan triangles well-formed even when a border polygon straddles the horizon, preventing fill artifacts for horizon-crossing features.

// GLSL VS_STENCIL — push back-hemisphere vertices to the horizon
if (p.z < 0.0) {
	vec2 v = p.xy - u_viewport * 0.5;
	float d = length(v);
	gl_Position = d < 1e-4
		? vec4(0.0, 0.0, 0.0, 1.0)            // directly behind center → origin
		: toNDC(u_viewport * 0.5 + v * (u_scale / d));  // project to horizon circle
	return;
}

6. Accessories Layer — Canvas2D Overlay

The border.js Worker runs on a Canvas2D context and refreshes astronomical data and map overlay information on a 1-second interval timer.

6.1 Greenwich Sidereal Time (GST)

Rendering stars and constellation lines requires knowing where the sky is facing at the current time. GST is computed from the current wall-clock time.

GST = (18.697374 + 24.0657098 × T) × 15 mod 360
T = Julian days from J2000.0 = (UNIX_ms / 86400000) + 2440587.5 − 2451545.0

Each star's right ascension (RA) minus GST gives the hour angle, which is then converted from equatorial to screen coordinates.

6.2 Stars (draw_stars)

Each star from the stars.6 catalogue is projected from RA/Dec to screen coordinates. Its color is derived from the B-V color index, and radius/opacity from apparent magnitude.

// B-V color index → star color (temperature spectrum)
const bvColor = bv =>
	bv < -0.3 ? "#b2c8ff" :  // O-type — blue-white
	bv <  0.0 ? "#d9e2ff" :  // B-type — blue-white
	bv <  0.3 ? "#f8faff" :  // A-type — white
	bv <  0.6 ? "#fff8f0" :  // F-type — yellow-white
	bv <  0.8 ? "#fff2c8" :  // G-type — yellow (Sun)
	bv <  1.1 ? "#ffe0b5" :  // K-type — orange
				 "#ffab91";   // M-type — red

// Apparent magnitude → radius and opacity
radius = (9 - mag) * 0.20;   // brighter → larger
alpha  = 1 - mag / 15;       // brighter → more opaque

6.3 Constellation Lines (draw_constellations)

Constellation lines are stored as RA/Dec coordinate pairs (MultiLineString). Each point is tested for visibility by converting to Cartesian 3D before drawing.

// Equatorial (ra, dec) → screen projection (px, py)
l  = ra - skyRot;                   // hour angle
z  = sinφ*sinδ + cosφ*cosδ*cos(l); // z < 0 → below horizon
x  = cosδ * (-sin(l));
y  = cosφ*sinδ - sinφ*cosδ*cos(l);
px = cx + sr * (x*cosγ - y*sinγ);  // sr = sky-dome radius (larger than globe)
py = cy - sr * (x*sinγ + y*cosγ);

6.4 Messier Objects (draw_messier)

Objects are drawn with type-specific symbols based on the type property.

typeObject typeSymbol
gcGlobular clusterCircle + crosshair
gx / ggGalaxyEllipse
ocOpen clusterDashed circle
otherNebula, etc.Rectangle

6.5 Globe Minimap (draw_globe)

Each of the four viewport edges is sampled at 30 points (120 total), inverse-projected to geographic coordinates, then re-projected onto the minimap to draw a viewport reticle polygon.

// Sample geographic coordinates along each viewport edge
const N = 30;
const edge = (x0, y0, x1, y1) =>
	Array.from({ length: N }, (_, i) => {
		const t = i / N;
		return proj.invert([x0 + (x1-x0)*t, y0 + (y1-y0)*t]);
	});
const geoPts = [...edge(0,0,w,0), ...edge(w,0,w,h), ...edge(w,h,0,h), ...edge(0,h,0,0)];

6.6 Other Overlays

FeatureDescription
Night side (night)Computes the terminator as a 31-point polygon from solar declination and longitude; fills with a semi-transparent dark overlay
Clock (clock)Displays UTC in a large font; visible only when zoomed out (zoom < 2)
Coordinates (latlng)Shows latitude, longitude, and elevation in real time as the cursor moves
Scale bar (scale)log10-rounded values with automatic unit selection (m/km; metric/imperial)
Attribution (credit)License text for each base map, drawn from the attribution property

7. Gint Layer — Full WebGL2 Vector Rendering

The gint.js Worker is a general-purpose layer created by the user via createRemoteLayer({ type:"gint" }). It accepts Gint binary data from GeoPBF and renders lines, polygons, and points with full support for hover picking, click events, feature highlighting, and polygon fill.

7.1 Module Structure

FileRole
gintState.jsShared GL state object (s)
gintPrograms.jsGLSL shader compilation, linking, and uniform lookup
gintTextures.jsarcTex / metaTex / ptTex texture creation and deletion
gintFBO.jsbaseFBO (clean scene) and pickFBO (picking) creation and deletion
gintRenderPasses.jsrenderCleanScene / drawOverlay / renderPickingBuffer — 3 render passes
gintIdentify.jsGPU picking + JS polygon hit-test fallback, hover / leave handlers

7.2 Three-Framebuffer Design

1
canvas (default FBO) — real-time rendering during pan
Drawn directly on every drawing() event. Lightweight and immediate.
↓ committed on drawn()
2
baseFBO — clean scene snapshot
Blitted back to canvas when hover highlight is cleared, restoring the previous frame with zero flicker.
↓ simultaneously
3
pickFBO — feature ID encoded as RGB24
Lines drawn wider than visual width to expand hit sensitivity to ~6 CSS px (pickMargin = 12 × dpr).

7.3 GPU Picking — RGB24 Feature ID

// Encode feature ID into RGB 24-bit in the picking FBO
uint fid1 = meta.a + 1u;  // shift by 1 so 0 means "no hit"
v_color = vec4(
	float(fid1 & 255u) / 255.0,
	float((fid1 >> 8u) & 255u) / 255.0,
	float((fid1 >> 16u) & 255u) / 255.0,
	1.0
);

// Read 1 px at cursor position
gl.readPixels(pickX, pickY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, px);
const fid1 = px[0] | (px[1] << 8) | (px[2] << 16);
const featureId = fid1 === 0 ? null : fid1 - 1;

7.4 JS Polygon Hit-Test Fallback

When GPU picking returns no hit (fid1 === 0) but polygon data exists, the cursor's geographic coordinate is converted to Morton integer space and passed to findPolygon (from geopbf). This also works when the globe is small enough that all viewport corners lie outside the sphere and lastViewBbox = null.

7.5 drawOverlay Highlight Flow

// When activeId changes (hover detected):
// 1. Blit baseFBO → canvas (restore clean scene)
gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, gl.COLOR_BUFFER_BIT, gl.NEAREST);

// 2. Redraw active feature's edges wider + yellow (u_pass=1)
gl.uniform1f(uRender.u_line_width, lineWidth + 2.0);
gl.uniform1i(uRender.u_active_id,  activeId);

// 3. If polygon edge range is known, stencil-dim inactive polygons
// (only when maskColor.alpha > 0 and polyEdgeByFid has a range)
Zero Main-Thread Round Trips

The entire hover detect → drawOverlay pipeline completes inside the Worker. There is no postMessage("redraw"); readPixels and drawOverlay run sequentially in the same Worker, eliminating main-thread round-trip latency.

7.6 WebGL Context Loss Recovery

canvas.addEventListener("webglcontextlost", e => {
	e.preventDefault();
	// Clear all texture, FBO, and program references
	s.arcTex = s.metaTex = s.ptTex = s.ptMetaTex = null;
	s.baseFBO = s.pickFBO = s.programs = null;
});
canvas.addEventListener("webglcontextrestored", () => {
	s.programs = createGintPrograms(s.gl);
	createFBOs(); uploadGintTextures();
	postMessage({ action: "redraw" });  // request redraw from main thread
});

8. State Management & Gadgets

8.1 IndexedDB State Persistence

The Cache helper from native-bucket (an IndexedDB wrapper) persists view state and base map name across sessions.

map.stat = await Cache("GIS/stat");
map.baseName = await map.stat("base") || "osm.street";
map.view     = await map.stat("view") || [[135, 35, 0], 2];
// view = [[lng, lat, rotation], zoom]

// Auto-saved after each pan/zoom commit (drawn event)
map.stat("view", getView());

8.2 Elevation Integration (AltPBF)

The function returned by altpbf's createGetHeight is registered as map.getHeight. This adds an alt field to Move and Click events, enabling coordinate display and elevation profiles.

8.3 Gadget System

UI components are registered via map.gadget(name, func) — a plugin pattern where built-in and user-defined gadgets are treated identically. Each gadget function is called with this = map and can freely build UI within a DOM region reserved by map.addFrame.

// Invoking built-in gadgets
map.gadget.zoom();           // places zoom buttons in leftTop
map.gadget.layers();         // adds base map switcher button
const setTip = map.gadget.tip();   // returns the tip control function
setTip("Click for details");       // set tip text

// Registering and invoking a custom gadget
map.gadget("myTool", function () {
	map.addFrame("rightTop");
	map.rightTop.append("div").text("Hello");
});
map.gadget.myTool();

Frame Positions

Frame namePositionTypical use
leftTopTop-leftZoom, north, layer switcher, and other action buttons
rightTopTop-rightAuxiliary buttons and user UI
leftBottomBottom-leftLegend
rightBottomBottom-rightSupplementary info
leftFrameFixed panel left of mapSide menu (pushes map, resize-aware)
rightFrameFixed panel right of mapAttribute panel (pushes map, resize-aware)
overlaysFull-map overlaytip / pop / explain / loading, etc.

Built-in Gadgets — Interaction (gadget1.js)

GadgetDescription
zoom() Zoom-in (+) and zoom-out (−) buttons. Each click snaps to the nearest integer zoom level and animates via map.mag()
north() Displays the current Z-axis rotation (map tilt) live inside the button's SVG icon, and calls map.north() on click to animate back to north-up
layers() Base map switcher dropdown. Renders layer names in the current locale from map.Layers and calls map.setBase() on selection
leftPanel(opts)
rightPanel(opts)
Manages side panel open/close. With opts.active = true the panel physically pushes the map area (resize mode); otherwise it overlaps the map as a modal. Opening animation is interpolated with d3.easeCubic (configurable). Returns a D3 Selection for the panel content area.
full() Toggles fullscreen via the Fullscreen API, automatically swapping the button icon to match state
constellation() Toggles constellation lines and labels. Sends toggle-constellations to the Accessories Worker, which also toggles Messier object display in sync
cpos() Obtains the current position via the Geolocation API and navigates there with flyToFeature. The location is shown as an animated SVG marker (pulsing circle) on the overlay, automatically shown/hidden based on map.tester() as the map moves
shot() Downloads a screenshot. All Canvas layers are composited onto an OffscreenCanvas, then html2canvas renders the overlay (excluding buttons and noprint elements) on top. Saved as WebP.
print() Renders at 3× resolution and opens a new window that calls window.print(). Automatically detects portrait vs. landscape and applies appropriate CSS layout.
measure() Toggle button that enters distance/area measurement mode. When active, cancels Click events and launches the createPolygon tool, displaying distance and area in real time as vertices are added

Built-in Gadgets — Information Display (gadget2.js)

GadgetDescription
loading() Displays a tile loading indicator tied to LoadStart / LoadEnd events. When multiple tiles are loading simultaneously, all names are listed; the indicator disappears automatically once everything completes
explain() A panel in the upper-left area for explanatory text or HTML. The returned function accepts a string or a function receiving a D3 Selection. Pass opts.permanent = true to hide the close button.
legend() A collapsible legend panel in the lower-left. Clicking collapses it; a re-open button restores it. Content is set the same way as explain.
tip() A tooltip that follows the cursor. Pass text to the returned function to show it; pass null to hide. Automatically flips to the opposite side near screen edges. Uses a wider offset on touch devices.
pop() Creates popups pinned to specific map locations. Each call to the returned pop(content, event) spawns a new popup. A Canvas2D leader line is drawn automatically from the popup to its coordinate. Popups can be dragged and pin-locked. pop.clear() removes all.
contextmenu() A context menu shown on right-click or long-press. Pass an array of [{ icon, name, func }] to the returned function to define menu items.
// pop example
const pop = map.gadget.pop();
map.onClick(e => {
	if (!e) return;
	pop(`Lat: ${e.lat.toFixed(4)}<br>Lng: ${e.lng.toFixed(4)}`, e);
});

// contextmenu example
const setMenu = map.gadget.contextmenu();
setMenu([
	{ name: "Center here", func: map => map.flyToFeature({ type: "Point", coordinates: [map.center[0], map.center[1]] }) },
	{ name: "Face north",  func: map => map.north() },
]);
CSS Theme Customization

map.setProperties() writes CSS custom properties to the document root. Key properties: spaceColor (space background), earthFilter (globe CSS filter), borderColor (button border), buttonSize, fontSize, fontFamily, and more.


API Summary

MethodDescription
map.setBase(name)Switch base map (includes tile cache reset)
map.setView(center, zoom, angle)Instantly change view position
map.flyToFeature(feature, opts)Navigate with d3.interpolateZoom animation
map.zoomToFeature(feature, opts)Navigate with two-phase animation
map.bbox([x0,y0,x1,y1])Zoom to bounding box / get current bbox
map.mag(n, duration)Animate scale to n× the current value
map.north(duration)Animate Z-rotation (tilt) back to 0
map.autoRotate(true/false)Auto-rotate at 0.01°/ms via d3.timer
map.tester([lng,lat])Check whether a point is visible in the current viewport
map.createLayer(opts)Add a Canvas2D layer (for GeoJSON rendering)
map.createRemoteLayer(opts)Add a Worker + OffscreenCanvas layer
ortho-map · GPL-3.0 · Kenji Yoshida · 2026