import { mainLizmap } from './Globals.js'
import map from './map.js'
import { MapLayerLoadStatus, MapLayerState } from '../modules/state/MapLayer.js';
import ImageWMS from 'ol/source/ImageWMS.js';
import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer.js';
import TileWMS from 'ol/source/TileWMS.js';
import { BaseLayerState } from './state/BaseLayer.js';
import { ValidationError } from './Errors.js';
/**
* Class for manage the load/display selected layers as a single OpenLayers ImageWMS layer
* @class
*/
export default class SingleWMSLayer {
/**
* Initialize the ImageWMS layer
* @param {map} mainMapInstance the main map instance
*/
//constructor(singleWMSLayerList) {
constructor(mainMapInstance) {
if (!mainLizmap.state.map.singleWMSLayer || !mainMapInstance || !(mainMapInstance instanceof map) || mainMapInstance.statesSingleWMSLayers.size == 0) {
throw new ValidationError('The Configuration is not valid, could not load the map as single WMS Layer');
}
/**
* the map instance
* @type {map}
*/
this._mainMapInstance = mainMapInstance;
/**
* all the layers that should be inluded in the single ImageWMS. Contains layers names with their states sorted by layerOrder (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
* @type {Map<string,MapLayerState|BaseLayerState>}
*/
this._singleWMSLayerList = mainMapInstance.statesSingleWMSLayers;
/**
* list of base layers names
* @type {String[]}
*/
this._baseLayers = [];
/**
* list of all map layers names
* @type {String[]}
*/
this._mapLayers = [];
/**
* list of layers names on the current single layer displayed on map
* @type {String[]}
*/
this._layersName = [];
/**
* list of layers wms names on the current single layer displayed on map
* @type {String[]}
*/
this._layersWmsName = [];
/**
* list of layers styles on the current single layer displayed on map
* @type {String[]}
*/
this._layerStyles = [];
/**
* list of selection token parameter on the current single layer displayed on map
* @type {String[]}
*/
this._selectionTokens = [];
/**
* list of filter token parameter on the current single layer displayed on map
* @type {String[]}
*/
this._filterTokens = [];
/**
* list of legendOn parameter on the current single layer displayed on map
* @type {String[]}
*/
this._legendOn = [];
/**
* list of legendOff parameter on the current single layer displayed on map
* @type {String[]}
*/
this._legendOff = [];
/**
* the single WMS layer instance
* @type {?ImageLayer<ImageWMS>}
*/
this._layer = null;
/**
* timeout function to manage the image layer reload
* @type {?function}
*/
this._timeout = null
/**
* the WMS Ratio.
* @type {number}
*/
this._WMSRatio = mainMapInstance._WMSRatio;
/**
* the image format
* @type {string}
* @todo could the format be readed from project config too?
*/
this._format = "image/png";
/**
* minimun map scale
* @type {number}
*/
this._minScale = 1;
/**
* maximum map scale
* @type {number}
*/
this._maxScale = 1000000000000;
/**
* meters per units
* @type {number}
*/
this._metersPerUnit = mainMapInstance.getView().getProjection().getMetersPerUnit();
/**
* ordered layers
* @type {String[]}
*/
this._orderedLayers = [];
// construct base and map layers array
this._singleWMSLayerList.forEach((m,k) => {
if (m instanceof BaseLayerState) {
this._baseLayers.push(k);
} else if (m instanceof MapLayerState){
this._mapLayers.push(k);
}
});
this._orderedLayers = this._baseLayers.concat(this._mapLayers);
// initialize single Image layer
this.initializeLayer();
// register listener for map layers
mainLizmap.state.rootMapGroup.addListener(
evt => {
if (this._mapLayers.includes(evt.name)) {
// the layer is included in the single WMS request
// wait a bit in order to reduce the amount of requests
// e.g. when user turn on/off layers quickly
clearTimeout(this._timeout);
this._timeout = setTimeout(()=>{
this.updateMap();
},600)
}
},
['layer.visibility.changed','group.visibility.changed','layer.symbol.checked.changed', 'layer.style.changed', 'layer.selection.token.changed', 'layer.filter.token.changed']
);
// register listener for base layers
mainLizmap.state.baseLayers.addListener(
() => {
this.updateMap();
},
['baselayers.selection.changed']
);
}
/**
* @type {ImageLayer<ImageWMS>}
*
*/
get layer(){
return this._layer;
}
/**
* creates the layer instance and get the startup single image
* @memberof SingleWMSLayer
*/
initializeLayer(){
let minResolution = undefined; //Utils.getResolutionFromScale(this._minScale, this._metersPerUnit);
let maxResolution = undefined; //Utils.getResolutionFromScale(this._maxScale, this._metersPerUnit);
if(this._mainMapInstance.useTileWms){
this._layer = new TileLayer({
// extent: extent,
minResolution: minResolution,
maxResolution: maxResolution,
source: new TileWMS({
url: mainLizmap.serviceURL,
serverType: 'qgis',
tileGrid: this._mainMapInstance.customTileGrid,
params: {
LAYERS: null,
FORMAT: this._format,
STYLES: null,
DPI: 96,
TILED: 'true'
},
wrapX: false, // do not reused across the 180° meridian.
hidpi: this._mainMapInstance.hidpi, // pixelRatio is used in useTileWms and customTileGrid definition
})
});
} else {
this._layer = new ImageLayer({
minResolution: minResolution,
maxResolution: maxResolution,
source: new ImageWMS({
url: mainLizmap.serviceURL,
serverType: 'qgis',
ratio: this._WMSRatio,
hidpi: this._mainMapInstance.hidpi,
params: {
LAYERS: null,
FORMAT: this._format,
STYLES: null,
DPI: 96
}
})
});
// Force no cache w/ Firefox
if(navigator.userAgent.includes("Firefox")){
this._layer.getSource().setImageLoadFunction((image, src) => {
(image.getImage()).src = src + '&ts=' + Date.now();
});
}
}
this._layer.setVisible(false);
this._layer.setProperties({
name: "singleWMSLayer"
});
// put the layer on top of the base layers
this._layer.setZIndex(0);
// manage the spinners
this._layer.getSource().on('imageloadstart', () => {
for (const name of this._layersName) {
//add spinners on visible layers
const mapLayer = mainLizmap.state.rootMapGroup.getMapLayerByName(name);
mapLayer.loadStatus = MapLayerLoadStatus.Loading;
}
});
this._layer.getSource().on('imageloadend', () => {
//remove spinners
for (const name of this._mapLayers) {
const mapLayer = mainLizmap.state.rootMapGroup.getMapLayerByName(name);
mapLayer.loadStatus = MapLayerLoadStatus.Ready;
}
});
this._layer.getSource().on('imageloaderror', () => {
for (const name of this._mapLayers) {
const mapLayer = mainLizmap.state.rootMapGroup.getMapLayerByName(name);
mapLayer.loadStatus = MapLayerLoadStatus.Error;
}
});
// get the first image to display
this.updateMap();
}
/**
* update layer content
* @memberof SingleWMSLayer
*/
updateMap(){
this.prepareWMSParams()
const wmsParams = this._layer.getSource().getParams();
let param = {
LAYERS: this._layersWmsName.join(","),
FORMAT: this._format,
STYLES: this._layerStyles.join(","),
DPI: 96
}
if (this._selectionTokens.join("").split("").length > 0)
param["SELECTIONTOKEN"] = this._selectionTokens.join(";");
if (this._filterTokens.join("").split("").length > 0)
param["FILTERTOKEN"] = this._filterTokens.join(";");
if (this._legendOn.join("").split("").length > 0)
param["LEGEND_ON"] = this._legendOn.join(";");
if (this._legendOff.join("").split("").length > 0)
param["LEGEND_OFF"] = this._legendOff.join(";");
// updateParams merges the object, need to manually remove object keys
// that aren't in the current param object
for (const key of Object.keys(wmsParams)) {
if(!Object.hasOwn(param, key)){
delete wmsParams[key];
}
}
Object.assign(wmsParams, param);
// update the map
if (this._layersWmsName.length == 0) {
// don't want to perform WMS request if the layer list is empty
this._layer.setVisible(false)
} else {
this._layer.getSource().updateParams(wmsParams);
this._layer.setVisible(true)
}
}
/**
* update class properties to construct the wms parameters used to get the single image
* @memberof SingleWMSLayer
*/
prepareWMSParams(){
const baseLayersState = mainLizmap.state.baseLayers;
this._layersName = [];
this._layersWmsName = [];
this._layerStyles = [];
this._selectionTokens =[];
this._filterTokens = [];
this._legendOn = [];
this._legendOff = [];
this._orderedLayers.forEach((layerName) => {
//since _orderedLayers property respect the layerOrder, if there is a baselayer in the list, then it will be put
//at the bottom of the image
const currentLayerState = this._singleWMSLayerList.get(layerName);
// detect baseLayer, if any
if (currentLayerState instanceof BaseLayerState) {
if(this._baseLayers.includes(baseLayersState.selectedBaseLayerName)){
// get item state
const selectedBaseLayerState = baseLayersState.selectedBaseLayer.itemState;
this._layersWmsName.push(selectedBaseLayerState.wmsName);
this._layerStyles.push(selectedBaseLayerState.wmsSelectedStyleName || "");
}
} else if (currentLayerState instanceof MapLayerState){
// get item visibility
if(currentLayerState.visibility) {
this._layersName.push(currentLayerState.name);
this._layersWmsName.push(currentLayerState.wmsName);
this._layerStyles.push(currentLayerState.wmsSelectedStyleName || "");
const wmsParam = currentLayerState.wmsParameters;
if (wmsParam){
if(wmsParam.SELECTIONTOKEN)
this._selectionTokens.push(wmsParam.SELECTIONTOKEN);
if(wmsParam.FILTERTOKEN)
this._filterTokens.push(wmsParam.FILTERTOKEN);
if(wmsParam.LEGEND_ON)
this._legendOn.push(wmsParam.LEGEND_ON);
if(wmsParam.LEGEND_OFF)
this._legendOff.push(wmsParam.LEGEND_OFF);
}
}
}
})
}
}