From f3036b79f2d2d46cf5343cf82a752b40eb8b6011 Mon Sep 17 00:00:00 2001 From: melitele Date: Sun, 5 Oct 2025 22:40:16 -0600 Subject: [PATCH 1/3] use `isHidden` function instead of checking layer `visibility` property directly --- src/style/style_layer.ts | 2 +- src/style/style_layer/color_relief_style_layer.ts | 2 +- src/style/style_layer/heatmap_style_layer.ts | 2 +- src/style/style_layer/hillshade_style_layer.ts | 2 +- src/style/style_layer_index.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/style/style_layer.ts b/src/style/style_layer.ts index 94130587fb4..f44250bd9a4 100644 --- a/src/style/style_layer.ts +++ b/src/style/style_layer.ts @@ -283,7 +283,7 @@ 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'; diff --git a/src/style/style_layer/color_relief_style_layer.ts b/src/style/style_layer/color_relief_style_layer.ts index 8551b6fc623..a42cd6a5444 100644 --- a/src/style/style_layer/color_relief_style_layer.ts +++ b/src/style/style_layer/color_relief_style_layer.ts @@ -98,6 +98,6 @@ export class ColorReliefStyleLayer extends StyleLayer { } hasOffscreenPass() { - return this.visibility !== 'none' && !!this.colorRampTextures; + return !this.isHidden() && !!this.colorRampTextures; } } diff --git a/src/style/style_layer/heatmap_style_layer.ts b/src/style/style_layer/heatmap_style_layer.ts index 6c61533c7f0..b668b2dd533 100644 --- a/src/style/style_layer/heatmap_style_layer.ts +++ b/src/style/style_layer/heatmap_style_layer.ts @@ -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(); } } diff --git a/src/style/style_layer/hillshade_style_layer.ts b/src/style/style_layer/hillshade_style_layer.ts index b0be2d303db..638e80b04be 100644 --- a/src/style/style_layer/hillshade_style_layer.ts +++ b/src/style/style_layer/hillshade_style_layer.ts @@ -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(); } } diff --git a/src/style/style_layer_index.ts b/src/style/style_layer_index.ts index c4abb8b0660..57e179f035f 100644 --- a/src/style/style_layer_index.ts +++ b/src/style/style_layer_index.ts @@ -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; } From 43f4063cb6203a1ba9231201a9e27b91e25bdd05 Mon Sep 17 00:00:00 2001 From: melitele Date: Mon, 6 Oct 2025 00:05:10 -0600 Subject: [PATCH 2/3] recalculate `visibility` when relevant global state properties change https://github.com/maplibre/maplibre-gl-js/issues/6495 --- src/style/style.ts | 5 +++++ src/style/style_layer.ts | 41 ++++++++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/style/style.ts b/src/style/style.ts index 13bd9b08e58..7e101db1aa9 100644 --- a/src/style/style.ts +++ b/src/style/style.ts @@ -370,6 +370,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); @@ -379,6 +380,10 @@ export class Style extends Evented { this._updatePaintProperty(layer, name, value); } } + if (visibilityAffectingGlobalStateRefs?.has(ref)) { + layer.recalculateVisibility(); + this._updateLayer(layer); + } } } diff --git a/src/style/style_layer.ts b/src/style/style_layer.ts index f44250bd9a4..2c2b0e6fdcb 100644 --- a/src/style/style_layer.ts +++ b/src/style/style_layer.ts @@ -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, @@ -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'; @@ -87,7 +92,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; @@ -99,6 +106,8 @@ export abstract class StyleLayer extends Evented { _featureFilter: FeatureFilter; + _visibilityExpression: any; + readonly onAdd: ((map: Map) => void); readonly onRemove: ((map: Map) => void); @@ -127,6 +136,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']; @@ -179,6 +190,10 @@ export abstract class StyleLayer extends Evented { getLayoutAffectingGlobalStateRefs(): Set { const globalStateRefs = new Set(); + 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]; @@ -219,6 +234,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}`; @@ -229,6 +252,8 @@ export abstract class StyleLayer extends Evented { if (name === 'visibility') { this.visibility = value; + this._visibilityExpression.setValue(value); + this.recalculateVisibility(); return; } @@ -286,7 +311,7 @@ export abstract class StyleLayer extends Evented { 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) { @@ -297,6 +322,10 @@ export abstract class StyleLayer extends Evented { return this._transitioningPaint.hasTransition(); } + recalculateVisibility() { + this._visibility = this._visibilityExpression.evaluate(); + } + recalculate(parameters: EvaluationParameters, availableImages: Array) { if (parameters.getCrossfadeParameters) { this._crossfadeParameters = parameters.getCrossfadeParameters(); @@ -325,7 +354,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) => { From d59547d2f6d16be26ee82a537cdb2fe21660fc12 Mon Sep 17 00:00:00 2001 From: melitele Date: Mon, 3 Nov 2025 00:58:17 -0700 Subject: [PATCH 3/3] tmp: maplibre-style-spec --- package-lock.json | 5 ++--- package.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b864fbea29..f78d6a1c353 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,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/vt-pbf": "^4.0.3", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "3.2.5", @@ -2634,8 +2634,7 @@ }, "node_modules/@maplibre/maplibre-gl-style-spec": { "version": "24.3.1", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.3.1.tgz", - "integrity": "sha512-TUM5JD40H2mgtVXl5IwWz03BuQabw8oZQLJTmPpJA0YTYF+B+oZppy5lNMO6bMvHzB+/5mxqW9VLG3wFdeqtOw==", + "resolved": "git+ssh://git@github.com/melitele/maplibre-style-spec.git#2876484e7df11dd54eef5d9cf5f03b1a93428451", "license": "ISC", "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", diff --git a/package.json b/package.json index 4a1b381533f..54d132193ed 100644 --- a/package.json +++ b/package.json @@ -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/vt-pbf": "^4.0.3", "@types/geojson": "^7946.0.16", "@types/geojson-vt": "3.2.5",