Source: map/layer/GeoJSONLayer/GeoJSONLayer.js

import { Layer } from "../Layer/Layer.js";
import { saveAsFile } from "../../../utils/utils.js";
import { GeoJSONLayerProxy } from "./GeoJSONLayer.proxy.js";
import { GeoJSONLayerProxyLeaflet } from "./GeoJSONLayer.proxy.leaflet.js";
import { EventManager } from "../../../events/EventManager.js";
import { GeoJSONLayerSchema } from './GeoJSONLayer.schema.js';

export { GeoJSONLayer }

/**
 * GeoJSON Layer Implementation
 * 
 * @author rhess <robin.hess@awi.de>
 * @memberof vef.map.layer
 */
class GeoJSONLayer 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_(GeoJSONLayerSchema.getSchema());

        // framework specific map implementations
        this.layerProxies_.leaflet = GeoJSONLayerProxyLeaflet;
        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_(type) {
        // init the event callbacks for the buttons in the popup
        if (this.editingEnabled && (this.popupEvents_.length == 0)) this.initPopupEvents_();

        if (this.activeLayer_ !== this.previousActiveLayer_) {
            this.activeLayer_.setData(this.geoJSON);
        }

        this.previousActiveLayer_ = this.activeLayer_;
    }

    setData(geoJson) {
        this.geoJSON = {
            type: "FeatureCollection",
            features: []
        };
        if (typeof geoJson == "string") {
            fetch(geoJson).then(response => response.json()).then(json => {
                this.geoJSON = json;
                this.activeLayer_.setData(this.geoJSON);
            })
        } else if (typeof geoJson == "object") {
            this.geoJSON = geoJson;
        } else {
            console.warn("Invalid or missing GeoJSON");
        }

        this.activeLayer_.setData(this.geoJSON);
    }

    /**
     * Set label of a feature
     * @param {object} feature 
     * @param {string} label 
     */
    setLabel(feature, label) {
        feature.properties.label = label;
        this.activeLayer_.updateTooltips(feature);
    }

    bringToFront() {
        this.activeLayer_.bringToFront();
    }


    bringToBack() {
        this.activeLayer_.bringToBack();
    }

    /**
     * Set color of a feature
     * @param {object} feature 
     * @param {string} color 
     */
    setColor(feature, color) {
        feature.properties.color = color;
        this.activeLayer_.updateStyle();
    }

    /** 
     * init the event callbacks for the buttons in the popup
     * @private
     */
    initPopupEvents_() {
        let editing = false;
        this.popupEvents_ = [
            {
                title: "<i class='fas fa-draw-polygon'></i><br/>edit shape",
                callback: e => {
                    if (editing) {
                        this.stopEditing_(e.content.feature);
                    } else {
                        this.startEditing_(e.content.feature);
                    }
                    editing = !editing;
                },
                toggleMode: true,
                isEnabled: () => { return editing },
                width: "33%"
            },
            {
                title: "<i class='fas fa-download'></i><br/>export",
                callback: e => this.exportGeometry("download"),
                width: "34%"
            },
            {
                title: "<i class='fas fa-trash'></i><br/>remove",
                callback: e => this.fire("layer_request_remove", this),
                width: "33%"
            }
        ];
    }

    /**
     * start editing for a vector based layer
     * @private
     */
    startEditing_(feature) {
        this.activeLayer_.startEditing(feature);
    }

    /**
     * stop editing for a vector based layer
     * @private
     */
    stopEditing_(feature) {
        this.activeLayer_.stopEditing(feature);
        this.geoJSON = this.activeLayer_.getGeoJson();
        if (!("properties" in this.geoJSON)) this.geoJSON.properties = {};
        if (!("title" in this.geoJSON.properties)) this.geoJSON.properties.title = this.title;
        this.activeLayer_.setData(this.geoJSON);
        EventManager.fire("close_map_popup", {});
    }

    /**
     * Trigger a file download containing the geometry as a geojson file
     * 
     * @param {string} mode supported values: "string", "object", "download" (default)
     */
    exportGeometry(mode) {
        // only export first feature, if only one feature exists
        let geoJSON = this.geoJSON;
        if (("features" in geoJSON) && (geoJSON.features.length == 1)) geoJSON = geoJSON.features[0];

        if (mode == "object") return this.geoJSON;

        const jsonString = JSON.stringify(this.geoJSON);
        if (mode == "string") return jsonString

        const blob = new Blob([jsonString], { type: "text/plain;charset=utf-8" });
        saveAsFile(blob, this.title + ".json");
    }

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