Source: map/importer/binder/FileBinder.js

import { CsvLayer } from "../../layer/CsvLayer/CsvLayer.js";
import { GeoJSONLayer } from "../../layer/GeoJSONLayer/GeoJSONLayer.js";
import { Layer } from "../../layer/Layer/Layer.js";
import { Folder } from "../../layer/Folder/Folder.js";
import { createLayerTreeStructure } from "../utils.js";
import { OgcBinder } from "./OgcBinder.js";

/**
 * load file based layers (gejson, csv)
 * @memberof vef.map.importer.binder
 */
class FileBinder {

    /**
     * Creates layers from a QGis project
     * 
     * @param {string} text xml text
     * 
     * @returns {Promise} {structure, layers}
     */
    static loadQGisProject(text) {
        const structure = {};
        const layers = {}

        function getLayerCapabilities() {
            return new Promise(resolve => {
                const promises = [];
                const services = { "wms": {}, "wfs": {} };

                for (let id in layers) {
                    if (layers[id] instanceof Layer) continue;
                    if (!["wms", "wfs"].includes(layers[id].type)) {
                        layers[id] = new Folder({ title: layers[id].title });
                        continue;
                    }
                    if (!(layers[id].url in services[layers[id].type])) {
                        services[layers[id].type][layers[id].url] = "loading";
                        const promise = OgcBinder.getCapabilities(null, layers[id].url, layers[id].type);
                        promise.then(result => { services[layers[id].type][layers[id].url] = result; });
                        promises.push(promise);
                    }
                }

                Promise.all(promises).then(data => {
                    for (let id1 in layers) {
                        if (layers[id1] instanceof Layer) continue;
                        const service = services[layers[id1].type][layers[id1].url];
                        for (let index = 0; index < service.length; ++index) {
                            for (let id2 in service[index].layers) {
                                const layer = service[index].layers[id2];
                                const name = layers[id1].name.toString().split(",")[0].trim()
                                if (name == layer.name) {
                                    layer.uniqueId = id1;
                                    layer.name = layers[id1].name;
                                    layer.title = layers[id1].title;
                                    layer.active = layers[id1].active;
                                    if (layers[id1].styles) layer.queryParams.styles = layers[id1].styles;
                                    layers[id1] = layer;
                                    break;
                                }
                            }
                        }
                        if ((!layers[id1] instanceof Layer)) layers[id1] = new Folder(layers[id1]);
                    }
                    resolve()
                });
            })
        }

        function addLayer(layer, parent) {
            if (!(parent in structure)) structure[parent] = [];
            structure[parent].push(layer.uniqueId);
            layers[layer.uniqueId] = layer;
        }

        function parseLayerTreeGroup(group, parent) {
            const nodeName = group.nodeName.toLowerCase();
            switch (nodeName) {
                case "layer-tree-group":
                    const folder = new Folder({
                        title: group.getAttribute("name"),
                        active: (group.getAttribute("checked") == "Qt::Checked") ? true : false
                    });

                    addLayer(folder, parent);

                    for (let i = 0; i < group.childNodes.length; ++i) {
                        parseLayerTreeGroup(group.childNodes[i], folder.uniqueId);
                    }

                    break;
                case "layer-tree-layer":
                    const params = group.getAttribute("source").split("&").reduce((accumulator, entry) => {
                        const parts = entry.split("=");
                        if (parts[0] in accumulator) {
                            accumulator[parts[0]] += (parts.length > 1) ? ("," + parts[1]) : "";
                        } else {
                            accumulator[parts[0]] = (parts.length > 1) ? parts[1] : "";
                        }

                        return accumulator;
                    }, {});

                    const layer = {
                        uniqueId: group.getAttribute("id"),
                        type: group.getAttribute("providerKey").toLowerCase(),
                        title: group.getAttribute("name"),
                        active: (group.getAttribute("checked") == "Qt::Checked") ? true : false,
                        name: params.layers,
                        url: params.url,
                        styles: params.styles
                    };

                    if (layer) addLayer(layer, parent);

                    break;
            }
        }

        return new Promise(resolve => {
            try {
                const parser = new DOMParser();
                const xml = parser.parseFromString(text, "text/xml");
                const layerTree = xml.querySelector("qgis > layer-tree-group");

                for (let i = 0; i < layerTree.childNodes.length; ++i) {
                    parseLayerTreeGroup(layerTree.childNodes[i], "#");
                }
            } catch (error) {
                console.warn(error);
            }

            getLayerCapabilities().then(() => {
                resolve({ structure: structure, layers: layers });
            });

        })

    }

    /**
     * Creates a Layer from a GeoJson source that is
     * already parsed into a JavaScript object.
     * 
     * @param {object | string} json geojson object
     * @param {string} title  layer title (optional)
     * 
     * @returns {object} {structure, layers}
     */
    static loadGeoJsonLayer(json, title) {
        json = (typeof json == "string") ? JSON.parse(json) : json;

        if (json.features) {
            const layers = [];
            for (let i = 0; i < json.features.length; ++i) {
                const feature = json.features[i];
                layers.push(new GeoJSONLayer({
                    title: (feature && feature.properties && feature.properties.title) ? feature.properties.title : (title + " [" + i + "]"),
                    geoJSON: feature,
                    active: true
                }));
            }
            return createLayerTreeStructure(layers, title);
        } else {
            return createLayerTreeStructure([new GeoJSONLayer({
                title: (json && json.properties && json.properties.title) ? json.properties.title : title,
                geoJSON: json,
                active: true
            })]);
        }
    }

    /**
     * Creates a GeoJsonLayer from a DShip JSOn export
     * 
     * @param {object | string} json geojson object
     * @param {string} title  layer title (optional)
     * 
     * @returns {object} {structure, layers}
     */
    static loadDshipLayer(json, title) {
        json = (typeof json == "string") ? JSON.parse(json) : json;

        const GeoJson = {
            type: "Feature",
            properties: {},
            geometry: {
                type: "LineString",
                coordinates: []
            }
        };

        let iLat = -1;
        let iLon = -1;
        let iTime = -1;

        for (let i = 0; i < json.header.columns.length; ++i) {
            const col = json.header.columns[i];
            if (col.shortname.toLowerCase().includes("lat")) iLat = i;
            if (col.shortname.toLowerCase().includes("lon")) iLon = i;
            if (col.shortname.toLowerCase().includes("time")) iTime = i;
        }

        if ((iLat < 0) || (iLon < 0)) return null;

        for (let i = 0; i < json.data.length; ++i) {
            const data = json.data[i];
            if ((i == 0) && (iTime >= 0)) GeoJson.properties["Start Date"] = data[iTime];
            if ((i == (json.data.length - 1)) && (iTime >= 0)) GeoJson.properties["End Date"] = data[iTime];
            GeoJson.geometry.coordinates.push([data[iLon], data[iLat]]);
        }

        return FileBinder.loadGeoJsonLayer(GeoJson, title);
    }

    /**
     * @param {File | String} file file or URL
     * @returns {Promise}
     */
    static getFileContents(file) {
        return new Promise(resolve => {
            if (file instanceof File) {
                const reader = new FileReader();
                reader.onload = e => resolve(e.target.result);
                reader.readAsText(file);
            } else if (typeof file == "string") {
                fetch(file).then(res => res.text()).then(text => resolve(text));
            } else {
                resolve(null);
            }
        })
    }

    /**
     * Creates a Layer from csv or geojson.
     * Automatically tries to set the type based on the filename
     * 
     * @param {File | string} file File object or a path to a file
     * @param {object} options
     * @param {string} mode geojson or csv
     * 
     * @returns {MeasurementsLayer | MeasurementsLayer[]} layers
     */
    static loadFileLayer(file, options, mode) {
        return new Promise(resolve => {
            const name = (typeof file == "string") ? file : file.name;
            mode = mode || FileBinder.guessFileType(name);

            switch (mode) {
                case "csv":
                    CsvLayer.createLayers(file, options).then(data => resolve(data));
                    break;
                case "geojson":
                    FileBinder.getFileContents(file).then(text => {
                        resolve(FileBinder.loadGeoJsonLayer(text, name))
                    });
                    break;
                case "qgis":
                    FileBinder.getFileContents(file).then(text => {
                        FileBinder.loadQGisProject(text).then(layers => resolve(layers))
                    });
                    break;
                case "dship":
                    FileBinder.getFileContents(file).then(text => {
                        resolve(FileBinder.loadDshipLayer(text, name));
                    });
                    break;
                default:
                    resolve(null);
                    break;
            }
        });
    }

    /**
     * Guess file type based on the filename
     * @param {string} filename
     * @returns {string} type
     */
    static guessFileType(filename) {
        if (typeof filename != "string") return null;
        filename = filename.toLowerCase().trim();

        if (filename.endsWith(".tab") || filename.endsWith(".tsv") || filename.endsWith(".csv") || filename.endsWith("format=textfile")) {
            return "csv";
        }

        if (filename.endsWith(".dship.json")) {
            return "dship";
        }

        if (filename.endsWith(".geojson") || filename.endsWith(".json")) {
            return "geojson";
        }

        if (filename.endsWith(".qgs")) {
            return "qgis";
        }

        return null;
    }

}

export { FileBinder };