Mercurial > gemma
changeset 2413:f39c4b432601 staging_consolidation
merge with default
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Thu, 28 Feb 2019 12:29:46 +0100 |
parents | 228387d5f2c5 (current diff) 0ed53a7a1221 (diff) |
children | df56bc53e86d |
files | client/src/components/Sidebar.vue client/src/components/ui/box/Header.vue client/src/router.js |
diffstat | 14 files changed, 352 insertions(+), 152 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/ImportStretches.vue Thu Feb 28 12:04:18 2019 +0100 +++ b/client/src/components/ImportStretches.vue Thu Feb 28 12:29:46 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; }
--- a/client/src/components/Sidebar.vue Thu Feb 28 12:04:18 2019 +0100 +++ b/client/src/components/Sidebar.vue Thu Feb 28 12:29:46 2019 +0100 @@ -225,13 +225,15 @@ } }, mounted() { - this.$store.dispatch("imports/getStaging").catch(error => { - 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); } }; </script> @@ -268,9 +270,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 {
--- a/client/src/components/identify/Identify.vue Thu Feb 28 12:04:18 2019 +0100 +++ b/client/src/components/identify/Identify.vue Thu Feb 28 12:29:46 2019 +0100 @@ -138,9 +138,21 @@ // 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] }); + let val = feature.getProperties()[key]; + + // if val is a valid json string, parse it and spread its values into + // the array + try { + let json = JSON.parse(val); + Object.keys(json).forEach(key => { + propsArray.push({ key, val: json[key] }); + }); + } catch (e) { + // if val is not json then just put the key value pair into the array + // avoid cyclic object value (I didn't further investigate what's the + // problem here but feature.getGeometryName() needs to be skipped) + if (key !== feature.getGeometryName()) propsArray.push({ key, val }); + } }); // change labels and remove unneeded properties
--- a/client/src/components/identify/formatter.js Thu Feb 28 12:04:18 2019 +0100 +++ b/client/src/components/identify/formatter.js Thu Feb 28 12:29:46 2019 +0100 @@ -10,7 +10,7 @@ } // remove certain props - let propsToRemove = ["nobjnm"]; + let propsToRemove = ["nobjnm", "reference_water_levels"]; if (propsToRemove.indexOf(p.key) !== -1) return null; return p;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/ui/UIBoxHeader.vue Thu Feb 28 12:29:46 2019 +0100 @@ -0,0 +1,63 @@ +<template> + <h6 class="box-header"> + <span class="box-title"> + <font-awesome-icon + :icon="icon" + class="box-icon" + v-if="icon" + fixed-width + /> + {{ $gettext(title) }} + </span> + <span class="box-close" @click="closeCallback" v-if="closeCallback"> + <font-awesome-icon icon="times" /> + </span> + </h6> +</template> + +<style lang="sass"> +.box-header + display: flex + justify-content: space-between + align-items: center + min-height: 35px + padding-left: .5rem + border-bottom: 1px solid #dee2e6 + color: $color-info + margin-bottom: 0 + padding: 0.25rem + font-weight: bold + .box-title + padding-left: 0.25rem + .box-icon + margin-right: 0.25rem + .box-close + color: #888 + padding: 3px 7px + border-radius: 0.25rem + cursor: pointer + transition: background-color 0.3s, color 0.3s + &:hover + color: #666 + background-color: #eee +</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): + * Markus Kottländer <markus.kottlaender@intevation.de> + */ + +export default { + props: ["icon", "title", "closeCallback"] +}; +</script>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/ui/UITableBody.vue Thu Feb 28 12:29:46 2019 +0100 @@ -0,0 +1,51 @@ +<template> + <transition-group + name="fade" + tag="div" + class="table-body text-left small" + :style="'overflow-y: auto; max-height: ' + maxHeight" + v-if="data.length" + > + <div + v-for="(item, index) in data" + :key="index" + :class="['border-top row mx-0', { active: active === item }]" + > + <slot :item="item" :index="index"></slot> + </div> + </transition-group> + <div v-else class="small text-center py-3 border-top"> + <translate>No results.</translate> + </div> +</template> + +<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): + * Markus Kottländer <markus.kottlaender@intevation.de> + */ + +export default { + props: { + data: { + type: Array + }, + maxHeight: { + type: String, + default: "18rem" + }, + active: { + type: [Object, Array] + } + } +}; +</script>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/ui/UITableHeader.vue Thu Feb 28 12:29:46 2019 +0100 @@ -0,0 +1,94 @@ +<template> + <div + :class="['table-header row no-gutters bg-light', { sortable: sortable }]" + > + <a + v-for="column in columns" + @click.prevent="sortBy(column.id)" + :key="column.id" + :class="[ + 'd-flex py-2 align-items-center justify-content-center small col ' + + column.class, + { active: sortColumn === column.id } + ]" + > + <span + :style="'opacity: ' + (sortColumn === column.id ? '1' : '0.3')" + v-if="sortable" + > + <font-awesome-icon + :icon="sortIcon(column.id)" + class="ml-1" + fixed-width + /> + </span> + {{ $gettext(column.title) }} + </a> + <div v-if="extraColumnForButtons" class="col"></div> + </div> +</template> + +<style lang="sass"> +.table-header + > a + border-right: solid 1px #e7e8e9 + background-color: #f8f9fa + a + outline: none + &:hover + text-decoration: none + background-color: #f8f9fa + &.sortable + a + cursor: pointer + &:hover, + &.active + background: #e7e8e9 +</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): + * Markus Kottländer <markus.kottlaender@intevation.de> + */ +export default { + props: { + columns: { type: Array }, + sortable: { type: Boolean, default: true }, + extraColumnForButtons: { type: Boolean, default: true } + }, + data() { + return { + sortColumn: null, + sortDirection: "ASC" + }; + }, + methods: { + sortIcon(id) { + if (this.sortColumn === id) { + return "sort-" + (this.sortDirection === "ASC" ? "down" : "up"); + } + return "sort"; + }, + sortBy(id) { + if (this.sortable) { + this.sortColumn = id; + this.sortDirection = this.sortDirection === "ASC" ? "DESC" : "ASC"; + this.$emit("sortingChanged", { + sortColumn: this.sortColumn, + sortDirection: this.sortDirection + }); + } + } + } +}; +</script>
--- a/client/src/components/ui/box/Header.vue Thu Feb 28 12:04:18 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -<template> - <h6 class="box-header"> - <span class="box-title"> - <font-awesome-icon - :icon="icon" - class="box-icon" - v-if="icon" - fixed-width - /> - {{ $gettext(title) }} - </span> - <span class="box-close" @click="closeCallback" v-if="closeCallback"> - <font-awesome-icon icon="times" /> - </span> - </h6> -</template> - -<style lang="sass"> -.box-header - display: flex - justify-content: space-between - align-items: center - min-height: 35px - padding-left: .5rem - border-bottom: 1px solid #dee2e6 - color: $color-info - margin-bottom: 0 - padding: 0.25rem - font-weight: bold - .box-title - padding-left: 0.25rem - .box-icon - margin-right: 0.25rem - .box-close - color: #888 - padding: 3px 7px - border-radius: 0.25rem - cursor: pointer - transition: background-color 0.3s, color 0.3s - &:hover - color: #666 - background-color: #eee -</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): - * Markus Kottländer <markus.kottlaender@intevation.de> - */ - -export default { - props: ["icon", "title", "closeCallback"] -}; -</script>
--- a/client/src/main.js Thu Feb 28 12:04:18 2019 +0100 +++ b/client/src/main.js Thu Feb 28 12:29:46 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;
--- a/client/src/router.js Thu Feb 28 12:04:18 2019 +0100 +++ b/client/src/router.js Thu Feb 28 12:29:46 2019 +0100 @@ -168,6 +168,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 +183,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); @@ -200,6 +202,7 @@ if (!isWaterwayAdmin) { next("/"); } else { + store.commit("application/searchQuery", ""); store.commit("application/showContextBox", true); store.commit("application/contextBoxContent", "staging"); store.commit("application/showSearchbar", true); @@ -238,6 +241,7 @@ if (!isSysadmin) { next("/"); } else { + store.commit("application/searchQuery", ""); store.commit("application/showContextBox", true); store.commit("application/contextBoxContent", "stretches"); store.commit("application/showSearchbar", true);
--- a/schema/gemma.sql Thu Feb 28 12:04:18 2019 +0100 +++ b/schema/gemma.sql Thu Feb 28 12:29:46 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
--- a/schema/isrs_functions.sql Thu Feb 28 12:04:18 2019 +0100 +++ b/schema/isrs_functions.sql Thu Feb 28 12:29:46 2019 +0100 @@ -82,25 +82,29 @@ ST_Dump(ST_Transform(area, z)) 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_Union(ST_Transform(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;
--- a/schema/isrs_tests.sql Thu Feb 28 12:04:18 2019 +0100 +++ b/schema/isrs_tests.sql Thu Feb 28 12:29:46 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(
--- a/schema/tap_tests_data.sql Thu Feb 28 12:04:18 2019 +0100 +++ b/schema/tap_tests_data.sql Thu Feb 28 12:29:46 2019 +0100 @@ -80,7 +80,12 @@ 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, 2 0)'), 4326), 'testriver' ), ( @@ -88,6 +93,24 @@ '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');