Skip to main content

Fill patterns

Render repeated planar textures with shared images and image expressions

Shared planar fill layers support uniform and per-feature RGBA / SDF fill-pattern, reported separately by commonFillPatternsUniform and commonFillPatternsPerFeature. Strings select names directly. image returns an available typed image or actual null, allowing coalesce fallback. case, match and lexical let/var are supported; integer zoom and global-state can select names. Unused branches and fallbacks do not produce missing-image demand.

import { map } from 'sqsmap';
const data = [];
for (let y = 0; y < 16; y++) for (let x = 0; x < 16; x++) {
  data.push(...(((x >> 3) ^ (y >> 3)) ? [0, 0, 255, 255] : [255, 0, 0, 255]));
}
map.addImage('checker', { width: 16, height: 16, data: new Uint8Array(data) }, { pixelRatio: 2 });
map.addSource('area', { type: 'geojson', data: {
  type: 'Feature', properties: {}, geometry: { type: 'Polygon', coordinates: [
    [[116.38, 39.88], [116.42, 39.88], [116.42, 39.92], [116.38, 39.92], [116.38, 39.88]]
  ] }
} });
map.addLayer({ id: 'pattern', type: 'fill', source: 'area', paint: {
  'fill-pattern': ['coalesce', ['image', 'primary'], ['image', 'checker']],
  'fill-opacity': 0.8
} });
map.jumpTo({ center: [116.4, 39.9], zoom: 13 });
await map.sqs.flush();

RGBA patterns retain bitmap colors multiplied by fill-opacity, ignoring fill-color and outlines. The period is image dimensions divided by pixelRatio, anchored in world coordinates and rotated with the map. The 16×16 image above has an 8×8 CSS pixel period. Image updates, removal, registration, global state, and feature properties/state re-evaluate selection. Different polygons in one layer can select different images while retaining feature draw order; a missing image hides only features selecting it. Only missing names actually attempted by visible polygons enter styleimagemissing. When every candidate is missing, nothing is drawn or hit by rendered queries.

Setting fill-pattern to null removes the property and restores solid fill and outlines. Queries respect polygon geometry and holes, without per-pixel texture-alpha picking. Native and WASM writes validate image, source and layer candidates before commit; failures preserve resources, expressions and identity.

Selected images currently require power-of-two dimensions from 2 through 512 on each side. This implementation enforces that budget for every selected pattern; the MapLibre specification also describes this range for seamless patterns. Pattern transitions, line patterns and 3D/globe combinations remain unsupported and fail explicitly. Existing resource and expression budgets apply. Mobile application, device and formal release acceptance remain separate.

Copy portable-fill-pattern.js, or run the complete browser page. The same script checks 16 behaviors in native persistent sessions, the browser main thread and an ordinary map Worker, followed by actual red/blue texture and polygon-hole checks. See style API and missing images.

Per-feature selection uses actual properties and feature-state. For absent state, fall back within the image name before calling image; image(null) is not a valid name.

map.setPaintProperty('pattern', 'fill-pattern',
  ['image', ['coalesce', ['feature-state', 'tile'], ['get', 'tile']]]);
// Each source polygon has properties.tile naming a registered image and a stable id.
map.setFeatureState({ source: 'area', id: 7 }, { tile: 'checker' });
await map.sqs.flush();

Property, state, and image candidates undergo per-feature validation before commit. Invalid name types, unsupported dimensions, or invalid SDF foreground colors reject the candidate while retaining source/state/material and identity. Ready features participate in geometry/hole queries; missing-image features do not. Materials keep their atlas generation until the corresponding geometry commits.

The 25-check portable per-feature script and complete browser page include two polygons in one layer, holes/overlap, property/state selection, image lifecycle, and failure recovery. The original 16-check uniform script remains supported; its historical per-feature-unavailable assertion now checks an invalid image argument.

A candidate admits at most 100,000 displayed features in total and an estimated 2,000,000 expression-structure nodes multiplied by displayed-feature counts, including selected SDF foreground/opacity expressions. Excess rejects with RESOURCE_LIMIT without partial commit; existing per-feature expression budgets also apply.

Register an SDF pattern using addImage(name, image, {sdf: true}). Its alpha channel stores distance:0.5 is the boundary and larger values are foreground. The implementation uses periodic interpolation inside each image and screen derivatives for antialiasing. commonFillPatternsSdf reports support. Image RGB is ignored; fill-color supplies the foreground (black by default), and its alpha multiplies fill-opacity. Property and feature-state colors use the existing expressions. RGBA patterns still ignore fill-color. Transparent SDF foreground or zero opacity is excluded from rendered queries; per-texel texture-alpha picking remains unsupported.

map.addImage('mask', {width:8, height:8,
  data:new Uint8Array(Array.from({length:64},()=>[0,0,0,255]).flat())}, {sdf:true});
map.setPaintProperty('pattern', 'fill-pattern', 'mask');
map.setPaintProperty('pattern', 'fill-color',
  ['coalesce', ['feature-state','tone'], ['get','tone']]);
await map.sqs.flush({render:true});

This is an independently implemented SqsMap feature following the MapLibre fill-color specification. Its current GL JS and Native support tables still list SDF pattern colorization as unsupported, so this guide makes no claim of pixel-for-pixel MapLibre rendering compatibility.

The 24-check SDF portable script and complete browser page demonstrate shared-mask colors, transparency/opacity, RGBA switching, image updates and strict failure recovery.