import { WMSLayer } from "./layer/WMSLayer/WMSLayer.js";
import { WFSLayer } from "./layer/WFSLayer/WFSLayer.js";
import { Layer } from "./layer/Layer/Layer.js";
import { GeoJSONLayer } from "./layer/GeoJSONLayer/GeoJSONLayer.js";
import { EventObject } from "../events/EventObject.js";
import { OpusLayer } from "./layer/OpusLayer/OpusLayer.js";
import { IndexBinder } from "./importer/binder/IndexBinder.js";
import { FilterLayer } from "./layer/FilterLayer/FilterLayer.js";
import { OgcBinder } from "./importer/binder/OgcBinder.js";
import { MeasurementsLayer } from "./layer/MeasurementsLayer/MeasurementsLayer.js";
import { loadLayer } from "./utils/loadLayer.js";
import { RELOAD_METADATA_ERROR } from "./utils/messageTemplates.js";
import { PromiseQueue } from "../media/data/PromiseQueue.js";
export { LayerManager };
/**
* LayerManager class for getting and handling layers
*
* @author sjaswal <shahzeib.jaswal@awi.de>
* @author awalter <andreas.walter@awi.de>
* @author rhess <robin.hess@awi.de>
*
* @memberof vef.map
*/
class LayerManager extends EventObject {
/**
* Creates an empty LayerManager instance to which Layers can be added
* using a maps-project configuration or manually using the
* method addLayer.
*/
constructor() {
super({
"layermanager_add_layer": [],
"layermanager_remove_layer": [],
"layermanager_filterlayer_change": [],
"layermanager_layer_mouseover": [],
"layermanager_layer_mouseout": [],
"layermanager_toggle_selection": []
});
this.layers_ = {};
this.filterTimeout_ = null;
this.reloadQueue_ = new PromiseQueue();
}
/**
* Add layer to the LayerManager using a syntax that is
* compatible to the Layer's constructor.
*
* @param {object[]} layers layer configs
* @param {object[]} cache cached layer configs
*
* @returns {Promise} resolved layers
*/
loadLayers(layers, cache) {
return new Promise((resolve) => {
this.resolveCatalogLayers_(layers).then(catalogLayers => {
const servicesToReload = {
"wms": [],
"wfs": []
};
for (let id in layers) {
let inCatalog = false;
let cachedLayer = (id in cache) ? cache[id] : {};
if (("catalogId" in layers[id]) && (layers[id].catalogId in catalogLayers)) {
cachedLayer = catalogLayers[layers[id].catalogId];
inCatalog = true;
}
const layer = loadLayer(layers[id], cachedLayer, id);
this.addLayer(layer);
// Collect layers that could not be updated from the catalog
if (("catalogId" in layers[id]) && !inCatalog && ["wms", "wfs"].includes(layer.type)) {
if (!servicesToReload[layer.type].includes(layer.serviceUrl)) {
servicesToReload[layer.type].push(layer.serviceUrl)
}
}
}
// reload service metadata of layers that cannot be loaded from the catalog
for (let type in servicesToReload) {
for (let url of servicesToReload[type]) {
this.reloadQueue_.enqueue(() => this.reloadServiceMetadata(url, type));
}
}
resolve();
});
});
}
/**
* Resolves catalog layers by their IDs.
*
* This function takes an object of layers, extracts the catalog IDs from the layers,
* and fetches the corresponding layers from the catalog using the IndexBinder.
*
* @param {Object} layers - An object containing layer information.
* @returns {Promise<Object>} A promise that resolves to an object containing the fetched layers.
*/
async resolveCatalogLayers_(layers) {
const catalogIds = [];
for (let id in layers) {
if ("catalogId" in layers[id]) {
catalogIds.push(layers[id].catalogId);
}
}
if (catalogIds.length == 0) {
return {};
} else {
try {
const layers = await new IndexBinder().fetchLayersByIds(catalogIds);
return layers;
} catch (e) {
console.warn(e);
return {};
}
}
}
/**
* Add an existing Layer to the LayerManager
*
* @param {Object} layer
*/
addLayer(layer) {
if (!(layer instanceof Layer)) return;
this.layers_[layer.uniqueId] = layer;
layer.on("layer_request_remove", () => this.fire("layermanager_remove_layer", layer));
if (layer instanceof FilterLayer) {
this.initFilterLayer_(layer);
} else if ((layer instanceof MeasurementsLayer) || (layer instanceof OpusLayer) || (layer instanceof GeoJSONLayer) || (layer instanceof WFSLayer)) {
layer.on("layer_mouseover", e => this.fire("layermanager_layer_mouseover", e));
layer.on("layer_mouseout", e => this.fire("layermanager_layer_mouseout", e));
}
this.fire("layermanager_add_layer", layer);
}
/**
* Method that returns a layer from LayerManager based on UniqueId provided
*
* @param {string} id
*/
getLayerById(id) {
if (this.layers_.hasOwnProperty(id)) {
return this.layers_[id];
}
}
/**
* Reloads the metadata of all layers from the given service identified by url and type
* @param {string} url
* @param {string} type
*/
reloadServiceMetadata(url, type) {
this.forEach(layer => {
if ((url == layer.serviceUrl) && (type == layer.type)) {
layer.fire("layer_loading", layer);
}
});
return OgcBinder.getCapabilities(null, url, type).then(data => {
this.forEach(layer => {
if ((url == layer.serviceUrl) && (type == layer.type)) {
for (let i = 0; i < data.length; ++i) {
for (let id in data[i].layers) {
if ((data[i].layers[id].name == layer.cache.name) || (data[i].layers[id].name == layer.name)) {
layer.cache = data[i].layers[id].cache;
if ("catalogId" in layer.cache) layer.config.catalogId = layer.cache.catalogId;
layer.removeMessage(RELOAD_METADATA_ERROR);
layer.fire("layer_update", layer);
}
}
}
}
});
}).catch(() => {
this.forEach(layer => {
if ((url == layer.serviceUrl) && (type == layer.type)) {
layer.addMessage(RELOAD_METADATA_ERROR);
}
});
}).finally(() => {
this.forEach(layer => {
if ((url == layer.serviceUrl) && (type == layer.type)) {
layer.fire("layer_loaded", layer);
}
});
});
}
/**
* Reloads the metadata of all layers
* @param {string} url
* @param {string} type
*/
reloadAllServiceMetadata() {
const reloaded = { wms: [], wfs: [] };
this.forEach(layer => {
if (layer instanceof WMSLayer || layer instanceof WFSLayer) {
const type = layer.type.toLowerCase();
if (!reloaded[type].includes(layer.serviceUrl)) {
reloaded[type].push(layer.serviceUrl);
this.reloadServiceMetadata(layer.serviceUrl, type);
}
}
});
}
/**
* Iterate over each layer inside the LayerManager
*
* @param {function} callback
*/
forEach(callback) {
for (let i in this.layers_) {
callback(this.layers_[i]);
}
}
/**
* Clear LayerManager and dispose layers
*/
dispose() {
this.forEach(l => l.dispose());
this.layers_ = {};
}
/**
* @private
*/
initFilterLayer_(layer) {
const applyFilter = () => {
if (this.filterTimeout_) clearTimeout(this.filterTimeout_);
this.filterTimeout_ = setTimeout(() => {
this.fire("layermanager_filterlayer_change", this);
this.filterTimeout_ = null;
}, 100);
};
layer.on("layer_select", applyFilter);
layer.on("layer_deselect", applyFilter);
}
/**
* Get selected FilterLayers
* @returns {object[]} filters
*/
getActiveFilter() {
const mapping = {};
const filters = [];
this.forEach(layer => {
if ((layer instanceof FilterLayer) && layer.active) {
if (!(layer.targetLayer in mapping)) mapping[layer.targetLayer] = {};
if (!(layer.column in mapping[layer.targetLayer])) {
const excluded = Object.keys(this.layers_);
const index = excluded.indexOf(layer.targetLayer);
if (index > -1) excluded.splice(index, 1);
const f = {
excludedLayers: excluded,
column: layer.column,
values: [layer.values]
};
mapping[layer.targetLayer][layer.column] = f;
filters.push(f);
} else {
const f = mapping[layer.targetLayer][layer.column];
f.values.push(layer.values);
}
}
})
return filters;
}
}