Skip to content

Commit 4e874cc

Browse files
mind84github-actions[bot]
authored andcommitted
take into account group opacity for printing
1 parent cbac3e1 commit 4e874cc

File tree

8 files changed

+2807
-3
lines changed

8 files changed

+2807
-3
lines changed

assets/src/components/Print.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ export default class Print extends HTMLElement {
245245

246246
// Add visible layers in defined order
247247
const orderedVisibleLayers = {};
248-
mainLizmap.state.rootMapGroup.findMapLayers().forEach(layer => {
248+
mainLizmap.state.rootMapGroup.findExplodedMapLayers().forEach(layer => {
249249
if (layer.visibility) {
250250
orderedVisibleLayers[layer.layerOrder] = layer;
251251
}
@@ -267,9 +267,9 @@ export default class Print extends HTMLElement {
267267

268268
// Handle qgis layer opacity otherwise client value override it
269269
if (layer.layerConfig?.opacity) {
270-
opacityLayers.push(parseInt(255 * layer.opacity * layer.layerConfig.opacity));
270+
opacityLayers.push(parseInt(255 * layer.calculateTotalOpacity() * layer.layerConfig.opacity));
271271
} else {
272-
opacityLayers.push(parseInt(255 * layer.opacity));
272+
opacityLayers.push(parseInt(255 * layer.calculateTotalOpacity()));
273273
}
274274
if ('FILTERTOKEN' in layerWmsParams) {
275275
filter.push(layerWmsParams['FILTERTOKEN']);

assets/src/modules/state/Layer.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,19 @@ export class LayerItemState extends EventDispatcher {
552552
}
553553
return this._visibility;
554554
}
555+
556+
/**
557+
* Calculate total opacity by including also all parent groups opacity values
558+
* @returns {number} the total opacity
559+
*/
560+
calculateTotalOpacity(){
561+
let opacity = this.opacity;
562+
if(this._parentGroup !== null){
563+
opacity = opacity*this._parentGroup.calculateTotalOpacity();
564+
}
565+
566+
return Math.round(opacity * 100) / 100;
567+
}
555568
}
556569

557570
/**

assets/src/modules/state/MapLayer.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,30 @@ export class MapGroupState extends MapItemState {
424424
return layersCount;
425425
}
426426

427+
/**
428+
* Find all layers by exploding the layers within every "group as layer" groups
429+
* @returns {LayerLayerState[]} The layer states of listed layers
430+
*/
431+
findExplodedMapLayers(){
432+
let layers = [];
433+
for(const item of this.getChildren()) {
434+
if (item instanceof MapLayerState) {
435+
const itemState = item.itemState;
436+
if(itemState instanceof LayerGroupState && itemState.groupAsLayer){
437+
//count the layers inside the group
438+
layers = layers.concat(itemState.findLayers());
439+
}
440+
else if(itemState instanceof LayerLayerState){
441+
layers.push(item.itemState);
442+
}
443+
} else if (item instanceof MapGroupState) {
444+
layers = layers.concat(item.findExplodedMapLayers());
445+
}
446+
}
447+
448+
return layers;
449+
}
450+
427451
/**
428452
* Find layer items
429453
* @returns {MapLayerState[]} The layer names of all map layers
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// @ts-check
2+
import { expect } from '@playwright/test';
3+
import { ProjectPage } from './project';
4+
5+
/**
6+
* Playwright Page
7+
* @typedef {import('@playwright/test').Page} Page
8+
*/
9+
10+
/**
11+
* Playwright Page
12+
* @typedef {import('@playwright/test').Locator} Locator
13+
*/
14+
15+
export class PrintPage extends ProjectPage {
16+
// Metadata
17+
/**
18+
* The print panel
19+
* @type {Locator}
20+
*/
21+
printPanel;
22+
23+
/**
24+
* The print button on menu
25+
* @type {Locator}
26+
*/
27+
printSwitcherButton;
28+
29+
/**
30+
* The print scale combobox
31+
* @type {Locator}
32+
*/
33+
printScale;
34+
35+
/**
36+
* The launch print button on print panel
37+
* @type {Locator}
38+
*/
39+
launchPrintButton;
40+
41+
/**
42+
* Constructor for a QGIS project page
43+
* @param {Page} page The playwright page
44+
* @param {string} project The project name
45+
* @param {string} repository The repository name, default to testsrepository
46+
*/
47+
constructor(page, project, repository = 'testsrepository') {
48+
super(page, project, repository);
49+
50+
this.printPanel = page.locator('#print');
51+
this.printSwitcherButton = page.locator('#button-print');
52+
this.launchPrintButton = page.locator('#print-launch');
53+
this.printScaleCombobox = page.locator('#print-scale');
54+
}
55+
56+
/**
57+
* openPrintPanel function
58+
* opens the print mini-dock panel
59+
*/
60+
async openPrintPanel() {
61+
await this.page.locator('#button-print').click();
62+
}
63+
64+
/**
65+
* setPrintScale function
66+
* Set the print scale
67+
* @param {string} scale The scale
68+
*/
69+
async setPrintScale(scale) {
70+
await this.printScaleCombobox.selectOption(scale);
71+
}
72+
73+
/**
74+
* launchPrint function
75+
* Launch print
76+
*/
77+
async launchPrint() {
78+
// Launch print
79+
await this.launchPrintButton.click();
80+
// check message
81+
await expect(this.page.locator('div.alert')).toHaveCount(1);
82+
}
83+
}

tests/end2end/playwright/pages/project.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,27 @@ export class ProjectPage extends BasePage {
174174
await this.page.locator('#attribute-layer-list-table').locator(`button[value=${layer}]`).click();
175175
}
176176

177+
/**
178+
* openLayerInfo function
179+
* Open the info layer panel for the given layer
180+
* @param {string} layer Name of the layer
181+
*/
182+
async openLayerInfo(layer) {
183+
await this.page.getByTestId(layer).locator('.node').first().hover();
184+
await this.page.getByTestId(layer).locator('.layer-actions').first().locator('i.icon-info-sign').click();
185+
}
186+
187+
/**
188+
* setLayerOpacity function
189+
* Open the info layer panel for the given layer
190+
* @param {string} layer Name of the layer
191+
* @param {string} opacity Layer opacity, possible values '0','20','40','60','80','100'
192+
*/
193+
async setLayerOpacity(layer, opacity = '100') {
194+
await this.openLayerInfo(layer);
195+
await this.page.getByRole('link', { name: opacity }).click();
196+
}
197+
177198
/**
178199
* editingSubmitForm function
179200
* Submit the form

tests/end2end/playwright/print.spec.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @ts-check
22
import { test, expect } from '@playwright/test';
3+
import {PrintPage} from "./pages/printpage";
34
import { gotoMap, expectParametersToContain, getAuthStorageStatePath, expectToHaveLengthCompare } from './globals';
45

56
test.describe('Print', () => {
@@ -335,6 +336,153 @@ test.describe('Print', () => {
335336
});
336337
});
337338

339+
test.describe(
340+
'Print opacities',
341+
{
342+
tag: ['@readonly'],
343+
},() =>
344+
{
345+
346+
test('Group as layer', async ({ page }) => {
347+
348+
const printPage = new PrintPage(page, 'group_as_layer_opacity');
349+
await printPage.open();
350+
await printPage.openPrintPanel();
351+
await printPage.setPrintScale('50000');
352+
353+
// set opacity of `Group_opacity` (group as layer) layer to 60%
354+
await printPage.setLayerOpacity('Group_opacity','60');
355+
356+
// opacity notes:
357+
// the `raster` layer already has 80% opacity (inherited from QGIS project)
358+
// setting the opacity to 60% on the `Group_opacity` causes:
359+
// - `raster` layer to have a final total opacity of 48% (0.8*0.6)
360+
// -> OPACITIES PARAM = 255*0.48 ~= 122
361+
// - `single_wms_polygons` layer to have a final total opacity of 60% (1*0.6)
362+
// -> OPACITIES PARAM = 255*0.6 = 153
363+
// other layers have opacity 100% (255)
364+
365+
const mapLayers = [
366+
'OpenStreetMap',
367+
'raster',
368+
'single_wms_polygons',
369+
'single_wms_polygons_group_as_layer',
370+
'single_wms_points_group',
371+
]
372+
373+
const expectedParameters = {
374+
'SERVICE': 'WMS',
375+
'REQUEST': 'GetPrint',
376+
'VERSION': '1.3.0',
377+
'FORMAT': 'pdf',
378+
'TRANSPARENT': 'true',
379+
'CRS': 'EPSG:3857',
380+
'DPI': '100',
381+
'TEMPLATE': 'test',
382+
'map0:EXTENT': /425189.\d+,5401412.\d+,439539.\d+,5411262.\d+/,
383+
'map0:SCALE': '50000',
384+
'map0:LAYERS': mapLayers.join(','),
385+
'map0:STYLES': 'default,default,default,default,default',
386+
'map0:OPACITIES': '255,122,153,255,255',
387+
}
388+
// Test `test` template
389+
let getPrintPromise = page.waitForRequest(
390+
request =>
391+
request.method() === 'POST' &&
392+
request.postData()?.includes('GetPrint') === true
393+
);
394+
395+
// Launch print
396+
await printPage.launchPrint();
397+
398+
// check request
399+
let getPrintRequest = await getPrintPromise;
400+
401+
// check response parameters
402+
let name = "Group as layer opacity requests";
403+
let getPrintParams = await expectParametersToContain(
404+
name, getPrintRequest.postData() ?? '', expectedParameters);
405+
406+
await expectToHaveLengthCompare(
407+
name,
408+
Array.from(getPrintParams.keys()),
409+
13,
410+
Object.keys(expectedParameters)
411+
);
412+
})
413+
414+
test('Layers in group with opacity', async ({ page }) => {
415+
const printPage = new PrintPage(page, 'group_as_layer_opacity');
416+
await printPage.open();
417+
await printPage.openPrintPanel();
418+
await printPage.setPrintScale('50000');
419+
420+
// set opacity of `Group_a` group opacity to 60%
421+
await printPage.setLayerOpacity('Group_a','60');
422+
// set opacity of `single_wms_points_group` (belonging to `Group_a`) layer to 40%
423+
await printPage.setLayerOpacity('single_wms_points_group','40');
424+
// set opacity of `single_wms_polygons_group_as_layer` (belonging to `Group_a`) layer to 80%
425+
await printPage.setLayerOpacity('single_wms_polygons_group_as_layer','80');
426+
427+
// opacity notes:
428+
// setting the opacity to 60% on `Group_a` causes:
429+
// - `single_wms_points_group` layer to have a final total opacity of 24% (0.4*0.6)
430+
// -> OPACITIES PARAM = 255*0.24 ~= 61
431+
// - `single_wms_polygons_group_as_layer` layer to have a final total opacity of 48% (0.8*0.6)
432+
// -> OPACITIES PARAM = 255*0.48 ~= 122
433+
// other layers have opacity 100%, except for `raster` layer which has a 80% opacity by default,
434+
// resulting in a OPACITIES PARAM = 255*0.8 = 204
435+
436+
const mapLayers = [
437+
'OpenStreetMap',
438+
'raster',
439+
'single_wms_polygons',
440+
'single_wms_polygons_group_as_layer',
441+
'single_wms_points_group',
442+
]
443+
444+
const expectedParameters = {
445+
'SERVICE': 'WMS',
446+
'REQUEST': 'GetPrint',
447+
'VERSION': '1.3.0',
448+
'FORMAT': 'pdf',
449+
'TRANSPARENT': 'true',
450+
'CRS': 'EPSG:3857',
451+
'DPI': '100',
452+
'TEMPLATE': 'test',
453+
'map0:EXTENT': /425189.\d+,5401412.\d+,439539.\d+,5411262.\d+/,
454+
'map0:SCALE': '50000',
455+
'map0:LAYERS': mapLayers.join(','),
456+
'map0:STYLES': 'default,default,default,default,default',
457+
'map0:OPACITIES': '255,204,255,122,61',
458+
}
459+
// Test `test` template
460+
let getPrintPromise = page.waitForRequest(
461+
request =>
462+
request.method() === 'POST' &&
463+
request.postData()?.includes('GetPrint') === true
464+
);
465+
466+
// Launch print
467+
await printPage.launchPrint();
468+
469+
// check request
470+
let getPrintRequest = await getPrintPromise;
471+
472+
// check response parameters
473+
let name = "Layers in group with opacity request";
474+
let getPrintParams = await expectParametersToContain(
475+
name, getPrintRequest.postData() ?? '', expectedParameters);
476+
await expectToHaveLengthCompare(
477+
name,
478+
Array.from(getPrintParams.keys()),
479+
13,
480+
Object.keys(expectedParameters)
481+
);
482+
})
483+
}
484+
);
485+
338486
test.describe('Print in popup', () => {
339487
test.beforeEach(async ({ page }) => {
340488
const url = '/index.php/view/map/?repository=testsrepository&project=print';

0 commit comments

Comments
 (0)