Mercurial > gemma
changeset 2486:bca9a7a89f28 octree-diff
Merged default into octree-diff branch.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Fri, 01 Mar 2019 18:28:50 +0100 |
parents | 86173ac7f222 (current diff) 219c5b57ef5b (diff) |
children | bd46ffbb944e |
files | |
diffstat | 14 files changed, 323 insertions(+), 159 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/ImportSoundingresults.vue Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/components/ImportSoundingresults.vue Fri Mar 01 18:28:50 2019 +0100 @@ -111,7 +111,10 @@ <a download="meta.json" :href="dataLink" - class="btn btn-outline-info" + :class="[ + 'btn btn-outline-info', + { disabled: !bottleneck || !importDate || !depthReference } + ]" > <translate>Download Meta.json</translate> </a> @@ -323,16 +326,18 @@ return this.$gettext("Confirm"); }, dataLink() { - return ( - "data:text/json;charset=utf-8," + - encodeURIComponent( - JSON.stringify({ - depthReference: this.depthReference, - bottleneck: this.bottleneck.properties.objnam, - date: this.importDate - }) - ) - ); + if (this.bottleneck && this.depthReference && this.import) { + return ( + "data:text/json;charset=utf-8," + + encodeURIComponent( + JSON.stringify({ + depthReference: this.depthReference, + bottleneck: this.bottleneck.properties.objnam, + date: this.importDate + }) + ) + ); + } }, depthReferenceOptions() { if (
--- a/client/src/components/Maplayer.vue Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/components/Maplayer.vue Fri Mar 01 18:28:50 2019 +0100 @@ -337,6 +337,21 @@ ); layer.data.setVisible(layer.isVisible); + layer = this.getLayerByName(LAYERS.GAUGES); + layer.data.getSource().setLoader( + this.buildVectorLoader( + { + featureNS: "gemma", + featurePrefix: "gemma", + featureTypes: ["gauges_geoserver"], + geometryName: "geom" + }, + "/internal/wfs", + layer.data.getSource() + ) + ); + layer.data.setVisible(layer.isVisible); + layer = this.getLayerByName(LAYERS.STRETCHES); layer.data.getSource().setLoader( this.buildVectorLoader(
--- a/client/src/components/Search.vue Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/components/Search.vue Fri Mar 01 18:28:50 2019 +0100 @@ -58,6 +58,12 @@ class="mr-1" fixed-width /> + <font-awesome-icon + icon="ruler" + v-if="entry.type === 'gauge'" + class="mr-1" + fixed-width + /> {{ entry.name }} </a> </div> @@ -269,6 +275,7 @@ if (resultEntry.type === "bottleneck") zoom = 17; if (resultEntry.type === "rhm") zoom = 15; if (resultEntry.type === "city") zoom = 13; + if (resultEntry.type === "gauge") zoom = 15; this.$store.commit("map/moveMap", { coordinates: resultEntry.geom.coordinates, zoom,
--- a/client/src/components/importoverview/ImportOverview.vue Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/components/importoverview/ImportOverview.vue Fri Mar 01 18:28:50 2019 +0100 @@ -7,10 +7,10 @@ /> <div class="d-flex flex-row w-100 justify-content-end"> <button - class="btn btn-sm btn-dark align-self-start mt-3 mr-3" + class="btn btn-sm btn-outline-info align-self-start mt-3 mr-3" @click="refresh" > - <translate>Refresh</translate> + <font-awesome-icon icon="redo"></font-awesome-icon> </button> </div> <div class="d-flex flex-row w-100 border-bottom"> @@ -55,7 +55,7 @@ icon="angle-down" fixed-width ></font-awesome-icon> - <Logs v-if="logsVisible"></Logs> + <Logs v-if="logsVisible" :reload="reload"></Logs> <div v-else> <h6> <small><translate>Logs</translate></small> @@ -85,6 +85,11 @@ export default { name: "importoverview", + data() { + return { + reload: false + }; + }, components: { Staging: () => import("./staging/Staging.vue"), Logs: () => import("./importlogs/Logs.vue") @@ -100,22 +105,30 @@ this.$store.commit("imports/setLogsVisibility", !this.logsVisible); }, refresh() { + this.reload = true; this.loadImportQueue(); this.loadLogs(); }, loadImportQueue() { - this.$store.dispatch("imports/getStaging").catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` + this.$store + .dispatch("imports/getStaging") + .then(() => { + this.reload = false; + }) + .catch(error => { + const { status, data } = error.response; + displayError({ + title: "Backend Error", + message: `${status}: ${data.message || data}` + }); }); - }); }, loadLogs() { this.$store .dispatch("imports/getImports") - .then(() => {}) + .then(() => { + this.reload = false; + }) .catch(error => { const { status, data } = error.response; displayError({
--- a/client/src/components/importoverview/importlogs/LogDetail.vue Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/components/importoverview/importlogs/LogDetail.vue Fri Mar 01 18:28:50 2019 +0100 @@ -51,72 +51,62 @@ </div> <div class="detailstable d-flex flex-row"> <div :class="collapse"> - <table class="table table-responsive"> - <thead> - <tr> - <th class="type pb-0"> - <small class="condensed"><translate>Kind</translate></small> - </th> - <th class="datetime pb-0"> - <a href="#" @click="sortAsc = !sortAsc" class="sort-link" - ><small class="condensed"><translate>Date</translate></small> - <small class="condensed" - ><font-awesome-icon - :icon="sortIcon" - class="ml-1" - ></font-awesome-icon></small - ></a> - </th> - <th class="message pb-0"> - <small class="condensed"><translate>Message</translate></small> - </th> - </tr> - </thead> - <tbody> - <tr - v-for="(entry, index) in sortedEntries" - :key="index" - class="detailsrow" + <div class="text-left"> + <small style="margin-right:10px" class="type condensed" + ><translate>Kind</translate></small + > + <a + href="#" + @click="sortAsc = !sortAsc" + style="margin-right:58px" + class="datetime sort-link" + ><small class="condensed"><translate>Date</translate></small> + <small class="message condensed" + ><font-awesome-icon + :icon="sortIcon" + class="ml-1" + ></font-awesome-icon></small + ></a> + <small class="condensed"><translate>Message</translate></small> + </div> + <div class="logentries"> + <div + v-for="(entry, index) in sortedEntries" + :key="index" + class="detailsrow text-left" + > + <small + :class="[ + 'condensed type', + { + 'text-danger': entry.kind.toUpperCase() == 'ERROR', + 'text-warning': entry.kind.toUpperCase() == 'WARN' + } + ]" + >{{ entry.kind.toUpperCase() }}</small > - <td class="type"> - <span - :class="[ - 'condensed', - { - 'text-danger': entry.kind.toUpperCase() == 'ERROR', - 'text-warning': entry.kind.toUpperCase() == 'WARN' - } - ]" - >{{ entry.kind.toUpperCase() }}</span - > - </td> - <td class="datetime"> - <span - :class="[ - 'condensed', - { - 'text-danger': entry.kind.toUpperCase() == 'ERROR', - 'text-warning': entry.kind.toUpperCase() == 'WARN' - } - ]" - >{{ formatDateTime(entry.time) }}</span - > - </td> - <td class="message"> - <span - :class="[ - 'condensed', - { - 'text-danger': entry.kind.toUpperCase() == 'ERROR', - 'text-warning': entry.kind.toUpperCase() == 'WARN' - } - ]" - >{{ entry.message }}</span - > - </td> - </tr> - </tbody> - </table> + <small + :class="[ + 'condensed datetime', + { + 'text-danger': entry.kind.toUpperCase() == 'ERROR', + 'text-warning': entry.kind.toUpperCase() == 'WARN' + } + ]" + >{{ formatDateTime(entry.time) }}</small + > + <small + :class="[ + 'condensed message', + { + 'text-danger': entry.kind.toUpperCase() == 'ERROR', + 'text-warning': entry.kind.toUpperCase() == 'WARN' + } + ]" + >{{ entry.message }}</small + > + </div> + </div> </div> </div> </div> @@ -265,6 +255,10 @@ </script> <style lang="scss" scoped> +.logentries { + overflow: auto; +} + .condensed { font-stretch: condensed; } @@ -316,41 +310,20 @@ } .detailsrow { - line-height: 0.1em; + line-height: 0.7rem; } .type { - width: 65px; white-space: nowrap; - padding-left: 0px; - border-top: 0px; - padding-bottom: $small-offset; } .datetime { - width: 200px; white-space: nowrap; - padding-left: 0px; - border-top: 0px; - padding-bottom: $small-offset; + padding-left: 10px; + padding-right: 10px; } .message { - min-width: 700px; white-space: nowrap; - padding-left: 0px; - border-top: 0px; - padding-bottom: $small-offset; -} - -thead, -tbody { - display: block; -} - -tbody { - height: 150px; - overflow-y: auto; - overflow-x: auto; } </style>
--- a/client/src/components/importoverview/importlogs/Logs.vue Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/components/importoverview/importlogs/Logs.vue Fri Mar 01 18:28:50 2019 +0100 @@ -10,7 +10,7 @@ <button @click="setFilter('pending')" :class="pendingStyle"> <translate>Pending</translate> </button> - <button @click="setFilter('rejected')" :class="rejectedStyle"> + <button @click="setFilter('declined')" :class="rejectedStyle"> <translate>Rejected</translate> </button> <button @click="setFilter('accepted')" :class="acceptedStyle"> @@ -44,17 +44,20 @@ */ import { mapState } from "vuex"; +import { displayError } from "@/lib/errors.js"; export default { name: "logsection", components: { LogDetail: () => import("./LogDetail.vue") }, + props: ["reload"], data() { return { + loading: false, failed: false, pending: false, - rejected: false, + declined: false, accepted: false, warning: false }; @@ -65,7 +68,7 @@ return { btn: true, "btn-sm": true, - "btn-light": !this.pending, + "btn-outline-info": !this.pending, "btn-info": this.pending }; }, @@ -73,7 +76,7 @@ return { btn: true, "btn-sm": true, - "btn-light": !this.failed, + "btn-outline-info": !this.failed, "btn-info": this.failed }; }, @@ -81,15 +84,15 @@ return { btn: true, "btn-sm": true, - "btn-light": !this.rejected, - "btn-info": this.rejected + "btn-outline-info": !this.declined, + "btn-info": this.declined }; }, acceptedStyle() { return { btn: true, "btn-sm": true, - "btn-light": !this.accepted, + "btn-outline-info": !this.accepted, "btn-info": this.accepted }; }, @@ -97,19 +100,31 @@ return { btn: true, "btn-sm": true, - "btn-light": !this.warning, + "btn-outline-info": !this.warning, "btn-info": this.warning }; } }, + watch: { + reload() { + if (!this.reload) return; + this.warning = false; + this.successful = false; + this.failed = false; + this.pending = false; + this.accepted = false; + this.declined = false; + } + }, methods: { setFilter(name) { + if (this.loading) return; this[name] = !this[name]; const allSet = this.failed && this.pending && this.accepted && - this.rejected && + this.declined && this.warning; if (allSet) { this.warning = false; @@ -117,8 +132,32 @@ this.failed = false; this.pending = false; this.accepted = false; - this.rejected = false; + this.declined = false; } + this.loadFiltered(); + }, + loadFiltered() { + this.loading = true; + const filter = [ + "failed", + "pending", + "accepted", + "declined", + "warning" + ].filter(x => this[x]); + this.$store + .dispatch("imports/getImports", filter) + .then(() => { + this.loading = false; + }) + .catch(error => { + this.loading = false; + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); } } }; @@ -127,6 +166,6 @@ <style lang="scss" scoped> .logdetails { overflow-y: auto; - height: 650px; + max-height: 650px; } </style>
--- a/client/src/components/systemconfiguration/PDFTemplates.vue Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/components/systemconfiguration/PDFTemplates.vue Fri Mar 01 18:28:50 2019 +0100 @@ -30,7 +30,10 @@ <td class="text-right"> <button class="btn btn-sm btn-dark" - @click="deleteTemplate(template)" + @click=" + deleteTemplate(template); + showSuccessUploadMsg = false; + " > <font-awesome-icon icon="trash" /> </button> @@ -39,7 +42,13 @@ </transition-group> </table> </transition> - <button class="btn btn-info mt-2" @click="$refs.uploadTemplate.click()"> + <button + class="btn btn-info mt-2" + @click=" + $refs.uploadTemplate.click(); + showSuccessUploadMsg = false; + " + > <font-awesome-icon icon="spinner" class="fa-spin fa-fw" @@ -48,6 +57,11 @@ <font-awesome-icon icon="upload" class="fa-fw" v-else /> <translate>Upload new template</translate> </button> + <div v-if="showSuccessUploadMsg" class="text-center"> + <p class="text-muted" v-translate> + {{ templateToUpload.name }} uploaded successfully + </p> + </div> </div> </div> </template> @@ -84,7 +98,9 @@ data() { return { templates: [], - uploading: false + uploading: false, + templateToUpload: "", + showSuccessUploadMsg: false }; }, methods: { @@ -119,6 +135,8 @@ ) .then(() => { this.loadTemplates(); + this.templateToUpload = template; + this.showSuccessUploadMsg = true; }) .catch(e => { const { status, data } = e.response; @@ -129,6 +147,7 @@ }) .finally(() => { this.uploading = false; + this.$refs.uploadTemplate.value = null; }); } else { displayError({
--- a/client/src/store/imports.js Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/store/imports.js Fri Mar 01 18:28:50 2019 +0100 @@ -160,7 +160,8 @@ }, getImports({ commit }, filter) { let queryParams = ""; - if (filter) queryParams = "?states=" + filter.join(","); + if (filter && filter.length > 0) + queryParams = "?states=" + filter.join(","); return new Promise((resolve, reject) => { HTTP.get("/imports" + queryParams, { headers: { "X-Gemma-Auth": localStorage.getItem("token") }
--- a/client/src/store/map.js Fri Mar 01 18:28:12 2019 +0100 +++ b/client/src/store/map.js Fri Mar 01 18:28:50 2019 +0100 @@ -20,7 +20,15 @@ import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js"; import OSM from "ol/source/OSM"; import Draw from "ol/interaction/Draw.js"; -import { Icon, Stroke, Style, Fill, Text, Circle } from "ol/style.js"; +import { + Icon, + Stroke, + Style, + Fill, + Text, + Circle, + RegularShape +} from "ol/style.js"; import VectorSource from "ol/source/Vector.js"; import Point from "ol/geom/Point.js"; import { bbox as bboxStrategy } from "ol/loadingstrategy"; @@ -47,6 +55,7 @@ BOTTLENECKISOLINE: "Bottleneck isolines", DISTANCEMARKS: "Distance marks", DISTANCEMARKSAXIS: "Distance marks, Axis", + GAUGES: "Gauges", DRAWTOOL: "Draw Tool", CUTTOOL: "Cut Tool" }; @@ -369,6 +378,39 @@ showInLegend: true }, { + name: LAYERS.GAUGES, + forLegendStyle: { point: true, resolution: 8 }, + data: new VectorLayer({ + source: new VectorSource({ + strategy: bboxStrategy + }), + style: function(feature) { + return new Style({ + image: new RegularShape({ + radius: 6, + fill: new Fill({ color: "rgba(255, 255, 0, 0.1)" }), + stroke: new Stroke({ color: "red", width: 1 }), + points: 3, + rotation: 0, + angle: 0 + }), + text: new Text({ + font: '10px "Open Sans", "sans-serif"', + offsetY: 10, + fill: new Fill({ + color: "black" + }), + text: feature.get("objname") + }) + }); + }, + maxResolution: 100, + minResolution: 0 + }), + isVisible: true, + showInLegend: true, + }, + { name: LAYERS.DRAWTOOL, data: new VectorLayer({ source: new VectorSource({ wrapX: false }),
--- a/schema/demo-data/published_services.sql Fri Mar 01 18:28:12 2019 +0100 +++ b/schema/demo-data/published_services.sql Fri Mar 01 18:28:50 2019 +0100 @@ -14,6 +14,7 @@ INSERT INTO sys_admin.published_services (name) VALUES ('waterway.stretches_geoserver'), ('waterway.fairway_dimensions'), + ('waterway.gauges_geoserver'), ('waterway.distance_marks_ashore_geoserver'), ('waterway.distance_marks_geoserver'), ('waterway.sounding_results_contour_lines_geoserver'),
--- a/schema/gemma.sql Fri Mar 01 18:28:12 2019 +0100 +++ b/schema/gemma.sql Fri Mar 01 18:28:50 2019 +0100 @@ -252,6 +252,28 @@ value int NOT NULL ) + CREATE VIEW gauges_geoserver AS + SELECT + g.location, + isrs_asText(g.location) AS isrs_code, + g.objname, + g.geom, + g.applicability_from_km, + g.applicability_to_km, + g.validity, + g.zero_point, + g.geodref, + g.date_info, + g.source_organization, + json_strip_nulls(json_object_agg(coalesce(r.depth_reference,'empty'), + r.value)) + AS reference_water_levels + FROM gauges g LEFT JOIN LATERAL ( + SELECT gauge_id, depth_reference, value + FROM gauges_reference_water_levels + ) r ON r.gauge_id = g.location + GROUP BY g.location + CREATE TABLE gauge_measurements ( id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, fk_gauge_id isrs NOT NULL REFERENCES gauges, @@ -482,7 +504,7 @@ CHECK(ST_IsValid(CAST(area AS geometry))), surtyp varchar REFERENCES survey_types, coverage varchar REFERENCES coverage_types, - depth_reference varchar(4) NOT NULL REFERENCES depth_references, + depth_reference varchar(4) NOT NULL, -- REFERENCES depth_references, point_cloud geography(MULTIPOINTZ, 4326) NOT NULL CHECK(ST_IsSimple(CAST(point_cloud AS geometry))), octree_checksum varchar, @@ -630,6 +652,7 @@ -- Configure primary keys for geoserver views INSERT INTO waterway.gt_pk_metadata VALUES + ('waterway', 'gauges_geoserver', 'location'), ('waterway', 'distance_marks_geoserver', 'location_code'), ('waterway', 'distance_marks_ashore_geoserver', 'id'), ('waterway', 'bottlenecks_geoserver', 'id'),
--- a/schema/isrs_functions.sql Fri Mar 01 18:28:12 2019 +0100 +++ b/schema/isrs_functions.sql Fri Mar 01 18:28:50 2019 +0100 @@ -66,11 +66,8 @@ 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))) + SELECT ST_ShortestLine( + ST_Boundary(refgeom), ST_Boundary(geom)) UNION -- Linestring to be added SELECT geom)))
--- a/schema/isrs_tests.sql Fri Mar 01 18:28:12 2019 +0100 +++ b/schema/isrs_tests.sql Fri Mar 01 18:28:50 2019 +0100 @@ -42,26 +42,24 @@ ) IS NULL, 'ISRSrange_area returns NULL, if given area does not intersect with axis'); -SELECT ok( - ST_DWithin( - (SELECT geom FROM waterway.distance_marks_virtual - WHERE location_code = ('AT', 'XXX', '00001', '00000', 0)::isrs), - ST_Boundary(ISRSrange_area(isrsrange( - ('AT', 'XXX', '00001', '00000', 0)::isrs, - ('AT', 'XXX', '00001', '00000', 1)::isrs), - (SELECT ST_Collect(CAST(area AS geometry)) - FROM waterway.waterway_area))), - 1) - AND - ST_DWithin( - (SELECT geom FROM waterway.distance_marks_virtual - WHERE location_code = ('AT', 'XXX', '00001', '00000', 1)::isrs), - ST_Boundary(ISRSrange_area(isrsrange( - ('AT', 'XXX', '00001', '00000', 0)::isrs, - ('AT', 'XXX', '00001', '00000', 1)::isrs), - (SELECT ST_Collect(CAST(area AS geometry)) - FROM waterway.waterway_area))), - 1), +SELECT results_eq($$ + SELECT every(ST_DWithin( + ST_Boundary(ISRSrange_area( + isrsrange( + ('AT', 'XXX', '00001', '00000', 0)::isrs, + ('AT', 'XXX', '00001', '00000', 1)::isrs), + (SELECT ST_Collect(CAST(area AS geometry)) + FROM waterway.waterway_area))), + geom, + 1)) + FROM waterway.distance_marks_virtual + WHERE location_code IN( + ('AT', 'XXX', '00001', '00000', 0)::isrs, + ('AT', 'XXX', '00001', '00000', 1)::isrs) + $$, + $$ + SELECT true + $$, 'Resulting polygon almost ST_Touches points corresponding to stretch'); \set test_area 'POLYGON((-1 1, 2 1, 2 -1, -1 -1, -1 1))' @@ -101,11 +99,22 @@ 4326))), 'Self-intersecting multipolygon leads to one polygon in result'); -SELECT ok( - ISRSrange_area( - isrsrange( +SELECT results_eq($$ + SELECT every(ST_DWithin( + ST_Boundary(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))), + geom, + 1)) + FROM waterway.distance_marks_virtual + WHERE location_code IN( ('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, + ('AT', 'XXX', '00001', '00000', 2)::isrs) + $$, + $$ + SELECT true + $$, 'Area generated from non-matching distance mark and non-contiguous axis');
--- a/schema/search_functions.sql Fri Mar 01 18:28:12 2019 +0100 +++ b/schema/search_functions.sql Fri Mar 01 18:28:50 2019 +0100 @@ -51,10 +51,30 @@ END; $$; +CREATE OR REPLACE FUNCTION search_gauges(search_string text) RETURNS jsonb + LANGUAGE plpgsql + AS $$ +DECLARE + _result jsonb; +BEGIN + SELECT COALESCE(json_agg(r),'[]') + INTO _result + FROM (SELECT objname AS name, + ST_AsGeoJSON(geom)::json AS geom, + 'gauge' AS type + FROM waterway.gauges + WHERE objname ILIKE '%' || search_string || '%' + ORDER BY name) r; + RETURN _result; +END; +$$; + CREATE OR REPLACE FUNCTION search_most(search_string text) RETURNS jsonb LANGUAGE plpgsql AS $$ BEGIN - RETURN search_bottlenecks(search_string) || search_cities(search_string); + RETURN search_bottlenecks(search_string) + || search_gauges(search_string) + || search_cities(search_string); END; $$;