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 };