From 8c70acf8be82df1698be8eaee28fdb6739cb55ff Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Fri, 9 Aug 2024 11:54:53 +0200 Subject: [PATCH 01/24] chores(init): Initialise new folder --- elements/map/src-2/controls/controls.css | 28 +++++++++++++++++++++ elements/map/src-2/main.js | 32 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 elements/map/src-2/controls/controls.css create mode 100644 elements/map/src-2/main.js diff --git a/elements/map/src-2/controls/controls.css b/elements/map/src-2/controls/controls.css new file mode 100644 index 000000000..5b8503bed --- /dev/null +++ b/elements/map/src-2/controls/controls.css @@ -0,0 +1,28 @@ +.geolocation { + top: 65px; + left: 0.5em; +} +.ol-touch.geolocation { + top: 80px; +} + +.loading-indicator { + position: absolute; + pointer-events: none !important; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, 0); +} + +.loading-indicator.small { + bottom: 0.5em; + left: 0.5em; + width: 30px; + height: 30px; +} + +.loading-indicator.fullscreen { + width: 100%; + height: 100%; +} diff --git a/elements/map/src-2/main.js b/elements/map/src-2/main.js new file mode 100644 index 000000000..789787bcc --- /dev/null +++ b/elements/map/src-2/main.js @@ -0,0 +1,32 @@ +import { html, LitElement } from "lit"; +import olCss from "ol/ol.css?inline"; +import controlCss from "./controls/controls.css?inline"; + +export class EOxMap extends LitElement { + static get properties() { + return {}; + } + + constructor() { + super(); + } + + render() { + return html` + +
+ + `; + } +} + +customElements.define("eox-map-2", EOxMap); From 9f0c90326d6a01bee8cd3dbeabdef84b4fcf1f65 Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Wed, 14 Aug 2024 11:13:09 +0200 Subject: [PATCH 02/24] chores: main.js convereted from ts to js --- elements/map/map.stories.js | 8 + elements/map/src-2/main.js | 427 +++++++++++++++++++++++++++++++++++- 2 files changed, 432 insertions(+), 3 deletions(-) diff --git a/elements/map/map.stories.js b/elements/map/map.stories.js index 93cebb343..d511a65c1 100644 --- a/elements/map/map.stories.js +++ b/elements/map/map.stories.js @@ -1,5 +1,6 @@ // Global import of eox-elements in .storybook/preview.js! import { html } from "lit"; +import "./src-2/main.js"; export default { title: "Elements/eox-map", @@ -13,6 +14,13 @@ export default { .layers=${args.layers} .zoom=${args.zoom} > + `, }; diff --git a/elements/map/src-2/main.js b/elements/map/src-2/main.js index 789787bcc..c059c9fac 100644 --- a/elements/map/src-2/main.js +++ b/elements/map/src-2/main.js @@ -1,16 +1,437 @@ -import { html, LitElement } from "lit"; +import { LitElement, html } from "lit"; +import Map from "ol/Map.js"; +import View from "ol/View.js"; import olCss from "ol/ol.css?inline"; -import controlCss from "./controls/controls.css?inline"; +import controlCss from "../src/controls/controls.css?inline"; +// import { EOxSelectInteraction } from "../src/select"; +import { + // EoxLayer, + createLayer, + updateLayer, +} from "../src/generate"; +// import { Draw, Modify } from "ol/interaction"; +// import Control from "ol/control/Control"; +import { getLayerById, getFlatLayersArray } from "../src/layer"; +import { getCenterFromProperty } from "../src/center"; +import { + addOrUpdateControl, + // controlDictionary, + // controlType, +} from "../src/controls/controls"; +import { buffer } from "ol/extent"; +import "../src/compare"; +import { + addScrollInteractions, + coordinatesRoughlyEquals, + removeDefaultScrollInteractions, + transform, + transformExtent, +} from "../src/utils"; +import GeoJSON from "ol/format/GeoJSON"; +import { + parseText, + registerProjection, + registerProjectionFromCode, + READ_FEATURES_OPTIONS, + cancelAnimation, +} from "../helpers"; +// import Feature from "ol/Feature"; +import VectorLayer from "ol/layer/Vector.js"; +import { + transform as olTransform, + getPointResolution, + get as getProjection, +} from "ol/proj"; +import { getElement } from "../../../utils"; export class EOxMap extends LitElement { static get properties() { - return {}; + return { + center: { attribute: false, type: Array }, + layers: { attribute: false, type: Array }, + preventScroll: { attribute: "prevent-scroll", type: Boolean }, + config: { attribute: false, type: Object }, + sync: { attribute: "sync", type: String }, + projection: { attribute: "projection", type: String }, + }; } constructor() { super(); + // this._center = [0, 0]; + // this._zoom = 0; + // this._controls = {}; + // this._layers = []; + // this._preventScroll = false; + // this._config = null; + // this._animationOptions = {}; + // this._projection = "EPSG:3857"; + // this._sync = undefined; + this.map = new Map({ + controls: [], + layers: [], + view: new View({ + center: [0, 0], + zoom: 0, + projection: this.projection, + }), + }); + this.interactions = {}; + this.selectInteractions = {}; + this.mapControls = {}; } + set center(center) { + const centerIsSame = + center?.length && + coordinatesRoughlyEquals(center, this.map.getView().getCenter()); + if (center && !centerIsSame) { + if (!this.projection || this.projection === "EPSG:3857") { + const mercatorCenter = getCenterFromProperty(center); + this._center = mercatorCenter; + } else { + this._center = center; + } + this._animateToState(); + } + } + + get center() { + return this._center; + } + + get lonLatCenter() { + if (this.projection === "EPSG:4326") { + return this.map.getView().getCenter(); + } + return transform(this.map.getView().getCenter(), this.projection); + } + + get lonLatExtent() { + const currentExtent = this.map + .getView() + .calculateExtent(this.map.getSize()); + if (this.projection === "EPSG:4326") { + return currentExtent; + } + return transformExtent(currentExtent, this.projection); + } + + set zoom(zoom) { + if (zoom === undefined) return; + this._zoom = zoom; + this._animateToState(); + } + + get zoom() { + return this._zoom; + } + + set zoomExtent(extent) { + if (!extent || !extent.length) { + this._zoomExtent = undefined; + return; + } + const view = this.map.getView(); + cancelAnimation(view); + setTimeout(() => { + view.fit(extent, this.animationOptions); + }, 0); + this._zoomExtent = extent; + } + + set controls(controls) { + const oldControls = this._controls; + const newControls = controls; + console.log(controls); + + if (oldControls) { + console.log(oldControls); + const oldControlTypes = Object.keys(oldControls); + const newControlTypes = Object.keys(newControls); + for (let i = 0, ii = oldControlTypes.length; i < ii; i++) { + const oldControlType = oldControlTypes[i]; + if (!newControlTypes.includes(oldControlType)) { + this.removeControl(oldControlType); + } + } + } + if (newControls) { + const keys = Object.keys(controls); + for (let i = 0, ii = keys.length; i < ii; i++) { + const key = keys[i]; + addOrUpdateControl(this, oldControls, key, controls[key]); + } + } + this._controls = newControls; + } + + get controls() { + return this._controls; + } + + set layers(layers) { + const oldLayers = this._layers; + const newLayers = JSON.parse(JSON.stringify(layers)).reverse(); + + if (oldLayers) { + oldLayers.forEach((l) => { + if ( + !l.properties?.id || + !newLayers.find( + (newLayer) => newLayer.properties.id === l.properties.id + ) + ) { + const layerToBeRemoved = getLayerById(this, l.properties?.id); + const jsonDefinition = layerToBeRemoved.get("_jsonDefinition"); + jsonDefinition.interactions?.forEach((interaction) => { + if (interaction.type === "select") { + this.removeSelect(interaction.options.id); + } else { + this.removeInteraction(interaction.options.id); + } + }); + this.map.removeLayer(layerToBeRemoved); + } + }); + } + + newLayers.forEach((l) => { + this.addOrUpdateLayer(l); + }); + + const sortedIds = newLayers.map((l) => l.properties?.id); + this.map + .getLayers() + .getArray() + .sort((layerA, layerB) => { + return ( + sortedIds.indexOf(layerA.get("id")) - + sortedIds.indexOf(layerB.get("id")) + ); + }); + this._layers = newLayers; + } + + get layers() { + return this._layers; + } + + set preventScroll(preventScroll) { + if (preventScroll) { + removeDefaultScrollInteractions(this.map); + addScrollInteractions(this.map, true); + } else addScrollInteractions(this.map); + + this._preventScroll = preventScroll; + } + + get preventScroll() { + return this._preventScroll; + } + + set config(config) { + this._config = config; + if (config?.animationOptions !== undefined) { + this.animationOptions = config.animationOptions; + } + this.projection = config?.view?.projection || "EPSG:3857"; + this.layers = config?.layers || []; + this.controls = config?.controls || {}; + if (this.preventScroll === undefined) { + this.preventScroll = config?.preventScroll; + } + this.zoom = config?.view?.zoom || 0; + this.center = config?.view?.center || [0, 0]; + this.zoomExtent = config?.view?.zoomExtent; + } + + set animationOptions(animationOptions) { + this._animationOptions = animationOptions; + } + + get animationOptions() { + return this._animationOptions; + } + + get config() { + return this._config; + } + + set projection(projection) { + const oldView = this.map.getView(); + if ( + projection && + getProjection(projection) && + projection !== oldView.getProjection().getCode() + ) { + const newCenter = olTransform( + oldView.getCenter(), + oldView.getProjection().getCode(), + projection + ); + + const newProjection = getProjection(projection); + const oldResolution = oldView.getResolution(); + const oldMPU = oldView.getProjection().getMetersPerUnit(); + const newMPU = newProjection.getMetersPerUnit(); + const oldPointResolution = + getPointResolution( + oldView.getProjection(), + 1 / oldMPU, + oldView.getCenter(), + "m" + ) * oldMPU; + const newPointResolution = + getPointResolution(newProjection, 1 / newMPU, newCenter, "m") * newMPU; + + const newResolution = + (oldResolution * oldPointResolution) / newPointResolution; + + const newView = new View({ + zoom: oldView.getZoom(), + center: newCenter, + resolution: newResolution, + rotation: oldView.getRotation(), + projection, + }); + const eventTypes = [ + "change:center", + "change:resolution", + "change:rotation", + "propertychange", + ]; + eventTypes.forEach((eventType) => { + const existingListeners = oldView.getListeners(eventType); + if (existingListeners?.length) { + for (let i = existingListeners.length - 1; i >= 0; i--) { + const listener = existingListeners[i]; + oldView.un(eventType, listener); + newView.on(eventType, listener); + } + } + }); + this.map.setView(newView); + this.getFlatLayersArray(this.map.getLayers().getArray()) + .filter((l) => l instanceof VectorLayer) + .forEach((l) => l.getSource().refresh()); + this._projection = projection; + this.center = newCenter; + } + } + + set sync(sync) { + this._sync = sync; + if (sync) { + setTimeout(() => { + const originMap = getElement(sync); + if (originMap) { + this.map.setView(originMap.map.getView()); + } + }); + } + } + + get sync() { + return this._sync; + } + + addOrUpdateLayer = (json) => { + if (!json.interactions) { + json.interactions = []; + } + const id = json.properties?.id; + + const existingLayer = id ? getLayerById(this, id) : false; + let layer; + if (existingLayer) { + updateLayer(this, json, existingLayer); + layer = existingLayer; + } else { + layer = createLayer(this, json); + this.map.addLayer(layer); + } + return layer; + }; + + removeInteraction = (id) => { + this.map.removeInteraction(this.interactions[id]); + delete this.interactions[id]; + if (this.interactions[`${id}_modify`]) { + this.map.removeInteraction(this.interactions[`${id}_modify`]); + delete this.interactions[`${id}_modify`]; + } + }; + + removeSelect = (id) => { + this.selectInteractions[id].remove(); + delete this.selectInteractions[id]; + }; + + removeControl = (id) => { + this.map.removeControl(this.mapControls[id]); + delete this.mapControls[id]; + }; + + getLayerById = (layerId) => { + return getLayerById(this, layerId); + }; + + parseFeature = (features) => { + const format = new GeoJSON(); + return format.writeFeaturesObject(features, READ_FEATURES_OPTIONS); + }; + + parseTextToFeature = (text, vectorLayer, replaceFeatures = false) => { + parseText(text, vectorLayer, this, replaceFeatures); + }; + + registerProjectionFromCode = registerProjectionFromCode; + + registerProjection = registerProjection; + + getFlatLayersArray = getFlatLayersArray; + + transform = transform; + + transformExtent = transformExtent; + + buffer(extent, value) { + return buffer(extent, value); + } + + firstUpdated() { + this.map.once("change:target", (e) => { + e.target.getView().setCenter(this.center); + }); + this.map.setTarget(this.renderRoot.querySelector("div")); + if (this._zoomExtent) { + this.map.getView().fit(this._zoomExtent, this.animationOptions); + } else { + this._animateToState(); + } + this.map.on("loadend", () => { + this.dispatchEvent(new CustomEvent("loadend", { detail: this.map })); + }); + this.dispatchEvent(new CustomEvent("mapmounted", { detail: this.map })); + } + + _animateToState() { + const animateToOptions = Object.assign({}, this.animationOptions); + const view = this.map.getView(); + cancelAnimation(view); + if (!animateToOptions || !Object.keys(animateToOptions).length) { + view.setCenter(this.center); + view.setZoom(this.zoom); + return; + } + animateToOptions.center = getCenterFromProperty(this.center); + animateToOptions.zoom = this.zoom; + view.animate(animateToOptions); + } + + updated(_changedProperties) {} + render() { return html` + ${choose( + this.enabled, + [ + ["first", () => html``], + ["second", () => html``], + ], + () => html` +
+
+ hujhjjh +
+
+ +
+ +
+ ` + )} + `; + } +} + +customElements.define("eox-map-compare", EOxMapCompare); diff --git a/elements/map/src-2/helpers/center.js b/elements/map/src-2/helpers/center.js new file mode 100644 index 000000000..d6b834245 --- /dev/null +++ b/elements/map/src-2/helpers/center.js @@ -0,0 +1,31 @@ +import { equals } from "ol/coordinate"; +import { fromLonLat } from "ol/proj"; + +/** + * returns an ol-coordinate from a given center property. + * the coordinate expected coordinate system is EPSG:3857, however, + * if both parts of the coordinate are between -180 and 180, the coordinate + * system EPSG:4326 is assumed. + * use `map.getView().setCenter()` to manually set the center of the view. + * + * @param {Array} centerProperty + * @returns {import("ol/coordinate")} + */ +export function getCenterFromProperty(centerProperty) { + if (centerProperty) { + const coordinate = centerProperty; + // compare: + // https://github.com/openlayers/openlayers/blob/v7.4.0/src/ol/proj.js + if ( + !equals(coordinate, [0, 0]) && + coordinate[0] >= -180 && + coordinate[0] <= 180 && + coordinate[1] >= -90 && + coordinate[1] <= 90 + ) { + return fromLonLat(coordinate); + } + return coordinate; + } + return [0, 0]; +} diff --git a/elements/map/src-2/helpers/draw.js b/elements/map/src-2/helpers/draw.js new file mode 100644 index 000000000..91ac55fc6 --- /dev/null +++ b/elements/map/src-2/helpers/draw.js @@ -0,0 +1,67 @@ +import Modify from "ol/interaction/Modify"; +import Draw, { createBox } from "ol/interaction/Draw"; +import { addNewFeature } from "../helpers"; + +/** + * @typedef {import("../../types").DrawOptions} DrawOptions + * */ + +/** + * Adds a `draw`-interaction to the map. + * Additionally, if {options.modify} is set, it also adds a `modify` interaction. The name `modify`-interaction + * follows the naming convention `${DrawOptions.id}_modify` + * + * @param {import("../main").EOxMap} EOxMap + * @param {import("ol/layer").Vector} drawLayer + * @param {DrawOptions} options + */ +export function addDraw(EOxMap, drawLayer, options) { + const options_ = Object.assign({}, options); + if (EOxMap.interactions[options_.id]) { + throw Error(`Interaction with id: ${options_.id} already exists.`); + } + options_.modify = + typeof options_.modify === "boolean" ? options_.modify : true; + + const source = drawLayer.getSource(); + + if (options_.type === "Box") { + options_.geometryFunction = createBox(); + options_.type = "Circle"; + } + + //@ts-expect-error box + const drawInteraction = new Draw({ + ...options_, + source, + }); + + // TODO cleaner way of initializing as inactive? + if (options_.active === false) { + drawInteraction.setActive(false); + } + + drawInteraction.on("drawend", (e) => { + if (!drawLayer.get("isDrawingEnabled")) return; + addNewFeature(e, drawLayer, EOxMap, true); + }); + + // identifier to retrieve the interaction + EOxMap.map.addInteraction(drawInteraction); + EOxMap.interactions[options_.id] = drawInteraction; + const modifyInteraction = new Modify({ + source, + }); + modifyInteraction.setActive(options_.modify); + EOxMap.map.addInteraction(modifyInteraction); + EOxMap.interactions[`${options_.id}_modify`] = modifyInteraction; + + const removeLayerListener = () => { + if (!EOxMap.getLayerById(drawLayer.get("id"))) { + EOxMap.removeInteraction(options_.id); + EOxMap.removeInteraction(`${options_.id}_modify`); + EOxMap.map.getLayerGroup().un("change", removeLayerListener); + } + }; + EOxMap.map.getLayerGroup().on("change", removeLayerListener); +} diff --git a/elements/map/src-2/helpers/generate.js b/elements/map/src-2/helpers/generate.js new file mode 100644 index 000000000..e664718d9 --- /dev/null +++ b/elements/map/src-2/helpers/generate.js @@ -0,0 +1,243 @@ +import { GeoJSON, MVT } from "ol/format"; +import { + Group, + Image, + Tile as TileLayer, + Vector as VectorLayer, + VectorTile as VectorTileLayer, +} from "ol/layer"; +import { + ImageWMS, + OSM, + Tile as TileSource, + TileWMS, + Vector as VectorSource, + VectorTile as VectorTileSource, + WMTS, + XYZ, +} from "ol/source"; +import { Collection } from "ol"; +import { addDraw, addSelect, generateTileGrid } from "./"; +import { get as getProjection } from "ol/proj"; + +/** + * @typedef {import("../../types").AnyLayer} AnyLayer + * @typedef {import("../../types").EoxLayer} EoxLayer + * @typedef {import("../../types").SelectLayer} SelectLayer + * */ + +/** + * Creates an ol-layer from a given EoxLayer definition object. + * + * @param {EOxMap} EOxMap + * @param {EoxLayer} layer + * @param {boolean} [createInteractions=true] + * @returns {AnyLayer} + */ + +const basicOlFormats = { + GeoJSON, + MVT, +}; + +const basicOlLayers = { + Group, + Image, + Tile: TileLayer, + Vector: VectorLayer, + VectorTile: VectorTileLayer, +}; + +const basicOlSources = { + ImageWMS, + OSM, + Tile: TileSource, + TileWMS, + Vector: VectorSource, + VectorTile: VectorTileSource, + WMTS, + XYZ, +}; + +/** + * Creates an OpenLayers layer from a given EoxLayer definition object. + * + * @param {EOxMap} EOxMap - The map instance. + * @param {EoxLayer} layer - The layer definition. + * @param {boolean=} createInteractions - Whether to create interactions for the layer. + * @returns {AnyLayer} - The created OpenLayers layer. + */ +export function createLayer(EOxMap, layer, createInteractions = true) { + layer = JSON.parse(JSON.stringify(layer)); + + const availableFormats = { + ...window.eoxMapAdvancedOlFormats, + ...basicOlFormats, + }; + + const availableLayers = { + ...window.eoxMapAdvancedOlLayers, + ...basicOlLayers, + }; + + const availableSources = { + ...window.eoxMapAdvancedOlSources, + ...basicOlSources, + }; + + const newLayer = availableLayers[layer.type]; + const newSource = availableSources[layer.source?.type]; + + if (!newLayer) { + throw new Error(`Layer type ${layer.type} not supported!`); + } + + if (layer.source && !newSource) { + throw new Error(`Source type ${layer.source.type} not supported!`); + } + + const tileGrid = generateTileGrid(layer); + + const olLayer = new newLayer({ + ...layer, + ...(layer.source && { + source: new newSource({ + ...layer.source, + ...(layer.source.format && + layer.source.type !== "WMTS" && { + format: new availableFormats[ + typeof layer.source.format === "object" + ? layer.source.format.type + : layer.source.format + ]({ + ...(typeof layer.source.format === "object" && { + ...layer.source.format, + }), + }), + }), + ...(layer.source.tileGrid && { tileGrid }), + ...(layer.source.projection && { + projection: getProjection(layer.source.projection), + }), + }), + }), + ...(layer.type === "Group" && { layers: [] }), + ...layer.properties, + style: undefined, // override layer style, apply style after + }); + + olLayer.set("_jsonDefinition", layer, true); + + if (layer.type === "Group") { + const groupLayers = layer.layers + .reverse() + .map((l) => createLayer(EOxMap, l)); + groupLayers.forEach((l) => l.set("_group", olLayer, true)); + olLayer.setLayers(new Collection(groupLayers)); + } + + if (layer.style) { + olLayer.setStyle(layer.style); + } + + if (createInteractions && layer.interactions?.length) { + for (let i = 0; i < layer.interactions.length; i++) { + const interactionDefinition = layer.interactions[i]; + addInteraction(EOxMap, olLayer, interactionDefinition); + } + } + + setSyncListeners(olLayer, layer); + return olLayer; +} + +/** + * Adds an interaction to a given layer. + * + * @param {EOxMap} EOxMap - The map instance. + * @param {AnyLayer} olLayer - The OpenLayers layer. + * @param {EOxInteraction} interactionDefinition - The interaction definition. + */ +function addInteraction(EOxMap, olLayer, interactionDefinition) { + if (interactionDefinition.type === "draw") { + addDraw(EOxMap, olLayer, interactionDefinition.options); + } else if (interactionDefinition.type === "select") { + addSelect(EOxMap, olLayer, interactionDefinition.options); + } +} + +/** + * Updates an existing layer with a new definition. + * + * @param {EOxMap} EOxMap - The map instance. + * @param {EoxLayer} newLayerDefinition - The new layer definition. + * @param {AnyLayer} existingLayer - The existing layer to be updated. + * @returns {AnyLayer} - The updated layer. + */ +export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { + const existingJsonDefinition = existingLayer.get("_jsonDefinition"); + + if ( + newLayerDefinition.type !== existingJsonDefinition.type || + newLayerDefinition.source?.type !== existingJsonDefinition.source?.type + ) { + throw new Error("Layers are not compatible to be updated"); + } + + const newLayer = createLayer(EOxMap, newLayerDefinition, false); + + if ( + JSON.stringify(newLayerDefinition.source) !== + JSON.stringify(existingJsonDefinition.source) + ) { + existingLayer.setSource(newLayer.getSource()); + } + + if ( + ["Vector", "VectorTile"].includes(newLayerDefinition.type) && + JSON.stringify(newLayerDefinition.style) !== + JSON.stringify(existingJsonDefinition.style) + ) { + existingLayer.setStyle(newLayer.getStyle()); + } + + if ( + JSON.stringify(newLayerDefinition.properties) !== + JSON.stringify(existingJsonDefinition.properties) + ) { + existingLayer.setProperties(newLayerDefinition.properties); + } + + if (newLayerDefinition.visible !== existingJsonDefinition.visible) { + existingLayer.setVisible(newLayerDefinition.visible); + } + if (newLayerDefinition.opacity !== existingJsonDefinition.opacity) { + existingLayer.setOpacity(newLayerDefinition.opacity); + } + + setSyncListeners(existingLayer, newLayerDefinition); + existingLayer.set("_jsonDefinition", newLayerDefinition, true); + return existingLayer; +} + +/** + * Set listeners to keep the state of the layer in sync with the input JSON definition. + * + * @param {AnyLayer} olLayer - The OpenLayers layer. + * @param {EoxLayer} eoxLayer - The EoxLayer definition. + */ +function setSyncListeners(olLayer, eoxLayer) { + olLayer.on("change:opacity", () => { + eoxLayer.opacity = olLayer.getOpacity(); + }); + olLayer.on("change:visible", () => { + eoxLayer.visible = olLayer.getVisible(); + }); + olLayer.on("change:zIndex", () => { + eoxLayer.zIndex = olLayer.getZIndex(); + }); + olLayer.on("propertychange", (e) => { + if (e.key === "map") return; + eoxLayer.properties[e.key] = e.target.get(e.key); + }); +} diff --git a/elements/map/src-2/helpers/index.js b/elements/map/src-2/helpers/index.js index 22577122f..e927d4a8c 100644 --- a/elements/map/src-2/helpers/index.js +++ b/elements/map/src-2/helpers/index.js @@ -1,5 +1,3 @@ -import coordinatesRoughlyEquals from "./coordinates-roughly-equals.js"; - export { default as addNewFeature } from "./add-new-feature"; export { default as cancelAnimation } from "./cancel-animation"; export { default as parseTextToFeature } from "./parse-text"; @@ -12,3 +10,9 @@ export { removeDefaultScrollInteractions, } from "./interactions"; export { default as coordinatesRoughlyEquals } from "./coordinates-roughly-equals"; +export { getCenterFromProperty } from "./center"; +export { addDraw } from "./draw"; +export { EOxSelectInteraction, addSelect } from "./select"; +export { generateTileGrid } from "./tile-grid"; +export { getLayerById, getFlatLayersArray } from "./layer"; +export { createLayer, updateLayer } from "./generate"; diff --git a/elements/map/src-2/helpers/layer.js b/elements/map/src-2/helpers/layer.js new file mode 100644 index 000000000..607fccaf5 --- /dev/null +++ b/elements/map/src-2/helpers/layer.js @@ -0,0 +1,50 @@ +import Group from "ol/layer/Group"; + +/** + * @typedef {import("../../types").AnyLayerWithSource} AnyLayerWithSource + * @typedef {import("../../types").AnyLayer} AnyLayer + */ + +/** + * Retrieves a layer by its ID from the EOxMap. + * + * @param {EOxMap} EOxMap - Instance of EOxMap class + * @param {string} layerId - ID of the OpenLayers layer + * @returns {AnyLayerWithSource | undefined} - Layer or `undefined` if the layer does not exist + */ +export function getLayerById(EOxMap, layerId) { + const flatLayers = getFlatLayersArray( + /** @type {Array} */ (EOxMap.map.getLayers().getArray()) + ); + return flatLayers.find((l) => l.get("id") === layerId); +} + +/** + * Returns a flat array of all map layers, including groups and nested layers. + * To get all layers without groups, you can use the native OL `getAllLayers` method on the map itself: + * https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html#getAllLayers + * + * @param {Array} layers - Array of layers + * @returns {Array} - Flat array of layers + * @example getFlatLayersArray(eoxMap.map.getAllLayers()) + */ +export function getFlatLayersArray(layers) { + const flatLayers = []; + flatLayers.push(...layers); + + let groupLayers = flatLayers.filter((l) => l instanceof Group); + + while (groupLayers.length) { + const newGroupLayers = []; + for (let i = 0; i < groupLayers.length; i++) { + const layersInsideGroup = groupLayers[i].getLayers().getArray(); + flatLayers.push(...layersInsideGroup); + newGroupLayers.push( + ...layersInsideGroup.filter((l) => l instanceof Group) + ); + } + groupLayers = newGroupLayers; + } + + return /** @type {Array} */ (flatLayers); +} diff --git a/elements/map/src-2/helpers/select.js b/elements/map/src-2/helpers/select.js new file mode 100644 index 000000000..2e497cf66 --- /dev/null +++ b/elements/map/src-2/helpers/select.js @@ -0,0 +1,274 @@ +import { Overlay } from "ol"; +import "../../src/tooltip"; +import { EOxMapTooltip } from "../../src/tooltip"; +import { createLayer } from "./generate"; +import Feature from "ol/Feature"; +import RenderFeature from "ol/render/Feature"; +import VectorLayer from "ol/layer/Vector"; +import { createEmpty, extend, isEmpty } from "ol/extent"; + +/** + * @typedef {import('../main').EOxMap} EOxMap + * @typedef {import("../../types").EoxLayer} EoxLayer + * @typedef {import("../../types").SelectLayer} SelectLayer + * @typedef {import("../../types").SelectOptions} SelectOptions + */ + +/** + * Class representing the EOxSelectInteraction. + */ +export class EOxSelectInteraction { + /** + * @param {EOxMap} eoxMap - Instance of EOxMap class. + * @param {SelectLayer} selectLayer - Layer for selection. + * @param {SelectOptions} options - Options for selection interaction. + */ + constructor(eoxMap, selectLayer, options) { + this.eoxMap = eoxMap; + this.selectLayer = selectLayer; + this.options = options; + this.active = options.active || selectLayer.getVisible(); + this.panIn = options.panIn || false; + + const existingTooltip = this.eoxMap.map.getOverlayById("eox-map-tooltip"); + + let overlay; + this.selectedFids = []; + + if (existingTooltip) { + this.tooltip = existingTooltip.getElement(); + overlay = existingTooltip; + } else { + this.tooltip = + this.eoxMap.querySelector("eox-map-tooltip") || + options.overlay?.element; + + if (this.tooltip) { + overlay = new Overlay({ + element: this.tooltip, + position: undefined, + offset: [0, 0], + positioning: "top-left", + className: "eox-map-tooltip", + id: "eox-map-tooltip", + ...options.overlay, + }); + this.eoxMap.map.addOverlay(overlay); + } + } + + const pointerLeaveListener = () => { + if (overlay && options.condition === "pointermove") { + overlay.setPosition(undefined); + } + }; + eoxMap.map.on("change:target", (e) => { + e.oldValue?.removeEventListener("pointerleave", pointerLeaveListener); + e.target + .getTargetElement() + ?.addEventListener("pointerleave", pointerLeaveListener); + }); + eoxMap.map + .getTargetElement() + ?.addEventListener("pointerleave", pointerLeaveListener); + + let layerDefinition; + if (this.options.layer) { + layerDefinition = this.options.layer; + } else { + const originalJsonDefinition = this.selectLayer.get("_jsonDefinition"); + layerDefinition = { + ...originalJsonDefinition, + style: options.style, + properties: { + id: this.selectLayer.get("id") + "_select", + }, + source: { + type: originalJsonDefinition.type, + }, + }; + } + layerDefinition.renderMode = "vector"; + delete layerDefinition.interactions; + + this.selectStyleLayer = createLayer(eoxMap, layerDefinition); + + //@ts-expect-error VectorSource for VectorLayer, VectorTileSource for VectorTileLayer + this.selectStyleLayer.setSource(this.selectLayer.getSource()); + this.selectStyleLayer.setMap(this.eoxMap.map); + + const initialStyle = this.selectStyleLayer.getStyleFunction(); + + this.selectStyleLayer.setStyle((feature, resolution) => { + if ( + this.selectedFids.length && + this.selectedFids.includes(this.getId(feature)) + ) { + return initialStyle(feature, resolution); + } + return null; + }); + + const listener = (event) => { + if (!this.active) { + return; + } + const currentZoom = this.eoxMap.map.getView().getZoom(); + if ( + event.dragging || + !this.active || + currentZoom < this.selectLayer.getMinZoom() || + currentZoom > this.selectLayer.getMaxZoom() + ) { + return; + } + this.selectLayer.getFeatures(event.pixel).then((features) => { + const feature = features.length ? features[0] : null; + + const newSelectFids = feature ? [this.getId(feature)] : []; + const idChanged = this.selectedFids[0] !== newSelectFids[0]; + this.selectedFids = newSelectFids; + if (idChanged) { + this.selectStyleLayer.changed(); + if (feature && this.panIn) this.panIntoFeature(feature); + } + + if (overlay) { + const xPosition = + event.pixel[0] > this.eoxMap.offsetWidth / 2 ? "right" : "left"; + const yPosition = + event.pixel[1] > this.eoxMap.offsetHeight / 2 ? "bottom" : "top"; + overlay.setPositioning(`${yPosition}-${xPosition}`); + overlay.setPosition(feature ? event.coordinate : null); + if (feature && this.tooltip.renderContent) { + this.tooltip.renderContent(feature); + } + } + + const selectdEvt = new CustomEvent("select", { + detail: { + id: options.id, + originalEvent: event, + feature: feature, + }, + }); + this.eoxMap.dispatchEvent(selectdEvt); + }); + }; + this.eoxMap.map.on(options.condition || "click", listener); + + this.selectLayer.on("change:opacity", () => { + this.selectStyleLayer.setOpacity(this.selectLayer.getOpacity()); + }); + + this.selectLayer.on("change:visible", () => { + const visible = this.selectLayer.getVisible(); + this.selectStyleLayer.setVisible(visible); + this.setActive(visible); + }); + + this.changeSourceListener = () => { + //@ts-expect-error VectorSource for VectorLayer, VectorTileSource for VectorTileLayer + this.selectStyleLayer.setSource(this.selectLayer.getSource()); + }; + + this.selectLayer.on("change:source", this.changeSourceListener); + + const changeLayerListener = () => { + if (eoxMap.getLayerById(selectLayer.get("id"))) { + eoxMap.selectInteractions[options.id]?.setActive(true); + this.selectStyleLayer?.setMap(this.eoxMap.map); + overlay?.setMap(this.eoxMap.map); + } else { + eoxMap.selectInteractions[options.id]?.setActive(false); + this.selectStyleLayer?.setMap(null); + overlay?.setMap(null); + } + }; + eoxMap.map.getLayerGroup().on("change", changeLayerListener); + } + + setActive(active) { + this.active = active; + } + + panIntoFeature = (featureOrExtent, options) => { + const extent = + featureOrExtent instanceof Feature || + featureOrExtent instanceof RenderFeature + ? featureOrExtent.getGeometry().getExtent() + : featureOrExtent; + this.eoxMap.map.getView().fit(extent, options || { duration: 750 }); + }; + + highlightById(ids, fitOptions) { + this.selectedFids = ids; + if (ids.length && fitOptions) { + const extent = createEmpty(); + if (this.selectLayer instanceof VectorLayer) { + for (let i = 0; i < this.selectedFids.length; i++) { + const id = this.selectedFids[i]; + const feature = this.selectLayer.getSource().getFeatureById(id); + if (feature && feature.getGeometry()) { + extend(extent, feature.getGeometry().getExtent()); + } + } + } else { + const map = this.eoxMap.map; + const allRenderedFeatures = this.selectLayer.getFeaturesInExtent( + map.getView().calculateExtent(map.getSize()) + ); + for (let i = 0; i < allRenderedFeatures.length; i++) { + const renderFeature = allRenderedFeatures[i]; + if (this.selectedFids.includes(this.getId(renderFeature))) { + extend(extent, renderFeature.getGeometry().getExtent()); + } + } + } + if (!isEmpty(extent)) { + this.panIntoFeature(extent, fitOptions); + } + } + this.selectStyleLayer.changed(); + } + + remove() { + this.selectStyleLayer.setMap(null); + delete this.eoxMap.selectInteractions[this.options.id]; + this.selectLayer.un("change:source", this.changeSourceListener); + } + + getId(feature) { + if (this.options.idProperty) { + return feature.get(this.options.idProperty); + } + if (feature.getId() !== undefined) { + return feature.getId(); + } + if (feature.get("id") !== undefined) { + return feature.get("id"); + } + throw Error( + "No feature id found. Please provide which feature property should be taken instead using idProperty." + ); + } +} + +/** + * Adds a `select`-interaction to the map. + * @param {EOxMap} EOxMap - Instance of EOxMap class. + * @param {SelectLayer} selectLayer - Layer to be selected. + * @param {SelectOptions} options - Options for the select interaction. + */ +export function addSelect(EOxMap, selectLayer, options) { + if (EOxMap.interactions[options.id]) { + throw Error(`Interaction with id: ${options.id} already exists.`); + } + EOxMap.selectInteractions[options.id] = new EOxSelectInteraction( + EOxMap, + selectLayer, + options + ); + + return EOxMap.selectInteractions[options.id]; +} diff --git a/elements/map/src-2/helpers/tile-grid.js b/elements/map/src-2/helpers/tile-grid.js new file mode 100644 index 000000000..8549f9157 --- /dev/null +++ b/elements/map/src-2/helpers/tile-grid.js @@ -0,0 +1,45 @@ +import { createXYZ } from "ol/tilegrid"; +import WMTSTileGrid from "ol/tilegrid/WMTS"; +import { getTopLeft, getWidth } from "ol/extent.js"; +import { get as getProjection } from "ol/proj.js"; + +/** + * Generates a WMTS tile grid for WMTS layers or else an XYZ tile grid, if defined. + * + * @param {EoxLayer} layer - The layer configuration object. + * @returns {import("ol/tilegrid/WMTS").default | import("ol/tilegrid/TileGrid").default | undefined} - The generated tile grid or undefined. + */ +export function generateTileGrid(layer) { + let tileGrid; + if (!layer.source?.tileGrid) { + return undefined; + } + + if (layer.source.tileGrid) { + if (layer.source.type === "WMTS") { + const projection = getProjection("EPSG:3857"); + const projectionExtent = projection.getExtent(); + const size = getWidth(projectionExtent) / 128; + const resolutions = new Array(19); + const matrixIds = new Array(19); + for (let z = 0; z < 19; ++z) { + // generate resolutions and matrixIds arrays for this WMTS + resolutions[z] = size / Math.pow(2, z); + matrixIds[z] = z; + } + + tileGrid = new WMTSTileGrid({ + resolutions: resolutions, + origin: getTopLeft(projectionExtent), + projection: layer.source.tileGrid.projection || "EPSG:3857", + matrixIds: matrixIds, + ...layer.source.tileGrid, + }); + } else { + tileGrid = createXYZ({ + ...layer.source.tileGrid, + }); + } + } + return tileGrid; +} diff --git a/elements/map/src-2/main.js b/elements/map/src-2/main.js index 8acd751c4..c9be4818c 100644 --- a/elements/map/src-2/main.js +++ b/elements/map/src-2/main.js @@ -3,9 +3,8 @@ import Map from "ol/Map.js"; import View from "ol/View.js"; import olCss from "ol/ol.css?inline"; import controlCss from "../src/controls/controls.css?inline"; -import { getLayerById, getFlatLayersArray } from "../src/layer"; import { buffer } from "ol/extent"; -import "../src/compare"; +import "./components/compare"; import { transform, transformExtent, @@ -13,6 +12,8 @@ import { parseTextToFeature, registerProjection, registerProjectionFromCode, + getLayerById, + getFlatLayersArray, } from "./helpers"; import { animateToStateMethod, diff --git a/elements/map/src-2/methods/map/add-update-layer.js b/elements/map/src-2/methods/map/add-update-layer.js index 1b24fb04c..81c980307 100644 --- a/elements/map/src-2/methods/map/add-update-layer.js +++ b/elements/map/src-2/methods/map/add-update-layer.js @@ -1,5 +1,4 @@ -import { createLayer, updateLayer } from "../../../src/generate"; -import { getLayerById } from "../../../src/layer"; +import { createLayer, updateLayer, getLayerById } from "../../helpers"; export default function addOrUpdateLayerMethod(json, EOxMap) { if (!json.interactions) { diff --git a/elements/map/src-2/methods/map/animate-to-state.js b/elements/map/src-2/methods/map/animate-to-state.js index 7960f9364..096f186f6 100644 --- a/elements/map/src-2/methods/map/animate-to-state.js +++ b/elements/map/src-2/methods/map/animate-to-state.js @@ -1,5 +1,4 @@ -import { cancelAnimation } from "../../helpers"; -import { getCenterFromProperty } from "../../../src/center.ts"; +import { cancelAnimation, getCenterFromProperty } from "../../helpers"; export default function animateToStateMethod(EOxMap) { const animateToOptions = Object.assign({}, EOxMap.animationOptions); diff --git a/elements/map/src-2/methods/map/setters.js b/elements/map/src-2/methods/map/setters.js index 63a94a42c..77a295960 100644 --- a/elements/map/src-2/methods/map/setters.js +++ b/elements/map/src-2/methods/map/setters.js @@ -1,12 +1,12 @@ -import { getCenterFromProperty } from "../../../src/center"; import { cancelAnimation, + getCenterFromProperty, addScrollInteractions, coordinatesRoughlyEquals, removeDefaultScrollInteractions, + getLayerById, } from "../../helpers"; import { addOrUpdateControl } from "../../../src/controls/controls"; -import { getLayerById } from "../../../src/layer"; import { get as getProjection, getPointResolution, diff --git a/elements/map/types.js b/elements/map/types.js new file mode 100644 index 000000000..fbfa8dfbd --- /dev/null +++ b/elements/map/types.js @@ -0,0 +1,79 @@ +/** + * @typedef {Object} DrawOptions + * @property {string | number} id - The ID of the interaction. + * @property {"Point" | "LineString" | "Polygon" | "Circle" | "Box"} type - The type of geometry to draw. + * @property {boolean} [modify] - Whether to add a modify interaction. + * @property {boolean} [active] - Whether draw tool active or not + */ + +/** + * @typedef {"Group" | "Heatmap" | "Image" | "Layer" | "Tile" | "Vector" | "VectorImage" | "VectorTile"} layerType + */ + +/** + * @typedef {"BingMaps" | "Cluster" | "GeoTIFF" | "IIIF" | "Image" | "ImageCanvas" | "ImageStatic" | "ImageWMS" | "OSM" | "Raster" | "StadiaMaps" | "Tile" | "TileArcGISRest" | "TileDebug" | "TileImage" | "TileJSON" | "TileWMS" | "UrlTile" | "Vector" | "VectorTile" | "WMTS" | "XYZ" | "WMTSCapabilities"} sourceType + */ + +/** + * @typedef {import("ol/layer/Vector").default | import("ol/layer/VectorTile").default} VectorOrVectorTileLayer + */ + +/** + * @typedef {import("ol/layer/BaseImage").default | import("ol/layer/Tile").default | VectorOrVectorTileLayer} AnyLayerWithSource + */ + +/** + * @typedef {AnyLayerWithSource | import("ol/layer/Group").default} AnyLayer + */ + +/** + * @typedef {Object} formatWithOptions + * @property {string} type + * @property {string} [dataProjection] + * @property {string} [featureProjection] + */ + +/** + * @typedef {Object} EOxInteraction + * @property {"draw" | "select"} type + * @property {DrawOptions | SelectOptions} options + */ + +/** + * @typedef {Object} EoxLayer + * @property {layerType} type + * @property {Object} [properties] + * @property {string} properties.id + * @property {number} [minZoom] + * @property {number} [maxZoom] + * @property {number} [minResolution] + * @property {number} [maxResolution] + * @property {number} [opacity] + * @property {boolean} [visible] + * @property {Object} [source] + * @property {sourceType} source.type + * @property {string | formatWithOptions} [source.format] + * @property {Object} [source.tileGrid] + * @property {import("ol/proj").ProjectionLike} [source.projection] + * @property {Array} [layers] + * @property {import("ol/style/flat").FlatStyleLike} [style] + * @property {Array} [interactions] + * @property {number} [zIndex] + * @property {"vector" | "vectorImage"} [renderMode] + */ + +/** + * @typedef {import('ol/layer/VectorTile').default | import('ol/layer/Vector').default} SelectLayer + */ + +/** + * @typedef {Object} SelectOptions + * @property {string | number} id - The ID of the interaction. + * @property {string} [idProperty] - Optional property used as ID. + * @property {"click" | "pointermove"} condition - Condition for the interaction (click or pointermove). + * @property {EoxLayer} [layer] - Optional layer configuration. + * @property {import('ol/style/flat.js').FlatStyleLike} [style] - Style options for the interaction. + * @property {import('ol/Overlay').Options} [overlay] - Overlay options for the interaction. + * @property {boolean} [active] - Whether the interaction is active. + * @property {boolean} [panIn] - Whether to pan into the selected feature. + */ From 02946576b353f9512d04bf5c82debb6695df695d Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Mon, 16 Sep 2024 14:20:21 +0530 Subject: [PATCH 11/24] refactor: controls, custom, plugina nd tooltip ported from ts to js --- elements/map/map.stories.js | 3 +- elements/map/src-2/components/tooltip.js | 55 ++++++ elements/map/src-2/controls/controls.js | 91 ++++++++++ elements/map/src-2/controls/geo-location.js | 163 ++++++++++++++++++ .../map/src-2/controls/loading-indicator.js | 70 ++++++++ .../src-2/custom/sources/WMTSCapabilities.js | 135 +++++++++++++++ elements/map/src-2/helpers/center.js | 2 - elements/map/src-2/helpers/generate.js | 14 ++ elements/map/src-2/helpers/select.js | 3 +- elements/map/src-2/main.js | 2 +- elements/map/src-2/methods/map/setters.js | 2 +- .../advancedLayersAndSources/formats.js | 3 + .../plugins/advancedLayersAndSources/index.js | 3 + .../advancedLayersAndSources/layers.js | 11 ++ .../advancedLayersAndSources/sources.js | 7 + elements/map/src/tooltip.ts | 2 +- elements/map/test/stacLayer.cy.ts | 2 +- elements/map/test/wmtsCapabilities.cy.ts | 2 +- elements/map/types.js | 76 ++++++++ .../vite.config.advancedLayersAndSources.ts | 2 +- 20 files changed, 636 insertions(+), 12 deletions(-) create mode 100644 elements/map/src-2/components/tooltip.js create mode 100644 elements/map/src-2/controls/controls.js create mode 100644 elements/map/src-2/controls/geo-location.js create mode 100644 elements/map/src-2/controls/loading-indicator.js create mode 100644 elements/map/src-2/custom/sources/WMTSCapabilities.js create mode 100644 elements/map/src-2/plugins/advancedLayersAndSources/formats.js create mode 100644 elements/map/src-2/plugins/advancedLayersAndSources/index.js create mode 100644 elements/map/src-2/plugins/advancedLayersAndSources/layers.js create mode 100644 elements/map/src-2/plugins/advancedLayersAndSources/sources.js diff --git a/elements/map/map.stories.js b/elements/map/map.stories.js index d511a65c1..4349e8875 100644 --- a/elements/map/map.stories.js +++ b/elements/map/map.stories.js @@ -567,8 +567,7 @@ export const TooltipWithPropertyTransform = { .zoom=${args.zoom} > { - console.log(hoverFeature); + .propertyTransform=${({ key, value }) => { if (key.includes("COLOR")) { return { key: key.toLowerCase(), value }; } diff --git a/elements/map/src-2/components/tooltip.js b/elements/map/src-2/components/tooltip.js new file mode 100644 index 000000000..22a7cce3d --- /dev/null +++ b/elements/map/src-2/components/tooltip.js @@ -0,0 +1,55 @@ +import { html, render } from "lit"; +import { TemplateElement } from "../../../../utils/templateElement"; + +export class EOxMapTooltip extends TemplateElement { + static get properties() { + return { + propertyTransform: { attribute: false, property: true, type: Function }, + }; + } + + constructor() { + super(); + this.propertyTransform = (property, _feature) => property; + } + + renderContent(feature) { + render( + this.hasTemplate("properties") + ? html`${this.renderTemplate( + "properties", + feature.getProperties(), + // `tooltip-${this.content.id}` + "tooltip-1" + )}` + : html` +
    + ${Object.entries(feature.getProperties()) + .map(([key, value]) => + this.propertyTransform({ key, value }, feature) + ) + .filter((v) => v) + .map( + ({ key, value }) => + html`
  • ${key}: ${value}
  • ` + )} +
`, + this.shadowRoot + ); + } +} + +customElements.define("eox-map-tooltip", EOxMapTooltip); diff --git a/elements/map/src-2/controls/controls.js b/elements/map/src-2/controls/controls.js new file mode 100644 index 000000000..901667f7b --- /dev/null +++ b/elements/map/src-2/controls/controls.js @@ -0,0 +1,91 @@ +import * as olControls from "ol/control"; +import { generateLayers } from "../helpers/generate"; +import Geolocation from "./geo-location"; +import LoadingIndicator from "./loading-indicator"; + +/** + * @typedef {import("../../types").ControlDictionary} ControlDictionary + * @typedef {import("../../types").ControlType} ControlType + */ + +const availableControls = { + ...olControls, + Geolocation, + LoadingIndicator, +}; + +/** + * Adds a control to the map and to the `mapControls` dictionary if it doesn't exist yet. + * Removes and remakes the control if the options differ; otherwise, leaves the control untouched. + * + * @param {import("../main").EOxMap} EOxMap - The map instance. + * @param {ControlDictionary} existingControls - Dictionary of existing controls. + * @param {ControlType} type - The type of control to add or update. + * @param {object} options - Options for the control. + */ +export function addOrUpdateControl(EOxMap, existingControls, type, options) { + if (existingControls && existingControls[type]) { + const controlHasChanged = + JSON.stringify(existingControls[type]) !== JSON.stringify(options); + if (controlHasChanged) { + EOxMap.removeControl(type); + addControl(EOxMap, type, options); + } + } else { + addControl(EOxMap, type, options); + } +} + +/** + * Adds a control to the map. + * + * @param {import("../main").EOxMap} EOxMap - The map instance. + * @param {ControlType} type - The type of control to add. + * @param {object} options - Options for the control. + */ +export function addControl(EOxMap, type, options) { + const controlOptions = Object.assign({}, options); + + if (options && options.layers) { + controlOptions.layers = generateLayers(EOxMap, options.layers); // Parse layers (e.g., for OverviewMap) + } + + const control = new availableControls[type](controlOptions); + EOxMap.map.addControl(control); + EOxMap.mapControls[type] = control; +} + +/** + * Adds initial controls from web component properties, if any are given. + * + * @param {import("../main").EOxMap} EOxMap - The map instance. + */ +export function addInitialControls(EOxMap) { + const controls = /** @type {ControlDictionary | Array} */ ( + EOxMap.controls + ); + + if (controls) { + if (Array.isArray(controls)) { + controls.forEach((controlName) => { + const control = new availableControls[controlName]({}); + EOxMap.map.addControl(control); + EOxMap.mapControls[controlName] = control; + }); + } else { + const keys = Object.keys(controls); + for (let i = 0; i < keys.length; i++) { + const controlName = /** @type {ControlType} */ (keys[i]); + const controlOptions = controls[controlName]; + + if (controlOptions && controlOptions.layers) { + controlOptions.layers = generateLayers(EOxMap, controlOptions.layers); // Parse layers (e.g., for OverviewMap) + } + + const control = new availableControls[controlName](controlOptions); + EOxMap.map.addControl(control); + EOxMap.mapControls[controlName] = control; + } + } + } +} diff --git a/elements/map/src-2/controls/geo-location.js b/elements/map/src-2/controls/geo-location.js new file mode 100644 index 000000000..bd18f296e --- /dev/null +++ b/elements/map/src-2/controls/geo-location.js @@ -0,0 +1,163 @@ +import { Control } from "ol/control"; +import Feature from "ol/Feature"; +import Geolocation from "ol/Geolocation"; +import Point from "ol/geom/Point"; +import { Vector as VectorSource } from "ol/source"; +import { Vector as VectorLayer } from "ol/layer"; +import { Fill, Stroke, Style } from "ol/style"; + +/** + * @typedef {import("../../types").GeolocationOptions} GeolocationOptions + */ + +export default class GeolocationControl extends Control { + /** + * @param {GeolocationOptions} [opt_options] - Control options. + */ + constructor(opt_options) { + const options = opt_options || {}; + + const element = document.createElement("div"); + element.className = "geolocation ol-unselectable ol-control"; + const button = document.createElement("button"); + button.title = "Show your location"; + const image = document.createElement("img"); + + if (options.buttonIcon) { + image.src = options.buttonIcon; + } else { + // Fallback icon + const icon = `url( + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-6 -6 36 36'%3E %3Cpath d='M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M3.05,13H1V11H3.05C3.5,6.83 6.83,3.5 11,3.05V1H13V3.05C17.17,3.5 20.5,6.83 20.95,11H23V13H20.95C20.5,17.17 17.17,20.5 13,20.95V23H11V20.95C6.83,20.5 3.5,17.17 3.05,13M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z' /%3E%3C/svg%3E" + )`; + image.style.background = "var(--ol-subtle-foreground-color)"; + image.style.maskImage = icon; + image.style.webkitMaskImage = icon; + } + + image.style.height = "100%"; + image.style.width = "100%"; + image.style.position = "absolute"; + image.style.pointerEvents = "none"; + element.appendChild(image); + element.appendChild(button); + + super({ element: element }); + + this._centerWhenReady = options.centerWhenReady; + this._highAccuracy = options.highAccuracy; + this._trackAccuracy = options.trackAccuracy; + this._trackHeading = options.trackHeading; + + this._positionFeature = new Feature({ + geometry: new Point([NaN, NaN]), + heading: 0, + }); + + this._source = new VectorSource({ + features: [this._positionFeature], + }); + + if (this._trackAccuracy) { + this._accuracyFeature = new Feature(); + this._accuracyFeature.setStyle( + new Style({ + fill: new Fill({ color: "rgba(0, 0, 0, 0.2)" }), + stroke: new Stroke({ width: 2, color: "rgba(0, 0, 0, 0.7)" }), + }) + ); + this._source.addFeature(this._accuracyFeature); + } + + this._layer = new VectorLayer({ source: this._source }); + + if (options.style) { + this._layer.setStyle(options.style); + } + + button.addEventListener("click", this.centerOnPosition.bind(this), false); + } + + /** + * Remove the control from its current map and attach it to the new map. + * Pass `null` to just remove the control from the current map. + * @param {import("ol/Map").default|null} map - Map instance. + * @api + */ + setMap(map) { + this._layer.setMap(map); + super.setMap(map); + if (map && this._centerWhenReady) { + this.initGeolocation(); + } + } + + /** + * Initializes the geolocation control. + * Calling this will cause a user prompt about allowing geolocation in the browser. + * @returns {Promise} Returns a promise that resolves to coordinates on success, or an error event. + */ + initGeolocation() { + return new Promise((resolve, reject) => { + const map = this.getMap(); + if (map) { + this._geolocation = new Geolocation({ + tracking: true, + trackingOptions: { enableHighAccuracy: this._highAccuracy }, + projection: map.getView().getProjection(), + }); + } + + if (this._centerWhenReady) { + this._geolocation.once("change:position", (e) => { + map.getView().setCenter(e.target.getPosition()); + }); + } + + this._geolocation.on("error", (evt) => reject(evt)); + + this._geolocation.on("change:accuracyGeometry", () => { + if (this._trackAccuracy) { + this._accuracyFeature.setGeometry( + this._geolocation.getAccuracyGeometry() + ); + } + }); + + this._geolocation.on("change:heading", (e) => { + if (this._trackHeading && this._highAccuracy) { + this._positionFeature.set("heading", e.target.getHeading()); + } + }); + + this._geolocation.on("change:position", () => { + const coordinates = this._geolocation.getPosition(); + this._positionFeature.getGeometry().setCoordinates(coordinates || null); + resolve(coordinates); + }); + }); + } + + /** + * Returns the geolocation control button. + * @returns {HTMLElement} The control element. + */ + getElement() { + return this.element; + } + + /** + * Centers the map on the position of the geolocation, if possible. + */ + async centerOnPosition() { + try { + await this.initGeolocation(); + const coordinates = this._geolocation?.getPosition(); + if (coordinates) { + this.getMap()?.getView().setCenter(coordinates); + } + } catch (e) { + console.error(e); + } + } +} diff --git a/elements/map/src-2/controls/loading-indicator.js b/elements/map/src-2/controls/loading-indicator.js new file mode 100644 index 000000000..9fac22bfb --- /dev/null +++ b/elements/map/src-2/controls/loading-indicator.js @@ -0,0 +1,70 @@ +import { Control } from "ol/control.js"; + +/** + * @typedef {import("../../types").LoadingIndicatorOptions} LoadingIndicatorOptions + */ + +export default class LoadingIndicatorControl extends Control { + /** + * @param {LoadingIndicatorOptions} [opt_options] - Control options. + */ + constructor(opt_options) { + const options = opt_options || {}; + if (options.opacity === undefined) { + options.opacity = 1; + } + if (options.type === undefined) { + options.type = "small"; + } + + const element = document.createElement("div"); + element.className = "LoadingIndicator ol-unselectable ol-control"; + element.classList.add("loading-indicator"); + element.style.opacity = String(options.opacity); + + if (options.spinnerSvg) { + element.innerHTML = options.spinnerSvg; + } else { + // Fallback icon + element.innerHTML = ` + + `; + } + + if (options.type === "fullscreen") { + element.classList.add("fullscreen"); + } else { + element.classList.add("small"); + } + + super({ element: element }); + } + + /** + * Remove the control from its current map and attach it to the new map. + * Pass `null` to just remove the control from the current map. + * Subclasses may set up event handlers to get notified about changes to + * the map here. + * @param {import("ol/Map").default|null} map - Map instance. + * @api + */ + setMap(map) { + super.setMap(map); + if (map) { + map.on("loadstart", () => { + this.getElement().style.visibility = "visible"; + }); + map.on("loadend", () => { + this.getElement().style.visibility = "hidden"; + }); + } + } + + /** + * Returns the loading indicator element. + * @returns {HTMLElement} - The control element. + */ + getElement() { + return this.element; + } +} diff --git a/elements/map/src-2/custom/sources/WMTSCapabilities.js b/elements/map/src-2/custom/sources/WMTSCapabilities.js new file mode 100644 index 000000000..a0870cfa1 --- /dev/null +++ b/elements/map/src-2/custom/sources/WMTSCapabilities.js @@ -0,0 +1,135 @@ +import { TileImage } from "ol/source"; +import { optionsFromCapabilities } from "ol/source/WMTS"; +import WMTSCapabilitiesFormat from "ol/format/WMTSCapabilities"; +import { createFromTileUrlFunctions } from "ol/tileurlfunction"; +import { appendParams } from "ol/uri"; + +/** + * @typedef {import("../../../types").WMTSCapabilitiesOptions} WMTSCapabilitiesOptions + */ + +class WMTSCapabilities extends TileImage { + /** + * @param {WMTSCapabilitiesOptions} options - Image tile options. + */ + constructor(options) { + super({ + attributions: options.attributions, + cacheSize: options.cacheSize, + tilePixelRatio: options.tilePixelRatio, + transition: options.transition, + interpolate: + options.interpolate !== undefined ? options.interpolate : true, + key: options.key, + attributionsCollapsible: options.attributionsCollapsible, + zDirection: options.zDirection, + wrapX: options.wrapX, + }); + + this.version_ = options.version !== undefined ? options.version : "1.0.0"; + this.dimensions_ = + options.dimensions !== undefined ? options.dimensions : {}; + this.layer_ = options.layer; + + fetch(options.url) + .then((response) => response.text()) + .then((xml) => + new window.DOMParser().parseFromString(xml, "application/xml") + ) + .then((xml) => { + this.handleCapabilitiesResponse(xml, options); + }); + } + + /** + * @param {Document} xml - The XML document containing WMTS capabilities. + * @param {WMTSCapabilitiesOptions} options - Options for WMTS capabilities. + */ + handleCapabilitiesResponse(xml, options) { + const format = new WMTSCapabilitiesFormat(); + const parsedXml = format.read(xml); + const capabilitiesOptions = optionsFromCapabilities(parsedXml, options); + + this.crossOrigin = capabilitiesOptions.crossOrigin; + this.projection = /** @type {import("ol/proj").Projection} */ ( + capabilitiesOptions.projection + ); + this.tileGrid = capabilitiesOptions.tileGrid; + this.requestEncoding_ = capabilitiesOptions.requestEncoding; + this.matrixSet_ = capabilitiesOptions.matrixSet; + this.style_ = capabilitiesOptions.style; + this.format_ = capabilitiesOptions.format; + this.dimensions_ = capabilitiesOptions.dimensions; + this.setUrls(capabilitiesOptions.urls); + + if (this.urls && this.urls.length > 0) { + this.tileUrlFunction = createFromTileUrlFunctions( + this.urls.map(this.createFromWMTSTemplate.bind(this)) + ); + } + this.setState("ready"); + } + + /** + * @param {string} template - The WMTS URL template. + * @return {import("../Tile.js").UrlFunction} - The tile URL function. + */ + createFromWMTSTemplate(template) { + const requestEncoding = this.requestEncoding_; + + const context = { + layer: this.layer_, + style: this.style_, + tilematrixset: this.matrixSet_, + }; + + if (requestEncoding === "KVP") { + Object.assign(context, { + Service: "WMTS", + Request: "GetTile", + Version: this.version_, + Format: this.format_, + }); + } + + template = + requestEncoding === "KVP" + ? appendParams(template, context) + : template.replace(/\{(\w+?)\}/g, (m, p) => + p.toLowerCase() in context ? context[p.toLowerCase()] : m + ); + + const tileGrid = /** @type {import("../tilegrid/WMTS.js").default} */ ( + this.tileGrid + ); + const dimensions = this.dimensions_; + + return ( + /** + * @param {import("../tilecoord.js").TileCoord} tileCoord - The tile coordinate. + * @return {string|undefined} - The tile URL. + */ + function (tileCoord) { + if (!tileCoord) { + return undefined; + } + const localContext = { + TileMatrix: tileGrid.getMatrixId(tileCoord[0]), + TileCol: tileCoord[1], + TileRow: tileCoord[2], + }; + Object.assign(localContext, dimensions); + + let url = template; + if (requestEncoding === "KVP") { + url = appendParams(url, localContext); + } else { + url = url.replace(/\{(\w+?)\}/g, (_, p) => localContext[p]); + } + return url; + } + ); + } +} + +export default WMTSCapabilities; diff --git a/elements/map/src-2/helpers/center.js b/elements/map/src-2/helpers/center.js index d6b834245..81a7c6f75 100644 --- a/elements/map/src-2/helpers/center.js +++ b/elements/map/src-2/helpers/center.js @@ -14,8 +14,6 @@ import { fromLonLat } from "ol/proj"; export function getCenterFromProperty(centerProperty) { if (centerProperty) { const coordinate = centerProperty; - // compare: - // https://github.com/openlayers/openlayers/blob/v7.4.0/src/ol/proj.js if ( !equals(coordinate, [0, 0]) && coordinate[0] >= -180 && diff --git a/elements/map/src-2/helpers/generate.js b/elements/map/src-2/helpers/generate.js index e664718d9..962f93aad 100644 --- a/elements/map/src-2/helpers/generate.js +++ b/elements/map/src-2/helpers/generate.js @@ -220,6 +220,20 @@ export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { return existingLayer; } +/** + * Generates layers for the map from an array of layer definitions. + * + * @param {EOxMap} EOxMap - The map instance. + * @param {Array} layerArray - Array of layer definitions. + * @returns {Array} - An array of created layers. + */ +export const generateLayers = (EOxMap, layerArray) => { + if (!layerArray) { + return []; + } + return [...layerArray].reverse().map((l) => createLayer(EOxMap, l)); +}; + /** * Set listeners to keep the state of the layer in sync with the input JSON definition. * diff --git a/elements/map/src-2/helpers/select.js b/elements/map/src-2/helpers/select.js index 2e497cf66..e2773dbe3 100644 --- a/elements/map/src-2/helpers/select.js +++ b/elements/map/src-2/helpers/select.js @@ -1,6 +1,5 @@ import { Overlay } from "ol"; -import "../../src/tooltip"; -import { EOxMapTooltip } from "../../src/tooltip"; +import "../components/tooltip"; import { createLayer } from "./generate"; import Feature from "ol/Feature"; import RenderFeature from "ol/render/Feature"; diff --git a/elements/map/src-2/main.js b/elements/map/src-2/main.js index c9be4818c..47c6c05e0 100644 --- a/elements/map/src-2/main.js +++ b/elements/map/src-2/main.js @@ -2,7 +2,7 @@ import { LitElement, html } from "lit"; import Map from "ol/Map.js"; import View from "ol/View.js"; import olCss from "ol/ol.css?inline"; -import controlCss from "../src/controls/controls.css?inline"; +import controlCss from "./controls/controls.css?inline"; import { buffer } from "ol/extent"; import "./components/compare"; import { diff --git a/elements/map/src-2/methods/map/setters.js b/elements/map/src-2/methods/map/setters.js index 77a295960..5fd7e218f 100644 --- a/elements/map/src-2/methods/map/setters.js +++ b/elements/map/src-2/methods/map/setters.js @@ -6,7 +6,7 @@ import { removeDefaultScrollInteractions, getLayerById, } from "../../helpers"; -import { addOrUpdateControl } from "../../../src/controls/controls"; +import { addOrUpdateControl } from "../../controls/controls"; import { get as getProjection, getPointResolution, diff --git a/elements/map/src-2/plugins/advancedLayersAndSources/formats.js b/elements/map/src-2/plugins/advancedLayersAndSources/formats.js new file mode 100644 index 000000000..7f0ce7280 --- /dev/null +++ b/elements/map/src-2/plugins/advancedLayersAndSources/formats.js @@ -0,0 +1,3 @@ +import * as olFormats from "ol/format"; + +window.eoxMapAdvancedOlFormats = olFormats; diff --git a/elements/map/src-2/plugins/advancedLayersAndSources/index.js b/elements/map/src-2/plugins/advancedLayersAndSources/index.js new file mode 100644 index 000000000..3a441d777 --- /dev/null +++ b/elements/map/src-2/plugins/advancedLayersAndSources/index.js @@ -0,0 +1,3 @@ +import "./formats"; +import "./layers"; +import "./sources"; diff --git a/elements/map/src-2/plugins/advancedLayersAndSources/layers.js b/elements/map/src-2/plugins/advancedLayersAndSources/layers.js new file mode 100644 index 000000000..0b3900ebd --- /dev/null +++ b/elements/map/src-2/plugins/advancedLayersAndSources/layers.js @@ -0,0 +1,11 @@ +import * as olLayers from "ol/layer"; +import STAC from "ol-stac"; +import { register } from "ol/proj/proj4"; +import proj4 from "proj4"; + +register(proj4); // required to support source reprojection + +window.eoxMapAdvancedOlLayers = { + ...olLayers, + STAC, +}; diff --git a/elements/map/src-2/plugins/advancedLayersAndSources/sources.js b/elements/map/src-2/plugins/advancedLayersAndSources/sources.js new file mode 100644 index 000000000..796bce202 --- /dev/null +++ b/elements/map/src-2/plugins/advancedLayersAndSources/sources.js @@ -0,0 +1,7 @@ +import * as olSources from "ol/source"; +import WMTSCapabilities from "../../custom/sources/WMTSCapabilities"; + +window.eoxMapAdvancedOlSources = { + ...olSources, + WMTSCapabilities, +}; diff --git a/elements/map/src/tooltip.ts b/elements/map/src/tooltip.ts index c20d32506..16206ce80 100644 --- a/elements/map/src/tooltip.ts +++ b/elements/map/src/tooltip.ts @@ -63,4 +63,4 @@ export class EOxMapTooltip extends TemplateElement { } } -customElements.define("eox-map-tooltip", EOxMapTooltip); +customElements.define("eox-map-tooltip-2", EOxMapTooltip); diff --git a/elements/map/test/stacLayer.cy.ts b/elements/map/test/stacLayer.cy.ts index 78fd9c3f5..5985c9c69 100644 --- a/elements/map/test/stacLayer.cy.ts +++ b/elements/map/test/stacLayer.cy.ts @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src/plugins/advancedLayersAndSources/index"; +import "../src-2/plugins/advancedLayersAndSources/index"; import "../src-2/main"; import stacLayerJson from "./stacLayer.json"; import stacFixture from "./fixtures/stac.json"; diff --git a/elements/map/test/wmtsCapabilities.cy.ts b/elements/map/test/wmtsCapabilities.cy.ts index cbfadb5db..f394b595d 100644 --- a/elements/map/test/wmtsCapabilities.cy.ts +++ b/elements/map/test/wmtsCapabilities.cy.ts @@ -1,6 +1,6 @@ import { html } from "lit"; import "../main"; -import "../src/plugins/advancedLayersAndSources/index"; +import "../src-2/plugins/advancedLayersAndSources/index"; describe("WMTS Capabilities Source", () => { it("loads a layer from WMTS capabilities", () => { diff --git a/elements/map/types.js b/elements/map/types.js index fbfa8dfbd..d59168aea 100644 --- a/elements/map/types.js +++ b/elements/map/types.js @@ -77,3 +77,79 @@ * @property {boolean} [active] - Whether the interaction is active. * @property {boolean} [panIn] - Whether to pan into the selected feature. */ + +/** + * @typedef {import('ol/control/Control').Options} ControlOptions + */ + +/** + * @typedef {Object} GeolocationOptions + * @extends ControlOptions + * + * @property {import('ol/style/flat.js').FlatStyleLike} [style] - Style definition of the position feature. + * @property {boolean} [centerWhenReady] - Will pan the view to the user location on the first position update. + * @property {boolean} [highAccuracy] - Enables high accuracy of geolocator. Required for tracking the heading. + * @property {boolean} [trackAccuracy] - Tracks accuracy and displays it as a circle underneath the position feature. + * @property {boolean} [trackHeading] - Tracks heading and sets it as a 'heading' property on the position feature. "highAccuracy" must be set in order to track heading. + * @property {string} [buttonIcon] - Image source of the control element icon. + */ + +/** + * @typedef {"small" | "fullscreen"} LoadingIndicatorType + */ + +/** + * @typedef {import('ol/control/Control').Options} ControlOptions + */ + +/** + * @typedef {Object} LoadingIndicatorOptions + * @extends ControlOptions + * + * @property {string} [spinnerSvg] - SVG to be used as the spinner icon. + * @property {number} [opacity] - Opacity, defaults to 1. + * @property {LoadingIndicatorType} [type] - Type of appearance: "small" or "fullscreen". Defaults to "button" style. + */ + +/** + * @typedef {"Attribution" | "FullScreen" | "MousePosition" | "OverviewMap" | "Rotate" | "ScaleLine" | "ZoomSlider" | "ZoomToExtent" | "Zoom" | "Geolocation" | "LoadingIndicator"} ControlType + */ + +/** + * @typedef {Object} ControlDictionary + * @property {Object} [Attribution] - Options for the Attribution control. + * @property {Object} [FullScreen] - Options for the FullScreen control. + * @property {Object} [MousePosition] - Options for the MousePosition control. + * @property {Object} [OverviewMap] - Options for the OverviewMap control. + * @property {Object} [Rotate] - Options for the Rotate control. + * @property {Object} [ScaleLine] - Options for the ScaleLine control. + * @property {Object} [ZoomSlider] - Options for the ZoomSlider control. + * @property {Object} [ZoomToExtent] - Options for the ZoomToExtent control. + * @property {Object} [Zoom] - Options for the Zoom control. + * @property {Object} [Geolocation] - Options for the Geolocation control. + * @property {Object} [LoadingIndicator] - Options for the LoadingIndicator control. + */ + +/** + * @typedef {import('ol/Attribution').default | string | Array} AttributionLike + * @typedef {import('ol/proj').ProjectionLike} ProjectionLike + */ + +/** + * @typedef {Object} WMTSCapabilitiesOptions + * @property {string} url - The URL for the WMTS service. + * @property {string} layer - The layer name. + * @property {AttributionLike} [attributions] - Attributions for the layer. + * @property {boolean} [attributionsCollapsible] - Whether the attributions are collapsible. + * @property {number} [cacheSize] - The cache size. + * @property {null | string} [crossOrigin] - The cross-origin attribute. + * @property {boolean} [interpolate] - Whether to interpolate pixel values. + * @property {ProjectionLike} [projection] - The map projection. + * @property {number} [transition] - The duration of the transition. + * @property {string} [key] - Optional key for the layer. + * @property {number} [tilePixelRatio] - Pixel ratio for the tiles. + * @property {number} [zDirection] - Z-direction for tile rendering. + * @property {boolean} [wrapX] - Whether to wrap the X-axis. + * @property {Object} dimensions - Dimensions for the WMTS request. + * @property {string} version - The WMTS version. + */ diff --git a/elements/map/vite.config.advancedLayersAndSources.ts b/elements/map/vite.config.advancedLayersAndSources.ts index 034a50814..3dac2b0bd 100644 --- a/elements/map/vite.config.advancedLayersAndSources.ts +++ b/elements/map/vite.config.advancedLayersAndSources.ts @@ -4,7 +4,7 @@ import { defineConfig } from "vite"; export default defineConfig({ build: { lib: { - entry: "./src/plugins/advancedLayersAndSources/index.ts", + entry: "./src-2/plugins/advancedLayersAndSources/index.ts", name: "eox-map-advanced-layers-and-sources", // the proper extensions will be added fileName: "eox-map-advanced-layers-and-sources", From 030697d693062e23812c256c66f1dbd353b624af Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Mon, 16 Sep 2024 16:10:50 +0530 Subject: [PATCH 12/24] chores(test): Fix failing text due to partial refactor of updateLayer() function --- elements/map/src-2/helpers/generate.js | 110 ++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/elements/map/src-2/helpers/generate.js b/elements/map/src-2/helpers/generate.js index 962f93aad..e006fb011 100644 --- a/elements/map/src-2/helpers/generate.js +++ b/elements/map/src-2/helpers/generate.js @@ -177,15 +177,18 @@ function addInteraction(EOxMap, olLayer, interactionDefinition) { export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { const existingJsonDefinition = existingLayer.get("_jsonDefinition"); + // Check if the new layer is compatible with the existing one if ( newLayerDefinition.type !== existingJsonDefinition.type || newLayerDefinition.source?.type !== existingJsonDefinition.source?.type ) { - throw new Error("Layers are not compatible to be updated"); + throw new Error(`Layers are not compatible to be updated`); } + // Create a new layer to update properties, source, and interactions if needed const newLayer = createLayer(EOxMap, newLayerDefinition, false); + // Update source if different if ( JSON.stringify(newLayerDefinition.source) !== JSON.stringify(existingJsonDefinition.source) @@ -193,6 +196,7 @@ export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { existingLayer.setSource(newLayer.getSource()); } + // Update style if different if ( ["Vector", "VectorTile"].includes(newLayerDefinition.type) && JSON.stringify(newLayerDefinition.style) !== @@ -201,6 +205,7 @@ export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { existingLayer.setStyle(newLayer.getStyle()); } + // Update properties if different if ( JSON.stringify(newLayerDefinition.properties) !== JSON.stringify(existingJsonDefinition.properties) @@ -208,6 +213,7 @@ export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { existingLayer.setProperties(newLayerDefinition.properties); } + // Update visibility and opacity if different if (newLayerDefinition.visible !== existingJsonDefinition.visible) { existingLayer.setVisible(newLayerDefinition.visible); } @@ -215,8 +221,110 @@ export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { existingLayer.setOpacity(newLayerDefinition.opacity); } + // Update interactions if different + if ( + JSON.stringify(newLayerDefinition.interactions) !== + JSON.stringify(existingJsonDefinition.interactions) + ) { + existingJsonDefinition.interactions?.forEach((interactionDefinition) => { + const correspondingNewInteraction = newLayerDefinition.interactions.find( + (i) => i.type === interactionDefinition.type + ); + + if (!correspondingNewInteraction) { + // Remove interactions that don't exist in the new definition + EOxMap.removeInteraction(interactionDefinition.options.id); + } else { + // Update existing interaction if it has changed + if (correspondingNewInteraction.type === "draw") { + const olDrawInteraction = + EOxMap.interactions[correspondingNewInteraction.options.id]; + olDrawInteraction.setActive( + correspondingNewInteraction.options.active + ); + + const olModifyInteraction = + EOxMap.interactions[ + `${correspondingNewInteraction.options.id}_modify` + ]; + olModifyInteraction.setActive( + correspondingNewInteraction.options.modify + ); + } else { + const olSelectInteraction = + EOxMap.selectInteractions[correspondingNewInteraction.options.id]; + olSelectInteraction.setActive( + correspondingNewInteraction.options.active + ); + } + } + }); + + // Add new interactions + newLayerDefinition.interactions?.forEach((interactionDefinition) => { + const correspondingExistingInteraction = + existingJsonDefinition.interactions.find( + (i) => i.type === interactionDefinition.type + ); + + if (!correspondingExistingInteraction) { + addInteraction(EOxMap, existingLayer, interactionDefinition); + } + }); + } + + // Update group layers if the layer is a group + if (newLayerDefinition.type === "Group") { + const newLayerIds = newLayerDefinition.layers.map((l) => l.properties?.id); + const layerCollection = existingLayer.getLayers(); + + // Create a shallow copy of the layers to avoid modifying the collection while iterating + const layerArray = layerCollection.getArray().slice(); + + // Remove layers that do not exist in the new definition + layerArray.forEach((l) => { + if (!newLayerIds.includes(l.get("id"))) { + layerCollection.remove(l); + } + }); + + // Add or update layers in the group + newLayerDefinition.layers.forEach((layerDefinitionInsideGroup) => { + const newLayerId = layerDefinitionInsideGroup.properties.id; + if ( + layerCollection + .getArray() + .map((l) => l.get("id")) + .includes(newLayerId) + ) { + // Layer already exists, update it + updateLayer( + EOxMap, + layerDefinitionInsideGroup, + EOxMap.getLayerById(newLayerId) + ); + } else { + // New layer, add it to the group + const newLayer = createLayer(EOxMap, layerDefinitionInsideGroup); + layerCollection.push(newLayer); + } + }); + + // Reorder the layers to match the new definition + layerCollection.getArray().sort((layerA, layerB) => { + return ( + newLayerIds.indexOf(layerA.get("id")) - + newLayerIds.indexOf(layerB.get("id")) + ); + }); + + layerCollection.changed(); + } + + // Synchronize properties setSyncListeners(existingLayer, newLayerDefinition); existingLayer.set("_jsonDefinition", newLayerDefinition, true); + return existingLayer; } From b763af964c2a9e3e4b058b6cfeb73519c6b97291 Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Mon, 16 Sep 2024 16:15:31 +0530 Subject: [PATCH 13/24] chores(test): e2e test fail due to file name typo error --- elements/map/vite.config.advancedLayersAndSources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elements/map/vite.config.advancedLayersAndSources.ts b/elements/map/vite.config.advancedLayersAndSources.ts index 3dac2b0bd..db0dd5d9e 100644 --- a/elements/map/vite.config.advancedLayersAndSources.ts +++ b/elements/map/vite.config.advancedLayersAndSources.ts @@ -4,7 +4,7 @@ import { defineConfig } from "vite"; export default defineConfig({ build: { lib: { - entry: "./src-2/plugins/advancedLayersAndSources/index.ts", + entry: "./src-2/plugins/advancedLayersAndSources/index", name: "eox-map-advanced-layers-and-sources", // the proper extensions will be added fileName: "eox-map-advanced-layers-and-sources", From 5b9de3cf4cd247abe1a6bb32a047d0ccbfaa5347 Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Mon, 16 Sep 2024 17:17:16 +0530 Subject: [PATCH 14/24] chores(test): convert ts to js --- ...dateLayer.cy.ts => addOrUpdateLayer.cy.js} | 7 ++- .../map/test/{compare.cy.ts => compare.cy.js} | 0 ...{configObject.cy.ts => configObject.cy.js} | 5 +-- .../test/{controls.cy.ts => controls.cy.js} | 10 ++--- ...nteraction.cy.ts => drawInteraction.cy.js} | 41 ++++++++---------- .../map/test/{general.cy.ts => general.cy.js} | 27 ++++++------ .../{groupLayer.cy.ts => groupLayer.cy.js} | 43 +++++++++---------- ...teraction.cy.ts => hoverInteraction.cy.js} | 6 +-- ...mageWmsLayer.cy.ts => imageWmsLayer.cy.js} | 2 +- ...Reactivity.cy.ts => layerReactivity.cy.js} | 21 +++++---- ...eraction.cy.ts => selectInteraction.cy.js} | 39 ++++++++--------- .../test/{stacLayer.cy.ts => stacLayer.cy.js} | 2 +- elements/map/test/{sync.cy.ts => sync.cy.js} | 10 ++--- ...cProperties.cy.ts => syncProperties.cy.js} | 2 +- ...{tileWmsLayer.cy.ts => tileWmsLayer.cy.js} | 2 +- .../map/test/{tooltip.cy.ts => tooltip.cy.js} | 13 +++--- .../{updateStyle.cy.ts => updateStyle.cy.js} | 16 +++---- .../{vectorLayer.cy.ts => vectorLayer.cy.js} | 24 +++++------ ...ilesLayer.cy.ts => vectorTilesLayer.cy.js} | 8 ++-- ...wProjection.cy.ts => viewProjection.cy.js} | 11 +++-- elements/map/test/{wmts.cy.ts => wmts.cy.js} | 6 +-- ...abilities.cy.ts => wmtsCapabilities.cy.js} | 4 +- 22 files changed, 138 insertions(+), 161 deletions(-) rename elements/map/test/{addOrUpdateLayer.cy.ts => addOrUpdateLayer.cy.js} (93%) rename elements/map/test/{compare.cy.ts => compare.cy.js} (100%) rename elements/map/test/{configObject.cy.ts => configObject.cy.js} (95%) rename elements/map/test/{controls.cy.ts => controls.cy.js} (95%) rename elements/map/test/{drawInteraction.cy.ts => drawInteraction.cy.js} (86%) rename elements/map/test/{general.cy.ts => general.cy.js} (91%) rename elements/map/test/{groupLayer.cy.ts => groupLayer.cy.js} (90%) rename elements/map/test/{hoverInteraction.cy.ts => hoverInteraction.cy.js} (95%) rename elements/map/test/{imageWmsLayer.cy.ts => imageWmsLayer.cy.js} (94%) rename elements/map/test/{layerReactivity.cy.ts => layerReactivity.cy.js} (81%) rename elements/map/test/{selectInteraction.cy.ts => selectInteraction.cy.js} (85%) rename elements/map/test/{stacLayer.cy.ts => stacLayer.cy.js} (96%) rename elements/map/test/{sync.cy.ts => sync.cy.js} (79%) rename elements/map/test/{syncProperties.cy.ts => syncProperties.cy.js} (96%) rename elements/map/test/{tileWmsLayer.cy.ts => tileWmsLayer.cy.js} (89%) rename elements/map/test/{tooltip.cy.ts => tooltip.cy.js} (91%) rename elements/map/test/{updateStyle.cy.ts => updateStyle.cy.js} (73%) rename elements/map/test/{vectorLayer.cy.ts => vectorLayer.cy.js} (84%) rename elements/map/test/{vectorTilesLayer.cy.ts => vectorTilesLayer.cy.js} (80%) rename elements/map/test/{viewProjection.cy.ts => viewProjection.cy.js} (97%) rename elements/map/test/{wmts.cy.ts => wmts.cy.js} (83%) rename elements/map/test/{wmtsCapabilities.cy.ts => wmtsCapabilities.cy.js} (96%) diff --git a/elements/map/test/addOrUpdateLayer.cy.ts b/elements/map/test/addOrUpdateLayer.cy.js similarity index 93% rename from elements/map/test/addOrUpdateLayer.cy.ts rename to elements/map/test/addOrUpdateLayer.cy.js index b9c7284b7..94eb7ae73 100644 --- a/elements/map/test/addOrUpdateLayer.cy.ts +++ b/elements/map/test/addOrUpdateLayer.cy.js @@ -1,6 +1,5 @@ import { html } from "lit"; import "../src-2/main"; -import { EoxLayer } from "../src/generate"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; describe("Map", () => { @@ -36,8 +35,8 @@ describe("Map", () => { format: "GeoJSON", attributions: "Regions: @ openlayers.org", }, - } as EoxLayer; - const eoxMap = $el[0]; + }; + const eoxMap = $el[0]; eoxMap.layers = [layerDefinition]; const layer = eoxMap.getLayerById("regions"); @@ -58,7 +57,7 @@ describe("Map", () => { format: "GeoJSON", attributions: "Regions: @ openlayers.org", }, - } as EoxLayer; + }; eoxMap.addOrUpdateLayer(updatedLayerDefinition); expect(layer.getOpacity(), "update opacity").to.be.equal(1); diff --git a/elements/map/test/compare.cy.ts b/elements/map/test/compare.cy.js similarity index 100% rename from elements/map/test/compare.cy.ts rename to elements/map/test/compare.cy.js diff --git a/elements/map/test/configObject.cy.ts b/elements/map/test/configObject.cy.js similarity index 95% rename from elements/map/test/configObject.cy.ts rename to elements/map/test/configObject.cy.js index f2a9a5d01..d3af6bb85 100644 --- a/elements/map/test/configObject.cy.ts +++ b/elements/map/test/configObject.cy.js @@ -28,7 +28,7 @@ describe("config property", () => { >` ).as("eox-map"); cy.get("eox-map").and(async ($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect(eoxMap.map.getControls().getLength()).to.be.equal(1); expect(eoxMap.map.getLayers().getArray().length).to.be.equal(1); @@ -43,14 +43,13 @@ describe("config property", () => { expect(eoxMap.map.getView().getZoom()).to.be.equal(9); }); cy.get("eox-map").and(async ($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.config = { controls: {}, layers: [ { type: "Tile", - // @ts-ignore properties: { id: "osm", title: "bar" }, source: { type: "OSM" }, }, diff --git a/elements/map/test/controls.cy.ts b/elements/map/test/controls.cy.js similarity index 95% rename from elements/map/test/controls.cy.ts rename to elements/map/test/controls.cy.js index 968ce4519..ed904c026 100644 --- a/elements/map/test/controls.cy.ts +++ b/elements/map/test/controls.cy.js @@ -27,7 +27,7 @@ describe("webcomponent property parsing", () => { >` ).as("eox-map"); cy.get("eox-map").and(async ($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect( eoxMap.map.getControls().getLength(), "set controls via webcomponent properties" @@ -63,7 +63,6 @@ describe("webcomponent property parsing", () => { eoxMap.map .getControls() .getArray()[0] - //@ts-ignore .getOverviewMap() .getLayers() .getArray()[0] @@ -96,9 +95,8 @@ describe("webcomponent property parsing", () => { >` ).as("eox-map"); cy.get("eox-map").and(async ($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect(eoxMap.map.getControls().getLength()).to.be.equal(1); - //@ts-ignore expect(eoxMap.map.getControls().getArray()[0].getElement()).to.exist; }); }); @@ -113,7 +111,7 @@ describe("webcomponent property parsing", () => { >` ).as("eox-map"); cy.get("eox-map").and(async ($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect(eoxMap.map.getControls().getLength()).to.be.equal(1); const loadingIndicatorElement = eoxMap.map .getTargetElement() @@ -137,7 +135,7 @@ describe("webcomponent property parsing", () => { >` ).as("eox-map"); cy.get("eox-map").and(async ($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect(eoxMap.map.getControls().getLength()).to.be.equal(1); const loadingIndicatorElement = eoxMap.map .getTargetElement() diff --git a/elements/map/test/drawInteraction.cy.ts b/elements/map/test/drawInteraction.cy.js similarity index 86% rename from elements/map/test/drawInteraction.cy.ts rename to elements/map/test/drawInteraction.cy.js index 54c50ddfe..5556db84f 100644 --- a/elements/map/test/drawInteraction.cy.ts +++ b/elements/map/test/drawInteraction.cy.js @@ -3,8 +3,6 @@ import "../src-2/main"; import drawInteractionLayerJson from "./drawInteraction.json"; import vectorLayerJson from "./vectorLayer.json"; import { simulateEvent } from "./utils/events"; -import { Point } from "ol/geom"; -import { EOxMap } from "../main"; describe("draw interaction", () => { beforeEach(() => {}); @@ -14,19 +12,19 @@ describe("draw interaction", () => { ); cy.get("eox-map").and(($el) => { // get the interaction via the source key - let drawInteraction = ($el[0]).interactions["drawInteraction"]; + let drawInteraction = $el[0].interactions["drawInteraction"]; expect(drawInteraction).to.exist; expect(drawInteraction.getActive()).to.equal(true); - const eoxMap = $el[0]; + const eoxMap = $el[0]; const map = eoxMap.map; const originalNumberOfInteractions = map.getInteractions().getLength(); const newLayerJson = [ Object.assign({}, drawInteractionLayerJson[0]), - ] as Array; + ]; delete newLayerJson[0].interactions; eoxMap.layers = newLayerJson; - drawInteraction = ($el[0]).interactions["drawInteraction"]; + drawInteraction = $el[0].interactions["drawInteraction"]; expect(drawInteraction, "remove interaction from dictionary").to.not .exist; expect( @@ -41,14 +39,14 @@ describe("draw interaction", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; simulateEvent(eoxMap.map, "pointerdown", 10, 20); simulateEvent(eoxMap.map, "pointerup", 10, 20); const drawLayer = eoxMap.getLayerById( "drawLayer" - ) as import("ol/layer/Vector").default; + ); const features = drawLayer.getSource().getFeatures(); - const geometry = features[0].getGeometry() as Point; + const geometry = features[0].getGeometry(); expect(features).to.have.length(1); expect(geometry.getCoordinates().length).to.be.equal(2); const buffer = eoxMap.buffer(geometry.getExtent(), 100); @@ -62,11 +60,11 @@ describe("draw interaction", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect(eoxMap.interactions.drawInteraction).to.exist; expect(eoxMap.interactions.drawInteraction_modify).to.exist; - ($el[0]).removeInteraction("drawInteraction"); - ($el[0]).removeInteraction("drawInteraction_modify"); + $el[0].removeInteraction("drawInteraction"); + $el[0].removeInteraction("drawInteraction_modify"); expect(eoxMap.interactions.drawInteraction).to.not.exist; expect(eoxMap.interactions.drawInteraction_modify).to.not.exist; }); @@ -77,12 +75,10 @@ describe("draw interaction", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect(eoxMap.interactions.drawInteraction).to.exist; expect(eoxMap.interactions.drawInteraction_modify).to.exist; - eoxMap.layers = vectorLayerJson as Array< - import("../src/generate").EoxLayer - >; + eoxMap.layers = vectorLayerJson; expect(eoxMap.interactions.drawInteraction).to.not.exist; expect(eoxMap.interactions.drawInteraction_modify).to.not.exist; }); @@ -94,7 +90,7 @@ describe("draw interaction", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.addEventListener("drawend", (evt) => { //@ts-expect-error geojson is defined on drawend event. expect(evt.detail.geojson.properties.measure).to.be.greaterThan(0); @@ -116,7 +112,7 @@ describe("draw interaction", () => { const drawLayer = eoxMap.getLayerById( "drawLayer" - ) as import("ol/layer").Vector; + ); const source = drawLayer.getSource(); const features = source.getFeatures(); expect(features).to.have.length(1); @@ -129,7 +125,7 @@ describe("draw interaction", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.addEventListener("drawend", (evt) => { //@ts-expect-error geojson is defined on drawend event. expect(evt.detail.geojson.properties.measure).to.be.greaterThan(0); @@ -157,7 +153,7 @@ describe("draw interaction", () => { const drawLayer = eoxMap.getLayerById( "drawLayer" - ) as import("ol/layer").Vector; + ); const features = drawLayer.getSource().getFeatures(); expect(features).to.have.length(1); }); @@ -170,9 +166,9 @@ describe("draw interaction", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; - eoxMap.addEventListener("drawend", (evt: UIEventInit) => { + eoxMap.addEventListener("drawend", (evt) => { //@ts-expect-error geojson is defined in drawend event const geojson = evt.detail.geojson; expect(geojson.properties.measure).to.be.greaterThan(0); @@ -194,7 +190,6 @@ describe("draw interaction", () => { ).to.be.equal(false); const newLayerJson = [drawInteractionLayerJson[0]]; newLayerJson[0].interactions[0].options.modify = true; - //@ts-ignore eoxMap.layers = newLayerJson; expect( eoxMap.interactions.drawInteraction_modify.getActive(), diff --git a/elements/map/test/general.cy.ts b/elements/map/test/general.cy.js similarity index 91% rename from elements/map/test/general.cy.ts rename to elements/map/test/general.cy.js index d1dab9eea..f53ec436e 100644 --- a/elements/map/test/general.cy.ts +++ b/elements/map/test/general.cy.js @@ -1,6 +1,5 @@ import { html } from "lit"; import { equals } from "ol/coordinate"; -import { EoxLayer } from "../src/generate"; import "../src-2/main"; describe("Map", () => { @@ -16,7 +15,7 @@ describe("Map", () => { >` ).as("eox-map"); cy.get("eox-map").and(($el) => { - expect(($el[0]).map).to.exist; + expect($el[0].map).to.exist; }); }); @@ -29,7 +28,7 @@ describe("Map", () => { .layers=${[ { type: "Tile", properties: { id: "osm" }, source: { type: "OSM" } }, ]} - @mapmounted=${(e: CustomEvent) => { + @mapmounted=${(e) => { expect(e.detail.getTargetElement(), "fires mounted event").to.not.be .undefined; }} @@ -48,7 +47,7 @@ describe("Map", () => { >` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const zoom = eoxMap.map.getView().getZoom(); const center = eoxMap.map.getView().getCenter(); expect(eoxMap.zoom).to.equal(zoom); @@ -83,7 +82,7 @@ describe("Map", () => { ).as("eox-map"); return new Cypress.Promise((resolve) => { cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.zoom = 4; eoxMap.center = [2200000, 6100000]; setTimeout(() => { @@ -123,7 +122,7 @@ describe("Map", () => { ).as("eox-map"); return new Cypress.Promise((resolve) => { cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; setTimeout(() => { // after half the animation time, expect to be in the middle of the animation (not initial, not target center) const center = eoxMap.map.getView().getCenter(); @@ -143,7 +142,7 @@ describe("Map", () => { cy.get("eox-map").and(($el) => { expect( equals( - ($el[0]).map.getView().getCenter(), + $el[0].map.getView().getCenter(), [2226389.8158654715, 2273030.926987689] ), "parse lon lat center" @@ -154,7 +153,7 @@ describe("Map", () => { it("correctly initializes center as 0,0 if none provided", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const center = ($el[0]).map.getView().getCenter(); + const center = $el[0].map.getView().getCenter(); expect( center, "set center to [0, 0] if nothing is defined" @@ -174,7 +173,7 @@ describe("Map", () => { >` ).as("eox-map"); cy.get("eox-map").and(($el) => { - expect(($el[0]).getLayerById("osm").get("id") === "osm").to.exist; + expect($el[0].getLayerById("osm").get("id") === "osm").to.exist; }); }); @@ -206,11 +205,9 @@ describe("Map", () => { >` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const layersArray = eoxMap.getFlatLayersArray( - eoxMap.map.getLayers().getArray() as Array< - import("../src/generate").AnyLayer - > + eoxMap.map.getLayers().getArray() ); expect(layersArray.length).to.equal(3); expect(layersArray.find((l) => l.get("id") === "group1")).to.exist; @@ -230,8 +227,8 @@ describe("Map", () => { { type: "Tile", properties: { id: "2" }, source: { type: "OSM" } }, { type: "Tile", properties: { id: "3" }, source: { type: "OSM" } }, ]; - const eoxMap = $el[0]; - eoxMap.layers = layersArray; + const eoxMap = $el[0]; + eoxMap.layers = layersArray; expect(layersArray.map((l) => l.properties.id).join("")).to.eq("123"); expect(layersArray.map((l) => l.properties.id).join("")).to.not.eq("321"); expect( diff --git a/elements/map/test/groupLayer.cy.ts b/elements/map/test/groupLayer.cy.js similarity index 90% rename from elements/map/test/groupLayer.cy.ts rename to elements/map/test/groupLayer.cy.js index 1b1fdef09..a4cb6a179 100644 --- a/elements/map/test/groupLayer.cy.ts +++ b/elements/map/test/groupLayer.cy.js @@ -1,6 +1,5 @@ import { html } from "lit"; import "../src-2/main"; -import { EoxLayer } from "../src/generate"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; const osmJson = { @@ -11,7 +10,7 @@ const osmJson = { source: { type: "OSM", }, -} as EoxLayer; +}; const layersJson = [ { @@ -50,7 +49,7 @@ const layersJson = [ }, ], }, -] as Array; +]; describe("layers", () => { it("loads a Vector Layer in a group", () => { @@ -66,7 +65,7 @@ describe("layers", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const groupLayer = eoxMap.getLayerById("group"); expect(groupLayer, "find group layer").to.exist; @@ -110,9 +109,9 @@ describe("layers", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; layersJson[0].layers.push(osmJson); - eoxMap.layers = layersJson as Array; + eoxMap.layers = layersJson; const newOsmInGroup = eoxMap.getLayerById("osm"); expect(newOsmInGroup, "reactively add layer to group").to.exist; }); @@ -131,7 +130,7 @@ describe("layers", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const jsonCopy = JSON.parse(JSON.stringify(layersJson)); jsonCopy[0].layers.length = 1; eoxMap.layers = jsonCopy; @@ -154,10 +153,10 @@ describe("layers", () => { }); cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; layersJson[0].layers[1].layers[0].opacity = 0.2; - eoxMap.layers = layersJson as Array; + eoxMap.layers = layersJson; expect( eoxMap.getLayerById("layerInsideGroupInsideGroup").getOpacity(), @@ -200,7 +199,7 @@ describe("layers", () => { }, ], }, - ] as Array; + ]; cy.mount(html``).as("eox-map"); @@ -217,15 +216,15 @@ describe("layers", () => { url: "https://openlayers.org/data/vector/ecoregions.json", format: "GeoJSON", }, - } as EoxLayer); + }); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; - eoxMap.layers = layersJson as Array; + const eoxMap = $el[0]; + eoxMap.layers = layersJson; const layer = eoxMap.getLayerById( "regionsRed" - ) as import("ol/layer").Vector; - const styleObject = layer.getStyle() as import("ol/style/flat").FlatStyle; + ); + const styleObject = layer.getStyle(); const fillColor = styleObject["fill-color"]; expect(fillColor, "reactive layer 2 levels deep").to.be.equal("red"); }); @@ -275,13 +274,13 @@ describe("layers", () => { }, ], }, - ] as Array; + ]; cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const allRealLayers = eoxMap.getFlatLayersArray( - eoxMap.map.getAllLayers() as Array + eoxMap.map.getAllLayers() ); expect(allRealLayers.length).to.be.equal(3); layersJson[0].layers = [ @@ -300,9 +299,9 @@ describe("layers", () => { }, opacity: 0.5, }); - eoxMap.layers = layersJson as Array; + eoxMap.layers = layersJson; const newRealLayers = eoxMap.getFlatLayersArray( - eoxMap.map.getAllLayers() as Array + eoxMap.map.getAllLayers() ); expect( @@ -341,7 +340,7 @@ describe("layers", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; groupLayer = { type: "Group", properties: { id: "BASELAYERS", title: "Base Layers" }, @@ -359,7 +358,7 @@ describe("layers", () => { }, ], }; - eoxMap.layers = [groupLayer] as Array; + eoxMap.layers = [groupLayer]; const layer = eoxMap.getLayerById("cloudless-2022"); expect( diff --git a/elements/map/test/hoverInteraction.cy.ts b/elements/map/test/hoverInteraction.cy.js similarity index 95% rename from elements/map/test/hoverInteraction.cy.ts rename to elements/map/test/hoverInteraction.cy.js index 8ad9e59e9..ffdbfe749 100644 --- a/elements/map/test/hoverInteraction.cy.ts +++ b/elements/map/test/hoverInteraction.cy.js @@ -18,7 +18,7 @@ describe("select interaction with hover", () => { ` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; let selectCounter = 0; let featureSelectCounter = 0; @@ -52,7 +52,7 @@ describe("select interaction with hover", () => { req.reply(ecoRegionsFixture); } ); - (vectorLayerStyleJson as Array).push({ + (vectorLayerStyleJson).push({ type: "Tile", properties: { id: "osm", @@ -68,7 +68,7 @@ describe("select interaction with hover", () => { ` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; let selectCounter = 0; let featureSelectCounter = 0; eoxMap.addEventListener("select", (evt) => { diff --git a/elements/map/test/imageWmsLayer.cy.ts b/elements/map/test/imageWmsLayer.cy.js similarity index 94% rename from elements/map/test/imageWmsLayer.cy.ts rename to elements/map/test/imageWmsLayer.cy.js index ffe5444fa..39a8f9c98 100644 --- a/elements/map/test/imageWmsLayer.cy.ts +++ b/elements/map/test/imageWmsLayer.cy.js @@ -12,7 +12,7 @@ describe("layers", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.map.getView().setZoom(0); const layers = eoxMap.map.getLayers().getArray(); expect(layers).to.have.length(1); diff --git a/elements/map/test/layerReactivity.cy.ts b/elements/map/test/layerReactivity.cy.js similarity index 81% rename from elements/map/test/layerReactivity.cy.ts rename to elements/map/test/layerReactivity.cy.js index e6e0a869c..bf82020af 100644 --- a/elements/map/test/layerReactivity.cy.ts +++ b/elements/map/test/layerReactivity.cy.js @@ -1,6 +1,5 @@ import { html } from "lit"; import drawInteractionLayerJson from "./drawInteraction.json"; -import { EoxLayer } from "../src/generate"; import "../src-2/main"; const OsmJson = { @@ -11,7 +10,7 @@ const OsmJson = { source: { type: "OSM", }, -} as EoxLayer; +}; const OsmJson2 = { type: "Tile", @@ -21,13 +20,13 @@ const OsmJson2 = { source: { type: "OSM", }, -} as EoxLayer; +}; describe("Map", () => { it("add layer by setting it as property", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.layers = [OsmJson]; expect(eoxMap.getLayerById("osm")).to.exist; }); @@ -36,7 +35,7 @@ describe("Map", () => { it("add another layer", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.layers = [OsmJson]; eoxMap.layers = [OsmJson, OsmJson2]; expect(eoxMap.map.getLayers().getArray().length).to.be.equal(2); @@ -46,7 +45,7 @@ describe("Map", () => { it("remove a layer", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.layers = [OsmJson, OsmJson2]; eoxMap.layers = [OsmJson]; expect(eoxMap.map.getLayers().getArray().length).to.be.equal(1); @@ -56,13 +55,13 @@ describe("Map", () => { it("add an interaction to an existing layer", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const interactions = drawInteractionLayerJson[0].interactions; delete drawInteractionLayerJson[0].interactions; - eoxMap.layers = drawInteractionLayerJson as Array; + eoxMap.layers = drawInteractionLayerJson; drawInteractionLayerJson[0].interactions = interactions; - eoxMap.layers = drawInteractionLayerJson as Array; + eoxMap.layers = drawInteractionLayerJson; expect(eoxMap.interactions.drawInteraction).to.exist; expect(eoxMap.interactions.drawInteraction_modify).to.exist; }); @@ -71,11 +70,11 @@ describe("Map", () => { it("always completely remake layers without ID", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; delete OsmJson.properties; eoxMap.layers = [OsmJson]; delete drawInteractionLayerJson[0].properties; - eoxMap.layers = [drawInteractionLayerJson[0] as EoxLayer]; + eoxMap.layers = [drawInteractionLayerJson[0]]; expect(eoxMap.map.getLayers().getArray().length).to.be.equal(1); }); }); diff --git a/elements/map/test/selectInteraction.cy.ts b/elements/map/test/selectInteraction.cy.js similarity index 85% rename from elements/map/test/selectInteraction.cy.ts rename to elements/map/test/selectInteraction.cy.js index 43e1b185a..f046a051f 100644 --- a/elements/map/test/selectInteraction.cy.ts +++ b/elements/map/test/selectInteraction.cy.js @@ -3,7 +3,6 @@ import "../src-2/main"; import vectorTileLayerJson from "./vectorTilesLayer.json"; import vectorLayerJson from "./vectorLayer.json"; import { simulateEvent } from "./utils/events"; -import { EOxInteraction, EoxLayer } from "../src/generate"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; const vectorTileInteraction = [ @@ -39,13 +38,13 @@ describe("select interaction on click", () => { return new Cypress.Promise((resolve) => { const layerJson = JSON.parse( JSON.stringify(vectorTileLayerJson) - ) as Array; + ); layerJson[0].interactions = - vectorTileInteraction as Array; + vectorTileInteraction; cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; - eoxMap.addEventListener("select", (evt: CustomEventInit) => { + const eoxMap = $el[0]; + eoxMap.addEventListener("select", (evt) => { expect(evt.detail.feature).to.exist; resolve(); }); @@ -65,7 +64,7 @@ describe("select interaction on click", () => { ); const styleJson = JSON.parse( JSON.stringify(vectorLayerJson) - ) as Array; + ); styleJson[0].minZoom = 3; styleJson[0].interactions = [ { @@ -77,14 +76,14 @@ describe("select interaction on click", () => { "stroke-color": "white", "stroke-width": 3, }, - } as import("../src/select").SelectOptions, + }, }, ]; cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; - eoxMap.addEventListener("select", (evt: CustomEventInit) => { + eoxMap.addEventListener("select", (evt) => { expect(evt.detail.feature).to.exist; }); eoxMap.map.on("loadend", () => { @@ -103,7 +102,7 @@ describe("select interaction on click", () => { ); const styleJson = JSON.parse( JSON.stringify(vectorLayerJson) - ) as Array; + ); styleJson[0].interactions = [ { type: "select", @@ -114,7 +113,7 @@ describe("select interaction on click", () => { "stroke-color": "white", "stroke-width": 3, }, - } as import("../src/select").SelectOptions, + }, }, ]; cy.mount( @@ -122,7 +121,7 @@ describe("select interaction on click", () => { ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.map.on("loadend", () => { //on loadend, programmatically select a few features... eoxMap.selectInteractions.selectInteraction.highlightById( @@ -148,8 +147,8 @@ describe("select interaction on click", () => { it.only("programmatically highlight by IDs (VectorTileLayer)", () => { const layerJson = JSON.parse( JSON.stringify(vectorTileLayerJson) - ) as Array; - layerJson[0].interactions = vectorTileInteraction as Array; + ); + layerJson[0].interactions = vectorTileInteraction; return new Cypress.Promise((resolve) => { cy.intercept(/^.*geoserver.*$/, { fixture: @@ -157,13 +156,13 @@ describe("select interaction on click", () => { encoding: "binary", }); layerJson[0].interactions = - vectorTileInteraction as Array; + vectorTileInteraction; cy.mount( html`` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.map.on("loadend", () => { //on loadend, programmatically select a few features... eoxMap.selectInteractions.selectInteraction.highlightById([889], { @@ -186,7 +185,7 @@ describe("select interaction on click", () => { it("remove interaction", () => { const styleJson = JSON.parse( JSON.stringify(vectorTileLayerJson) - ) as Array; + ); styleJson[0].interactions = [ { type: "select", @@ -197,14 +196,14 @@ describe("select interaction on click", () => { "stroke-color": "white", "stroke-width": 3, }, - } as import("../src/select").SelectOptions, + }, }, ]; cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect(eoxMap.selectInteractions.selectInteraction).to.exist; - eoxMap.layers = vectorLayerJson as Array; + eoxMap.layers = vectorLayerJson; expect(eoxMap.selectInteractions.selectInteraction).to.not.exist; }); }); diff --git a/elements/map/test/stacLayer.cy.ts b/elements/map/test/stacLayer.cy.js similarity index 96% rename from elements/map/test/stacLayer.cy.ts rename to elements/map/test/stacLayer.cy.js index 5985c9c69..86a6ac5ea 100644 --- a/elements/map/test/stacLayer.cy.ts +++ b/elements/map/test/stacLayer.cy.js @@ -14,7 +14,7 @@ describe("layers", () => { ); cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.map.getLayers().getArray(); const stacLayer = eoxMap.getLayerById("stacLayer"); expect(stacLayer).to.exist; diff --git a/elements/map/test/sync.cy.ts b/elements/map/test/sync.cy.js similarity index 79% rename from elements/map/test/sync.cy.ts rename to elements/map/test/sync.cy.js index 08c86d448..4b6871062 100644 --- a/elements/map/test/sync.cy.ts +++ b/elements/map/test/sync.cy.js @@ -9,12 +9,12 @@ describe("map syncing", () => { html` ` ); cy.get("eox-map#a").and(($el) => { - const olMapView = ($el[0]).map.getView(); + const olMapView = $el[0].map.getView(); olMapView.setZoom(zoom); olMapView.setCenter(center); }); cy.get("eox-map#b").and(($el) => { - const olMapView = ($el[0]).map.getView(); + const olMapView = $el[0].map.getView(); expect(olMapView.getZoom()).to.be.equal(zoom); expect(olMapView.getCenter()).to.deep.eq(center); }); @@ -24,13 +24,13 @@ describe("map syncing", () => { const center = [10, 10]; cy.mount(html` `); cy.get("eox-map#a").and(($el) => { - const olMapView = ($el[0]).map.getView(); + const olMapView = $el[0].map.getView(); olMapView.setZoom(zoom); olMapView.setCenter(center); }); cy.get("eox-map#b").and(($el) => { - const eoxMap = $el[0]; - eoxMap.sync = document.querySelector("eox-map#a"); + const eoxMap = $el[0]; + eoxMap.sync = document.querySelector("eox-map#a"); const olMapView = eoxMap.map.getView(); expect(olMapView.getZoom()).to.be.equal(zoom); expect(olMapView.getCenter()).to.deep.eq(center); diff --git a/elements/map/test/syncProperties.cy.ts b/elements/map/test/syncProperties.cy.js similarity index 96% rename from elements/map/test/syncProperties.cy.ts rename to elements/map/test/syncProperties.cy.js index c21ad1213..f36e7f5f9 100644 --- a/elements/map/test/syncProperties.cy.ts +++ b/elements/map/test/syncProperties.cy.js @@ -16,7 +16,7 @@ describe("layers", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const layer = eoxMap.getLayerById("regions"); layer.setVisible(true); diff --git a/elements/map/test/tileWmsLayer.cy.ts b/elements/map/test/tileWmsLayer.cy.js similarity index 89% rename from elements/map/test/tileWmsLayer.cy.ts rename to elements/map/test/tileWmsLayer.cy.js index 1a43bb014..bfb6a88c5 100644 --- a/elements/map/test/tileWmsLayer.cy.ts +++ b/elements/map/test/tileWmsLayer.cy.js @@ -11,7 +11,7 @@ describe("layers", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const layers = ($el[0]).map.getLayers().getArray(); + const layers = $el[0].map.getLayers().getArray(); expect(layers).to.have.length(1); expect(layers[0].get("id")).to.be.equal("customId"); }); diff --git a/elements/map/test/tooltip.cy.ts b/elements/map/test/tooltip.cy.js similarity index 91% rename from elements/map/test/tooltip.cy.ts rename to elements/map/test/tooltip.cy.js index e45288522..7f01035bd 100644 --- a/elements/map/test/tooltip.cy.ts +++ b/elements/map/test/tooltip.cy.js @@ -2,7 +2,6 @@ import { html } from "lit"; import "../src-2/main"; import vectorLayerStyleJson from "./hoverInteraction.json"; import { simulateEvent } from "./utils/events"; -import { EoxLayer } from "../src/generate"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; describe("tooltip", () => { @@ -19,7 +18,7 @@ describe("tooltip", () => { ` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.map.on("loadend", () => { simulateEvent(eoxMap.map, "pointermove", 120, -140); @@ -45,14 +44,14 @@ describe("tooltip", () => { req.reply(ecoRegionsFixture); } ); - const multiplelayersJson = [...vectorLayerStyleJson] as EoxLayer[]; - const secondLayer = structuredClone(vectorLayerStyleJson[0]) as EoxLayer; + const multiplelayersJson = [...vectorLayerStyleJson]; + const secondLayer = structuredClone(vectorLayerStyleJson[0]); multiplelayersJson.unshift(secondLayer); secondLayer.properties.id = "regions2"; secondLayer.interactions[0].options.id = "selectInteraction2"; ( secondLayer.interactions[0] - .options as import("../src/select").SelectOptions + .options ).layer.properties.id = "selectLayer2"; secondLayer.visible = false; cy.mount( @@ -61,7 +60,7 @@ describe("tooltip", () => { ` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.map.on("loadend", () => { simulateEvent(eoxMap.map, "pointermove", 120, -140); @@ -83,7 +82,7 @@ describe("tooltip", () => { // hide first rendered layer and show second layer cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.map.getLayers().getArray()[0].setVisible(false); eoxMap.map.getLayers().getArray()[1].setVisible(true); setTimeout(() => { diff --git a/elements/map/test/updateStyle.cy.ts b/elements/map/test/updateStyle.cy.js similarity index 73% rename from elements/map/test/updateStyle.cy.ts rename to elements/map/test/updateStyle.cy.js index 0cc954e65..9e74513a7 100644 --- a/elements/map/test/updateStyle.cy.ts +++ b/elements/map/test/updateStyle.cy.js @@ -1,9 +1,7 @@ import { html } from "lit"; import "../src-2/main"; -import { EOxMap } from "../main"; import vectorLayerStyleJson from "./vectorLayer.json"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; -import { EoxLayer } from "../src/generate"; describe("layers", () => { it("correctly updates applies flat style", () => { @@ -13,7 +11,7 @@ describe("layers", () => { req.reply(ecoRegionsFixture); } ); - (vectorLayerStyleJson[0] as import("../src/generate").EoxLayer).style = { + (vectorLayerStyleJson[0]).style = { "fill-color": "yellow", "stroke-color": "black", "stroke-width": 2, @@ -28,7 +26,7 @@ describe("layers", () => { cy.get("eox-map").and(($el) => { const updatedLayerJson = { ...vectorLayerStyleJson[0], - } as import("../src/generate").EoxLayer; + }; updatedLayerJson.style = [ { "fill-color": ["string", ["get", "COLOR"], "#eee"], @@ -37,20 +35,20 @@ describe("layers", () => { "text-value": ["string", ["get", "ECO_NAME"], ""], }, ]; - ($el[0]).addOrUpdateLayer(updatedLayerJson as EoxLayer); - const layer = ($el[0]).map.getLayers().getArray()[0]; + $el[0].addOrUpdateLayer(updatedLayerJson); + const layer = $el[0].map.getLayers().getArray()[0]; - const features = (layer as import("ol/layer/Vector").default) + const features = (layer) .getSource() .getFeatures(); expect(features.length).to.be.greaterThan(0); const styleFunction = ( - layer as import("ol/layer/Vector").default + layer ).getStyleFunction(); const featureStyle = ( - styleFunction(features[0], 1) as Array + styleFunction(features[0], 1) )[0]; const featureColor = featureStyle.getFill().getColor(); diff --git a/elements/map/test/vectorLayer.cy.ts b/elements/map/test/vectorLayer.cy.js similarity index 84% rename from elements/map/test/vectorLayer.cy.ts rename to elements/map/test/vectorLayer.cy.js index 6031e1b45..2b9401f3f 100644 --- a/elements/map/test/vectorLayer.cy.ts +++ b/elements/map/test/vectorLayer.cy.js @@ -16,7 +16,7 @@ describe("layers", () => { "eox-map" ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const layers = eoxMap.map.getLayers().getArray(); expect(layers).to.have.length(1); expect(eoxMap.getLayerById("regions")).to.exist; @@ -34,7 +34,7 @@ describe("layers", () => { }); cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.registerProjection( "EPSG:3035", "+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs" @@ -55,14 +55,12 @@ describe("layers", () => { dataProjection: "EPSG:3035", }, }, - } as import("../src/generate").EoxLayer; + }; eoxMap.layers = [layerWithFormats]; const source = eoxMap .getLayerById("0") - .getSource() as unknown as import("ol/source/Vector").default< - import("ol/Feature").default - >; + .getSource(); const coordinates = source .getFeatures()[0] .getGeometry() @@ -86,7 +84,7 @@ describe("layers", () => { req.reply(ecoRegionsFixture); } ); - (vectorLayerStyleJson[0] as import("../src/generate").EoxLayer).style = { + (vectorLayerStyleJson[0]).style = { "fill-color": "yellow", "stroke-color": "black", "stroke-width": 2, @@ -96,9 +94,9 @@ describe("layers", () => { ); cy.get("eox-map").and(($el) => { return new Cypress.Promise((resolve) => { - const layer = ($el[0]).map + const layer = $el[0].map .getLayers() - .getArray()[0] as import("ol/layer/Vector").default; + .getArray()[0]; // wait for features to load layer.getSource().on("featuresloadend", () => { const feature = layer.getSource().getFeatures()[0]; @@ -116,7 +114,7 @@ describe("layers", () => { req.reply(ecoRegionsFixture); } ); - (vectorLayerStyleJson[0] as import("../src/generate").EoxLayer).style = { + (vectorLayerStyleJson[0]).style = { "fill-color": ["string", ["get", "COLOR"], "#eee"], "stroke-color": "black", "stroke-width": 2, @@ -127,11 +125,11 @@ describe("layers", () => { cy.get("eox-map").and(($el) => { return new Cypress.Promise((resolve) => { // wait for features to load - const eoxMap = $el[0]; + const eoxMap = $el[0]; const layer = eoxMap.getLayerById( "regions" - ) as import("ol/layer/Vector").default; - const source = layer.getSource() as import("ol/source/Vector").default; + ); + const source = layer.getSource(); source.on("featuresloadend", () => { const feature = source.getFeatures()[0]; const styles = layer.getStyleFunction()(feature, 100); diff --git a/elements/map/test/vectorTilesLayer.cy.ts b/elements/map/test/vectorTilesLayer.cy.js similarity index 80% rename from elements/map/test/vectorTilesLayer.cy.ts rename to elements/map/test/vectorTilesLayer.cy.js index 63e0ef513..f9912afd5 100644 --- a/elements/map/test/vectorTilesLayer.cy.ts +++ b/elements/map/test/vectorTilesLayer.cy.js @@ -1,8 +1,6 @@ import { html } from "lit"; -import { VectorTile } from "ol/layer"; import "../src-2/main"; import vectorTileLayerStyleJson from "./vectorTilesLayer.json"; -import { EoxLayer } from "../src/generate"; describe("VectorTile Layer", () => { it("loads a Vector Tile Layer, applies flat style", () => { @@ -12,7 +10,7 @@ describe("VectorTile Layer", () => { encoding: "binary", }); - (vectorTileLayerStyleJson[0] as EoxLayer).style = { + (vectorTileLayerStyleJson[0]).style = { "fill-color": "yellow", "stroke-color": "black", "stroke-width": 4, @@ -22,8 +20,8 @@ describe("VectorTile Layer", () => { ).as("eox-map"); return new Cypress.Promise((resolve) => { cy.get("eox-map").should(($el) => { - const eoxMap = $el[0]; - const layer = eoxMap.getLayerById("countries") as VectorTile; + const eoxMap = $el[0]; + const layer = eoxMap.getLayerById("countries"); setTimeout(() => { // to do: not able to wait for rendercomplete directly, as `applyStyle` is async const features = layer.getFeaturesInExtent( diff --git a/elements/map/test/viewProjection.cy.ts b/elements/map/test/viewProjection.cy.js similarity index 97% rename from elements/map/test/viewProjection.cy.ts rename to elements/map/test/viewProjection.cy.js index 2e8184b7e..3321470f4 100644 --- a/elements/map/test/viewProjection.cy.ts +++ b/elements/map/test/viewProjection.cy.js @@ -2,7 +2,6 @@ import { html } from "lit"; import "../src-2/main"; import vectorLayerStyleJson from "./vectorLayer.json"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; -import { EOxMap } from "../main"; describe("view projections", () => { it("can set the initial projection of the view", () => { @@ -26,7 +25,7 @@ describe("view projections", () => { >` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; expect(eoxMap.map.getView().getProjection().getCode()).to.be.equal( "EPSG:4326" ); @@ -54,7 +53,7 @@ describe("view projections", () => { >` ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.map .getView() .on("change:resolution", (e) => @@ -110,7 +109,7 @@ describe("view projections", () => { ); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const testExtent = [-10, -9, 10, 9]; eoxMap.registerProjection( "ESRI:53009", @@ -221,7 +220,7 @@ describe("view projections", () => { ).as("eox-map"); cy.get("eox-map").and(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.registerProjectionFromCode("EPSG:32633").then(() => { eoxMap.setAttribute("projection", "EPSG:32633"); expect(eoxMap.map.getView().getProjection().getCode()).to.be.equal( @@ -236,7 +235,7 @@ describe("view projections", () => { cy.get("eox-map").then(($el) => { return new Cypress.Promise((resolve) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; eoxMap.registerProjection( "EPSG:32633", "+proj=utm +zone=33 +datum=WGS84 +units=m +no_defs +type=crs" diff --git a/elements/map/test/wmts.cy.ts b/elements/map/test/wmts.cy.js similarity index 83% rename from elements/map/test/wmts.cy.ts rename to elements/map/test/wmts.cy.js index cd8f5cb11..b8174c3f3 100644 --- a/elements/map/test/wmts.cy.ts +++ b/elements/map/test/wmts.cy.js @@ -26,13 +26,13 @@ describe("layers", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const layer = ($el[0]).map + const layer = $el[0].map .getLayers() - .getArray()[0] as import("../src/generate").AnyLayerWithSource; + .getArray()[0]; expect(layer).to.exist; expect(layer.get("id")).to.be.equal("customId"); - const source = layer.getSource() as import("ol/source/WMTS").default; + const source = layer.getSource(); expect( source.getTileGrid().getTileSize(0), "use tileGrid options" diff --git a/elements/map/test/wmtsCapabilities.cy.ts b/elements/map/test/wmtsCapabilities.cy.js similarity index 96% rename from elements/map/test/wmtsCapabilities.cy.ts rename to elements/map/test/wmtsCapabilities.cy.js index f394b595d..6e7601863 100644 --- a/elements/map/test/wmtsCapabilities.cy.ts +++ b/elements/map/test/wmtsCapabilities.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../main"; +import "../src-2/main"; import "../src-2/plugins/advancedLayersAndSources/index"; describe("WMTS Capabilities Source", () => { @@ -35,7 +35,7 @@ describe("WMTS Capabilities Source", () => { ).as("eox-map"); cy.get("eox-map").should(($el) => { - const eoxMap = $el[0]; + const eoxMap = $el[0]; const layer = eoxMap.getLayerById("wmtsLayer"); layer.once("postrender", (e) => { expect(e.target.getSource().getState()).to.be.equal("ready"); From 64828b16de3d5b611ae94c1a3d3fdf313512debc Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Mon, 16 Sep 2024 17:26:45 +0530 Subject: [PATCH 15/24] chores(test): make e2e and component testing compatible with src-2 main --- .storybook/preview.js | 2 +- elements/map/main.ts | 694 ------------------ elements/map/src/center.ts | 30 - elements/map/src/compare.ts | 138 ---- elements/map/src/controls/Geolocation.ts | 227 ------ elements/map/src/controls/LoadingIndicator.ts | 83 --- elements/map/src/controls/controls.css | 28 - elements/map/src/controls/controls.ts | 99 --- .../src/custom/sources/WMTSCapabilities.ts | 161 ---- elements/map/src/draw.ts | 79 -- elements/map/src/generate.ts | 477 ------------ elements/map/src/layer.ts | 52 -- .../advancedLayersAndSources/formats.ts | 3 - .../plugins/advancedLayersAndSources/index.ts | 3 - .../advancedLayersAndSources/layers.ts | 11 - .../advancedLayersAndSources/sources.ts | 7 - elements/map/src/select.ts | 334 --------- elements/map/src/tileGrid.ts | 45 -- elements/map/src/tooltip.ts | 66 -- elements/map/src/utils.ts | 137 ---- elements/map/test/drawInteraction.cy.js | 16 +- elements/map/test/groupLayer.cy.js | 4 +- elements/map/test/hoverInteraction.cy.js | 2 +- elements/map/test/selectInteraction.cy.js | 26 +- elements/map/test/tooltip.cy.js | 5 +- elements/map/test/updateStyle.cy.js | 14 +- elements/map/test/vectorLayer.cy.js | 16 +- elements/map/test/vectorTilesLayer.cy.js | 2 +- elements/map/test/wmts.cy.js | 4 +- elements/map/typings.d.ts | 2 +- elements/map/vite.config.ts | 2 +- .../test/cases/load-map-section.js | 2 +- .../storytelling/test/cases/load-map-tour.js | 2 +- 33 files changed, 30 insertions(+), 2743 deletions(-) delete mode 100644 elements/map/main.ts delete mode 100644 elements/map/src/center.ts delete mode 100644 elements/map/src/compare.ts delete mode 100644 elements/map/src/controls/Geolocation.ts delete mode 100644 elements/map/src/controls/LoadingIndicator.ts delete mode 100644 elements/map/src/controls/controls.css delete mode 100644 elements/map/src/controls/controls.ts delete mode 100644 elements/map/src/custom/sources/WMTSCapabilities.ts delete mode 100644 elements/map/src/draw.ts delete mode 100644 elements/map/src/generate.ts delete mode 100644 elements/map/src/layer.ts delete mode 100644 elements/map/src/plugins/advancedLayersAndSources/formats.ts delete mode 100644 elements/map/src/plugins/advancedLayersAndSources/index.ts delete mode 100644 elements/map/src/plugins/advancedLayersAndSources/layers.ts delete mode 100644 elements/map/src/plugins/advancedLayersAndSources/sources.ts delete mode 100644 elements/map/src/select.ts delete mode 100644 elements/map/src/tileGrid.ts delete mode 100644 elements/map/src/tooltip.ts delete mode 100644 elements/map/src/utils.ts diff --git a/.storybook/preview.js b/.storybook/preview.js index 8b0dddf85..0c8d2a2eb 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -10,7 +10,7 @@ import "../elements/jsonform/src/main.js"; import "../elements/layercontrol/src/main.js"; import "../elements/layout/src/main.js"; import "../elements/map/src-2/main.js"; -import "../elements/map/src/plugins/advancedLayersAndSources/index.ts"; +import "../elements/map/src-2/plugins/advancedLayersAndSources/index"; import "../elements/stacinfo/src/main.js"; import "../elements/storytelling/src/main.js"; import "../elements/timecontrol/src/main.js"; diff --git a/elements/map/main.ts b/elements/map/main.ts deleted file mode 100644 index 0328d9c3f..000000000 --- a/elements/map/main.ts +++ /dev/null @@ -1,694 +0,0 @@ -import { LitElement, PropertyValueMap, html } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; -import Map from "ol/Map.js"; -import View, { ViewObjectEventTypes } from "ol/View.js"; -import olCss from "ol/ol.css?inline"; -import controlCss from "./src/controls/controls.css?inline"; -import { EOxSelectInteraction } from "./src/select"; -import { EoxLayer, createLayer, updateLayer } from "./src/generate"; -import { Draw, Modify } from "ol/interaction"; -import Control from "ol/control/Control"; -import { getLayerById, getFlatLayersArray } from "./src/layer"; -import { getCenterFromProperty } from "./src/center"; -import { - addOrUpdateControl, - controlDictionary, - controlType, -} from "./src/controls/controls"; -import { buffer } from "ol/extent"; -import "./src/compare"; -import { - addScrollInteractions, - coordinatesRoughlyEquals, - removeDefaultScrollInteractions, - transform, - transformExtent, -} from "./src/utils"; -import GeoJSON from "ol/format/GeoJSON"; -import { - parseTextToFeature as parseText, - registerProjection, - registerProjectionFromCode, - cancelAnimation, -} from "./src-2/helpers"; -import { READ_FEATURES_OPTIONS } from "./src-2/enums"; -import Feature from "ol/Feature"; -import { Geometry } from "ol/geom"; -import VectorLayer from "ol/layer/Vector.js"; -import { - ProjectionLike, - transform as olTransform, - getPointResolution, - get as getProjection, -} from "ol/proj"; -import { Coordinate } from "ol/coordinate"; -import { getElement } from "../../utils"; - -type EOxAnimationOptions = import("ol/View").AnimationOptions & - import("ol/View").FitOptions; - -type ConfigObject = { - controls: controlDictionary; - layers: Array; - view: { - center: Array; - zoom: number; - zoomExtent?: import("ol/extent").Extent; - projection?: ProjectionLike; - }; - preventScroll: boolean; - animationOptions?: EOxAnimationOptions; -}; - -/** - * The `eox-map` is a wrapper for the library [OpenLayers](https://openlayers.org/) with additional features and helper functions. - * - * Basic usage: - * - * ``` - * import "@eox/map" - * - * - * ``` - * - * Some basic layers, sources and formats are included in the default bundle, for advanced usage it is - * required to import the `advanced-layers-and-sources` plugin. - * - * Included in the base bundle: - * - Formats: `GeoJSON`, `MVT` - * - Layers: `Group`, `Image`, `Tile`, `Vector`, `VectorTile` - * - Sources: `ImageWMS`, `OSM`, `Tile`, `TileWMS`, `Vector`, `VectorTile`, `WMTS`, `XYZ` - * - * In order to use the rest of the layers and sources provided by OpenLayers, import the plugin as well: - * - * ``` - * import "@eox/map/dist/eox-map-advanced-layers-and-sources.js" - * import "@eox/map/dist/eox-map.js" - * - * - * ``` - * Included in the advanced plugin bundle: - * - Layers: - * - All OpenLayers layer types - * - [`STAC`](https://github.com/m-mohr/ol-stac) - * - Sources: - * - All OpenLayers source types - * - [`WMTSCapabilities`](https://github.com/EOX-A/EOxElements/tree/main/elements/map/src/custom/sources/WMTSCapabilities.ts) - * - Reprojection through [proj4](https://github.com/proj4js/proj4js) - */ -@customElement("eox-map") -export class EOxMap extends LitElement { - private _center: Coordinate = [0, 0]; - - set center(center: Coordinate) { - const centerIsSame = - center?.length && - coordinatesRoughlyEquals(center, this.map.getView().getCenter()); - if (center && !centerIsSame) { - if (!this.projection || this.projection === "EPSG:3857") { - // we allow lat-lon center when map is in web mercator - const mercatorCenter = getCenterFromProperty(center); - this._center = mercatorCenter; - } else { - this._center = center; - } - this._animateToState(); - } - } - - /** - * Map center, always in the same projection as the view. - * when setting the map center, - */ - @property({ attribute: false, type: Array }) - get center() { - return this._center; - } - - /** - * current center of the view in EPSG:4326 - */ - get lonLatCenter() { - if (this.projection === "EPSG:4326") { - return this.map.getView().getCenter(); - } - return transform(this.map.getView().getCenter(), this.projection); - } - - /** - * current extent - */ - get lonLatExtent() { - const currentExtent = this.map - .getView() - .calculateExtent(this.map.getSize()); - if (this.projection === "EPSG:4326") { - return currentExtent; - } - return transformExtent(currentExtent, this.projection); - } - - private _zoom: number = 0; - - set zoom(zoom: number) { - if (zoom === undefined) return; - this._zoom = zoom; - this._animateToState(); - } - - /** - * Map center, always in the same projection as the view. - * when setting the map center, - */ - get zoom() { - return this._zoom; - } - - private _zoomExtent: import("ol/extent").Extent; - /** - * extent to zoom to (debounced) - * @type {import("ol/extent").Extent} - */ - set zoomExtent(extent: import("ol/extent").Extent) { - if (!extent || !extent.length) { - this._zoomExtent = undefined; - return; - } - const view = this.map.getView(); - cancelAnimation(view); - setTimeout(() => { - view.fit(extent, this.animationOptions); - }, 0); - this._zoomExtent = extent; - } - - private _controls: controlDictionary; - - set controls(controls: controlDictionary) { - const oldControls = this._controls; - const newControls = controls; - - // remove controls that are not defined anymore - if (oldControls) { - const oldControlTypes = Object.keys(oldControls); - const newControlTypes = Object.keys(newControls); - for (let i = 0, ii = oldControlTypes.length; i < ii; i++) { - const oldControlType = oldControlTypes[i]; - if (!newControlTypes.includes(oldControlType)) { - this.removeControl(oldControlType); - } - } - } - if (newControls) { - const keys = Object.keys(controls); - for (let i = 0, ii = keys.length; i < ii; i++) { - const key = keys[i] as controlType; - addOrUpdateControl(this, oldControls, key, controls[key]); - } - } - this._controls = newControls; - } - - /** - * Map controls, in JSON format - */ - get controls() { - return this._controls; - } - - private _layers: Array; - - /** - * The map layers, in eox-map JSON format! - * @type {Array} - */ - @property({ attribute: false, type: Array }) - set layers(layers: Array) { - const oldLayers = this._layers; - const newLayers = JSON.parse( - JSON.stringify(layers) - ).reverse() as Array; - - // remove layers that are not defined anymore - if (oldLayers) { - oldLayers.forEach((l: EoxLayer) => { - if ( - !l.properties?.id || // always remove old layers without id - !newLayers.find( - (newLayer) => newLayer.properties.id === l.properties.id - ) - ) { - const layerToBeRemoved = getLayerById(this, l.properties?.id); - const jsonDefinition = layerToBeRemoved.get("_jsonDefinition"); - jsonDefinition.interactions?.forEach( - (interaction: import("./src/generate").EOxInteraction) => { - if (interaction.type === "select") { - this.removeSelect(interaction.options.id); - } else { - this.removeInteraction(interaction.options.id); - } - } - ); - this.map.removeLayer(layerToBeRemoved); - } - }); - } - - newLayers.forEach((l) => { - this.addOrUpdateLayer(l); - }); - - // after all layers were added/updated/deleted, rearrange them in the correct order - const sortedIds = newLayers.map((l) => l.properties?.id); - this.map - .getLayers() - .getArray() - .sort((layerA, layerB) => { - return ( - sortedIds.indexOf(layerA.get("id")) - - sortedIds.indexOf(layerB.get("id")) - ); - }); - this._layers = newLayers; - } - - get layers() { - return this._layers; - } - - private _preventScroll: boolean; - - /** - * Set new `preventScroll` - */ - @property({ attribute: "prevent-scroll", type: Boolean }) - set preventScroll(preventScroll: boolean) { - if (preventScroll) { - removeDefaultScrollInteractions(this.map); - addScrollInteractions(this.map, true); - } else addScrollInteractions(this.map); - - this._preventScroll = preventScroll; - } - - /** - * Prevent accidental scrolling / drag-pan of the map. - * Scrolling only enabled while pressing the platform modifier key (ctrl/cmd). - * @type Boolean - */ - get preventScroll() { - return this._preventScroll; - } - - private _config: ConfigObject; - - set config(config: ConfigObject) { - this._config = config; - if (config?.animationOptions !== undefined) { - this.animationOptions = config.animationOptions; - } - this.projection = config?.view?.projection || "EPSG:3857"; - this.layers = config?.layers || []; - this.controls = config?.controls || {}; - if (this.preventScroll === undefined) { - this.preventScroll = config?.preventScroll; - } - this.zoom = config?.view?.zoom || 0; - this.center = config?.view?.center || [0, 0]; // set center after projection, order matters - this.zoomExtent = config?.view?.zoomExtent; - } - - private _animationOptions: EOxAnimationOptions = {}; - - /** - * option that are used when setting the `zoom`, `center` or `zoomExtent` of the map. - * animation options for `zoom` or `center`: https://openlayers.org/en/latest/apidoc/module-ol_View.html#~AnimationOptions - * animation options for `zoomExtent`: https://openlayers.org/en/latest/apidoc/module-ol_View.html#~FitOptions - * by default, no animations are disabled - */ - set animationOptions(animationOptions: EOxAnimationOptions) { - this._animationOptions = animationOptions; - } - /** - * sets animation properties - * @type {import("ol/View").AnimationOptions} animationOptions - */ - get animationOptions() { - return this._animationOptions; - } - - /** - * Config object, including `controls`, `layers` and `view`. - * Alternative way of defining the map config. - */ - @property({ attribute: false, type: Object }) - get config() { - return this._config; - } - - private _projection: ProjectionLike = "EPSG:3857"; - - /** - * @type ProjectionLike - */ - get projection() { - return this._projection || "EPSG:3857"; - } - - /** - * projection of the map view as SRS-identifier (e.g. EPSG:4326) - */ - @property({ attribute: "projection", type: String }) - set projection(projection: ProjectionLike) { - const oldView = this.map.getView(); - if ( - projection && - getProjection(projection) && - projection !== oldView.getProjection().getCode() - ) { - const newCenter = olTransform( - oldView.getCenter(), - oldView.getProjection().getCode(), - projection - ); - - const newProjection = getProjection(projection); - const oldResolution = oldView.getResolution(); - const oldMPU = oldView.getProjection().getMetersPerUnit(); - const newMPU = newProjection.getMetersPerUnit(); - const oldPointResolution = - getPointResolution( - oldView.getProjection(), - 1 / oldMPU, - oldView.getCenter(), - "m" - ) * oldMPU; - const newPointResolution = - getPointResolution(newProjection, 1 / newMPU, newCenter, "m") * newMPU; - - const newResolution = - (oldResolution * oldPointResolution) / newPointResolution; - - const newView = new View({ - zoom: oldView.getZoom(), - center: newCenter, - resolution: newResolution, - rotation: oldView.getRotation(), - projection, - }); - const eventTypes = [ - "change:center", - "change:resolution", - "change:rotation", - "propertychange", - ] as Array; - eventTypes.forEach((eventType: ViewObjectEventTypes) => { - const existingListeners = oldView.getListeners(eventType); - if (existingListeners?.length) { - for (let i = existingListeners.length - 1; i >= 0; i--) { - const listener = existingListeners[i]; - oldView.un(eventType as any, listener as any); - newView.on(eventType as any, listener as any); - } - } - }); - this.map.setView(newView); - this.getFlatLayersArray( - this.map.getLayers().getArray() as Array< - import("./src/generate").AnyLayer - > - ) - .filter((l) => l instanceof VectorLayer) - .forEach((l) => l.getSource().refresh()); - this._projection = projection; - this.center = newCenter; - } - } - - private _sync: string | EOxMap | undefined; - /** - * Sync map with another map view by providing its query selector or an eox-map DOM element - */ - @property() - set sync(sync: string | EOxMap | undefined) { - this._sync = sync; - if (sync) { - // Wait for next render tick - setTimeout(() => { - const originMap = getElement(sync) as EOxMap; - if (originMap) { - this.map.setView(originMap.map.getView()); - } - }); - } - } - - get sync() { - return this._sync; - } - - /** - * The native OpenLayers map object. - * See [https://openlayers.org/en/latest/apidoc/](https://openlayers.org/en/latest/apidoc/) - */ - @state() - map: Map = new Map({ - controls: [], - layers: [], - view: new View({ - center: [0, 0], - zoom: 0, - projection: this.projection, - }), - }); - - /** - * Dictionary of ol interactions associated with the map. - */ - @state() - interactions: { [index: string]: Draw | Modify } = {}; - - /** - * Dictionary of select interactions. - */ - @state() - selectInteractions: { [index: string]: EOxSelectInteraction } = {}; - - /** - * Dictionary of ol controls associated with the map. - */ - @state() - mapControls: { [index: string]: Control } = {}; - - /** - * animates to current definition. - * will animate to zoom/center if animationOptions are set - */ - private _animateToState() { - const animateToOptions = Object.assign({}, this.animationOptions); - const view = this.map.getView(); - cancelAnimation(view); - if (!animateToOptions || !Object.keys(animateToOptions).length) { - view.setCenter(this.center); - view.setZoom(this.zoom); - return; - } - animateToOptions.center = getCenterFromProperty(this.center); - animateToOptions.zoom = this.zoom; - view.animate(animateToOptions); - } - - /** - * Creates or updates an existing layer - * will update an layer if the ID already exists - * @param json EoxLayer JSON definition - * @returns the created or updated ol layer - */ - addOrUpdateLayer = (json: EoxLayer) => { - if (!json.interactions) { - json.interactions = []; - } - const id = json.properties?.id; - - // if id is undefined, never try to update an existing layer, always create a new one instead. - const existingLayer = id ? getLayerById(this, id) : false; - let layer; - if (existingLayer) { - updateLayer(this, json, existingLayer); - layer = existingLayer; - } else { - layer = createLayer(this, json); - this.map.addLayer(layer); - } - return layer; - }; - - /** - * Removes a given ol-interaction from the map. Layer have to be removed seperately - * @param id id of the interaction - */ - removeInteraction = (id: string | number) => { - this.map.removeInteraction(this.interactions[id]); - delete this.interactions[id]; - if (this.interactions[`${id}_modify`]) { - this.map.removeInteraction(this.interactions[`${id}_modify`]); - delete this.interactions[`${id}_modify`]; - } - }; - - /** - * Removes a given EOxSelectInteraction from the map. - * @param id id of the interaction - */ - removeSelect = (id: string | number) => { - this.selectInteractions[id].remove(); - delete this.selectInteractions[id]; - }; - - /** - * Removes a given control from the map. - * @param id id of the control element - */ - removeControl = (id: string) => { - this.map.removeControl(this.mapControls[id]); - delete this.mapControls[id]; - }; - - /** - * Gets an OpenLayers-Layer by its "id" - * @param id id of the layer - */ - getLayerById = (layerId: string) => { - return getLayerById(this, layerId); - }; - - /** - * Converts an array of OpenLayers Feature objects into a GeoJSON object. - * This function utilizes the OpenLayers GeoJSON format writer to serialize - * - * @param features - The array of features to be serialized. - * @returns The GeoJSON representation of the input features. - */ - parseFeature = (features: Feature[]) => { - const format = new GeoJSON(); - return format.writeFeaturesObject(features, READ_FEATURES_OPTIONS); - }; - - /** - * Parses a text representation of geographic features and adds them to a vector layer. - * - * @param text - The string representation of the features to be parsed. - * @param vectorLayer - The vector layer to which the parsed features will be added. - * @param replaceFeatures - A boolean flag indicating whether to replace the existing features in the vector layer. - */ - parseTextToFeature = ( - text: string, - vectorLayer: VectorLayer, - replaceFeatures: boolean = false - ) => { - parseText(text, vectorLayer, this, replaceFeatures); - }; - - /** - * given a projection code, this fetches the definition from epsg.io - * and registers the projection using proj4 - * @param code The EPSG code (e.g. 4326 or 'EPSG:4326'). - * - */ - registerProjectionFromCode = registerProjectionFromCode; - - /** - * registers a projection under a given name, defined via a proj4 definition - * @param name name of the projection (e.g. "EPSG:4326") - * @param projection proj4 projection definition string - */ - registerProjection = registerProjection; - - /** - * Returns a flat array of provided layers, including groups and nested layers. - * To get all layers without groups, you can use the native - * OL `getAllLayers` method on the map itself: - * https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html#getAllLayers - * - * @param layers layers Array - * @example getFlatLayersArray(eoxMap.map.getAllLayers()) - */ - getFlatLayersArray = getFlatLayersArray; - - /** - * Transforms coordinates to a given projection. - * If no `destination` is defined, the coordinate is transformed to EPSG:4326 - * @param {import('ol/coordinate').Coordinate} coordinate - * @param {import('ol/proj').ProjectionLike} source - * @param {import('ol/proj').ProjectionLike=} destination - * @returns {import('ol/coordinate'.Coordinate)} - */ - transform = transform; - - /** - * Transforms an extent to a given projection. - * If no `destination` is defined, the extent is transformed to EPSG:4326. - * @param {import('ol/extent').Extent} extent - * @param {import('ol/proj').ProjectionLike} source - * @param {import('ol/proj').ProjectionLike=} destination - * @returns {import('ol/extent').Extent} - */ - transformExtent = transformExtent; - - render() { - const shadowStyleFix = ` - :host { - display: block; - } - .eox-map-tooltip { - pointer-events: none !important; - } - `; - - return html` - -
- - `; - } - - /** - * Return extent increased by the provided value. - * @param {import("ol/extent").Extent} extent - * @param {number} value - * @returns {import("ol/extent").Extent} - */ - buffer(extent: import("ol/extent").Extent, value: number) { - return buffer(extent, value); - } - - firstUpdated() { - this.map.once("change:target", (e) => { - // set center again after target, as y-coordinate might be 0 otherwise - e.target.getView().setCenter(this.center); - }); - this.map.setTarget(this.renderRoot.querySelector("div")); - if (this._zoomExtent) { - this.map.getView().fit(this._zoomExtent, this.animationOptions); - } else { - this._animateToState(); - } - this.map.on("loadend", () => { - /** - * OpenLayers map has finished loading, passes the map instance as detail. - * For all other native events, attach an event listener to the map instance directly - */ - this.dispatchEvent(new CustomEvent("loadend", { detail: this.map })); - }); - this.dispatchEvent(new CustomEvent("mapmounted", { detail: this.map })); - } - - protected updated( - _changedProperties: // eslint-disable-next-line - PropertyValueMap | globalThis.Map - ): void {} -} diff --git a/elements/map/src/center.ts b/elements/map/src/center.ts deleted file mode 100644 index d78024d76..000000000 --- a/elements/map/src/center.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { equals } from "ol/coordinate"; -import { fromLonLat } from "ol/proj"; - -/** - * returns an ol-coordinate from a given center property. - * the coordinate expected coordinate system is EPSG:3857, however, - * if both parts of the coordinate are between -180 and 180, the coordinate - * system EPSG:4326 is assumed. - * use `map.getView().setCenter()` to manually set the center of the view. - * @param {Array} centerProperty - * @returns {import("ol/coordinate")} - */ -export function getCenterFromProperty(centerProperty: Array) { - if (centerProperty) { - const coordinate = centerProperty; - // compare: - // https://github.com/openlayers/openlayers/blob/v7.4.0/src/ol/proj.js - if ( - !equals(coordinate, [0, 0]) && - coordinate[0] >= -180 && - coordinate[0] <= 180 && - coordinate[1] >= -90 && - coordinate[1] <= 90 - ) { - return fromLonLat(coordinate); - } - return coordinate; - } - return [0, 0]; -} diff --git a/elements/map/src/compare.ts b/elements/map/src/compare.ts deleted file mode 100644 index f00c400f6..000000000 --- a/elements/map/src/compare.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { html } from "lit"; -import { choose } from "lit/directives/choose.js"; -import { property } from "lit/decorators.js"; -import { TemplateElement } from "../../../utils/templateElement"; - -type HTMLElementEvent = Event & { - target: T; -}; -export class EOxMapCompare extends TemplateElement { - @property() - value = 50; - - @property({ type: String }) - enabled = "true"; - - render() { - return html` - - ${choose( - this.enabled, - [ - ["first", () => html``], - ["second", () => html``], - ], - () => html` -
-
- -
-
- -
- ) => - (this.value = parseInt(evt.target.value))} - /> -
- ` - )} - `; - } -} - -customElements.define("eox-map-compare", EOxMapCompare); diff --git a/elements/map/src/controls/Geolocation.ts b/elements/map/src/controls/Geolocation.ts deleted file mode 100644 index 9e28b7171..000000000 --- a/elements/map/src/controls/Geolocation.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { Control } from "ol/control.js"; -import Feature from "ol/Feature.js"; -import Geolocation from "ol/Geolocation.js"; -import Point from "ol/geom/Point.js"; -import { Vector as VectorSource } from "ol/source.js"; -import { Vector as VectorLayer } from "ol/layer.js"; -import { Fill, Stroke, Style } from "ol/style"; - -export type GeolocationOptions = import("ol/control/Control").Options & { - /** - * @property {import("ol/style/flat.js").FlatStyleLike} style style definition of the position feature. - */ - style?: import("ol/style/flat.js").FlatStyleLike; - /** - * @property {boolean} centerWhenReady will pan the view to the user-location on the first position update. - */ - centerWhenReady?: boolean; - /** - * @property {boolean} highAccuracy enables high accuracy of geolocator. Required for tracking the heading. - */ - highAccuracy?: boolean; - /** - * @property {boolean} trackAccuracy tracks accuracy and displays it as a circle underneath the position feature. - */ - trackAccuracy?: boolean; - /** - * @property {boolean} trackHeading tracks heading and sets it as 'heading'-property on the position feature. - * "highAccuracy" must be set in order to track heading. - */ - trackHeading?: boolean; - /** - * @property {string} buttonIcon image src of control element icon - */ - buttonIcon?: string; -}; - -export default class GeolocationControl extends Control { - /** - * @param {GeolocationOptions} [opt_options] Control options. - */ - constructor(opt_options: GeolocationOptions) { - const options = opt_options || {}; - - const element = document.createElement("div"); - element.className = "geolocation ol-unselectable ol-control"; - const button = document.createElement("button"); - button.title = "Show your location"; - const image = document.createElement("img"); - if (options.buttonIcon) { - image.src = options.buttonIcon; - } else { - // fallback icon - const icon = `url( - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-6 -6 36 36'%3E %3Cpath d='M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M3.05,13H1V11H3.05C3.5,6.83 6.83,3.5 11,3.05V1H13V3.05C17.17,3.5 20.5,6.83 20.95,11H23V13H20.95C20.5,17.17 17.17,20.5 13,20.95V23H11V20.95C6.83,20.5 3.5,17.17 3.05,13M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z' /%3E%3C/svg%3E" - )`; - image.style.background = "var(--ol-subtle-foreground-color)"; - image.style.maskImage = icon; - image.style.webkitMaskImage = icon; - } - image.style.height = "100%"; - image.style.width = "100%"; - image.style.position = "absolute"; - image.style.pointerEvents = "none"; - element.appendChild(image); - element.appendChild(button); - - super({ - element: element, - }); - - this._centerWhenReady = options.centerWhenReady; - this._highAccuracy = options.highAccuracy; - this._trackAccuracy = options.trackAccuracy; - this._trackHeading = options.trackHeading; - - this._positionFeature = new Feature({ - geometry: new Point([NaN, NaN]), - heading: 0, - }); - - this._source = new VectorSource({ - features: [this._positionFeature], - }) as VectorSource< - Feature - >; - if (this._trackAccuracy) { - this._accuracyFeature = new Feature(); - // flat styles only work at the layer atm. - // for now, override the accuracy-feature style with a default one - this._accuracyFeature.setStyle( - new Style({ - fill: new Fill({ - color: "rgba(0, 0, 0, 0.2)", - }), - stroke: new Stroke({ - width: 2, - color: "rgba(0, 0, 0, 0.7)", - }), - }) - ); - this._source.addFeature(this._accuracyFeature); - } - - this._layer = new VectorLayer({ - source: this._source, - }); - - if (options.style) { - this._layer.setStyle(options.style); - } - - button.addEventListener("click", this.centerOnPosition.bind(this), false); - } - - /** - * Remove the control from its current map and attach it to the new map. - * Pass `null` to just remove the control from the current map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {import("ol/Map").default|null} map Map. - * @api - */ - setMap(map: import("ol/Map.js").default | null) { - this._layer.setMap(map); - super.setMap(map); - if (map && this._centerWhenReady) { - this.initGeolocation(); - } - } - - private _centerWhenReady: boolean; - private _highAccuracy: boolean; - private _positionFeature: Feature; - private _accuracyFeature: Feature; - private _trackAccuracy: boolean; - private _trackHeading: boolean; - private _layer: VectorLayer; - private _source: VectorSource< - Feature - >; - private _geolocation: Geolocation; - - /** - * initializes the geolocation control. - * calling this will cause a user prompt about allowing geolocation in the browser. - * @returns {Promise} returns a promise that resolves to coordinates on success, or an error event. - * this can be used to await the users input on the browsers location accept/deny - */ - initGeolocation() { - return new Promise((resolve, reject) => { - const map = this.getMap(); - if (map) { - this._geolocation = new Geolocation({ - tracking: true, - trackingOptions: { - enableHighAccuracy: this._highAccuracy, - }, - // take the projection to use from the map's view - projection: map.getView().getProjection(), - }); - } - - if (this._centerWhenReady) { - this._geolocation.once("change:position", (e) => { - map.getView().setCenter(e.target.getPosition()); - }); - } - - this._geolocation.on("error", function (evt) { - reject(evt); - }); - - this._geolocation.on("change:accuracyGeometry", () => { - if (this._trackAccuracy) { - this._accuracyFeature.setGeometry( - this._geolocation.getAccuracyGeometry() - ); - } - }); - this._geolocation.on("change:heading", (e) => { - if (this._trackHeading && this._highAccuracy) { - this._positionFeature.set("heading", e.target.getHeading()); - } - }); - - this._geolocation.on("change:position", () => { - const coordinates = this._geolocation.getPosition(); - this._positionFeature - .getGeometry() - .setCoordinates(coordinates ? coordinates : null); - resolve(coordinates); - }); - - this._geolocation.on("change:accuracyGeometry", () => { - if (this._trackAccuracy && this._accuracyFeature) { - this._accuracyFeature.setGeometry( - this._geolocation.getAccuracyGeometry() - ); - } - }); - }); - } - - /** - * returns the geolocation control button - * @returns - */ - getElement() { - return this.element; - } - - /** - * centers the map on the position of the geolocation, if possible - */ - async centerOnPosition() { - try { - await this.initGeolocation(); - const coordinates = this._geolocation?.getPosition(); - if (coordinates) { - this.getMap()?.getView().setCenter(coordinates); - } - } catch (e) { - // user denied geolocation - console.error(e); - } - } -} diff --git a/elements/map/src/controls/LoadingIndicator.ts b/elements/map/src/controls/LoadingIndicator.ts deleted file mode 100644 index bdfc4779d..000000000 --- a/elements/map/src/controls/LoadingIndicator.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Control } from "ol/control.js"; - -type LoadingIndicatorType = "small" | "fullscreen"; - -export type LoadingIndicatorOptions = import("ol/control/Control").Options & { - /** - * @property {string} spinnerSvg svg to be used as spinner icon - */ - spinnerSvg?: string; - /** - * @property {Opacity} opacity opacity, defaults to 1 - */ - opacity?: number; - /** - * @property {LoadingIndicatorType} type type of appearance. Small button style of fullscreen style. Defaults to button style. - */ - type?: LoadingIndicatorType; -}; - -export default class LoadingIndicatorControl extends Control { - /** - * @param {LoadingIndicatorOptions} [opt_options] Control options. - */ - constructor(opt_options: LoadingIndicatorOptions) { - const options = (opt_options || {}) as LoadingIndicatorOptions; - if (options.opacity === undefined) { - options.opacity = 1; - } - if (options.type === undefined) { - options.type = "small"; - } - - const element = document.createElement("div"); - element.className = "LoadingIndicator ol-unselectable ol-control"; - element.classList.add("loading-indicator"); - element.style.opacity = String(options.opacity); - - if (options.spinnerSvg) { - element.innerHTML = options.spinnerSvg; - } else { - // fallback icon - element.innerHTML = ` - - `; - } - if (options.type === "fullscreen") { - element.classList.add("fullscreen"); - } else { - element.classList.add("small"); - } - super({ - element: element, - }); - } - - /** - * Remove the control from its current map and attach it to the new map. - * Pass `null` to just remove the control from the current map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {import("ol/Map").default|null} map Map. - * @api - */ - setMap(map: import("ol/Map.js").default | null) { - super.setMap(map); - if (map) { - map.on("loadstart", () => { - this.getElement().style.visibility = "visible"; - }); - map.on("loadend", () => { - this.getElement().style.visibility = "hidden"; - }); - } - } - - /** - * returns the geolocation control button - * @returns - */ - getElement() { - return this.element; - } -} diff --git a/elements/map/src/controls/controls.css b/elements/map/src/controls/controls.css deleted file mode 100644 index 5b8503bed..000000000 --- a/elements/map/src/controls/controls.css +++ /dev/null @@ -1,28 +0,0 @@ -.geolocation { - top: 65px; - left: 0.5em; -} -.ol-touch.geolocation { - top: 80px; -} - -.loading-indicator { - position: absolute; - pointer-events: none !important; - display: flex; - align-items: center; - justify-content: center; - background-color: rgba(0, 0, 0, 0); -} - -.loading-indicator.small { - bottom: 0.5em; - left: 0.5em; - width: 30px; - height: 30px; -} - -.loading-indicator.fullscreen { - width: 100%; - height: 100%; -} diff --git a/elements/map/src/controls/controls.ts b/elements/map/src/controls/controls.ts deleted file mode 100644 index def29fd36..000000000 --- a/elements/map/src/controls/controls.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { EOxMap } from "../../main"; -import * as olControls from "ol/control"; -import { generateLayers } from "../generate"; -import Geolocation from "./Geolocation"; -import LoadingIndicator from "./LoadingIndicator"; - -const availableControls = { - ...olControls, - Geolocation, - LoadingIndicator, -}; - -export type controlType = - | "Attribution" - | "FullScreen" - | "MousePosition" - | "OverviewMap" - | "Rotate" - | "ScaleLine" - | "ZoomSlider" - | "ZoomToExtent" - | "Zoom" - | "Geolocation" - | "LoadingIndicator"; - -export type controlDictionary = { - [key in controlType]?: object; -}; - -/** - * adds a control to the map and to the `mapControls`-Dictionary, if it didnt exist yet. - * removes and remakes the control if the options differ - * otherwise leaves the control untouched - * @param EOxMap - * @param type - * @param options - */ -export function addOrUpdateControl( - EOxMap: EOxMap, - existingControls: controlDictionary, - type: controlType, - options: object -) { - if (existingControls && existingControls[type]) { - const controlHasChanged = - JSON.stringify(existingControls[type]) !== JSON.stringify(options); - if (controlHasChanged) { - EOxMap.removeControl(type); - addControl(EOxMap, type, options); - } - } else { - addControl(EOxMap, type, options); - } -} - -/** - * adds initial controls from webcomponent properties, if any are given. - */ -export function addControl(EOxMap: EOxMap, type: controlType, options: object) { - const controlOptions = Object.assign({}, options); - //@ts-expect-error options need to be according to the given control. - if (options && options.layers) { - //@ts-expect-error layers is not defined for each control - controlOptions.layers = generateLayers(EOxMap, options.layers); // parse layers (OverviewMap) - } - const control = new availableControls[type](controlOptions); - EOxMap.map.addControl(control); - EOxMap.mapControls[type] = control; -} - -/** - * adds initial controls from webcomponent properties, if any are given. - */ -export function addInitialControls(EOxMap: EOxMap) { - const controls = EOxMap.controls as controlDictionary | Array; - if (controls) { - if (Array.isArray(controls)) { - controls.forEach((controlName) => { - const control = new availableControls[controlName]({}); - EOxMap.map.addControl(control); - EOxMap.mapControls[controlName] = control; - }); - } else { - const keys = Object.keys(controls); - for (let i = 0, ii = keys.length; i < ii; i++) { - const controlName = keys[i] as controlType; - const controlOptions = controls[controlName]; - //@ts-expect-error layers is not defined for each control - if (controlOptions && controlOptions.layers) { - //@ts-expect-error layers is not defined for each control - controlOptions.layers = generateLayers(controlOptions.layers); // parse layers (OverviewMap) - } - const control = new availableControls[controlName](controlOptions); - EOxMap.map.addControl(control); - EOxMap.mapControls[controlName] = control; - } - } - } -} diff --git a/elements/map/src/custom/sources/WMTSCapabilities.ts b/elements/map/src/custom/sources/WMTSCapabilities.ts deleted file mode 100644 index e683d5e95..000000000 --- a/elements/map/src/custom/sources/WMTSCapabilities.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { TileImage } from "ol/source"; -import { optionsFromCapabilities } from "ol/source/WMTS"; -import WMTSCapabilitiesFormat from "ol/format/WMTSCapabilities.js"; -import { createFromTileUrlFunctions } from "ol/tileurlfunction.js"; -import { appendParams } from "ol/uri.js"; -import { AttributionLike } from "ol/source/Source"; -import { Projection, ProjectionLike } from "ol/proj"; - -type WMTSCapabilitiesOptions = { - url: string; - layer: string; - attributions?: AttributionLike; - attributionsCollapsible?: boolean; - cacheSize?: number; - crossOrigin?: null | string; - interpolate?: boolean; - projection?: ProjectionLike; - transition?: number; - key?: string; - tilePixelRatio?: number; - zDirection?: number; - wrapX?: boolean; - dimensions: object; - version: string; -}; - -class WMTSCapabilities extends TileImage { - constructor(options: WMTSCapabilitiesOptions) { - /** - * @param {WMTSCapabilitiesOptions} options Image tile options. - */ - super({ - attributions: options.attributions, - cacheSize: options.cacheSize, - tilePixelRatio: options.tilePixelRatio, - transition: options.transition, - interpolate: - options.interpolate !== undefined ? options.interpolate : true, - key: options.key, - attributionsCollapsible: options.attributionsCollapsible, - zDirection: options.zDirection, - wrapX: options.wrapX, - }); - - this.version_ = options.version !== undefined ? options?.version : "1.0.0"; - this.dimensions_ = - options.dimensions !== undefined ? options.dimensions : {}; - this.layer_ = options.layer; - - fetch(options.url) - .then((response) => response.text()) - .then((xml) => - new window.DOMParser().parseFromString(xml, "application/xml") - ) - .then((xml) => { - this.handleCapabilitiesResponse(xml, options); - }); - } - - version_: string; - dimensions_: object; - format_: string; - layer_: string; - matrixSet_: string; - style_: string; - requestEncoding_: string; - - /** - * - * @param {Document} xml - */ - handleCapabilitiesResponse(xml: Document, options: WMTSCapabilitiesOptions) { - const format = new WMTSCapabilitiesFormat(); - const parsedXml = format.read(xml); - const capabilitiesOptions = optionsFromCapabilities(parsedXml, options); - - this.crossOrigin = capabilitiesOptions.crossOrigin; - this.projection = capabilitiesOptions.projection as Projection; - this.tileGrid = capabilitiesOptions.tileGrid; - this.requestEncoding_ = capabilitiesOptions.requestEncoding; - this.matrixSet_ = capabilitiesOptions.matrixSet; - this.style_ = capabilitiesOptions.style; - this.format_ = capabilitiesOptions.format; - this.dimensions_ = capabilitiesOptions.dimensions; - this.setUrls(capabilitiesOptions.urls); - if (this.urls && this.urls.length > 0) { - this.tileUrlFunction = createFromTileUrlFunctions( - this.urls.map(this.createFromWMTSTemplate.bind(this)) - ); - } - this.setState("ready"); - } - - /** - * @param {string} template Template. - * @return {import("../Tile.js").UrlFunction} Tile URL function. - */ - createFromWMTSTemplate(template: string) { - const requestEncoding = this.requestEncoding_; - - // context property names are lower case to allow for a case insensitive - // replacement as some services use different naming conventions - const context = { - layer: this.layer_, - style: this.style_, - tilematrixset: this.matrixSet_, - }; - - if (requestEncoding == "KVP") { - Object.assign(context, { - Service: "WMTS", - Request: "GetTile", - Version: this.version_, - Format: this.format_, - }); - } - - template = - requestEncoding == "KVP" - ? appendParams(template, context) - : template.replace(/\{(\w+?)\}/g, function (m, p) { - //@ts-ignore - return p.toLowerCase() in context ? context[p.toLowerCase()] : m; - }); - - const tileGrid = - /** @type {import("../tilegrid/WMTS.js").default} */ this.tileGrid; - const dimensions = this.dimensions_; - - return ( - /** - * @param {import("../tilecoord.js").TileCoord} tileCoord Tile coordinate. - * @return {string|undefined} Tile URL. - */ - function (tileCoord: Array) { - if (!tileCoord) { - return undefined; - } - const localContext = { - //@ts-ignore - TileMatrix: tileGrid.getMatrixId(tileCoord[0]), - TileCol: tileCoord[1], - TileRow: tileCoord[2], - }; - Object.assign(localContext, dimensions); - let url = template; - if (requestEncoding == "KVP") { - url = appendParams(url, localContext); - } else { - url = url.replace(/\{(\w+?)\}/g, function (_, p) { - //@ts-ignore - return localContext[p]; - }); - } - return url; - } - ); - } -} - -export default WMTSCapabilities; diff --git a/elements/map/src/draw.ts b/elements/map/src/draw.ts deleted file mode 100644 index 535ea403f..000000000 --- a/elements/map/src/draw.ts +++ /dev/null @@ -1,79 +0,0 @@ -import Modify from "ol/interaction/Modify"; -import Draw, { createBox } from "ol/interaction/Draw"; -import { EOxMap } from "../main"; -import { Vector as VectorLayer } from "ol/layer"; -import { addNewFeature } from "../src-2/helpers"; - -export type DrawOptions = Omit< - import("ol/interaction/Draw").Options, - "type" -> & { - id: string | number; - type: "Point" | "LineString" | "Polygon" | "Circle" | "Box"; - modify?: boolean; - // TODO - active?: boolean; -}; - -/** - * Adds a `draw`-interaction to the map. - * Additionally, if {options.modify} is set, it also adds a `modify` interaction. The name `modify`-interaction - * follows the naming convention `${DrawOptions.id}_modify` - * @param {EOxMap} EOxMap - * @param {VectorLayer} drawLayer - * @param {DrawOptions} options - */ -export function addDraw( - EOxMap: EOxMap, - drawLayer: VectorLayer, - options: DrawOptions -): void { - const options_ = Object.assign({}, options); - if (EOxMap.interactions[options_.id]) { - throw Error(`Interaction with id: ${options_.id} already exists.`); - } - options_.modify = - typeof options_.modify === "boolean" ? options_.modify : true; - - const source = drawLayer.getSource(); - - if (options_.type === "Box") { - options_.geometryFunction = createBox(); - options_.type = "Circle"; - } - - //@ts-expect-error box - const drawInteraction = new Draw({ - ...options_, - source, - }); - - // TODO cleaner way of initializing as inactive? - if (options_.active === false) { - drawInteraction.setActive(false); - } - - drawInteraction.on("drawend", (e) => { - if (!drawLayer.get("isDrawingEnabled")) return; - addNewFeature(e, drawLayer, EOxMap, true); - }); - - // identifier to retrieve the interaction - EOxMap.map.addInteraction(drawInteraction); - EOxMap.interactions[options_.id] = drawInteraction; - const modifyInteraction = new Modify({ - source, - }); - modifyInteraction.setActive(options_.modify); - EOxMap.map.addInteraction(modifyInteraction); - EOxMap.interactions[`${options_.id}_modify`] = modifyInteraction; - - const removeLayerListener = () => { - if (!EOxMap.getLayerById(drawLayer.get("id"))) { - EOxMap.removeInteraction(options_.id); - EOxMap.removeInteraction(`${options_.id}_modify`); - EOxMap.map.getLayerGroup().un("change", removeLayerListener); - } - }; - EOxMap.map.getLayerGroup().on("change", removeLayerListener); -} diff --git a/elements/map/src/generate.ts b/elements/map/src/generate.ts deleted file mode 100644 index 2818bd169..000000000 --- a/elements/map/src/generate.ts +++ /dev/null @@ -1,477 +0,0 @@ -import { GeoJSON, MVT } from "ol/format"; - -import { - Group, - Image, - Tile as TileLayer, - Vector as VectorLayer, - VectorTile as VectorTileLayer, -} from "ol/layer"; - -import { - ImageWMS, - OSM, - Tile as TileSource, - TileWMS, - Vector as VectorSource, - VectorTile as VectorTileSource, - WMTS, - XYZ, -} from "ol/source"; - -import { FlatStyleLike } from "ol/style/flat"; -import { Collection } from "ol"; -import { DrawOptions, addDraw } from "./draw"; -import { EOxMap } from "../main"; -import { - EOxSelectInteraction, - SelectLayer, - SelectOptions, - addSelect, -} from "./select"; -import type Layer from "ol/layer/Base"; - -export type layerType = - | "Group" - | "Heatmap" - | "Image" - | "Layer" - | "Tile" - | "Vector" - | "VectorImage" - | "VectorTile"; -export type sourceType = - | "BingMaps" - | "Cluster" - | "GeoTIFF" - | "IIIF" - | "Image" - | "ImageCanvas" - | "ImageStatic" - | "ImageWMS" - | "OSM" - | "Raster" - | "StadiaMaps" - | "Tile" - | "TileArcGISRest" - | "TileDebug" - | "TileImage" - | "TileJSON" - | "TileWMS" - | "UrlTile" - | "Vector" - | "VectorTile" - | "WMTS" - | "XYZ" - | "WMTSCapabilities"; - -export type VectorOrVectorTileLayer = VectorLayer | VectorTileLayer; - -export type AnyLayerWithSource = - | import("ol/layer/BaseImage").default< - import("ol/source/Image").default, - import("ol/renderer/canvas/ImageLayer").default - > - | import("ol/layer/Tile").default - | VectorOrVectorTileLayer; - -/** - * any realistic layer, image, tile, vector. has "setSource" - */ -export type AnyLayer = AnyLayerWithSource | import("ol/layer/Group").default; - -const basicOlFormats = { - GeoJSON, - MVT, -}; - -export type formatWithOptions = { - type: string; - dataProjection?: string; - featureProjection?: string; -}; - -const basicOlLayers = { - Group, - Image, - Tile: TileLayer, - Vector: VectorLayer, - VectorTile: VectorTileLayer, -}; - -const basicOlSources = { - ImageWMS, - OSM, - Tile: TileSource, - TileWMS, - Vector: VectorSource, - VectorTile: VectorTileSource, - WMTS, - XYZ, -}; - -export type EOxInteraction = { - type: "draw" | "select"; - options: DrawOptions | SelectOptions; -}; - -export type EoxLayer = { - type: layerType; - properties?: object & { - id: string; - }; - minZoom?: number; - maxZoom?: number; - minResolution?: number; - maxResolution?: number; - opacity?: number; - visible?: boolean; - source?: { - type: sourceType; - format?: string | formatWithOptions; - tileGrid?: object; - projection?: import("ol/proj").ProjectionLike; - }; - layers?: Array; - style?: FlatStyleLike; - interactions?: Array; - zIndex?: number; - renderMode?: "vector" | "vectorImage"; -}; -import { get as getProjection } from "ol/proj.js"; -import { generateTileGrid } from "./tileGrid"; - -/** - * creates an ol-layer from a given EoxLayer definition object - * @param {EOxMap} EOxMap - * @param {EoxLayer} layer - * @param {boolean=} createInteractions - * @returns {AnyLayer} - */ -export function createLayer( - EOxMap: EOxMap, - layer: EoxLayer, - createInteractions: boolean = true -): AnyLayer { - layer = JSON.parse(JSON.stringify(layer)); - - const availableFormats = { - ...window.eoxMapAdvancedOlFormats, - ...basicOlFormats, - }; - - const availableLayers = { - ...window.eoxMapAdvancedOlLayers, - ...basicOlLayers, - }; - - const availableSources = { - ...window.eoxMapAdvancedOlSources, - ...basicOlSources, - }; - - const newLayer = availableLayers[layer.type]; - const newSource = availableSources[layer.source?.type]; - if (!newLayer) { - if (!window.eoxMapAdvancedOlLayers) { - throw new Error( - `Layer type ${layer.type} not created! Forgot to import advanced layers & sources plugin from @eox/map/dist/eox-map-advanced-layers-and-sources.js?` - ); - } else { - throw new Error(`Layer type ${layer.type} not supported!`); - } - } - if (layer.source && !newSource) { - if (!window.eoxMapAdvancedOlSources) { - throw new Error( - `Source type ${layer.source.type} not created! Forgot to import advanced layers & sources plugin from @eox/map/dist/eox-map-advanced-layers-and-sources.js?` - ); - } else { - throw new Error(`Source type ${layer.source.type} not supported!`); - } - } - - const tileGrid = generateTileGrid(layer); - - const olLayer = new newLayer({ - ...layer, - ...(layer.source && { - source: new newSource({ - ...layer.source, - // @ts-ignore - ...(layer.source.format && - layer.source.type !== "WMTS" && { - // @ts-ignore - format: new availableFormats[ - typeof layer.source.format === "object" - ? layer.source.format.type - : layer.source.format - ]({ - // @ts-ignore - ...(typeof layer.source.format === "object" && { - // @ts-ignore - ...layer.source.format, - }), - }), - }), - ...(layer.source.tileGrid && { - tileGrid, - }), - ...(layer.source.projection && { - projection: getProjection(layer.source.projection), - }), - }), - }), - ...(layer.type === "Group" && { - layers: [], - }), - ...layer.properties, - style: undefined, // override layer style, apply style after - }) as AnyLayer; - - olLayer.set("_jsonDefinition", layer, true); - - if (layer.type === "Group") { - const groupLayers = layer.layers - .reverse() - .map((l) => createLayer(EOxMap, l)); - // set a reference to the parent group to each layer of the group - groupLayers.forEach((l) => l.set("_group", olLayer, true)); - (olLayer as unknown as import("ol/layer/Group").default).setLayers( - new Collection(groupLayers) - ); - } - - if (layer.style) { - (olLayer as VectorOrVectorTileLayer).setStyle(layer.style); - } - if (createInteractions && layer.interactions?.length) { - for (let i = 0, ii = layer.interactions.length; i < ii; i++) { - const interactionDefinition = layer.interactions[i]; - addInteraction(EOxMap, olLayer as SelectLayer, interactionDefinition); - } - } - - setSyncListeners(olLayer, layer); - return olLayer; -} - -/** - * adds an interaction to a given layer - * @param EOxMap - * @param olLayer - * @param interactionDefinition - */ -function addInteraction( - EOxMap: EOxMap, - olLayer: SelectLayer, - interactionDefinition: EOxInteraction -) { - if (interactionDefinition.type === "draw") { - addDraw( - EOxMap, - olLayer as VectorLayer, - interactionDefinition.options as DrawOptions - ); - } else if (interactionDefinition.type === "select") { - addSelect(EOxMap, olLayer, interactionDefinition.options as SelectOptions); - } -} - -/** - * updates an existing layer - * @param {EoxLayer} newLayerDefinition - * @param {AnyLayer} existingLayer - * @returns {existingLayer} - */ -export function updateLayer( - EOxMap: EOxMap, - newLayerDefinition: EoxLayer, - existingLayer: AnyLayer -) { - const existingJsonDefintion = existingLayer.get( - "_jsonDefinition" - ) as EoxLayer; - - // there are probably more cases that make the layers incompatible - if ( - newLayerDefinition.type !== existingJsonDefintion.type || - newLayerDefinition.source?.type !== existingJsonDefintion.source?.type - ) { - throw new Error(`Layers are not compatible to be updated`); - } - - // create a completely new layer to take new source/style/properties from, if changed - // interactions are handled seperately - const newLayer = createLayer(EOxMap, newLayerDefinition, false); - - if ( - JSON.stringify(newLayerDefinition.source) !== - JSON.stringify(existingJsonDefintion.source) - ) { - (existingLayer as AnyLayerWithSource).setSource( - //@ts-expect-error format is not defined on image layer. expect the sources to be of the same type. - newLayer.getSource() - ); - } - - if ( - ["Vector", "VectorTile"].includes(newLayerDefinition.type) && - JSON.stringify(newLayerDefinition.style) !== - JSON.stringify(existingJsonDefintion.style) - ) { - (existingLayer as VectorLayer).setStyle( - (newLayer as VectorLayer).getStyle() - ); - } - - if ( - JSON.stringify(newLayerDefinition.properties) !== - JSON.stringify(existingJsonDefintion.properties) - ) { - existingLayer.setProperties(newLayerDefinition.properties); - } - - if (newLayerDefinition.visible !== existingJsonDefintion.visible) { - existingLayer.setVisible(newLayerDefinition.visible); - } - if (newLayerDefinition.opacity !== existingJsonDefintion.opacity) { - existingLayer.setOpacity(newLayerDefinition.opacity); - } - if ( - JSON.stringify(newLayerDefinition.interactions) !== - JSON.stringify(existingJsonDefintion.interactions) - ) { - existingJsonDefintion.interactions?.forEach((interactionDefinition) => { - const correspondingNewInteraction = newLayerDefinition.interactions.find( - (i) => i.type === interactionDefinition.type - ); - if (!correspondingNewInteraction) { - // remove all interactions that do not exist in the new layer definition - EOxMap.removeInteraction(interactionDefinition.options.id); - } else { - // interaction exists, but has changed - if (correspondingNewInteraction.type === "draw") { - const olDrawInteraction = EOxMap.interactions[ - correspondingNewInteraction.options.id - ] as import("ol/interaction").Draw; - olDrawInteraction.setActive( - correspondingNewInteraction.options.active - ); - const olModifyInteraction = EOxMap.interactions[ - `${correspondingNewInteraction.options.id}_modify` - ] as import("ol/interaction").Modify; - olModifyInteraction.setActive( - (correspondingNewInteraction.options as DrawOptions).modify - ); - } else { - const olSelectInteraction = EOxMap.selectInteractions[ - correspondingNewInteraction.options.id - ] as EOxSelectInteraction; - olSelectInteraction.setActive( - correspondingNewInteraction.options.active - ); - } - } - }); - // for each truly "new" interaction, add the corresponding interaction - newLayerDefinition.interactions?.forEach((interactionDefinition) => { - const correspondingExistingInteraction = - existingJsonDefintion.interactions.find( - (i) => i.type === interactionDefinition.type - ); - if (!correspondingExistingInteraction) { - addInteraction( - EOxMap, - existingLayer as SelectLayer, - interactionDefinition - ); - } - }); - } - - if (newLayerDefinition.type === "Group") { - const newLayerIds = newLayerDefinition.layers.map((l) => l.properties?.id); - // remove all layers from the group that do not exist in the new layer definition - const layerCollection = ( - existingLayer as unknown as import("ol/layer/Group").default - ).getLayers(); - // We can't remove the layers of a collection while iterating on it, - // this fails in removing all layers as expected. - // One solution is to create a shallow copy which we iterate - // in order to remove the layers from the actual layer collection - const layerArray = layerCollection.getArray().slice(); - layerArray.forEach((l: Layer) => { - if (!newLayerIds.includes(l.get("id"))) { - layerCollection.remove(l); - } - }); - - // add or update all layers - newLayerDefinition.layers.forEach((layerDefinitionInsideGroup) => { - const newLayerId = layerDefinitionInsideGroup.properties.id; - if ( - layerCollection - .getArray() - .map((l: Layer) => l.get("id")) - .includes(newLayerId) - ) { - // layer already existed - updateLayer( - EOxMap, - layerDefinitionInsideGroup, - EOxMap.getLayerById(newLayerId) - ); - } else { - // new layer inside this group - const newLayer = createLayer(EOxMap, layerDefinitionInsideGroup); - layerCollection.push(newLayer); - } - }); - - // after all layers were added/updated/deleted, rearrange them in the correct order - layerCollection.getArray().sort((layerA: Layer, layerB: Layer) => { - return ( - // change this order? the reverse order, because we want the topmost layer to be on top - newLayerIds.indexOf(layerA.get("id")) - - newLayerIds.indexOf(layerB.get("id")) - ); - }); - layerCollection.changed(); - } - setSyncListeners(existingLayer, newLayerDefinition); - existingLayer.set("_jsonDefinition", newLayerDefinition, true); - return existingLayer; -} - -export const generateLayers = (EOxMap: EOxMap, layerArray: Array) => { - if (!layerArray) { - return []; - } - return [...layerArray].reverse().map((l) => createLayer(EOxMap, l)); -}; - -/** - * set listeners to keep state of layer in sync with input json - * @param {Layer} olLayer - * @param {EoxLayer} eoxLayer - */ -function setSyncListeners(olLayer: Layer, eoxLayer: EoxLayer) { - olLayer.on("change:opacity", () => { - eoxLayer.opacity = olLayer.getOpacity(); - }); - olLayer.on("change:visible", () => { - eoxLayer.visible = olLayer.getVisible(); - }); - olLayer.on("change:zIndex", () => { - eoxLayer.zIndex = olLayer.getZIndex(); - }); - olLayer.on("propertychange", (e) => { - if (e.key === "map") { - // do not sync property when setting the "map" of the layer - return; - } - //@ts-ignore - eoxLayer.properties[e.key] = e.target.get(e.key); - }); -} diff --git a/elements/map/src/layer.ts b/elements/map/src/layer.ts deleted file mode 100644 index c7fb8403e..000000000 --- a/elements/map/src/layer.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { EOxMap } from "../main"; -import Group from "ol/layer/Group"; - -/** - * - * @param EOxMap instance of eox map class - * @param layerId id of ol-layer - * @returns {import("./generate").AnyLayerWithSource} Layer or `undefined` if layer does not exist - */ -export function getLayerById(EOxMap: EOxMap, layerId: string) { - const flatLayers = getFlatLayersArray( - EOxMap.map.getLayers().getArray() as Array< - import("./generate").AnyLayerWithSource - > - ); - return flatLayers.find((l) => l.get("id") === layerId); -} - -/** - * Returns a flat array of all map layers, including groups and nested layers. - * To get all layers without groups, you can use the native - * OL `getAllLayers` method on the map itself: - * https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html#getAllLayers - * - * @param layers layers Array - * @example getFlatLayersArray(eoxMap.map.getAllLayers()) - */ -export function getFlatLayersArray( - layers: Array -) { - const flatLayers = []; - flatLayers.push(...layers); - - let groupLayers = flatLayers.filter( - (l) => l instanceof Group - ) as unknown as Array; - - while (groupLayers.length) { - const newGroupLayers = []; - for (let i = 0, ii = groupLayers.length; i < ii; i++) { - const layersInsideGroup = groupLayers[i].getLayers().getArray(); - flatLayers.push(...layersInsideGroup); - newGroupLayers.push( - ...(layersInsideGroup.filter((l) => l instanceof Group) as Array) - ); - } - groupLayers = newGroupLayers; - } - return flatLayers as unknown as Array< - import("./generate").AnyLayerWithSource - >; -} diff --git a/elements/map/src/plugins/advancedLayersAndSources/formats.ts b/elements/map/src/plugins/advancedLayersAndSources/formats.ts deleted file mode 100644 index 7f0ce7280..000000000 --- a/elements/map/src/plugins/advancedLayersAndSources/formats.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as olFormats from "ol/format"; - -window.eoxMapAdvancedOlFormats = olFormats; diff --git a/elements/map/src/plugins/advancedLayersAndSources/index.ts b/elements/map/src/plugins/advancedLayersAndSources/index.ts deleted file mode 100644 index 3a441d777..000000000 --- a/elements/map/src/plugins/advancedLayersAndSources/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import "./formats"; -import "./layers"; -import "./sources"; diff --git a/elements/map/src/plugins/advancedLayersAndSources/layers.ts b/elements/map/src/plugins/advancedLayersAndSources/layers.ts deleted file mode 100644 index 453063c43..000000000 --- a/elements/map/src/plugins/advancedLayersAndSources/layers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as olLayers from "ol/layer"; -import STAC from "ol-stac"; -import { register } from "ol/proj/proj4.js"; -import proj4 from "proj4"; - -register(proj4); // required to support source reprojection - -window.eoxMapAdvancedOlLayers = { - ...olLayers, - STAC, -}; diff --git a/elements/map/src/plugins/advancedLayersAndSources/sources.ts b/elements/map/src/plugins/advancedLayersAndSources/sources.ts deleted file mode 100644 index 796bce202..000000000 --- a/elements/map/src/plugins/advancedLayersAndSources/sources.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as olSources from "ol/source"; -import WMTSCapabilities from "../../custom/sources/WMTSCapabilities"; - -window.eoxMapAdvancedOlSources = { - ...olSources, - WMTSCapabilities, -}; diff --git a/elements/map/src/select.ts b/elements/map/src/select.ts deleted file mode 100644 index 27efc53df..000000000 --- a/elements/map/src/select.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { EOxMap } from "../main"; -import { Overlay } from "ol"; -import "./tooltip"; -import { EOxMapTooltip } from "./tooltip"; -import { EoxLayer, createLayer } from "./generate"; -import Feature from "ol/Feature"; -import RenderFeature from "ol/render/Feature"; -import VectorTileLayer from "ol/layer/VectorTile.js"; -import VectorLayer from "ol/layer/Vector.js"; -import MapBrowserEvent from "ol/MapBrowserEvent"; -import { Extent, createEmpty, extend, isEmpty } from "ol/extent"; -import { FitOptions } from "ol/View"; - -export type SelectLayer = VectorTileLayer | VectorLayer; - -export type SelectOptions = Omit< - import("ol/interaction/Select").Options, - "condition" -> & { - id: string | number; - idProperty?: string; - condition: "click" | "pointermove"; - layer?: EoxLayer; - style?: import("ol/style/flat.js").FlatStyleLike; - overlay?: import("ol/Overlay").Options; - active?: boolean; - panIn?: boolean; -}; - -export class EOxSelectInteraction { - eoxMap: EOxMap; - options: SelectOptions; - active: boolean; - panIn: boolean; - tooltip: HTMLElement; - selectedFids: Array; - selectLayer: SelectLayer; - selectStyleLayer: SelectLayer; - changeSourceListener: () => void; - - constructor( - eoxMap: EOxMap, - selectLayer: SelectLayer, - options: SelectOptions - ) { - this.eoxMap = eoxMap; - this.selectLayer = selectLayer; - this.options = options; - this.active = options.active || selectLayer.getVisible(); - this.panIn = options.panIn || false; - - const existingTooltip = this.eoxMap.map.getOverlayById("eox-map-tooltip"); - - let overlay: Overlay; - this.selectedFids = []; - - if (existingTooltip) { - this.tooltip = existingTooltip.getElement(); - overlay = existingTooltip; - } else { - this.tooltip = - this.eoxMap.querySelector("eox-map-tooltip") || - options.overlay?.element; - - if (this.tooltip) { - overlay = new Overlay({ - element: this.tooltip, - position: undefined, - offset: [0, 0], - positioning: "top-left", - className: "eox-map-tooltip", - id: "eox-map-tooltip", - ...options.overlay, - }); - this.eoxMap.map.addOverlay(overlay); - } - } - - const pointerLeaveListener = () => { - if (overlay && options.condition === "pointermove") { - overlay.setPosition(undefined); - } - }; - eoxMap.map.on("change:target", (e) => { - e.oldValue?.removeEventListener("pointerleave", pointerLeaveListener); - e.target - .getTargetElement() - ?.addEventListener("pointerleave", pointerLeaveListener); - }); - eoxMap.map - .getTargetElement() - ?.addEventListener("pointerleave", pointerLeaveListener); - - // a layer that only renders the selected features, for displaying purposes only - // unmanaged by the map - let layerDefinition; - if (this.options.layer) { - layerDefinition = this.options.layer; - } else { - // a layer can be defined by only its style property as a shorthand. - const originalJsonDefinition = this.selectLayer.get("_jsonDefinition"); - layerDefinition = { - ...originalJsonDefinition, - style: options.style, - properties: { - id: this.selectLayer.get("id") + "_select", - }, - source: { - type: originalJsonDefinition.type, - }, - } as EoxLayer; - } - layerDefinition.renderMode = "vector"; - delete layerDefinition.interactions; - - this.selectStyleLayer = createLayer( - eoxMap, - layerDefinition as EoxLayer - ) as SelectLayer; - - //@ts-expect-error VectorSource for VectorLayer, VectorTileSource for VectorTileLayer - this.selectStyleLayer.setSource(this.selectLayer.getSource()); - this.selectStyleLayer.setMap(this.eoxMap.map); - - const initialStyle = this.selectStyleLayer.getStyleFunction(); - - this.selectStyleLayer.setStyle( - (feature: import("ol/Feature").FeatureLike, resolution: number) => { - if ( - this.selectedFids.length && - this.selectedFids.includes(this.getId(feature)) - ) { - return initialStyle(feature, resolution); - } - return null; - } - ); - - const listener = (event: MapBrowserEvent) => { - if (!this.active) { - return; - } - const currentZoom = this.eoxMap.map.getView().getZoom(); - if ( - event.dragging || - !this.active || - currentZoom < this.selectLayer.getMinZoom() || - currentZoom > this.selectLayer.getMaxZoom() - ) { - return; - } - this.selectLayer - .getFeatures(event.pixel) - .then((features: Array) => { - const feature = features.length ? features[0] : null; - - const newSelectFids = feature ? [this.getId(feature)] : []; - const idChanged = this.selectedFids[0] !== newSelectFids[0]; - this.selectedFids = newSelectFids; - if (idChanged) { - this.selectStyleLayer.changed(); - if (feature && this.panIn) this.panIntoFeature(feature); - } - - if (overlay) { - const xPosition = - event.pixel[0] > this.eoxMap.offsetWidth / 2 ? "right" : "left"; - const yPosition = - event.pixel[1] > this.eoxMap.offsetHeight / 2 ? "bottom" : "top"; - overlay.setPositioning(`${yPosition}-${xPosition}`); - overlay.setPosition(feature ? event.coordinate : null); - if (feature && (this.tooltip).renderContent) { - (this.tooltip).renderContent(feature); - } - } - - const selectdEvt = new CustomEvent("select", { - detail: { - id: options.id, - originalEvent: event, - feature: feature, - }, - }); - this.eoxMap.dispatchEvent(selectdEvt); - }); - }; - this.eoxMap.map.on(options.condition || "click", listener); - - // if the parent layer changes, also change the selection layer. - this.selectLayer.on("change:opacity", () => { - this.selectStyleLayer.setOpacity(this.selectLayer.getOpacity()); - }); - - this.selectLayer.on("change:visible", () => { - const visible = this.selectLayer.getVisible(); - this.selectStyleLayer.setVisible(visible); - this.setActive(visible); - }); - - this.changeSourceListener = () => { - //@ts-expect-error VectorSource for VectorLayer, VectorTileSource for VectorTileLayer - this.selectStyleLayer.setSource(this.selectLayer.getSource()); - }; - - this.selectLayer.on("change:source", this.changeSourceListener); - - const changeLayerListener = () => { - if (eoxMap.getLayerById(selectLayer.get("id"))) { - eoxMap.selectInteractions[options.id]?.setActive(true); - this.selectStyleLayer?.setMap(this.eoxMap.map); - overlay?.setMap(this.eoxMap.map); - } else { - // remove the baselayer and the interaction if vector layer is removed - eoxMap.selectInteractions[options.id]?.setActive(false); - this.selectStyleLayer?.setMap(null); - overlay?.setMap(null); - } - }; - eoxMap.map.getLayerGroup().on("change", changeLayerListener); - } - - setActive(active: boolean) { - this.active = active; - } - - /** - * Pan into the feature's extent - * @param featureOrExtent Feature, Render Feature or Extent - * @param options fit options, defaults to 750ms animation - */ - panIntoFeature = ( - featureOrExtent: Feature | RenderFeature | Extent, - options?: FitOptions - ) => { - const extent = - featureOrExtent instanceof Feature || - featureOrExtent instanceof RenderFeature - ? featureOrExtent.getGeometry().getExtent() - : featureOrExtent; - this.eoxMap.map.getView().fit(extent, options || { duration: 750 }); - }; - - /** - * highlights one or more features by their IDs. Does not fire select events. - * @param {Array} ids - * @param {FitOptions} fitOptions - */ - highlightById(ids: Array, fitOptions?: FitOptions) { - this.selectedFids = ids; - if (ids.length && fitOptions) { - const extent = createEmpty(); - if (this.selectLayer instanceof VectorLayer) { - for (let i = 0, ii = this.selectedFids.length; i < ii; i++) { - const id = this.selectedFids[i]; - const feature = this.selectLayer.getSource().getFeatureById(id); - if (feature && feature.getGeometry()) { - extend(extent, feature.getGeometry().getExtent()); - } - } - } else { - const map = this.eoxMap.map; - if (!map.getView()) { - return; - } - const allRenderedFeatures = this.selectLayer.getFeaturesInExtent( - map.getView().calculateExtent(map.getSize()) - ); - for (let i = 0, ii = allRenderedFeatures.length; i < ii; i++) { - const renderFeature = allRenderedFeatures[i]; - if (this.selectedFids.includes(this.getId(renderFeature))) { - extend(extent, renderFeature.getGeometry().getExtent()); - } - } - } - if (!isEmpty(extent)) { - this.panIntoFeature(extent, fitOptions); - } - } - this.selectStyleLayer.changed(); // force rerender to highlight selected fids - } - - /** - * removes this interaction and the connected unmanaged layer from the map - */ - remove() { - this.selectStyleLayer.setMap(null); - delete this.eoxMap.selectInteractions[this.options.id]; - this.selectLayer.un("change:source", this.changeSourceListener); - //this.eoxMap.map.getLayers().un("remove", this.removeListener); - } - - /** - * returns the ID of a feature. - * @param feature - * @returns {number | string} ID value of feature - */ - private getId(feature: Feature | RenderFeature) { - if (this.options.idProperty) { - return feature.get(this.options.idProperty); - } - if (feature.getId() !== undefined) { - return feature.getId(); - } - if (feature.get("id") !== undefined) { - return feature.get("id"); - } - throw Error( - "No feature id found. Please provide which feature property should be taken instead using idProperty." - ); - } -} - -/** - * Adds a `select`-interaction to the map. - * @param {EOxMap} EOxMap - * @param {SelectLayer} selectLayer - * @param {SelectOptions} options - */ -export function addSelect( - EOxMap: EOxMap, - selectLayer: SelectLayer, - options: SelectOptions -) { - if (EOxMap.interactions[options.id]) { - throw Error(`Interaction with id: ${options.id} already exists.`); - } - EOxMap.selectInteractions[options.id] = new EOxSelectInteraction( - EOxMap, - selectLayer, - options - ); - - return EOxMap.selectInteractions[options.id]; -} diff --git a/elements/map/src/tileGrid.ts b/elements/map/src/tileGrid.ts deleted file mode 100644 index 2d857f219..000000000 --- a/elements/map/src/tileGrid.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { createXYZ } from "ol/tilegrid"; -import WMTSTileGrid from "ol/tilegrid/WMTS"; -import { getTopLeft, getWidth } from "ol/extent.js"; -import { get as getProjection } from "ol/proj.js"; - -/** - * generates a WMTS tile grid for WMTS layers or else an XYZ tile grid, if defined. - * @param layer - * @returns {WMTSTileGrid | import("ol/tilegrid/TileGrid").default | undefined} - */ -export function generateTileGrid(layer: import("./generate").EoxLayer) { - let tileGrid; - if (!layer.source?.tileGrid) { - return undefined; - } - - if (layer.source.tileGrid) { - if (layer.source.type === "WMTS") { - const projection = getProjection("EPSG:3857"); - const projectionExtent = projection.getExtent(); - const size = getWidth(projectionExtent) / 128; - const resolutions = new Array(19); - const matrixIds = new Array(19); - for (let z = 0; z < 19; ++z) { - // generate resolutions and matrixIds arrays for this WMTS - resolutions[z] = size / Math.pow(2, z); - matrixIds[z] = z; - } - - tileGrid = new WMTSTileGrid({ - resolutions: resolutions, - origin: getTopLeft(projectionExtent), - // @ts-ignore - projection: layer.source.tileGrid.projection || "EPSG:3857", - matrixIds: matrixIds, - ...layer.source.tileGrid, - }); - } else { - tileGrid = createXYZ({ - ...layer.source.tileGrid, - }); - } - } - return tileGrid; -} diff --git a/elements/map/src/tooltip.ts b/elements/map/src/tooltip.ts deleted file mode 100644 index 16206ce80..000000000 --- a/elements/map/src/tooltip.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { html, render } from "lit"; -import { property } from "lit/decorators.js"; -import Feature from "ol/Feature"; -import RenderFeature from "ol/render/Feature"; -import { TemplateElement } from "../../../utils/templateElement"; - -export class EOxMapTooltip extends TemplateElement { - /** - * Transform the default rendering of each feature property key/value. - * Useful for e.g. translating keys or introducing a whitelist. - * Passes the current property key and value as first argument, - * and the hovered feature as second argument. - * @example - * .propertyTransform=${({key, value}, feature) => myWhitelist.includes(key)} - */ - @property() - propertyTransform = ( - property: { key: string; value: unknown }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _feature: Feature | RenderFeature - ) => property; - - renderContent(feature: Feature | RenderFeature) { - render( - this.hasTemplate("properties") - ? html`${this.renderTemplate( - "properties", - feature.getProperties(), - // `tooltip-${this.content.id}` - "tooltip-1" - )}` - : html` -
    - ${Object.entries(feature.getProperties()) - .map(([key, value]) => - this.propertyTransform({ key, value }, feature) - ) - .filter((v) => v) - .map( - ({ key, value }) => - html`
  • ${key}: ${value}
  • ` - )} -
`, - this.shadowRoot - ); - } - - constructor() { - super(); - } -} - -customElements.define("eox-map-tooltip-2", EOxMapTooltip); diff --git a/elements/map/src/utils.ts b/elements/map/src/utils.ts deleted file mode 100644 index be958c2dd..000000000 --- a/elements/map/src/utils.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { DragPan, MouseWheelZoom } from "ol/interaction"; -import { platformModifierKeyOnly } from "ol/events/condition"; -import { - transform as olTransform, - transformExtent as olTransformExtent, -} from "ol/proj"; - -/** - * a helper function to determin approximate equality between 2 coordinates. - * based on ol "equals" - * @param {import("ol/coordinate")} coordinate1 First coordinate. - * @param {import("ol/coordinate")} coordinate2 Second coordinate. - * @param {number?} epsilon tolerance - * @return {boolean} The two coordinates are equal. - */ -export function coordinatesRoughlyEquals( - coordinate1: import("ol/coordinate").Coordinate, - coordinate2: import("ol/coordinate").Coordinate, - epsilon = 0.001 -): boolean { - let equals = true; - for (let i = coordinate1.length - 1; i >= 0; --i) { - if (!numbersRoughlyEquals(coordinate1[i], coordinate2[i], epsilon)) { - equals = false; - break; - } - } - return equals; -} - -/** - * helper function to roughly compare number, e.g. for coordinate or zoom comparison - * @param {number} number1 - * @param {number} number2 - * @param {number?} epsilon tolerance - * @return {boolean} - */ -export function numbersRoughlyEquals( - number1: number, - number2: number, - epsilon = 0.001 -): boolean { - return Math.abs(number1 - number2) < epsilon; -} - -/** - * Adds interactions for preventing scroll behavior on the specified OpenLayers map. - * - * @param {import("ol").Map} map - The OpenLayers map to which the interactions should be added. - * @param {boolean} customInteraction - state if customInteraction to be added or default - */ -export function addScrollInteractions( - map: import("ol").Map, - customInteraction: boolean = false -) { - if (customInteraction) { - map.addInteraction( - new MouseWheelZoom({ - condition: platformModifierKeyOnly, - }) - ); - - const isTouchDevice = - "ontouchstart" in window || navigator.maxTouchPoints > 0; - if (isTouchDevice) { - map.addInteraction( - new DragPan({ - condition: function (event) { - return ( - (this as DragPan).getPointerCount() === 2 || - platformModifierKeyOnly(event) - ); - }, - }) - ); - } else map.addInteraction(new DragPan()); - } else { - map.addInteraction(new MouseWheelZoom()); - map.addInteraction(new DragPan()); - } -} - -/** - * Removes interactions that prevent scroll behavior from the specified OpenLayers map. - * - * @param {import("ol").Map} map - The OpenLayers map from which the interactions should be removed. - */ -export function removeDefaultScrollInteractions(map: import("ol").Map) { - map - .getInteractions() - .getArray() - .forEach((interaction) => { - if ( - interaction instanceof MouseWheelZoom || - interaction instanceof DragPan - ) - map.removeInteraction(interaction); - }); -} - -/** - * Transforms coordinates to a given projection. - * If no `destination` is defined, the coordinate is transformed to EPSG:4326 - * @param {import('ol/coordinate').Coordinate} coordinate - * @param {import('ol/proj').ProjectionLike} source - * @param {import('ol/proj').ProjectionLike=} destination - * @returns {import('ol/coordinate'.Coordinate)} - */ -export function transform( - coordinate: import("ol/coordinate").Coordinate, - source: import("ol/proj").ProjectionLike, - destination?: import("ol/proj").ProjectionLike -) { - if (!destination) { - destination = "EPSG:4326"; - } - return olTransform(coordinate, source, destination); -} - -/** - * Transforms an extent to a given projection. - * Uf no `destination` is defined, the extent is transformed to EPSG:4326. - * @param {import('ol/extent').Extent} extent - * @param {import('ol/proj').ProjectionLike} source - * @param {import('ol/proj').ProjectionLike=} destination - * @returns {import('ol/extent').Extent} - */ -export function transformExtent( - extent: import("ol/extent").Extent, - source: import("ol/proj").ProjectionLike, - destination?: import("ol/proj").ProjectionLike -) { - if (!destination) { - destination = "EPSG:4326"; - } - return olTransformExtent(extent, source, destination); -} diff --git a/elements/map/test/drawInteraction.cy.js b/elements/map/test/drawInteraction.cy.js index 5556db84f..25ad3b22d 100644 --- a/elements/map/test/drawInteraction.cy.js +++ b/elements/map/test/drawInteraction.cy.js @@ -19,9 +19,7 @@ describe("draw interaction", () => { const eoxMap = $el[0]; const map = eoxMap.map; const originalNumberOfInteractions = map.getInteractions().getLength(); - const newLayerJson = [ - Object.assign({}, drawInteractionLayerJson[0]), - ]; + const newLayerJson = [Object.assign({}, drawInteractionLayerJson[0])]; delete newLayerJson[0].interactions; eoxMap.layers = newLayerJson; drawInteraction = $el[0].interactions["drawInteraction"]; @@ -42,9 +40,7 @@ describe("draw interaction", () => { const eoxMap = $el[0]; simulateEvent(eoxMap.map, "pointerdown", 10, 20); simulateEvent(eoxMap.map, "pointerup", 10, 20); - const drawLayer = eoxMap.getLayerById( - "drawLayer" - ); + const drawLayer = eoxMap.getLayerById("drawLayer"); const features = drawLayer.getSource().getFeatures(); const geometry = features[0].getGeometry(); expect(features).to.have.length(1); @@ -110,9 +106,7 @@ describe("draw interaction", () => { simulateEvent(eoxMap.map, "pointerdown", 30, 20); simulateEvent(eoxMap.map, "pointerup", 30, 20); - const drawLayer = eoxMap.getLayerById( - "drawLayer" - ); + const drawLayer = eoxMap.getLayerById("drawLayer"); const source = drawLayer.getSource(); const features = source.getFeatures(); expect(features).to.have.length(1); @@ -151,9 +145,7 @@ describe("draw interaction", () => { simulateEvent(eoxMap.map, "pointerdown", 10, 20); simulateEvent(eoxMap.map, "pointerup", 10, 20); - const drawLayer = eoxMap.getLayerById( - "drawLayer" - ); + const drawLayer = eoxMap.getLayerById("drawLayer"); const features = drawLayer.getSource().getFeatures(); expect(features).to.have.length(1); }); diff --git a/elements/map/test/groupLayer.cy.js b/elements/map/test/groupLayer.cy.js index a4cb6a179..f293fe114 100644 --- a/elements/map/test/groupLayer.cy.js +++ b/elements/map/test/groupLayer.cy.js @@ -221,9 +221,7 @@ describe("layers", () => { cy.get("eox-map").and(($el) => { const eoxMap = $el[0]; eoxMap.layers = layersJson; - const layer = eoxMap.getLayerById( - "regionsRed" - ); + const layer = eoxMap.getLayerById("regionsRed"); const styleObject = layer.getStyle(); const fillColor = styleObject["fill-color"]; expect(fillColor, "reactive layer 2 levels deep").to.be.equal("red"); diff --git a/elements/map/test/hoverInteraction.cy.js b/elements/map/test/hoverInteraction.cy.js index ffdbfe749..b3b9156c6 100644 --- a/elements/map/test/hoverInteraction.cy.js +++ b/elements/map/test/hoverInteraction.cy.js @@ -52,7 +52,7 @@ describe("select interaction with hover", () => { req.reply(ecoRegionsFixture); } ); - (vectorLayerStyleJson).push({ + vectorLayerStyleJson.push({ type: "Tile", properties: { id: "osm", diff --git a/elements/map/test/selectInteraction.cy.js b/elements/map/test/selectInteraction.cy.js index f046a051f..fd6f6b3cc 100644 --- a/elements/map/test/selectInteraction.cy.js +++ b/elements/map/test/selectInteraction.cy.js @@ -36,11 +36,8 @@ describe("select interaction on click", () => { encoding: "binary", }); return new Cypress.Promise((resolve) => { - const layerJson = JSON.parse( - JSON.stringify(vectorTileLayerJson) - ); - layerJson[0].interactions = - vectorTileInteraction; + const layerJson = JSON.parse(JSON.stringify(vectorTileLayerJson)); + layerJson[0].interactions = vectorTileInteraction; cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { const eoxMap = $el[0]; @@ -62,9 +59,7 @@ describe("select interaction on click", () => { req.reply(ecoRegionsFixture); } ); - const styleJson = JSON.parse( - JSON.stringify(vectorLayerJson) - ); + const styleJson = JSON.parse(JSON.stringify(vectorLayerJson)); styleJson[0].minZoom = 3; styleJson[0].interactions = [ { @@ -100,9 +95,7 @@ describe("select interaction on click", () => { req.reply(ecoRegionsFixture); } ); - const styleJson = JSON.parse( - JSON.stringify(vectorLayerJson) - ); + const styleJson = JSON.parse(JSON.stringify(vectorLayerJson)); styleJson[0].interactions = [ { type: "select", @@ -145,9 +138,7 @@ describe("select interaction on click", () => { }); it.only("programmatically highlight by IDs (VectorTileLayer)", () => { - const layerJson = JSON.parse( - JSON.stringify(vectorTileLayerJson) - ); + const layerJson = JSON.parse(JSON.stringify(vectorTileLayerJson)); layerJson[0].interactions = vectorTileInteraction; return new Cypress.Promise((resolve) => { cy.intercept(/^.*geoserver.*$/, { @@ -155,8 +146,7 @@ describe("select interaction on click", () => { "./map/test/fixtures/tiles/mapbox-streets-v6/14/8937/5679.vector.pbf,null", encoding: "binary", }); - layerJson[0].interactions = - vectorTileInteraction; + layerJson[0].interactions = vectorTileInteraction; cy.mount( html`` ).as("eox-map"); @@ -183,9 +173,7 @@ describe("select interaction on click", () => { }); it("remove interaction", () => { - const styleJson = JSON.parse( - JSON.stringify(vectorTileLayerJson) - ); + const styleJson = JSON.parse(JSON.stringify(vectorTileLayerJson)); styleJson[0].interactions = [ { type: "select", diff --git a/elements/map/test/tooltip.cy.js b/elements/map/test/tooltip.cy.js index 7f01035bd..d21bd7271 100644 --- a/elements/map/test/tooltip.cy.js +++ b/elements/map/test/tooltip.cy.js @@ -49,10 +49,7 @@ describe("tooltip", () => { multiplelayersJson.unshift(secondLayer); secondLayer.properties.id = "regions2"; secondLayer.interactions[0].options.id = "selectInteraction2"; - ( - secondLayer.interactions[0] - .options - ).layer.properties.id = "selectLayer2"; + secondLayer.interactions[0].options.layer.properties.id = "selectLayer2"; secondLayer.visible = false; cy.mount( html` diff --git a/elements/map/test/updateStyle.cy.js b/elements/map/test/updateStyle.cy.js index 9e74513a7..65c33a88b 100644 --- a/elements/map/test/updateStyle.cy.js +++ b/elements/map/test/updateStyle.cy.js @@ -11,7 +11,7 @@ describe("layers", () => { req.reply(ecoRegionsFixture); } ); - (vectorLayerStyleJson[0]).style = { + vectorLayerStyleJson[0].style = { "fill-color": "yellow", "stroke-color": "black", "stroke-width": 2, @@ -38,18 +38,12 @@ describe("layers", () => { $el[0].addOrUpdateLayer(updatedLayerJson); const layer = $el[0].map.getLayers().getArray()[0]; - const features = (layer) - .getSource() - .getFeatures(); + const features = layer.getSource().getFeatures(); expect(features.length).to.be.greaterThan(0); - const styleFunction = ( - layer - ).getStyleFunction(); - const featureStyle = ( - styleFunction(features[0], 1) - )[0]; + const styleFunction = layer.getStyleFunction(); + const featureStyle = styleFunction(features[0], 1)[0]; const featureColor = featureStyle.getFill().getColor(); diff --git a/elements/map/test/vectorLayer.cy.js b/elements/map/test/vectorLayer.cy.js index 2b9401f3f..4de29115e 100644 --- a/elements/map/test/vectorLayer.cy.js +++ b/elements/map/test/vectorLayer.cy.js @@ -58,9 +58,7 @@ describe("layers", () => { }; eoxMap.layers = [layerWithFormats]; - const source = eoxMap - .getLayerById("0") - .getSource(); + const source = eoxMap.getLayerById("0").getSource(); const coordinates = source .getFeatures()[0] .getGeometry() @@ -84,7 +82,7 @@ describe("layers", () => { req.reply(ecoRegionsFixture); } ); - (vectorLayerStyleJson[0]).style = { + vectorLayerStyleJson[0].style = { "fill-color": "yellow", "stroke-color": "black", "stroke-width": 2, @@ -94,9 +92,7 @@ describe("layers", () => { ); cy.get("eox-map").and(($el) => { return new Cypress.Promise((resolve) => { - const layer = $el[0].map - .getLayers() - .getArray()[0]; + const layer = $el[0].map.getLayers().getArray()[0]; // wait for features to load layer.getSource().on("featuresloadend", () => { const feature = layer.getSource().getFeatures()[0]; @@ -114,7 +110,7 @@ describe("layers", () => { req.reply(ecoRegionsFixture); } ); - (vectorLayerStyleJson[0]).style = { + vectorLayerStyleJson[0].style = { "fill-color": ["string", ["get", "COLOR"], "#eee"], "stroke-color": "black", "stroke-width": 2, @@ -126,9 +122,7 @@ describe("layers", () => { return new Cypress.Promise((resolve) => { // wait for features to load const eoxMap = $el[0]; - const layer = eoxMap.getLayerById( - "regions" - ); + const layer = eoxMap.getLayerById("regions"); const source = layer.getSource(); source.on("featuresloadend", () => { const feature = source.getFeatures()[0]; diff --git a/elements/map/test/vectorTilesLayer.cy.js b/elements/map/test/vectorTilesLayer.cy.js index f9912afd5..4d9aacc68 100644 --- a/elements/map/test/vectorTilesLayer.cy.js +++ b/elements/map/test/vectorTilesLayer.cy.js @@ -10,7 +10,7 @@ describe("VectorTile Layer", () => { encoding: "binary", }); - (vectorTileLayerStyleJson[0]).style = { + vectorTileLayerStyleJson[0].style = { "fill-color": "yellow", "stroke-color": "black", "stroke-width": 4, diff --git a/elements/map/test/wmts.cy.js b/elements/map/test/wmts.cy.js index b8174c3f3..88b2ed007 100644 --- a/elements/map/test/wmts.cy.js +++ b/elements/map/test/wmts.cy.js @@ -26,9 +26,7 @@ describe("layers", () => { cy.mount(html``).as("eox-map"); cy.get("eox-map").and(($el) => { - const layer = $el[0].map - .getLayers() - .getArray()[0]; + const layer = $el[0].map.getLayers().getArray()[0]; expect(layer).to.exist; expect(layer.get("id")).to.be.equal("customId"); diff --git a/elements/map/typings.d.ts b/elements/map/typings.d.ts index 07c8b979b..1a327d54c 100644 --- a/elements/map/typings.d.ts +++ b/elements/map/typings.d.ts @@ -1,4 +1,4 @@ -import { EOxMap as eoxMap } from "./main"; +import { EOxMap as eoxMap } from "./src-2/main"; declare global { export type EOxMap = eoxMap; } diff --git a/elements/map/vite.config.ts b/elements/map/vite.config.ts index ba27f3a95..dbd2e1376 100644 --- a/elements/map/vite.config.ts +++ b/elements/map/vite.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ build: { emptyOutDir: false, lib: { - entry: "./main.ts", + entry: "./src-2/main", name: "eox-map", // the proper extensions will be added fileName: "eox-map", diff --git a/elements/storytelling/test/cases/load-map-section.js b/elements/storytelling/test/cases/load-map-section.js index 233623868..ed9f3d026 100644 --- a/elements/storytelling/test/cases/load-map-section.js +++ b/elements/storytelling/test/cases/load-map-section.js @@ -1,5 +1,5 @@ import { TEST_SELECTORS } from "../../src/enums"; -import "../../../map/main"; +import "../../../map/src-2/main"; // Destructure TEST_SELECTORS object const { storyTelling } = TEST_SELECTORS; diff --git a/elements/storytelling/test/cases/load-map-tour.js b/elements/storytelling/test/cases/load-map-tour.js index 9356789c2..893c4446c 100644 --- a/elements/storytelling/test/cases/load-map-tour.js +++ b/elements/storytelling/test/cases/load-map-tour.js @@ -1,5 +1,5 @@ import { TEST_SELECTORS } from "../../src/enums"; -import "../../../map/main"; +import "../../../map/src-2/main"; // Destructure TEST_SELECTORS object const { storyTelling } = TEST_SELECTORS; From c9a32e5bfc8621b99e6e4052409b66d37359b439 Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Mon, 16 Sep 2024 17:59:30 +0530 Subject: [PATCH 16/24] refactor: renamed main refactoring folder to --- .storybook/preview.js | 4 ++-- elements/map/map.stories.js | 2 +- elements/map/{src-2 => src}/components/compare.js | 0 elements/map/{src-2 => src}/components/tooltip.js | 0 elements/map/{src-2 => src}/controls/controls.css | 0 elements/map/{src-2 => src}/controls/controls.js | 0 elements/map/{src-2 => src}/controls/geo-location.js | 0 elements/map/{src-2 => src}/controls/loading-indicator.js | 0 .../map/{src-2 => src}/custom/sources/WMTSCapabilities.js | 0 elements/map/{src-2 => src}/enums/index.js | 0 elements/map/{src-2 => src}/enums/map.js | 0 elements/map/{src-2 => src}/helpers/add-new-feature.js | 0 elements/map/{src-2 => src}/helpers/cancel-animation.js | 0 elements/map/{src-2 => src}/helpers/center.js | 0 .../map/{src-2 => src}/helpers/coordinates-roughly-equals.js | 0 elements/map/{src-2 => src}/helpers/draw.js | 0 elements/map/{src-2 => src}/helpers/generate.js | 0 elements/map/{src-2 => src}/helpers/index.js | 0 elements/map/{src-2 => src}/helpers/interactions.js | 0 elements/map/{src-2 => src}/helpers/layer.js | 0 elements/map/{src-2 => src}/helpers/parse-feature.js | 0 elements/map/{src-2 => src}/helpers/parse-text.js | 0 .../{src-2 => src}/helpers/register-projection-from-code.js | 0 elements/map/{src-2 => src}/helpers/register-projection.js | 0 elements/map/{src-2 => src}/helpers/select.js | 0 elements/map/{src-2 => src}/helpers/tile-grid.js | 0 elements/map/{src-2 => src}/helpers/transform.js | 0 elements/map/{src-2 => src}/main.js | 0 elements/map/{src-2 => src}/methods/map/add-update-layer.js | 0 elements/map/{src-2 => src}/methods/map/animate-to-state.js | 0 elements/map/{src-2 => src}/methods/map/first-updated.js | 0 elements/map/{src-2 => src}/methods/map/getters.js | 0 elements/map/{src-2 => src}/methods/map/index.js | 0 elements/map/{src-2 => src}/methods/map/remove.js | 0 elements/map/{src-2 => src}/methods/map/setters.js | 0 .../plugins/advancedLayersAndSources/formats.js | 0 .../{src-2 => src}/plugins/advancedLayersAndSources/index.js | 0 .../{src-2 => src}/plugins/advancedLayersAndSources/layers.js | 0 .../plugins/advancedLayersAndSources/sources.js | 0 elements/map/test/addOrUpdateLayer.cy.js | 2 +- elements/map/test/compare.cy.js | 2 +- elements/map/test/configObject.cy.js | 2 +- elements/map/test/controls.cy.js | 2 +- elements/map/test/drawInteraction.cy.js | 2 +- elements/map/test/general.cy.js | 2 +- elements/map/test/groupLayer.cy.js | 2 +- elements/map/test/hoverInteraction.cy.js | 2 +- elements/map/test/imageWmsLayer.cy.js | 2 +- elements/map/test/layerReactivity.cy.js | 2 +- elements/map/test/selectInteraction.cy.js | 2 +- elements/map/test/stacLayer.cy.js | 4 ++-- elements/map/test/sync.cy.js | 2 +- elements/map/test/syncProperties.cy.js | 2 +- elements/map/test/tileWmsLayer.cy.js | 2 +- elements/map/test/tooltip.cy.js | 2 +- elements/map/test/updateStyle.cy.js | 2 +- elements/map/test/vectorLayer.cy.js | 2 +- elements/map/test/vectorTilesLayer.cy.js | 2 +- elements/map/test/viewProjection.cy.js | 2 +- elements/map/test/wmts.cy.js | 2 +- elements/map/test/wmtsCapabilities.cy.js | 4 ++-- elements/map/typings.d.ts | 2 +- elements/map/vite.config.advancedLayersAndSources.ts | 2 +- elements/map/vite.config.ts | 2 +- elements/storytelling/test/cases/load-map-section.js | 2 +- elements/storytelling/test/cases/load-map-tour.js | 2 +- 66 files changed, 32 insertions(+), 32 deletions(-) rename elements/map/{src-2 => src}/components/compare.js (100%) rename elements/map/{src-2 => src}/components/tooltip.js (100%) rename elements/map/{src-2 => src}/controls/controls.css (100%) rename elements/map/{src-2 => src}/controls/controls.js (100%) rename elements/map/{src-2 => src}/controls/geo-location.js (100%) rename elements/map/{src-2 => src}/controls/loading-indicator.js (100%) rename elements/map/{src-2 => src}/custom/sources/WMTSCapabilities.js (100%) rename elements/map/{src-2 => src}/enums/index.js (100%) rename elements/map/{src-2 => src}/enums/map.js (100%) rename elements/map/{src-2 => src}/helpers/add-new-feature.js (100%) rename elements/map/{src-2 => src}/helpers/cancel-animation.js (100%) rename elements/map/{src-2 => src}/helpers/center.js (100%) rename elements/map/{src-2 => src}/helpers/coordinates-roughly-equals.js (100%) rename elements/map/{src-2 => src}/helpers/draw.js (100%) rename elements/map/{src-2 => src}/helpers/generate.js (100%) rename elements/map/{src-2 => src}/helpers/index.js (100%) rename elements/map/{src-2 => src}/helpers/interactions.js (100%) rename elements/map/{src-2 => src}/helpers/layer.js (100%) rename elements/map/{src-2 => src}/helpers/parse-feature.js (100%) rename elements/map/{src-2 => src}/helpers/parse-text.js (100%) rename elements/map/{src-2 => src}/helpers/register-projection-from-code.js (100%) rename elements/map/{src-2 => src}/helpers/register-projection.js (100%) rename elements/map/{src-2 => src}/helpers/select.js (100%) rename elements/map/{src-2 => src}/helpers/tile-grid.js (100%) rename elements/map/{src-2 => src}/helpers/transform.js (100%) rename elements/map/{src-2 => src}/main.js (100%) rename elements/map/{src-2 => src}/methods/map/add-update-layer.js (100%) rename elements/map/{src-2 => src}/methods/map/animate-to-state.js (100%) rename elements/map/{src-2 => src}/methods/map/first-updated.js (100%) rename elements/map/{src-2 => src}/methods/map/getters.js (100%) rename elements/map/{src-2 => src}/methods/map/index.js (100%) rename elements/map/{src-2 => src}/methods/map/remove.js (100%) rename elements/map/{src-2 => src}/methods/map/setters.js (100%) rename elements/map/{src-2 => src}/plugins/advancedLayersAndSources/formats.js (100%) rename elements/map/{src-2 => src}/plugins/advancedLayersAndSources/index.js (100%) rename elements/map/{src-2 => src}/plugins/advancedLayersAndSources/layers.js (100%) rename elements/map/{src-2 => src}/plugins/advancedLayersAndSources/sources.js (100%) diff --git a/.storybook/preview.js b/.storybook/preview.js index 0c8d2a2eb..df91433dd 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -9,8 +9,8 @@ import "../elements/itemfilter/src/main.js"; import "../elements/jsonform/src/main.js"; import "../elements/layercontrol/src/main.js"; import "../elements/layout/src/main.js"; -import "../elements/map/src-2/main.js"; -import "../elements/map/src-2/plugins/advancedLayersAndSources/index"; +import "../elements/map/src/main.js"; +import "../elements/map/src/plugins/advancedLayersAndSources/index"; import "../elements/stacinfo/src/main.js"; import "../elements/storytelling/src/main.js"; import "../elements/timecontrol/src/main.js"; diff --git a/elements/map/map.stories.js b/elements/map/map.stories.js index 4349e8875..5b95ccd7e 100644 --- a/elements/map/map.stories.js +++ b/elements/map/map.stories.js @@ -1,6 +1,6 @@ // Global import of eox-elements in .storybook/preview.js! import { html } from "lit"; -import "./src-2/main.js"; +import "./src/main.js"; export default { title: "Elements/eox-map", diff --git a/elements/map/src-2/components/compare.js b/elements/map/src/components/compare.js similarity index 100% rename from elements/map/src-2/components/compare.js rename to elements/map/src/components/compare.js diff --git a/elements/map/src-2/components/tooltip.js b/elements/map/src/components/tooltip.js similarity index 100% rename from elements/map/src-2/components/tooltip.js rename to elements/map/src/components/tooltip.js diff --git a/elements/map/src-2/controls/controls.css b/elements/map/src/controls/controls.css similarity index 100% rename from elements/map/src-2/controls/controls.css rename to elements/map/src/controls/controls.css diff --git a/elements/map/src-2/controls/controls.js b/elements/map/src/controls/controls.js similarity index 100% rename from elements/map/src-2/controls/controls.js rename to elements/map/src/controls/controls.js diff --git a/elements/map/src-2/controls/geo-location.js b/elements/map/src/controls/geo-location.js similarity index 100% rename from elements/map/src-2/controls/geo-location.js rename to elements/map/src/controls/geo-location.js diff --git a/elements/map/src-2/controls/loading-indicator.js b/elements/map/src/controls/loading-indicator.js similarity index 100% rename from elements/map/src-2/controls/loading-indicator.js rename to elements/map/src/controls/loading-indicator.js diff --git a/elements/map/src-2/custom/sources/WMTSCapabilities.js b/elements/map/src/custom/sources/WMTSCapabilities.js similarity index 100% rename from elements/map/src-2/custom/sources/WMTSCapabilities.js rename to elements/map/src/custom/sources/WMTSCapabilities.js diff --git a/elements/map/src-2/enums/index.js b/elements/map/src/enums/index.js similarity index 100% rename from elements/map/src-2/enums/index.js rename to elements/map/src/enums/index.js diff --git a/elements/map/src-2/enums/map.js b/elements/map/src/enums/map.js similarity index 100% rename from elements/map/src-2/enums/map.js rename to elements/map/src/enums/map.js diff --git a/elements/map/src-2/helpers/add-new-feature.js b/elements/map/src/helpers/add-new-feature.js similarity index 100% rename from elements/map/src-2/helpers/add-new-feature.js rename to elements/map/src/helpers/add-new-feature.js diff --git a/elements/map/src-2/helpers/cancel-animation.js b/elements/map/src/helpers/cancel-animation.js similarity index 100% rename from elements/map/src-2/helpers/cancel-animation.js rename to elements/map/src/helpers/cancel-animation.js diff --git a/elements/map/src-2/helpers/center.js b/elements/map/src/helpers/center.js similarity index 100% rename from elements/map/src-2/helpers/center.js rename to elements/map/src/helpers/center.js diff --git a/elements/map/src-2/helpers/coordinates-roughly-equals.js b/elements/map/src/helpers/coordinates-roughly-equals.js similarity index 100% rename from elements/map/src-2/helpers/coordinates-roughly-equals.js rename to elements/map/src/helpers/coordinates-roughly-equals.js diff --git a/elements/map/src-2/helpers/draw.js b/elements/map/src/helpers/draw.js similarity index 100% rename from elements/map/src-2/helpers/draw.js rename to elements/map/src/helpers/draw.js diff --git a/elements/map/src-2/helpers/generate.js b/elements/map/src/helpers/generate.js similarity index 100% rename from elements/map/src-2/helpers/generate.js rename to elements/map/src/helpers/generate.js diff --git a/elements/map/src-2/helpers/index.js b/elements/map/src/helpers/index.js similarity index 100% rename from elements/map/src-2/helpers/index.js rename to elements/map/src/helpers/index.js diff --git a/elements/map/src-2/helpers/interactions.js b/elements/map/src/helpers/interactions.js similarity index 100% rename from elements/map/src-2/helpers/interactions.js rename to elements/map/src/helpers/interactions.js diff --git a/elements/map/src-2/helpers/layer.js b/elements/map/src/helpers/layer.js similarity index 100% rename from elements/map/src-2/helpers/layer.js rename to elements/map/src/helpers/layer.js diff --git a/elements/map/src-2/helpers/parse-feature.js b/elements/map/src/helpers/parse-feature.js similarity index 100% rename from elements/map/src-2/helpers/parse-feature.js rename to elements/map/src/helpers/parse-feature.js diff --git a/elements/map/src-2/helpers/parse-text.js b/elements/map/src/helpers/parse-text.js similarity index 100% rename from elements/map/src-2/helpers/parse-text.js rename to elements/map/src/helpers/parse-text.js diff --git a/elements/map/src-2/helpers/register-projection-from-code.js b/elements/map/src/helpers/register-projection-from-code.js similarity index 100% rename from elements/map/src-2/helpers/register-projection-from-code.js rename to elements/map/src/helpers/register-projection-from-code.js diff --git a/elements/map/src-2/helpers/register-projection.js b/elements/map/src/helpers/register-projection.js similarity index 100% rename from elements/map/src-2/helpers/register-projection.js rename to elements/map/src/helpers/register-projection.js diff --git a/elements/map/src-2/helpers/select.js b/elements/map/src/helpers/select.js similarity index 100% rename from elements/map/src-2/helpers/select.js rename to elements/map/src/helpers/select.js diff --git a/elements/map/src-2/helpers/tile-grid.js b/elements/map/src/helpers/tile-grid.js similarity index 100% rename from elements/map/src-2/helpers/tile-grid.js rename to elements/map/src/helpers/tile-grid.js diff --git a/elements/map/src-2/helpers/transform.js b/elements/map/src/helpers/transform.js similarity index 100% rename from elements/map/src-2/helpers/transform.js rename to elements/map/src/helpers/transform.js diff --git a/elements/map/src-2/main.js b/elements/map/src/main.js similarity index 100% rename from elements/map/src-2/main.js rename to elements/map/src/main.js diff --git a/elements/map/src-2/methods/map/add-update-layer.js b/elements/map/src/methods/map/add-update-layer.js similarity index 100% rename from elements/map/src-2/methods/map/add-update-layer.js rename to elements/map/src/methods/map/add-update-layer.js diff --git a/elements/map/src-2/methods/map/animate-to-state.js b/elements/map/src/methods/map/animate-to-state.js similarity index 100% rename from elements/map/src-2/methods/map/animate-to-state.js rename to elements/map/src/methods/map/animate-to-state.js diff --git a/elements/map/src-2/methods/map/first-updated.js b/elements/map/src/methods/map/first-updated.js similarity index 100% rename from elements/map/src-2/methods/map/first-updated.js rename to elements/map/src/methods/map/first-updated.js diff --git a/elements/map/src-2/methods/map/getters.js b/elements/map/src/methods/map/getters.js similarity index 100% rename from elements/map/src-2/methods/map/getters.js rename to elements/map/src/methods/map/getters.js diff --git a/elements/map/src-2/methods/map/index.js b/elements/map/src/methods/map/index.js similarity index 100% rename from elements/map/src-2/methods/map/index.js rename to elements/map/src/methods/map/index.js diff --git a/elements/map/src-2/methods/map/remove.js b/elements/map/src/methods/map/remove.js similarity index 100% rename from elements/map/src-2/methods/map/remove.js rename to elements/map/src/methods/map/remove.js diff --git a/elements/map/src-2/methods/map/setters.js b/elements/map/src/methods/map/setters.js similarity index 100% rename from elements/map/src-2/methods/map/setters.js rename to elements/map/src/methods/map/setters.js diff --git a/elements/map/src-2/plugins/advancedLayersAndSources/formats.js b/elements/map/src/plugins/advancedLayersAndSources/formats.js similarity index 100% rename from elements/map/src-2/plugins/advancedLayersAndSources/formats.js rename to elements/map/src/plugins/advancedLayersAndSources/formats.js diff --git a/elements/map/src-2/plugins/advancedLayersAndSources/index.js b/elements/map/src/plugins/advancedLayersAndSources/index.js similarity index 100% rename from elements/map/src-2/plugins/advancedLayersAndSources/index.js rename to elements/map/src/plugins/advancedLayersAndSources/index.js diff --git a/elements/map/src-2/plugins/advancedLayersAndSources/layers.js b/elements/map/src/plugins/advancedLayersAndSources/layers.js similarity index 100% rename from elements/map/src-2/plugins/advancedLayersAndSources/layers.js rename to elements/map/src/plugins/advancedLayersAndSources/layers.js diff --git a/elements/map/src-2/plugins/advancedLayersAndSources/sources.js b/elements/map/src/plugins/advancedLayersAndSources/sources.js similarity index 100% rename from elements/map/src-2/plugins/advancedLayersAndSources/sources.js rename to elements/map/src/plugins/advancedLayersAndSources/sources.js diff --git a/elements/map/test/addOrUpdateLayer.cy.js b/elements/map/test/addOrUpdateLayer.cy.js index 94eb7ae73..0d23d9abf 100644 --- a/elements/map/test/addOrUpdateLayer.cy.js +++ b/elements/map/test/addOrUpdateLayer.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; describe("Map", () => { diff --git a/elements/map/test/compare.cy.js b/elements/map/test/compare.cy.js index 011d71f9b..6ce7a346e 100644 --- a/elements/map/test/compare.cy.js +++ b/elements/map/test/compare.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import imageWmsLayerStyleJson from "./imageWmsLayer.json"; describe("comparison", () => { diff --git a/elements/map/test/configObject.cy.js b/elements/map/test/configObject.cy.js index d3af6bb85..d75085b55 100644 --- a/elements/map/test/configObject.cy.js +++ b/elements/map/test/configObject.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; describe("config property", () => { it("sets controls, layers and view using the config object", () => { diff --git a/elements/map/test/controls.cy.js b/elements/map/test/controls.cy.js index ed904c026..c4c0d9b80 100644 --- a/elements/map/test/controls.cy.js +++ b/elements/map/test/controls.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import tileWmsLayerStyleJson from "./tileWmsLayer.json"; describe("webcomponent property parsing", () => { diff --git a/elements/map/test/drawInteraction.cy.js b/elements/map/test/drawInteraction.cy.js index 25ad3b22d..d389f9c50 100644 --- a/elements/map/test/drawInteraction.cy.js +++ b/elements/map/test/drawInteraction.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import drawInteractionLayerJson from "./drawInteraction.json"; import vectorLayerJson from "./vectorLayer.json"; import { simulateEvent } from "./utils/events"; diff --git a/elements/map/test/general.cy.js b/elements/map/test/general.cy.js index f53ec436e..bb2d94b22 100644 --- a/elements/map/test/general.cy.js +++ b/elements/map/test/general.cy.js @@ -1,6 +1,6 @@ import { html } from "lit"; import { equals } from "ol/coordinate"; -import "../src-2/main"; +import "../src/main"; describe("Map", () => { it("map should exist", () => { diff --git a/elements/map/test/groupLayer.cy.js b/elements/map/test/groupLayer.cy.js index f293fe114..b73afb63e 100644 --- a/elements/map/test/groupLayer.cy.js +++ b/elements/map/test/groupLayer.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; const osmJson = { diff --git a/elements/map/test/hoverInteraction.cy.js b/elements/map/test/hoverInteraction.cy.js index b3b9156c6..94789434a 100644 --- a/elements/map/test/hoverInteraction.cy.js +++ b/elements/map/test/hoverInteraction.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import vectorLayerStyleJson from "./hoverInteraction.json"; import { simulateEvent } from "./utils/events"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; diff --git a/elements/map/test/imageWmsLayer.cy.js b/elements/map/test/imageWmsLayer.cy.js index 39a8f9c98..0c61407a6 100644 --- a/elements/map/test/imageWmsLayer.cy.js +++ b/elements/map/test/imageWmsLayer.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import imageWmsLayerStyleJson from "./imageWmsLayer.json"; // fixme: imageWMS is identical to tileWMS diff --git a/elements/map/test/layerReactivity.cy.js b/elements/map/test/layerReactivity.cy.js index bf82020af..eac34e97a 100644 --- a/elements/map/test/layerReactivity.cy.js +++ b/elements/map/test/layerReactivity.cy.js @@ -1,6 +1,6 @@ import { html } from "lit"; import drawInteractionLayerJson from "./drawInteraction.json"; -import "../src-2/main"; +import "../src/main"; const OsmJson = { type: "Tile", diff --git a/elements/map/test/selectInteraction.cy.js b/elements/map/test/selectInteraction.cy.js index fd6f6b3cc..dd79df6e4 100644 --- a/elements/map/test/selectInteraction.cy.js +++ b/elements/map/test/selectInteraction.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import vectorTileLayerJson from "./vectorTilesLayer.json"; import vectorLayerJson from "./vectorLayer.json"; import { simulateEvent } from "./utils/events"; diff --git a/elements/map/test/stacLayer.cy.js b/elements/map/test/stacLayer.cy.js index 86a6ac5ea..7b008ada0 100644 --- a/elements/map/test/stacLayer.cy.js +++ b/elements/map/test/stacLayer.cy.js @@ -1,6 +1,6 @@ import { html } from "lit"; -import "../src-2/plugins/advancedLayersAndSources/index"; -import "../src-2/main"; +import "../src/plugins/advancedLayersAndSources/index"; +import "../src/main"; import stacLayerJson from "./stacLayer.json"; import stacFixture from "./fixtures/stac.json"; diff --git a/elements/map/test/sync.cy.js b/elements/map/test/sync.cy.js index 4b6871062..a534bbc98 100644 --- a/elements/map/test/sync.cy.js +++ b/elements/map/test/sync.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; describe("map syncing", () => { it("syncs one map to another", () => { diff --git a/elements/map/test/syncProperties.cy.js b/elements/map/test/syncProperties.cy.js index f36e7f5f9..f417d6b74 100644 --- a/elements/map/test/syncProperties.cy.js +++ b/elements/map/test/syncProperties.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import vectorLayerStyleJson from "./vectorLayer.json"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; diff --git a/elements/map/test/tileWmsLayer.cy.js b/elements/map/test/tileWmsLayer.cy.js index bfb6a88c5..3bc73be00 100644 --- a/elements/map/test/tileWmsLayer.cy.js +++ b/elements/map/test/tileWmsLayer.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import tileWmsLayerStyleJson from "./tileWmsLayer.json"; describe("layers", () => { diff --git a/elements/map/test/tooltip.cy.js b/elements/map/test/tooltip.cy.js index d21bd7271..d9ac61b54 100644 --- a/elements/map/test/tooltip.cy.js +++ b/elements/map/test/tooltip.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import vectorLayerStyleJson from "./hoverInteraction.json"; import { simulateEvent } from "./utils/events"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; diff --git a/elements/map/test/updateStyle.cy.js b/elements/map/test/updateStyle.cy.js index 65c33a88b..880eac992 100644 --- a/elements/map/test/updateStyle.cy.js +++ b/elements/map/test/updateStyle.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import vectorLayerStyleJson from "./vectorLayer.json"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; diff --git a/elements/map/test/vectorLayer.cy.js b/elements/map/test/vectorLayer.cy.js index 4de29115e..a7efcb0d1 100644 --- a/elements/map/test/vectorLayer.cy.js +++ b/elements/map/test/vectorLayer.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import vectorLayerStyleJson from "./vectorLayer.json"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; import tempFixture from "./fixtures/budapest3035.json"; diff --git a/elements/map/test/vectorTilesLayer.cy.js b/elements/map/test/vectorTilesLayer.cy.js index 4d9aacc68..162562b50 100644 --- a/elements/map/test/vectorTilesLayer.cy.js +++ b/elements/map/test/vectorTilesLayer.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import vectorTileLayerStyleJson from "./vectorTilesLayer.json"; describe("VectorTile Layer", () => { diff --git a/elements/map/test/viewProjection.cy.js b/elements/map/test/viewProjection.cy.js index 3321470f4..fcbb0522c 100644 --- a/elements/map/test/viewProjection.cy.js +++ b/elements/map/test/viewProjection.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; import vectorLayerStyleJson from "./vectorLayer.json"; import ecoRegionsFixture from "./fixtures/ecoregions.json"; diff --git a/elements/map/test/wmts.cy.js b/elements/map/test/wmts.cy.js index 88b2ed007..3783684d6 100644 --- a/elements/map/test/wmts.cy.js +++ b/elements/map/test/wmts.cy.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import "../src-2/main"; +import "../src/main"; describe("layers", () => { it("create wmts with tile grid", () => { diff --git a/elements/map/test/wmtsCapabilities.cy.js b/elements/map/test/wmtsCapabilities.cy.js index 6e7601863..230ec8182 100644 --- a/elements/map/test/wmtsCapabilities.cy.js +++ b/elements/map/test/wmtsCapabilities.cy.js @@ -1,6 +1,6 @@ import { html } from "lit"; -import "../src-2/main"; -import "../src-2/plugins/advancedLayersAndSources/index"; +import "../src/main"; +import "../src/plugins/advancedLayersAndSources/index"; describe("WMTS Capabilities Source", () => { it("loads a layer from WMTS capabilities", () => { diff --git a/elements/map/typings.d.ts b/elements/map/typings.d.ts index 1a327d54c..d9000ec8a 100644 --- a/elements/map/typings.d.ts +++ b/elements/map/typings.d.ts @@ -1,4 +1,4 @@ -import { EOxMap as eoxMap } from "./src-2/main"; +import { EOxMap as eoxMap } from "./src/main"; declare global { export type EOxMap = eoxMap; } diff --git a/elements/map/vite.config.advancedLayersAndSources.ts b/elements/map/vite.config.advancedLayersAndSources.ts index db0dd5d9e..228ab06bd 100644 --- a/elements/map/vite.config.advancedLayersAndSources.ts +++ b/elements/map/vite.config.advancedLayersAndSources.ts @@ -4,7 +4,7 @@ import { defineConfig } from "vite"; export default defineConfig({ build: { lib: { - entry: "./src-2/plugins/advancedLayersAndSources/index", + entry: "./src/plugins/advancedLayersAndSources/index", name: "eox-map-advanced-layers-and-sources", // the proper extensions will be added fileName: "eox-map-advanced-layers-and-sources", diff --git a/elements/map/vite.config.ts b/elements/map/vite.config.ts index dbd2e1376..24f2604ba 100644 --- a/elements/map/vite.config.ts +++ b/elements/map/vite.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ build: { emptyOutDir: false, lib: { - entry: "./src-2/main", + entry: "./src/main", name: "eox-map", // the proper extensions will be added fileName: "eox-map", diff --git a/elements/storytelling/test/cases/load-map-section.js b/elements/storytelling/test/cases/load-map-section.js index ed9f3d026..945da6055 100644 --- a/elements/storytelling/test/cases/load-map-section.js +++ b/elements/storytelling/test/cases/load-map-section.js @@ -1,5 +1,5 @@ import { TEST_SELECTORS } from "../../src/enums"; -import "../../../map/src-2/main"; +import "../../../map/src/main"; // Destructure TEST_SELECTORS object const { storyTelling } = TEST_SELECTORS; diff --git a/elements/storytelling/test/cases/load-map-tour.js b/elements/storytelling/test/cases/load-map-tour.js index 893c4446c..cd5db9083 100644 --- a/elements/storytelling/test/cases/load-map-tour.js +++ b/elements/storytelling/test/cases/load-map-tour.js @@ -1,5 +1,5 @@ import { TEST_SELECTORS } from "../../src/enums"; -import "../../../map/src-2/main"; +import "../../../map/src/main"; // Destructure TEST_SELECTORS object const { storyTelling } = TEST_SELECTORS; From b20058c5c331febed4b08dca333811488cf5d638 Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Wed, 18 Sep 2024 22:10:31 +0530 Subject: [PATCH 17/24] refactor: Added comments to helpers, plugin, methods, custom, controls etc --- elements/map/src/controls/controls.js | 15 +++ elements/map/src/controls/geo-location.js | 32 ++++- .../map/src/controls/loading-indicator.js | 24 +++- .../src/custom/sources/WMTSCapabilities.js | 28 +++- elements/map/src/helpers/add-new-feature.js | 3 + elements/map/src/helpers/cancel-animation.js | 4 +- elements/map/src/helpers/center.js | 12 +- .../src/helpers/coordinates-roughly-equals.js | 28 ++-- elements/map/src/helpers/draw.js | 27 ++-- elements/map/src/helpers/generate.js | 52 +++++--- elements/map/src/helpers/interactions.js | 13 +- elements/map/src/helpers/layer.js | 39 ++++-- elements/map/src/helpers/parse-feature.js | 9 ++ elements/map/src/helpers/parse-text.js | 9 ++ .../helpers/register-projection-from-code.js | 9 +- .../map/src/helpers/register-projection.js | 19 ++- elements/map/src/helpers/select.js | 55 +++++++- elements/map/src/helpers/tile-grid.js | 30 ++++- .../map/src/methods/map/add-update-layer.js | 20 ++- .../map/src/methods/map/animate-to-state.js | 14 ++ elements/map/src/methods/map/first-updated.js | 14 ++ elements/map/src/methods/map/getters.js | 24 +++- elements/map/src/methods/map/remove.js | 29 +++++ elements/map/src/methods/map/setters.js | 120 +++++++++++++++++- .../advancedLayersAndSources/formats.js | 2 + .../advancedLayersAndSources/layers.js | 7 +- .../advancedLayersAndSources/sources.js | 3 + 27 files changed, 538 insertions(+), 103 deletions(-) diff --git a/elements/map/src/controls/controls.js b/elements/map/src/controls/controls.js index 901667f7b..58226a48b 100644 --- a/elements/map/src/controls/controls.js +++ b/elements/map/src/controls/controls.js @@ -25,13 +25,17 @@ const availableControls = { */ export function addOrUpdateControl(EOxMap, existingControls, type, options) { if (existingControls && existingControls[type]) { + // Check if the current control options differ from the new ones const controlHasChanged = JSON.stringify(existingControls[type]) !== JSON.stringify(options); + if (controlHasChanged) { + // Remove the old control and add the updated one EOxMap.removeControl(type); addControl(EOxMap, type, options); } } else { + // Add the control if it doesn't already exist addControl(EOxMap, type, options); } } @@ -44,13 +48,18 @@ export function addOrUpdateControl(EOxMap, existingControls, type, options) { * @param {object} options - Options for the control. */ export function addControl(EOxMap, type, options) { + // Create a shallow copy of the control options to avoid modifying the original object const controlOptions = Object.assign({}, options); + // If the control has layers (e.g., for OverviewMap), generate them if (options && options.layers) { controlOptions.layers = generateLayers(EOxMap, options.layers); // Parse layers (e.g., for OverviewMap) } + // Create a new control instance using the type and options const control = new availableControls[type](controlOptions); + + // Add the control to the map and store it in the map's controls dictionary EOxMap.map.addControl(control); EOxMap.mapControls[type] = control; } @@ -67,21 +76,27 @@ export function addInitialControls(EOxMap) { if (controls) { if (Array.isArray(controls)) { + // If controls are provided as an array of control names controls.forEach((controlName) => { + // Create a new control without options const control = new availableControls[controlName]({}); + EOxMap.map.addControl(control); EOxMap.mapControls[controlName] = control; }); } else { + // If controls are provided as a dictionary with options const keys = Object.keys(controls); for (let i = 0; i < keys.length; i++) { const controlName = /** @type {ControlType} */ (keys[i]); const controlOptions = controls[controlName]; + // If the control has layers (e.g., for OverviewMap), generate them if (controlOptions && controlOptions.layers) { controlOptions.layers = generateLayers(EOxMap, controlOptions.layers); // Parse layers (e.g., for OverviewMap) } + // Create a new control instance using the control name and options const control = new availableControls[controlName](controlOptions); EOxMap.map.addControl(control); EOxMap.mapControls[controlName] = control; diff --git a/elements/map/src/controls/geo-location.js b/elements/map/src/controls/geo-location.js index bd18f296e..1fd679235 100644 --- a/elements/map/src/controls/geo-location.js +++ b/elements/map/src/controls/geo-location.js @@ -17,16 +17,18 @@ export default class GeolocationControl extends Control { constructor(opt_options) { const options = opt_options || {}; + // Create the main control element and the button for geolocation const element = document.createElement("div"); element.className = "geolocation ol-unselectable ol-control"; const button = document.createElement("button"); button.title = "Show your location"; const image = document.createElement("img"); + // Set the button icon, using a fallback icon if none is provided if (options.buttonIcon) { image.src = options.buttonIcon; } else { - // Fallback icon + // Fallback icon: A data URL SVG icon const icon = `url( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-6 -6 36 36'%3E %3Cpath d='M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M3.05,13H1V11H3.05C3.5,6.83 6.83,3.5 11,3.05V1H13V3.05C17.17,3.5 20.5,6.83 20.95,11H23V13H20.95C20.5,17.17 17.17,20.5 13,20.95V23H11V20.95C6.83,20.5 3.5,17.17 3.05,13M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z' /%3E%3C/svg%3E" )`; @@ -35,6 +37,7 @@ export default class GeolocationControl extends Control { image.style.webkitMaskImage = icon; } + // Style the icon to fit inside the button image.style.height = "100%"; image.style.width = "100%"; image.style.position = "absolute"; @@ -42,22 +45,27 @@ export default class GeolocationControl extends Control { element.appendChild(image); element.appendChild(button); + // Call the superclass constructor to set up the control super({ element: element }); + // Store the configuration options this._centerWhenReady = options.centerWhenReady; this._highAccuracy = options.highAccuracy; this._trackAccuracy = options.trackAccuracy; this._trackHeading = options.trackHeading; + // Create a feature for the current position this._positionFeature = new Feature({ geometry: new Point([NaN, NaN]), heading: 0, }); + // Set up a vector source for the geolocation features this._source = new VectorSource({ features: [this._positionFeature], }); + // If tracking accuracy, create an accuracy feature and add it to the source if (this._trackAccuracy) { this._accuracyFeature = new Feature(); this._accuracyFeature.setStyle( @@ -69,24 +77,31 @@ export default class GeolocationControl extends Control { this._source.addFeature(this._accuracyFeature); } + // Create a vector layer for the geolocation features this._layer = new VectorLayer({ source: this._source }); + // Apply a custom style to the layer if provided if (options.style) { this._layer.setStyle(options.style); } + // Add an event listener for the button click to center on the geolocation button.addEventListener("click", this.centerOnPosition.bind(this), false); } /** * Remove the control from its current map and attach it to the new map. * Pass `null` to just remove the control from the current map. + * * @param {import("ol/Map").default|null} map - Map instance. * @api */ setMap(map) { + // Set the vector layer on the specified map this._layer.setMap(map); super.setMap(map); + + // If the control should center on geolocation when ready, initialize geolocation if (map && this._centerWhenReady) { this.initGeolocation(); } @@ -94,8 +109,9 @@ export default class GeolocationControl extends Control { /** * Initializes the geolocation control. - * Calling this will cause a user prompt about allowing geolocation in the browser. - * @returns {Promise} Returns a promise that resolves to coordinates on success, or an error event. + * This will prompt the user for geolocation permissions in the browser. + * + * @returns {Promise} - A promise that resolves to the coordinates on success, or an error event on failure. */ initGeolocation() { return new Promise((resolve, reject) => { @@ -108,14 +124,17 @@ export default class GeolocationControl extends Control { }); } + // Center the map on the user's location when the geolocation is first determined if (this._centerWhenReady) { this._geolocation.once("change:position", (e) => { map.getView().setCenter(e.target.getPosition()); }); } + // Handle geolocation errors this._geolocation.on("error", (evt) => reject(evt)); + // Update the accuracy feature when the accuracy geometry changes this._geolocation.on("change:accuracyGeometry", () => { if (this._trackAccuracy) { this._accuracyFeature.setGeometry( @@ -124,12 +143,14 @@ export default class GeolocationControl extends Control { } }); + // Track heading changes if the option is enabled this._geolocation.on("change:heading", (e) => { if (this._trackHeading && this._highAccuracy) { this._positionFeature.set("heading", e.target.getHeading()); } }); + // Update the position feature when the position changes this._geolocation.on("change:position", () => { const coordinates = this._geolocation.getPosition(); this._positionFeature.getGeometry().setCoordinates(coordinates || null); @@ -139,8 +160,9 @@ export default class GeolocationControl extends Control { } /** - * Returns the geolocation control button. - * @returns {HTMLElement} The control element. + * Returns the geolocation control element. + * + * @returns {HTMLElement} - The control element. */ getElement() { return this.element; diff --git a/elements/map/src/controls/loading-indicator.js b/elements/map/src/controls/loading-indicator.js index 9fac22bfb..c10f91e62 100644 --- a/elements/map/src/controls/loading-indicator.js +++ b/elements/map/src/controls/loading-indicator.js @@ -6,10 +6,14 @@ import { Control } from "ol/control.js"; export default class LoadingIndicatorControl extends Control { /** - * @param {LoadingIndicatorOptions} [opt_options] - Control options. + * Creates a loading indicator control to show when map loading events occur. + * + * @param {LoadingIndicatorOptions} [opt_options] - Options for configuring the loading indicator control. */ constructor(opt_options) { const options = opt_options || {}; + + // Set default options if not provided if (options.opacity === undefined) { options.opacity = 1; } @@ -17,43 +21,48 @@ export default class LoadingIndicatorControl extends Control { options.type = "small"; } + // Create the main control element const element = document.createElement("div"); element.className = "LoadingIndicator ol-unselectable ol-control"; element.classList.add("loading-indicator"); element.style.opacity = String(options.opacity); + // Set the spinner icon; use a default SVG icon if none is provided if (options.spinnerSvg) { element.innerHTML = options.spinnerSvg; } else { - // Fallback icon + // Fallback SVG spinner icon element.innerHTML = ` `; } + // Add the appropriate CSS class based on the type of loading indicator if (options.type === "fullscreen") { element.classList.add("fullscreen"); } else { element.classList.add("small"); } + // Call the superclass constructor to set up the control super({ element: element }); } /** - * Remove the control from its current map and attach it to the new map. - * Pass `null` to just remove the control from the current map. - * Subclasses may set up event handlers to get notified about changes to - * the map here. - * @param {import("ol/Map").default|null} map - Map instance. + * Attaches the control to a map and sets up event listeners to show/hide the loading indicator based on map load events. + * + * @param {import("ol/Map").default|null} map - The map instance to attach the control to, or `null` to remove it. * @api */ setMap(map) { super.setMap(map); if (map) { + // Show the loading indicator when map loading starts map.on("loadstart", () => { this.getElement().style.visibility = "visible"; }); + + // Hide the loading indicator when map loading ends map.on("loadend", () => { this.getElement().style.visibility = "hidden"; }); @@ -62,6 +71,7 @@ export default class LoadingIndicatorControl extends Control { /** * Returns the loading indicator element. + * * @returns {HTMLElement} - The control element. */ getElement() { diff --git a/elements/map/src/custom/sources/WMTSCapabilities.js b/elements/map/src/custom/sources/WMTSCapabilities.js index a0870cfa1..0fdafa345 100644 --- a/elements/map/src/custom/sources/WMTSCapabilities.js +++ b/elements/map/src/custom/sources/WMTSCapabilities.js @@ -10,7 +10,9 @@ import { appendParams } from "ol/uri"; class WMTSCapabilities extends TileImage { /** - * @param {WMTSCapabilitiesOptions} options - Image tile options. + * Constructs a WMTS capabilities instance and fetches the WMTS capabilities XML from the provided URL. + * + * @param {WMTSCapabilitiesOptions} options - Image tile options including URL, layer, and other properties. */ constructor(options) { super({ @@ -26,11 +28,13 @@ class WMTSCapabilities extends TileImage { wrapX: options.wrapX, }); + // Store WMTS-specific properties this.version_ = options.version !== undefined ? options.version : "1.0.0"; this.dimensions_ = options.dimensions !== undefined ? options.dimensions : {}; this.layer_ = options.layer; + // Fetch WMTS capabilities XML from the provided URL fetch(options.url) .then((response) => response.text()) .then((xml) => @@ -42,14 +46,21 @@ class WMTSCapabilities extends TileImage { } /** + * Handles the WMTS capabilities XML response by parsing it and setting up the necessary properties. + * * @param {Document} xml - The XML document containing WMTS capabilities. * @param {WMTSCapabilitiesOptions} options - Options for WMTS capabilities. */ handleCapabilitiesResponse(xml, options) { const format = new WMTSCapabilitiesFormat(); + + // Parse the WMTS capabilities XML document const parsedXml = format.read(xml); + + // Generate WMTS options from the parsed capabilities and input options const capabilitiesOptions = optionsFromCapabilities(parsedXml, options); + // Set various properties from the parsed capabilities this.crossOrigin = capabilitiesOptions.crossOrigin; this.projection = /** @type {import("ol/proj").Projection} */ ( capabilitiesOptions.projection @@ -60,9 +71,12 @@ class WMTSCapabilities extends TileImage { this.style_ = capabilitiesOptions.style; this.format_ = capabilitiesOptions.format; this.dimensions_ = capabilitiesOptions.dimensions; + + // Set URLs for the tile source this.setUrls(capabilitiesOptions.urls); if (this.urls && this.urls.length > 0) { + // Create the tile URL function from the provided URLs and the WMTS template this.tileUrlFunction = createFromTileUrlFunctions( this.urls.map(this.createFromWMTSTemplate.bind(this)) ); @@ -71,12 +85,15 @@ class WMTSCapabilities extends TileImage { } /** + * Creates a URL function for WMTS tiles based on a template. + * * @param {string} template - The WMTS URL template. * @return {import("../Tile.js").UrlFunction} - The tile URL function. */ createFromWMTSTemplate(template) { const requestEncoding = this.requestEncoding_; + // Context for the URL, including layer, style, and tile matrix set const context = { layer: this.layer_, style: this.style_, @@ -92,6 +109,7 @@ class WMTSCapabilities extends TileImage { }); } + // Prepare the URL template based on the request encoding type (KVP or RESTful) template = requestEncoding === "KVP" ? appendParams(template, context) @@ -106,13 +124,17 @@ class WMTSCapabilities extends TileImage { return ( /** + * Returns the tile URL based on the tile coordinates. + * * @param {import("../tilecoord.js").TileCoord} tileCoord - The tile coordinate. - * @return {string|undefined} - The tile URL. + * @return {string|undefined} - The tile URL, or `undefined` if tileCoord is invalid. */ function (tileCoord) { if (!tileCoord) { return undefined; } + + // Create the local context for the tile URL, including matrix, row, and column const localContext = { TileMatrix: tileGrid.getMatrixId(tileCoord[0]), TileCol: tileCoord[1], @@ -121,6 +143,8 @@ class WMTSCapabilities extends TileImage { Object.assign(localContext, dimensions); let url = template; + + // Generate the URL based on the request encoding type (KVP or RESTful) if (requestEncoding === "KVP") { url = appendParams(url, localContext); } else { diff --git a/elements/map/src/helpers/add-new-feature.js b/elements/map/src/helpers/add-new-feature.js index 48f532c7d..123816f22 100644 --- a/elements/map/src/helpers/add-new-feature.js +++ b/elements/map/src/helpers/add-new-feature.js @@ -87,11 +87,14 @@ export default function addNewFeature( * @param {{ [p: string]: any }} geoJSON - An object representing GeoJSON data associated with the event. */ function dispatchEvt(EOxMap, type, e, geoJSON) { + // Create a new custom event with the specified type and detail, including the original event and GeoJSON data const evt = new CustomEvent(type, { detail: { originalEvent: e, geoJSON, }, }); + + // Dispatch the custom event on the EOxMap object EOxMap.dispatchEvent(evt); } diff --git a/elements/map/src/helpers/cancel-animation.js b/elements/map/src/helpers/cancel-animation.js index 135a3c4f8..3b0079ef6 100644 --- a/elements/map/src/helpers/cancel-animation.js +++ b/elements/map/src/helpers/cancel-animation.js @@ -1,8 +1,10 @@ /** * cancels any animation of the given view * - * @param {import("ol/View").default} view + * @param {import("ol/View").default} view - The OpenLayers view object whose animations need to be canceled. */ export default function cancelAnimation(view) { + // This effectively stops any ongoing animations by setting the rotation to its current value. + // doesn't change the view but forces the animation to halt. view.setRotation(view.getRotation()); } diff --git a/elements/map/src/helpers/center.js b/elements/map/src/helpers/center.js index 81a7c6f75..5e666465b 100644 --- a/elements/map/src/helpers/center.js +++ b/elements/map/src/helpers/center.js @@ -8,12 +8,15 @@ import { fromLonLat } from "ol/proj"; * system EPSG:4326 is assumed. * use `map.getView().setCenter()` to manually set the center of the view. * - * @param {Array} centerProperty - * @returns {import("ol/coordinate")} + * @param {Array} centerProperty - The input coordinate as [longitude, latitude]. + * @returns {import("ol/coordinate")} - The converted coordinate in EPSG:3857. */ export function getCenterFromProperty(centerProperty) { if (centerProperty) { const coordinate = centerProperty; + + // Check if the coordinate is not the default [0, 0] and falls within the longitude (-180 to 180) + // and latitude (-90 to 90) range. This check helps to identify if the input is in EPSG:4326. if ( !equals(coordinate, [0, 0]) && coordinate[0] >= -180 && @@ -21,9 +24,14 @@ export function getCenterFromProperty(centerProperty) { coordinate[1] >= -90 && coordinate[1] <= 90 ) { + // Convert the EPSG:4326 (longitude/latitude) coordinate to EPSG:3857 return fromLonLat(coordinate); } + + // Convert the EPSG:4326 (longitude/latitude) coordinate to EPSG:3857 return coordinate; } + + // Convert the EPSG:4326 (longitude/latitude) coordinate to EPSG:3857 return [0, 0]; } diff --git a/elements/map/src/helpers/coordinates-roughly-equals.js b/elements/map/src/helpers/coordinates-roughly-equals.js index 1210dd09f..5ca6328af 100644 --- a/elements/map/src/helpers/coordinates-roughly-equals.js +++ b/elements/map/src/helpers/coordinates-roughly-equals.js @@ -1,10 +1,12 @@ /** - * a helper function to determin approximate equality between 2 coordinates. - * based on ol "equals" - * @param {import("ol/coordinate")} coordinate1 First coordinate. - * @param {import("ol/coordinate")} coordinate2 Second coordinate. - * @param {number?} epsilon tolerance - * @return {boolean} The two coordinates are equal. + * A helper function to determine approximate equality between two coordinates. + * This function is based on OpenLayers' "equals" method but includes a tolerance (epsilon). + * + * @param {import("ol/coordinate")} coordinate1 - The first coordinate to compare. + * @param {import("ol/coordinate")} coordinate2 - The first coordinate to compare. + * @param {number?} [epsilon] - The first coordinate to compare. + * + * @return {boolean} - True if the coordinates are approximately equal, false otherwise. */ export default function coordinatesRoughlyEquals( coordinate1, @@ -12,22 +14,28 @@ export default function coordinatesRoughlyEquals( epsilon = 0.001 ) { let equals = true; + + // Iterate through each dimension of the coordinates (e.g., [x, y] or [longitude, latitude]) for (let i = coordinate1.length - 1; i >= 0; --i) { + // Use a helper function to compare each coordinate component with the specified tolerance (epsilon) if (!numbersRoughlyEquals(coordinate1[i], coordinate2[i], epsilon)) { equals = false; break; } } + + // Return true if all components are approximately equal, false otherwise return equals; } /** * helper function to roughly compare number, e.g. for coordinate or zoom comparison - * @param {number} number1 - * @param {number} number2 - * @param {number?} epsilon tolerance - * @return {boolean} + * @param {number} number1 - The first number to compare. + * @param {number} number2 - The second number to compare. + * @param {number?} [epsilon] - The first coordinate to compare. + * @return {boolean} - True if the numbers are approximately equal within the given tolerance, false otherwise. */ function numbersRoughlyEquals(number1, number2, epsilon = 0.001) { + // Check if the absolute difference between the numbers is less than the tolerance return Math.abs(number1 - number2) < epsilon; } diff --git a/elements/map/src/helpers/draw.js b/elements/map/src/helpers/draw.js index 91ac55fc6..a1c5a2e75 100644 --- a/elements/map/src/helpers/draw.js +++ b/elements/map/src/helpers/draw.js @@ -7,24 +7,29 @@ import { addNewFeature } from "../helpers"; * */ /** - * Adds a `draw`-interaction to the map. - * Additionally, if {options.modify} is set, it also adds a `modify` interaction. The name `modify`-interaction - * follows the naming convention `${DrawOptions.id}_modify` + * Adds a `draw` interaction to the map. If `options.modify` is set, it also adds a `modify` interaction. + * The `modify` interaction's name follows the convention `${DrawOptions.id}_modify`. * - * @param {import("../main").EOxMap} EOxMap - * @param {import("ol/layer").Vector} drawLayer - * @param {DrawOptions} options + * @param {import("../main").EOxMap} EOxMap - The map object where the interactions will be added. + * @param {import("ol/layer").Vector} drawLayer - The map object where the interactions will be added. + * @param {DrawOptions} options - The map object where the interactions will be added. + * + * @throws Will throw an error if an interaction with the given ID already exists. */ export function addDraw(EOxMap, drawLayer, options) { + // Create a shallow copy of the options to avoid modifying the original object const options_ = Object.assign({}, options); - if (EOxMap.interactions[options_.id]) { + + // Check if an interaction with the specified ID already exists + if (EOxMap.interactions[options_.id]) throw Error(`Interaction with id: ${options_.id} already exists.`); - } + options_.modify = typeof options_.modify === "boolean" ? options_.modify : true; const source = drawLayer.getSource(); + // Handle the "Box" type drawing, using a circle with a geometry function to create a box if (options_.type === "Box") { options_.geometryFunction = createBox(); options_.type = "Circle"; @@ -36,11 +41,12 @@ export function addDraw(EOxMap, drawLayer, options) { source, }); - // TODO cleaner way of initializing as inactive? + // Set the draw interaction to inactive if `options.active` is explicitly set to false if (options_.active === false) { drawInteraction.setActive(false); } + // Listen for the 'drawend' event to handle the addition of new features to the layer drawInteraction.on("drawend", (e) => { if (!drawLayer.get("isDrawingEnabled")) return; addNewFeature(e, drawLayer, EOxMap, true); @@ -56,6 +62,7 @@ export function addDraw(EOxMap, drawLayer, options) { EOxMap.map.addInteraction(modifyInteraction); EOxMap.interactions[`${options_.id}_modify`] = modifyInteraction; + // Listener function to remove interactions when the associated layer is removed const removeLayerListener = () => { if (!EOxMap.getLayerById(drawLayer.get("id"))) { EOxMap.removeInteraction(options_.id); @@ -63,5 +70,7 @@ export function addDraw(EOxMap, drawLayer, options) { EOxMap.map.getLayerGroup().un("change", removeLayerListener); } }; + + // Subscribe to the 'change' event on the layer group to detect when layers are removed EOxMap.map.getLayerGroup().on("change", removeLayerListener); } diff --git a/elements/map/src/helpers/generate.js b/elements/map/src/helpers/generate.js index e006fb011..c6221c707 100644 --- a/elements/map/src/helpers/generate.js +++ b/elements/map/src/helpers/generate.js @@ -62,42 +62,45 @@ const basicOlSources = { /** * Creates an OpenLayers layer from a given EoxLayer definition object. * - * @param {EOxMap} EOxMap - The map instance. + * @param {import("../main").EOxMap} EOxMap - The map instance. * @param {EoxLayer} layer - The layer definition. - * @param {boolean=} createInteractions - Whether to create interactions for the layer. + * @param {boolean=} createInteractions - Whether to create interactions for the layer (default: true). * @returns {AnyLayer} - The created OpenLayers layer. + * + * @throws Will throw an error if the specified layer or source type is not supported. */ export function createLayer(EOxMap, layer, createInteractions = true) { layer = JSON.parse(JSON.stringify(layer)); + // Merge available formats, layers, and sources from global scope (if any) with basic ones const availableFormats = { ...window.eoxMapAdvancedOlFormats, ...basicOlFormats, }; - const availableLayers = { ...window.eoxMapAdvancedOlLayers, ...basicOlLayers, }; - const availableSources = { ...window.eoxMapAdvancedOlSources, ...basicOlSources, }; + // Get the layer and source constructors based on the provided layer definition const newLayer = availableLayers[layer.type]; const newSource = availableSources[layer.source?.type]; + // Throw an error if the specified layer type or source type is not supported if (!newLayer) { throw new Error(`Layer type ${layer.type} not supported!`); } - if (layer.source && !newSource) { throw new Error(`Source type ${layer.source.type} not supported!`); } const tileGrid = generateTileGrid(layer); + // Create the OpenLayers layer with the specified options const olLayer = new newLayer({ ...layer, ...(layer.source && { @@ -105,6 +108,7 @@ export function createLayer(EOxMap, layer, createInteractions = true) { ...layer.source, ...(layer.source.format && layer.source.type !== "WMTS" && { + // Set the format (e.g., GeoJSON, MVT) for the source format: new availableFormats[ typeof layer.source.format === "object" ? layer.source.format.type @@ -115,19 +119,22 @@ export function createLayer(EOxMap, layer, createInteractions = true) { }), }), }), + // Set the format (e.g., GeoJSON, MVT) for the source ...(layer.source.tileGrid && { tileGrid }), + // Set the projection, converting it using OpenLayers' `getProjection` method ...(layer.source.projection && { projection: getProjection(layer.source.projection), }), }), }), - ...(layer.type === "Group" && { layers: [] }), + ...(layer.type === "Group" && { layers: [] }), // Initialize an empty layer collection for group layers ...layer.properties, - style: undefined, // override layer style, apply style after + style: undefined, // Reset the style; it will be applied later if specified }); olLayer.set("_jsonDefinition", layer, true); + // Handle group layers by recursively creating their sublayers if (layer.type === "Group") { const groupLayers = layer.layers .reverse() @@ -140,6 +147,7 @@ export function createLayer(EOxMap, layer, createInteractions = true) { olLayer.setStyle(layer.style); } + // Add interactions (e.g., draw, select) to the layer if requested if (createInteractions && layer.interactions?.length) { for (let i = 0; i < layer.interactions.length; i++) { const interactionDefinition = layer.interactions[i]; @@ -147,32 +155,35 @@ export function createLayer(EOxMap, layer, createInteractions = true) { } } + // Add interactions (e.g., draw, select) to the layer if requested setSyncListeners(olLayer, layer); return olLayer; } /** - * Adds an interaction to a given layer. + * Adds an interaction (e.g., draw, select) to a given layer. * - * @param {EOxMap} EOxMap - The map instance. + * @param {import("../main").EOxMap} EOxMap - The map instance. * @param {AnyLayer} olLayer - The OpenLayers layer. * @param {EOxInteraction} interactionDefinition - The interaction definition. */ function addInteraction(EOxMap, olLayer, interactionDefinition) { - if (interactionDefinition.type === "draw") { + // Add draw or select interactions based on the interaction type + if (interactionDefinition.type === "draw") addDraw(EOxMap, olLayer, interactionDefinition.options); - } else if (interactionDefinition.type === "select") { + else if (interactionDefinition.type === "select") addSelect(EOxMap, olLayer, interactionDefinition.options); - } } /** * Updates an existing layer with a new definition. * - * @param {EOxMap} EOxMap - The map instance. + * @param {import("../main").EOxMap} EOxMap - The map instance. * @param {EoxLayer} newLayerDefinition - The new layer definition. * @param {AnyLayer} existingLayer - The existing layer to be updated. * @returns {AnyLayer} - The updated layer. + * + * @throws Will throw an error if the new layer is not compatible with the existing one. */ export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { const existingJsonDefinition = existingLayer.get("_jsonDefinition"); @@ -331,14 +342,14 @@ export function updateLayer(EOxMap, newLayerDefinition, existingLayer) { /** * Generates layers for the map from an array of layer definitions. * - * @param {EOxMap} EOxMap - The map instance. + * @param {import("../main").EOxMap} EOxMap - The map instance. * @param {Array} layerArray - Array of layer definitions. * @returns {Array} - An array of created layers. */ export const generateLayers = (EOxMap, layerArray) => { - if (!layerArray) { - return []; - } + if (!layerArray) return []; + + // Reverse the layer array to maintain the stacking order return [...layerArray].reverse().map((l) => createLayer(EOxMap, l)); }; @@ -349,15 +360,22 @@ export const generateLayers = (EOxMap, layerArray) => { * @param {EoxLayer} eoxLayer - The EoxLayer definition. */ function setSyncListeners(olLayer, eoxLayer) { + // Sync opacity changes to the layer definition olLayer.on("change:opacity", () => { eoxLayer.opacity = olLayer.getOpacity(); }); + + // Sync visibility changes to the layer definition olLayer.on("change:visible", () => { eoxLayer.visible = olLayer.getVisible(); }); + + // Sync zIndex changes to the layer definition olLayer.on("change:zIndex", () => { eoxLayer.zIndex = olLayer.getZIndex(); }); + + // Sync other property changes to the layer definition olLayer.on("propertychange", (e) => { if (e.key === "map") return; eoxLayer.properties[e.key] = e.target.get(e.key); diff --git a/elements/map/src/helpers/interactions.js b/elements/map/src/helpers/interactions.js index 72ebdeae0..56c8236fc 100644 --- a/elements/map/src/helpers/interactions.js +++ b/elements/map/src/helpers/interactions.js @@ -2,13 +2,16 @@ import { DragPan, MouseWheelZoom } from "ol/interaction"; import { platformModifierKeyOnly } from "ol/events/condition"; /** - * Adds interactions for preventing scroll behavior on the specified OpenLayers map. + * Adds interactions to control scroll behavior on the specified OpenLayers map. + * If `customInteraction` is true, enables zooming only with a platform-specific modifier key (e.g., `Ctrl` on Windows). + * On touch devices, allows dragging with two fingers or a modifier key. * * @param {import("ol").Map} map - The OpenLayers map to which the interactions should be added. - * @param {boolean} customInteraction - state if customInteraction to be added or default + * @param {boolean} customInteraction - Indicates whether to use custom interactions (default: false). */ export function addScrollInteractions(map, customInteraction = false) { if (customInteraction) { + // Add MouseWheelZoom interaction that only works when a platform modifier key (e.g., Ctrl) is pressed map.addInteraction( new MouseWheelZoom({ condition: platformModifierKeyOnly, @@ -17,7 +20,9 @@ export function addScrollInteractions(map, customInteraction = false) { const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0; + if (isTouchDevice) { + // On touch devices, allow dragging with two fingers or a modifier key map.addInteraction( new DragPan({ condition: function (event) { @@ -27,8 +32,9 @@ export function addScrollInteractions(map, customInteraction = false) { }, }) ); - } else map.addInteraction(new DragPan()); + } else map.addInteraction(new DragPan()); // On non-touch devices, add the default DragPan interaction } else { + // Add default MouseWheelZoom and DragPan interactions map.addInteraction(new MouseWheelZoom()); map.addInteraction(new DragPan()); } @@ -40,6 +46,7 @@ export function addScrollInteractions(map, customInteraction = false) { * @param {import("ol").Map} map - The OpenLayers map from which the interactions should be removed. */ export function removeDefaultScrollInteractions(map) { + // Iterate through all interactions on the map and remove those related to scrolling (MouseWheelZoom and DragPan) map .getInteractions() .getArray() diff --git a/elements/map/src/helpers/layer.js b/elements/map/src/helpers/layer.js index 607fccaf5..6b83d917d 100644 --- a/elements/map/src/helpers/layer.js +++ b/elements/map/src/helpers/layer.js @@ -6,45 +6,58 @@ import Group from "ol/layer/Group"; */ /** - * Retrieves a layer by its ID from the EOxMap. + * Retrieves a layer by its ID from the EOxMap instance. * - * @param {EOxMap} EOxMap - Instance of EOxMap class - * @param {string} layerId - ID of the OpenLayers layer - * @returns {AnyLayerWithSource | undefined} - Layer or `undefined` if the layer does not exist + * @param {import("../main").EOxMap} EOxMap - Instance of the EOxMap class. + * @param {string} layerId - The ID of the OpenLayers layer to find. + * @returns {AnyLayerWithSource | undefined} - The layer with the specified ID, or `undefined` if it does not exist. */ export function getLayerById(EOxMap, layerId) { - const flatLayers = getFlatLayersArray( - /** @type {Array} */ (EOxMap.map.getLayers().getArray()) - ); + // Get a flat array of all layers in the map, including those inside groups + const flatLayers = getFlatLayersArray(EOxMap.map.getLayers().getArray()); + + // Find and return the layer with the specified ID return flatLayers.find((l) => l.get("id") === layerId); } /** - * Returns a flat array of all map layers, including groups and nested layers. - * To get all layers without groups, you can use the native OL `getAllLayers` method on the map itself: + * Returns a flat array of all map layers, including nested layers within groups. + * + * Note: If you want to get all layers without groups, use the native OpenLayers `getAllLayers` method: * https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html#getAllLayers * - * @param {Array} layers - Array of layers - * @returns {Array} - Flat array of layers - * @example getFlatLayersArray(eoxMap.map.getAllLayers()) + * @param {Array} layers - Array of OpenLayers layers, possibly containing groups. + * @returns {Array} - A flat array of all layers, including those inside groups. */ export function getFlatLayersArray(layers) { const flatLayers = []; + + // Add the initial layers to the flatLayers array flatLayers.push(...layers); + // Filter the initial layers to find group layers that may contain nested layers let groupLayers = flatLayers.filter((l) => l instanceof Group); + // Loop through group layers to flatten the nested layers while (groupLayers.length) { const newGroupLayers = []; for (let i = 0; i < groupLayers.length; i++) { + // Filter the initial layers to find group layers that may contain nested layers const layersInsideGroup = groupLayers[i].getLayers().getArray(); + + // Add these nested layers to the flatLayers array flatLayers.push(...layersInsideGroup); + + // Check for nested groups inside the current group and add them to the newGroupLayers array newGroupLayers.push( ...layersInsideGroup.filter((l) => l instanceof Group) ); } + + // Continue processing any newly found group layers groupLayers = newGroupLayers; } - return /** @type {Array} */ (flatLayers); + // Continue processing any newly found group layers + return flatLayers; } diff --git a/elements/map/src/helpers/parse-feature.js b/elements/map/src/helpers/parse-feature.js index 32fbf6d98..ffa5eec62 100644 --- a/elements/map/src/helpers/parse-feature.js +++ b/elements/map/src/helpers/parse-feature.js @@ -1,7 +1,16 @@ import GeoJSON from "ol/format/GeoJSON"; import { READ_FEATURES_OPTIONS } from "../enums"; +/** + * Converts an array of OpenLayers features into a GeoJSON object. + * + * @param {Array} features - An array of OpenLayers features to be converted. + * @returns {Object} - A GeoJSON object representing the provided features. + */ export default function parseFeature(features) { + // Create a new GeoJSON format instance const format = new GeoJSON(); + + // Convert the OpenLayers features into a GeoJSON object using the specified options return format.writeFeaturesObject(features, READ_FEATURES_OPTIONS); } diff --git a/elements/map/src/helpers/parse-text.js b/elements/map/src/helpers/parse-text.js index e0ee84ce1..fe74eb401 100644 --- a/elements/map/src/helpers/parse-text.js +++ b/elements/map/src/helpers/parse-text.js @@ -51,9 +51,13 @@ export default function parseTextToFeature( * @returns {GeoJSON | KML | TopoJSON | null} A format reader (GeoJSON, KML, or TopoJSON) if a matching format is detected, otherwise null. */ function getFormatReader(text) { + // Check if the text is in GeoJSON format if (isGeoJSON(text)) return new GeoJSON(); + // Check if the text is in KML format else if (isKML(text)) return new KML({ extractStyles: false }); + // Check if the text is in TopoJSON format else if (isTopoJSON(text)) return new TopoJSON(); + // Return `null` if no known format is detected else return null; } @@ -65,7 +69,9 @@ function getFormatReader(text) { */ export function isGeoJSON(text) { try { + // Return `null` if no known format is detected const obj = JSON.parse(text); + return obj.type === "FeatureCollection" || obj.type === "Feature"; } catch (e) { return false; @@ -79,6 +85,7 @@ export function isGeoJSON(text) { * @returns {boolean} - `true` if the string contains KML tags, `false` otherwise. */ export function isKML(text) { + // Return `null` if no known format is detected return text.includes(""); } @@ -90,7 +97,9 @@ export function isKML(text) { */ export function isTopoJSON(text) { try { + // Return `null` if no known format is detected const obj = JSON.parse(text); + return obj.type === "Topology" && obj.objects; } catch (e) { return false; diff --git a/elements/map/src/helpers/register-projection-from-code.js b/elements/map/src/helpers/register-projection-from-code.js index d0e2204b6..97f9e5e35 100644 --- a/elements/map/src/helpers/register-projection-from-code.js +++ b/elements/map/src/helpers/register-projection-from-code.js @@ -2,12 +2,15 @@ import { fromEPSGCode, register } from "ol/proj/proj4"; import proj4 from "proj4"; /** - * given a projection code, this fetches the definition from epsg.io - * and registers the projection using proj4 - * @param {string | number} code - The EPSG code (e.g. 4326 or 'EPSG:4326'). + * Fetches the projection definition for a given EPSG code from epsg.io and registers the projection using proj4. * + * @param {string | number} code - The EPSG code (e.g., 4326 or 'EPSG:4326'). + * @returns {Promise} - A promise that resolves to the registered projection. */ export default async function registerProjectionFromCode(code) { + // Register proj4 with OpenLayers to allow handling of custom projections register(proj4); + + // Fetch and register the projection definition from epsg.io using the provided EPSG code= return await fromEPSGCode(code); } diff --git a/elements/map/src/helpers/register-projection.js b/elements/map/src/helpers/register-projection.js index 2bcf030d3..d12b62848 100644 --- a/elements/map/src/helpers/register-projection.js +++ b/elements/map/src/helpers/register-projection.js @@ -3,19 +3,24 @@ import { get as getProj } from "ol/proj"; import { register } from "ol/proj/proj4"; /** - * registers a projection under a given name, defined via a proj4 definition - * @param {string} name - name of the projection (e.g. "EPSG:4326") - * @param {string | proj4.ProjectionDefinition} projection - proj4 projection definition string - * @param {import("ol/extent").Extent} extent + * Registers a projection under a given name using a proj4 definition. + * This allows OpenLayers to recognize and work with custom or predefined projections. + * + * @param {string} name - The name of the projection (e.g., "EPSG:4326"). + * @param {string | proj4.ProjectionDefinition} projection - The proj4 projection definition string or object. + * @param {import("ol/extent").Extent} [extent=undefined] - Optional extent for the projection. Defines the coordinate system's valid area. */ export default function registerProjection( name, projection, extent = undefined ) { + // Register the projection with proj4 using the provided name and definition proj4.defs(name, projection); + + // Register the projection with proj4 using the provided name and definition register(proj4); - if (typeof extent !== "undefined") { - getProj(name).setExtent(extent); - } + + // If an extent is provided, set it on the projection in OpenLayers + if (typeof extent !== "undefined") getProj(name).setExtent(extent); } diff --git a/elements/map/src/helpers/select.js b/elements/map/src/helpers/select.js index e2773dbe3..261f02294 100644 --- a/elements/map/src/helpers/select.js +++ b/elements/map/src/helpers/select.js @@ -15,12 +15,14 @@ import { createEmpty, extend, isEmpty } from "ol/extent"; /** * Class representing the EOxSelectInteraction. + * Handles the selection interaction for a specified layer, including highlighting features, + * displaying tooltips, and panning into selected features. */ export class EOxSelectInteraction { /** - * @param {EOxMap} eoxMap - Instance of EOxMap class. + * @param {EOxMap} eoxMap - Instance of the EOxMap class. * @param {SelectLayer} selectLayer - Layer for selection. - * @param {SelectOptions} options - Options for selection interaction. + * @param {SelectOptions} options - Options for the selection interaction. */ constructor(eoxMap, selectLayer, options) { this.eoxMap = eoxMap; @@ -28,11 +30,11 @@ export class EOxSelectInteraction { this.options = options; this.active = options.active || selectLayer.getVisible(); this.panIn = options.panIn || false; + this.selectedFids = []; + // Retrieve or create the tooltip overlay for displaying feature information const existingTooltip = this.eoxMap.map.getOverlayById("eox-map-tooltip"); - let overlay; - this.selectedFids = []; if (existingTooltip) { this.tooltip = existingTooltip.getElement(); @@ -56,6 +58,7 @@ export class EOxSelectInteraction { } } + // Set up event listeners to handle pointer leave events const pointerLeaveListener = () => { if (overlay && options.condition === "pointermove") { overlay.setPosition(undefined); @@ -71,6 +74,7 @@ export class EOxSelectInteraction { .getTargetElement() ?.addEventListener("pointerleave", pointerLeaveListener); + // Set up the layer for the selection styling let layerDefinition; if (this.options.layer) { layerDefinition = this.options.layer; @@ -90,24 +94,26 @@ export class EOxSelectInteraction { layerDefinition.renderMode = "vector"; delete layerDefinition.interactions; + // Create a new layer for the selection styling this.selectStyleLayer = createLayer(eoxMap, layerDefinition); //@ts-expect-error VectorSource for VectorLayer, VectorTileSource for VectorTileLayer this.selectStyleLayer.setSource(this.selectLayer.getSource()); this.selectStyleLayer.setMap(this.eoxMap.map); + // Set up the initial style for the selection layer const initialStyle = this.selectStyleLayer.getStyleFunction(); - this.selectStyleLayer.setStyle((feature, resolution) => { if ( this.selectedFids.length && this.selectedFids.includes(this.getId(feature)) ) { - return initialStyle(feature, resolution); + return initialStyle(feature, resolution); // Apply style only if the feature is selected } return null; }); + // Listener to handle selection events const listener = (event) => { if (!this.active) { return; @@ -121,6 +127,8 @@ export class EOxSelectInteraction { ) { return; } + + // Fetch features at the clicked pixel this.selectLayer.getFeatures(event.pixel).then((features) => { const feature = features.length ? features[0] : null; @@ -132,6 +140,7 @@ export class EOxSelectInteraction { if (feature && this.panIn) this.panIntoFeature(feature); } + // Fetch features at the clicked pixel if (overlay) { const xPosition = event.pixel[0] > this.eoxMap.offsetWidth / 2 ? "right" : "left"; @@ -154,8 +163,11 @@ export class EOxSelectInteraction { this.eoxMap.dispatchEvent(selectdEvt); }); }; + + // Set up the map event listener for the specified condition (e.g., click, pointermove) this.eoxMap.map.on(options.condition || "click", listener); + // Set up the map event listener for the specified condition (e.g., click, pointermove) this.selectLayer.on("change:opacity", () => { this.selectStyleLayer.setOpacity(this.selectLayer.getOpacity()); }); @@ -166,6 +178,7 @@ export class EOxSelectInteraction { this.setActive(visible); }); + // Set up the map event listener for the specified condition (e.g., click, pointermove) this.changeSourceListener = () => { //@ts-expect-error VectorSource for VectorLayer, VectorTileSource for VectorTileLayer this.selectStyleLayer.setSource(this.selectLayer.getSource()); @@ -173,6 +186,7 @@ export class EOxSelectInteraction { this.selectLayer.on("change:source", this.changeSourceListener); + // Set up the map event listener for the specified condition (e.g., click, pointermove) const changeLayerListener = () => { if (eoxMap.getLayerById(selectLayer.get("id"))) { eoxMap.selectInteractions[options.id]?.setActive(true); @@ -187,10 +201,19 @@ export class EOxSelectInteraction { eoxMap.map.getLayerGroup().on("change", changeLayerListener); } + /** + * Sets the active state of the interaction. + * @param {boolean} active - Whether the interaction should be active. + */ setActive(active) { this.active = active; } + /** + * Pans the map to the specified feature or extent. + * @param {Feature | RenderFeature | import("ol/extent").Extent} featureOrExtent - The feature or extent to pan to. + * @param {Object} [options] - Additional options for the panning animation. + */ panIntoFeature = (featureOrExtent, options) => { const extent = featureOrExtent instanceof Feature || @@ -200,6 +223,11 @@ export class EOxSelectInteraction { this.eoxMap.map.getView().fit(extent, options || { duration: 750 }); }; + /** + * Highlights features by their IDs and optionally pans into their extent. + * @param {Array} ids - Array of feature IDs to highlight. + * @param {Object} [fitOptions] - Options for panning into the highlighted features. + */ highlightById(ids, fitOptions) { this.selectedFids = ids; if (ids.length && fitOptions) { @@ -231,12 +259,21 @@ export class EOxSelectInteraction { this.selectStyleLayer.changed(); } + /** + * Removes the selection interaction and associated layers from the map. + */ remove() { this.selectStyleLayer.setMap(null); delete this.eoxMap.selectInteractions[this.options.id]; this.selectLayer.un("change:source", this.changeSourceListener); } + /** + * Retrieves the ID of the given feature. + * @param {Feature | RenderFeature} feature - The feature whose ID is to be retrieved. + * @returns {string} - The ID of the feature. + * @throws Will throw an error if no valid ID is found. + */ getId(feature) { if (this.options.idProperty) { return feature.get(this.options.idProperty); @@ -254,10 +291,14 @@ export class EOxSelectInteraction { } /** - * Adds a `select`-interaction to the map. + * Adds a `select` interaction to the map. + * * @param {EOxMap} EOxMap - Instance of EOxMap class. * @param {SelectLayer} selectLayer - Layer to be selected. * @param {SelectOptions} options - Options for the select interaction. + * @returns {EOxSelectInteraction} - The created selection interaction instance. + * + * @throws Will throw an error if an interaction with the specified ID already exists. */ export function addSelect(EOxMap, selectLayer, options) { if (EOxMap.interactions[options.id]) { diff --git a/elements/map/src/helpers/tile-grid.js b/elements/map/src/helpers/tile-grid.js index 8549f9157..a4b6b1b18 100644 --- a/elements/map/src/helpers/tile-grid.js +++ b/elements/map/src/helpers/tile-grid.js @@ -1,33 +1,50 @@ import { createXYZ } from "ol/tilegrid"; import WMTSTileGrid from "ol/tilegrid/WMTS"; -import { getTopLeft, getWidth } from "ol/extent.js"; -import { get as getProjection } from "ol/proj.js"; +import { getTopLeft, getWidth } from "ol/extent"; +import { get as getProjection } from "ol/proj"; /** - * Generates a WMTS tile grid for WMTS layers or else an XYZ tile grid, if defined. + * @typedef {import("../../types").EoxLayer} EoxLayer + * */ + +/** + * Generates a tile grid for the specified layer. If the layer's source type is "WMTS", + * a `WMTSTileGrid` is generated. Otherwise, an `XYZ` tile grid is generated using the + * OpenLayers `createXYZ` function. Returns `undefined` if the layer's tile grid is not defined. * - * @param {EoxLayer} layer - The layer configuration object. - * @returns {import("ol/tilegrid/WMTS").default | import("ol/tilegrid/TileGrid").default | undefined} - The generated tile grid or undefined. + * @param {EoxLayer} layer - The layer configuration object, containing source and tile grid information. + * @returns {import("ol/tilegrid/WMTS").default | import("ol/tilegrid/TileGrid").default | undefined} - The generated tile grid or `undefined` if not applicable. */ export function generateTileGrid(layer) { let tileGrid; + + // Return undefined if no tile grid is defined in the layer's source if (!layer.source?.tileGrid) { return undefined; } + // Return undefined if no tile grid is defined in the layer's source if (layer.source.tileGrid) { + // Return undefined if no tile grid is defined in the layer's source if (layer.source.type === "WMTS") { + // Return undefined if no tile grid is defined in the layer's source const projection = getProjection("EPSG:3857"); const projectionExtent = projection.getExtent(); + + // Calculate the size of the tiles based on the extent and desired resolution const size = getWidth(projectionExtent) / 128; + + // Calculate the size of the tiles based on the extent and desired resolution const resolutions = new Array(19); const matrixIds = new Array(19); + for (let z = 0; z < 19; ++z) { // generate resolutions and matrixIds arrays for this WMTS resolutions[z] = size / Math.pow(2, z); matrixIds[z] = z; } + // Create a new WMTSTileGrid using the calculated resolutions and matrix IDs tileGrid = new WMTSTileGrid({ resolutions: resolutions, origin: getTopLeft(projectionExtent), @@ -36,10 +53,13 @@ export function generateTileGrid(layer) { ...layer.source.tileGrid, }); } else { + // For non-WMTS sources, generate an XYZ tile grid tileGrid = createXYZ({ ...layer.source.tileGrid, }); } } + + // Return the generated tile grid return tileGrid; } diff --git a/elements/map/src/methods/map/add-update-layer.js b/elements/map/src/methods/map/add-update-layer.js index 81c980307..b7221d045 100644 --- a/elements/map/src/methods/map/add-update-layer.js +++ b/elements/map/src/methods/map/add-update-layer.js @@ -1,19 +1,33 @@ import { createLayer, updateLayer, getLayerById } from "../../helpers"; +/** + * Adds a new layer to the map or updates an existing layer based on the provided JSON data. + * + * @param {Object} json - The JSON data containing layer properties and interactions. + * @param {import("../../main").EOxMap} EOxMap - The map object where the layer will be added or updated. + * @returns {Object} - The newly created or updated layer object. + */ export default function addOrUpdateLayerMethod(json, EOxMap) { - if (!json.interactions) { - json.interactions = []; - } + // Initialize interactions property if it doesn't exist in the JSON data + if (!json.interactions) json.interactions = []; + + // Retrieve the layer ID from the JSON properties const id = json.properties?.id; + // Check if a layer with the specified ID already exists on the map const existingLayer = id ? getLayerById(EOxMap, id) : false; let layer; + if (existingLayer) { + // If the layer exists, update it with the new JSON data updateLayer(EOxMap, json, existingLayer); layer = existingLayer; } else { + // If the layer does not exist, create a new layer and add it to the map layer = createLayer(EOxMap, json); EOxMap.map.addLayer(layer); } + + // Return the created or updated layer return layer; } diff --git a/elements/map/src/methods/map/animate-to-state.js b/elements/map/src/methods/map/animate-to-state.js index 096f186f6..8dda5c54c 100644 --- a/elements/map/src/methods/map/animate-to-state.js +++ b/elements/map/src/methods/map/animate-to-state.js @@ -1,15 +1,29 @@ import { cancelAnimation, getCenterFromProperty } from "../../helpers"; +/** + * Animates the map view to a specified state using the provided animation options. + * + * @param {import("../../main").EOxMap} EOxMap - The map object containing the current map view, center, zoom, and animation options. + */ export default function animateToStateMethod(EOxMap) { + // Copy the animation options from the map object const animateToOptions = Object.assign({}, EOxMap.animationOptions); const view = EOxMap.map.getView(); + + // Cancel any ongoing animations on the view cancelAnimation(view); + + // If no animation options are provided, reset the view to the default center and zoom if (!animateToOptions || !Object.keys(animateToOptions).length) { view.setCenter(EOxMap.center); view.setZoom(EOxMap.zoom); return; } + + // Set the animation options for the center and zoom of the map animateToOptions.center = getCenterFromProperty(EOxMap.center); animateToOptions.zoom = EOxMap.zoom; + + // Animate the map view using the specified options view.animate(animateToOptions); } diff --git a/elements/map/src/methods/map/first-updated.js b/elements/map/src/methods/map/first-updated.js index a7ecb7b98..fa543bc1f 100644 --- a/elements/map/src/methods/map/first-updated.js +++ b/elements/map/src/methods/map/first-updated.js @@ -1,16 +1,30 @@ import { animateToStateMethod } from "./"; +/** + * Sets up the initial state of the map and handles its first update, including setting the center, + * zoom extent, and dispatching events when the map is loaded and mounted. + * + * @param {Object} zoomExtent - Optional extent to fit the map view to a specific area. + * @param {import("../../main").EOxMap} EOxMap - The map object containing the map instance, center, animation options, and other properties. + */ export default function firstUpdatedMethod(zoomExtent, EOxMap) { + // Set the center of the map once the target changes EOxMap.map.once("change:target", (e) => { e.target.getView().setCenter(EOxMap.center); }); + + // Set the target element for the map rendering EOxMap.map.setTarget(EOxMap.renderRoot.querySelector("div")); + // Fit the map view to the specified extent if provided; otherwise, animate to the default state if (zoomExtent) EOxMap.map.getView().fit(zoomExtent, EOxMap.animationOptions); else animateToStateMethod(EOxMap); + // Listen for the "loadend" event and dispatch a custom "loadend" event when the map has finished loading EOxMap.map.on("loadend", () => { EOxMap.dispatchEvent(new CustomEvent("loadend", { detail: EOxMap.map })); }); + + // Dispatch a custom "mapmounted" event to indicate that the map has been successfully mounted EOxMap.dispatchEvent(new CustomEvent("mapmounted", { detail: EOxMap.map })); } diff --git a/elements/map/src/methods/map/getters.js b/elements/map/src/methods/map/getters.js index 751cca4aa..09ea20ff8 100644 --- a/elements/map/src/methods/map/getters.js +++ b/elements/map/src/methods/map/getters.js @@ -1,17 +1,35 @@ import { transform, transformExtent } from "../../helpers"; +/** + * Retrieves the current center of the map in longitude and latitude coordinates. + * + * @param {import("../../main").EOxMap} EOxMap - The map object containing the map instance and its projection information. + * @returns {Array} - The center coordinates of the map in longitude and latitude. + */ export function getLonLatCenterMethod(EOxMap) { + // If the map's projection is EPSG:4326 (longitude/latitude), return the center directly if (EOxMap.projection === "EPSG:4326") return EOxMap.map.getView().getCenter(); + + // Otherwise, transform the center coordinates to longitude/latitude return transform(EOxMap.map.getView().getCenter(), EOxMap.projection); } +/** + * Retrieves the current extent (bounding box) of the map in longitude and latitude coordinates. + * + * @param {import("../../main").EOxMap} EOxMap - The map object containing the map instance, projection information, and size. + * @returns {Array} - The extent of the map in longitude and latitude. + */ export function getLonLatExtentMethod(EOxMap) { + // Calculate the current extent of the map based on its view and size const currentExtent = EOxMap.map .getView() .calculateExtent(EOxMap.map.getSize()); - if (EOxMap.projection === "EPSG:4326") { - return currentExtent; - } + + // If the map's projection is EPSG:4326, return the extent directly + if (EOxMap.projection === "EPSG:4326") return currentExtent; + + // Otherwise, transform the extent to longitude/latitude return transformExtent(currentExtent, EOxMap.projection); } diff --git a/elements/map/src/methods/map/remove.js b/elements/map/src/methods/map/remove.js index 3924fc70b..9a844b304 100644 --- a/elements/map/src/methods/map/remove.js +++ b/elements/map/src/methods/map/remove.js @@ -1,18 +1,47 @@ +/** + * Removes an interaction from the map and deletes it from the interaction list in EOxMap. + * + * @param {string} id - The identifier for the interaction to be removed. + * @param {import("../../main").EOxMap} EOxMap - The map object containing the map instance and interactions list. + */ export function removeInteractionMethod(id, EOxMap) { + // Remove the interaction from the map using the provided ID EOxMap.map.removeInteraction(EOxMap.interactions[id]); + + // Delete the interaction from the EOxMap interactions list delete EOxMap.interactions[id]; + + // If a corresponding modify interaction exists, remove and delete it as well if (EOxMap.interactions[`${id}_modify`]) { EOxMap.map.removeInteraction(EOxMap.interactions[`${id}_modify`]); delete EOxMap.interactions[`${id}_modify`]; } } +/** + * Removes a select interaction from the map and deletes it from the select interactions list in EOxMap. + * + * @param {string} id - The identifier for the select interaction to be removed. + * @param {import("../../main").EOxMap} EOxMap - The map object containing the select interactions list. + */ export function removeSelectMethod(id, EOxMap) { + // Remove the select interaction using the provided ID EOxMap.selectInteractions[id].remove(); + + // Delete the select interaction from the EOxMap select interactions list delete EOxMap.selectInteractions[id]; } +/** + * Removes a control from the map and deletes it from the map controls list in EOxMap. + * + * @param {string} id - The identifier for the control to be removed. + * @param {import("../../main").EOxMap} EOxMap - The map object containing the map instance and controls list. + */ export function removeControlMethod(id, EOxMap) { + // Remove the control from the map using the provided ID EOxMap.map.removeControl(EOxMap.mapControls[id]); + + // Delete the control from the EOxMap map controls list delete EOxMap.mapControls[id]; } diff --git a/elements/map/src/methods/map/setters.js b/elements/map/src/methods/map/setters.js index 5fd7e218f..bba6ed5b3 100644 --- a/elements/map/src/methods/map/setters.js +++ b/elements/map/src/methods/map/setters.js @@ -16,49 +16,88 @@ import VectorLayer from "ol/layer/Vector"; import View from "ol/View"; import { getElement } from "../../../../../utils"; +/** + * Sets the new center of the map if it differs from the current center. + * + * @param {Array} center - The new center coordinates. + * @param {import("../../main").EOxMap} EOxMap - The map object containing the current map view and projection. + * @returns {Array|undefined} - The new center coordinates or undefined if unchanged. + */ export function setCenterMethod(center, EOxMap) { let newCenter = undefined; + // Check if the new center is the same as the current center const centerIsSame = center?.length && coordinatesRoughlyEquals(center, EOxMap.map.getView().getCenter()); + // Check if the new center is the same as the current center if (center && !centerIsSame) { if (!EOxMap.projection || EOxMap.projection === "EPSG:3857") { newCenter = getCenterFromProperty(center); } else newCenter = center; } + // Return the new center or undefined if unchanged return newCenter; } +/** + * Adjusts the map view to fit the specified extent. + * + * @param {Array} extent - The extent to fit the map view to. + * @param {import("../../main").EOxMap} EOxMap - The map object containing the map view and animation options. + * @returns {Array|undefined} - The extent if valid, otherwise undefined. + */ export function setZoomExtentMethod(extent, EOxMap) { + // If the extent is not provided or is empty, exit the function if (!extent || !extent.length) return undefined; const view = EOxMap.map.getView(); + + // Cancel any ongoing animations on the map view to ensure a smooth transition cancelAnimation(view); + + // Fit the map view to the extent asynchronously setTimeout(() => view.fit(extent, EOxMap.animationOptions), 0); + // Return the extent that was fitted return extent; } +/** + * Adds or updates controls on the map, removing any outdated controls. + * + * @param {Object} controls - New controls to set on the map. + * @param {Object} oldControls - Previously set controls on the map. + * @param {import("../../main").EOxMap} EOxMap - The map object containing the control management functions. + * @returns {Object} - The new controls. + */ export function setControlsMethod(controls, oldControls, EOxMap) { const newControls = controls; + // Remove old controls not present in the new controls if (oldControls) { const oldControlTypes = Object.keys(oldControls); const newControlTypes = Object.keys(newControls); + + // Loop through each old control type for (let i = 0, ii = oldControlTypes.length; i < ii; i++) { const oldControlType = oldControlTypes[i]; - if (!newControlTypes.includes(oldControlType)) { + + // If the old control type is not in the new controls, remove it + if (!newControlTypes.includes(oldControlType)) EOxMap.removeControl(oldControlType); - } } } + + // Add or update new controls if (newControls) { const keys = Object.keys(controls); for (let i = 0, ii = keys.length; i < ii; i++) { const key = keys[i]; + + // Add or update the control using the helper function addOrUpdateControl(EOxMap, oldControls, key, controls[key]); } } @@ -66,20 +105,33 @@ export function setControlsMethod(controls, oldControls, EOxMap) { return newControls; } +/** + * Adds or updates layers on the map, removing any outdated layers. + * + * @param {Array} layers - New layers to set on the map. + * @param {Array} oldLayers - Previously set layers on the map. + * @param {import("../../main").EOxMap} EOxMap - The map object containing layer management functions. + * @returns {Array} - The new layers. + */ export function setLayersMethod(layers, oldLayers, EOxMap) { + // Deep copy the new layers array and reverse it to maintain correct layer stacking order const newLayers = JSON.parse(JSON.stringify(layers)).reverse(); + // Remove old layers not present in the new layers if (oldLayers) { oldLayers.forEach((l) => { + // Check if the current old layer is not in the new layers list by its ID if ( !l.properties?.id || !newLayers.find( (newLayer) => newLayer.properties.id === l.properties.id ) ) { + // Retrieve the layer to be removed from the map const layerToBeRemoved = getLayerById(EOxMap, l.properties?.id); const jsonDefinition = layerToBeRemoved.get("_jsonDefinition"); + // Remove any interactions associated with the layer jsonDefinition.interactions?.forEach((interaction) => { if (interaction.type === "select") { EOxMap.removeSelect(interaction.options.id); @@ -87,15 +139,19 @@ export function setLayersMethod(layers, oldLayers, EOxMap) { EOxMap.removeInteraction(interaction.options.id); } }); + + // Remove the layer from the map EOxMap.map.removeLayer(layerToBeRemoved); } }); } + // Add or update new layers newLayers.forEach((l) => { EOxMap.addOrUpdateLayer(l); }); + // Sort layers by their IDs const sortedIds = newLayers.map((l) => l.properties?.id); EOxMap.map .getLayers() @@ -107,51 +163,89 @@ export function setLayersMethod(layers, oldLayers, EOxMap) { ); }); + // Return the new layers object return newLayers; } +/** + * Enables or disables scroll interactions on the map. + * + * @param {boolean} preventScroll - Whether to prevent scroll interactions. + * @param {import("../../main").EOxMap} EOxMap - The map object containing scroll interaction management functions. + * @returns {boolean} - The current scroll prevention state. + */ export function setPreventScrollMethod(preventScroll, EOxMap) { if (preventScroll) { + // Remove the default scroll interactions to prevent scrolling removeDefaultScrollInteractions(EOxMap.map); + + // Add custom scroll interactions with the prevent-scroll flag set to true addScrollInteractions(EOxMap.map, true); } else addScrollInteractions(EOxMap.map); + // Return the current scroll prevention state return preventScroll; } +/** + * Sets the map's configuration options. + * + * @param {Object} config - Configuration object containing view, layers, controls, and other properties. + * @param {import("../../main").EOxMap} EOxMap - The map object to configure. + * @returns {Object} - The applied configuration. + */ export function setConfigMethod(config, EOxMap) { + // Update animation options if provided in the configuration if (config?.animationOptions !== undefined) EOxMap.animationOptions = config.animationOptions; + // Set the map's projection, defaulting to 'EPSG:3857' if not specified EOxMap.projection = config?.view?.projection || "EPSG:3857"; + + // Set the layers, controls, and other map properties from the configuration EOxMap.layers = config?.layers || []; EOxMap.controls = config?.controls || {}; + // Set the scroll prevention option if it has not been defined yet if (EOxMap.preventScroll === undefined) EOxMap.preventScroll = config?.preventScroll; + // Set the zoom, center, and zoom extent of the map view EOxMap.zoom = config?.view?.zoom || 0; EOxMap.center = config?.view?.center || [0, 0]; EOxMap.zoomExtent = config?.view?.zoomExtent; + // Return the applied configuration return config; } +/** + * Sets a new projection for the map, transforming the view and adjusting related properties. + * + * @param {string} projection - The new projection code. + * @param {string} oldProjection - The current projection code. + * @param {import("../../main").EOxMap} EOxMap - The map object containing the current view and layer management functions. + * @returns {string} - The new projection code. + */ export function setProjectionMethod(projection, oldProjection, EOxMap) { let newProj = oldProjection; const oldView = EOxMap.map.getView(); + + // Check if the projection needs to be updated if ( projection && getProjection(projection) && projection !== oldView.getProjection().getCode() ) { + // Transform the map's center to the new projection const newCenter = olTransform( oldView.getCenter(), oldView.getProjection().getCode(), projection ); + // Calculate the new resolution in the new projection const newProjection = getProjection(projection); const oldResolution = oldView.getResolution(); const oldMPU = oldView.getProjection().getMetersPerUnit(); @@ -169,6 +263,7 @@ export function setProjectionMethod(projection, oldProjection, EOxMap) { const newResolution = (oldResolution * oldPointResolution) / newPointResolution; + // Create a new view using the new projection and properties const newView = new View({ zoom: oldView.getZoom(), center: newCenter, @@ -176,6 +271,8 @@ export function setProjectionMethod(projection, oldProjection, EOxMap) { rotation: oldView.getRotation(), projection, }); + + // Transfer existing event listeners from the old view to the new view const eventTypes = [ "change:center", "change:resolution", @@ -192,24 +289,37 @@ export function setProjectionMethod(projection, oldProjection, EOxMap) { } } }); + + // Set the new view on the map and refresh vector layers EOxMap.map.setView(newView); EOxMap.getFlatLayersArray(EOxMap.map.getLayers().getArray()) .filter((l) => l instanceof VectorLayer) .forEach((l) => l.getSource().refresh()); + + // Update the projection and center properties newProj = projection; EOxMap.center = newCenter; } + // Return the new projection code return newProj; } +/** + * Synchronizes the map view with another map element. + * + * @param {string} sync - The ID of the map element to synchronize with. + * @param {import("../../main").EOxMap} EOxMap - The map object to sync. + * @returns {string} - The sync parameter. + */ export function setSyncMethod(sync, EOxMap) { if (sync) { + // Use a timeout to ensure the target map is ready before syncing views setTimeout(() => { const originMap = getElement(sync); - if (originMap) { - EOxMap.map.setView(originMap.map.getView()); - } + + // Set the view of the current map to match the view of the origin map + if (originMap) EOxMap.map.setView(originMap.map.getView()); }); } diff --git a/elements/map/src/plugins/advancedLayersAndSources/formats.js b/elements/map/src/plugins/advancedLayersAndSources/formats.js index 7f0ce7280..f639d198e 100644 --- a/elements/map/src/plugins/advancedLayersAndSources/formats.js +++ b/elements/map/src/plugins/advancedLayersAndSources/formats.js @@ -1,3 +1,5 @@ import * as olFormats from "ol/format"; +// Attach all OpenLayers formats to the global `window` object under `eoxMapAdvancedOlFormats`. +// This makes the formats accessible throughout the application and allows dynamic use of different OpenLayers formats. window.eoxMapAdvancedOlFormats = olFormats; diff --git a/elements/map/src/plugins/advancedLayersAndSources/layers.js b/elements/map/src/plugins/advancedLayersAndSources/layers.js index 0b3900ebd..6e60ac9fb 100644 --- a/elements/map/src/plugins/advancedLayersAndSources/layers.js +++ b/elements/map/src/plugins/advancedLayersAndSources/layers.js @@ -3,8 +3,13 @@ import STAC from "ol-stac"; import { register } from "ol/proj/proj4"; import proj4 from "proj4"; -register(proj4); // required to support source reprojection +// Register proj4 with OpenLayers to support reprojection of map sources. +// This is necessary for supporting coordinate systems other than the default. +register(proj4); +// Extend the global window object to include advanced OpenLayers layers. +// This includes all standard OpenLayers layers (imported as `olLayers`) and the custom `STAC` layer. +// This setup allows for dynamic use of these layers across the application. window.eoxMapAdvancedOlLayers = { ...olLayers, STAC, diff --git a/elements/map/src/plugins/advancedLayersAndSources/sources.js b/elements/map/src/plugins/advancedLayersAndSources/sources.js index 796bce202..032d9185a 100644 --- a/elements/map/src/plugins/advancedLayersAndSources/sources.js +++ b/elements/map/src/plugins/advancedLayersAndSources/sources.js @@ -1,6 +1,9 @@ import * as olSources from "ol/source"; import WMTSCapabilities from "../../custom/sources/WMTSCapabilities"; +// Extend the global window object to include advanced OpenLayers sources. +// This includes all standard OpenLayers sources (imported as `olSources`) and a custom `WMTSCapabilities` source. +// This setup allows for dynamic use of these sources across the application. window.eoxMapAdvancedOlSources = { ...olSources, WMTSCapabilities, From 8220292701e60919c7897968635e1d28ece62b6d Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Thu, 19 Sep 2024 15:49:45 +0530 Subject: [PATCH 18/24] refactor: Added comments to components --- elements/map/src/components/compare.js | 19 ++ elements/map/src/components/tooltip.js | 13 + elements/map/src/main.js | 321 ++++++++++++++++++++++++- 3 files changed, 344 insertions(+), 9 deletions(-) diff --git a/elements/map/src/components/compare.js b/elements/map/src/components/compare.js index 07c29eb21..222dfb37d 100644 --- a/elements/map/src/components/compare.js +++ b/elements/map/src/components/compare.js @@ -2,6 +2,10 @@ import { html } from "lit"; import { choose } from "lit/directives/choose.js"; import { TemplateElement } from "../../../../utils/templateElement"; +/** + * A custom element for comparing two map layers or images using a slider. + * This component creates a split-screen view with an adjustable slider to compare two images or layers. + */ export class EOxMapCompare extends TemplateElement { static get properties() { return { @@ -12,12 +16,26 @@ export class EOxMapCompare extends TemplateElement { constructor() { super(); + + /** + * The current position of the comparison slider (0 to 100). + * @type {Number} + */ this.value = 50; + + /** + * Whether the comparison is enabled and which part is visible. + * Accepts values: "first", "second", or "true" (default). + * @type {String} + */ this.enabled = "true"; } /** + * Handles input events on the slider to update the comparison value. + * * @param {Event & {target: T }} evt - input event + * @private **/ #handleInput(evt) { this.value = parseInt(evt.target.value); @@ -144,4 +162,5 @@ export class EOxMapCompare extends TemplateElement { } } +// Define the custom element customElements.define("eox-map-compare", EOxMapCompare); diff --git a/elements/map/src/components/tooltip.js b/elements/map/src/components/tooltip.js index 22a7cce3d..f828e33cb 100644 --- a/elements/map/src/components/tooltip.js +++ b/elements/map/src/components/tooltip.js @@ -1,6 +1,10 @@ import { html, render } from "lit"; import { TemplateElement } from "../../../../utils/templateElement"; +/** + * A custom element that serves as a tooltip for the map. + * Displays information about map features in a styled format. + */ export class EOxMapTooltip extends TemplateElement { static get properties() { return { @@ -10,13 +14,21 @@ export class EOxMapTooltip extends TemplateElement { constructor() { super(); + + /** + * A function to transform properties before rendering. + * + * @type {Function} + */ this.propertyTransform = (property, _feature) => property; } renderContent(feature) { render( + // Check if a custom template named "properties" is available this.hasTemplate("properties") ? html`${this.renderTemplate( + // Render the custom template "properties", feature.getProperties(), // `tooltip-${this.content.id}` @@ -52,4 +64,5 @@ export class EOxMapTooltip extends TemplateElement { } } +// Define the custom element customElements.define("eox-map-tooltip", EOxMapTooltip); diff --git a/elements/map/src/main.js b/elements/map/src/main.js index 47c6c05e0..1c5faa159 100644 --- a/elements/map/src/main.js +++ b/elements/map/src/main.js @@ -1,6 +1,6 @@ import { LitElement, html } from "lit"; -import Map from "ol/Map.js"; -import View from "ol/View.js"; +import Map from "ol/Map"; +import View from "ol/View"; import olCss from "ol/ol.css?inline"; import controlCss from "./controls/controls.css?inline"; import { buffer } from "ol/extent"; @@ -34,7 +34,50 @@ import { firstUpdatedMethod, } from "./methods/map/"; +/** + * @typedef {import("../types").EoxLayer} EoxLayer + * */ + +/** + * The `eox-map` is a wrapper for the library [OpenLayers](https://openlayers.org/) with additional features and helper functions. + * + * Basic usage: + * + * ``` + * import "@eox/map" + * + * + * ``` + * + * Some basic layers, sources and formats are included in the default bundle, for advanced usage it is + * required to import the `advanced-layers-and-sources` plugin. + * + * Included in the base bundle: + * - Formats: `GeoJSON`, `MVT` + * - Layers: `Group`, `Image`, `Tile`, `Vector`, `VectorTile` + * - Sources: `ImageWMS`, `OSM`, `Tile`, `TileWMS`, `Vector`, `VectorTile`, `WMTS`, `XYZ` + * + * In order to use the rest of the layers and sources provided by OpenLayers, import the plugin as well: + * + * ``` + * import "@eox/map/dist/eox-map-advanced-layers-and-sources.js" + * import "@eox/map/dist/eox-map.js" + * + * + * ``` + * Included in the advanced plugin bundle: + * - Layers: + * - All OpenLayers layer types + * - [`STAC`](https://github.com/m-mohr/ol-stac) + * - Sources: + * - All OpenLayers source types + * - [`WMTSCapabilities`](https://github.com/EOX-A/EOxElements/tree/main/elements/map/src/custom/sources/WMTSCapabilities.ts) + * - Reprojection through [proj4](https://github.com/proj4js/proj4js) + * + * @element eox-map + */ export class EOxMap extends LitElement { + // Define static properties for the component static get properties() { return { center: { attribute: false, type: Array }, @@ -50,18 +93,97 @@ export class EOxMap extends LitElement { }; } - #zoomExtent = undefined; - #controls = undefined; - #layers = undefined; - #preventScroll = undefined; - #config = undefined; - #animationOptions = undefined; - #sync = undefined; + /** + * The current zoom extent of the map. + * This is used to define the maximum and minimum zoom levels. + * + * @type {Array} + * @private + */ + #zoomExtent; + + /** + * Stores the controls applied to the map, such as zoom and navigation tools. + * + * @type {Object} + * @private + */ + #controls; + + /** + * The array of layers currently added to the map. + * + * @type {Array} + * @private + */ + #layers; + + /** + * Indicates whether the map's scroll interactions (zooming, panning) are prevented. + * + * @type {Boolean} + * @private + */ + #preventScroll; + + /** + * Holds the configuration object for initializing and managing the map's settings. + * + * @type {Object} + * @private + */ + #config; + + /** + * Options for animating changes to the map's view, such as panning or zooming. + * + * @type {Object} + * @private + */ + #animationOptions; + + /** + * Represents the sync state of the map with another map instance. + * + * @type {String} + * @private + */ + #sync; + + /** + * The current center coordinates of the map. + * Stored as an array of two numbers representing the x and y coordinates. + * + * @type {Array} + * @private + */ #center = [0, 0]; + + /** + * The current zoom level of the map. + * + * @type {number} + * @private + */ #zoom = 0; + + /** + * The map's projection system, specifying how coordinates are mapped on the globe. + * Defaults to "EPSG:3857". + * + * @type {string} + * @private + */ #projection = "EPSG:3857"; + constructor() { super(); + + /** + * The OpenLayers map instance. + * + * @type {import("ol/Map").default} + */ this.map = new Map({ controls: [], layers: [], @@ -71,11 +193,34 @@ export class EOxMap extends LitElement { projection: this.projection, }), }); + + /** + * Object to store various map interactions (e.g., drag, zoom). + * + * @type {Object.} + */ this.interactions = {}; + + /** + * Object to store selection interactions for the map. + * + * @type {Object.} + */ this.selectInteractions = {}; + + /** + * Object to store map controls (e.g., custom buttons, geolocation). + * + * @type {Object.} + */ this.mapControls = {}; } + /** + * Sets the center of the map. If the new center is valid, updates the map's view. + * + * @param {Array} center - The new center coordinates [x, y]. + */ set center(center) { const newCenter = setCenterMethod(center, this); if (newCenter !== undefined) { @@ -84,128 +229,286 @@ export class EOxMap extends LitElement { } } + /** + * Gets the current center coordinates of the map. + * + * @returns {Array} The current center of the map. + */ get center() { return this.#center; } + /** + * Gets the current center of the map in longitude and latitude. + * + * @returns {Array} The geographic center [longitude, latitude]. + */ get lonLatCenter() { return getLonLatCenterMethod(this); } + /** + * Gets the current extent of the map in longitude and latitude. + * + * @returns {Array} The geographic extent [minLon, minLat, maxLon, maxLat]. + */ get lonLatExtent() { return getLonLatExtentMethod(this); } + /** + * Sets the zoom level of the map and animates the change. + * + * @param {number} zoom - The new zoom level. + */ set zoom(zoom) { if (zoom === undefined) return; this.#zoom = zoom; animateToStateMethod(this); } + /** + * Gets the current zoom level of the map. + * + * @returns {number} The current zoom level. + */ get zoom() { return this.#zoom; } + /** + * Sets the zoom extent of the map. + * + * @param {Array} extent - The new zoom extent. + */ set zoomExtent(extent) { this.#zoomExtent = setZoomExtentMethod(extent, this); } + /** + * Sets the controls for the map. + * + * @param {Object} controls - An array of control configurations. + */ set controls(controls) { this.#controls = setControlsMethod(controls, this.#controls, this); } + /** + * Gets the current map controls. + * + * @returns {Object} The current controls applied to the map. + */ get controls() { return this.#controls; } + /** + * Sets the layers for the map. + * + * @param {Array} layers - An array of layer configurations. + */ set layers(layers) { this.#layers = setLayersMethod(layers, this.#layers, this); } + /** + * Gets the current layers of the map. + * + * @returns {Array} The current layers applied to the map. + */ get layers() { return this.#layers; } + /** + * Enables or disables scroll interactions on the map. + * + * @param {Boolean} preventScroll - Whether to prevent scroll interactions. + */ set preventScroll(preventScroll) { this.#preventScroll = setPreventScrollMethod(preventScroll, this); } + /** + * Gets the current scroll interaction state. + * + * @returns {Boolean} `true` if scroll interactions are prevented, `false` otherwise. + */ get preventScroll() { return this.#preventScroll; } + /** + * Sets the configuration for the map. + * + * @param {Object} config - The configuration object. + */ set config(config) { this.#config = setConfigMethod(config, this); } + /** + * Gets the current configuration of the map. + * + * @returns {Object} The map's configuration object. + */ get config() { return this.#config; } + /** + * Sets animation options for map view changes. + * + * @param {Object} animationOptions - The animation options. + */ set animationOptions(animationOptions) { this.#animationOptions = animationOptions; } + /** + * Gets the current animation options. + * + * @returns {Object} The current animation options for the map. + */ get animationOptions() { return this.#animationOptions; } + /** + * Sets the map's projection. + * + * @param {string} projection - The projection code (e.g., "EPSG:3857"). + */ set projection(projection) { this.#projection = setProjectionMethod(projection, this.#projection, this); } + /** + * Gets the current map projection. + * + * @returns {string} The map's projection code. + */ get projection() { return this.#projection || "EPSG:3857"; } + /** + * Sets the sync state for the map. + * + * @param {string} sync - The ID of the map to sync with. + */ set sync(sync) { this.#sync = setSyncMethod(sync, this); } + /** + * Gets the current sync state of the map. + * + * @returns {string} The ID of the map that this map is synced with. + */ get sync() { return this.#sync; } + /** + * Adds or updates a layer on the map. + * + * @param {Object} json - The layer configuration in JSON format. + * @returns {Object} The added or updated layer. + */ addOrUpdateLayer(json) { return addOrUpdateLayerMethod(json, this); } + /** + * Removes an interaction from the map by its ID. + * + * @param {string} id - The ID of the interaction to remove. + */ removeInteraction(id) { removeInteractionMethod(id, this); } + /** + * Removes a select interaction from the map by its ID. + * + * @param {string} id - The ID of the select interaction to remove. + */ removeSelect(id) { removeSelectMethod(id, this); } + /** + * Removes a control from the map by its ID. + * + * @param {string} id - The ID of the control to remove. + */ removeControl(id) { removeControlMethod(id, this); } + /** + * Retrieves a layer from the map by its ID. + * + * @param {string} layerId - The ID of the layer to retrieve. + * @returns {Object} The layer object. + */ getLayerById(layerId) { return getLayerById(this, layerId); } + /** + * Lifecycle method called after the component's first update. + * Sets up initial configurations like zoom extent. + */ firstUpdated() { firstUpdatedMethod(this.#zoomExtent, this); } + /** + * Parses a feature from the input data. + * + * @type {Function} + */ parseFeature = parseFeature; + /** + * Parses text into a feature. + */ parseTextToFeature = parseTextToFeature; + /** + * Registers a projection from an EPSG code. + */ registerProjectionFromCode = registerProjectionFromCode; + /** + * Registers a custom projection. + */ registerProjection = registerProjection; + /** + * Retrieves all layers in a flat array. + */ getFlatLayersArray = getFlatLayersArray; + /** + * Transforms coordinates between different projections. + */ transform = transform; + /** + * Transforms the extent between different projections. + */ transformExtent = transformExtent; + /** + * Applies a buffer around an extent. + */ buffer = buffer; + // Renders the component's HTML template render() { return html` -
- - -
- `, -}; - -/** - * To compare two maps, wrap them in an `` element and assign them to the slot `first` and `second`. - * Also use the `sync` attribute so both move their view together. - * - * `eox-map-compare` also takes a `value` property (0 - 100) which determines the position of the comparison slider; - * and an `enabled` attribute, which can be either `"first"` (only left map visible), `"second"` (only second map visible) - * or `undefined` (default, both visible). - */ -export const ABCompare = { - args: { - layersA: [{ type: "Tile", source: { type: "OSM" } }], - layersB: [ - { - type: "Tile", - source: { - type: "TileWMS", - url: "https://services.sentinel-hub.com/ogc/wms/0635c213-17a1-48ee-aef7-9d1731695a54", - params: { - LAYERS: "AWS_VIS_WIND_V_10M", - }, - }, - }, - ], - }, - render: (args) => html` - - - - - - - - - `, -}; - -export const ConfigObject = { - args: { - config: { - controls: { - Zoom: {}, - }, - layers: [{ type: "Tile", source: { type: "OSM" } }], - view: { - center: [16.8, 48.2], - zoom: 9, - }, - }, - }, - render: (args) => html` - - `, -}; - -/** - * The projection of the view can be changed via the `projection`-attribute. - * Out-of-the-box the projections EPSG:3857 (default) and EPSG:4326 (geographic coordinates) - * are included, additional projections can be used by registering them via the `registerProjection` or - * `registerProjectionFromCode` helper functions beforehand. - */ -export const Projection = { - args: { - layers: [ - { - type: "Vector", - source: { - type: "Vector", - url: "https://openlayers.org/data/vector/ecoregions.json", - format: "GeoJSON", - }, - }, - { - type: "Tile", - properties: { - id: "osm", - title: "Background", - }, - source: { type: "OSM" }, - }, - ], - center: [16.8, 48.2], - zoom: 7, - }, - render: (args) => html` - - - - `, -}; - -/** - * With the convenience functions `transform` and `transformExtent` it is possible to transform coordinates - * and extents from any projection to EPSG.4326 (default) or any other projection. - * Basically, these methods are the `ol/proj` [transform](https://openlayers.org/en/latest/apidoc/module-ol_proj.html#.transform) - * and [transformExtent](https://openlayers.org/en/latest/apidoc/module-ol_proj.html#.transformExtent) functions, - * with the small adaptation that the destination defaults to EPSG:4326 if not defined. - */ -export const ProjectionTransform = { - args: { - layers: [ - { - type: "Tile", - properties: { - id: "osm", - title: "Background", - }, - source: { type: "OSM" }, - }, - ], - center: [16.8, 48.2], - zoom: 7, - }, - render: (args) => html` - - - `, -}; - -/** - * changing the properties `zoom`, `center` or `zoomExtent` will trigger animations, if the - * `animationOptions`-property is set. - * animation options for `zoom` or `center`: https://openlayers.org/en/latest/apidoc/module-ol_View.html#~AnimationOptions - * animation options for `zoomExtent`: https://openlayers.org/en/latest/apidoc/module-ol_View.html#~FitOptions - */ -export const Animations = { - args: { - layers: [ - { - type: "Tile", - properties: { - id: "osm", - title: "Background", - }, - source: { type: "OSM" }, - }, - ], - animationOptions: { - duration: 500, - }, - center: [16.8, 48.2], - zoom: 7, - }, - render: (args) => html` - - - - - `, -}; - -/** - * By setting the `prevent-scroll` attribute or by setting `preventScroll` property to `true` (either on the element or within the config object), - * the map doesnt mouse-scroll (on desktop) or drag-touch (on tab/mobile). Pressing the platform modifier key (ctrl/cmd) will enable scrolling. - * Useful for maps embedded in scrollable websites. - */ -export const PreventScroll = { - args: { - config: { - controls: { - Zoom: {}, - }, - layers: [{ type: "Tile", source: { type: "OSM" } }], - view: { - center: [16.8, 48.2], - zoom: 9, - }, - preventScroll: true, - }, - }, - render: (args) => html` - - `, -}; diff --git a/elements/map/src/helpers/select.js b/elements/map/src/helpers/select.js index 1574aca9e..bf4127058 100644 --- a/elements/map/src/helpers/select.js +++ b/elements/map/src/helpers/select.js @@ -239,7 +239,7 @@ export class EOxSelectInteraction { /** * Highlights features by their IDs and optionally pans into their extent. - * @param {Array} ids - Array of feature IDs to highlight. + * @param {Array} ids - Array of feature IDs to highlight. * @param {Object} [fitOptions] - Options for panning into the highlighted features. */ highlightById(ids, fitOptions) { diff --git a/elements/map/stories/ab-compare.js b/elements/map/stories/ab-compare.js new file mode 100644 index 000000000..80c813aea --- /dev/null +++ b/elements/map/stories/ab-compare.js @@ -0,0 +1,65 @@ +import { html } from "lit"; + +/** + * To compare two maps, wrap them in an `` element and assign them to the slot `first` and `second`. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const ABCompareStory = { + args: { + layersA: [{ type: "Tile", source: { type: "OSM" } }], + layersB: [ + { + type: "Tile", + source: { + type: "TileWMS", + url: "https://services.sentinel-hub.com/ogc/wms/0635c213-17a1-48ee-aef7-9d1731695a54", + params: { + LAYERS: "AWS_VIS_WIND_V_10M", + }, + }, + }, + ], + }, + render: /** @param {Object.} args **/ (args) => html` + + + + + + + + + `, +}; + +export default ABCompareStory; diff --git a/elements/map/stories/animations.js b/elements/map/stories/animations.js new file mode 100644 index 000000000..bb880c279 --- /dev/null +++ b/elements/map/stories/animations.js @@ -0,0 +1,59 @@ +import { html } from "lit"; + +/** + * Changing the properties `zoom`, `center` or `zoomExtent` will trigger animations + * + * @returns {Object} The story configuration with arguments for the component. + */ +const AnimationsStory = { + args: { + layers: [ + { + type: "Tile", + properties: { + id: "osm", + title: "Background", + }, + source: { type: "OSM" }, + }, + ], + animationOptions: { + duration: 500, + }, + center: [16.8, 48.2], + zoom: 7, + }, + render: /** @param {Object.} args **/ (args) => html` + + + + + `, +}; + +export default AnimationsStory; diff --git a/elements/map/stories/click-select.js b/elements/map/stories/click-select.js new file mode 100644 index 000000000..a89315d7a --- /dev/null +++ b/elements/map/stories/click-select.js @@ -0,0 +1,44 @@ +/** + * Renders `eox-map` with `Click` interaction + * + * @returns {Object} The story configuration with arguments for the component. + */ +const ClickSelectStory = { + args: { + layers: [ + { + type: "Vector", + background: "#1366dd", + properties: { + id: "regions", + }, + source: { + type: "Vector", + url: "https://openlayers.org/data/vector/ecoregions.json", + format: "GeoJSON", + attributions: "Regions: @ openlayers.org", + }, + style: { + "stroke-color": "black", + "stroke-width": 1, + "fill-color": "red", + }, + interactions: [ + { + type: "select", + options: { + id: "selectInteraction", + condition: "click", + style: { + "stroke-color": "white", + "stroke-width": 3, + }, + }, + }, + ], + }, + ], + }, +}; + +export default ClickSelectStory; diff --git a/elements/map/stories/config-object.js b/elements/map/stories/config-object.js new file mode 100644 index 000000000..ffe8b3bcd --- /dev/null +++ b/elements/map/stories/config-object.js @@ -0,0 +1,29 @@ +import { html } from "lit"; + +/** + * A single config object for all the attribute and properties + * + * @returns {Object} The story configuration with arguments for the component. + */ +const ConfigObjectStory = { + args: { + config: { + controls: { + Zoom: {}, + }, + layers: [{ type: "Tile", source: { type: "OSM" } }], + view: { + center: [16.8, 48.2], + zoom: 9, + }, + }, + }, + render: /** @param {Object.} args **/ (args) => html` + + `, +}; + +export default ConfigObjectStory; diff --git a/elements/map/stories/controls.js b/elements/map/stories/controls.js new file mode 100644 index 000000000..7bd22043d --- /dev/null +++ b/elements/map/stories/controls.js @@ -0,0 +1,40 @@ +/** + * Renders different `Controls` for the `eox-map` using control config + * + * @returns {Object} The story configuration with arguments for the component. + */ +const ControlsStory = { + args: { + controls: { + Zoom: {}, + Attribution: {}, + FullScreen: {}, + OverviewMap: { + layers: [ + { + type: "Tile", + properties: { + id: "overviewMap", + }, + source: { + type: "OSM", + }, + }, + ], + }, + }, + layers: [ + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "OSM", + }, + }, + ], + }, +}; + +export default ControlsStory; diff --git a/elements/map/stories/custom-full-screen-loading-indicator.js b/elements/map/stories/custom-full-screen-loading-indicator.js new file mode 100644 index 000000000..4f44564fe --- /dev/null +++ b/elements/map/stories/custom-full-screen-loading-indicator.js @@ -0,0 +1,35 @@ +/** + * Loading Indicators can also be centered over the entire map by setting the option `type` to `'fullscreen'`, adapting the opacity is adviced when doing so. + * Custom rotating SVG-Icons can be used by setting the svg data as the `spinnerSvg`-option. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const CustomFullScreenLoadingIndicatorStory = { + args: { + zoom: 9, + center: [0, 51.5], + controls: { + LoadingIndicator: { + type: "fullscreen", + opacity: 0.2, + spinnerSvg: ` + + `, + }, + Zoom: {}, + }, + layers: [ + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "OSM", + }, + }, + ], + }, +}; + +export default CustomFullScreenLoadingIndicatorStory; diff --git a/elements/map/stories/geo-location.js b/elements/map/stories/geo-location.js new file mode 100644 index 000000000..9ee5d7321 --- /dev/null +++ b/elements/map/stories/geo-location.js @@ -0,0 +1,41 @@ +/** + * Renders Geolocation Control in `eox-map` + * + * @returns {Object} The story configuration with arguments for the component. + */ +const GeolocationStory = { + args: { + zoom: 7, + controls: { + Geolocation: { + tracking: true, + trackHeading: true, + centerWhenReady: false, + highAccuracy: true, + trackAccuracy: true, + style: { + "circle-radius": 10, + "circle-fill-color": "red", + "circle-stroke-color": "white", + "circle-stroke-width": 2, + }, + buttonIcon: + "https://upload.wikimedia.org/wikipedia/commons/7/74/Location_icon_from_Noun_Project.png", + }, + Zoom: {}, + }, + layers: [ + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "OSM", + }, + }, + ], + }, +}; + +export default GeolocationStory; diff --git a/elements/map/stories/geo-tiff-layer.js b/elements/map/stories/geo-tiff-layer.js new file mode 100644 index 000000000..a1eaea3b8 --- /dev/null +++ b/elements/map/stories/geo-tiff-layer.js @@ -0,0 +1,38 @@ +/** + * Renders `GeoTIFF` layer as `WebGLTile` + * + * @returns {Object} The story configuration with arguments for the component. + */ +const GeoTIFFLayerStory = { + args: { + center: [5, 16.3], + layers: [ + { + type: "WebGLTile", + properties: { + id: "geotiffLayer", + }, + source: { + type: "GeoTIFF", + sources: [ + { + url: "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/36/Q/WD/2020/7/S2A_36QWD_20200701_0_L2A/TCI.tif", + }, + ], + }, + }, + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "OSM", + }, + }, + ], + zoom: 8, + }, +}; + +export default GeoTIFFLayerStory; diff --git a/elements/map/stories/group-layer.js b/elements/map/stories/group-layer.js new file mode 100644 index 000000000..e159d90e1 --- /dev/null +++ b/elements/map/stories/group-layer.js @@ -0,0 +1,49 @@ +/** + * Renders `Group` layer which contains multiple layers in a group + * + * @returns {Object} The story configuration with arguments for the component. + */ +const GroupLayerStory = { + args: { + layers: [ + { + type: "Group", + properties: { + id: "group", + }, + layers: [ + { + type: "Vector", + properties: { + id: "regions", + }, + source: { + type: "Vector", + url: "https://openlayers.org/data/vector/ecoregions.json", + format: "GeoJSON", + }, + }, + { + type: "Group", + properties: { + id: "groupLayerInsideGroup", + }, + layers: [ + { + type: "Tile", + properties: { + id: "layerInsideGroupInsideGroup", + }, + source: { + type: "OSM", + }, + }, + ], + }, + ], + }, + ], + }, +}; + +export default GroupLayerStory; diff --git a/elements/map/stories/highlight-features-and-animate.js b/elements/map/stories/highlight-features-and-animate.js new file mode 100644 index 000000000..62cd48372 --- /dev/null +++ b/elements/map/stories/highlight-features-and-animate.js @@ -0,0 +1,65 @@ +import { html } from "lit"; + +/** + * Renders `Select` with `animation` to the selection. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const HighlightFeaturesAndAnimateStory = { + args: { + config: { + layers: [ + { + type: "Vector", + background: "lightgrey", + properties: { + id: "regions", + }, + source: { + type: "Vector", + url: "https://openlayers.org/data/vector/ecoregions.json", + format: "GeoJSON", + attributions: "Regions: @ openlayers.org", + }, + style: { + "stroke-color": "black", + "stroke-width": 1, + "fill-color": "darkgrey", + }, + interactions: [ + { + type: "select", + options: { + id: "selectInteraction", + condition: "click", + style: { + "stroke-color": "white", + "stroke-width": 3, + }, + }, + }, + ], + }, + ], + }, + }, + render: /** @param {Object.} args **/ (args) => html` + { + /** @type {import("../src/main").EOxMap} **/ ( + /** @type {any} **/ document.querySelector( + "eox-map#highlightAndAnimate" + ) + ).selectInteractions.selectInteraction.highlightById([664, 795, 789], { + duration: 400, + padding: [50, 50, 50, 50], + }); + }} + > + `, +}; + +export default HighlightFeaturesAndAnimateStory; diff --git a/elements/map/stories/hover-select.js b/elements/map/stories/hover-select.js new file mode 100644 index 000000000..d47f03014 --- /dev/null +++ b/elements/map/stories/hover-select.js @@ -0,0 +1,43 @@ +/** + * Renders `eox-map` with `Hover` interaction + * + * @returns {Object} The story configuration with arguments for the component. + */ +const HoverSelectStory = { + args: { + layers: [ + { + type: "Vector", + source: { + type: "Vector", + url: "https://openlayers.org/data/vector/ecoregions.json", + format: "GeoJSON", + }, + interactions: [ + { + type: "select", + options: { + id: "selectInteraction", + condition: "pointermove", + layer: { + type: "Vector", + properties: { + id: "selectLayer", + }, + source: { + type: "Vector", + }, + style: { + "stroke-color": "red", + "stroke-width": 3, + }, + }, + }, + }, + ], + }, + ], + }, +}; + +export default HoverSelectStory; diff --git a/elements/map/stories/index.js b/elements/map/stories/index.js new file mode 100644 index 000000000..e2e6d771e --- /dev/null +++ b/elements/map/stories/index.js @@ -0,0 +1,25 @@ +export { default as PrimaryStory } from "./primary"; +export { default as VectorLayerStory } from "./vector-layer"; +export { default as VectorTileLayerStory } from "./vector-tile-layer"; +export { default as WMSLayerStory } from "./wms-layer"; +export { default as WMTSCapabilitiesLayerStory } from "./wmts-capabilities-layer"; +export { default as WMTSTileGridStory } from "./wmts-tile-grid"; +export { default as STACLayerStory } from "./stac-layer"; +export { default as GeoTIFFLayerStory } from "./geo-tiff-layer"; +export { default as GroupLayerStory } from "./group-layer"; +export { default as ControlsStory } from "./controls"; +export { default as GeolocationStory } from "./geo-location"; +export { default as StandardLoadingIndicatorStory } from "./standard-loading-indicator"; +export { default as CustomFullScreenLoadingIndicatorStory } from "./custom-full-screen-loading-indicator"; +export { default as HoverSelectStory } from "./hover-select"; +export { default as ClickSelectStory } from "./click-select"; +export { default as TooltipStory } from "./tooltip"; +export { default as TooltipWithPropertyTransformStory } from "./tooltip-with-property-transform"; +export { default as HighlightFeaturesAndAnimateStory } from "./highlight-features-and-animate"; +export { default as MapSyncStory } from "./map-sync"; +export { default as ABCompareStory } from "./ab-compare"; +export { default as ConfigObjectStory } from "./config-object"; +export { default as ProjectionStory } from "./projection"; +export { default as ProjectionTransformStory } from "./projection-transform"; +export { default as AnimationsStory } from "./animations"; +export { default as PreventScrollStory } from "./prevent-scroll"; diff --git a/elements/map/stories/map-sync.js b/elements/map/stories/map-sync.js new file mode 100644 index 000000000..eb72ebcf7 --- /dev/null +++ b/elements/map/stories/map-sync.js @@ -0,0 +1,39 @@ +import { html } from "lit"; + +/** + * Sync the views of two maps using the `sync` attribute (e.g. `sync="eox-map#a"`). + * + * @returns {Object} The story configuration with arguments for the component. + */ +const MapSyncStory = { + args: { + layers: [ + { + type: "Tile", + properties: { + id: "osm", + title: "Background", + }, + source: { type: "OSM" }, + }, + ], + }, + render: /** @param {Object.} args **/ (args) => html` + +
+ + +
+ `, +}; + +export default MapSyncStory; diff --git a/elements/map/stories/map.stories.js b/elements/map/stories/map.stories.js new file mode 100644 index 000000000..3798ea4c7 --- /dev/null +++ b/elements/map/stories/map.stories.js @@ -0,0 +1,216 @@ +// Global import of eox-elements in .storybook/preview.js! +import { html } from "lit"; +import "../src/main.js"; +import { + PrimaryStory, + VectorLayerStory, + VectorTileLayerStory, + WMSLayerStory, + WMTSCapabilitiesLayerStory, + WMTSTileGridStory, + STACLayerStory, + GeoTIFFLayerStory, + GroupLayerStory, + ControlsStory, + GeolocationStory, + StandardLoadingIndicatorStory, + CustomFullScreenLoadingIndicatorStory, + HoverSelectStory, + ClickSelectStory, + TooltipStory, + TooltipWithPropertyTransformStory, + HighlightFeaturesAndAnimateStory, + MapSyncStory, + ABCompareStory, + ConfigObjectStory, + ProjectionStory, + ProjectionTransformStory, + AnimationsStory, + PreventScrollStory, +} from "./index.js"; + +export default { + title: "Elements/eox-map", + tags: ["autodocs"], + component: "eox-map", + render: /** @param {Object.} args **/ (args) => html` + + + `, +}; + +/** + * Basic map rendered using `eox-map` configuration + */ +export const Primary = PrimaryStory; + +/** + * Basic vector layer map rendered using `GeoJSON` + */ +export const VectorLayer = VectorLayerStory; + +/** + * Basic vector layer map rendered using `MVT` tiles + */ +export const VectorTileLayer = VectorTileLayerStory; + +/** + * Renders WMS layer using geo-server + */ +export const WMSLayer = WMSLayerStory; + +/** + * Renders `WMTSCapabilities` layer and fetches the provided capabilities url + */ +export const WMTSCapabilitiesLayer = WMTSCapabilitiesLayerStory; + +/** + * Renders `WMTSCapabilities` layer and fetches the provided capabilities url + */ +export const WMTSTileGrid = WMTSTileGridStory; + +/** + * Renders STAC Layer using STAC url json. + */ +export const STACLayer = STACLayerStory; + +/** + * Renders `GeoTIFF` layer as `WebGLTile` + */ +export const GeoTIFFLayer = GeoTIFFLayerStory; + +/** + * Renders `Group` layer which contains multiple layers in a group + */ +export const GroupLayer = GroupLayerStory; + +/** + * Renders different `Controls` for the `eox-map` using control config + */ +export const Controls = ControlsStory; + +/** + * Renders Geolocation Control in `eox-map` + */ +export const Geolocation = GeolocationStory; + +/** + * A simple, unobtrusive loading indicator in the bottom left that appears any time when the map is loading. + */ +export const StandardLoadingIndicator = StandardLoadingIndicatorStory; + +/** + * Loading Indicators can also be centered over the entire map by setting the option `type` to `'fullscreen'`, adapting the opacity is adviced when doing so. + */ +export const CustomFullScreenLoadingIndicator = + CustomFullScreenLoadingIndicatorStory; + +/** + * Renders `eox-map` with `Hover` interaction + */ +export const HoverSelect = HoverSelectStory; + +/** + * Renders `eox-map` with `Click` interaction + */ +export const ClickSelect = ClickSelectStory; + +/** + * `eox-map` offers a built-in tooltip, which needs to be placed inside the default slot: + * ``` + * + * + * + * ``` + * This renders a list of all feature properties of the currently selected feature. + * Note that if multiple interactions are registered (e.g. `pointermove` and `singleclick`), + * the `pointermove` interaction will have higher priority for the tooltip. + */ +export const Tooltip = TooltipStory; + +/** + * The rendering of feature properties inside the tooltip can be transformed + * by passing a `propertyTransform` function to the tooltip element which applies to each property in the rendering loop: + * ``` + * + * key.includes("COLOR") ? { key: key.toLowerCase(), value }} + * > + * + * ``` + * + * The first argument are `key` and `value` of the current feature property; this object needs to be + * returned in order to render the property in the list. + * Additionally, the entire feature is passed as a second argument, for cases of more advanced property + * transformation in which needs access to the entire feature. + */ +export const TooltipWithPropertyTransform = TooltipWithPropertyTransformStory; + +/** + * Select interactions offer a `highlightById` method, with which vector features can be programmatically selected via their id property. + * It expects an array with a list of ids to be selected. + * Optionally, passing a second argument allows to set the [`fitOptions`](https://openlayers.org/en/latest/apidoc/module-ol_View.html#~FitOptions), + * adding view animation to the selection. + */ +export const HighlightFeaturesAndAnimate = HighlightFeaturesAndAnimateStory; + +/** + * Sync the views of two maps using the `sync` attribute (e.g. `sync="eox-map#a"`). + */ +export const MapSync = MapSyncStory; + +/** + * To compare two maps, wrap them in an `` element and assign them to the slot `first` and `second`. + * Also use the `sync` attribute so both move their view together. + * + * `eox-map-compare` also takes a `value` property (0 - 100) which determines the position of the comparison slider; + * and an `enabled` attribute, which can be either `"first"` (only left map visible), `"second"` (only second map visible) + * or `undefined` (default, both visible). + */ +export const ABCompare = ABCompareStory; + +export const ConfigObject = ConfigObjectStory; + +/** + * The projection of the view can be changed via the `projection`-attribute. + * Out-of-the-box the projections EPSG:3857 (default) and EPSG:4326 (geographic coordinates) + * are included, additional projections can be used by registering them via the `registerProjection` or + * `registerProjectionFromCode` helper functions beforehand. + */ +export const Projection = ProjectionStory; + +/** + * With the convenience functions `transform` and `transformExtent` it is possible to transform coordinates + * and extents from any projection to EPSG.4326 (default) or any other projection. + * Basically, these methods are the `ol/proj` [transform](https://openlayers.org/en/latest/apidoc/module-ol_proj.html#.transform) + * and [transformExtent](https://openlayers.org/en/latest/apidoc/module-ol_proj.html#.transformExtent) functions, + * with the small adaptation that the destination defaults to EPSG:4326 if not defined. + */ +export const ProjectionTransform = ProjectionTransformStory; + +/** + * changing the properties `zoom`, `center` or `zoomExtent` will trigger animations, if the + * `animationOptions`-property is set. + * animation options for `zoom` or `center`: https://openlayers.org/en/latest/apidoc/module-ol_View.html#~AnimationOptions + * animation options for `zoomExtent`: https://openlayers.org/en/latest/apidoc/module-ol_View.html#~FitOptions + */ +export const Animations = AnimationsStory; + +/** + * By setting the `prevent-scroll` attribute or by setting `preventScroll` property to `true` (either on the element or within the config object), + * the map doesnt mouse-scroll (on desktop) or drag-touch (on tab/mobile). Pressing the platform modifier key (ctrl/cmd) will enable scrolling. + * Useful for maps embedded in scrollable websites. + */ +export const PreventScroll = PreventScrollStory; diff --git a/elements/map/stories/prevent-scroll.js b/elements/map/stories/prevent-scroll.js new file mode 100644 index 000000000..35c635bc5 --- /dev/null +++ b/elements/map/stories/prevent-scroll.js @@ -0,0 +1,30 @@ +import { html } from "lit"; + +/** + * Prevent `eox-map` mouse-scroll or drag-touch event while scrolling a page. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const PreventScrollStory = { + args: { + config: { + controls: { + Zoom: {}, + }, + layers: [{ type: "Tile", source: { type: "OSM" } }], + view: { + center: [16.8, 48.2], + zoom: 9, + }, + preventScroll: true, + }, + }, + render: /** @param {Object.} args **/ (args) => html` + + `, +}; + +export default PreventScrollStory; diff --git a/elements/map/stories/primary.js b/elements/map/stories/primary.js new file mode 100644 index 000000000..e9e28fd14 --- /dev/null +++ b/elements/map/stories/primary.js @@ -0,0 +1,14 @@ +/** + * Basic map rendered using `eox-map` configuration + * + * @returns {Object} The story configuration with arguments for the component. + */ +const PrimaryStory = { + args: { + center: [15, 48], + layers: [{ type: "Tile", source: { type: "OSM" } }], + zoom: 7, + }, +}; + +export default PrimaryStory; diff --git a/elements/map/stories/projection-transform.js b/elements/map/stories/projection-transform.js new file mode 100644 index 000000000..d165e7601 --- /dev/null +++ b/elements/map/stories/projection-transform.js @@ -0,0 +1,51 @@ +import { html } from "lit"; + +/** + * With the convenience functions `transform` and `transformExtent` it is possible to transform coordinates + * and extents from any projection to EPSG.4326 (default) or any other projection. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const ProjectionTransformStory = { + args: { + layers: [ + { + type: "Tile", + properties: { + id: "osm", + title: "Background", + }, + source: { type: "OSM" }, + }, + ], + center: [16.8, 48.2], + zoom: 7, + }, + render: /** @param {Object.} args **/ (args) => html` + + + `, +}; + +export default ProjectionTransformStory; diff --git a/elements/map/stories/projection.js b/elements/map/stories/projection.js new file mode 100644 index 000000000..cb5663353 --- /dev/null +++ b/elements/map/stories/projection.js @@ -0,0 +1,59 @@ +import { html } from "lit"; + +/** + * The projection of the view can be changed via the `projection`-attribute. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const ProjectionStory = { + args: { + layers: [ + { + type: "Vector", + source: { + type: "Vector", + url: "https://openlayers.org/data/vector/ecoregions.json", + format: "GeoJSON", + }, + }, + { + type: "Tile", + properties: { + id: "osm", + title: "Background", + }, + source: { type: "OSM" }, + }, + ], + center: [16.8, 48.2], + zoom: 7, + }, + render: /** @param {Object.} args **/ (args) => html` + + + + `, +}; + +export default ProjectionStory; diff --git a/elements/map/stories/stac-layer.js b/elements/map/stories/stac-layer.js new file mode 100644 index 000000000..8bf8d463c --- /dev/null +++ b/elements/map/stories/stac-layer.js @@ -0,0 +1,31 @@ +/** + * Renders STAC (SpatioTemporal Asset Catalogs) Layer using STAC url json. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const STACLayerStory = { + args: { + center: [-122.38, 46.1], + layers: [ + { + type: "STAC", + properties: { + id: "stacLayer", + }, + url: "https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/10/T/ES/2022/7/S2A_10TES_20220726_0_L2A/S2A_10TES_20220726_0_L2A.json", + }, + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "OSM", + }, + }, + ], + zoom: 7, + }, +}; + +export default STACLayerStory; diff --git a/elements/map/stories/standard-loading-indicator.js b/elements/map/stories/standard-loading-indicator.js new file mode 100644 index 000000000..0636750c7 --- /dev/null +++ b/elements/map/stories/standard-loading-indicator.js @@ -0,0 +1,28 @@ +/** + * A simple, unobtrusive loading indicator in the bottom left that appears any time when the map is loading. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const StandardLoadingIndicatorStory = { + args: { + zoom: 9, + center: [0, 51.5], + controls: { + LoadingIndicator: {}, + Zoom: {}, + }, + layers: [ + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "OSM", + }, + }, + ], + }, +}; + +export default StandardLoadingIndicatorStory; diff --git a/elements/map/stories/tooltip-with-property-transform.js b/elements/map/stories/tooltip-with-property-transform.js new file mode 100644 index 000000000..0cf399584 --- /dev/null +++ b/elements/map/stories/tooltip-with-property-transform.js @@ -0,0 +1,63 @@ +import { html } from "lit"; + +/** + * The rendering of feature properties inside the tooltip can be transformed + * + * @returns {Object} The story configuration with arguments for the component. + */ +const TooltipWithPropertyTransformStory = { + args: { + layers: [ + { + type: "Vector", + source: { + type: "Vector", + url: "https://openlayers.org/data/vector/ecoregions.json", + format: "GeoJSON", + }, + interactions: [ + { + type: "select", + options: { + id: "selectInteraction", + condition: "pointermove", + style: { + "stroke-color": "red", + "stroke-width": 3, + }, + }, + }, + ], + }, + ], + center: [15, 48], + zoom: 4, + }, + render: /** @param {Object.} args **/ (args) => html` + + { + if (param.key.includes("COLOR")) { + return { key: param.key.toLowerCase(), value: param.value }; + } + return undefined; + } + } + > + + `, +}; + +export default TooltipWithPropertyTransformStory; diff --git a/elements/map/stories/tooltip.js b/elements/map/stories/tooltip.js new file mode 100644 index 000000000..7a4d43199 --- /dev/null +++ b/elements/map/stories/tooltip.js @@ -0,0 +1,50 @@ +import { html } from "lit"; + +/** + * Renders `eox-map` with `Tooltip` hover feature + * + * @returns {Object} The story configuration with arguments for the component. + */ +const TooltipStory = { + args: { + layers: [ + { + type: "Vector", + source: { + type: "Vector", + url: "https://openlayers.org/data/vector/ecoregions.json", + format: "GeoJSON", + }, + interactions: [ + { + type: "select", + options: { + id: "selectInteraction", + condition: "pointermove", + style: { + "stroke-color": "red", + "stroke-width": 3, + }, + }, + }, + ], + }, + ], + center: [15, 48], + zoom: 4, + }, + render: /** @param {Object.} args **/ (args) => html` + + + + `, +}; + +export default TooltipStory; diff --git a/elements/map/stories/vector-layer.js b/elements/map/stories/vector-layer.js new file mode 100644 index 000000000..74c15c10b --- /dev/null +++ b/elements/map/stories/vector-layer.js @@ -0,0 +1,31 @@ +/** + * Renders basic vector layer using `GeoJSON` + * + * @returns {Object} The story configuration with arguments for the component. + */ +const VectorLayerStory = { + args: { + layers: [ + { + type: "Vector", + background: "#1366dd", + properties: { + id: "regions", + }, + source: { + type: "Vector", + url: "https://openlayers.org/data/vector/ecoregions.json", + format: "GeoJSON", + attributions: "Regions: @ openlayers.org", + }, + style: { + "stroke-color": "#232323", + "stroke-width": 1, + "fill-color": ["string", ["get", "COLOR"], "#eee"], + }, + }, + ], + }, +}; + +export default VectorLayerStory; diff --git a/elements/map/stories/vector-tile-layer.js b/elements/map/stories/vector-tile-layer.js new file mode 100644 index 000000000..72578ff78 --- /dev/null +++ b/elements/map/stories/vector-tile-layer.js @@ -0,0 +1,32 @@ +/** + * Renders basic vector layer using `MVT` tile + * + * @returns {Object} The story configuration with arguments for the component. + */ +const VectorTileLayerStory = { + args: { + layers: [ + { + type: "VectorTile", + background: "#1a2b39", + declutter: true, + properties: { + id: "countries", + }, + source: { + type: "VectorTile", + url: "https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/ne:ne_10m_admin_0_countries@EPSG%3A900913@pbf/{z}/{x}/{-y}.pbf", + format: "MVT", + tileGrid: {}, + }, + style: { + "fill-color": "yellow", + "stroke-color": "#232323", + "stroke-width": 1, + }, + }, + ], + }, +}; + +export default VectorTileLayerStory; diff --git a/elements/map/stories/wms-layer.js b/elements/map/stories/wms-layer.js new file mode 100644 index 000000000..34913b1fd --- /dev/null +++ b/elements/map/stories/wms-layer.js @@ -0,0 +1,28 @@ +/** + * Renders WMS layer using geo-server + * + * @returns {Object} The story configuration with arguments for the component. + */ +const WMSLayerStory = { + args: { + center: [-10997148, 4569099], + layers: [ + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "TileWMS", + url: "https://ahocevar.com/geoserver/wms", + params: { LAYERS: "topp:states", TILED: true }, + ratio: 1, + serverType: "geoserver", + }, + }, + ], + zoom: 3, + }, +}; + +export default WMSLayerStory; diff --git a/elements/map/stories/wmts-capabilities-layer.js b/elements/map/stories/wmts-capabilities-layer.js new file mode 100644 index 000000000..ea022d32a --- /dev/null +++ b/elements/map/stories/wmts-capabilities-layer.js @@ -0,0 +1,28 @@ +/** + * Renders `WMTSCapabilities` layer. + * A source with type `WMTSCapabilities` automatically fetches the provided capabilities url + * and renders the specified layer. + * + * @returns {Object} The story configuration with arguments for the component. + */ +const WMTSCapabilitiesLayerStory = { + args: { + center: [20, 40], + layers: [ + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "WMTSCapabilities", + url: "https://tiles.maps.eox.at/wmts/1.0.0/WMTSCapabilities.xml", + layer: "s2cloudless-2017", + }, + }, + ], + zoom: 5, + }, +}; + +export default WMTSCapabilitiesLayerStory; diff --git a/elements/map/stories/wmts-tile-grid.js b/elements/map/stories/wmts-tile-grid.js new file mode 100644 index 000000000..9244b675c --- /dev/null +++ b/elements/map/stories/wmts-tile-grid.js @@ -0,0 +1,33 @@ +/** + * Renders `WMTS` layer. + * `WMTS` data can also be accessed directly without the need of fetching the capabilities. + * A TileGrid can be defined via the `tileGrid`-property of the Source + * + * @returns {Object} The story configuration with arguments for the component. + */ +const WMTSTileGridStory = { + args: { + center: [20, 40], + layers: [ + { + type: "Tile", + properties: { + id: "customId", + }, + source: { + type: "WMTS", + url: "https://tiles.maps.eox.at/wmts", + layer: "s2cloudless-2017_3857", + style: "default", + matrixSet: "GoogleMapsCompatible", + tileGrid: { + tileSize: [128, 128], + }, + }, + }, + ], + zoom: 5, + }, +}; + +export default WMTSTileGridStory; diff --git a/elements/map/tsconfig.json b/elements/map/tsconfig.json index 6777a8f4b..eb2ac4f6a 100644 --- a/elements/map/tsconfig.json +++ b/elements/map/tsconfig.json @@ -20,5 +20,5 @@ "checkJs": true }, "include": ["**/*.ts", "../../cypress/cypress.d.ts", "**/*.js"], - "exclude": ["dist", "test", "map.stories.js"] + "exclude": ["dist", "test"] // "test" can be removed once we refactor `test` code } From e04dc6d024cd80b9cb25e81d51d3055ecc6085cb Mon Sep 17 00:00:00 2001 From: srijitcoder Date: Thu, 26 Sep 2024 16:49:35 +0530 Subject: [PATCH 23/24] fix: replace passing of template string to normal value --- elements/map/src/helpers/select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elements/map/src/helpers/select.js b/elements/map/src/helpers/select.js index bf4127058..84df40064 100644 --- a/elements/map/src/helpers/select.js +++ b/elements/map/src/helpers/select.js @@ -115,7 +115,7 @@ export class EOxSelectInteraction { (feature, resolution) => { if ( this.selectedFids.length && - this.selectedFids.includes(`${this.getId(feature)}`) + this.selectedFids.includes(this.getId(feature)) ) { return initialStyle(feature, resolution); // Apply style only if the feature is selected } From c38fba90a7c366dde7533d9b9e318e867f58c546 Mon Sep 17 00:00:00 2001 From: silvester-pari Date: Thu, 26 Sep 2024 16:45:20 +0200 Subject: [PATCH 24/24] chore: convert vite config files to js --- elements/map/package.json | 4 ++-- ...sAndSources.ts => vite.config.advancedLayersAndSources.js} | 0 elements/map/{vite.config.ts => vite.config.js} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename elements/map/{vite.config.advancedLayersAndSources.ts => vite.config.advancedLayersAndSources.js} (100%) rename elements/map/{vite.config.ts => vite.config.js} (100%) diff --git a/elements/map/package.json b/elements/map/package.json index a0c14535f..b2c12aed5 100644 --- a/elements/map/package.json +++ b/elements/map/package.json @@ -27,8 +27,8 @@ ], "main": "./dist/eox-map.js", "scripts": { - "build": "tsc && vite build --config vite.config.advancedLayersAndSources.ts && vite build --config vite.config.ts", - "watch": "tsc && vite build --config vite.config.advancedLayersAndSources.ts && vite build --config vite.config.ts --watch", + "build": "tsc && vite build --config vite.config.advancedLayersAndSources.js && vite build --config vite.config.js", + "watch": "tsc && vite build --config vite.config.advancedLayersAndSources.js && vite build --config vite.config.js --watch", "format": "prettier --write .", "lint": "eslint --ext .js,.ts .", "lint:fix": "eslint --ext .js,.ts . --fix", diff --git a/elements/map/vite.config.advancedLayersAndSources.ts b/elements/map/vite.config.advancedLayersAndSources.js similarity index 100% rename from elements/map/vite.config.advancedLayersAndSources.ts rename to elements/map/vite.config.advancedLayersAndSources.js diff --git a/elements/map/vite.config.ts b/elements/map/vite.config.js similarity index 100% rename from elements/map/vite.config.ts rename to elements/map/vite.config.js