Feature state API
When a matching SDK reports commonPlanarLayerComposition:true, the shared Mercator view with zero pitch/roll and no terrain/extrusion combines media, backgrounds, labels, heat and supported static line/fill effects in complete layer order. This capability supersedes earlier media-combination exclusions only within that shared planar scope. Custom line animation, tilted/globe/terrain views and other unsupported material combinations retain their respective limits. Reordering and compatible diff preserve identities, caches, pending requests and fade clocks; material, geometry and texture budgets are validated before publication. See planar layer composition.
Feature state and selection/hover appearance
setFeatureState(target, state), await getFeatureState(target) and removeFeatureState(target, key?) follow the public MapLibre Map syntax with SqsMap's own shared implementation. They work with shared GeoJSON and MVT circle/line/fill layers. State does not edit business attributes. Applications build their own tool UI, query points/boxes and assign custom selected/hover keys.
import { map } from 'sqsmap';
map.addSource('trees', {type:'geojson', data:{
type:'Feature', id:'oak', properties:{name:'Oak'},
geometry:{type:'Point', coordinates:[116.4,39.9]}
}});
map.addLayer({id:'trees',type:'circle',source:'trees',paint:{
'circle-radius':['case',['coalesce',['feature-state','hover'],false],12,6],
'circle-color':['case',['coalesce',['feature-state','selected'],false],'#ff8800','#008800']
}});
await map.sqs.flush();
const pixel = await map.project([116.4,39.9]);
const hits = await map.queryRenderedFeatures(pixel,{layers:['trees']});
if (hits[0]) {
map.setFeatureState(hits[0],{selected:true,hover:true});
await map.sqs.flush();
console.log(await map.getFeatureState(hits[0]));
map.removeFeatureState({source:'trees',id:'oak'},'hover');
await map.sqs.flush();
}
A target is {source,id,sourceLayer?}. The source must exist; GeoJSON sourceLayer reports CAPABILITY_UNAVAILABLE. Numeric and string IDs are distinct, using the same safe finite numbers/strings as sources. State may be assigned before an ID exists. Effective IDs come from raw data, promoteId or generateId. Repeated IDs and collection children share state.
Set merges top-level keys; null remains a value. Get returns an independent snapshot or {}; editing that snapshot cannot change the map. Remove with a key removes one key, with source/id clears that feature, and with source alone clears the source; a key requires id. Values may contain bounded arbitrary JSON. Missing feature-state values return null; the example uses coalesce defaults. State expressions are for paint only, never filter/layout. Invalid dynamic types/sizes use the existing paint fallback and warningCount.
stateRevision is separate from dataRevision; getData and business attributes remain unchanged. Explicit stable-ID/promoteId state survives same-source setData/updateData. Whole-data replacement with generateId clears state to avoid selecting a different feature after index changes; reapply against new IDs. Source deletion clears state and same-name recreation cannot inherit it. Query features can be targets: supplied sqs.sourceInstanceId/dataRevision are checked and stale targets report CONFLICT. Direct source/id targets refer to the current source.
Rendered queries return a state snapshot and sqs.stateRevision. source/layer/sqs/state are reserved query fields; getData retains original foreign members. State affects actual drawing, query radius/width and evaluated paint. Only state-dependent style caches need refreshing. State does not automatically edit legacy GIS selection records or attributes. Successful writes use bounded sqs.sourceschange notifications for committed changes; frame presentation has its own lifecycle contract.
Limits: 16 KiB, 256 top-level keys, 4096 nodes and depth 32 per state; targets have a 64 KiB/4096-node/depth-32 limit. A map permits 10,000 nonempty targets and a conservative 4 MiB resident estimate; empty state does not occupy a target. Input, merged-result, display/GPU preparation, output allocation or event-budget failure rejects the entire mutation. Rendered result limits of 1000 items/8 MiB include state and evaluated layer descriptions.
SDK, C sqsm_map_source_json, independent one-shot/retained QuickJS and common application/web scripts share the names. A matching development SDK is required; npm 0.2.0 does not automatically provide these methods. Linux RTX pixels, an independent C11 host, actual application QuickJS and SDK/website main-thread/Worker routing have separate evidence. Actual software GPU browser MVT state acceptance is recorded in07.9; mobile devices, hardware GPU and external Windows/macOS acceptance remain separate. Standard pointer/layer subscriptions continue in the event phase; this example does not assume they exist.
MVT source-layer state
A vector tile target requires {source,sourceLayer,id}. The same ID in different source layers is independent; numeric/string IDs remain distinct. State may be set before a feature is downloaded. Same-source refreshes and view movement preserve it; source deletion/recreation clears it. Optional sqs source/data revision checks follow GeoJSON rules. State does not change dataRevision or business attributes.
map.setFeatureState({source:'roads',sourceLayer:'transportation',id:1},{selected:true});
await map.sqs.flush({render:true});
console.log(await map.getFeatureState({source:'roads',sourceLayer:'transportation',id:1}));
map.removeFeatureState({source:'roads',sourceLayer:'transportation'}); // Clear this sublayer
map.removeFeatureState({source:'roads'}); // Clear the entire source
Rendered queries return the matching source-layer state snapshot; feature-state drives actual paint. Same-layer IDs share state across tiles/world copies, without merging query geometry. Raster/media sources explicitly reject feature state. Existing GeoJSON state budgets apply.
The complete HTML state example includes selection,queries,view switching,credits and disposal. The portable script verifies isolation,clearing and actual pixels through native and real WASM main-thread/strict Worker. Pitched views retain distance fog. Mobile host and external-platform acceptance remain pending.