Skip to content

Commit d066a1c

Browse files
authored
Merge pull request #6558 from 3liz/backport-6114-to-release_3_10
[Backport release_3_10] Fix #5934: Display legend for external WMS layers
2 parents 2b8b294 + 6e9f1fb commit d066a1c

File tree

5 files changed

+264
-15
lines changed

5 files changed

+264
-15
lines changed

assets/src/modules/WMS.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,29 @@ export default class WMS {
8686
body: params,
8787
});
8888
}
89+
90+
/**
91+
* Get legend graphic as PNG image URL from WMS
92+
* @param {object} options - optional parameters which can override this._defaultGetLegendGraphicsParameters
93+
* @returns {string} PNG image URL
94+
* @memberof WMS
95+
*/
96+
getLegendGraphicPNG(options) {
97+
const layers = options['LAYERS'] ?? options['LAYER'];
98+
// Check if layer is specified
99+
if (!layers) {
100+
throw new RequestError(
101+
'LAYERS or LAYER parameter is required for getLegendGraphic request',
102+
options,
103+
);
104+
}
105+
106+
const params = new URLSearchParams({
107+
...this._defaultGetLegendGraphicParameters,
108+
...options,
109+
FORMAT: 'image/png' // Force PNG format for external WMS layers
110+
});
111+
112+
return `${globalThis['lizUrls'].wms}?${params}`;
113+
}
89114
}

assets/src/modules/action/Symbology.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,42 @@ export async function updateLayerTreeLayersSymbology(treeLayers, method=HttpRequ
3232

3333
if (method.toUpperCase() == HttpRequestMethods.GET) {
3434
for (const treeLayer of treeLayers) {
35+
// Check if this is an external WMS layer using the backend flag
36+
const isExternalWMS = treeLayer.layerConfig?.externalWmsToggle;
37+
38+
if (isExternalWMS) {
39+
// For external WMS layers, use GetLegendGraphic URL directly as DOM element src
40+
const wmsParams = {
41+
LAYER: treeLayer.wmsName,
42+
STYLES: treeLayer.wmsSelectedStyleName,
43+
LAYERTITLE: 'FALSE',
44+
};
45+
try {
46+
const pngUrl = wms.getLegendGraphicPNG(wmsParams);
47+
// Wrap in LayerSymbolsSymbology structure to make it expandable/hideable
48+
// This creates a single-item legend that can be collapsed
49+
treeLayer.symbology = {
50+
type: 'layer',
51+
name: treeLayer.wmsName,
52+
title: treeLayer.name,
53+
symbols: [{
54+
type: 'image',
55+
url: pngUrl,
56+
title: treeLayer.name,
57+
}]
58+
};
59+
} catch (error) {
60+
console.error('Error loading external WMS legend:', error);
61+
// Fallback to default icon will be handled by symbology state
62+
}
63+
continue;
64+
}
65+
66+
// Request JSON only for non external WMS
3567
const wmsParams = {
3668
LAYER: treeLayer.wmsName,
3769
STYLES: treeLayer.wmsSelectedStyleName,
3870
};
39-
4071
await wms.getLegendGraphic(wmsParams).then((response) => {
4172
for (const node of response.nodes) {
4273
// If the layer has no symbology, there is no type property

assets/src/modules/state/Symbology.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ export class BaseIconSymbology extends BaseObjectSymbology {
154154
* @type {string}
155155
*/
156156
get icon() {
157+
// Check if _icon exists and is a string
158+
if (!this._icon || typeof this._icon !== 'string') {
159+
return base64png + base64pngNullData;
160+
}
161+
162+
// Otherwise, it's base64 data that needs the prefix
157163
return base64png + this._icon;
158164
}
159165
}
@@ -179,7 +185,6 @@ export class LayerIconSymbology extends BaseIconSymbology {
179185
* @param {string} node.title - the node title
180186
*/
181187
constructor(node) {
182-
183188
if (!node.hasOwnProperty('type') || node.type != 'layer') {
184189
throw new ValidationError('The layer icon symbology is only available for layer type!');
185190
}
@@ -531,7 +536,11 @@ export class BaseSymbolsSymbology extends BaseObjectSymbology {
531536
*/
532537
this._icons = [];
533538
for(const symbol of this._symbols) {
534-
this._icons.push(new iconClass(symbol));
539+
if (symbol?.type === 'image') {
540+
this._icons.push(new SymbolImageSymbology(symbol));
541+
} else {
542+
this._icons.push(new iconClass(symbol));
543+
}
535544
}
536545

537546
/**
@@ -580,7 +589,7 @@ export class BaseSymbolsSymbology extends BaseObjectSymbology {
580589

581590
/**
582591
* The children icons
583-
* @type {BaseIconSymbology[]}
592+
* @type {Array<BaseIconSymbology|SymbolImageSymbology>}
584593
*/
585594
get children() {
586595
return [...this._icons];
@@ -589,7 +598,7 @@ export class BaseSymbolsSymbology extends BaseObjectSymbology {
589598
/**
590599
* Iterate through children icons
591600
* @generator
592-
* @yields {BaseIconSymbology} The next child icon
601+
* @yields {BaseIconSymbology|SymbolImageSymbology} The next child icon
593602
*/
594603
*getChildren() {
595604
for (const icon of this._icons) {
@@ -664,7 +673,7 @@ export class LayerSymbolsSymbology extends BaseSymbolsSymbology {
664673

665674
/**
666675
* The private children icons
667-
* @type {SymbolIconSymbology[]|SymbolRuleSymbology[]}
676+
* @type {Array<SymbolIconSymbology|SymbolImageSymbology>|Array<SymbolRuleSymbology|SymbolImageSymbology>}
668677
* @private
669678
*/
670679
this._icons;
@@ -697,14 +706,22 @@ export class LayerSymbolsSymbology extends BaseSymbolsSymbology {
697706
* @type {boolean}
698707
*/
699708
get legendOn() {
709+
let imageCount = 0;
700710
for (const symbol of this._icons) {
711+
if (symbol.type === 'image') {
712+
imageCount += 1;
713+
continue;
714+
}
701715
if (symbol.ruleKey === '') {
702716
return true;
703717
}
704718
if (symbol.legendOn) {
705719
return true;
706720
}
707721
}
722+
if (this._icons.length === imageCount) {
723+
return true;
724+
}
708725
return false;
709726
}
710727

@@ -721,7 +738,7 @@ export class LayerSymbolsSymbology extends BaseSymbolsSymbology {
721738

722739
/**
723740
* The children icons
724-
* @type {Array<SymbolIconSymbology|SymbolRuleSymbology>}
741+
* @type {Array<SymbolIconSymbology|SymbolRuleSymbology|SymbolImageSymbology>}
725742
*/
726743
get children() {
727744
if (this._root !== null) {
@@ -733,7 +750,7 @@ export class LayerSymbolsSymbology extends BaseSymbolsSymbology {
733750
/**
734751
* Iterate through children icons
735752
* @generator
736-
* @yields {SymbolIconSymbology|SymbolRuleSymbology} The next child icon
753+
* @yields {SymbolIconSymbology|SymbolRuleSymbology|SymbolImageSymbology} The next child icon
737754
*/
738755
*getChildren() {
739756
for (const child of this.children) {
@@ -751,7 +768,13 @@ export class LayerSymbolsSymbology extends BaseSymbolsSymbology {
751768
}
752769
let keyChecked = [];
753770
let keyUnchecked = [];
771+
let imageCount = 0;
754772
for (const symbol of this._icons) {
773+
if (symbol.type === 'image') {
774+
imageCount += 1;
775+
continue;
776+
}
777+
// If the ruleKey is empty, we don't want to add it to the parameters
755778
if (symbol.ruleKey === '') {
756779
keyChecked = [];
757780
keyUnchecked = [];
@@ -764,7 +787,7 @@ export class LayerSymbolsSymbology extends BaseSymbolsSymbology {
764787
}
765788
}
766789
if ((keyChecked.length != 0 || keyUnchecked.length != 0)
767-
&& keyChecked.length != this._icons.length) {
790+
&& keyChecked.length !== this._icons.length - imageCount) {
768791
params['LEGEND_ON'] = wmsName+':'+keyChecked.join();
769792
params['LEGEND_OFF'] = wmsName+':'+keyUnchecked.join();
770793
}

lizmap/modules/lizmap/lib/Project/Project.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,13 +2045,14 @@ public function getUpdatedConfig()
20452045
&& !array_key_exists('crs', $layerDatasource)) {
20462046
$layerDatasource['crs'] = 'EPSG:3857';
20472047
}
2048-
// if the layer datasource contains type and crs EPSG:3857
2049-
// external access can be provided
2050-
if (array_key_exists('type', $layerDatasource)
2051-
&& $layerDatasource['crs'] == 'EPSG:3857') {
2048+
// Set externalWmsToggle for:
2049+
// 1. WMS layers (no 'type' field or type='wms')
2050+
// 2. xyz/wmts layers with EPSG:3857 CRS (preserves original functionality)
2051+
if ((!array_key_exists('type', $layerDatasource) || $layerDatasource['type'] == 'wms')
2052+
|| (array_key_exists('type', $layerDatasource) && $layerDatasource['crs'] == 'EPSG:3857')) {
20522053
$obj->externalWmsToggle = 'True';
2053-
$obj->externalAccess = $layerDatasource;
20542054
}
2055+
$obj->externalAccess = $layerDatasource;
20552056
}
20562057
}
20572058
}

0 commit comments

Comments
 (0)