view client/src/store/map.js @ 2956:974122125a76

Let it be an error if closest points of DISMARs on axis are equal This might be the case e.g. if both distance marks are very far away from the available axis geometries. Instead of returning a point in such a case, which would likely be an unexpected result, raise an exception by means of STRICT.
author Tom Gottfried <tom@intevation.de>
date Mon, 08 Apr 2019 14:53:09 +0200
parents 1ac58e024942
children b74ebeb2bdc8
line wrap: on
line source

/* This is Free Software under GNU Affero General Public License v >= 3.0
 * without warranty, see README.md and license for details.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * License-Filename: LICENSES/AGPL-3.0.txt
 *
 * Copyright (C) 2018, 2019 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 *
 * Author(s):
 * * Bernhard Reiter <bernhard.reiter@intevation.de>
 * * Markus Kottländer <markus@intevation.de>
 * * Thomas Junk <thomas.junk@intevation.de>
 */

//import { HTTP } from "../lib/http";

import TileWMS from "ol/source/TileWMS.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import OSM from "ol/source/OSM";
import Draw from "ol/interaction/Draw.js";
import { Icon, Stroke, Style, Fill, Text, Circle } from "ol/style.js";
import VectorSource from "ol/source/Vector.js";
import Point from "ol/geom/Point.js";
import { bbox as bboxStrategy } from "ol/loadingstrategy";
import { HTTP } from "@/lib/http";
import { fromLonLat } from "ol/proj";
import { getLength, getArea } from "ol/sphere.js";
import { unByKey } from "ol/Observable";
import { getCenter } from "ol/extent";
import { transformExtent } from "ol/proj.js";
import bbox from "@turf/bbox";
import app from "@/main";

const LAYERS = {
  OPENSTREETMAP: "Open Streetmap",
  INLANDECDIS: "Inland ECDIS chart Danube",
  WATERWAYAREA: "Waterway Area",
  STRETCHES: "Stretches",
  FAIRWAYDIMENSIONSLOS1: "LOS 1 Fairway Dimensions",
  FAIRWAYDIMENSIONSLOS2: "LOS 2 Fairway Dimensions",
  FAIRWAYDIMENSIONSLOS3: "LOS 3 Fairway Dimensions",
  WATERWAYAXIS: "Waterway Axis",
  WATERWAYPROFILES: "Waterway Profiles",
  BOTTLENECKS: "Bottlenecks",
  BOTTLENECKSTATUS: "Critical Bottlenecks",
  BOTTLENECKISOLINE: "Bottleneck isolines",
  DISTANCEMARKS: "Distance marks",
  DISTANCEMARKSAXIS: "Distance marks, Axis",
  GAUGES: "Gauges",
  DRAWTOOL: "Draw Tool",
  CUTTOOL: "Cut Tool",
  DIFFERENCES: "Bottleneck Differences"
};

const moveMap = ({ view, extent, zoom, preventZoomOut }) => {
  const currentZoom = view.getZoom();
  zoom = zoom || currentZoom;
  view.fit(extent, {
    maxZoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
    duration: 700
  });
};

// initial state
const init = () => {
  return {
    openLayersMap: null,
    initialLoad: true,
    extent: {
      lat: 6155376,
      lon: 1819178,
      zoom: 11
    },
    identifyTool: null, // event binding (singleclick, dblclick)
    identifiedFeatures: [], // map features identified by clicking on the map
    currentMeasurement: null, // distance or area from line-/polygon-/cutTool
    lineTool: null, // open layers interaction object (Draw)
    polygonTool: null, // open layers interaction object (Draw)
    cutTool: null, // open layers interaction object (Draw)
    isolinesLegendImgDataURL: "",
    differencesLegendImgDataURL: "",
    layers: {
      [LAYERS.OPENSTREETMAP]: {
        data: new TileLayer({
          visible: true,
          source: new OSM()
        })
      },
      [LAYERS.INLANDECDIS]: {
        data: new TileLayer({
          visible: true,
          source: new TileWMS({
            preload: 1,
            url: "https://service.d4d-portal.info/wms/",
            crossOrigin: "anonymous",
            params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true }
          })
        })
      },
      [LAYERS.WATERWAYAREA]: {
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: new Style({
            stroke: new Stroke({
              color: "rgba(0, 102, 0, 1)",
              width: 2
            })
          })
        })
      },
      [LAYERS.STRETCHES]: {
        data: new VectorLayer({
          visible: false,
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: feature => {
            let style = new Style({
              stroke: new Stroke({
                color: "rgba(250, 200, 0, .8)",
                width: 2
              }),
              fill: new Fill({
                color: "rgba(250, 200, 10, .3)"
              })
            });
            if (feature.get("highlighted")) {
              style = new Style({
                stroke: new Stroke({
                  color: "rgba(250, 240, 10, .9)",
                  width: 5
                }),
                fill: new Fill({
                  color: "rgba(250, 240, 0, .7)"
                })
              });
            }

            return style;
          }
        })
      },
      [LAYERS.FAIRWAYDIMENSIONSLOS3]: {
        data: new VectorLayer({
          visible: true,
          source: new VectorSource(),
          style: function() {
            return [
              new Style({
                stroke: new Stroke({
                  color: "rgba(0, 0, 255, 1.0)",
                  width: 2
                }),
                fill: new Fill({
                  color: "rgba(255, 255, 255, 0.4)"
                })
              }),
              new Style({
                text: new Text({
                  font: 'bold 12px "Open Sans", "sans-serif"',
                  placement: "line",
                  fill: new Fill({
                    color: "black"
                  }),
                  text: "LOS: 3"
                  //, zIndex: 10
                })
              })
            ];
          }
        })
      },
      [LAYERS.FAIRWAYDIMENSIONSLOS2]: {
        data: new VectorLayer({
          visible: false,
          source: new VectorSource(),
          style: function() {
            return [
              new Style({
                stroke: new Stroke({
                  color: "rgba(0, 0, 255, 0.9)",
                  lineDash: [3, 6],
                  lineCap: "round",
                  width: 2
                }),
                fill: new Fill({
                  color: "rgba(240, 230, 0, 0.1)"
                })
              }),
              new Style({
                text: new Text({
                  font: 'bold 12px "Open Sans", "sans-serif"',
                  placement: "line",
                  fill: new Fill({
                    color: "black"
                  }),
                  text: "LOS: 2"
                  //, zIndex: 10
                })
              })
            ];
          }
        })
      },
      [LAYERS.FAIRWAYDIMENSIONSLOS1]: {
        data: new VectorLayer({
          visible: false,
          source: new VectorSource(),
          style: function() {
            return [
              new Style({
                stroke: new Stroke({
                  color: "rgba(0, 0, 255, 0.8)",
                  lineDash: [2, 4],
                  lineCap: "round",
                  width: 2
                }),
                fill: new Fill({
                  color: "rgba(240, 230, 0, 0.2)"
                })
              }),
              new Style({
                text: new Text({
                  font: 'bold 12px "Open Sans", "sans-serif"',
                  placement: "line",
                  fill: new Fill({
                    color: "black"
                  }),
                  text: "LOS: 1"
                  //, zIndex: 10
                })
              })
            ];
          }
        })
      },
      [LAYERS.WATERWAYAXIS]: {
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: new Style({
            stroke: new Stroke({
              color: "rgba(0, 0, 255, .5)",
              lineDash: [5, 5],
              width: 2
            })
          }),
          // TODO: Set layer in layertree active/inactive depending on
          // resolution.
          maxResolution: 5,
          minResolution: 0
        })
      },
      [LAYERS.WATERWAYPROFILES]: {
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: new Style({
            stroke: new Stroke({
              color: "rgba(0, 0, 255, .5)",
              lineDash: [5, 5],
              width: 2
            })
          }),
          maxResolution: 2.5,
          minResolution: 0
        })
      },
      [LAYERS.BOTTLENECKS]: {
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: function() {
            return new Style({
              stroke: new Stroke({
                color: "rgba(230, 230, 10, .8)",
                width: 4
              }),
              fill: new Fill({
                color: "rgba(230, 230, 10, .3)"
              })
            });
          }
        })
      },
      [LAYERS.BOTTLENECKISOLINE]: {
        data: new TileLayer({
          visible: false,
          source: new TileWMS({
            preload: 0,
            projection: "EPSG:3857",
            url: window.location.origin + "/api/internal/wms",
            params: {
              LAYERS: "sounding_results_contour_lines_geoserver",
              VERSION: "1.1.1",
              TILED: true
            },
            tileLoadFunction: function(tile, src) {
              // console.log("calling for", tile, src);
              HTTP.get(src, {
                headers: {
                  "X-Gemma-Auth": localStorage.getItem("token")
                },
                responseType: "blob"
              }).then(response => {
                tile.getImage().src = URL.createObjectURL(response.data);
              });
            } // TODO  tile.setState(TileState.ERROR);
          })
        })
      },
      [LAYERS.DIFFERENCES]: {
        data: new TileLayer({
          visible: false,
          source: new TileWMS({
            preload: 0,
            projection: "EPSG:4326",
            url: window.location.origin + "/api/internal/wms",
            params: {
              LAYERS: "sounding_differences",
              VERSION: "1.1.1",
              TILED: true
            },
            tileLoadFunction: function(tile, src) {
              // console.log("calling for", tile, src);
              HTTP.get(src, {
                headers: {
                  "X-Gemma-Auth": localStorage.getItem("token")
                },
                responseType: "blob"
              }).then(response => {
                tile.getImage().src = URL.createObjectURL(response.data);
              });
            } // TODO  tile.setState(TileState.ERROR);
          })
        })
      },
      [LAYERS.BOTTLENECKSTATUS]: {
        forLegendStyle: { point: true, resolution: 16 },
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: function(feature, resolution, isLegend) {
            let styles = [];
            if ((feature.get("fa_critical") && resolution > 15) || isLegend) {
              let bnCenter = getCenter(feature.getGeometry().getExtent());
              styles.push(
                new Style({
                  geometry: new Point(bnCenter),
                  image: new Icon({
                    src: require("@/assets/marker-bottleneck-critical.png"),
                    anchor: [0.5, 0.5],
                    scale: isLegend ? 0.5 : 1
                  })
                })
              );
            }
            if (feature.get("fa_critical") && !isLegend) {
              styles.push(
                new Style({
                  stroke: new Stroke({
                    color: "rgba(255, 0, 0, 1)",
                    width: 4
                  })
                })
              );
            }
            return styles;
          }
        })
      },
      [LAYERS.DISTANCEMARKS]: {
        forLegendStyle: { point: true, resolution: 8 },
        data: new VectorLayer({
          visible: false,
          source: new VectorSource({
            strategy: bboxStrategy
          })
        })
      },
      [LAYERS.DISTANCEMARKSAXIS]: {
        forLegendStyle: { point: true, resolution: 8 },
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: function(feature, resolution) {
            if (resolution < 10) {
              var s = new Style({
                image: new Circle({
                  radius: 5,
                  fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }),
                  stroke: new Stroke({ color: "blue", width: 1 })
                })
              });
              if (resolution < 6) {
                s.setText(
                  new Text({
                    offsetY: 12,
                    font: '10px "Open Sans", "sans-serif"',
                    fill: new Fill({
                      color: "black"
                    }),
                    text: (feature.get("hectometre") / 10).toString()
                  })
                );
              }
              return s;
            } else {
              return [];
            }
          }
        })
      },
      [LAYERS.GAUGES]: {
        forLegendStyle: { point: true, resolution: 8 },
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: function(feature, resolution, isLegend) {
            return [
              new Style({
                image: new Icon({
                  src: require("@/assets/marker-gauge.png"),
                  anchor: [0.5, isLegend ? 0.5 : 1],
                  scale: isLegend ? 0.5 : 1
                }),
                text: new Text({
                  font: '10px "Open Sans", "sans-serif"',
                  offsetY: 8,
                  fill: new Fill({
                    color: "white"
                  }),
                  text: feature.get("objname")
                })
              }),
              new Style({
                text: new Text({
                  font: '10px "Open Sans", "sans-serif"',
                  offsetY: 7,
                  offsetX: -1,
                  fill: new Fill({
                    color: "black"
                  }),
                  text: feature.get("objname")
                })
              })
            ];
          },
          maxResolution: 100,
          minResolution: 0
        })
      },
      [LAYERS.DRAWTOOL]: {
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({ wrapX: false }),
          style: function(feature) {
            // adapted from OpenLayer's LineString Arrow Example
            var geometry = feature.getGeometry();
            var styles = [
              // linestring
              new Style({
                stroke: new Stroke({
                  color: "#369aca",
                  width: 2
                })
              })
            ];

            if (geometry.getType() === "LineString") {
              geometry.forEachSegment(function(start, end) {
                var dx = end[0] - start[0];
                var dy = end[1] - start[1];
                var rotation = Math.atan2(dy, dx);
                // arrows
                styles.push(
                  new Style({
                    geometry: new Point(end),
                    image: new Icon({
                      // we need to make sure the image is loaded by Vue Loader
                      src: require("@/assets/linestring_arrow.png"),
                      // fiddling with the anchor's y value does not help to
                      // position the image more centered on the line ending, as the
                      // default line style seems to be slightly uncentered in the
                      // anti-aliasing, but the image is not placed with subpixel
                      // precision
                      anchor: [0.75, 0.5],
                      rotateWithView: true,
                      rotation: -rotation
                    })
                  })
                );
              });
            }
            return styles;
          }
        })
      },
      [LAYERS.CUTTOOL]: {
        data: new VectorLayer({
          visible: true,
          source: new VectorSource({ wrapX: false }),
          style: function(feature) {
            // adapted from OpenLayer's LineString Arrow Example
            var geometry = feature.getGeometry();
            var styles = [
              // linestring
              new Style({
                stroke: new Stroke({
                  color: "#333333",
                  width: 2,
                  lineDash: [7, 7]
                })
              })
            ];

            if (geometry.getType() === "LineString") {
              geometry.forEachSegment(function(start, end) {
                var dx = end[0] - start[0];
                var dy = end[1] - start[1];
                var rotation = Math.atan2(dy, dx);
                // arrows
                styles.push(
                  new Style({
                    geometry: new Point(end),
                    image: new Icon({
                      // we need to make sure the image is loaded by Vue Loader
                      src: require("@/assets/linestring_arrow_grey.png"),
                      // fiddling with the anchor's y value does not help to
                      // position the image more centered on the line ending, as the
                      // default line style seems to be slightly uncentered in the
                      // anti-aliasing, but the image is not placed with subpixel
                      // precision
                      anchor: [0.75, 0.5],
                      rotateWithView: true,
                      rotation: -rotation
                    })
                  })
                );
              });
            }
            return styles;
          }
        })
      }
    }
  };
};

export default {
  init,
  namespaced: true,
  state: init(),
  getters: {
    getLayerByName: state => name => {
      return state.layers[name];
    },
    getVSourceByName: (state, getters) => name => {
      return getters.getLayerByName(name).data.getSource();
    },
    filteredIdentifiedFeatures: state => {
      return state.identifiedFeatures.filter(f => f.getId());
    }
  },
  mutations: {
    initialLoad: (state, initialLoad) => {
      state.initialLoad = initialLoad;
    },
    extent: (state, extent) => {
      state.extent = extent;
    },
    setLayerVisible: (state, name) => {
      state.layers[name].data.setVisible(true);
    },
    setLayerInvisible: (state, name) => {
      state.layers[name].data.setVisible(false);
    },
    toggleVisibilityByName: (state, name) => {
      state.layers[name].data.setVisible(!state.layers[name].data.getVisible());
    },
    openLayersMap: (state, map) => {
      state.openLayersMap = map;
    },
    identifyTool: (state, events) => {
      state.identifyTool = events;
    },
    setIdentifiedFeatures: (state, identifiedFeatures) => {
      state.identifiedFeatures = identifiedFeatures;
    },
    setCurrentMeasurement: (state, measurement) => {
      state.currentMeasurement = measurement;
    },
    lineTool: (state, lineTool) => {
      state.lineTool = lineTool;
    },
    polygonTool: (state, polygonTool) => {
      state.polygonTool = polygonTool;
    },
    cutTool: (state, cutTool) => {
      state.cutTool = cutTool;
    },
    moveToBoundingBox: (state, { boundingBox, zoom, preventZoomOut }) => {
      const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857");
      let view = state.openLayersMap.getView();
      moveMap({ view, extent, zoom, preventZoomOut });
    },
    moveToExtent: (state, { feature, zoom, preventZoomOut }) => {
      const boundingBox = bbox(feature.geometry);
      const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857");
      let view = state.openLayersMap.getView();
      moveMap({ view, extent, zoom, preventZoomOut });
    },
    moveMap: (state, { coordinates, zoom, preventZoomOut }) => {
      let view = state.openLayersMap.getView();
      const currentZoom = view.getZoom();
      zoom = zoom || currentZoom;
      view.animate({
        zoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
        center: fromLonLat(coordinates, view.getProjection()),
        duration: 700
      });
    },
    isolinesLegendImgDataURL: (state, isolinesLegendImgDataURL) => {
      state.isolinesLegendImgDataURL = isolinesLegendImgDataURL;
    },
    differencesLegendImgDataURL: (state, differencesLegendImgDataURL) => {
      state.differencesLegendImgDataURL = differencesLegendImgDataURL;
    }
  },
  actions: {
    openLayersMap({ commit, dispatch, getters }, map) {
      const drawVectorSrc = getters.getVSourceByName("Draw Tool");
      const cutVectorSrc = getters.getVSourceByName("Cut Tool");

      // init line tool
      const lineTool = new Draw({
        source: drawVectorSrc,
        type: "LineString",
        maxPoints: 2
      });
      lineTool.setActive(false);
      lineTool.on("drawstart", () => {
        drawVectorSrc.clear();
        commit("setCurrentMeasurement", null);
      });
      lineTool.on("drawend", event => {
        commit("setCurrentMeasurement", {
          quantity: app.$gettext("Length"),
          unitSymbol: "m",
          value: Math.round(getLength(event.feature.getGeometry()) * 10) / 10
        });
        commit("application/showIdentify", true, { root: true });
      });

      // init polygon tool
      const polygonTool = new Draw({
        source: drawVectorSrc,
        type: "Polygon",
        maxPoints: 50
      });
      polygonTool.setActive(false);
      polygonTool.on("drawstart", () => {
        drawVectorSrc.clear();
        commit("setCurrentMeasurement", null);
      });
      polygonTool.on("drawend", event => {
        const areaSize = getArea(event.feature.getGeometry());
        commit("setCurrentMeasurement", {
          quantity: app.$gettext("Area"),
          unitSymbol: areaSize > 100000 ? "km²" : "m²",
          value:
            areaSize > 100000
              ? Math.round(areaSize / 1000) / 1000 // convert into 1 km² == 1000*1000 m² and round to 1000 m²
              : Math.round(areaSize)
        });
        commit("application/showIdentify", true, { root: true });
      });

      // init cut tool
      const cutTool = new Draw({
        source: cutVectorSrc,
        type: "LineString",
        maxPoints: 2,
        style: new Style({
          stroke: new Stroke({
            color: "#444",
            width: 2,
            lineDash: [7, 7]
          }),
          image: new Circle({
            fill: new Fill({ color: "#333" }),
            stroke: new Stroke({ color: "#fff", width: 1.5 }),
            radius: 6
          })
        })
      });
      cutTool.setActive(false);
      cutTool.on("drawstart", () => {
        dispatch("disableIdentifyTool");
        cutVectorSrc.clear();
      });
      cutTool.on("drawend", event => {
        commit("fairwayprofile/selectedCut", null, { root: true });
        dispatch("fairwayprofile/cut", event.feature, { root: true }).then(() =>
          // This setTimeout is an ugly workaround. If we would enable the
          // identifyTool here immediately then the click event from ending the
          // cut will trigger it. We don't want that.
          setTimeout(() => dispatch("enableIdentifyTool"), 1000)
        );
      });

      map.addInteraction(lineTool);
      map.addInteraction(cutTool);
      map.addInteraction(polygonTool);

      commit("lineTool", lineTool);
      commit("polygonTool", polygonTool);
      commit("cutTool", cutTool);
      commit("openLayersMap", map);
    },
    disableIdentifyTool({ state }) {
      unByKey(state.identifyTool);
      state.identifyTool = null;
    },
    enableIdentifyTool({ state, rootState, commit, dispatch, getters }) {
      if (!state.identifyTool) {
        state.identifyTool = state.openLayersMap.on(
          ["singleclick", "dblclick"],
          event => {
            commit("setIdentifiedFeatures", []);
            // checking our WFS layers
            var features = state.openLayersMap.getFeaturesAtPixel(event.pixel, {
              hitTolerance: 7
            });
            if (features) {
              let identifiedFeatures = [];

              for (let feature of features) {
                let id = feature.getId();
                // avoid identifying the same feature twice
                if (
                  identifiedFeatures.findIndex(
                    f => f.getId() === feature.getId()
                  ) === -1
                ) {
                  identifiedFeatures.push(feature);
                }

                // get selected bottleneck
                // RegExp.prototype.test() works with number, str and undefined
                if (/^bottlenecks/.test(id)) {
                  if (
                    rootState.bottlenecks.selectedBottleneck !=
                    feature.get("objnam")
                  ) {
                    dispatch(
                      "bottlenecks/setSelectedBottleneck",
                      feature.get("objnam"),
                      { root: true }
                    ).then(() => {
                      this.commit("bottlenecks/setFirstSurveySelected");
                    });
                    commit("moveMap", {
                      coordinates: getCenter(
                        feature
                          .getGeometry()
                          .clone()
                          .transform("EPSG:3857", "EPSG:4326")
                          .getExtent()
                      ),
                      zoom: 17,
                      preventZoomOut: true
                    });
                  }
                }

                // get selected gauge
                if (/^gauges/.test(id)) {
                  if (
                    rootState.gauges.selectedGaugeISRS !==
                    feature.get("isrs_code")
                  ) {
                    dispatch(
                      "gauges/selectedGaugeISRS",
                      feature.get("isrs_code"),
                      {
                        root: true
                      }
                    );
                    commit("moveMap", {
                      coordinates: getCenter(
                        feature
                          .getGeometry()
                          .clone()
                          .transform("EPSG:3857", "EPSG:4326")
                          .getExtent()
                      ),
                      zoom: null,
                      preventZoomOut: true
                    });
                  }
                }

                // get selected stretch
                if (/^stretches/.test(id)) {
                  if (rootState.imports.selectedStretchId === feature.getId()) {
                    commit("imports/selectedStretchId", null, { root: true });
                  } else {
                    commit("imports/selectedStretchId", feature.getId(), {
                      root: true
                    });
                    commit("moveMap", {
                      coordinates: getCenter(
                        feature
                          .getGeometry()
                          .clone()
                          .transform("EPSG:3857", "EPSG:4326")
                          .getExtent()
                      ),
                      zoom: null,
                      preventZoomOut: true
                    });
                  }
                }
              }

              commit("setIdentifiedFeatures", identifiedFeatures);
            }

            // DEBUG output and example how to remove the GeometryName
            /*
            for (let feature of features) {
              console.log("Identified:", feature.getId());
              for (let key of feature.getKeys()) {
                if (key != feature.getGeometryName()) {
                  console.log(key, feature.get(key));
                }
              }
            }
            */

            // trying the GetFeatureInfo way for WMS
            var wmsSource = getters.getVSourceByName(
              "Inland ECDIS chart Danube"
            );
            var url = wmsSource.getGetFeatureInfoUrl(
              event.coordinate,
              100 /* resolution */,
              "EPSG:3857",
              // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
              { INFO_FORMAT: "text/plain" }
            );

            if (url) {
              // cannot directly query here because of SOP
              console.log("GetFeatureInfo url:", url);
            }
          }
        );
      }
    }
  }
};

export { LAYERS };