Mercurial > gemma
changeset 3204:1253fe15e3e3
client: identify: implemented popup when clicking the map and the intention is not clear...
...because multiple features were identified. In that case no dialog will be opened automatically but a popup with
possible options is shown.
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Wed, 08 May 2019 17:10:17 +0200 |
parents | cb67ee72485b |
children | bf571429515f |
files | client/src/components/App.vue client/src/components/Bottlenecks.vue client/src/components/fairway/Profiles.vue client/src/components/map/MapPopup.vue client/src/store/bottlenecks.js client/src/store/map.js |
diffstat | 6 files changed, 333 insertions(+), 93 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/App.vue Wed May 08 16:19:31 2019 +0200 +++ b/client/src/components/App.vue Wed May 08 17:10:17 2019 +0200 @@ -23,6 +23,7 @@ <Toolbar v-if="isMapVisible" /> </div> </div> + <MapPopup /> </div> <router-view /> <vue-snotify /> @@ -101,7 +102,8 @@ Contextbox: () => import("./Contextbox"), Toolbar: () => import("./toolbar/Toolbar"), Popup: () => import("./Popup"), - Statistics: () => import("./Statistics") + Statistics: () => import("./Statistics"), + MapPopup: () => import("./map/MapPopup") } }; </script>
--- a/client/src/components/Bottlenecks.vue Wed May 08 16:19:31 2019 +0200 +++ b/client/src/components/Bottlenecks.vue Wed May 08 17:10:17 2019 +0200 @@ -156,9 +156,6 @@ bottleneck.properties.name ) .then(() => { - this.$store.commit("bottlenecks/setFirstSurveySelected"); - }) - .then(() => { this.$store.dispatch("map/moveToFeauture", { feature: bottleneck, zoom: 17,
--- a/client/src/components/fairway/Profiles.vue Wed May 08 16:19:31 2019 +0200 +++ b/client/src/components/fairway/Profiles.vue Wed May 08 17:10:17 2019 +0200 @@ -342,11 +342,7 @@ return this.$store.state.bottlenecks.selectedBottleneck; }, set(name) { - this.$store - .dispatch("bottlenecks/setSelectedBottleneck", name) - .then(() => { - this.$store.commit("bottlenecks/setFirstSurveySelected"); - }); + this.$store.dispatch("bottlenecks/setSelectedBottleneck", name); } }, selectedWaterLevel: {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/map/MapPopup.vue Wed May 08 17:10:17 2019 +0200 @@ -0,0 +1,243 @@ +<template> + <div class="map-popup rounded" ref="map-popup"> + <UIBoxHeader :title="title" :closeCallback="close" small /> + <div class="p-1 small text-nowrap"> + <div + class="d-flex flex-nowrap justify-content-between align-items-center" + v-if="bottlenecks.length" + v-for="bottleneck in bottlenecks" + :key="bottleneck.get('objnam')" + > + <div class="mr-2"> + <font-awesome-icon icon="ship" class="mr-1" fixed-width /> + {{ bottleneck.get("objnam") }} + </div> + <div> + <button + class="btn btn-xs btn-info" + v-tooltip="surveysLabel" + @click="openSurveys(bottleneck)" + > + <font-awesome-icon icon="chart-area" fixed-width /> + </button> + <button + class="btn btn-xs btn-info ml-1" + v-tooltip="fairwayAvailabilityLabel" + @click="openFairwayAvailabilityForBottleneck(bottleneck)" + > + <font-awesome-icon icon="chart-line" fixed-width /> + </button> + </div> + </div> + + <div + class="d-flex flex-nowrap justify-content-between align-items-center mt-1" + v-if="gauges.length" + v-for="gauge in gauges" + :key="gauge.get('objname')" + > + <div class="mr-2"> + <font-awesome-icon icon="ruler-vertical" class="mr-1" fixed-width /> + {{ gauge.get("objname") }} + </div> + <button + class="btn btn-xs btn-info" + v-tooltip="waterlevelsLabel" + @click="openGauges(gauge)" + > + <font-awesome-icon icon="ruler-vertical" fixed-width /> + </button> + </div> + + <div + class="d-flex flex-nowrap justify-content-between align-items-center mt-1" + v-if="stretches.length" + v-for="stretch in stretches" + :key="stretch.get('objnam')" + > + <div class="mr-2"> + <font-awesome-icon icon="road" class="mr-1" fixed-width /> + {{ stretch.get("objnam") }} + </div> + <button + class="btn btn-xs btn-info" + v-tooltip="fairwayAvailabilityLabel" + @click="openFairwayAvailabilityForStretch(stretch)" + > + <font-awesome-icon icon="chart-line" fixed-width /> + </button> + </div> + + <div + class="d-flex flex-nowrap justify-content-between align-items-center mt-1" + v-if="sections.length" + v-for="section in sections" + :key="section.get('objnam')" + > + <div class="mr-2"> + <font-awesome-icon icon="road" class="mr-1" fixed-width /> + {{ section.get("objnam") }} + </div> + <button + class="btn btn-xs btn-info" + v-tooltip="fairwayAvailabilityLabel" + > + <font-awesome-icon icon="chart-line" fixed-width /> + </button> + </div> + </div> + <div + v-if="identifiedCoordinates" + class="border-top text-muted p-1 coordinates" + > + Lat: {{ identifiedCoordinates[1].toFixed(8) }}, Lon: + {{ identifiedCoordinates[0].toFixed(8) }} + </div> + </div> +</template> + +<style lang="sass"> +.map-popup + position: absolute + background: #fff + min-width: 200px + min-height: 85px + box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2) + border-top-left-radius: 0 !important + margin-left: 10px + &::before + content: "" + position: absolute + top: 0 + left: -10px + border: 5px solid transparent + border-top: 5px solid white + border-right: 5px solid white + .coordinates + font-size: 70% +</style> + +<script> +/* This is Free Software under GNU Affero General Public License v >= 3.0 + * without warranty, see README.md and license for details. + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * License-Filename: LICENSES/AGPL-3.0.txt + * + * Copyright (C) 2018 by via donau + * – Österreichische Wasserstraßen-Gesellschaft mbH + * Software engineering by Intevation GmbH + * + * Author(s): + * Thomas Junk <thomas.junk@intevation.de> + * Markus Kottländer <markus.kottlaender@intevation.de> + */ +import { mapState } from "vuex"; +import Overlay from "ol/Overlay.js"; +import { getCenter } from "ol/extent"; + +export default { + computed: { + ...mapState("map", [ + "mapPopup", + "identifiedFeatures", + "identifiedCoordinates" + ]), + title() { + return this.$gettext("Identified Features"); + }, + bottlenecks() { + return this.identifiedFeatures.filter(f => + /^bottlenecks/.test(f.getId()) + ); + }, + gauges() { + return this.identifiedFeatures.filter(f => /^gauges/.test(f.getId())); + }, + stretches() { + return this.identifiedFeatures.filter(f => /^stretches/.test(f.getId())); + }, + sections() { + return this.identifiedFeatures.filter(f => /^sections/.test(f.getId())); + }, + surveysLabel() { + return this.$gettext("Surveys"); + }, + fairwayAvailabilityLabel() { + return this.$gettext("Fairway Availability"); + }, + waterlevelsLabel() { + return this.$gettext("Waterlevels"); + } + }, + methods: { + close() { + this.mapPopup.setPosition(undefined); + }, + openSurveys(bottleneck) { + this.$store.commit("application/showProfiles", true); + this.$store.dispatch( + "bottlenecks/setSelectedBottleneck", + bottleneck.get("objnam") + ); + this.$store.dispatch("map/moveMap", { + coordinates: getCenter( + bottleneck + .getGeometry() + .clone() + .transform("EPSG:3857", "EPSG:4326") + .getExtent() + ), + zoom: 17, + preventZoomOut: true + }); + this.close(); + }, + openGauges(gauge) { + this.$store.commit("application/showGauges", true); + this.$store.dispatch("gauges/selectedGaugeISRS", gauge.get("isrs_code")); + this.close(); + }, + openFairwayAvailability() { + this.$store.commit("application/showStatistics", true); + this.close(); + }, + openFairwayAvailabilityForBottleneck(bottleneck) { + this.$store.dispatch( + "bottlenecks/setSelectedBottleneck", + bottleneck.get("objnam") + ); + this.$store.dispatch("map/moveMap", { + coordinates: getCenter( + bottleneck + .getGeometry() + .clone() + .transform("EPSG:3857", "EPSG:4326") + .getExtent() + ), + zoom: 17, + preventZoomOut: true + }); + this.openFairwayAvailability(); + }, + openFairwayAvailabilityForStretch(stretch) { + this.$store.commit("imports/selectedStretchId", stretch.getId()); + this.$store.dispatch("map/moveToFeauture", { + feature: stretch, + zoom: 17 + }); + this.openFairwayAvailability(); + } + }, + mounted() { + const mapPopup = new Overlay({ + element: this.$refs["map-popup"], + autoPan: true, + autoPanAnimation: { + duration: 250 + } + }); + this.$store.commit("map/mapPopup", mapPopup); + } +}; +</script>
--- a/client/src/store/bottlenecks.js Wed May 08 16:19:31 2019 +0200 +++ b/client/src/store/bottlenecks.js Wed May 08 17:10:17 2019 +0200 @@ -73,9 +73,6 @@ .clear(); }); } - if (name) { - commit("application/showProfiles", true, { root: true }); - } commit("setSelectedBottleneck", name); if (name) { commit("surveysLoading", true); @@ -90,6 +87,7 @@ a.date_info < b.date_info ? 1 : -1 ); commit("setSurveys", surveys); + commit("setFirstSurveySelected"); resolve(response); }) .catch(error => {
--- a/client/src/store/map.js Wed May 08 16:19:31 2019 +0200 +++ b/client/src/store/map.js Wed May 08 17:10:17 2019 +0200 @@ -18,13 +18,13 @@ import { Stroke, Style, Fill, Circle } from "ol/style"; import { fromLonLat } from "ol/proj"; import { getLength, getArea } from "ol/sphere"; -import { getCenter } from "ol/extent"; import { transformExtent } from "ol/proj"; import bbox from "@turf/bbox"; import app from "@/main"; import { HTTP } from "@/lib/http"; import Feature from "ol/Feature"; import Point from "ol/geom/Point"; +import { toLonLat } from "ol/proj"; // initial state const init = () => { @@ -32,6 +32,7 @@ openLayersMaps: [], syncedMaps: [], syncedView: null, + mapPopup: null, initialLoad: true, extent: { lat: 6155376, @@ -39,6 +40,7 @@ zoom: 11 }, identifiedFeatures: [], // map features identified by clicking on the map + identifiedCoordinates: null, currentMeasurement: null, // distance or area from line-/polygon-/cutTool lineToolEnabled: false, polygonToolEnabled: false, @@ -86,6 +88,9 @@ syncedView: (state, view) => { state.syncedView = view; }, + mapPopup: (state, popup) => { + state.mapPopup = popup; + }, setIdentifiedFeatures: (state, identifiedFeatures) => { state.identifiedFeatures = identifiedFeatures; }, @@ -94,6 +99,9 @@ identifiedFeatures ); }, + identifiedCoordinates: (state, coordinates) => { + state.identifiedCoordinates = coordinates; + }, setCurrentMeasurement: (state, measurement) => { state.currentMeasurement = measurement; }, @@ -286,6 +294,11 @@ }, initIdentifyTool({ state, rootState, commit, dispatch }, map) { map.on(["singleclick", "dblclick"], event => { + commit( + "identifiedCoordinates", + toLonLat(event.coordinate, map.getView().getProjection()) + ); + state.mapPopup.setPosition(undefined); if ( state.lineToolEnabled || state.polygonToolEnabled || @@ -296,93 +309,78 @@ // checking our WFS layers var features = map.getFeaturesAtPixel(event.pixel, { hitTolerance: 7 }); if (features) { - let identifiedFeatures = []; + let all = []; + let bottlenecks = []; + let gauges = []; + let stretches = []; + let sections = []; for (let feature of features) { + // avoid identifying the same feature twice + if (all.findIndex(f => f.getId() === feature.getId()) === -1) + all.push(feature); + let id = feature.getId(); - // avoid identifying the same feature twice - if ( - identifiedFeatures.findIndex( - f => f.getId() === feature.getId() - ) === -1 - ) { - identifiedFeatures.push(feature); - } - - // get selected bottleneck // RegExp.prototype.test() works with number, str and undefined - if (/^bottlenecks/.test(id)) { - if ( - rootState.bottlenecks.selectedBottleneck != - feature.get("objnam") - ) { - dispatch( - "bottlenecks/setSelectedBottleneck", - feature.get("objnam"), - { root: true } - ).then(() => { - this.commit("bottlenecks/setFirstSurveySelected"); - }); - dispatch("moveMap", { - coordinates: getCenter( - feature - .getGeometry() - .clone() - .transform("EPSG:3857", "EPSG:4326") - .getExtent() - ), - zoom: 17, - preventZoomOut: true - }); - } - } - + // get selected bottleneck + if (/^bottlenecks/.test(id)) bottlenecks.push(feature); // get selected gauge - if (/^gauges/.test(id)) { - if ( - rootState.gauges.selectedGaugeISRS !== feature.get("isrs_code") - ) { - dispatch("gauges/selectedGaugeISRS", feature.get("isrs_code"), { - root: true - }); - dispatch("moveMap", { - coordinates: getCenter( - feature - .getGeometry() - .clone() - .transform("EPSG:3857", "EPSG:4326") - .getExtent() - ), - zoom: 15, - preventZoomOut: true - }); - } - } + if (/^gauges/.test(id)) gauges.push(feature); + // get selected stretch + if (/^stretches/.test(id)) stretches.push(feature); + // get selected section + if (/^sections/.test(id)) sections.push(feature); + } + + commit("setIdentifiedFeatures", all); - // get selected stretch - if (/^stretches/.test(id)) { - if (rootState.imports.selectedStretchId === feature.getId()) { - commit("imports/selectedStretchId", null, { root: true }); - } else { - commit("imports/selectedStretchId", feature.getId(), { - root: true - }); - dispatch("moveMap", { - coordinates: getCenter( - feature - .getGeometry() - .clone() - .transform("EPSG:3857", "EPSG:4326") - .getExtent() - ), - zoom: null, - preventZoomOut: true - }); - } + // Decide whether we open a related dialog immediately or show the + // popup with possible options first. + // The following cases require a manual decision via the popup because + // the targeted feature is not clear. + if ( + bottlenecks.length || + gauges.length > 1 || + stretches.length > 1 || + sections.length > 1 || + (sections.length && stretches.length) || + (gauges.length && sections.length) || + (gauges.length && stretches.length) + ) { + state.mapPopup.setMap(map); + state.mapPopup.setPosition(event.coordinate); + } + // The following scenarios lead to a distinct action without popup. + if ( + gauges.length === 1 && + !bottlenecks.length && + !sections.length && + !stretches.length + ) { + commit("application/showGauges", true, { root: true }); + dispatch("gauges/selectedGaugeISRS", gauges[0].get("isrs_code"), { + root: true + }); + } + if ( + stretches.length === 1 && + !sections.length && + !bottlenecks.length && + !gauges.length + ) { + if (rootState.imports.selectedStretchId === stretches[0].getId()) { + commit("application/showStatistics", false, { root: true }); + commit("imports/selectedStretchId", null, { + root: true + }); + } else { + commit("application/showStatistics", true, { root: true }); + commit("imports/selectedStretchId", stretches[0].getId(), { + root: true + }); + dispatch("moveToFeauture", { feature: stretches[0], zoom: 17 }); } } - - commit("setIdentifiedFeatures", identifiedFeatures); } // DEBUG output and example how to remove the GeometryName @@ -531,7 +529,13 @@ }); }, moveToFeauture({ dispatch }, { feature, zoom, preventZoomOut }) { - const boundingBox = bbox(feature.geometry); + const boundingBox = feature.hasOwnProperty("geometry") + ? bbox(feature.geometry) + : feature + .getGeometry() + .clone() + .transform("EPSG:3857", "EPSG:4326") + .getExtent(); dispatch("moveToBoundingBox", { boundingBox, zoom, preventZoomOut }); }, moveMap({ state }, { coordinates, zoom, preventZoomOut }) {