-
Notifications
You must be signed in to change notification settings - Fork 134
Description
In my current development work, I have a requirement to load a Mapbox Style using OpenLayers. However, after loading the map, I found that image-based line patterns are not supported. I searched for solutions online and reviewed the source code of ol-mapbox-style, ultimately locating the issue in src/stylefunction.js:
if (type != 1 && layer.type == 'line') {
if (!('line-pattern' in paint)) {
color = colorWithOpacity(
getValue(
layer,
'paint',
'line-color',
f,
functionCache,
featureState,
),
getValue(
layer,
'paint',
'line-opacity',
f,
functionCache,
featureState,
),
);
} else {
color = undefined; // line-pattern is not supported
}
// ....It turns out that line-pattern (image-based line styling) is intentionally unsupported here.
Based on the discussion in the related issue: line-pattern webgl#1045
I referenced the reply from @ahocevar
using a transform and doing separate stroke() operations for each segment.
Given the complexity and size of the source code, I didn't dive into the details directly. Instead, I used Claude Opus 4.5 to help me understand the problem and generate a patch to add line-pattern support. The AI did an excellent job of grasping my needs, and the solution leverages patch-package to apply the modification:
// Remove color = undefined; and add :
const lineIcon = getValue(
layer,
'paint',
'line-pattern',
f,
functionCache,
featureState,
);
if (lineIcon) {
const icon =
typeof lineIcon === 'string'
? fromTemplate(lineIcon, properties)
: lineIcon.toString();
const spriteImage = getSpriteImageForIcon(icon, spriteImages);
if (spriteData && spriteData[icon] && spriteImage) {
const lineOpacity = getValue(
layer,
'paint',
'line-opacity',
f,
functionCache,
featureState,
);
const lineWidth = getValue(
layer,
'paint',
'line-width',
f,
functionCache,
featureState,
);
const spriteImageData = spriteData[icon];
// Create a cached pattern image canvas
const icon_cache_key = icon + '.linepattern';
let patternCanvas = patternCache[icon_cache_key];
if (!patternCanvas) {
patternCanvas = createCanvas(
spriteImageData.width,
spriteImageData.height,
);
const pctx = patternCanvas.getContext('2d');
pctx.drawImage(
spriteImage.image,
spriteImageData.x,
spriteImageData.y,
spriteImageData.width,
spriteImageData.height,
0,
0,
spriteImageData.width,
spriteImageData.height,
);
patternCache[icon_cache_key] = patternCanvas;
}
// Create a custom renderer style for line-pattern
const patternHeight = spriteImageData.height;
const patternWidth = spriteImageData.width;
const rendererOpacity = lineOpacity === undefined ? 1 : lineOpacity;
++stylesLength;
style = new Style({
renderer: function (pixelCoordinates, state) {
const ctx = state.context;
const geometry = state.geometry;
const geomType = geometry.getType();
ctx.save();
ctx.globalAlpha = rendererOpacity;
// Get pixel coordinates array(s)
let coordArrays;
if (geomType === 'MultiLineString') {
coordArrays = pixelCoordinates;
} else {
coordArrays = [pixelCoordinates];
}
// Draw pattern along each line
for (let a = 0; a < coordArrays.length; a++) {
const coords = coordArrays[a];
if (!coords || coords.length < 2) continue;
for (let i = 0; i < coords.length - 1; i++) {
const start = coords[i];
const end = coords[i + 1];
const dx = end[0] - start[0];
const dy = end[1] - start[1];
const segmentLength = Math.sqrt(dx * dx + dy * dy);
if (segmentLength < 1) continue;
const angle = Math.atan2(dy, dx);
// Calculate how many pattern images fit in this segment
const numPatterns = Math.ceil(segmentLength / patternWidth);
for (let j = 0; j < numPatterns; j++) {
const t = (j * patternWidth) / segmentLength;
if (t > 1) break;
const x = start[0] + t * dx;
const y = start[1] + t * dy;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
// Draw the pattern image centered on the line
const drawWidth = Math.min(patternWidth, segmentLength - j * patternWidth);
ctx.drawImage(
patternCanvas,
0, 0, drawWidth, patternHeight,
0, -patternHeight / 2, drawWidth, patternHeight
);
ctx.restore();
}
}
}
ctx.restore();
},
zIndex: index,
});
styles[stylesLength] = style;
// Skip the normal stroke rendering for this layer
continue;
}
}After implementing the change, I ran:
npx patch-package ol-mapbox-style
Then re-run the project with:
npx vite --force
To my delight, the image-based line patterns now render successfully!
From @ahocevar's comment, I understand that official line-pattern support is planned for a future release. Due to my limited technical expertise, I don't fully comprehend all the details of the AI-generated code. However, I wanted to share this working patch with the maintainers in the hope that it might help accelerate the official implementation process.
I welcome any discussions or feedback from the community. Thank you!