# HG changeset patch # User Sascha L. Teichmann # Date 1551434787 -3600 # Node ID 3cf5d27a6c8b1d5c96bcaaceda7d1c7daa8483e0 # Parent 242104c338ff2c77bb58ade2778d4ffb3ca15a1f# Parent 9de710bdb535300aafcda2f6635390163a7d8796 Merged defualt into octree-diff branch. diff -r 242104c338ff -r 3cf5d27a6c8b client/src/assets/application.scss --- a/client/src/assets/application.scss Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/assets/application.scss Fri Mar 01 11:06:27 2019 +0100 @@ -57,6 +57,14 @@ border: 1px solid red; } +.debug2 { + border: 1px solid magenta; +} + +.debug3 { + border: 1px solid greenyellow; +} + %fully-centered { position: absolute; top: 50%; @@ -120,10 +128,12 @@ font-weight: bold; } -.fade-enter-active, .fade-leave-active { - transition: opacity .3s; +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.3s; } -.fade-enter, .fade-leave-to { +.fade-enter, +.fade-leave-to { opacity: 0; } diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/App.vue --- a/client/src/components/App.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/App.vue Fri Mar 01 11:06:27 2019 +0100 @@ -37,9 +37,6 @@ .small { width: $icon-width; } -.wide { - width: 600px; -} .userinterface { position: absolute; @@ -90,7 +87,9 @@ ...mapState("user", ["isAuthenticated"]), ...mapState("application", ["contextBoxContent", "showSearchbar"]), isMapVisible() { - return /stretches|review|bottlenecks|mainview/.test(this.routeName); + return /importoverview|stretches|review|bottlenecks|mainview/.test( + this.routeName + ); }, routeName() { const routeName = this.$route.name; diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/Bottlenecks.vue --- a/client/src/components/Bottlenecks.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/Bottlenecks.vue Fri Mar 01 11:06:27 2019 +0100 @@ -158,7 +158,7 @@ "showSearchbarLastState", "showSplitscreen" ]), - ...mapState("bottlenecks", ["bottlenecks"]), + ...mapState("bottlenecks", ["bottlenecksList"]), sortIcon() { return this.sortDirection === "ASC" ? "sort-amount-down" @@ -170,7 +170,7 @@ return formatSurveyDate(date); }, filteredAndSortedBottlenecks() { - return this.bottlenecks + return this.bottlenecksList .filter(bn => { return bn.properties.name .toLowerCase() @@ -287,7 +287,7 @@ } }, mounted() { - this.$store.dispatch("bottlenecks/loadBottlenecks"); + this.$store.dispatch("bottlenecks/loadBottlenecksList"); } }; diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/Contextbox.vue --- a/client/src/components/Contextbox.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/Contextbox.vue Fri Mar 01 11:06:27 2019 +0100 @@ -3,6 +3,9 @@ + @@ -25,9 +28,10 @@ export default { name: "contextbox", components: { - Bottlenecks: () => import("./Bottlenecks"), - Staging: () => import("./staging/Staging.vue"), - Stretches: () => import("./ImportStretches.vue") + Bottlenecks: () => import("@/components/Bottlenecks"), + Stretches: () => import("@/components/ImportStretches.vue"), + ImportOverview: () => + import("@/components/importoverview/ImportOverview.vue") }, computed: { ...mapState("application", [ @@ -71,7 +75,7 @@ background: #fff; } .contextbox > div:last-child { - width: 600px; + width: 660px; } .contextboxcollapsed { @@ -80,8 +84,7 @@ } .contextboxextended { - max-width: 700px; - max-height: 640px; + max-width: 660px; } .close-contextbox { diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/ImportSoundingresults.vue --- a/client/src/components/ImportSoundingresults.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/ImportSoundingresults.vue Fri Mar 01 11:06:27 2019 +0100 @@ -2,21 +2,30 @@
-
+
-
-
-
-
+
+
+ {{ message }} +
+
+
+
Bottleneck @@ -24,7 +33,7 @@
-
+
Projection (EPSG) @@ -41,9 +50,7 @@
-
-
-
+
Depthreference @@ -53,7 +60,7 @@ id="depthreference" > @@ -64,7 +71,7 @@
-
+
Date
-
-
- - {{ message }} - +
-
-
+
+
-
+
Download Meta.json - - + + + +
@@ -179,7 +184,7 @@ initialState() { this.importState = IMPORTSTATE.UPLOAD; this.depthReference = ""; - this.bottleneck = ""; + this.bottleneck = null; this.projection = ""; this.importDate = ""; this.uploadLabel = this.$gettext("choose .zip- file"); @@ -225,7 +230,9 @@ if (response.data.meta) { const { bottleneck, date, epsg } = response.data.meta; const depthReference = response.data.meta["depth-reference"]; - this.bottleneck = bottleneck; + this.bottleneck = this.bottlenecks.find( + bn => bn.properties.objnam === bottleneck + ); this.depthReference = depthReference; this.importDate = new Date(date).toISOString().split("T")[0]; this.projection = epsg; @@ -246,7 +253,8 @@ confirm() { let formData = new FormData(); formData.append("token", this.token); - if (this.bottleneck) formData.append("bottleneck", this.bottleneck); + if (this.bottleneck) + formData.append("bottleneck", this.bottleneck.properties.objnam); if (this.importDate) formData.append("date", this.importDate.split("T")[0]); if (this.depthReference) @@ -262,7 +270,9 @@ .then(() => { displayInfo({ title: this.$gettext("Import"), - message: this.$gettext("Starting import for ") + this.bottleneck + message: + this.$gettext("Starting import for ") + + this.bottleneck.properties.objnam }); this.initialState(); }) @@ -298,7 +308,7 @@ return this.disableUpload; }, availableBottlenecks() { - return this.bottlenecks.map(x => x.properties.name); + return this.bottlenecks; }, editState() { return this.importState === IMPORTSTATE.EDIT; @@ -318,63 +328,23 @@ encodeURIComponent( JSON.stringify({ depthReference: this.depthReference, - bottleneck: this.bottleneck, + bottleneck: this.bottleneck.properties.objnam, date: this.importDate }) ) ); + }, + depthReferenceOptions() { + if ( + this.bottleneck && + this.bottleneck.properties.reference_water_levels + ) { + return Object.keys( + JSON.parse(this.bottleneck.properties.reference_water_levels) + ); + } + return []; } - }, - depthReferenceOptions: [ - "", - // "NAP", - // "KP", - // "FZP", - // "ADR", - // "TAW", - // "PUL", - // "NGM", - // "ETRS", - // "POT", - // "LDC", - // "HDC", - // "ZPG", - // "GLW", - // "HSW", - // "LNW", - // "HNW", - // "IGN", - // "WGS", - "RN" //, - // "HBO" - ] + } }; - - diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/ImportStretches.vue --- a/client/src/components/ImportStretches.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/ImportStretches.vue Fri Mar 01 11:06:27 2019 +0100 @@ -346,8 +346,8 @@ this.countryCode = properties.countries; this.source = properties["source_organization"]; this.edit = true; - this.startrhm = this.sanitizeRHM(properties.lower); - this.endrhm = this.sanitizeRHM(properties.upper); + this.startrhm = properties.lower; + this.endrhm = properties.upper; this.idEditable = false; }, deleteStretch(stretch) { @@ -535,8 +535,8 @@ }, pointsValid() { if (!this.startrhm || !this.endrhm) return true; - const start = this.startrhm.replace(/\D+/, "") * 1; - const end = this.endrhm.replace(/\D+/, "") * 1; + const start = this.startrhm.replace(/\D+/g, "") * 1; + const end = this.endrhm.replace(/\D+/g, "") * 1; const result = start < end; return result; } diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/Maplayer.vue --- a/client/src/components/Maplayer.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/Maplayer.vue Fri Mar 01 11:06:27 2019 +0100 @@ -399,7 +399,6 @@ this.updateBottleneckFilter("does_not_exist", "1999-10-01"); this.$store.dispatch("map/disableIdentifyTool"); this.$store.dispatch("map/enableIdentifyTool"); - this.$store.dispatch("bottlenecks/loadBottlenecks"); } }; diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/Search.vue --- a/client/src/components/Search.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/Search.vue Fri Mar 01 11:06:27 2019 +0100 @@ -77,7 +77,7 @@ .searchgroup { margin-left: -3px; - width: 571px; + width: 630px; overflow: hidden; } diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/Sidebar.vue --- a/client/src/components/Sidebar.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/Sidebar.vue Fri Mar 01 11:06:27 2019 +0100 @@ -28,7 +28,7 @@ Bottlenecks
- + Logs
-
- - - Importqueue - -

{ - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` + setTimeout(() => { + this.$store.dispatch("imports/getStaging").catch(error => { + const { status, data } = error.response; + displayError({ + title: "Backend Error", + message: `${status}: ${data.message || data}` + }); }); - }); + }, 15000); } }; @@ -255,9 +247,13 @@ .indicator { left: auto; right: 10px; - top: 10px; + top: 12px; border-radius: 0.25rem; } + &.router-link-exact-active .indicator { + background: #fff; + color: #333; + } } .menu a svg path { diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/fairway/Fairwayprofile.vue --- a/client/src/components/fairway/Fairwayprofile.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/fairway/Fairwayprofile.vue Fri Mar 01 11:06:27 2019 +0100 @@ -142,22 +142,26 @@ ...mapGetters("fairwayprofile", ["totalLength"]), ...mapState("application", ["showSplitscreen"]), ...mapState("fairwayprofile", [ - "startPoint", + "additionalSurvey", + "currentProfile", "endPoint", - "currentProfile", - "additionalSurvey", + "fairwayData", "minAlt", "maxAlt", - "fairwayData", - "waterLevels", + "profileLoading", + "referenceWaterLevel", "selectedWaterLevel", - "profileLoading" + "startPoint", + "waterLevels" ]), ...mapState("bottlenecks", [ "selectedBottleneck", "selectedSurvey", "surveysLoading" ]), + relativeWaterLevelDelta() { + return this.selectedWaterLevel.value - this.referenceWaterLevel; + }, currentLevel: { get() { return this.selectedWaterLevel.date; @@ -192,6 +196,9 @@ return [0, this.totalLength]; }, yScaleRight() { + //ToDO calcReleativeDepth(this.maxAlt) to get the + // maximum depth according to the actual waterlevel + // additionally: take the one which is higher reference or current waterlevel const DELTA = this.maxAlt * 1.1 - this.maxAlt; return [this.maxAlt * 1 + DELTA, -DELTA]; } @@ -223,6 +230,25 @@ formatSurveyDate(value) { return formatSurveyDate(value); }, + calcRelativeDepth(depth) { + /* takes a depth value and substracts the delta of the relative waterlevel + * say the reference level is above the current level, the ground is nearer, + * thus, the depth is lower. + * + * E.g.: + * + * Reference waterlevel 5m, current 4m => delta = -1m + * If the distance to the ground was 3m from the 5m mark + * it is now only 2m from the current waterlevel. + * + * Vice versa: + * + * If the reference level is 5m and the current 6m => delta = +1m + * The ground is one meter farer away from the current waterlevel + * + */ + return depth - this.relativeWaterLevelDelta; + }, drawDiagram() { this.coordinatesSelect = null; const chartDiv = document.querySelector(".fairwayprofile"); diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/fairway/Profiles.vue --- a/client/src/components/fairway/Profiles.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/fairway/Profiles.vue Fri Mar 01 11:06:27 2019 +0100 @@ -25,7 +25,7 @@ Select Bottleneck bn.properties.name === this.selectedBottleneck ); if (!bottleneck) return; @@ -462,6 +466,9 @@ preventZoomOut: true }); } + }, + mounted() { + this.$store.dispatch("bottlenecks/loadBottlenecksList"); } }; diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/identify/Identify.vue --- a/client/src/components/identify/Identify.vue Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/identify/Identify.vue Fri Mar 01 11:06:27 2019 +0100 @@ -135,20 +135,40 @@ return this.featureId(feature); }, featureProps(feature) { + let featureId = this.featureId(feature); + // create array with {key, val} objects let propsArray = []; Object.keys(feature.getProperties()).forEach(key => { - // avoid cyclic object value - if (key !== feature.getGeometryName()) - propsArray.push({ key, val: feature.getProperties()[key] }); + // skip geometry (would lead to cyclic object error) + if (key !== feature.getGeometryName()) { + let val = feature.getProperties()[key]; + + // if val is a valid json object string, spread its values into the array + let jsonObj = this.getObjectFromString(val); + if (jsonObj) { + Object.keys(jsonObj).forEach(key => { + propsArray.push({ key, val: jsonObj[key] }); + }); + } else { + // otherwise just put the key value pair into the array + propsArray.push({ key, val }); + } + } }); // change labels and remove unneeded properties - if (formatter.hasOwnProperty(this.featureId(feature))) { - propsArray = propsArray - .map(formatter[this.featureId(feature)].props) - .filter(p => p); // remove empty entries + // for all features + propsArray = propsArray.map(formatter.all); + // feature specific + if ( + formatter.hasOwnProperty(featureId) && + formatter[featureId].hasOwnProperty("props") + ) { + propsArray = propsArray.map(formatter[featureId].props); } + // remove empty entries + propsArray = propsArray.filter(p => p); // remove underscores in labels that where not previously changed already propsArray = propsArray.map(prop => { @@ -156,6 +176,22 @@ }); return propsArray; + }, + getObjectFromString(val) { + // JSON.parse() accepts integers and null as valid json. So to be sure to + // get an object, we cannot just try JSON.parse() but we need to check if + // the given value is a string and starts with a {. + if ( + Object.prototype.toString.call(val) === "[object String]" && + val[0] === "{" + ) { + try { + return JSON.parse(val); + } catch (e) { + return null; + } + } + return null; } } }; diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/identify/formatter.js --- a/client/src/components/identify/formatter.js Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/components/identify/formatter.js Fri Mar 01 11:06:27 2019 +0100 @@ -1,66 +1,42 @@ const formatter = { + all(p) { + if (p.key === "objnam") p.key = "Name"; + if (p.key === "staging_done") p.val = p.val ? "yes" : "no"; + if (p.key === "date_info") { + p.val = new Date(p.val).toLocaleString(); + } + return p; + }, bottlenecks_geoserver: { label: "Bottleneck", props: p => { if (p.key === "bottleneck_id") p.key = "ID"; - if (p.key === "objnam") p.key = "Name"; if (p.key === "responsible_country") p.key = "Responsible Country"; - if (p.key === "date_info") { - p.val = new Date(p.val).toLocaleString(); - } // remove certain props - let propsToRemove = ["nobjnm"]; + let propsToRemove = ["nobjnm", "reference_water_levels"]; if (propsToRemove.indexOf(p.key) !== -1) return null; return p; } }, fairway_dimensions: { - label: "Fairway Dimensions", - props: p => { - if (p.key === "staging_done") p.val = p.val ? "yes" : "no"; - if (p.key === "date_info") { - p.val = new Date(p.val).toLocaleString(); - } - - // remove certain props - let propsToRemove = []; - if (propsToRemove.indexOf(p.key) !== -1) return null; - - return p; - } + label: "Fairway Dimensions" }, waterway_area: { - label: "Waterway Area", - props: p => p + label: "Waterway Area" }, distance_marks_geoserver: { - label: "Distance Mark", - props: p => p + label: "Distance Mark" }, waterway_axis: { - label: "Waterway Axis", - props: p => { - if (p.key === "objnam") p.key = "Name"; - return p; - } + label: "Waterway Axis" }, waterway_profiles: { - label: "Waterway Profile", - props: p => { - if (p.key === "staging_done") p.val = p.val ? "yes" : "no"; - if (p.key === "date_info") { - p.key = "Date info"; - p.val = new Date(p.val).toLocaleString(); - } - - // remove certain props - let propsToRemove = []; - if (propsToRemove.indexOf(p.key) !== -1) return null; - - return p; - } + label: "Waterway Profile" + }, + stretches_geoserver: { + label: "Stretch" } }; diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/importoverview/ImportOverview.vue --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importoverview/ImportOverview.vue Fri Mar 01 11:06:27 2019 +0100 @@ -0,0 +1,134 @@ + + + + + diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/importoverview/importlogs/LogDetail.vue --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importoverview/importlogs/LogDetail.vue Fri Mar 01 11:06:27 2019 +0100 @@ -0,0 +1,356 @@ + + + + + diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/importoverview/importlogs/Logs.vue --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importoverview/importlogs/Logs.vue Fri Mar 01 11:06:27 2019 +0100 @@ -0,0 +1,132 @@ + + + + + diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/importoverview/staging/Staging.vue --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importoverview/staging/Staging.vue Fri Mar 01 11:06:27 2019 +0100 @@ -0,0 +1,91 @@ + + + + + diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/importoverview/staging/StagingDetail.vue --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importoverview/staging/StagingDetail.vue Fri Mar 01 11:06:27 2019 +0100 @@ -0,0 +1,518 @@ + + + + + diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/importqueue/Importqueue.vue --- a/client/src/components/importqueue/Importqueue.vue Thu Feb 28 17:28:54 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,351 +0,0 @@ - - - - - diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/importqueue/Importqueuedetail.vue --- a/client/src/components/importqueue/Importqueuedetail.vue Thu Feb 28 17:28:54 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,359 +0,0 @@ - - - - - diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/staging/Staging.vue --- a/client/src/components/staging/Staging.vue Thu Feb 28 17:28:54 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,193 +0,0 @@ - - - - - diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/staging/StagingDetail.vue --- a/client/src/components/staging/StagingDetail.vue Thu Feb 28 17:28:54 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,583 +0,0 @@ - - - - - diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/ui/UIBoxHeader.vue --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/ui/UIBoxHeader.vue Fri Mar 01 11:06:27 2019 +0100 @@ -0,0 +1,63 @@ + + + + + diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/ui/UITableBody.vue --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/ui/UITableBody.vue Fri Mar 01 11:06:27 2019 +0100 @@ -0,0 +1,51 @@ + + + diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/ui/UITableHeader.vue --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/ui/UITableHeader.vue Fri Mar 01 11:06:27 2019 +0100 @@ -0,0 +1,94 @@ + + + + + diff -r 242104c338ff -r 3cf5d27a6c8b client/src/components/ui/box/Header.vue --- a/client/src/components/ui/box/Header.vue Thu Feb 28 17:28:54 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ - - - - - diff -r 242104c338ff -r 3cf5d27a6c8b client/src/main.js --- a/client/src/main.js Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/main.js Fri Mar 01 11:06:27 2019 +0100 @@ -29,7 +29,9 @@ import store from "./store"; import translations from "./locale/translations.json"; import { supportedLanguages, defaultLanguage } from "./locale/languages.js"; -import Header from "@/components/ui/box/Header"; +import UIBoxHeader from "@/components/ui/UIBoxHeader"; +import UITableHeader from "@/components/ui/UITableHeader"; +import UITableBody from "@/components/ui/UITableBody"; // styles import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; @@ -74,8 +76,11 @@ faRuler, faSearch, faShip, + faSort, faSortAmountDown, faSortAmountUp, + faSortDown, + faSortUp, faSpinner, faStar, faTasks, @@ -126,6 +131,9 @@ faRuler, faSearch, faShip, + faSort, + faSortDown, + faSortUp, faSortAmountDown, faSortAmountUp, faSpinner, @@ -154,7 +162,9 @@ // register global components Vue.component("font-awesome-icon", FontAwesomeIcon); -Vue.component("UIBoxHeader", Header); +Vue.component("UIBoxHeader", UIBoxHeader); +Vue.component("UITableHeader", UITableHeader); +Vue.component("UITableBody", UITableBody); // global vue config Vue.config.productionTip = false; diff -r 242104c338ff -r 3cf5d27a6c8b client/src/router.js --- a/client/src/router.js Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/router.js Fri Mar 01 11:06:27 2019 +0100 @@ -81,22 +81,6 @@ } }, { - path: "/importqueue/:id?", - name: "importqueue", - component: () => import("./components/importqueue/Importqueue.vue"), - meta: { - requiresAuth: true - }, - beforeEnter: (to, from, next) => { - const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"]; - if (!isWaterwayAdmin) { - next("/"); - } else { - next(); - } - } - }, - { path: "/importsoundingresults", name: "importsoundingresults", component: () => import("./components/ImportSoundingresults.vue"), @@ -168,6 +152,7 @@ requiresAuth: true }, beforeEnter: (to, from, next) => { + store.commit("application/searchQuery", ""); store.commit("application/showContextBox", false); store.commit("application/contextBoxContent", ""); store.commit("application/showSearchbar", false); @@ -182,6 +167,7 @@ requiresAuth: true }, beforeEnter: (to, from, next) => { + store.commit("application/searchQuery", ""); store.commit("application/showContextBox", true); store.commit("application/contextBoxContent", "bottlenecks"); store.commit("application/showSearchbar", true); @@ -189,8 +175,8 @@ } }, { - path: "/review/:id?", - name: "review", + path: "/imports/overview/:id?", + name: "importoverview", component: Main, meta: { requiresAuth: true @@ -201,7 +187,7 @@ next("/"); } else { store.commit("application/showContextBox", true); - store.commit("application/contextBoxContent", "staging"); + store.commit("application/contextBoxContent", "importoverview"); store.commit("application/showSearchbar", true); next(); } @@ -219,6 +205,7 @@ if (!isSysadmin) { next("/"); } else { + store.commit("application/searchQuery", ""); store.commit("application/showContextBox", true); store.commit("application/contextBoxContent", "stretches"); store.commit("application/showSearchbar", true); diff -r 242104c338ff -r 3cf5d27a6c8b client/src/store/bottlenecks.js --- a/client/src/store/bottlenecks.js Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/store/bottlenecks.js Fri Mar 01 11:06:27 2019 +0100 @@ -21,6 +21,7 @@ const init = () => { return { bottlenecks: [], + bottlenecksList: [], selectedBottleneck: null, surveys: [], selectedSurvey: null, @@ -36,6 +37,9 @@ setBottlenecks: (state, bottlenecks) => { state.bottlenecks = bottlenecks; }, + setBottlenecksList: (state, bottlenecksList) => { + state.bottlenecksList = bottlenecksList; + }, setSelectedBottleneck: (state, name) => { state.selectedBottleneck = name; }, @@ -102,13 +106,43 @@ } }); }, + loadBottlenecksList({ commit }) { + return new Promise((resolve, reject) => { + var bottleneckFeatureCollectionRequest = new WFS().writeGetFeature({ + srsName: "EPSG:4326", + featureNS: "gemma", + featurePrefix: "gemma", + featureTypes: ["bottleneck_overview"], + outputFormat: "application/json" + }); + HTTP.post( + "/internal/wfs", + new XMLSerializer().serializeToString( + bottleneckFeatureCollectionRequest + ), + { + headers: { + "X-Gemma-Auth": localStorage.getItem("token"), + "Content-type": "text/xml; charset=UTF-8" + } + } + ) + .then(response => { + commit("setBottlenecksList", response.data.features); + resolve(response); + }) + .catch(error => { + reject(error); + }); + }); + }, loadBottlenecks({ commit }) { return new Promise((resolve, reject) => { var bottleneckFeatureCollectionRequest = new WFS().writeGetFeature({ srsName: "EPSG:4326", featureNS: "gemma", featurePrefix: "gemma", - featureTypes: ["bottleneck_overview"], + featureTypes: ["bottlenecks_geoserver"], outputFormat: "application/json" }); HTTP.post( diff -r 242104c338ff -r 3cf5d27a6c8b client/src/store/imports.js --- a/client/src/store/imports.js Thu Feb 28 17:28:54 2019 +0100 +++ b/client/src/store/imports.js Fri Mar 01 11:06:27 2019 +0100 @@ -30,7 +30,9 @@ stretches: [], imports: [], staging: [], - importToReview: null + importToReview: null, + stagingVisible: true, + logsVisible: true }; }; @@ -67,6 +69,18 @@ init, namespaced: true, state: init(), + getters: { + processedReviews: state => { + return state.staging + .filter(x => x.status !== STATES.NEEDSAPPROVAL) + .map(r => { + return { + id: r.id, + state: r.status + }; + }); + } + }, mutations: { setStretches: (state, stretches) => { state.stretches = stretches; @@ -74,6 +88,12 @@ setImports: (state, imports) => { state.imports = imports; }, + setStagingVisibility: (state, visibility) => { + state.stagingVisible = visibility; + }, + setLogsVisibility: (state, visibility) => { + state.logsVisible = visibility; + }, setStaging: (state, staging) => { const enriched = staging.map(x => { return { ...x, status: STATES.NEEDSAPPROVAL }; @@ -138,9 +158,11 @@ }); }); }, - getImports({ commit }) { + getImports({ commit }, filter) { + let queryParams = ""; + if (filter) queryParams = "?states=" + filter.join(","); return new Promise((resolve, reject) => { - HTTP.get("/imports", { + HTTP.get("/imports" + queryParams, { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }) .then(response => { @@ -165,6 +187,22 @@ reject(error); }); }); + }, + confirmReview({ state }, reviewResults) { + return new Promise((resolve, reject) => { + HTTP.patch("/imports", reviewResults, { + headers: { + "X-Gemma-Auth": localStorage.getItem("token"), + "Content-type": "application/json" + } + }) + .then(response => { + resolve(response); + }) + .catch(error => { + reject(error); + }); + }); } } }; diff -r 242104c338ff -r 3cf5d27a6c8b schema/README.md --- a/schema/README.md Thu Feb 28 17:28:54 2019 +0100 +++ b/schema/README.md Fri Mar 01 11:06:27 2019 +0100 @@ -1,3 +1,5 @@ +The gemma database schema requires PostgreSQL >= 11. + `dot.tmpl` is a template file for `postgresql_autodoc` to be more similiar to UML and leave out some less useful labels. diff -r 242104c338ff -r 3cf5d27a6c8b schema/gemma.sql --- a/schema/gemma.sql Thu Feb 28 17:28:54 2019 +0100 +++ b/schema/gemma.sql Fri Mar 01 11:06:27 2019 +0100 @@ -473,56 +473,6 @@ PRIMARY KEY (bottleneck_id, riverbed) ) - -- Published view for GeoServer - CREATE VIEW bottlenecks_geoserver AS - WITH fairway_availability_latest AS ( - SELECT DISTINCT ON (bottleneck_id) bottleneck_id,date_info,critical - FROM fairway_availability - ORDER BY bottleneck_id, date_info DESC NULLS LAST), - gauge_measurements_waterlevel AS ( - SELECT DISTINCT ON (fk_gauge_id) fk_gauge_id,measure_date,predicted,water_level - FROM gauge_measurements WHERE predicted ='false' - ORDER BY fk_gauge_id, measure_date DESC NULLS LAST) - SELECT - b.id, - b.bottleneck_id, - b.objnam, - b.nobjnm, - b.stretch, - b.area, - b.rb, - b.lb, - b.responsible_country, - b.revisiting_time, - b.limiting, - b.date_info, - b.source_organization, - g.location AS gauge_isrs_code, - g.objname AS gauge_objname, - rwl_ldc.value AS ldc, - rwl_mw.value AS mw, - rwl_hdc.value AS hdc, - fal.date_info AS fa_date_info, - fal.critical AS fa_critical, - gmw.water_level as gm_waterlevel - FROM bottlenecks b, gauges g, - (SELECT gauge_id, value FROM gauges_reference_water_levels - WHERE depth_reference = 'LDC') rwl_ldc, - (SELECT gauge_id, value FROM gauges_reference_water_levels - WHERE depth_reference = 'MW') rwl_mw, - (SELECT gauge_id, value FROM gauges_reference_water_levels - WHERE depth_reference = 'HDC') rwl_hdc - LEFT JOIN LATERAL ( - SELECT bottleneck_id,date_info,critical - FROM fairway_availability_latest - WHERE b.id=bottleneck_id) fal ON TRUE - LEFT JOIN LATERAL ( - SELECT water_level - FROM gauge_measurements_waterlevel - WHERE b.fk_g_fid=fk_gauge_id) gmw ON TRUE - WHERE b.fk_g_fid = g.location AND g.location = rwl_ldc.gauge_id - AND g.location = rwl_mw.gauge_id AND g.location = rwl_hdc.gauge_id - CREATE TABLE sounding_results ( id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, bottleneck_id int NOT NULL REFERENCES bottlenecks(id), @@ -630,6 +580,52 @@ SELECT bottleneck_id, max(date_info) AS current FROM sounding_results GROUP BY bottleneck_id) sr ON sr.bottleneck_id = bn.id ORDER BY objnam + + -- Published view for GeoServer + CREATE VIEW bottlenecks_geoserver AS + WITH fairway_availability_latest AS ( + SELECT DISTINCT ON (bottleneck_id) bottleneck_id,date_info,critical + FROM fairway_availability + ORDER BY bottleneck_id, date_info DESC NULLS LAST), + gauge_measurements_waterlevel AS ( + SELECT DISTINCT ON (fk_gauge_id) + fk_gauge_id, measure_date, predicted, water_level + FROM gauge_measurements WHERE predicted ='false' + ORDER BY fk_gauge_id, measure_date DESC NULLS LAST) + SELECT + b.id, + b.bottleneck_id, + b.objnam, + b.nobjnm, + b.stretch, + b.area, + b.rb, + b.lb, + b.responsible_country, + b.revisiting_time, + b.limiting, + b.date_info, + b.source_organization, + g.location AS gauge_isrs_code, + g.objname AS gauge_objname, + json_object_agg(r.depth_reference, r.value) AS reference_water_levels, + fal.date_info AS fa_date_info, + fal.critical AS fa_critical, + gmw.water_level as gm_waterlevel + FROM bottlenecks b LEFT JOIN gauges g ON b.fk_g_fid = g.location + LEFT JOIN LATERAL ( + SELECT gauge_id,depth_reference,value + FROM gauges_reference_water_levels + ) r ON r.gauge_id = b.fk_g_fid + LEFT JOIN LATERAL ( + SELECT bottleneck_id,date_info,critical + FROM fairway_availability_latest + WHERE b.id=bottleneck_id) fal ON TRUE + LEFT JOIN LATERAL ( + SELECT water_level + FROM gauge_measurements_waterlevel + WHERE b.fk_g_fid=fk_gauge_id) gmw ON TRUE + GROUP BY b.id, g.location, fal.date_info, fal.critical, gmw.water_level; ; -- Configure primary keys for geoserver views diff -r 242104c338ff -r 3cf5d27a6c8b schema/isrs_functions.sql --- a/schema/isrs_functions.sql Thu Feb 28 17:28:54 2019 +0100 +++ b/schema/isrs_functions.sql Fri Mar 01 11:06:27 2019 +0100 @@ -26,7 +26,7 @@ area geometry ) RETURNS geometry AS $$ - WITH + WITH RECURSIVE -- Get coordinates of location codes points_geog AS ( SELECT geom FROM waterway.distance_marks_virtual @@ -37,9 +37,7 @@ SELECT best_utm(ST_Collect(geom::geometry)) AS z FROM points_geog), axis AS ( - -- Transform and sew together contiguous axis chunks - SELECT ST_LineMerge(ST_Collect(ST_Transform( - wtwaxs::geometry, z))) AS wtwaxs + SELECT id, ST_Transform(wtwaxs::geometry, z) AS wtwaxs FROM waterway.waterway_axis, utm_zone), -- In order to guarantee the following ST_Covers to work, -- snap distance mark coordinates to axis @@ -47,16 +45,49 @@ SELECT ST_ClosestPoint( wtwaxs, ST_Transform(points_geog.geom::geometry, z)) AS geom - FROM axis, points_geog, utm_zone), - axis_segment AS ( - -- select the contiguous axis on which distance marks lie - SELECT line - FROM ( - SELECT (ST_Dump(wtwaxs)).geom AS line - FROM axis) AS lines, + FROM points_geog, utm_zone, ( + SELECT ST_Collect(wtwaxs) AS wtwaxs + FROM axis) AS ax), + axis_snapped AS ( + -- Iteratively connect non-contiguous axis chunks + -- to find the contiguous axis on which given distance marks lie + (SELECT ARRAY[id] AS ids, wtwaxs + FROM axis, points + WHERE ST_Intersects( + ST_Buffer(axis.wtwaxs, 0.0001), points.geom) + FETCH FIRST ROW ONLY) + UNION + -- Connect endpoint of next linestring with closest + -- endpoint of merged linestring until a contiguous + -- linestring connecting both distance marks is build up + (SELECT refids || id, + ST_LineMerge(ST_Collect(ARRAY( + -- Linestring build up so far + SELECT refgeom + UNION + -- Fill eventual gap + SELECT ST_MakeLine( + ST_ClosestPoint( + ST_Boundary(refgeom), ST_Boundary(geom)), + ST_ClosestPoint( + ST_Boundary(geom), ST_Boundary(refgeom))) + UNION + -- Linestring to be added + SELECT geom))) + FROM axis_snapped AS axis_snapped (refids, refgeom), + axis AS axis (id, geom), (SELECT ST_Collect(points.geom) AS pts FROM points) AS points - WHERE ST_Covers(ST_Buffer(lines.line, 0.0001), points.pts)), + WHERE id <> ALL(refids) + AND NOT ST_Covers(ST_Buffer(refgeom, 0.0001), points.pts) + ORDER BY ST_Distance(ST_Boundary(refgeom), ST_Boundary(geom)) + FETCH FIRST ROW ONLY)), + axis_segment AS ( + -- Fetch end result from snapping + SELECT wtwaxs AS line + FROM axis_snapped + WHERE array_length(ids, 1) = ( + SELECT max(array_length(ids, 1)) FROM axis_snapped)), axis_substring AS ( -- Use linear referencing to clip axis between distance marks. -- Simplification is used to work-around the problem, that @@ -78,29 +109,34 @@ -- polygons, which intersect with the axis. The union is to avoid -- problems with invalid/self-intersecting multipolygons SELECT ST_Union(a_dmp.geom) AS area - FROM axis_substring, utm_zone, - ST_Dump(ST_Transform(area, z)) AS a_dmp + FROM axis_substring, utm_zone, LATERAL ( + SELECT ST_MakeValid(ST_Transform(geom, z)) AS geom + FROM ST_Dump(area)) AS a_dmp WHERE ST_Intersects(a_dmp.geom, axis_substring.line) ), + rotated_ends AS ( + SELECT ST_Collect(ST_Scale( + ST_Translate(e, + (ST_X(p1) - ST_X(p2)) / 2, + (ST_Y(p1) - ST_Y(p2)) / 2), + ST_Point(d, d), p1)) AS blade + FROM axis_substring, area_subset, + LATERAL (SELECT i, ST_PointN(line, i) AS p1 + FROM (VALUES (1), (-1)) AS idx (i)) AS ep, + ST_Rotate(ST_PointN(line, i*2), pi()/2, p1) AS ep2 (p2), + ST_Makeline(p1, p2) AS e (e), + LATERAL (SELECT (ST_MaxDistance(p1, area) / ST_Length(e)) + * 2) AS d (d)), range_area AS ( - -- Create a buffer around the clipped axis, as large as it could - -- potentially be intersecting with the area polygon that - -- intersects with the clipped axis. Get the intersection of that - -- buffer with the area polygon, which can potentially - -- be a multipolygon. - SELECT (ST_Dump(ST_Intersection( - ST_Buffer( - axis_substring.line, - ST_MaxDistance( - axis_substring.line, - area_subset.area), - 'endcap=flat'), - area_subset.area))).geom - FROM axis_substring, area_subset) + -- Split area by orthogonal lines at the ends of the clipped axis + SELECT (ST_Dump(ST_CollectionExtract( + ST_Split(area, blade), 3))).geom + FROM area_subset, rotated_ends) -- From the polygons returned by the last CTE, select only those -- around the clipped axis - SELECT ST_Collect(ST_Transform(range_area.geom, ST_SRID(area))) + SELECT ST_Multi(ST_Transform(ST_Union(range_area.geom), ST_SRID(area))) FROM axis_substring, range_area - WHERE ST_Intersects(range_area.geom, axis_substring.line) + WHERE ST_Intersects(ST_Buffer(range_area.geom, -0.0001), + axis_substring.line) $$ LANGUAGE sql; diff -r 242104c338ff -r 3cf5d27a6c8b schema/isrs_tests.sql --- a/schema/isrs_tests.sql Thu Feb 28 17:28:54 2019 +0100 +++ b/schema/isrs_tests.sql Fri Mar 01 11:06:27 2019 +0100 @@ -42,7 +42,6 @@ ) IS NULL, 'ISRSrange_area returns NULL, if given area does not intersect with axis'); -\set test_area 'POLYGON((-1 1, 2 1, 2 -1, -1 -1, -1 1))' SELECT ok( ST_DWithin( (SELECT geom FROM waterway.distance_marks_virtual @@ -50,8 +49,8 @@ ST_Boundary(ISRSrange_area(isrsrange( ('AT', 'XXX', '00001', '00000', 0)::isrs, ('AT', 'XXX', '00001', '00000', 1)::isrs), - ST_SetSRID(:'test_area'::geometry, - 4326)))::geography, + (SELECT ST_Collect(CAST(area AS geometry)) + FROM waterway.waterway_area))), 1) AND ST_DWithin( @@ -60,11 +59,12 @@ ST_Boundary(ISRSrange_area(isrsrange( ('AT', 'XXX', '00001', '00000', 0)::isrs, ('AT', 'XXX', '00001', '00000', 1)::isrs), - ST_SetSRID(:'test_area'::geometry, - 4326)))::geography, + (SELECT ST_Collect(CAST(area AS geometry)) + FROM waterway.waterway_area))), 1), 'Resulting polygon almost ST_Touches points corresponding to stretch'); +\set test_area 'POLYGON((-1 1, 2 1, 2 -1, -1 -1, -1 1))' SELECT ok( 2 = ST_NumGeometries( ISRSrange_area( @@ -100,3 +100,12 @@ 0)), 4326))), 'Self-intersecting multipolygon leads to one polygon in result'); + +SELECT ok( + ISRSrange_area( + isrsrange( + ('AT', 'XXX', '00001', '00000', 0)::isrs, + ('AT', 'XXX', '00001', '00000', 2)::isrs), + (SELECT ST_Collect(CAST(area AS geometry)) + FROM waterway.waterway_area)) IS NOT NULL, + 'Area generated from non-matching distance mark and non-contiguous axis'); diff -r 242104c338ff -r 3cf5d27a6c8b schema/run_tests.sh --- a/schema/run_tests.sh Thu Feb 28 17:28:54 2019 +0100 +++ b/schema/run_tests.sh Fri Mar 01 11:06:27 2019 +0100 @@ -28,7 +28,7 @@ -c 'SET client_min_messages TO WARNING' \ -c "DROP ROLE IF EXISTS $TEST_ROLES" \ -f tap_tests_data.sql \ - -c 'SELECT plan(57)' \ + -c 'SELECT plan(58)' \ -f isrs_tests.sql \ -f auth_tests.sql \ -f manage_users_tests.sql \ diff -r 242104c338ff -r 3cf5d27a6c8b schema/tap_tests_data.sql --- a/schema/tap_tests_data.sql Thu Feb 28 17:28:54 2019 +0100 +++ b/schema/tap_tests_data.sql Fri Mar 01 11:06:27 2019 +0100 @@ -76,18 +76,46 @@ ('AT', 'XXX', '00001', '00000', 1)::isrs, ST_SetSRID('POINT(1 0)'::geometry, 4326), 'someENC' +), ( + ('AT', 'XXX', '00001', '00000', 2)::isrs, + ST_SetSRID('POINT(1.6 0)'::geometry, 4326), + 'someENC' ); INSERT INTO waterway.waterway_axis (wtwaxs, objnam) VALUES ( ST_SetSRID(ST_CurveToLine( - 'CIRCULARSTRING(0 0, 0.5 0.5, 1 0, 1.5 -0.2, 2 0)'::geometry), + 'CIRCULARSTRING(0 0, 0.5 0.5, 0.6 0.4)'), 4326), 'testriver' ), ( + ST_SetSRID(ST_CurveToLine('CIRCULARSTRING(0.6 0.4, 1 0, 1.5 0)'), 4326), + 'testriver' +), ( ST_SetSRID('LINESTRING(0.5 0.5, 1 1)'::geometry, 4326), 'testriver' +), ( + ST_SetSRID('LINESTRING(1.5 0.1, 2 0)'::geometry, 4326), + 'testriver' ); +-- Simulate waterway area as non-intersecting buffers around axis +WITH RECURSIVE +buffer AS ( + SELECT id, ST_Buffer(wtwaxs, 10000, 'endcap=flat')::geometry AS buf + FROM waterway.waterway_axis), +cleaned AS ( + (SELECT ARRAY[id] AS ids, buf AS cbuf, buf AS others + FROM buffer ORDER BY id FETCH FIRST ROW ONLY) + UNION + (SELECT ids || id, + ST_Difference(buf, others), + ST_Union(buf, others) + FROM cleaned, buffer + WHERE id <> ALL(ids) + ORDER BY id ASC, ids DESC + FETCH FIRST ROW ONLY)) +INSERT INTO waterway.waterway_area (area) SELECT cbuf FROM cleaned; + INSERT INTO users.templates (template_name, country, template_data) VALUES ('AT', 'AT', '\x'), ('RO', 'RO', '\x');