Skip to content

Commit f6c9efa

Browse files
authored
Merge pull request #1415 from ahocevar/mapbox-updates
Updates for Mapbox compatibility
2 parents ec5ea8e + 109dee1 commit f6c9efa

File tree

8 files changed

+455
-44
lines changed

8 files changed

+455
-44
lines changed

examples/mapbox.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ if (!key) {
1313
}
1414
}
1515

16-
olms('map', 'mapbox://styles/mapbox/streets-v12', {accessToken: key});
16+
olms('map', 'mapbox://styles/mapbox/standard', {accessToken: key});

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/apply.js

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import VectorSource from 'ol/source/Vector.js';
3333
import VectorTileSource, {defaultLoadFunction} from 'ol/source/VectorTile.js';
3434
import TileGrid from 'ol/tilegrid/TileGrid.js';
3535
import {createXYZ} from 'ol/tilegrid.js';
36+
import {cameraObj, styleConfig} from './expressions.js';
3637
import {
3738
normalizeSourceUrl,
3839
normalizeSpriteDefinition,
@@ -41,7 +42,6 @@ import {
4142
import {hillshade, raster as rasterShader} from './shaders.js';
4243
import {
4344
_colorWithOpacity,
44-
cameraObj,
4545
getValue,
4646
styleFunctionArgs,
4747
stylefunction as applyStylefunction,
@@ -104,6 +104,17 @@ import {
104104
* specified for the source in the mapbox style definition.
105105
*/
106106

107+
const SUPPORTED_LAYER_TYPES = [
108+
'background',
109+
'circle',
110+
'fill',
111+
'fill-extrusion',
112+
'line',
113+
'symbol',
114+
'raster',
115+
'hillshade',
116+
];
117+
107118
/**
108119
* @param {import("ol/proj/Projection.js").default} projection Projection.
109120
* @param {number} [tileSize] Tile size.
@@ -600,7 +611,6 @@ function getBackgroundColor(glLayer, resolution, options, functionCache) {
600611
id: glLayer.id,
601612
type: glLayer.type,
602613
};
603-
const layout = glLayer.layout || {};
604614
const paint = glLayer.paint || {};
605615
background['paint'] = paint;
606616
cameraObj.zoom = getZoomForResolution(
@@ -625,7 +635,13 @@ function getBackgroundColor(glLayer, resolution, options, functionCache) {
625635
functionCache,
626636
);
627637
}
628-
return layout.visibility == 'none'
638+
return getValue(
639+
background,
640+
'layout',
641+
'visibility',
642+
emptyObj,
643+
functionCache,
644+
) === 'none'
629645
? undefined
630646
: _colorWithOpacity(bg, opacity);
631647
}
@@ -931,7 +947,7 @@ function updateRasterLayerProperties(glLayer, layer, zoom, functionCache) {
931947
layer.setOpacity(opacity);
932948
}
933949

934-
function manageVisibility(layer, mapOrGroup) {
950+
function manageVisibility(layer, mapOrGroup, functionCache) {
935951
function onChange() {
936952
const glStyle = mapOrGroup.get('mapbox-style');
937953
if (!glStyle) {
@@ -946,8 +962,13 @@ function manageVisibility(layer, mapOrGroup) {
946962
.some(function (mapboxLayer) {
947963
return (
948964
!mapboxLayer.layout ||
949-
!mapboxLayer.layout.visibility ||
950-
mapboxLayer.layout.visibility === 'visible'
965+
getValue(
966+
mapboxLayer,
967+
'layout',
968+
'visibility',
969+
emptyObj,
970+
functionCache,
971+
) === 'visible'
951972
);
952973
});
953974
if (layer.get('visible') !== visible) {
@@ -1039,7 +1060,10 @@ export function setupLayer(glStyle, styleUrl, glLayer, options) {
10391060
layer = setupRasterLayer(glSource, styleUrl, options);
10401061
}
10411062
layer.setVisible(
1042-
glLayer.layout ? glLayer.layout.visibility !== 'none' : true,
1063+
glLayer.layout
1064+
? getValue(glLayer, 'layout', 'visibility', emptyObj, functionCache) !==
1065+
'none'
1066+
: true,
10431067
);
10441068

10451069
layer.on('prerender', prerenderRasterLayer(glLayer, layer, functionCache));
@@ -1109,7 +1133,10 @@ export function setupLayer(glStyle, styleUrl, glLayer, options) {
11091133
}
11101134
});
11111135
layer.setVisible(
1112-
glLayer.layout ? glLayer.layout.visibility !== 'none' : true,
1136+
glLayer.layout
1137+
? getValue(glLayer, 'layout', 'visibility', emptyObj, functionCache) !==
1138+
'none'
1139+
: true,
11131140
);
11141141
}
11151142
if (layer) {
@@ -1126,6 +1153,15 @@ export function setupLayer(glStyle, styleUrl, glLayer, options) {
11261153
* @return {Promise} Promise that resolves when the style is loaded.
11271154
*/
11281155
function processStyle(glStyle, mapOrGroup, styleUrl, options) {
1156+
if (glStyle.schema) {
1157+
Object.assign(
1158+
styleConfig,
1159+
Object.keys(glStyle.schema).reduce((config, key) => {
1160+
config[key] = glStyle.schema[key]?.default;
1161+
return config;
1162+
}, {}),
1163+
);
1164+
}
11291165
const promises = [];
11301166

11311167
let view = null;
@@ -1172,10 +1208,10 @@ function processStyle(glStyle, mapOrGroup, styleUrl, options) {
11721208
for (let i = 0, ii = glLayers.length; i < ii; ++i) {
11731209
const glLayer = glLayers[i];
11741210
const type = glLayer.type;
1175-
if (type == 'heatmap') {
1211+
if (!SUPPORTED_LAYER_TYPES.includes(type)) {
11761212
//FIXME Unsupported layer type
11771213
// eslint-disable-next-line no-console
1178-
console.debug(`layers[${i}].type "${type}" not supported`);
1214+
console.warn(`layers[${i}].type "${type}" not supported`);
11791215
continue;
11801216
} else {
11811217
id = glLayer.source || getSourceIdByRef(glLayers, glLayer.ref);
@@ -1424,7 +1460,7 @@ export function finalizeLayer(
14241460
Object.assign({styleUrl: styleUrl}, options),
14251461
)
14261462
.then(function () {
1427-
manageVisibility(layer, mapOrGroup);
1463+
manageVisibility(layer, mapOrGroup, getFunctionCache(glStyle));
14281464
resolve();
14291465
})
14301466
.catch(reject);

src/expressions.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import {
2+
Color,
3+
CompoundExpression,
4+
expressions,
5+
} from '@maplibre/maplibre-gl-style-spec';
6+
import {fromString} from 'ol/color.js';
7+
8+
function hsla(ctx, [h, s, l, a]) {
9+
h = h.evaluate(ctx);
10+
s = s.evaluate(ctx);
11+
l = l.evaluate(ctx);
12+
const alpha = a ? a.evaluate(ctx) : 1;
13+
return Color.parse(`hsla(${h}, ${s}%, ${l}%, ${alpha})`);
14+
}
15+
16+
function rgbaToHsla(rgba) {
17+
const r = rgba[0] / 255;
18+
const g = rgba[1] / 255;
19+
const b = rgba[2] / 255;
20+
const a = rgba[3];
21+
const max = Math.max(r, g, b);
22+
const min = Math.min(r, g, b);
23+
const l = (max + min) / 2;
24+
let h, s;
25+
26+
if (max === min) {
27+
h = 0;
28+
s = 0;
29+
} else {
30+
const d = max - min;
31+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
32+
switch (max) {
33+
case r:
34+
h = (g - b) / d + (g < b ? 6 : 0);
35+
break;
36+
case g:
37+
h = (b - r) / d + 2;
38+
break;
39+
case b:
40+
h = (r - g) / d + 4;
41+
break;
42+
default:
43+
h = 0;
44+
}
45+
h /= 6;
46+
}
47+
return [h * 360, s * 100, l * 100, a];
48+
}
49+
50+
export function wrapImageExtraArgs(expression) {
51+
if (Array.isArray(expression)) {
52+
if (expression.length === 0) {
53+
return expression;
54+
}
55+
const op = expression[0];
56+
if (op === 'literal') {
57+
return expression;
58+
}
59+
if (
60+
op === 'image' &&
61+
expression.length === 3 &&
62+
typeof expression[2] === 'object' &&
63+
expression[2] !== null &&
64+
!Array.isArray(expression[2])
65+
) {
66+
const newExpression = [
67+
'image-config',
68+
wrapImageExtraArgs(expression[1]),
69+
['literal', expression[2]],
70+
];
71+
return newExpression;
72+
}
73+
const length = expression.length;
74+
for (let i = 1; i < length; ++i) {
75+
const arg = expression[i];
76+
const newArg = wrapImageExtraArgs(arg);
77+
if (newArg !== arg) {
78+
const newExpression = [op];
79+
for (let j = 1; j < i; ++j) {
80+
newExpression.push(expression[j]);
81+
}
82+
newExpression.push(newArg);
83+
for (let j = i + 1; j < length; ++j) {
84+
newExpression.push(wrapImageExtraArgs(expression[j]));
85+
}
86+
return newExpression;
87+
}
88+
}
89+
}
90+
return expression;
91+
}
92+
93+
// Shared config object for global expression evaluation context
94+
export const styleConfig = {};
95+
export const cameraObj = {zoom: 0, distanceFromCenter: 0};
96+
97+
// Add unsupported expressions to the MapLibre GL Style spec
98+
CompoundExpression.register(expressions, {
99+
...CompoundExpression.definitions,
100+
'pitch': [{kind: 'number'}, [], (ctx) => cameraObj.pitch || 0],
101+
'distance-from-center': [
102+
{kind: 'number'},
103+
[],
104+
(ctx) => cameraObj.distanceFromCenter || 0,
105+
],
106+
'to-hsla': [
107+
{kind: 'array', itemType: {kind: 'number'}, N: 4},
108+
[{kind: 'string'}],
109+
(ctx, [v]) => {
110+
return rgbaToHsla(fromString(v.evaluate(ctx)));
111+
},
112+
],
113+
'hsl': [
114+
{kind: 'color'},
115+
[{kind: 'number'}, {kind: 'number'}, {kind: 'number'}],
116+
hsla,
117+
],
118+
'hsla': [
119+
{kind: 'color'},
120+
[{kind: 'number'}, {kind: 'number'}, {kind: 'number'}, {kind: 'number'}],
121+
hsla,
122+
],
123+
'image-config': [
124+
{kind: 'value'},
125+
[{kind: 'string'}, {kind: 'value'}],
126+
(ctx, [v, c]) => v.evaluate(ctx),
127+
],
128+
'measure-light': [{kind: 'number'}, [{kind: 'value'}], () => 1],
129+
'config': [
130+
{kind: 'value'},
131+
[{kind: 'string'}],
132+
(ctx, [key]) => {
133+
const value = styleConfig[key.evaluate(ctx)];
134+
return value === undefined ? {} : value;
135+
},
136+
],
137+
});

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ export {
2020
updateMapboxLayer,
2121
updateMapboxSource,
2222
} from './apply.js';
23+
export {styleConfig} from './expressions.js';
2324
export {default as MapboxVectorLayer} from './MapboxVectorLayer.js';

0 commit comments

Comments
 (0)