Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@mapbox/unitbezier": "^0.0.1",
"@mapbox/vector-tile": "^2.0.4",
"@mapbox/whoots-js": "^3.1.0",
"@maplibre/maplibre-gl-style-spec": "^24.3.1",
"@maplibre/maplibre-gl-style-spec": "git+https://github.com/melitele/maplibre-style-spec.git#global-state-visibility-dist",
"@maplibre/mlt": "^1.1.2",
"@maplibre/vt-pbf": "^4.1.0",
"@types/geojson": "^7946.0.16",
Expand Down
28 changes: 28 additions & 0 deletions src/style/style.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1871,6 +1871,34 @@ describe('Style.setGlobalStateProperty', () => {
expect(style.tileManagers['circle-source-id'].reload).toHaveBeenCalled();
});

test('reloads sources when state property is used in visibility', async() => {
const style = new Style(getStubMap());
style.loadJSON(createStyleJSON({
sources: {
'circle-source-id': createGeoJSONSource()
},
layers: [{
id: 'layer-id',
type: 'circle',
source: 'circle-source-id',
layout: {
'visibility': ['case', ['global-state', 'visibility'], 'visible', 'none']
}
}]
}));

await style.once('style.load');

style.tileManagers['circle-source-id'].resume = vi.fn();
style.tileManagers['circle-source-id'].reload = vi.fn();

style.setGlobalStateProperty('visibility', true);
style.update({} as EvaluationParameters);

expect(style.tileManagers['circle-source-id'].resume).toHaveBeenCalled();
expect(style.tileManagers['circle-source-id'].reload).toHaveBeenCalled();
});

test('does not reload sources when state property is set to the same value as current one', async () => {
const style = new Style(getStubMap());
style.loadJSON(createStyleJSON({
Expand Down
5 changes: 5 additions & 0 deletions src/style/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ export class Style extends Evented {
const layer = this._layers[layerId];
const layoutAffectingGlobalStateRefs = layer.getLayoutAffectingGlobalStateRefs();
const paintAffectingGlobalStateRefs = layer.getPaintAffectingGlobalStateRefs();
const visibilityAffectingGlobalStateRefs = layer.getVisibilityAffectingGlobalStateRefs();

if (layoutAffectingGlobalStateRefs.has(ref)) {
sourceIdsToReload.add(layer.source);
Expand All @@ -401,6 +402,10 @@ export class Style extends Evented {
this._updatePaintProperty(layer, name, value);
}
}
if (visibilityAffectingGlobalStateRefs?.has(ref)) {
layer.recalculateVisibility();
this._updateLayer(layer);
}
}
}

Expand Down
30 changes: 30 additions & 0 deletions src/style/style_layer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,18 @@ describe('StyleLayer.getLayoutAffectingGlobalStateRefs', () => {

expect(layer.getLayoutAffectingGlobalStateRefs()).toEqual(new Set<string>(['textSize', 'textTransform']));
});

test('returns global-state references from visibility', () => {
const layer = createStyleLayer({
id: 'background',
type: 'background',
layout: {
'visibility': ['global-state', 'visibility']
}
} as LayerSpecification, {});

expect(layer.getLayoutAffectingGlobalStateRefs()).toEqual(new Set<string>(['visibility']));
});
});

describe('StyleLayer.getPaintAffectingGlobalStateRefs', () => {
Expand Down Expand Up @@ -487,4 +499,22 @@ describe('StyleLayer.globalState', () => {
expect(layer.paint.get('circle-color').evaluate(undefined, {})).toEqual(new Color(1, 0, 0, 1));
expect(layer.paint.get('circle-radius').evaluate(undefined, {})).toBe(15);
});

test('uses layer global state when recalculating visiblity', () => {
const globalState = {visibility: 'none'};
const layer = createStyleLayer({
id: 'background',
type: 'background',
layout: {
'visibility': ['global-state', 'visibility']
}
} as LayerSpecification, globalState) as BackgroundStyleLayer;

expect(layer.isHidden()).toBe(true);

globalState.visibility = 'visible';
layer.recalculateVisibility();

expect(layer.isHidden()).toBe(false);
});
});
43 changes: 36 additions & 7 deletions src/style/style_layer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {filterObject} from '../util/util';

import {featureFilter, latest as styleSpec, supportsPropertyExpression} from '@maplibre/maplibre-gl-style-spec';
import {createVisibilityExpression, featureFilter, latest as styleSpec, supportsPropertyExpression} from '@maplibre/maplibre-gl-style-spec';
import {
validateStyle,
validateLayoutProperty,
Expand All @@ -12,9 +12,14 @@ import {Layout, Transitionable, type Transitioning, type Properties, PossiblyEva

import type {Bucket, BucketParameters} from '../data/bucket';
import type Point from '@mapbox/point-geometry';
import type {FeatureFilter, FeatureState,
import type {
FeatureFilter,
FeatureState,
LayerSpecification,
FilterSpecification} from '@maplibre/maplibre-gl-style-spec';
FilterSpecification,
ExpressionSpecification,
VisibilitySpecification
} from '@maplibre/maplibre-gl-style-spec';
import type {TransitionParameters, PropertyValue} from './properties';
import {type EvaluationParameters} from './evaluation_parameters';
import type {CrossfadeParameters} from './evaluation_parameters';
Expand Down Expand Up @@ -85,7 +90,9 @@ export abstract class StyleLayer extends Evented {
minzoom: number;
maxzoom: number;
filter: FilterSpecification | void;
visibility: 'visible' | 'none' | void;
visibility: VisibilitySpecification | void;
private _visibility: 'visible' | 'none' | void;

_crossfadeParameters: CrossfadeParameters;

_unevaluatedLayout: Layout<any>;
Expand All @@ -97,6 +104,8 @@ export abstract class StyleLayer extends Evented {

_featureFilter: FeatureFilter;

_visibilityExpression: any;

readonly onAdd: ((map: Map) => void);
readonly onRemove: ((map: Map) => void);

Expand Down Expand Up @@ -125,6 +134,8 @@ export abstract class StyleLayer extends Evented {
this.minzoom = layer.minzoom;
this.maxzoom = layer.maxzoom;

this._visibilityExpression = createVisibilityExpression(this.visibility as VisibilitySpecification, globalState);

if (layer.type !== 'background') {
this.source = layer.source;
this.sourceLayer = layer['source-layer'];
Expand Down Expand Up @@ -177,6 +188,10 @@ export abstract class StyleLayer extends Evented {
getLayoutAffectingGlobalStateRefs(): Set<string> {
const globalStateRefs = new Set<string>();

for (const globalStateRef of this._visibilityExpression.getGlobalStateRefs()) {
globalStateRefs.add(globalStateRef);
}

if (this._unevaluatedLayout) {
for (const propertyName in this._unevaluatedLayout._values) {
const value = this._unevaluatedLayout._values[propertyName];
Expand Down Expand Up @@ -217,6 +232,14 @@ export abstract class StyleLayer extends Evented {
return globalStateRefs;
}

/**
* Get list of global state references that are used within visibility expression.
* This is used to determine if layer visibility needs to be updated when global state property changes.
*/
getVisibilityAffectingGlobalStateRefs() {
return this._visibilityExpression.getGlobalStateRefs();
}

setLayoutProperty(name: string, value: any, options: StyleSetterOptions = {}) {
if (value !== null && value !== undefined) {
const key = `layers.${this.id}.layout.${name}`;
Expand All @@ -227,6 +250,8 @@ export abstract class StyleLayer extends Evented {

if (name === 'visibility') {
this.visibility = value;
this._visibilityExpression.setValue(value);
this.recalculateVisibility();
return;
}

Expand Down Expand Up @@ -281,10 +306,10 @@ export abstract class StyleLayer extends Evented {
return false;
}

isHidden(zoom: number, roundMinZoom: boolean = false) {
isHidden(zoom: number = this.minzoom, roundMinZoom: boolean = false) {
if (this.minzoom && zoom < (roundMinZoom ? Math.floor(this.minzoom) : this.minzoom)) return true;
if (this.maxzoom && zoom >= this.maxzoom) return true;
return this.visibility === 'none';
return this.visibility === 'none' || this._visibility === 'none';
}

updateTransitions(parameters: TransitionParameters) {
Expand All @@ -295,6 +320,10 @@ export abstract class StyleLayer extends Evented {
return this._transitioningPaint.hasTransition();
}

recalculateVisibility() {
this._visibility = this._visibilityExpression.evaluate();
}

recalculate(parameters: EvaluationParameters, availableImages: Array<string>) {
if (parameters.getCrossfadeParameters) {
this._crossfadeParameters = parameters.getCrossfadeParameters();
Expand Down Expand Up @@ -323,7 +352,7 @@ export abstract class StyleLayer extends Evented {

if (this.visibility) {
output.layout = output.layout || {};
output.layout.visibility = this.visibility;
output.layout.visibility = this.visibility as ExpressionSpecification;
}

return filterObject(output, (value, key) => {
Expand Down
2 changes: 1 addition & 1 deletion src/style/style_layer/color_relief_style_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,6 @@ export class ColorReliefStyleLayer extends StyleLayer {
}

hasOffscreenPass() {
return this.visibility !== 'none' && !!this.colorRampTextures;
return !this.isHidden() && !!this.colorRampTextures;
}
}
2 changes: 1 addition & 1 deletion src/style/style_layer/heatmap_style_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,6 @@ export class HeatmapStyleLayer extends StyleLayer {
}

hasOffscreenPass() {
return this.paint.get('heatmap-opacity') !== 0 && this.visibility !== 'none';
return this.paint.get('heatmap-opacity') !== 0 && !this.isHidden();
}
}
2 changes: 1 addition & 1 deletion src/style/style_layer/hillshade_style_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ export class HillshadeStyleLayer extends StyleLayer {
}

hasOffscreenPass() {
return this.paint.get('hillshade-exaggeration') !== 0 && this.visibility !== 'none';
return this.paint.get('hillshade-exaggeration') !== 0 && !this.isHidden();
}
}
2 changes: 1 addition & 1 deletion src/style/style_layer_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class StyleLayerIndex {
const layers = layerConfigs.map((layerConfig) => this._layers[layerConfig.id]);

const layer = layers[0];
if (layer.visibility === 'none') {
if (layer.isHidden()) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion test/build/min.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('test min build', () => {
const decreaseQuota = 4096;

// feel free to update this value after you've checked that it has changed on purpose :-)
const expectedBytes = 1011249;
const expectedBytes = 1011929;

expect(actualBytes, `Consider changing expectedBytes to: ${actualBytes}`).toBeLessThan(expectedBytes + increaseQuota);
expect(actualBytes).toBeGreaterThan(expectedBytes - decreaseQuota);
Expand Down