Skip to main content

Solid line caps and joins

Control planar line endpoints, corners and join limits through shared scripts

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.

Shared planar solid lines accept explicit layout line-cap, line-join, line-miter-limit and line-round-limit, reported by commonLineTopologySolid. Any one enables the new topology path, whose defaults are butt/miter and limits 2/1.05. Omitting all four preserves the existing fast-line path; this does not imply standard miter joins in legacy dash/animation rendering.

LayoutAccepted valuesBehavior
line-capbutt / square / roundFlat endpoint; half-width extension; half-width-radius round endpoint
line-joinmiter / bevel / roundPointed corner; cut corner; rounded corner
line-miter-limitFinite 0..16, default 2Converts excessive miter corners to bevel
line-round-limitFinite 0..16, default 1.05Converts shallow round corners to miter

The 0..16 range is a SqsMap resource budget, not a MapLibre restriction; exceeding it produces RESOURCE_LIMIT. Expression support for all four is reported by commonLineTopologyLayoutExpressions. Existing property/global-state, type/lexical and lazy branching expressions are accepted; layout rejects feature-state. Zoom must be a top-level step or numeric interpolate, optionally wrapped in let, and is evaluated at integer zoom. Zoom-bearing layouts require an explicit Web Mercator plane and reject other planar coordinate systems. Width retains the shared 0..24 CSS pixel boundary; existing paint expressions still control color, width and opacity.

import { map } from 'sqsmap';
map.addSource('route', {type:'geojson', data:{
  type:'Feature', id:1, properties:{}, geometry:{type:'LineString',
    coordinates:[[116.38,39.90],[116.40,39.91],[116.42,39.90]]}
}});
map.addLayer({id:'route',type:'line',source:'route',layout:{
  'line-cap':'round', 'line-join':'round', 'line-round-limit':1.05
},paint:{'line-color':'#1677ff','line-width':12,'line-opacity':0.7}});
map.jumpTo({center:[116.4,39.9],zoom:13});
await map.sqs.flush({render:true});
map.setLayoutProperty('route','line-join','miter');
map.setLayoutProperty('route','line-miter-limit',4);
await map.sqs.flush({render:true});

Closed paths have no endpoint caps. Repeated points are removed, and entirely zero-length paths may produce empty output. Candidates validate before commit, preserving layer/source identity and the previous frame on failure. Each round arc has at most 32 segments; all candidate layers share a worst-case expansion budget of one million vertices. Near reversals or unstable inner joins are rejected; arbitrary self-intersecting topology is not claimed. Segments shorter than their stroke width and self-intersections do not yet guarantee disjoint join regions, so translucent colors may accumulate. Normalized neighboring directions require dot greater than -0.99999999, rejecting turns within approximately0.008 degrees of a180-degree reversal. Core acceptance records the exact boundary.

Dash/gradient/animation, 3D/globe and media-scene combinations with this topology remain unsupported and reject explicitly. setStyle(URL) basemap style compilation remains separate; existing GeoJSON URL and shared MVT sources still use common bindings and are not disabled merely because they use network addresses. Queries use a conservative envelope enlarged for cap/join limits, so nearby uncolored pixels can still hit a feature; this supports feature identification rather than exact pixel picking.

The 23-check portable script and complete browser page cover caps, join/limit switching, rejected candidates and actual cap pixels. Core GPU acceptance additionally checks sharp corners/fallbacks, closed paths, transparent joins, rotation and DPR. Device/external-platform and formal release acceptance remain separate.

Semantics follow the MapLibre line specification, with an independent SqsMap implementation and explicit subset boundaries.

The four layout expressions resolve per feature within one layer. Existing geometry/resource/combination limits remain. Each expression admits32 levels,1024 nodes and8192 evaluation steps, with a candidate estimate of two million structure nodes multiplied by displayed-feature counts. Excess or an actually evaluated invalid result rejects the complete candidate, preserving source/global/camera/frame and identity. Unread branches do not trigger result-enum errors. Feed and query use the same per-feature options, including dynamic square caps and miter extents.

map.setLayoutProperty('route','line-cap',
  ['let','name',['coalesce',['global-state','capOverride'],['get','cap']],
   ['step',['zoom'],['var','name'],11,'round']]);
map.setLayoutProperty('route','line-join',['get','join']);
map.setLayoutProperty('route','line-miter-limit',
  ['interpolate',['linear'],['zoom'],10,['get','limit'],12,8]);
map.setLayoutProperty('route','line-round-limit',['get','roundLimit']);
await map.sqs.flush({render:true});

The24-check expression script and complete browser page demonstrate three caps in one layer, property/global/integer-zoom changes and failure recovery. The23-check literal script remains applicable.