import { EventObject } from "../../events/EventObject.js";
import { PopupPagination } from "./PopupPagination.js";
import { PopupHeader } from "./PopupHeader.js";
import { PopupRenderer } from "./PopupRenderer.js";
import { GeoJSONLayer } from "../layer/GeoJSONLayer/GeoJSONLayer.js";
import { reprojectGeojson } from "../utils/reprojectGeojson.js";
import "./MapPopup.css";
import { EventManager } from "../../events/EventManager.js";
export { MapPopup }
/**
* Class for getting popup data and creating popups on Map
*
* @author sjaswal <shahzeib.jaswal@awi.de>
* @author rkoppe <roland.koppe@awi.de>
* @author rhess <robin.hess@awi.de>
*
* @memberof vef.map.popup
*/
class MapPopup extends EventObject {
/**
* @param {Map} map
*/
constructor(map, options) {
// set available events
super({
"show_popup": [],
"backToSidebar": []
});
this.map_ = map;
this.options_ = Object.assign({
showMore: true
}, options || {});
this.pagination_ = new PopupPagination();
this.header_ = new PopupHeader();
this.promises_ = [];
this.selectedFeature = new GeoJSONLayer({
interactive: false,
opacity: 1,
opacityRatio: 0,
style: {
color: "#FFFFFF",
weight: 4,
radius: 7
}
});
this.initHeightTesting_();
}
/**
* Method that requests the featureinfo and displays it in a popup
*
* @param {Layer[]} layers
* @param {object} bbox
*/
requestPopup(layers, lat, lng) {
const options = this.map_.getFeatureInfoOptions(lat, lng);
const promises = [];
for (let i = 0; i < layers.length; ++i) {
if (layers[i].getFeatureInfo) promises.push(layers[i].getFeatureInfo(options));
}
this.promises_ = promises;
// display placeholder
this.map_.showPopup(lat, lng, `
<div class="vef-map-popup-placeholder">
<svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" style="margin: auto; shape-rendering: auto;" width="22px" height="22px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="32" stroke-width="8" stroke="var(--primary-color)" stroke-dasharray="50.26548245743669 50.26548245743669" fill="none" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" keyTimes="0;1" values="0 50 50;360 50 50"></animateTransform>
</circle>
</svg>
</div>
`);
// wait until all layers have responded
Promise.all(promises)
.then(responses => {
// don't render an old request that responded later
if (this.promises_ != promises) return;
// wait until the renderer has responded
const rendererPromises = [];
for (let i = 0; i < responses.length; ++i) {
rendererPromises.push(PopupRenderer.render(responses[i].data, responses[i].data, responses[i].layer, "popup"));
}
Promise.all(rendererPromises).then(content => {
const popupWrapper = this.buildPopup_(content);
this.map_.showPopup(lat, lng, popupWrapper);
});
});
}
initHeightTesting_() {
let div = this.map_.getElement().querySelector(".popup-content-height-test")
if (!div) {
div = document.createElement("div");
div.classList.add("popup-content-height-test", "vef-map-popup-content");
this.map_.getElement().appendChild(div);
}
this.heightTestContainer_ = div;
}
/**
* Calculate height of an element by adding it to an invisible container
* and checking the offsetHeight
*
* @param {HTMLElement} element
* @returns {number} height
*/
testHeight_(element) {
element.style.display = "block";
this.heightTestContainer_.appendChild(element);
const height = element.offsetHeight;
element.style.display = "none";
element.remove();
return height;
}
buildPopup_(responses) {
const items = [];
let itemIndex = 0;
this.header_.reset();
// popup content wrapper element
const $wrapper = document.createElement('div');
const $content = document.createElement('div');
$content.classList.add("vef-map-popup-content");
let minHeight = 0;
// prepare markup for features
for (let i = 0; i < responses.length; ++i) {
if (!responses[i]) continue;
for (let j = 0; j < responses[i].length; ++j) {
const content = responses[i][j];
const item = {
popup: document.createElement('div'),
sidebar: null,
content: content
};
item.popup.style.display = 'none';
const contentInner = document.createElement("div");
contentInner.classList.add("vef-map-popup-content-inner");
if (content.html instanceof HTMLElement) {
contentInner.appendChild(content.html);
} else {
contentInner.insertAdjacentHTML("beforeend", content.html);
}
// add show more link
if (this.options_.showMore && (content.feature || content.response)) {
const a = document.createElement("a");
a.classList.add("popup-link");
a.href = "#";
a.innerHTML = "<i class='fas fa-eye'></i> show more";
a.addEventListener("click", e => {
e.preventDefault();
if (item.onShowMore) item.onShowMore(item);
});
contentInner.appendChild(a);
}
// event button group
const buttonGroup = document.createElement("div");
buttonGroup.classList.add("vef-map-popup-button-group");
// create buttons for the events
const events = content.layer.getPopupEvents();
for (let k = 0; k < events.length; ++k) {
const a = document.createElement("a");
a.classList.add("popup-button");
a.href = "#";
a.style.width = events[k].width || "100%";
a.innerHTML = events[k].title;
if (events[k].toggleMode && events[k].isEnabled()) {
a.classList.add("active");
}
a.addEventListener("click", e => {
e.preventDefault();
events[k].callback(item);
if (events[k].toggleMode) {
if (a.classList.contains("active")) {
a.classList.remove("active");
} else {
a.classList.add("active");
}
}
});
buttonGroup.appendChild(a);
}
this.header_.addLayer(content.layer);
item.popup.appendChild(contentInner);
item.popup.appendChild(buttonGroup);
const height = this.testHeight_(item.popup);
if (height > minHeight) minHeight = height;
$content.appendChild(item.popup);
items.push(item);
}
}
$content.style.minHeight = minHeight + "px";
if (items.length == 0) {
// set popup placeholder for empty content
$wrapper.innerHTML = `
<div class="vef-map-popup-placeholder">
<p>
No data to display!
</p>
</div>
`;
return $wrapper;
}
this.header_.initDropdown();
$wrapper.appendChild(this.header_.getElement());
$wrapper.appendChild($content);
// event listener for handling prev/next navigation
const hideAndShow = (index) => {
items[itemIndex].popup.style.display = 'none';
itemIndex = index;
const item = items[itemIndex];
const content = item.content;
item.popup.style.display = 'block';
this.header_.setLayer(content.layer, content.title);
// attach the sidebar content to the item and fire the "show_popup" event
if (content.feature || content.response) {
PopupRenderer.render(content.feature || content.response, content.response, content.layer, 'sidebar')
.then(sidebar => {
item.sidebar = sidebar[0].html;
if (content.feature) {
this.selectedFeature.setData({
type: "Feature",
geometry: (content?.response?.crs?.properties?.name)
? reprojectGeojson(content?.response?.crs?.properties?.name, "EPSG:4326", content.feature.geometry)
: content.feature.geometry,
properties: {}
});
} else {
this.selectedFeature.setData(null);
}
this.fire("show_popup", item);
EventManager.fire("backToSidebar")
});
} else {
this.selectedFeature.setData(null);
}
}
// show content of the first entry
hideAndShow(0);
if (items.length > 1) {
this.pagination_.off("change");
this.pagination_.setPageCount(items.length);
this.pagination_.setPage(1);
this.pagination_.on("change", page => hideAndShow(page - 1));
$wrapper.appendChild(this.pagination_.getElement());
this.header_.on("show_index", index => this.pagination_.setPage(index + 1));
}
return $wrapper;
}
}