Source: map/layer/WFSLayer/WFSLayer.js

import { L } from "../../leaflet/index.js";

import { ServiceLayer } from "../ServiceLayer/ServiceLayer.js";
import { WFSLayerSchema } from "./WFSLayer.schema.js";
import { GeoJSONLayerProxy } from "../GeoJSONLayer/GeoJSONLayer.proxy.js";
import { GeoJSONLayerProxyLeaflet } from "../GeoJSONLayer/GeoJSONLayer.proxy.leaflet.js";
import { NETWORK_LOADING_ERROR } from "../../utils/messageTemplates.js";

/**
 * WFS Layer Implementation
 * 
 * @author rhess <robin.hess@awi.de>
 * @author sjaswal <shahzeib.jaswal@awi.de>
 * 
 * @memberof vef.map.layer
 */
class WFSLayer 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_(WFSLayerSchema.getSchema());

        // ignore availableCrs and always use the default value
        if ("availableCrs" in this.config) delete this.config.availableCrs;
        if ("availableCrs" in this.cache) delete this.cache.availableCrs;

        // currently active geoJSON response
        this.geoJSON = null;

        // current url and requests for the data
        this.activeUrl_ = null;
        this.activeRequest_ = null;

        // framework specific map implementations
        this.layerProxies_.leaflet = GeoJSONLayerProxyLeaflet;
        this.activeLayer_ = new GeoJSONLayerProxy();
        this.previousActiveLayer_ = null;
    }

    /**
     * Creates URL for getting wfs layer
     */
    createUrl_() {
        const url = this.serviceUrl;
        const software = this.serviceSoftware.toLowerCase();

        let params = {
            service: this.type,
            version: (software == "esri") ? '2.0.0' : this.serviceVersion,
            request: 'GetFeature',
            typeName: this.getRequestName(),
            outputFormat: (software == "esri") ? 'GEOJSON' : 'application/json',
            SrsName: 'EPSG:4326'
        };

        // apply the filter
        if (this.filter) {
            const filterParams = this.createFilterParams_(this.filter);
            Object.assign(params, filterParams, this.queryParams);
        }
        return url + L.Util.getParamString(params);
    }

    /**
     * fix invalid GeoJson as a workaround for invalid data from the WFS
     * 
     * @private
     */
    fixGeoJson_(text) {
        // Workaround for ESRI LineString syntax in 10.7
        // Also remove features with coordinates as nan
        // by replacing nan with null and then removing them in the next step
        // ToDo: Remove after updating to 10.8
        if (this.serviceSoftware.toLowerCase() == "esri") {
            text = text.replaceAll('"LineString"', '"MultiLineString"');
            text = text.replace(/\bnan\b/g, null);

            let parsedJson = JSON.parse(text);

            for (let i = parsedJson.features.length - 1; i >= 0; i--) {
                if (parsedJson.features[i].geometry.coordinates.includes(null)) {
                    parsedJson.features.splice(i, 1);
                }
            }
            return parsedJson;
        } else return JSON.parse(text);
    }

    /**
     * create a map layer based on the service options
     * 
     * @param {string} type e.g. "leaflet"
     * @private
     */
    createMapLayer_(type, options) {
        const proxy = super.createMapLayer_(type, this);
        if (proxy) {
            proxy.on("layerproxy_click", (e) => this.fire("layer_click", e));
            proxy.on("layerproxy_mouseover", (e) => this.fire("layer_mouseover", e));
            proxy.on("layerproxy_mouseout", (e) => this.fire("layer_mouseout", e));
        }
        return proxy;
    }

    /**
     * Helper method to update the mapLayer according to
     * the current state. Might be necessary after switching the map type
     * 
     * @private
     */
    updateMapLayer_(type) {
        const url = this.createUrl_();

        if (this.activeUrl_ != url) {
            this.loadGeojson_(url);
        } else if (this.activeLayer_ !== this.previousActiveLayer_) {
            this.activeLayer_.setData(this.geoJSON);
        }

        this.previousActiveLayer_ = this.activeLayer_;
    }

    /**
     * load the geojson content of the layer and insert it
     * 
     * @private
     */
    loadGeojson_(url) {
        if (this.activeRequest_) {
            this.activeRequest_.abort();
            this.activeRequest_ = null;
            this.fire("layer_loaded");
        }

        this.fire("layer_loading");

        const xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = () => {
            if (xhttp.readyState == 4) {
                if (xhttp.status == 200) {
                    const json = this.fixGeoJson_(xhttp.responseText)
                    this.geoJSON = json;
                    this.activeUrl_ = url;
                    this.activeLayer_.setData(this.geoJSON);
                } else {
                    this.addMessage(NETWORK_LOADING_ERROR);
                }
                this.activeRequest_ = null;
                this.fire("layer_loaded");
            }
        };
        xhttp.open("GET", url, true);
        xhttp.send();

        this.activeRequest_ = xhttp;
    }

    /**
     * Apply a filter on the layer. Uses WMS filter string
     */
    applyFilter() {
        // only apply filter if the layer is active
        if (this.active) {
            const url = this.createUrl_();
            if (this.activeUrl_ != url) {
                this.loadGeojson_(url);
            }
        }
    }

    /**
     * Get Info of the last clicked features
     * @returns {Promise} feature info
     */
    getFeatureInfo() {
        return new Promise(resolve => {
            resolve(this.activeLayer_.getClickedFeatures());
        });
    }

    /**
     * Set the transparency between 0 and 1
     * @override
     * @param {number} opacity
     */
    setOpacity(opacity) {
        this.opacity = opacity;
        this.activeLayer_.updateStyle();
    }

    /**
     * Get the bounding box as an object
     * 
     * @returns {object} { min: {x, y}, max: {x, y}, crs: "CRS:84" }
     */
    getBounds() {
        const bounds = this.activeLayer_.getBounds();
        return (bounds) ? bounds : super.getBounds();
    }

}

export { WFSLayer };