Web pitched maps
Development core ab68002a connects actual pitched rendering in WASM MapView, main-thread maps and strict rendering Workers. These hosts share the Rust camera, scene, styles and ground queries. webPitchedCamera and webPitchedStyleRasterSources describe the matching development backend; native libraries expose nativePitchedCamera / nativePitchedStyleRasterSources. The global styleRasterPitchedSources flag remains false pending full query, legacy-entry and complete cross-entry acceptance.
Select setProjection({type:'mercator'}), then use pitch 0–65 and logical zoom 0–24. Coordinates are lng, lat; longitude 181 preserves an explicit world copy. CameraPose now declares pitch:number. A compilable declaration does not update an older npm release or certify every host. Use JS, Worker, WASM and declarations from the same build.
Copy the complete HTML/CSS/script with all button handlers to run it. It defaults to the website development SDK; change sdk when hosting your own matching SDK. Buttons cover pitch, imagery, style clearing, animation, pan, zoom, resize, invalid input and destruction. The green PNG is a local validation fixture with no third-party online source. Real imagery requires an authorized source and its required attribution.
The same portable script runs through the browser script Worker/contract WASM with main-thread or strict Worker maps, and through one-shot and persistent embedded native QuickJS. Ordinary browser JavaScript uses MapView.create; debugging scripts use this shared facade:
// @gis-api 0.5-session
// @permissions ["project.read", "map.write"]
import { map } from 'sqsmap';
const checks=[];
function check(name,value) { if(!value) throw Error(name); checks.push(name); }
const capabilities=await map.sqs.getCapabilities();
check('actual shared pitched backend',capabilities.nativePitchedCamera === true || capabilities.webPitchedCamera === true || capabilities.desktopPitchedCamera === true);
map.setProjection({type:'mercator'});
map.jumpTo({center:[181,40],zoom:19,bearing:33,padding:{left:30,right:2,top:15,bottom:4}});
map.addSource('points',{type:'geojson',data:{type:'FeatureCollection',features:[
{type:'Feature',id:7,properties:{radius:40,color:'#ff0000'},geometry:{type:'Point',coordinates:[181,40]}},
{type:'Feature',id:8,properties:{radius:1,color:'#ff0000'},geometry:{type:'Point',coordinates:[181,-80]}},
{type:'Feature',id:9,properties:{radius:4,color:'#0000ff'},geometry:{type:'Point',coordinates:[181.0002,40.0001]}}
]}});
map.addLayer({id:'dots',type:'circle',source:'points',paint:{'circle-color':['get','color'],'circle-radius':['get','radius']}});
await map.sqs.flush();
for(const pitch of [30,65]) {
map.setPitch(pitch); await map.sqs.flush();
check('pitch '+pitch,await map.getPitch()===pitch);
const p=await map.project([181,40]);
const state=await map.sqs.cameraState();
check('padded point '+pitch,Math.abs(p.x-(state.viewport.width/2+14))<.01 && Math.abs(p.y-(state.viewport.height/2+5.5))<.01);
const ll=await map.unproject(p);
check('ground round trip '+pitch,Math.abs(ll.lng-181)<1e-6 && Math.abs(ll.lat-40)<1e-6);
const hits=await map.queryRenderedFeatures([p.x+32,p.y]);
check('original circle identity '+pitch,hits.length===1&&hits[0].id===7);
check('radius expression '+pitch,hits[0].properties.radius===40);
}
export default {checks,pitch:await map.getPitch(),point:await map.project([181,40])};
Camera commands, bounds fitting, projection, unprojection, queries and animation share the Rust implementation. Confirm writes with flush. Authoritative browser SDK reads return Promise; use await. Scripts use map.sqs.flush for command confirmation. Browser MapView.flush({render:true}) additionally requires an actual presented frame matching the current pose. Pending geometry/resources continue pumping downloads and rendering. A stale planar/older pitched frame is never accepted as the new frame, and normal preparation is not reported as a rendering failure. Command application, actual frames, finished downloads, animation completion and idle are distinct states.
Logical viewport dimensions and API points use CSS pixels. DPR scales only the physical target and draw lens. Content zoom controls imagery LOD, minzoom/maxzoom and feature paint; physical zoom = content zoom + log2(DPR), without changing style evaluation. Physical viewport edges must be positive integers and the current scene supports physical zoom 0–26, so not every DPR/zoom combination is representable. Resize and density changes validate the candidate pose before changing the frame.
Without a caller style, an internal scene_only scene makes no network requests and invents no public style/source/credit. Standard style raster imagery shares actual depth with business content. Clearing a style preserves nonzero pitched Mercator and switches to the internal scene. Existing zero-pitch clearing defaults remain. Call setPitch(0) before selecting geographic projection; leaving globe explicitly clears previously stored planar pitch.
This stage supports shared circle/fill and simple line color, width and opacity. Data-driven circle radius/color use logical zoom; DPR scales their rendered size. Depth bias never moves the public projected point. queryRenderedFeatures preserves original source/layer identity, properties and order, with shared clipping for hidden points and near/far crossings. Complete symbol/terrain/3D queries and exact perspective line-width hits remain pending; unsupported styles reject explicitly. Synchronous screenToLngLat/viewState, argument-free fit and direct setBearing are connected; see camera snapshots. Full legacy pick/select adaptation continues. Prefer common project/unproject/getScaleAt/fitBounds/queryRenderedFeatures for pitched scripts.
A main-thread write can throw synchronously; a Worker write can emit error and reject during flush. After handling failure, advance the watermark using submittedSequence from the receipt before confirming later commands. Reusing an old watermark can repeat a historical failure. Invalid inputs preserve the original camera.
Local acceptance covers actual main/strict Worker maps, DPR 1/2, 30/65 degrees, asymmetric padding, longitude 181, data-driven circles and an offset blue point, imagery, clearing, animation, pan, zoom, resize and atomic rejection. Thirty-two real screenshots verify position and CSS radius. Windows/macOS, mobile devices and formal releases still require their own acceptance. See native pitched maps and shared perspective math.
The web workbench also supports local single-band DEMs as a 3D surface. The top visible DEM shapes the ground, missing values remain absent, and workspace and engine task Worker restoration are supported. SeeLocal DEM 3D surface.