Source: map/layer/OpusLayer/OpusLayer.js

import { Layer } from "../Layer/Layer.js";
import { OpusLayerProxyLeaflet } from "./OpusLayer.proxy.leaflet.js";
import { GeoJSONLayerProxy } from "../GeoJSONLayer/GeoJSONLayer.proxy.js";
import { OpusLayerSchema } from "./OpusLayer.schema.js";
import "./OpusLayer.css";

/**
 * Opus Layer Implementation
 * 
 * @author rhess <robin.hess@awi.de>
 * 
 * @memberof vef.map.layer
 */
class OpusLayer extends Layer {

    /**
     * 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);

        this.setSchema_(OpusLayerSchema.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;

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

    /**
     * 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_() {
        if (!this.recorders_) {
            this.loadRecorders_();
        } else {
            this.applyRecorders_();
        }
    }

    /**
     * load the geojson content of the layer and insert it
     * 
     * @private
     */
    loadRecorders_() {
        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) {
                    this.recorders_ = JSON.parse(xhttp.responseText);
                    this.applyRecorders_();
                }
                this.activeRequest_ = null;
                this.fire("layer_loaded");
            }
        };
        xhttp.open("GET", this.url + "/recorder-positions", true);
        xhttp.send();

        this.activeRequest_ = xhttp;
    }

    /**
     * Internal method to apply the current geojson to the active mapLayer
     * 
     * @private
     */
    applyRecorders_() {
        let recorders = [...this.recorders_];

        if (this.filter) {
            for (let i = 0; i < this.filter.length; ++i) {
                const filter = this.filter[i];

                if (filter.values.length == 0) continue;
                for (let j = recorders.length - 1; j >= 0; --j) {
                    const recorder = recorders[j];
                    const column = filter.column;
                    const values = filter.values[0];

                    let matched = true;

                    // Time column name checks are ignored for now
                    if (filter.type.toLowerCase() == "time") {
                        const start = new Date(recorder.analysis_start_timestamp).getTime();
                        const end = new Date(recorder.analysis_end_timestamp).getTime();
                        const d1 = new Date(values[1]).getTime();

                        matched = false;
                        switch (values[0].toLowerCase()) {
                            case "bt":
                                const d2 = new Date(values[2]).getTime();
                                if (((d1 <= start) || (d1 <= end)) && ((d1 >= start) || (d2 >= end))) matched = true;
                                break;
                            case "eq":
                            case "lt":
                                if ((d1 >= start) || (d1 >= end)) matched = true;
                                break;
                            case "gt":
                                if ((d1 <= start) || (d1 <= end)) matched = true;
                                break;
                        }
                    }

                    if (matched && (column in recorder)) {
                        if (
                            (values[0].toLowerCase() == "like") &&
                            !(recorder[column].toString().toLowerCase().includes(values[1].toString().trim().toLowerCase()))
                        ) {
                            matched = false;
                        } else if (
                            (values[0].toLowerCase() == "eq") &&
                            !(recorder[column].toString().toLowerCase() == values[1].toString().trim().toLowerCase())
                        ) {
                            matched = false;
                        }
                    }

                    if (!matched) recorders.splice(j, 1)
                }
            }
        }

        // use a timeout to wait until the projection has really changed
        setTimeout(() => this.activeLayer_.setData(recorders), 0);
    }

    /**
     * Apply a filter on the layer
     */
    applyFilter() {
        // only apply filter if the layer is active
        if (this.active && this.filter && this.recorders_) this.applyRecorders_();
    }

    fetchRecorderMetadata_(feature) {
        return new Promise((resolve) => {
            if ("recorder" in feature.properties) {
                resolve(feature);
            } else {
                const xhttp = new XMLHttpRequest();
                xhttp.onreadystatechange = () => {
                    if (xhttp.readyState == 4) {
                        if (xhttp.status == 200) {
                            const recorder = JSON.parse(xhttp.responseText);
                            Object.assign(feature.properties, recorder);
                        } else {
                            console.error("could not resolve recorder", feature)
                        }
                        // resolve, even if it fails
                        resolve(feature);
                    }
                };
                const r = feature.properties;
                xhttp.open("GET", this.url + `/recorder-metadata/${r.recorderDatasetName}`, true);
                xhttp.send();
            }
        })
    }

    /**
     * Get Info of the last clicked features
     * @returns {Promise} feature info
     */
    getFeatureInfo() {
        return new Promise(resolve => {
            const promises = [];
            this.activeLayer_.getClickedFeatures().data.features.forEach(feature => {
                promises.push(this.fetchRecorderMetadata_(feature));
            });

            Promise.all(promises).then(features => {
                resolve({
                    layer: this,
                    data: {
                        type: "FeatureCollection",
                        features: features
                    }
                });
            });
        });
    }

    /**
     * Set the transparency between 0 and 1
     * @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();
    }

    /**
     * Print the Metadata from this layer as html.
     */
    printMetadata() {
        return super.printMetadata({
            abstract: this.abstract,
            title: this.title,
            type: this.type
        })
    }

}

export { OpusLayer };