diff --git a/cosi/AccessibilityAnalysis/components/AccessibilityAnalysis.vue b/cosi/AccessibilityAnalysis/components/AccessibilityAnalysis.vue index f1f630fdf..96b66e2fb 100644 --- a/cosi/AccessibilityAnalysis/components/AccessibilityAnalysis.vue +++ b/cosi/AccessibilityAnalysis/components/AccessibilityAnalysis.vue @@ -108,6 +108,7 @@ export default { ...mapGetters("Tools/DistrictSelector", ["boundingGeometry"]), ...mapGetters("Tools/FeaturesList", ["activeVectorLayerList", "isFeatureActive"]), ...mapGetters("Tools/AreaSelector", {areaSelectorGeom: "geometry"}), + ...mapGetters("Tools/SelectionManager", ["activeSelection"]), ...mapGetters("Tools/ScenarioBuilder", ["scenarioUpdated"]), ...mapGetters("Tools/Routing/Directions", ["directionsRouteSource", "directionsRouteLayer", "routingDirections"]), ...mapGetters("Tools/Routing", {routingActive: "active", activeRoutingToolOption: "activeRoutingToolOption"}), @@ -255,7 +256,7 @@ export default { mode () { this.setSetByFeature(false); - if (this.mode === "region") { + if (this.mode === "region" && this.activeSelection === null) { this.resetIsochroneBBox(); } @@ -494,7 +495,7 @@ export default { } this.dataSets[this.activeSet].geojson = this.exportAsGeoJson(this.mapLayer); - this.addNewSelection({selection: analysisSet.results, source: this.$t("additional:modules.tools.cosi.accessibilityAnalysis.title"), id: this._mode + ", " + this._transportType + ", [...]"}); + this.addNewSelection({selection: analysisSet.results, source: this.$t("additional:modules.tools.cosi.accessibilityAnalysis.title"), id: this.$t("additional:modules.tools.cosi.accessibilityAnalysis.transportTypes." + this._transportType) + ", " + this.$t("additional:modules.tools.cosi.accessibilityAnalysis.scaleUnits." + this._scaleUnit) + ", [...]"}); }, exportAsGeoJson, // pagination features diff --git a/cosi/AreaSelector/components/AreaSelector.vue b/cosi/AreaSelector/components/AreaSelector.vue index 04f6410c5..360b00a39 100644 --- a/cosi/AreaSelector/components/AreaSelector.vue +++ b/cosi/AreaSelector/components/AreaSelector.vue @@ -65,8 +65,12 @@ export default { geometry (geom) { this.feature = geomPickerGetFeature(this.$refs["geometry-picker"]) || new Feature({geometry: geom}); this.drawingLayer.getSource().clear(); - this.drawingLayer.getSource().addFeature(this.feature); - this.addNewSelection({selection: [new Feature(geom)], source: this.$t("additional:modules.tools.cosi.areaSelector.title"), id: "areaselector-index"}); + + if (this.feature) { + this.drawingLayer.getSource().addFeature(this.feature); + } + + this.addNewSelection({selection: [new Feature(geom)], source: this.$t("additional:modules.tools.cosi.areaSelector.title"), id: this.$t("additional:modules.tools.cosi.areaSelector.title") + " #" + new Feature(geom).ol_uid}); setBBoxToGeom.call(this, geom || this.boundingGeometry); } }, diff --git a/cosi/DistrictSelector/store/actionsDistrictSelector.js b/cosi/DistrictSelector/store/actionsDistrictSelector.js index 08ee1d23b..0dcce30df 100644 --- a/cosi/DistrictSelector/store/actionsDistrictSelector.js +++ b/cosi/DistrictSelector/store/actionsDistrictSelector.js @@ -60,7 +60,7 @@ const actions = { const referenceLevel = districtLevel.referenceLevel, // reference names of the districts refNames = districts.map(district => { - return district.statFeatures[0].get(districtLevel.referenceLevel.stats.keyOfAttrName); + return district.statFeatures[0]?.get(districtLevel.referenceLevel.stats.keyOfAttrName); }), // reference districts refDistricts = referenceLevel.districts.filter(district => { diff --git a/cosi/SelectionManager/components/SelectionManager.vue b/cosi/SelectionManager/components/SelectionManager.vue index 0d45fdb3e..0bb6e5256 100644 --- a/cosi/SelectionManager/components/SelectionManager.vue +++ b/cosi/SelectionManager/components/SelectionManager.vue @@ -8,10 +8,12 @@ import VectorSource from "ol/source/Vector.js"; import getBoundingGeometry from "../../utils/getBoundingGeometry.js"; import {setBBoxToGeom} from "../../utils/setBBoxToGeom.js"; import ToolInfo from "../../components/ToolInfo.vue"; -import {getModelByAttributes} from "../../utils/radioBridge.js"; +import {addModelsByAttributes, getModelByAttributes} from "../../utils/radioBridge.js"; import Feature from "ol/Feature"; import Polygon from "ol/geom/Polygon"; import {default as turfCenterOfMass} from "@turf/center-of-mass"; +import {default as turfConcave} from "@turf/concave"; +import {featureCollection as turfFeatureCollection} from "@turf/helpers"; import GeoJSON from "ol/format/GeoJSON"; import {Fill, Stroke, Style} from "ol/style.js"; @@ -29,7 +31,9 @@ export default { // array that stores the indexes of selections that are about to be merged selectionsToMerge: [], // Bool that reacts to changes in the this.selections array - allSelectionsPossible: true + allSelectionsPossible: true, + // Helper variable to deal with broken loader + dataLoaded: false }; }, computed: { @@ -47,6 +51,9 @@ export default { }, activeLayerList () { return this.activeVectorLayerList.map(layer => layer.getProperties().name); + }, + selectionsLength () { + return this.selections.length; } }, watch: { @@ -54,10 +61,30 @@ export default { // that pattern allows us to use data from external tools and apply internal methods to it. acceptSelection (selection) { if (selection) { - this.selections.push(selection); + // check for stored vector layers to load + selection.storedLayers.forEach(layerName => { + if (!getModelByAttributes({type: "layer", name: layerName})) { + addModelsByAttributes({name: layerName}); + } + }); + + const format = new GeoJSON(); + + selection.selection = selection.selection.map(sel => format.readFeature(sel)); + selection.abv = this.selections.filter(sel => sel.abv === selection.id.match(/\b([A-Z0-9])/g).join("")).length > 0 ? selection.id.match(/\b([A-Z0-9])/g).join("") + "-" + this.selections.filter(sel => sel.abv === selection.id.match(/\b([A-Z0-9])/g).join("")).length : selection.id.match(/\b([A-Z0-9])/g).join(""); + this.addSelection(selection); this.highlightSelection(this.selections.length - 1); } }, + // watcher on activeSet so that the active selection can be changed via the inputActiveSelection mutation from outside of the component + activeSelection (value) { + if (value !== null) { + this.activateSelection(value); + } + else { + this.removeLayer(); + } + }, /** * @description Changes the style of the selection manager VectorLayer based on if the addon is active or not. * @returns {void} @@ -93,11 +120,15 @@ export default { } } }, - /** - * @description Sets the all selections button active again if the this.selections array changes. - * @returns {void} - */ - selections () { + // Sets the all selections button active again if the this.selections array changes and updates the selection index + selectionsLength (newValue, oldValue) { + if ((oldValue && newValue && oldValue < newValue) || (!oldValue && newValue)) { + this.inputActiveSelection(newValue - 1); + } + else if (newValue && newValue < this.activeSelection + 1) { + this.inputActiveSelection(0); + } + this.allSelectionsPossible = true; } }, @@ -109,110 +140,115 @@ export default { ...mapActions("Tools/SelectionManager", Object.keys(actions)), ...mapMutations("Maps", ["setLayerVisibility", "addLayerToMap", "removeLayerFromMap"]), /** - * @description Creates VectorLayer for the chosen selection - * @param {Integer} index - Index of the chosen selection in this.selections - * @param {boolean} forceUpdate - update even if selection hasn't changed + * @description Highlights activeSelection + * @param {Integer} index - Index of the active selection in this.selections * @returns {void} */ - highlightSelection (index) { - if (this.activeSelection !== index) { - this.setActiveSelection(index); - if (this.selections[index].storedLayers.length > 0) { - this.setStoredLayersActive(this.selections[index].storedLayers); - } - if (this.selections[index].settings.bufferActive) { - this.rerenderSelection(index); - } - else { - - // remove selection manager layers - this.map.getLayers().getArray().filter(layer => layer.get("name") === "selection_manager").forEach(layer => this.map.removeLayer(layer)); - - const vectorSource = new VectorSource({ - features: this.selections[index].selection - }), - layer = new VectorLayer({ - name: "selection_manager", - source: vectorSource, - style: new Style({ - fill: new Fill({ - color: "rgba(214, 96, 93, 0.35)" - }), - stroke: new Stroke({ - color: "#D6605D", - width: 3 - }) - }) - }); - - layer.setZIndex(9999); - this.map.addLayer(layer); - - setBBoxToGeom.call(this, getBoundingGeometry(this.selections[index].selection, 0)); - } + activateSelection (index) { + if (this.selections[index].settings.bufferActive) { + this.rerenderSelection(index); } else { - this.removeLayer(); - this.setActiveSelection(null); + this.highlightSelection(index); } }, /** - * @description Creates VectorLayer for the chosen selection if there is a buffer value and overwrites standard selection. + * @description Creates VectorLayer for the chosen selection * @param {Integer} index - Index of the chosen selection in this.selections + * @param {boolean} forceUpdate - update even if selection hasn't changed * @returns {void} */ - rerenderSelection (index) { - if (this.activeSelection === index) { + highlightSelection (index) { + if (this.selections[index].storedLayers.length > 0) { + this.setStoredLayersActive(this.selections[index].storedLayers); + } - // remove selection manager layers - this.map.getLayers().getArray().filter(layer => layer.get("name") === "selection_manager").forEach(layer => this.map.removeLayer(layer)); + // remove selection manager layers + this.map.getLayers().getArray().filter(layer => layer.get("name") === "selection_manager").forEach(layer => this.map.removeLayer(layer)); - const vectorSource = new VectorSource({ - features: this.selections[index].bufferedSelection - }), - layer = new VectorLayer({ - name: "selection_manager", - source: vectorSource, - style: new Style({ - fill: new Fill({ - color: "rgba(214, 187, 47, 0.35)" - }), - stroke: new Stroke({ - color: "#D6B62F", - width: 3 - }) + const vectorSource = new VectorSource({ + features: this.selections[index].selection + }), + layer = new VectorLayer({ + name: "selection_manager", + source: vectorSource, + style: new Style({ + fill: new Fill({ + color: "rgba(214, 96, 93, 0.35)" + }), + stroke: new Stroke({ + color: "#D6605D", + width: 3 }) - }); + }) + }); - layer.setZIndex(9999); - this.map.addLayer(layer); + layer.setZIndex(9999); + this.map.addLayer(layer); - setBBoxToGeom.call(this, getBoundingGeometry(this.selections[index].bufferedSelection)); - } + setBBoxToGeom.call(this, getBoundingGeometry(this.selections[index].selection, 0)); }, /** - * @description Creates VectorLayer for the chosen selection on hover in the selection menu. + * @description Creates VectorLayer for the chosen selection if there is a buffer value and overwrites standard selection. * @param {Integer} index - Index of the chosen selection in this.selections * @returns {void} */ - hoverSelection (index) { - this.map.getLayers().getArray().filter(layer => layer.get("name") === "selection_manager_hover_layer").forEach(layer => this.map.removeLayer(layer)); + rerenderSelection (index) { + if (this.selections[index].storedLayers.length > 0) { + this.setStoredLayersActive(this.selections[index].storedLayers); + } + + // remove selection manager layers + this.map.getLayers().getArray().filter(layer => layer.get("name") === "selection_manager").forEach(layer => this.map.removeLayer(layer)); const vectorSource = new VectorSource({ - features: this.selections[index].selection + features: this.selections[index].bufferedSelection }), layer = new VectorLayer({ - name: "selection_manager_hover_layer", + name: "selection_manager", source: vectorSource, style: new Style({ fill: new Fill({ - color: "rgba(214, 96, 93, 0.2)" + color: "rgba(214, 187, 47, 0.35)" + }), + stroke: new Stroke({ + color: "#D6B62F", + width: 3 }) }) }); layer.setZIndex(9999); this.map.addLayer(layer); + + setBBoxToGeom.call(this, getBoundingGeometry(this.selections[index].bufferedSelection)); + }, + /** + * @description Creates VectorLayer for the chosen selection on hover in the selection menu. + * @param {Integer} index - Index of the chosen selection in this.selections + * @param {Integer} color - RGB String of color that the hover shall have + * @returns {void} + */ + hoverSelection (index, color) { + if (this.activeSelection !== index) { + this.map.getLayers().getArray().filter(layer => layer.get("name") === "selection_manager_hover_layer").forEach(layer => this.map.removeLayer(layer)); + + const vectorSource = new VectorSource({ + features: this.selections[index].selection + }), + layer = new VectorLayer({ + name: "selection_manager_hover_layer", + source: vectorSource, + style: new Style({ + fill: new Fill({ + color: color + }) + }) + }); + + layer.setZIndex(9999); + this.map.addLayer(layer); + } }, /** * @description Removes the hover selection layer. @@ -239,8 +275,9 @@ export default { * @param {Array} storedLayers - The names of the stored layers inside the saved selection * @returns {void} */ - setStoredLayersActive (storedLayers) { - // inverse the stored layers to get the theme data layers that are about to hideflat().filter(layer => !storedLayers.includes(layer.id)), + async setStoredLayersActive (storedLayers) { + + // hide all active layers this.activeVectorLayerList.forEach(layer => { const layerId = layer.getProperties().id, model = getModelByAttributes({type: "layer", id: layerId}); @@ -250,13 +287,25 @@ export default { } }); - storedLayers.forEach(layerName => { + // show all layers stored in the current selection + storedLayers.forEach(async layerName => { const model = getModelByAttributes({type: "layer", name: layerName}); if (model) { model.set("isSelected", true); } }); + + // a bug in the loaderOverlay.hide() is not working, even if it's fired with a timeOut + // so here's a very! uncanny workaround to make it work in this case + await this.$nextTick(); + + // setTimout because something triggers LoaderOverlay.show() after the last $nextTick() and I can't figure out what + setTimeout(()=> { + // remove the classes manually + document.getElementById("loader").classList.remove("loader-is-loading"); + document.getElementById("masterportal-container").classList.remove("blurry"); + }, 1500); }, /** * @description Adds index of selection to extendedOptions to extend to option menu in the frontend. @@ -278,18 +327,21 @@ export default { allSelections () { const flatArray = this.selections.map(selection => selection.selection).flat(); - this.addNewSelection({selection: flatArray, source: this.$t("additional:modules.tools.cosi.selectionManager.title"), id: this.$t("additional:modules.tools.cosi.selectionManager.allSelections") + " (" + flatArray.length + ")"}); + this.addNewSelection({selection: flatArray, source: this.$t("additional:modules.tools.cosi.selectionManager.title"), id: this.$t("additional:modules.tools.cosi.selectionManager.allSelections") + " (" + this.selections.length + ")"}); this.highlightSelection(this.selections.length - 1); this.allSelectionsPossible = false; }, /** * @description Calculates the center of each feature and draws a polygon along these points. + * @param {Array} selectionInc - Array of selections that is supposed to be connected + * @param {bool} indexArr - if true selections must be mapped via index from this.selections * @returns {void} */ - connectSelections () { - const format = new GeoJSON(), + connectSelections (selectionInc, indexArr) { + const selectionArray = indexArr ? this.selections.filter(selection => selectionInc.includes(this.selections.indexOf(selection))) : selectionInc, + format = new GeoJSON(), newPolygonCoords = [], - selectionsCopy = this.selections.map(selection => selection.selection); + selectionsCopy = selectionArray.map(selection => selection.selection); selectionsCopy.forEach(selection => { if (Array.isArray(selection)) { @@ -306,9 +358,13 @@ export default { } }); - selectionsCopy.push(new Feature(new Polygon([newPolygonCoords.map(point => point.geometry.coordinates)]))); - this.addNewSelection({selection: selectionsCopy.flat(), source: this.$t("additional:modules.tools.cosi.selectionManager.title"), id: this.$t("additional:modules.tools.cosi.selectionManager.connectedSelections") + " (" + selectionsCopy.length - 1 + ")"}); + selectionsCopy.push(new Feature(new Polygon(turfConcave(turfFeatureCollection(newPolygonCoords)).geometry.coordinates))); + this.addNewSelection({selection: selectionsCopy.flat(), source: this.$t("additional:modules.tools.cosi.selectionManager.title"), id: this.$t("additional:modules.tools.cosi.selectionManager.connectedSelections") + " (" + (selectionsCopy.length - 1) + ")"}); this.highlightSelection(this.selections.length - 1); + + if (indexArr) { + this.selectionsToMerge = []; + } }, /** * @description Toggles the buffered selection to overwrite standard selection on the OL Layer. @@ -319,7 +375,6 @@ export default { if (this.selections[index].settings.bufferActive) { this.selections[index].settings.bufferActive = false; if (this.activeSelection === index) { - this.setActiveSelection(null); this.highlightSelection(index); } } @@ -349,6 +404,11 @@ export default { this.rerenderSelection(index); } }, + /** + * @description Creates a new selection from the buffered value. + * @param {Integer} index - Index of the chosen selection in this.selections + * @returns {void} + */ addBufferAsSelection (index) { this.addNewSelection({selection: this.selections[index].bufferedSelection, source: this.$t("additional:modules.tools.cosi.selectionManager.title"), id: this.$t("additional:modules.tools.cosi.selectionManager.buffer") + " " + this.selections[index].id}); this.selections[index].settings.buffer = 0; @@ -360,7 +420,6 @@ export default { * @returns {void} */ removeLayer () { - this.setActiveSelection(null); this.map.getLayers().getArray().filter(layer => layer.get("name") === "selection_manager").forEach(layer => this.map.removeLayer(layer)); }, /** @@ -371,9 +430,21 @@ export default { removeSelection (index) { this.selections.splice(index, 1); + // if no selections are left => remove the ol layer if (this.selections.length === 0) { + this.inputActiveSelection(null); this.removeLayer(); } + + // if selection was in cache => remove it there as well + if (this.selectionsToMerge.includes(index)) { + this.selectionsToMerge.splice(this.selectionsToMerge.indexOf(index), 1); + } + + // if selection was active selection => reset active selection + if (this.selections.length > 0 && index === this.activeSelection) { + this.inputActiveSelection(0); + } }, /** * @description Resets the whole this.selections array @@ -382,15 +453,14 @@ export default { removeAllSelections () { this.clearAllSelections(); this.removeLayer(); - this.setActiveSelection(null); + this.inputActiveSelection(null); }, /** * @description Merges selections from selectionToMerge array into a single selection. * @param {Integer} index - Index of the chosen selection in this.selections * @returns {void} */ - addMergedSelection (index) { - this.selectionsToMerge.push(index); + addMergedSelection () { const flatSelections = this.selections.filter((selection, i) => this.selectionsToMerge.includes(i)).map(selection => selection.selection), flatArray = flatSelections.flat(); @@ -445,7 +515,13 @@ export default { :locale="currentLocale" /> -
+
+ @@ -470,6 +546,63 @@ export default { mdi-close-box-multiple
+
+
+
+ + + + +
+
+ +