/**
* @module config/LayerTree.js
* @name LayerTree
* @copyright 2023 3Liz
* @author DHONT René-Luc
* @license MPL-2.0
*/
import { ValidationError, ConversionError } from './../Errors.js';
import { Extent } from './../utils/Extent.js';
import { AttributionConfig } from './Attribution.js';
import { LayerConfig, LayersConfig } from './Layer.js';
/**
* Class representing a wMS layer Geographic Bounding Box
* @class
* @augments Extent
*/
export class LayerGeographicBoundingBoxConfig extends Extent {
/**
* Create the WMS layer Geographic Bounding Box
* @param {...number} args - the 4 values describing the Geographic Bounding Box: west, south, east, north
* @throws {ValidationError} for number of args different of 4
* @throws {ConversionError} for values not number
*/
constructor(...args) {
super(...args);
}
/**
* @type {number}
*/
get west() {
return this[0];
}
/**
* @type {number}
*/
get south() {
return this[1];
}
/**
* @type {number}
*/
get east() {
return this[2];
}
/**
* @type {number}
*/
get north() {
return this[3];
}
}
/**
* Class representing a WMS layer Bounding Box
* @class
* @augments Extent
*/
export class LayerBoundingBoxConfig extends Extent {
/**
* Create the WMS layer Geographic Bounding Box
* @param {string} crs - the CRS name
* @param {number[]} values - the 4 values describing the Geographic Bounding Box: west, south, east, north
* @throws {ValidationError} for number of args different of 4
* @throws {ConversionError} for values not number
*/
constructor(crs, values) {
super(...values);
this._crs = crs;
}
/**
* The CRS name
* @type {string}
*/
get crs() {
return this._crs;
}
}
/**
* Class representing a WMS layer Style
* @class
*/
export class LayerStyleConfig {
/**
* Create a WMS layer Style instance
* @param {string} wmsName - the layer WMS style name
* @param {string} wmsTitle - the layer WMS style title
*/
constructor(wmsName, wmsTitle) {
this._wmsName = wmsName;
this._wmsTitle = wmsTitle;
}
/**
* WMS Style name
* @type {string}
*/
get wmsName() {
return this._wmsName;
}
/**
* WMS Style title
* @type {string}
*/
get wmsTitle() {
if (!this._wmsTitle) {
return this._wmsName;
}
return this._wmsTitle;
}
}
/**
* Class representing a layer tree item config
* @class
*/
export class LayerTreeItemConfig {
/**
* Create a layer tree item config instance
* @param {string} name - the QGIS layer name
* @param {string} type - the layer tree item type
* @param {number} level - the layer tree item level
* @param {object} wmsCapaLayer - the WMS capabilities layer element
* @param {LayerConfig} [layerCfg] - the lizmap layer config
*/
constructor(name, type, level, wmsCapaLayer, layerCfg) {
this._name = name;
this._type = type;
this._level = level;
this._wmsCapa = wmsCapaLayer;
if (!layerCfg) {
this._layerCfg = null;
} else {
this._layerCfg = layerCfg;
}
}
/**
* The layer name - QGIS layer name
* @type {string}
*/
get name() {
return this._name;
}
/**
* The layer tree item type
* @type {string}
*/
get type() {
return this._type;
}
/**
* the layer tree item level
* @type {number}
*/
get level() {
return this._level;
}
/**
* WMS layer name
* @type {?string}
*/
get wmsName() {
if(!this._wmsCapa.hasOwnProperty('Name')) {
return null;
}
return this._wmsCapa.Name;
}
/**
* WMS layer title
* @type {string}
*/
get wmsTitle() {
return this._wmsCapa.Title;
}
/**
* WMS layer abstract
* @type {?string}
*/
get wmsAbstract() {
if(!this._wmsCapa.hasOwnProperty('Abstract')) {
return null;
}
return this._wmsCapa.Abstract;
}
/**
* WMS layer Geographic Bounding Box
* @type {?LayerGeographicBoundingBoxConfig}
*/
get wmsGeographicBoundingBox() {
if(!this._wmsCapa.hasOwnProperty('EX_GeographicBoundingBox')) {
return null;
}
return new LayerGeographicBoundingBoxConfig(...this._wmsCapa.EX_GeographicBoundingBox);
}
/**
* WMS layer Bounding Boxes
* @type {LayerBoundingBoxConfig[]}
*/
get wmsBoundingBoxes() {
let wmsBoundingBoxes = [];
for(const wmsBoundingBox of this._wmsCapa.BoundingBox) {
wmsBoundingBoxes.push(new LayerBoundingBoxConfig(wmsBoundingBox.crs, wmsBoundingBox.extent))
}
return [...wmsBoundingBoxes];
}
/**
* WMS layer minimum scale denominator
* If the minimum scale denominator is not defined: -1 is returned
* @type {number}
*/
get wmsMinScaleDenominator() {
if(!this._wmsCapa.hasOwnProperty('MinScaleDenominator')) {
return -1;
}
return this._wmsCapa.MinScaleDenominator;
}
/**
* WMS layer maximum scale denominator
* If the maximum scale denominator is not defined: -1 is returned
* @type {number}
*/
get wmsMaxScaleDenominator() {
if(!this._wmsCapa.hasOwnProperty('MaxScaleDenominator')) {
return -1;
}
return this._wmsCapa.MaxScaleDenominator;
}
/**
* Lizmap layer config
* @type {?LayerConfig}
*/
get layerConfig() {
return this._layerCfg;
}
}
/**
* Class representing a layer tree layer config
* @class
* @augments LayerTreeItemConfig
*/
export class LayerTreeLayerConfig extends LayerTreeItemConfig {
/**
* Create a layer tree layer config instance
* @param {string} name - the QGIS layer name
* @param {number} level - the layer tree item level
* @param {object} wmsCapaLayer - the WMS capabilities layer element
* @param {LayerConfig} layerCfg - the lizmap layer config
*/
constructor(name, level, wmsCapaLayer, layerCfg) {
super(name, 'layer', level, wmsCapaLayer, layerCfg);
this._wmsStyles = null;
}
/**
* WMS layer styles
* @type {LayerStyleConfig[]}
*/
get wmsStyles() {
if (this._wmsStyles !== null) {
return this._wmsStyles;
}
if(!this._wmsCapa?.['Style']) {
this._wmsStyles = [new LayerStyleConfig('', 'Default')];
} else {
let wmsStyles = [];
for(const wmsStyle of this._wmsCapa.Style) {
wmsStyles.push(new LayerStyleConfig(wmsStyle.Name, wmsStyle.Title))
}
this._wmsStyles = wmsStyles;
}
return [...this._wmsStyles];
}
/**
* WMS layer attribution
* @type {?AttributionConfig}
*/
get wmsAttribution() {
if(!this._wmsCapa?.['Attribution']) {
return null;
}
const attribution = this._wmsCapa.Attribution;
if (!attribution.hasOwnProperty('Title')) {
return null;
}
return new AttributionConfig({
title: attribution.Title,
url: attribution.hasOwnProperty('OnlineResource') ? attribution.OnlineResource : null,
});
}
}
/**
* Class representing a layer tree group config
* @class
* @augments LayerTreeItemConfig
*/
export class LayerTreeGroupConfig extends LayerTreeItemConfig {
/**
* Create a layer tree group config instance
* @param {string} name - the QGIS layer name
* @param {number} level - the layer tree item level
* @param {LayerTreeItemConfig[]} items - the children layer tree items
* @param {object} wmsCapaLayer - the WMS capabilities layer element
* @param {LayerConfig} [layerCfg] - the lizmap layer config
*/
constructor(name, level, items, wmsCapaLayer, layerCfg) {
super(name, 'group', level, wmsCapaLayer, layerCfg);
this._items = items;
}
/**
* Children items count
* @type {number}
*/
get childrenCount() {
return this._items.length;
}
/**
* Children items
* @type {LayerTreeItemConfig[]}
*/
get children() {
return [...this._items];
}
/**
* Iterate through children items
* @generator
* @yields {LayerTreeItemConfig} The next child item
*/
*getChildren() {
for (const item of this._items) {
yield item;
}
}
/**
* Find layer names
* @returns {string[]} The layer names of all the tree layer
*/
findTreeLayerConfigNames() {
let names = []
for(const item of this.getChildren()) {
if (item instanceof LayerTreeLayerConfig) {
names.push(item.name);
} else if (item instanceof LayerTreeGroupConfig) {
names = names.concat(item.findTreeLayerConfigNames());
}
}
return names;
}
/**
* Find layer items
* @returns {LayerTreeLayerConfig[]} All the tree layer layers config
*/
findTreeLayerConfigs() {
let items = []
for(const item of this.getChildren()) {
if (item instanceof LayerTreeLayerConfig) {
items.push(item);
} else if (item instanceof LayerTreeGroupConfig) {
items = items.concat(item.findTreeLayerConfigs());
}
}
return items;
}
}
/**
* Function to build layer tree items config based on WMS capabilities
* @function
* @param {object} wmsCapaLayerGroup - the wms layer capabilities
* @param {LayersConfig} layersCfg - the lizmap layers config instance
* @param {number} level - the wms layer level
* @returns {LayerTreeItemConfig[]} the layer tree items of the wms layer
*/
function buildLayerTreeGroupConfigItems(wmsCapaLayerGroup, layersCfg, level) {
let items = [];
if (!wmsCapaLayerGroup.hasOwnProperty('Layer')) {
return items;
}
for(const wmsCapaLayer of wmsCapaLayerGroup.Layer) {
const wmsName = wmsCapaLayer.Name;
const cfg = layersCfg.getLayerConfigByWmsName(wmsName);
if (cfg == null) {
console.log('The WMS layer name `'+ wmsName +'` is unknown!');
continue;
}
if (wmsCapaLayer.hasOwnProperty('Layer') && wmsCapaLayer.Layer.length != 0) {
const groupItems = buildLayerTreeGroupConfigItems(wmsCapaLayer, layersCfg, level+1);
items.push(new LayerTreeGroupConfig(cfg.name, level+1, groupItems, wmsCapaLayer, cfg));
} else {
// avoid to add the baseLayers group to the map if it doesn't contain any layer.
if(wmsName.toLowerCase() != 'baselayers') {
items.push(new LayerTreeLayerConfig(cfg.name, level+1, wmsCapaLayer, cfg));
}
}
}
return items;
}
/**
* Function to build the root layer tree config based on WMS capabilities
* @function
* @param {object} wmsCapaLayerRoot - the wms root layer capabilities
* @param {LayersConfig} layersCfg - the lizmap layers config instance
* @returns {LayerTreeGroupConfig} The root layer tree config based on WMS capabilities
*/
export function buildLayerTreeConfig(wmsCapaLayerRoot, layersCfg) {
let items = buildLayerTreeGroupConfigItems(wmsCapaLayerRoot, layersCfg, 0);
return new LayerTreeGroupConfig('root', 0, items, wmsCapaLayerRoot);
}