import { isEqual } from "lodash";
import { ServiceLayer } from "../ServiceLayer/ServiceLayer.js";
import { colorScaleToWmsParams, copyColorScale } from "../../../ui/color/Utils.js";
import { WMSLayerProxyLeaflet } from "./WMSLayer.proxy.leaflet.js";
import { FeatureInfoHelper } from "../../utils/FeatureInfoHelper.js";
import { WMSLayerProxy } from "./WMSLayer.proxy.js";
import { POPUP_NETWORK_ERROR, POPUP_PARSING_ERROR } from "../../utils/messageTemplates.js";
import { WMSLayerSchema } from "./WMSLayer.schema.js";
export { WMSLayer }
/**
* WMS Layer Implementation
*
* @author rhess <robin.hess@awi.de>
* @author sjaswal <shahzeib.jaswal@awi.de>
* @memberof vef.map.layer
*/
class WMSLayer extends ServiceLayer {
/**
* Set all properties for the layer based on the options object.
*
* @param {object} config
* @param {object} cache
* @param {string} id
*/
constructor(config, cache, id) {
// calling parent constructor
super(config, cache, id);
// init default values and define getters and setters
this.setSchema_(WMSLayerSchema.getSchema());
// internal properties
this.zIndex_ = 0;
this.filterParams_ = {};
this.featureInfoHelper_ = new FeatureInfoHelper();
this.activeLayer_ = new WMSLayerProxy();
// framework specific map implementations
this.layerProxies_.leaflet = WMSLayerProxyLeaflet;
this.featureInfoHelper_.on("loading_error", () => this.addMessage(POPUP_NETWORK_ERROR));
this.featureInfoHelper_.on("parsing_error", () => this.addMessage(POPUP_PARSING_ERROR));
this.featureInfoHelper_.on("loaded", () => {
this.removeMessage(POPUP_NETWORK_ERROR);
this.removeMessage(POPUP_PARSING_ERROR);
});
}
/**
* create a map layer based on the service options
*
* @param {string} type e.g. "leaflet"
* @private
*/
createMapLayer_(type, options) {
return super.createMapLayer_(type, {
url: this.serviceUrl,
layers: this.getRequestName(),
version: this.serviceVersion,
attribution: this.attribution,
format: this.format
});
}
/**
* Helper method to update the mapLayer according to
* the current state. Might be necessary after switching the map type.
* calls internal setters to apply options to the active mapLayer
*
* @private
*/
updateMapLayer_() {
if (Number.isFinite(this.opacity)) this.activeLayer_.setOpacity(this.opacity);
if (Number.isFinite(this.zIndex_)) this.activeLayer_.setZIndex(this.zIndex_);
this.setProjection(this.crs_);
this.activeLayer_.setFormat(this.format);
// set the color scale or a style if defined
if (this.colorScale) {
this.setColorScale(this.colorScale, true);
} else if (this.queryParams.styles) {
this.setStyle(this.queryParams.styles, true);
} else if ((this.serviceSoftware == "rasdaman") && !this.getStyle() && (Object.keys(this.availableStyles).length > 0)) {
// apply first style for rasdaman, because the default style is not returned automatically be the service
this.setStyle(Object.keys(this.availableStyles)[0], true);
} else {
this.setStyle("", true);
}
// apply filter, static filter and static query parameters
this.applyFilter(true);
// reload was intentionally prevented in the setters to call it in the end
this.reload();
}
/**
* Returns the Legend Graphic for this layer.
* May depend on current color scale.
*
* @returns html <img>-tag (null or undefined if there is no legend graphic)
*/
getLegendGraphic() {
let url = this.legendUrl || `${this.serviceUrl}?REQUEST=GetLegendGraphic`;
const urlLowerCase = url.toLowerCase();
const parts = url.split("?");
if (urlLowerCase.includes("request=getlegendgraphic") && (parts.length == 2)) {
const params = parts[1].split("&");
const style = this.getStyle();
// set current style if the predefined style is empty.
// cannot be done with "addParam" because "style=" already exists as an empty property
for (let i = 0; i < params.length; ++i) {
if (params[i].toLowerCase() == "style=") {
params[i] += encodeURIComponent(style);
}
}
// only add param if it does not exist
const addParam = (key, value) => {
if (value && !urlLowerCase.includes(key.toLowerCase() + "=")) {
params.push(key + "=" + encodeURIComponent(value));
}
};
addParam("SERVICE", "WMS");
addParam("LAYER", this.getRequestName());
addParam("STYLE", style);
addParam("ENV", this.queryParams.env);
addParam("TRANSPARENT", "true");
addParam("FORMAT", this.format);
addParam("VERSION", this.serviceVersion);
addParam("LEGEND_OPTIONS", "forceLabels:on;fontSize:16;fontAntiAliasing:true;fontName:PT Sans");
url = parts[0] + ((params.length) ? ("?" + params.join("&")) : "");
}
const image = document.createElement("img");
image.src = url;
return image;
}
/**
* Switches the projection for the visualization
* of the layer. CRS hast to be included in availableCRS
*
* @param {string} crs target crs code
* @returns {boolean} returns true if successful
*/
setProjection(crs) {
const success = super.setProjection(crs);
if (success) {
this.activeLayer_.setLayers(this.getRequestName());
}
return success;
}
/**
* Set the style of the layer. Does not check if a style exists in availableStyles (non-listed styles are allowed)
*
* @param {string} styleName style name
* @param {boolean} noReload prevents reload if true
*/
setStyle(styleName, noReload) {
// remove "env" from colorscale settings
if (typeof this.queryParams.env == "string") delete this.queryParams.env;
if (styleName) {
this.queryParams.styles = styleName;
} else if ("styles" in this.queryParams) {
delete this.queryParams.styles;
}
this.colorScale = null;
if (!noReload) this.reload();
}
/**
* Get the active style of the layer.
*/
getStyle() {
return ("styles" in this.queryParams) ? this.queryParams.styles : "";
}
/**
* Set the colorscale for the layer
*
* @param colorScale
* @param {boolean} noReload prevents reload if true
*/
setColorScale(colorScale, noReload) {
this.colorScale = copyColorScale(colorScale);
const params = colorScaleToWmsParams(colorScale);
Object.assign(this.queryParams, params);
if (!noReload) this.reload();
}
/**
* Get the active colorscale for the layer
*/
getColorScale() {
return (this.colorScale) ? copyColorScale(this.colorScale) : null;
}
getQueryParams_() {
return Object.assign({}, this.queryParams, this.filterParams_);
}
/**
* Reload the Layer on the map
*/
reload() {
const params = this.getQueryParams_();
this.activeLayer_.setParams(params);
this.activeLayer_.reload();
}
/**
* Apply a filter on the layer. Uses WMS filter string
*
* @param {boolean} noReload prevents reload if true
*/
applyFilter(noReload) {
const params = (this.filter) ? this.createFilterParams_(this.filter) : {};
const changed = !isEqual(params, this.filterParams_);
this.filterParams_ = params;
if (!noReload && changed) this.reload();
}
/**
* Get feature info for a specific location.
* e.g. for displaying it in a map-popup
*
* @param {object} options getFeatureInfo params from map
* @returns {Promise} feature info promise
*/
getFeatureInfo(options) {
const helper = this.featureInfoHelper_;
return (this.serviceSoftware.toLowerCase() in helper)
? helper[this.serviceSoftware.toLowerCase()](this, options)
: helper.geoserver(this, options)
;
}
/**
* Set the z-index of a layer
* @param {number} index
*/
setZIndex(index) {
this.zIndex_ = index;
this.activeLayer_.setZIndex(index);
}
/**
* Get the bounding box as an object
*
* @returns {object} { min: {x, y}, max: {x, y}, crs: "CRS:84" }
*/
getBounds() {
return {
min: {
x: (this.minX < this.maxX) ? this.minX : this.maxX,
y: (this.minY < this.maxY) ? this.minY : this.maxY
},
max: {
x: (this.minX > this.maxX) ? this.minX : this.maxX,
y: (this.minY > this.maxY) ? this.minY : this.maxY
},
crs: "CRS:84"
}
}
setFormat(format) {
this.format = format;
this.activeLayer_.setFormat(format);
this.activeLayer_.reload();
}
}