Skip to main content

Planar solid line offset

line-offset controls signed line displacement and polygon boundary insets

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.

When the matching SDK provides commonLineGradients, its noncluster GeoJSON lineMetrics:true gradient channel supports the stable caps/joins, filtering/sorting and offset/translate described here. Historical solid/animation gradient rejection limits do not reject these combinations in the new gradient channel. Check matching capability and the gradient guide; other unsupported combinations still reject.

The shared planar solid-line paint property line-offset is reported by commonLineOffsetSolid. Values use CSS pixels and default to 0. Positive LineString values offset to the right of travel, negative values to the left; reversing a line reverses these sides. Positive Polygon values inset the outer boundary and expand hole boundaries away from their interiors; negative values reverse that direction. Closed LineStrings retain directional line semantics instead of becoming polygons.

import { map } from 'sqsmap';
map.setStyle(null);
map.setProjection({type:'mercator'});
map.addSource('route', {type:'geojson', data:{type:'Feature', id:1,
  properties:{offset:8}, geometry:{type:'LineString',
    coordinates:[[116.38,39.9],[116.42,39.9]]}}});
map.addLayer({id:'route', type:'line', source:'route', paint:{
  'line-color':'#1677ff', 'line-width':6,
  'line-offset':['coalesce',['feature-state','offset'],['get','offset']]
}});
map.jumpTo({center:[116.4,39.9], zoom:13, pitch:0});
await map.sqs.flush({render:true});
map.setFeatureState({source:'route',id:1}, {offset:-8});
await map.sqs.flush({render:true});

Existing numeric, property, paint feature-state, global-state, type/lexical/lazy branch and continuous-zoom expressions are supported. For example ['interpolate',['linear'],['zoom'],10,4,12,12] changes the offset at fractional zoom. Setting the property to null removes it. Supply explicit numeric coalesce fallbacks for missing values; numeric strings do not silently become numbers.

Project budgets allow an absolute offset of 128 CSS pixels and total actual vertex extrusion of 2048 CSS pixels. Corners can amplify displacement, so validation uses actual generated vertices rather than abs(offset) alone. Exceeding these limits reports RESOURCE_LIMIT. These are SqsMap budgets, not MapLibre specification limits. The existing 32 levels/1,024 nodes/8,192 evaluation steps, combined topology/offset 2,000,000-node candidate estimate and 1,000,000 worst-case topology vertex budget remain applicable.

Actual nonzero offsets use shared solid topology: endpoints, caps and joins are built around the displaced centerline, including explicit cap/join options and feature sorting. Zero retains legacy fast paths and their existing combination limits. Nonzero offsets currently reject dash, gradient, animation, pitched/3D, globe and media combinations at candidate validation. Degeneration, short-segment reversal or unstable turns reject; full self-intersection repair is not provided.

Property/state/global/camera failures preserve previous sources, values, camera, identities and display. Geometry preflight uses the candidate zoom CSS scale, preventing stale renderer scale from skipping degeneration checks. Rendered queries use conservative envelopes based on actual extrusion and may include nearby unpainted pixels; they are not pixel picking. Original IDs/properties, filters and MVT mappings are retained.

The 30-check shared script and complete HTML include reversed lines, polygon rings/holes, property/state/global/fractional-zoom updates, recovery and identities. Acceptance also reads actual displaced pixels. Tasks are marked implemented only after code, matching artifacts and acceptance finish. Device, external-platform and formal-release acceptance remain separate.

Semantics follow the official MapLibre line-offset specification, with an independently implemented SqsMap subset.

Result budgets apply during actual candidate evaluation: an unselected future zoom branch129 does not fail early. Entering that branch still rejects atomically and retains the previous camera/display. Direct numbers or literal129 reject early.