@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
245 lines (244 loc) • 9.56 kB
JavaScript
import LayerWms from '../../../models/layers/layerwms.js';
import LayerWmts from '../../../models/layers/layerwmts.js';
import GeoConsts from '../../../tools/geoconsts.js';
import { isLayerVisible } from './printUtils.js';
import LegendHelper from '../../../tools/legendhelper.js';
import LayerWmsExternal from '../../../models/layers/layerwmsexternal.js';
/**
* Class representing an encoder for creating a legend in the MFP (MapFishPrint) format.
* Iterates on the layer tree layers to collect layers in the right order and with all metadata.
*/
export class MFPLegendEncoder {
options;
defaultOptions = {
useExtent: true,
label: {},
params: {},
showGroupsTitle: true
};
/**
* Sets the options for encoding legend.
*/
setOptions(options) {
this.options = { ...this.defaultOptions, ...options };
}
/**
* Encodes the legend based on the provided options.
* @returns The encoded legend or null if no legend classes were found.
*/
encodeLegend(options) {
this.setOptions(options);
const layerLegend = this.encodeLayersLegend(options.state.layers.layersList);
const legend = { classes: layerLegend };
return legend.classes.length > 0 ? legend : null;
}
/**
* Encodes the legends of every given base layers (recursively).
* @returns The encoded legends as an array of MFPLegendClass objects.
*/
encodeLayersLegend(layers) {
const groupClasses = [];
[...layers].forEach((layer) => {
const item = this.encodeLayerLegendClasses(layer);
if (item) {
this.addClassItemToArray(groupClasses, item);
}
});
return groupClasses;
}
/**
* Encodes the legend classes for a given layer, based on its type or instance.
* @returns The encoded legend classes for the layer, or null if the layer is not supported.
*/
encodeLayerLegendClasses(layer) {
if (layer.children) {
return this.encodeLayerGroupLegendClasses(layer);
}
if (layer instanceof LayerWms || layer instanceof LayerWmsExternal) {
return this.encodeLayerWmsLegendClasses(layer);
}
if (layer instanceof LayerWmts) {
return this.encodeLayerWmtsLegendClasses(layer);
}
return null;
}
/**
* @returns The encoded legend class for the layer group.
*/
encodeLayerGroupLegendClasses(layerGroup) {
const legendGroupItem = {};
if (this.options?.showGroupsTitle) {
legendGroupItem.name = this.options?.i18nManager.getTranslation(layerGroup.name);
}
const groupClasses = [];
legendGroupItem.classes = groupClasses;
layerGroup.children.forEach((childLayer) => {
const child = this.encodeLayerLegendClasses(childLayer);
if (child) {
this.addClassItemToArray(groupClasses, child);
}
});
return this.tryToSimplifyLegendGroup(legendGroupItem);
}
/**
* @returns The encoded legend classes for a given WMTS
* layer, or null if the layer is not visible or inactive.
*/
encodeLayerWmtsLegendClasses(layerWmts) {
if (!isLayerVisible(layerWmts, this.options?.printResolution) || layerWmts.inactive) {
return null;
}
let icon_dpi = this.getMetadataLegendImage(layerWmts);
if (!icon_dpi && layerWmts._olayer) {
const url = LegendHelper.getWMTSLegendURL(layerWmts._olayer);
if (url) {
icon_dpi = {
url: url,
dpi: GeoConsts.SCREEN_DOTS_PER_INCH
};
}
}
if (icon_dpi) {
return {
name: this.options?.i18nManager.getTranslation(layerWmts.name),
icons: [icon_dpi.url]
};
}
return null;
}
/**
* @returns The encoded legend classes for a WMS
* layer, or null if the layer is not visible or inactive.
*/
encodeLayerWmsLegendClasses(layerWms) {
if (!isLayerVisible(layerWms, this.options?.printResolution) || layerWms.inactive) {
return null;
}
const ogcServer = layerWms.ogcServer;
const serverType = ogcServer?.type ?? '';
const dpi = this.options?.dpi ?? GeoConsts.SCREEN_DOTS_PER_INCH;
// Case node as a legend image
const icon_dpi = this.getMetadataLegendImage(layerWms);
if (icon_dpi) {
return this.getLegendClassForWMS(layerWms.name, icon_dpi, serverType);
}
// Case node has no legend image => Get the url for each WMS layer.
const legendLayerClasses = [];
const legendGroupItem = {
classes: legendLayerClasses
};
if (this.options?.showGroupsTitle) {
legendGroupItem.name = this.options?.i18nManager.getTranslation(layerWms.name);
}
const layerNames = layerWms.layers?.split(',') ?? [];
layerNames.forEach((name) => {
const url = LegendHelper.getWMSLegendURL(layerWms.ogcServer, name, {
dpi,
serverType,
scale: this.options?.scale,
bbox: this.options?.extent,
srs: this.options?.mapManager.getMap().getView().getProjection().getCode(),
additionalQueryString: this.options?.params ? this.options?.params[serverType] : undefined
});
if (!url) {
console.error('Missing legend url for wms: ', layerWms.name);
return;
}
const icon_dpi = {
url: url,
dpi: serverType === 'qgisserver' ? dpi : GeoConsts.SCREEN_DOTS_PER_INCH
};
legendLayerClasses.push(this.getLegendClassForWMS(name, icon_dpi, serverType));
});
// Case of wms layer with only one layer, use the node name and remove the useless class level.
if (legendLayerClasses.length === 1) {
const firstLegendLayer = legendLayerClasses[0];
delete firstLegendLayer.classes;
firstLegendLayer.name = this.options?.i18nManager.getTranslation(layerWms.name);
return firstLegendLayer;
}
return this.tryToSimplifyLegendGroup(legendGroupItem);
}
/**
* @returns a MFPLegendClass object representing the legend class for a WMS layer name.
*/
getLegendClassForWMS(layerName, icon_dpi, serverType) {
const legendItem = {
name: this.mustHideLabel(serverType) ? '' : this.options?.i18nManager.getTranslation(layerName),
icons: [icon_dpi.url]
};
if (icon_dpi.dpi !== GeoConsts.SCREEN_DOTS_PER_INCH) {
Object.assign(legendItem, { dpi: icon_dpi.dpi });
}
return legendItem;
}
/**
* Retrieves the metadata legend image or hiDPILegendImages for the given layer.
* @returns The metadata legend image URL and DPI, or null if not found.
*/
getMetadataLegendImage(layer) {
let dpi = this.options?.dpi ?? -1;
if (dpi === -1) {
dpi = GeoConsts.SCREEN_DOTS_PER_INCH;
}
let found_dpi = dpi;
let legendImage = layer.legendImage;
const hiDPILegendImages = layer.hiDPILegendImages;
let dist = Number.MAX_VALUE;
if (legendImage) {
dist = Math.abs(Math.log(GeoConsts.SCREEN_DOTS_PER_INCH / dpi));
found_dpi = GeoConsts.SCREEN_DOTS_PER_INCH;
}
if (hiDPILegendImages) {
for (const str_dpi in hiDPILegendImages) {
const new_dpi = parseFloat(str_dpi);
const new_dist = Math.abs(Math.log(new_dpi / dpi));
if (new_dist < dist) {
dist = new_dist;
found_dpi = new_dpi;
legendImage = hiDPILegendImages[str_dpi];
}
}
}
if (legendImage) {
return {
url: legendImage,
dpi: found_dpi
};
}
return null;
}
/**
* Determines whether the label for a specific server type must be hidden.
* @returns True if the label must be hidden, false otherwise.
* @protected
*/
mustHideLabel(serverType) {
const label = this.options?.label;
return !!label && label[serverType] === false;
}
/**
* Add a classItem to a classes array if the classItem to add is not null.
* If the classItem have embedded classes, these classes must have classItem. Otherwise, the given
* classItem will be not added.
* @protected
*/
addClassItemToArray(classes, classItem) {
if (classItem && (classItem.classes ? classItem.classes.length > 0 : true)) {
classes.push(classItem);
}
}
/**
* If a Legend item have only one child and the child name is identical to its name, then return
* only the children (cut one level). Shrink also if both names are null or undefined.
* Otherwise, return the given legend item.
* @returns The same legend item or a shrunk one.
* @protected
*/
tryToSimplifyLegendGroup(legendGroupItem) {
if (legendGroupItem.classes?.length === 1 && legendGroupItem.classes[0].name === legendGroupItem.name) {
return legendGroupItem.classes[0];
}
return legendGroupItem;
}
}