Skip to main content

Native pitched maps

Shared pitched cameras, imagery and queries in native libraries and embedded QuickJS.

Current development source connects MapSession, the C ABI and both embedded QuickJS entries to the actual shared perspective scene. Native capabilities nativePitchedCamera and nativePitchedStyleRasterSources identify this backend. The common styleRasterPitchedSources flag remains false pending complete query, legacy-entry and cross-entry acceptance; browser and desktop adapters now connect the shared scene. This is not a new DLL/SO release or external-device acceptance.

Select mercator, use zoom 0–24, then set pitch in 0–65 degrees. Coordinates are longitude/latitude; 181 degrees preserves an explicit world copy. Padding, bearing, ground projection, bounds fitting and animations use the quantized pose actually rendered. Native sessions use DPR 1 and viewport edges of 1–4096 pixels. Geographic perspective and roll are unavailable; setPitch(0) before selecting geographic projection. Clearing a style preserves pitched Mercator and replaces the caller style with the internal scene. Exiting globe explicitly resets planar pitch.

The existing camera_json envelope is unchanged:

{"method":"jumpTo","args":[{"center":[181,40],"zoom":19,"bearing":33,"pitch":65,"padding":{"left":30,"right":2,"top":15,"bottom":4}}]}

This complete script runs in a matching native library's one-shot or retained QuickJS session. The embedding application supplies UI, downloads and owner-thread pumping. Common method syntax stays aligned across platforms; capability queries identify the temporary backend difference.

// @gis-api 0.5-session
import { map } from 'sqsmap';
const checks=[];
function check(name,value) { if(!value) throw Error(name); checks.push(name); }
check('actual native pitched backend',(await map.sqs.getCapabilities()).nativePitchedCamera === 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]);
 check('padded point '+pitch,Math.abs(p.x-174)<.01 && Math.abs(p.y-125.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])};

Without a caller basemap, the internal scene makes no network requests and publishes no caller style, source or credit. A standard raster style renders imagery and business content in the same depth scene. Command confirmation only means application. render_rgba reads a frame matching the current pose; geometry preparation can return CAPABILITY_UNAVAILABLE, so continue pumping and retry rather than displaying an old planar frame. Downloads, animation completion and idle are separate states.

Common circle/fill paint and simple line color, width and opacity are evaluated at logical zoom. Circle radius/stroke follow the existing 0–128 logical-pixel limits; radius zero is invisible and depth bias never shifts the public projected position. Ground queries clip near/far planes and hidden points while preserving crossing segments, polygon rings, holes and original feature identity. Exact perspective line-width picking and complete 3D/terrain/symbol queries remain pending. Animated lines, heatmaps, symbols and unsupported line paint are explicitly rejected at the shared native perspective boundary.

Invalid poses, unsupported business styles and foreign output failures reject before commit. Resize validates the new pose first. Returning to a planar view without a basemap clears the internal 3D background.

Local software Vulkan pixels cover 30/65 degrees, bearing, asymmetric padding, longitude 181, offset data-driven circles, imagery, atomic rejection, animations and resize. C and both QuickJS entries read actual GPU frames. Windows/macOS, mobile devices and formal releases require separate acceptance. See perspective cameras for remaining integration and native C API for threading and buffer ownership.

The matching WASM/main/strict Worker backend now renders common pitched poses too; see web pitched maps.