Mercurial > gemma
changeset 1948:8e6f6c376b75
merge translations from weblate
author | Fadi Abbud <fadi.abbud@intevation.de> |
---|---|
date | Tue, 22 Jan 2019 11:22:17 +0100 |
parents | 4235fa8f59d7 (diff) 99196fbc3897 (current diff) |
children | fd74afccfdf1 |
files | client/src/locale/ro_RO/LC_MESSAGES/app.po |
diffstat | 49 files changed, 1794 insertions(+), 584 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgtags Fri Jan 18 06:51:49 2019 +0000 +++ b/.hgtags Tue Jan 22 11:22:17 2019 +0100 @@ -1,2 +1,4 @@ 532c8392048fe720bf31b7efb475591ab95a74ec v1-uat1 136aaa7f00af0b500c5c75f9fd9aabfc7fa526ee v1.1.0 +724758455a4e44d25b096f00bcb353ceda0e0d57 v2-preview20190111 +0059aa870a396e2112efae43f822c0c133454d7a v2
--- a/client/.env Fri Jan 18 06:51:49 2019 +0000 +++ b/client/.env Tue Jan 22 11:22:17 2019 +0100 @@ -5,8 +5,9 @@ VUE_APP_API_URL=/api/ VUE_BACKEND_API_URL=http://gemma_backend:8000 -#URL of secondary logo -VUE_APP_SECONDARY_LOGO_URL= - #Path of vendored images is copied during a webpack build VUE_APP_VENDOR_IMG_PATH= + +#Logos to be potentially loaded by the SPA. Can be left blank. +VUE_APP_SECONDARY_LOGO_URL= +VUE_APP_LOGO_FOR_PDF_URL=
--- a/client/package.json Fri Jan 18 06:51:49 2019 +0000 +++ b/client/package.json Tue Jan 22 11:22:17 2019 +0100 @@ -32,6 +32,7 @@ "debounce": "^1.2.0", "font-awesome": "^4.7.0", "glob-all": "^3.1.0", + "jspdf": "^1.5.3", "locale2": "^2.2.0", "ol": "^5.3.0", "path": "^0.12.7",
--- a/client/src/components/ImportStretches.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/ImportStretches.vue Tue Jan 22 11:22:17 2019 +0100 @@ -2,193 +2,231 @@ <div class="d-flex flex-column mb-3"> <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center"> <font-awesome-icon icon="road" class="mr-2"></font-awesome-icon> - <translate>Define section and stretches</translate> + <translate>Define stretches</translate> </h6> - <div class="ml-3 mr-3"> - <div class="d-flex flex-row justify-content-between"> - <div class="mt-2 w-50 mr-2 text-left"> - <small class="text-muted"> <translate>ID</translate> </small> - <input - id="id" - type="text" - class="form-control" - placeholder="AT_Section_12" - aria-label="id" - v-model="id" - /> - <span class="text-left text-danger"> - <small v-if="idError && !id"> - <translate>Please enter an id</translate> - </small> - </span> - </div> - <div class="mt-2 w-50 ml-2 text-left"> - <small class="text-muted"><translate>Function</translate> </small> - <select v-model="funktion" class="custom-select"> - <option value="section"><translate>Section</translate></option> - <option v-if="isSysAdmin" value="stretch" - ><translate>Stretch</translate></option - > - </select> - <span class="text-left text-danger"> - <small v-if="funktionError && !funktion"> - <translate>Please enter a function</translate> - </small> - </span> - </div> + <div v-if="!edit" class="mb-3 mr-3 ml-3 text-left"> + <table v-if="stretches.length > 0" class="table"> + <thead> + <tr> + <th class="header"><translate>Name</translate></th> + <th class="header"><translate>Datum</translate></th> + <th class="header"><translate>Source organization</translate></th> + <th class="tools"> </th> + <th class="tools"> </th> + </tr> + </thead> + <tbody> + <tr class="small" v-for="(stretch, index) in stretches" :key="index"> + <td class=""> + <a @click="moveMapToStretch(index)" href="#">{{ + stretch.properties.name + }}</a> + </td> + <td class=""> + {{ formatSurveyDate(stretch.properties["date_info"]) }} + </td> + <td>{{ stretch.properties["source_organization"] }}</td> + <td> + <font-awesome-icon + @click="editStretch(index)" + icon="pencil-alt" + fixed-width + ></font-awesome-icon> + </td> + <td> + <font-awesome-icon + @click="deleteStretch(index)" + icon="trash" + fixed-width + ></font-awesome-icon> + </td> + </tr> + </tbody> + </table> + <div class="mt-3" v-if="stretches.length == 0"> + <translate>No results.</translate> </div> - <div class="d-flex flex-column justify-content-between"> - <div class="mt-2 text-left"> - <small class="text-muted"> <translate>Start rhm</translate> </small> - <div class="d-flex flex-row"> + </div> + <div v-if="edit"> + <div class="ml-3 mr-3"> + <div class="d-flex flex-row justify-content-between"> + <div class="mt-2 w-50 mr-2 text-left"> + <small class="text-muted"> <translate>ID</translate> </small> <input - id="startrhm" + id="id" type="text" class="form-control" - placeholder="e.g. ATXXX00001000000019900" - aria-label="startrhm" - v-model="startrhm" + placeholder="AT_Section_12" + aria-label="id" + v-model="id" /> - <span class="input-group-text"> - <font-awesome-icon - @click="togglePipette('start')" - :class="{ 'text-info': pipetteStart }" - icon="bullseye" - ></font-awesome-icon> + <span class="text-left text-danger"> + <small v-if="idError && !id"> + <translate>Please enter an id</translate> + </small> + </span> + </div> + <div class="mt-2 w-50 ml-2 text-left"> + <div> + <small class="text-muted"> + <translate>Countrycode</translate> + </small> + <input + id="countryCode" + type="text" + class="form-control" + placeholder="AT" + aria-label="id" + v-model="countryCode" + /> + <span class="text-left text-danger"> + <small v-if="countryCodeError && !countryCode"> + <translate>Please enter a countrycode </translate> + </small> + </span> + </div> + <div class="w-50 ml-2"></div> + </div> + </div> + <div class="d-flex flex-column justify-content-between"> + <div class="mt-2 text-left"> + <small class="text-muted"> <translate>Start rhm</translate> </small> + <div class="d-flex flex-row"> + <input + id="startrhm" + type="text" + class="form-control" + placeholder="e.g. ATXXX00001000000019900" + aria-label="startrhm" + v-model="startrhm" + /> + <span class="input-group-text"> + <font-awesome-icon + @click="togglePipette('start')" + :class="{ 'text-info': pipetteStart }" + icon="bullseye" + ></font-awesome-icon> + </span> + </div> + <span class="text-left text-danger"> + <small v-if="startrhmError && !startrhm"> + <translate>Please enter a start point</translate> + </small> </span> </div> - <span class="text-left text-danger"> - <small v-if="startrhmError && !startrhm"> - <translate>Please enter a start point</translate> - </small> - </span> + <div class="mt-2 text-left"> + <small class="text-muted"> <translate>End rhm</translate> </small> + <div class="d-flex flex-row"> + <input + id="endrhm" + type="text" + class="form-control" + placeholder="e.g. ATXXX00001000000019900" + aria-label="endrhm" + v-model="endrhm" + /> + <span class="input-group-text"> + <font-awesome-icon + @click="togglePipette('end')" + :class="{ 'text-info': pipetteEnd }" + icon="bullseye" + ></font-awesome-icon> + </span> + </div> + <span class="text-left text-danger"> + <small v-if="endrhmError && !endrhm"> + <translate>Please enter an end point</translate> + </small> + </span> + </div> </div> - <div class="mt-2 text-left"> - <small class="text-muted"> <translate>End rhm</translate> </small> - <div class="d-flex flex-row"> + <div class="d-flex flex-row justify-content-between"> + <div class="mt-2 mr-2 w-50 text-left"> + <small class="text-muted"> + <translate>Object name</translate> + </small> <input - id="endrhm" + id="objbn" type="text" class="form-control" - placeholder="e.g. ATXXX00001000000019900" - aria-label="endrhm" - v-model="endrhm" + placeholder="" + aria-label="objbn" + v-model="objbn" /> - <span class="input-group-text"> - <font-awesome-icon - @click="togglePipette('end')" - :class="{ 'text-info': pipetteEnd }" - icon="bullseye" - ></font-awesome-icon> + <span class="text-left text-danger"> + <small v-if="objbnError && !objbn"> + <translate>Please enter an objectname</translate> + </small> + </span> + </div> + <div class="mt-2 ml-2 w-50 text-left"> + <small class="text-muted"> + <translate>National Object name</translate> + </small> + <input + id="nobjbn" + type="text" + class="form-control" + placeholder="" + aria-label="nobjbn" + v-model="nobjbn" + /> + <span class="text-left text-danger"> + <small v-if="nobjbnError && !nobjbn"> + <translate>Please enter an objectname</translate> + </small> </span> </div> - <span class="text-left text-danger"> - <small v-if="endrhmError && !endrhm"> - <translate>Please enter an end point</translate> - </small> - </span> + </div> + <div class="d-flex flex-row justify-content-between"> + <div class="mt-2 mr-2 w-50 text-left"> + <small class="text-muted"> <translate>Date info</translate> </small> + <input + id="date_info" + type="date" + class="form-control" + placeholder="date_info" + aria-label="date_info" + v-model="date_info" + /> + <span class="text-left text-danger"> + <small v-if="date_infoError && !date_info"> + <translate>Please enter a date</translate> + </small> + </span> + </div> + <div class="mt-2 ml-2 w-50 text-left"> + <small class="text-muted"> <translate>Source</translate> </small> + <input + id="source" + type="text" + class="form-control" + placeholder="source" + aria-label="source" + v-model="source" + /> + <span class="text-left text-danger"> + <small v-if="sourceError && !source"> + <translate>Please enter a source</translate> + </small> + </span> + </div> </div> </div> - <div class="d-flex flex-row justify-content-between"> - <div class="mt-2 mr-2 w-50 text-left"> - <small class="text-muted"> <translate>Object name</translate> </small> - <input - id="objbn" - type="text" - class="form-control" - placeholder="" - aria-label="objbn" - v-model="objbn" - /> - <span class="text-left text-danger"> - <small v-if="objbnError && !objbn"> - <translate>Please enter an objectname</translate> - </small> - </span> - </div> - <div class="mt-2 ml-2 w-50 text-left"> - <small class="text-muted"> - <translate>National Object name</translate> - </small> - <input - id="nobjbn" - type="text" - class="form-control" - placeholder="" - aria-label="nobjbn" - v-model="nobjbn" - /> - <span class="text-left text-danger"> - <small v-if="nobjbnError && !nobjbn"> - <translate>Please enter an objectname</translate> - </small> - </span> - </div> - </div> - <div class="d-flex flex-row justify-content-between"> - <div class="mt-2 mr-2 w-50 text-left"> - <small class="text-muted"> <translate>Date info</translate> </small> - <input - id="date_info" - type="date" - class="form-control" - placeholder="date_info" - aria-label="date_info" - v-model="date_info" - /> - <span class="text-left text-danger"> - <small v-if="date_infoError && !date_info"> - <translate>Please enter a date</translate> - </small> - </span> - </div> - <div class="mt-2 ml-2 w-50 text-left"> - <small class="text-muted"> <translate>Source</translate> </small> - <input - id="source" - type="text" - class="form-control" - placeholder="source" - aria-label="source" - v-model="source" - /> - <span class="text-left text-danger"> - <small v-if="sourceError && !source"> - <translate>Please enter a source</translate> - </small> - </span> - </div> - </div> - <div - v-if="funktion === 'stretch'" - class="d-flex flex-row justify-content-between" - > - <div class="mt-2 w-50 mr-2 text-left "> - <small class="text-muted"> <translate>Countrycode</translate> </small> - <input - id="countryCode" - type="text" - class="form-control" - placeholder="AT" - aria-label="id" - v-model="id" - /> - <span class="text-left text-danger"> - <small v-if="countryCodeError && !countryCode"> - <translate>Please enter a countrycode </translate> - </small> - </span> - </div> - <div class="w-50 ml-2"></div> + <div class="text-right mt-2 mr-3 mb-3"> + <button @click="edit = false" class="btn btn-warning mr-2">Back</button> + <button + @click="save" + type="submit" + class="shadow-sm btn btn-info submit-button" + > + <translate>Submit</translate> + </button> </div> </div> - <div class="text-right mt-2 mr-3 mb-3"> - <button - @click="submit" - type="submit" - class="shadow-sm btn btn-info submit-button" - > - <translate>Submit</translate> + <div class="text-right mr-3"> + <button v-if="!edit" @click="startEdit()" class="btn btn-info"> + <translate>New stretch</translate> </button> </div> </div> @@ -209,12 +247,15 @@ * Thomas Junk <thomas.junk@intevation.de> */ import { mapState, mapGetters } from "vuex"; -import { displayInfo } from "@/lib/errors.js"; +import { displayError, displayInfo } from "@/lib/errors.js"; +import { formatSurveyDate } from "@/lib/date.js"; +import center from "@turf/center"; export default { name: "importstretches", data() { return { + edit: false, id: "", funktion: "", startrhm: "", @@ -237,7 +278,74 @@ countryCodeError: false }; }, + mounted() { + this.edit = false; + this.loadStretches(); + }, methods: { + editStretch(index) { + const { date_info, name, objnam, nobjnam } = this.stretches[ + index + ].properties; + this.date_info = date_info; + this.id = name; + this.nobjbn = nobjnam; + this.objbn = objnam; + this.source = this.stretches[index]; + this.edit = true; + }, + deleteStretch(index) { + displayInfo({ + title: this.$gettext("Not implemented"), + message: this.$gettext("Deleting " + this.stretches[index].id) + }); + }, + moveMapToStretch(index) { + const { coordinates } = center(this.stretches[index]).geometry; + this.$store.commit("map/moveMap", { + coordinates: coordinates, + zoom: 17, + preventZoomOut: true + }); + }, + formatSurveyDate(d) { + return formatSurveyDate(d); + }, + loadStretches() { + this.$store.dispatch("imports/loadStretches").catch(error => { + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + }, + clean() { + this.id = ""; + this.funktion = ""; + this.startrhm = ""; + this.endrhm = ""; + this.objbn = ""; + this.nobjbn = ""; + this.countryCode = ""; + this.date_info = new Date().toISOString().split("T")[0]; + this.source = ""; + this.pipetteStart = false; + this.pipetteEnd = false; + this.idError = false; + this.funktionError = false; + this.startrhmError = false; + this.endrhmError = false; + this.objbnError = false; + this.nobjbnError = false; + this.date_infoError = false; + this.sourceError = false; + this.countryCodeError = false; + }, + startEdit() { + this.clean(); + this.edit = true; + }, togglePipette(t) { if (t === "start") { this.pipetteStart = !this.pipetteStart; @@ -254,7 +362,6 @@ "startrhm", "endrhm", "objbn", - "nobjbn", "date_info", "source" ]; @@ -266,12 +373,37 @@ } }); }, - submit() { + save() { this.validate(); - displayInfo({ - title: this.$gettext("Sections"), - message: this.$gettext("Not implemented!") - }); + const data = { + name: this.id, + from: this.startrhm, + to: this.endrhm, + "source-organization": this.source, + "date-info": this.date_info, + objnam: this.objbn, + nobjnam: this.nobjbn, + countries: this.countryCode.split(",") + }; + this.$store + .dispatch("imports/saveStretch", data) + .then(() => { + displayInfo({ + title: this.$gettext("Import"), + message: this.$gettext("Starting import of stretch") + }); + this.clean(); + this.edit = false; + this.loadStretches(); + }) + .catch(error => { + console.log(error); + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); } }, watch: { @@ -293,7 +425,8 @@ }, computed: { ...mapState("map", ["identifiedFeatures", "currentMeasurement"]), - ...mapGetters("user", ["isSysAdmin"]) + ...mapGetters("user", ["isSysAdmin"]), + ...mapState("imports", ["stretches"]) } }; </script>
--- a/client/src/components/Maplayer.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/Maplayer.vue Tue Jan 22 11:22:17 2019 +0100 @@ -14,25 +14,6 @@ .mapfull { height: 100vh; } - -// the following css part is for browser-printing based pdf generation -@page { - size: A4 landscape !important; - margin: 4mm !important; - // according to https://www.w3.org/TR/css-page-3/#page-size-prop - // we shall now have 210 - 2*4 = 202 mm width and 297 - 2*4 = 289 mm height -} - -@media print { - .mapfull { - width: 2000px; - height: 2828px; - } - .mapsplit { - width: 2000px; - height: 2828px; - } -} </style> <script> @@ -42,7 +23,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * - * Copyright (C) 2018 by via donau + * Copyright (C) 2018, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * @@ -155,34 +136,6 @@ } layer.isVisible = exists; layer.data.setVisible(exists); - }, - onBeforePrint(/* evt */) { - // console.log("onBeforePrint(", evt ,")"); - // - // the following code shows how to get the current map canvas - // and change it, however this does not work well enough, as - // another mechanism seems to update the size again before the rendering - // for printing is done: - // console.log(this.openLayersMap.getViewport()); - // var canvas = this.openLayersMap.getViewport().getElementsByTagName("canvas")[0]; - // console.log(canvas); - // canvas.width=1000; - // canvas.height=1414; - // - // An experiment which also did not work: - // this.openLayersMap.setSize([1000, 1414]); // estimate portait DIN A4 - // - // according to documentation - // http://openlayers.org/en/latest/apidoc/module-ol_PluggableMap-PluggableMap.html#updateSize - // "Force a recalculation of the map viewport size. This should be called when third-party code changes the size of the map viewport." - // but did not help - // this.openLayersMap.updateSize(); - }, - onAfterPrint(/* evt */) { - // could be used to undo changes that have been done for printing - // though https://www.tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/ - // reported that this was not feasable (back then). - // console.log("onAfterPrint(", evt, ")"); } }, watch: { @@ -316,16 +269,16 @@ ) ); - layer = this.getLayerByName("Waterway Area, named"); + layer = this.getLayerByName("Stretches"); layer.data.getSource().setLoader( this.buildVectorLoader( { featureNS: "gemma", featurePrefix: "gemma", - featureTypes: ["hydro_seaare"], + featureTypes: ["stretches_geoserver"], geometryName: "geom" }, - "/external/d4d", + "/internal/wfs", layer.data.getSource() ) ); @@ -373,9 +326,6 @@ console.log(error); }); - window.addEventListener("beforeprint", this.onBeforePrint); - window.addEventListener("afterprint", this.onAfterPrint); - // so none is shown this.updateBottleneckFilter("does_not_exist", "1999-10-01"); this.$store.dispatch("map/enableIdentifyTool");
--- a/client/src/components/Pdftool.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/Pdftool.vue Tue Jan 22 11:22:17 2019 +0100 @@ -7,8 +7,8 @@ > <div style="width: 20rem"> <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center"> - <font-awesome-icon icon="file-pdf" class="mr-2"></font-awesome-icon - ><translate>Generate PDF</translate> + <font-awesome-icon icon="file-pdf" class="mr-2"></font-awesome-icon> + <translate>Generate PDF</translate> <font-awesome-icon icon="times" class="ml-auto text-muted" @@ -16,15 +16,21 @@ ></font-awesome-icon> </h6> <div class="p-3"> - <b><translate>Chose format:</translate></b> + <b><translate>Choose format:</translate></b> <select v-model="form.format" class="form-control d-block w-100"> <option value="landscape"><translate>landscape</translate></option> <option value="portrait"><translate>portrait</translate></option> </select> + <select v-model="form.resolution" class="form-control d-block w-100"> + <option value="80">80 dpi</option> + <option value="120">120 dpi</option> + <option value="200">200 dpi</option> + </select> <select v-model="form.paperSize" class="form-control d-block w-100"> <option value="a3"><translate>ISO A3</translate></option> <option value="a4"><translate>ISO A4</translate></option> </select> + <!-- <small class="d-block my-2"> <input type="radio" @@ -33,22 +39,24 @@ v-model="form.downloadType" selected /> - <label for="pdfexport-downloadtype-download" class="ml-1 mr-2" - ><translate>Download</translate></label - > + <label for="pdfexport-downloadtype-download" class="ml-1 mr-2"> + <translate>Download</translate> + </label> <input type="radio" id="pdfexport-downloadtype-open" value="open" v-model="form.downloadType" /> - <label for="pdfexport-downloadtype-open" class="ml-1" - ><translate>Open in new window</translate></label - > + <label for="pdfexport-downloadtype-open" class="ml-1"> + <translate>Open in new window</translate> + </label> </small> + --> <button @click="download" type="button" + v-bind:disabled="!readyToGenerate" class="btn btn-sm btn-info d-block w-100" > <translate>Generate PDF</translate> @@ -65,15 +73,19 @@ * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * - * Copyright (C) 2018 by via donau + * Copyright (C) 2018, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): * * Markus Kottländer <markus.kottlaender@intevation.de> * * Bernhard E. Reiter <bernhard@intevation.de> + * * Fadi Abbud <fadi.abbud@intevation.de> */ -import { mapState } from "vuex"; +import { mapGetters, mapState } from "vuex"; +import jsPDF from "jspdf"; +import { getPointResolution } from "ol/proj.js"; +import locale2 from "locale2"; var paperSizes = { // in millimeter, landscape [width, height] @@ -88,34 +100,120 @@ form: { format: "landscape", paperSize: "a4", - downloadType: "download" - } + downloadType: "download", + resolution: "120" + }, + logoImageForPDF: null, // a HTMLImageElement instance + readyToGenerate: true // if the user is allowed to press the button }; }, computed: { - ...mapState("application", ["showPdfTool"]), - ...mapState("bottlenecks", ["selectedSurvey"]) + ...mapState("application", ["showPdfTool", "logoForPDF"]), + ...mapState("bottlenecks", ["selectedSurvey"]), + ...mapState("map", ["openLayersMap", "isolinesLegendImgDataURL"]), + ...mapGetters("map", ["getLayerByName"]), + ...mapState("user", ["user"]) }, methods: { - isLandscape() { - return this.form.format !== "portrait"; - }, download() { - /* eslint-disable no-unused-vars */ - const width = this.isLandscape() - ? paperSizes[this.form.paperSize][0] - : paperSizes[this.form.paperSize][1]; - const height = this.isLandscape() - ? paperSizes[this.form.paperSize][1] - : paperSizes[this.form.paperSize][0]; + // disable button while working on it + this.readyToGenerate = false; + + console.log( + "will generate pdf with", + this.form.paperSize, + this.form.format, + this.form.resolution + ); + var width, height; + + if (this.form.format !== "portrait") { + // landscape, default + width = paperSizes[this.form.paperSize][0]; + height = paperSizes[this.form.paperSize][1]; + } else { + // switch width and height + width = paperSizes[this.form.paperSize][1]; + height = paperSizes[this.form.paperSize][0]; + } + + // FUTURE: consider margins + + // dots per mm = dots per inch / (25.4 mm/inch) + var pixelsPerMapMillimeter = this.form.resolution / 25.4; + var mapSizeForPrint = [ + // in pixel + Math.round(width * pixelsPerMapMillimeter), + Math.round(height * pixelsPerMapMillimeter) + ]; + + // generate PDF and open it + // our units are milimeters; width 0 x height 0 is left upper corner + + // Step 1 prepare and save current map extend + // Then add callback "rendercomplete" for Step 3 + // which will generate the pdf and resets the map view + // Step 2 which starts rendering a map with the necessary image size + + var map = this.openLayersMap; + var mapSize = map.getSize(); // size in pixels of the map in the DOM + // Calculate the extent for the current view state and the passed size. + // The size is the pixel dimensions of the box into which the calculated + // extent should fit. + var mapExtent = map.getView().calculateExtent(mapSize); - // TODO: replace this src with an API reponse after actually generating PDFs - let src = !this.isLandscape() - ? "/img/PrintTemplate-Var2-Landscape.pdf" - : "/img/PrintTemplate-Var2-Portrait.pdf"; + var pdf = new jsPDF(this.form.format, "mm", this.form.paperSize); + var northarrowSize = 3; + var self = this; + + // set a callback for after the next complete rendering of the map + map.once("rendercomplete", function(event) { + let canvas = event.context.canvas; + + // because we are using Web Mercator, a pixel represents + // a differently sizes spot depending on the place of the map. + // So we use a value calculated from the center of the current view. + let view = map.getView(); + let proj = view.getProjection(); + let metersPerPixel = // average meters (reality) per pixel (map) + getPointResolution(proj, view.getResolution(), view.getCenter()) * + proj.getMetersPerUnit(); + // DEBUG console.log("metersPerPixel = ", metersPerPixel); + + let scaleNominator = Math.round( + // the x in 1:x map scale + 1000 * pixelsPerMapMillimeter * metersPerPixel + ); + console.log("scaleNominator = ", scaleNominator); + var data = canvas.toDataURL("image/jpeg"); + pdf.addImage(data, "JPEG", 0, 0, width, height); + self.addScaleBar(pdf, width, height, scaleNominator); + self.addNorthArrow(pdf, 15, 9, northarrowSize); + self.addPageInfo(pdf); + self.addAboutBox(pdf, width, height); + + if (self.getLayerByName("Bottleneck isolines").isVisible) { + self.addLegend(pdf, width, height); + } + + pdf.save("map.pdf"); + // reset to original size + map.setSize(mapSize); + map.getView().fit(mapExtent, { size: mapSize }); + + // as we are done: re-enable button + self.readyToGenerate = true; + }); + + // trigger rendering + this.prepareRendering(function() { + map.setSize(mapSizeForPrint); + map.getView().fit(mapExtent, { size: mapSizeForPrint }); + + /* let a = document.createElement("a"); - a.href = src; + a.href = src; // need the generated PDF in here (as dataURL?) if (this.form.downloadType === "download") a.download = src.substr(src.lastIndexOf("/") + 1); @@ -124,6 +222,208 @@ document.body.appendChild(a); a.click(); document.body.removeChild(a); + */ + }); + }, + prepareRendering(callback) { + // call callback() once the preparations are done + this.logoImageForPDF = new Image(); + + this.logoImageForPDF.onload = function() { + callback(); + }; + + if (this.logoForPDF) { + this.logoImageForPDF.src = this.logoForPDF; + } else { + this.logoImageForPDF.src = "/img/gemma-logo-for-pdf.png"; + } + }, + addRoundedBox(doc, x, y, w, h) { + // draws a rounded background box at (x,y) width x height + // using jsPDF units + doc.setDrawColor(255, 255, 255); + doc.setFillColor(255, 255, 255); + doc.roundedRect(x, y, w, h, 3, 3, "FD"); + }, + addScaleBar(doc, docWidth, docHeight, scaleNominator) { + // scaleNominator is the x in 1:x of the map scale + + // hardcode maximal width for now and place in lower right corner + let maxWidth = 80; // in mm + + // reduce width until we'll find a nice number for printing + // strategy: + // 1. check which unit prefix we shall use to get [10:10000[ + // 2. using a mapping for the leading digit to get [1:10[ + // 3. select a smaller number which is nicely dividable + // 4. scale up again to get length in paper mm and to be shown + + // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10#Polyfill + let log10 = + Math.log10 || // more precise, but unsupported by IE + function(x) { + return Math.log(x) * Math.LOG10E; + }; + + let maxLength = maxWidth * scaleNominator; + + let unit = "mm"; + let unitConversionFactor = 1; + if (maxLength >= 1e7) { + // >= 10 km + unit = "km"; + unitConversionFactor = 1e6; + } else if (maxLength >= 1e4) { + // >= 10 m + unit = "m"; + unitConversionFactor = 1e3; + } + + maxLength /= unitConversionFactor; + + // DEBUG console.log(maxLength, unit); + let unroundedLength = maxLength; + let numberOfDigits = Math.floor(log10(unroundedLength)); + let factor = Math.pow(10, numberOfDigits); + let mapped = unroundedLength / factor; + // DEBUG console.log(mapped); + + var length = Math.floor(maxLength); // just to have an upper limit + + // manually only use numbers that are very nice to devide by 4 + // note that this is taken into account for rounding later + if (mapped > 8) { + length = 8 * factor; + } else if (mapped > 4) { + length = 4 * factor; + } else if (mapped > 2) { + length = 2 * factor; + } else { + length = factor; + } + + let size = (length * unitConversionFactor) / scaleNominator / 4; + + let x = docWidth - (size * 4 + 8); + let y = docHeight - 6; + + this.addRoundedBox(doc, x - 4, y - 4, size * 4 + 12, 10); + + doc.setDrawColor(0, 0, 0); + doc.setFillColor(0, 0, 0); + doc.rect(x, y, size, 1, "FD"); + doc.setFillColor(255, 255, 255); + doc.setDrawColor(0, 0, 0); + doc.rect(x + size, y, size, 1, "FD"); + doc.setFillColor(0, 0, 0); + doc.setDrawColor(0, 0, 0); + doc.rect(x + size * 2, y, size * 2, 1, "FD"); + doc.setFontSize(5); + doc.text(x, y + 3, "0"); + // /4 and could give 2.5. We still round, because of floating point arith + doc.text( + x + size, + y + 3, + (Math.round((length * 10) / 4) / 10).toString() + ); + doc.text(x + size * 2, y + 3, Math.round(length / 2).toString()); + doc.text(x + size * 4, y + 3, Math.round(length).toString() + " " + unit); + }, + + addNorthArrow(doc, x1, y1, size) { + var y2 = y1 + size * 3; + var x3 = x1 - size * 2; + var y3 = y1 + size * 5; + var x4 = x1 + size * 2; + //white triangle + doc.setFillColor(255, 255, 255); + doc.setDrawColor(255, 255, 255); + doc.triangle(x3 - 0.8, y3 + 1.2, x1, y1 - 1.2, x1, y2 + 0.6, "F"); + doc.triangle(x1, y1 - 1.2, x1, y2 + 0.6, x4 + 0.8, y3 + 1.2, "F"); + //north arrow + doc.setDrawColor(0, 0, 0); + doc.setFillColor(255, 255, 255); + doc.triangle(x3, y3, x1, y1, x1, y2, "FD"); + doc.setFillColor(0, 0, 0); + doc.triangle(x1, y1, x1, y2, x4, y3, "FD"); + doc.setFontSize(size * 3.1); + doc.setTextColor(255, 255, 255); + doc.setFontStyle("bold"); + doc.text(size < 3 ? x1 - 0.5 : x1 - 1.3, y3 + 1, "N"); + doc.setFontSize(size * 3); + doc.setTextColor(0, 0, 0); + doc.setFontStyle("normal"); + doc.text(size < 3 ? x1 - 0.5 : x1 - 1.3, y3 + 1, "N"); + }, + // add some text at specific coordinates and determine how many wrolds in single line + addText(doc, postitionX, positionY, size, color, lineWidth, text) { + // split the incoming string to an array, each element is a string of words in a single line + var textLines = doc.splitTextToSize(text, lineWidth); + // get the longest line to fit the white backround to it + var longestString = ""; + textLines.forEach(function(element) { + if (element.length > longestString.length) longestString = element; + }); + var indexOfMaxString = textLines.indexOf(longestString); + // white background (rectangular) around the text + doc.setFillColor(255, 255, 255); + doc.setDrawColor(255, 255, 255); + doc.rect( + postitionX - doc.getStringUnitWidth(textLines[indexOfMaxString]) / size, + size > 10 ? positionY - size / 1.8 : positionY - size / 2.4, + doc.getStringUnitWidth(textLines[indexOfMaxString]) * (size / 2.6), + textLines.length * (size / 2), + "FD" + ); + //rounded rectangular + /* doc.roundedRect( + postitionX - doc.getStringUnitWidth(textLines[indexOfMaxString]) / size, + size > 10 ? positionY - size / 1.8 : positionY - size / 2.6, + doc.getStringUnitWidth(textLines[indexOfMaxString]) * (size / 2.6), + textLines.length * (size / 2), + 3, + 3, + "FD" + ); */ + doc.setTextColor(color); + doc.setFontSize(size); + doc.text(postitionX, positionY, textLines); + }, + addPageInfo(doc) { + this.addRoundedBox(doc, 0, 0, 110, 8); + let str = + this.$gettext("Date of publication:") + + " " + + new Date().toLocaleString(locale2) + + " " + + this.$gettext("– printed by:") + + " " + + this.user; + this.addText(doc, 5, 5, 9, "black", 100, str); + }, + addAboutBox(doc, docWidth, docHeight) { + let top = docHeight - 20; + this.addRoundedBox(doc, 0, top, 120, 20); + + let logoImage = this.logoImageForPDF; + let aspectRatio = logoImage.width / logoImage.height; + doc.addImage(logoImage, "PNG", 5, docHeight - 19, 110, 110 / aspectRatio); + + let str = + "Dislaimer: Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua."; + this.addText(doc, 5, docHeight - 6, 8, "black", 115, str); + }, + addLegend(doc, docWidth) { + // transforming into an HTMLImageElement only to find out + // the width x height of the legend image + // FUTURE: find a better way to get the width and height + let legendImage = new Image(); + legendImage.src = this.isolinesLegendImgDataURL; + let aspectRatio = legendImage.width / legendImage.height; + + this.addRoundedBox(doc, docWidth - 54, 0, 54, 50 / aspectRatio + 4); + doc.addImage(legendImage, docWidth - 52, 2, 50, 50 / aspectRatio); } } };
--- a/client/src/components/Sidebar.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/Sidebar.vue Tue Jan 22 11:22:17 2019 +0100 @@ -40,6 +40,8 @@ ></font-awesome-icon> <span class="fix-trans-space" v-translate>Staging area</span> </a> + </div> + <div v-if="isSysAdmin"> <a :class="['secondary', { active: isActive('stretches') }]" @click="toggleContextBox('stretches')" @@ -50,10 +52,10 @@ fixed-width icon="road" ></font-awesome-icon> - <span class="fix-trans-space" v-translate - >Define sections and stretches</span - > + <span class="fix-trans-space" v-translate>Define stretches</span> </a> + </div> + <div v-if="isWaterwayAdmin"> <small class="text-muted pl-3"> <translate>Import</translate> </small> <hr class="m-0" /> <router-link to="/importsoundingresults">
--- a/client/src/components/importqueue/Importqueuedetail.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/importqueue/Importqueuedetail.vue Tue Jan 22 11:22:17 2019 +0100 @@ -8,7 +8,7 @@ {{ formatDate(job.enqueued) }} </div> <div @click="showDetails(job.id)" class="kind mt-1 mr-2"> - {{ job.kind }} + {{ job.kind.toUpperCase() }} </div> <div @click="showDetails(job.id)" class="user mt-1 mr-2"> {{ job.user }} @@ -67,7 +67,7 @@ class="detailsrow" > <td class="type"> - <span class="condensed">{{ entry.kind }}</span> + <span class="condensed">{{ entry.kind.toUpperCase() }}</span> </td> <td class="datetime"> <span class="condensed">{{ formatDateTime(entry.time) }}</span>
--- a/client/src/components/importschedule/Importschedule.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/importschedule/Importschedule.vue Tue Jan 22 11:22:17 2019 +0100 @@ -44,7 +44,7 @@ <tbody> <tr v-for="schedule in schedules" :key="schedule.id"> <td>{{ schedule.id }}</td> - <td>{{ schedule.kind }}</td> + <td>{{ schedule.kind.toUpperCase() }}</td> <td>{{ schedule.user }}</td> <td>{{ schedule.cron }}</td> <td>
--- a/client/src/components/importschedule/Importscheduledetail.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/importschedule/Importscheduledetail.vue Tue Jan 22 11:22:17 2019 +0100 @@ -246,7 +246,7 @@ class="form-control" type="number" /> - <div class="ml-2 my-auto">m</div> + <div class="ml-2 my-auto"> m</div> </div> <div v-if="!minWidth" class="d-flex flex-row"> <small @@ -268,7 +268,7 @@ class="form-control" type="number" /> - <div class="ml-2 my-auto">m</div> + <div class="ml-2 my-auto"> m</div> </div> <div v-if="!maxWidth" class="d-flex flex-row"> <small
--- a/client/src/components/layers/Layerselect.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/layers/Layerselect.vue Tue Jan 22 11:22:17 2019 +0100 @@ -17,7 +17,7 @@ }}</label> </div> <div v-if="isVisible && layername == 'Bottleneck isolines'"> - <img class="rounded my-1 d-block" :src="isolinesLegendImgUrl" /> + <img class="rounded my-1 d-block" :src="isolinesLegendImgDataURL" /> </div> </div> </template> @@ -38,25 +38,25 @@ * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * - * Copyright (C) 2018 by via donau + * Copyright (C) 2018, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): - * Thomas Junk <thomas.junk@intevation.de> + * * Thomas Junk <thomas.junk@intevation.de> + * * Bernhard Reiter <bernhard.reiter@intevation.de> */ import { HTTP } from "@/lib/http"; +import { mapState } from "vuex"; export default { props: ["layername", "layerindex", "isVisible"], name: "layerselect", - data() { - return { - isolinesLegendImgUrl: "" - }; - }, components: { LegendElement: () => import("./LegendElement.vue") }, + computed: { + ...mapState("map", ["isolinesLegendImgDataURL"]) + }, methods: { visibilityToggled() { this.$emit("visibilityToggled", this.layerindex); @@ -64,7 +64,7 @@ }, created() { // fetch legend image for bottleneck isolines - // TODO: move to store + // directly read it as dataURL so it is reusable later if (this.layername == "Bottleneck isolines") { const src = "/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=sounding_results_contour_lines_geoserver&legend_options=columns:4;fontAntiAliasing:true"; @@ -75,8 +75,12 @@ }, responseType: "blob" }).then(response => { - var urlCreator = window.URL || window.webkitURL; - this.isolinesLegendImgUrl = urlCreator.createObjectURL(response.data); + var that = this; + const reader = new FileReader(); + reader.onload = function() { + that.$store.commit("map/isolinesLegendImgDataURL", this.result); + }; + reader.readAsDataURL(response.data); }); } }
--- a/client/src/components/staging/StagingDetail.vue Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/components/staging/StagingDetail.vue Tue Jan 22 11:22:17 2019 +0100 @@ -3,23 +3,25 @@ <div class="d-flex flex-row"> <div class="mt-auto d-flex flex-row mb-auto small name text-left"> <a - v-if="!isBottleneck(data.kind.toUpperCase())" + v-if="isSoundingResult(data.kind.toUpperCase())" @click="zoomTo()" href="#" >{{ data.summary.bottleneck }}</a > - <span v-else class="text-left" + <span v-if="isBottleneck(data.kind.toUpperCase())" class="text-left" ><translate>Bottlenecks</translate> ({{ data.summary.bottlenecks.length }})</span > + <span v-if="isFairwayDimension(data.kind.toUpperCase())">-</span> </div> <div class="mt-auto mb-auto small text-left type"> {{ data.kind.toUpperCase() }} </div> - <div class="mt-auto mb-auto small text-left date"> + <div v-if="data.summary" class="mt-auto mb-auto small text-left date"> {{ formatSurveyDate(data.summary.date) }} </div> + <div v-else class="mt-auto mb-auto small text-left date">-</div> <div class="mt-auto mb-auto small text-left imported"> {{ formatSurveyDate(data.enqueued.split("T")[0]) }} </div> @@ -248,9 +250,15 @@ }); }); }, + isFairwayDimension(kind) { + return kind === "FD"; + }, isBottleneck(kind) { return kind === "BN"; }, + isSoundingResult(kind) { + return kind === "SR"; + }, formatSurveyDate(date) { return formatSurveyDate(date); },
--- a/client/src/locale/bg_BG/LC_MESSAGES/app.po Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/locale/bg_BG/LC_MESSAGES/app.po Tue Jan 22 11:22:17 2019 +0100 @@ -18,7 +18,11 @@ "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.4-dev\n" -#: src/components/importschedule/Importscheduledetail.vue:565 +#: src/components/Pdftool.vue:402 +msgid "– printed by:" +msgstr "" + +#: src/components/importschedule/Importscheduledetail.vue:452 msgid "15 minutes" msgstr "" @@ -91,7 +95,7 @@ #: src/components/ImportSoundingresults.vue:16 #, fuzzy msgid "Bottleneck" -msgstr "Критични участъци" +msgstr "" #: src/components/Systemconfiguration.vue:19 msgid "Bottleneck Areas fill-color" @@ -149,12 +153,7 @@ #: src/components/usermanagement/Userdetail.vue:33 #, fuzzy msgid "Country" -msgstr "Държава" - -#: src/components/ImportStretches.vue:166 -#, fuzzy -msgid "Countrycode" -msgstr "Държава" +msgstr "" #: src/components/importschedule/Importscheduledetail.vue:495 msgid "Cronstring" @@ -198,11 +197,7 @@ msgid "Depthreference" msgstr "" -#: src/components/importschedule/Importscheduledetail.vue:46 -msgid "Distance Marks Virtual" -msgstr "" - -#: src/components/Pdftool.vue:36 +#: src/components/Pdftool.vue:10 msgid "Download" msgstr "" @@ -410,7 +405,7 @@ #: src/components/layers/Layers.vue:10 #, fuzzy msgid "Layers" -msgstr "Слоеве" +msgstr "" #: src/components/Login.vue:58 msgid "Login" @@ -488,7 +483,7 @@ #: src/components/Bottlenecks.vue:9 src/components/staging/Staging.vue:11 #, fuzzy msgid "Name" -msgstr "име" +msgstr "" #: src/components/ImportStretches.vue:110 msgid "National Object name" @@ -576,7 +571,7 @@ #: src/components/usermanagement/Userdetail.vue:331 #, fuzzy msgid "Please choose a country" -msgstr "Моля, изберете държава" +msgstr "" #: src/components/usermanagement/Userdetail.vue:336 msgid "Please choose a role" @@ -632,10 +627,6 @@ #: src/components/ImportStretches.vue:156 #, fuzzy msgid "Please enter a source" -msgstr "Моля, изберете държава" - -#: src/components/importschedule/Importscheduledetail.vue:297 -msgid "Please enter a source orgranization" msgstr "" #: src/components/ImportStretches.vue:61 @@ -892,7 +883,7 @@ #: src/components/staging/Staging.vue:12 #, fuzzy msgid "Type" -msgstr "Тип" +msgstr "" #: src/components/ImportWaterwayProfiles.vue:89 msgid "under construction"
--- a/client/src/locale/de_AT/LC_MESSAGES/app.po Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/locale/de_AT/LC_MESSAGES/app.po Tue Jan 22 11:22:17 2019 +0100 @@ -196,11 +196,7 @@ msgid "Depthreference" msgstr "Tiefenreferenz" -#: src/components/importschedule/Importscheduledetail.vue:46 -msgid "Distance Marks Virtual" -msgstr "" - -#: src/components/Pdftool.vue:36 +#: src/components/Pdftool.vue:10 msgid "Download" msgstr "Herunterladen" @@ -265,7 +261,7 @@ msgid "Fairwaydimension" msgstr "Waterway-Admin" -#: src/components/importschedule/Importscheduledetail.vue:158 +#: src/components/importschedule/Importscheduledetail.vue:155 msgid "Featuretype" msgstr ""
--- a/client/src/locale/en_GB/LC_MESSAGES/app.po Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/locale/en_GB/LC_MESSAGES/app.po Tue Jan 22 11:22:17 2019 +0100 @@ -194,11 +194,7 @@ msgid "Depthreference" msgstr "" -#: src/components/importschedule/Importscheduledetail.vue:46 -msgid "Distance Marks Virtual" -msgstr "" - -#: src/components/Pdftool.vue:36 +#: src/components/Pdftool.vue:10 msgid "Download" msgstr ""
--- a/client/src/locale/hr_HR/LC_MESSAGES/app.po Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/locale/hr_HR/LC_MESSAGES/app.po Tue Jan 22 11:22:17 2019 +0100 @@ -18,7 +18,11 @@ "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 3.4-dev\n" -#: src/components/importschedule/Importscheduledetail.vue:565 +#: src/components/Pdftool.vue:402 +msgid "– printed by:" +msgstr "" + +#: src/components/importschedule/Importscheduledetail.vue:452 msgid "15 minutes" msgstr "" @@ -91,7 +95,7 @@ #: src/components/ImportSoundingresults.vue:16 #, fuzzy msgid "Bottleneck" -msgstr "Kritični sektor" +msgstr "" #: src/components/Systemconfiguration.vue:19 msgid "Bottleneck Areas fill-color" @@ -197,11 +201,7 @@ msgid "Depthreference" msgstr "" -#: src/components/importschedule/Importscheduledetail.vue:46 -msgid "Distance Marks Virtual" -msgstr "" - -#: src/components/Pdftool.vue:36 +#: src/components/Pdftool.vue:10 msgid "Download" msgstr "" @@ -220,7 +220,7 @@ #: src/components/importschedule/Importscheduledetail.vue:53 #, fuzzy msgid "Email Notification" -msgstr "E-mail Obavijest" +msgstr "" #: src/components/ImportStretches.vue:66 msgid "End rhm" @@ -630,10 +630,6 @@ #: src/components/ImportStretches.vue:156 #, fuzzy msgid "Please enter a source" -msgstr "Odaberite zemlju" - -#: src/components/importschedule/Importscheduledetail.vue:297 -msgid "Please enter a source orgranization" msgstr "" #: src/components/ImportStretches.vue:61
--- a/client/src/locale/hu_HU/LC_MESSAGES/app.po Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/locale/hu_HU/LC_MESSAGES/app.po Tue Jan 22 11:22:17 2019 +0100 @@ -194,11 +194,7 @@ msgid "Depthreference" msgstr "" -#: src/components/importschedule/Importscheduledetail.vue:46 -msgid "Distance Marks Virtual" -msgstr "" - -#: src/components/Pdftool.vue:36 +#: src/components/Pdftool.vue:10 msgid "Download" msgstr ""
--- a/client/src/locale/sk_SK/LC_MESSAGES/app.po Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/locale/sk_SK/LC_MESSAGES/app.po Tue Jan 22 11:22:17 2019 +0100 @@ -13,7 +13,11 @@ "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Weblate 3.4-dev\n" -#: src/components/importschedule/Importscheduledetail.vue:565 +#: src/components/Pdftool.vue:402 +msgid "– printed by:" +msgstr "" + +#: src/components/importschedule/Importscheduledetail.vue:452 msgid "15 minutes" msgstr "" @@ -191,11 +195,7 @@ msgid "Depthreference" msgstr "" -#: src/components/importschedule/Importscheduledetail.vue:46 -msgid "Distance Marks Virtual" -msgstr "" - -#: src/components/Pdftool.vue:36 +#: src/components/Pdftool.vue:10 msgid "Download" msgstr "" @@ -214,7 +214,7 @@ #: src/components/importschedule/Importscheduledetail.vue:53 #, fuzzy msgid "Email Notification" -msgstr "Email Oznámenia" +msgstr "" #: src/components/ImportStretches.vue:66 msgid "End rhm" @@ -437,7 +437,7 @@ #: src/components/Sidebar.vue:15 #, fuzzy msgid "Map" -msgstr "Mapa" +msgstr "" #: src/components/importschedule/Importscheduledetail.vue:584 msgid "March" @@ -625,10 +625,6 @@ #: src/components/ImportStretches.vue:156 #, fuzzy msgid "Please enter a source" -msgstr "Vyberte krajinu" - -#: src/components/importschedule/Importscheduledetail.vue:297 -msgid "Please enter a source orgranization" msgstr "" #: src/components/ImportStretches.vue:61
--- a/client/src/store/application.js Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/store/application.js Tue Jan 22 11:22:17 2019 +0100 @@ -21,6 +21,7 @@ return { appTitle: process.env.VUE_APP_TITLE, secondaryLogo: process.env.VUE_APP_SECONDARY_LOGO_URL, + logoForPDF: process.env.VUE_APP_LOGO_FOR_PDF_URL, showSidebar: false, showUsermenu: false, showSplitscreen: false,
--- a/client/src/store/imports.js Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/store/imports.js Tue Jan 22 11:22:17 2019 +0100 @@ -14,6 +14,7 @@ import { HTTP } from "@/lib/http"; import Vue from "vue"; +import { WFS } from "ol/format.js"; /* eslint-disable no-unused-vars */ /* eslint-disable no-unreachable */ @@ -97,6 +98,7 @@ // initial state const init = () => { return { + stretches: [], imports: [], staging: [], schedules: [], @@ -111,6 +113,9 @@ namespaced: true, state: init(), mutations: { + setStretches: (state, stretches) => { + state.stretches = stretches; + }, clearCurrentSchedule: state => { state.currentSchedule = initializeCurrentSchedule(); }, @@ -210,6 +215,53 @@ } }, actions: { + loadStretches({ commit }) { + return new Promise((resolve, reject) => { + var stretchesFeatureCollectionRequest = new WFS().writeGetFeature({ + srsName: "EPSG:4326", + featureNS: "gemma", + featurePrefix: "gemma", + featureTypes: ["stretches_geoserver"], + outputFormat: "application/json" + }); + HTTP.post( + "/internal/wfs", + new XMLSerializer().serializeToString( + stretchesFeatureCollectionRequest + ), + { + headers: { + "X-Gemma-Auth": localStorage.getItem("token"), + "Content-type": "text/xml; charset=UTF-8" + } + } + ) + .then(response => { + if (response.data.features) { + commit("setStretches", response.data.features); + } else { + commit("setStretches", []); + } + resolve(response); + }) + .catch(error => { + reject(error); + }); + }); + }, + saveStretch({ commit }, stretch) { + return new Promise((resolve, reject) => { + HTTP.post("/imports/stretch", stretch, { + headers: { "X-Gemma-Auth": localStorage.getItem("token") } + }) + .then(response => { + resolve(response); + }) + .catch(error => { + reject(error); + }); + }); + }, loadSchedule({ commit }, id) { return new Promise((resolve, reject) => { HTTP.get("/imports/config/" + id, {
--- a/client/src/store/map.js Fri Jan 18 06:51:49 2019 +0000 +++ b/client/src/store/map.js Tue Jan 22 11:22:17 2019 +0100 @@ -4,13 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * - * Copyright (C) 2018 by via donau + * Copyright (C) 2018, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): - * Markus Kottländer <markus@intevation.de> - * Thomas Junk <thomas.junk@intevation.de> + * * Bernhard Reiter <bernhard.reiter@intevation.de> + * * Markus Kottländer <markus@intevation.de> + * * Thomas Junk <thomas.junk@intevation.de> */ //import { HTTP } from "../lib/http"; @@ -45,6 +46,7 @@ lineTool: null, // open layers interaction object (Draw) polygonTool: null, // open layers interaction object (Draw) cutTool: null, // open layers interaction object (Draw) + isolinesLegendImgDataURL: "", layers: [ { name: "Open Streetmap", @@ -60,6 +62,7 @@ source: new TileWMS({ preload: 1, url: "https://service.d4d-portal.info/wms/", + crossOrigin: "anonymous", params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true } }) }), @@ -67,6 +70,41 @@ showInLegend: true }, { + name: "Waterway Area", + data: new VectorLayer({ + source: new VectorSource({ + strategy: bboxStrategy + }), + style: new Style({ + stroke: new Stroke({ + color: "rgba(0, 102, 0, 1)", + width: 2 + }) + }) + }), + isVisible: true, + showInLegend: true + }, + { + name: "Stretches", + data: new VectorLayer({ + source: new VectorSource({ + strategy: bboxStrategy + }), + style: new Style({ + stroke: new Stroke({ + color: "rgba(250, 200, 0, .8)", + width: 2 + }), + fill: new Fill({ + color: "rgba(250, 200, 10, .3)" + }) + }) + }), + isVisible: false, + showInLegend: true + }, + { name: "Fairway Dimensions", data: new VectorLayer({ source: new VectorSource(), @@ -96,38 +134,6 @@ showInLegend: true }, { - name: "Waterway Area, named", - data: new VectorLayer({ - source: new VectorSource({ - strategy: bboxStrategy - }), - style: new Style({ - stroke: new Stroke({ - color: "rgba(0, 132, 0, 1)", - width: 2 - }) - }) - }), - isVisible: false, - showInLegend: true - }, - { - name: "Waterway Area", - data: new VectorLayer({ - source: new VectorSource({ - strategy: bboxStrategy - }), - style: new Style({ - stroke: new Stroke({ - color: "rgba(0, 102, 0, 1)", - width: 2 - }) - }) - }), - isVisible: true, - showInLegend: true - }, - { name: "Waterway Axis", data: new VectorLayer({ source: new VectorSource({ @@ -145,17 +151,6 @@ showInLegend: true }, { - name: "Distance marks", - forLegendStyle: { point: true, resolution: 8 }, - data: new VectorLayer({ - source: new VectorSource({ - strategy: bboxStrategy - }) - }), - isVisible: false, - showInLegend: true - }, - { name: "Bottlenecks", data: new VectorLayer({ source: new VectorSource({ @@ -203,6 +198,17 @@ showInLegend: true }, { + name: "Distance marks", + forLegendStyle: { point: true, resolution: 8 }, + data: new VectorLayer({ + source: new VectorSource({ + strategy: bboxStrategy + }) + }), + isVisible: false, + showInLegend: true + }, + { name: "Distance marks, Axis", forLegendStyle: { point: true, resolution: 8 }, data: new VectorLayer({ @@ -392,6 +398,9 @@ center: fromLonLat(coordinates, view.getProjection()), duration: 700 }); + }, + isolinesLegendImgDataURL: (state, isolinesLegendImgDataURL) => { + state.isolinesLegendImgDataURL = isolinesLegendImgDataURL; } }, actions: {
--- a/client/yarn.lock Fri Jan 18 06:51:49 2019 +0000 +++ b/client/yarn.lock Tue Jan 22 11:22:17 2019 +0100 @@ -1157,6 +1157,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g== +abab@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4= + abab@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" @@ -1196,6 +1201,13 @@ dependencies: acorn "^5.0.0" +acorn-globals@^1.0.4: + version "1.0.9" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf" + integrity sha1-VbtemGkVB7dFedBRNBMhfDgMVM8= + dependencies: + acorn "^2.1.0" + acorn-globals@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" @@ -1273,6 +1285,11 @@ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913" integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw== +acorn@^2.1.0, acorn@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" + integrity sha1-q259nYhqrKiwhbwzEreaGYQz8Oc= + acorn@^3.0.4, acorn@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" @@ -1331,7 +1348,7 @@ fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.1.0, ajv@^6.5.5: +ajv@^6.1.0: version "6.6.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== @@ -1341,6 +1358,16 @@ json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.5.5: + version "6.7.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96" + integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -1876,6 +1903,11 @@ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-arraybuffer@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= + base64-js@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" @@ -2314,6 +2346,16 @@ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000913.tgz#560311ecf242eaf12159b720e64b11ebd759b5e4" integrity sha512-PP7Ypc35XY1mNduHqweGNOp0qfNUCmaQauGOYDByvirlFjrzRyY72pBRx7jnBidOB8zclg00DAzsy2H475BouQ== +canvg@1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/canvg/-/canvg-1.5.3.tgz#aad17915f33368bf8eb80b25d129e3ae922ddc5f" + integrity sha512-7Gn2IuQzvUQWPIuZuFHrzsTM0gkPz2RRT9OcbdmA03jeKk8kltrD8gqUzNX15ghY/4PV5bbe5lmD6yDLDY6Ybg== + dependencies: + jsdom "^8.1.0" + rgbcolor "^1.0.1" + stackblur-canvas "^1.4.1" + xmldom "^0.1.22" + capture-exit@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" @@ -2945,6 +2987,13 @@ postcss "^7.0.1" timsort "^0.3.0" +css-line-break@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.0.1.tgz#19f2063a33e95fb2831b86446c0b80c188af450a" + integrity sha1-GfIGOjPpX7KDG4ZEbAuAwYivRQo= + dependencies: + base64-arraybuffer "^0.1.5" + css-loader@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.1.tgz#6885bb5233b35ec47b006057da01cc640b6b79fe" @@ -3123,11 +3172,18 @@ dependencies: css-tree "1.0.0-alpha.29" -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": +cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0", "cssom@>= 0.3.2 < 0.4.0": version "0.3.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797" integrity sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog== +"cssstyle@>= 0.2.34 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + integrity sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ= + dependencies: + cssom "0.3.x" + cssstyle@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.1.1.tgz#18b038a9c44d65f7a8e428a653b9f6fe42faf5fb" @@ -3966,7 +4022,7 @@ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.x.x, escodegen@^1.9.1: +escodegen@1.x.x, escodegen@^1.6.1, escodegen@^1.9.1: version "1.11.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" integrity sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw== @@ -4474,6 +4530,10 @@ loader-utils "^1.0.2" schema-utils "^1.0.0" +file-saver@eligrey/FileSaver.js#1.3.8: + version "1.3.8" + resolved "https://codeload.github.com/eligrey/FileSaver.js/tar.gz/e865e37af9f9947ddcced76b549e27dc45c1cb2e" + file-uri-to-path@1: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -5229,6 +5289,13 @@ toposort "^1.0.0" util.promisify "1.0.0" +html2canvas@1.0.0-alpha.12: + version "1.0.0-alpha.12" + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.0.0-alpha.12.tgz#3b1992e3c9b3f56063c35fd620494f37eba88513" + integrity sha1-OxmS48mz9WBjw1/WIElPN+uohRM= + dependencies: + css-line-break "1.0.1" + htmlparser2@^3.9.1: version "3.10.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" @@ -5322,7 +5389,7 @@ debug "2" extend "3" -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.4: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.13, iconv-lite@^0.4.17, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -6413,6 +6480,29 @@ ws "^5.2.0" xml-name-validator "^3.0.0" +jsdom@^8.1.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-8.5.0.tgz#d4d8f5dbf2768635b62a62823b947cf7071ebc98" + integrity sha1-1Nj12/J2hjW2KmKCO5R89wcevJg= + dependencies: + abab "^1.0.0" + acorn "^2.4.0" + acorn-globals "^1.0.4" + array-equal "^1.0.0" + cssom ">= 0.3.0 < 0.4.0" + cssstyle ">= 0.2.34 < 0.3.0" + escodegen "^1.6.1" + iconv-lite "^0.4.13" + nwmatcher ">= 1.3.7 < 2.0.0" + parse5 "^1.5.1" + request "^2.55.0" + sax "^1.1.4" + symbol-tree ">= 3.1.0 < 4.0.0" + tough-cookie "^2.2.0" + webidl-conversions "^3.0.1" + whatwg-url "^2.0.1" + xml-name-validator ">= 2.0.1 < 3.0.0" + jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -6487,6 +6577,18 @@ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= +jspdf@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/jspdf/-/jspdf-1.5.3.tgz#5a12c011479defabef5735de55c913060ed219f2" + integrity sha512-J9X76xnncMw+wIqb15HeWfPMqPwYxSpPY8yWPJ7rAZN/ZDzFkjCSZObryCyUe8zbrVRNiuCnIeQteCzMn7GnWw== + dependencies: + canvg "1.5.3" + file-saver eligrey/FileSaver.js#1.3.8 + html2canvas "1.0.0-alpha.12" + omggif "1.0.7" + promise-polyfill "8.1.0" + stackblur-canvas "2.2.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -7623,6 +7725,11 @@ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +"nwmatcher@>= 1.3.7 < 2.0.0": + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ== + nwsapi@^2.0.7: version "2.0.9" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.9.tgz#77ac0cdfdcad52b6a1151a84e73254edc33ed016" @@ -7721,6 +7828,11 @@ pixelworks "1.1.0" rbush "2.0.2" +omggif@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.7.tgz#59d2eecb0263de84635b3feb887c0c9973f1e49d" + integrity sha1-WdLuywJj3oRjWz/riHwMmXPx5J0= + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -8004,6 +8116,11 @@ resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + integrity sha1-m387DeMr543CQBsXVzzK8Pb1nZQ= + parse5@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" @@ -8629,6 +8746,11 @@ resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise-polyfill@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.0.tgz#30059da54d1358ce905ac581f287e184aedf995d" + integrity sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA== + promise@^7.0.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -8687,9 +8809,9 @@ integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= psl@^1.1.24, psl@^1.1.28: - version "1.1.29" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" - integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== + version "1.1.31" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" + integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== public-encrypt@^4.0.0: version "4.0.3" @@ -9197,7 +9319,7 @@ stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@^2.87.0, request@^2.88.0: +request@^2.55.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -9315,6 +9437,11 @@ resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= +rgbcolor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d" + integrity sha1-1lBezbMEplldom+ktDMHMGd1lF0= + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -9435,7 +9562,7 @@ pify "^3.0.0" semver "^5.5.0" -sax@^1.2.4, sax@~1.2.4: +sax@^1.1.4, sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -9861,9 +9988,9 @@ integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= sshpk@^1.7.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" - integrity sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA== + version "1.16.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de" + integrity sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -9899,6 +10026,16 @@ resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== +stackblur-canvas@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-2.2.0.tgz#cacc5924a0744b3e183eb2e6c1d8559c1a17c26e" + integrity sha512-5Gf8dtlf8k6NbLzuly2NkGrkS/Ahh+I5VUjO7TnFizdJtgpfpLLEdQlLe9umbcnZlitU84kfYjXE67xlSXfhfQ== + +stackblur-canvas@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-1.4.1.tgz#849aa6f94b272ff26f6471fa4130ed1f7e47955b" + integrity sha1-hJqm+UsnL/JvZHH6QTDtH35HlVs= + stackframe@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b" @@ -10138,7 +10275,7 @@ unquote "~1.1.1" util.promisify "~1.0.0" -symbol-tree@^3.2.2: +"symbol-tree@>= 3.1.0 < 4.0.0", symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= @@ -10357,7 +10494,7 @@ resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= -tough-cookie@>=2.3.3, tough-cookie@^2.3.4: +tough-cookie@>=2.3.3, tough-cookie@^2.2.0, tough-cookie@^2.3.4: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -10380,6 +10517,11 @@ dependencies: punycode "^2.1.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + tree-kill@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a" @@ -10883,6 +11025,11 @@ dependencies: defaults "^1.0.3" +webidl-conversions@^3.0.0, webidl-conversions@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -11052,6 +11199,14 @@ resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-2.0.1.tgz#5396b2043f020ee6f704d9c45ea8519e724de659" + integrity sha1-U5ayBD8CDub3BNnEXqhRnnJN5lk= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" @@ -11172,11 +11327,21 @@ dependencies: async-limiter "~1.0.0" +"xml-name-validator@>= 2.0.1 < 3.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU= + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xmldom@^0.1.22: + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" + integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= + xregexp@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
--- a/docker/Dockerfile.geoserv Fri Jan 18 06:51:49 2019 +0000 +++ b/docker/Dockerfile.geoserv Tue Jan 22 11:22:17 2019 +0100 @@ -14,7 +14,7 @@ ENV CATALINA_TMPDIR /tmp/tomcat8-tmp ENV GS_URL https://downloads.sourceforge.net/project/geoserver/GeoServer -ENV GS_VERSION 2.14.1 +ENV GS_VERSION 2.14.2 ENV GS_DATADIR /opt/geoserver/data ENV CATALINA_OPTS="-DGEOSERVER_DATA_DIR=$GS_DATADIR"
--- a/pkg/controllers/manualimports.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/controllers/manualimports.go Tue Jan 22 11:22:17 2019 +0100 @@ -144,6 +144,22 @@ return dma, due, retries, dmai.SendEmail } +func importStretch(input interface{}) (interface{}, time.Time, int, bool) { + sti := input.(*models.StretchImport) + st := &imports.Stretch{ + Name: sti.Name, + From: sti.From, + To: sti.To, + ObjNam: sti.ObjNam, + NObjNam: sti.NObjNam, + Source: sti.Source, + Date: sti.Date, + Countries: sti.Countries, + } + due, retries := retry(sti.Attributes) + return st, due, retries, sti.SendEmail +} + func manualImport( kind imports.JobKind, setup func(interface{}) (interface{}, time.Time, int, bool),
--- a/pkg/controllers/routes.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/controllers/routes.go Tue Jan 22 11:22:17 2019 +0100 @@ -229,6 +229,12 @@ NoConn: true, })).Methods(http.MethodPost) + api.Handle("/imports/stretch", sysAdmin(&JSONHandler{ + Input: func() interface{} { return new(models.StretchImport) }, + Handle: manualImport(imports.STJobKind, importStretch), + NoConn: true, + })).Methods(http.MethodPost) + // Import scheduler configuration api.Handle("/imports/config/{id:[0-9]+}/run", waterwayAdmin(&JSONHandler{
--- a/pkg/controllers/srimports.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/controllers/srimports.go Tue Jan 22 11:22:17 2019 +0100 @@ -116,11 +116,11 @@ } if v := req.FormValue("date"); v != "" { - date, err := time.Parse(models.SoundingResultDateFormat, v) + date, err := time.Parse(models.DateFormat, v) if err != nil { return err } - sr.Date = &models.SoundingResultDate{Time: date} + sr.Date = &models.Date{Time: date} } if v := req.FormValue("depth-reference"); v != "" {
--- a/pkg/imports/agm.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/imports/agm.go Tue Jan 22 11:22:17 2019 +0100 @@ -27,6 +27,7 @@ "time" "gemma.intevation.de/gemma/pkg/common" + "gemma.intevation.de/gemma/pkg/misc" "gemma.intevation.de/gemma/pkg/models" ) @@ -109,19 +110,10 @@ return os.RemoveAll(agm.Dir) } -func guessDate(s string) (time.Time, error) { - var err error - var t time.Time - for _, layout := range [...]string{ - "02.01.2006 15:04", - "2006-01-02T15:04:05-07:00", - } { - if t, err = time.Parse(layout, s); err == nil { - break - } - } - return t, err -} +var guessDate = misc.TimeGuesser([]string{ + "02.01.2006 15:04", + "2006-01-02T15:04:05-07:00", +}).Guess // Do executes the actual approved gauge measurements import. func (agm *ApprovedGaugeMeasurements) Do(
--- a/pkg/imports/bn.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/imports/bn.go Tue Jan 22 11:22:17 2019 +0100 @@ -66,8 +66,7 @@ ISRSrange_area( isrsrange(isrs_fromText($5), isrs_fromText($6)), (SELECT ST_Union(CAST(area AS geometry)) - FROM waterway.fairway_dimensions - WHERE level_of_service = 3)), + FROM waterway.waterway_area)), $7, $8, $9, @@ -258,14 +257,14 @@ } feedback.Info("Storing %d bottlenecks took %s", len(nids), time.Since(start)) - if err = tx.Commit(); err == nil { - feedback.Info("Import of bottlenecks was successful") + if err := tx.Commit(); err != nil { + return nil, err } - + feedback.Info("Import of bottlenecks was successful") summary := struct { Bottlenecks []string `json:"bottlenecks"` }{ Bottlenecks: nids, } - return &summary, err + return &summary, nil }
--- a/pkg/imports/dmv.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/imports/dmv.go Tue Jan 22 11:22:17 2019 +0100 @@ -188,7 +188,7 @@ code.FairwaySection, code.Orc, code.Hectometre, - float64(*dr.Lat), float64(*dr.Lon), + float64(*dr.Lon), float64(*dr.Lat), string(*dr.Relenc), ); err != nil { return nil, err
--- a/pkg/imports/fd.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/imports/fd.go Tue Jan 22 11:22:17 2019 +0100 @@ -23,6 +23,7 @@ "time" "gemma.intevation.de/gemma/pkg/common" + "gemma.intevation.de/gemma/pkg/misc" "gemma.intevation.de/gemma/pkg/wfs" ) @@ -44,12 +45,18 @@ type fdTime struct{ time.Time } +var guessFDTime = misc.TimeGuesser([]string{ + "20060102", + "2006", + "", +}).Guess + func (fdt *fdTime) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } - t, err := time.Parse("20060102", s) + t, err := guessFDTime(s) if err == nil { *fdt = fdTime{t} } @@ -67,7 +74,7 @@ func (fdJobCreator) Description() string { return "fairway dimension" } -func (fdJobCreator) AutoAccept() bool { return true } +func (fdJobCreator) AutoAccept() bool { return false } func (fdJobCreator) Create(_ JobKind, data string) (Job, error) { fd := new(FairwayDimension) @@ -83,19 +90,41 @@ } } -// StageDone is a NOP for fairway dimensions imports. -func (fdJobCreator) StageDone(context.Context, *sql.Tx, int64) error { - return nil +func (fdJobCreator) StageDone( + ctx context.Context, + tx *sql.Tx, + id int64, +) error { + // Delete the old features. + if _, err := tx.ExecContext(ctx, deleteFairwayDimensionSQL); err != nil { + return err + } + + _, err := tx.ExecContext(ctx, fdStageDoneSQL, id) + return err } // CleanUp for fairway dimension imports is a NOP. func (*FairwayDimension) CleanUp() error { return nil } type fairwayDimensionProperties struct { - HydroSorDat fdTime `json:"hydro_sordat"` + HydroSorDat *fdTime `json:"hydro_sordat"` +} + +type fdSummary struct { + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + ID int64 `json:"id"` } const ( + fdStageDoneSQL = ` +UPDATE waterway.fairway_dimensions SET staging_done = true +WHERE id IN ( + SELECT key from waterway.track_imports + WHERE import_id = $1 AND + relation = 'waterway.fairway_dimensions'::regclass)` + deleteFairwayDimensionSQL = ` WITH resp AS ( SELECT best_utm(area::geometry) AS t, @@ -106,7 +135,7 @@ DELETE FROM waterway.fairway_dimensions WHERE ST_Covers( (SELECT a FROM resp), - ST_Transform(area::geometry, (SELECT t FROM resp))) + ST_Transform(area::geometry, (SELECT t FROM resp))) AND staging_done = true ` // The ST_MakeValid (line125) and ST_Buffer (line124) are a workarround to @@ -131,7 +160,10 @@ )).geom AS geom ) AS clipped WHERE clipped.geom IS NOT NULL -` +RETURNING id, + ST_X(ST_Centroid(area::geometry)), + ST_Y(ST_Centroid(area::geometry)) + ` ) // Do executes the actual fairway dimension import. @@ -185,16 +217,13 @@ } defer insertStmt.Close() - // Delete the old features. - if _, err := tx.ExecContext(ctx, deleteFairwayDimensionSQL); err != nil { - return nil, err - } - var ( unsupported = stringCounter{} missingProperties int badProperties int features int + outside int + fds []fdSummary ) if err := wfs.DownloadURLs(urls, func(r io.Reader) error { @@ -217,6 +246,7 @@ feedback.Info("Using EPSG: %d", epsg) + features: for _, feature := range rfc.Features { if feature.Geometry.Coordinates == nil { missingProperties++ @@ -226,16 +256,25 @@ var props fairwayDimensionProperties if err := json.Unmarshal(*feature.Properties, &props); err != nil { + feedback.Warn("bad property: %v", err) badProperties++ continue } + var dateInfo time.Time + if props.HydroSorDat == nil || props.HydroSorDat.IsZero() { + dateInfo = start + } else { + dateInfo = (*props.HydroSorDat).Time + } switch feature.Geometry.Type { case "Polygon": var p polygonSlice if err := json.Unmarshal(*feature.Geometry.Coordinates, &p); err != nil { return err } - if _, err := insertStmt.ExecContext( + var fdid int64 + var lat, lon float64 + err = insertStmt.QueryRowContext( ctx, p.asWKB(), epsg, @@ -243,12 +282,23 @@ fd.MinWidth, fd.MaxWidth, fd.Depth, - props.HydroSorDat.Time, + dateInfo, fd.SourceOrganization, - ); err != nil { - feedback.Error("error: %s", err) + ).Scan(&fdid, &lat, &lon) + switch { + case err == sql.ErrNoRows: + outside++ + // ignore -> filtered by responsibility_areas + continue features + case err != nil: return err } + // Store for potential later removal. + if err = track(ctx, tx, importID, "waterway.fairway_dimensions", fdid); err != nil { + return err + } + fds = append(fds, fdSummary{ID: fdid, Lat: lat, Lon: lon}) + features++ default: unsupported[feature.Geometry.Type]++ @@ -272,6 +322,10 @@ feedback.Warn("Unsupported types found: %s", unsupported) } + if outside > 0 { + feedback.Info("Features outside responsibility areas: %d", outside) + } + if features == 0 { err := errors.New("No features found") feedback.Error("%v", err) @@ -283,5 +337,16 @@ features, time.Since(start)) } - return nil, err + summary := struct { + Date time.Time `json:"date"` + LOS int `json:"los"` + SourceOrganization string `json:"source-organization"` + FdArea []fdSummary `json:"fd-area"` + }{ + Date: time.Now(), + LOS: fd.LOS, + SourceOrganization: fd.SourceOrganization, + FdArea: fds, + } + return &summary, err }
--- a/pkg/imports/sr.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/imports/sr.go Tue Jan 22 11:22:17 2019 +0100 @@ -48,7 +48,7 @@ // Override data // Date if given overrides the date value from the meta.json. - Date *models.SoundingResultDate `json:"date,omitempty"` + Date *models.Date `json:"date,omitempty"` // Date if given overrides the name of the bottleneck from the meta.json. Bottleneck *string `json:"bottleneck,omitempty"` // EPSG if given overrides the EPSG code from the meta.json. @@ -311,10 +311,10 @@ } summary := struct { - Bottleneck string `json:"bottleneck"` - Date models.SoundingResultDate `json:"date"` - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` + Bottleneck string `json:"bottleneck"` + Date models.Date `json:"date"` + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` }{ Bottleneck: m.Bottleneck, Date: m.Date,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/imports/st.go Tue Jan 22 11:22:17 2019 +0100 @@ -0,0 +1,227 @@ +// 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): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package imports + +import ( + "context" + "database/sql" + "errors" + "time" + + "gemma.intevation.de/gemma/pkg/common" + "gemma.intevation.de/gemma/pkg/models" +) + +type Stretch struct { + Name string `json:"name"` + From models.Isrs `json:"from"` + To models.Isrs `json:"to"` + ObjNam string `json:"objnam"` + NObjNam *string `json:"nobjnam"` + Source string `json:"source-organization"` + Date models.Date `json:"date-info"` + Countries models.UniqueCountries `json:"countries"` +} + +const STJobKind JobKind = "st" + +type stJobCreator struct{} + +func init() { + RegisterJobCreator(STJobKind, stJobCreator{}) +} + +func (stJobCreator) Description() string { return "stretch" } + +func (stJobCreator) AutoAccept() bool { return false } + +func (stJobCreator) Create(_ JobKind, data string) (Job, error) { + st := new(Stretch) + if err := common.FromJSONString(data, st); err != nil { + return nil, err + } + return st, nil +} + +func (stJobCreator) Depends() []string { + return []string{ + "stretches", + } +} + +const ( + stDeleteSQL = ` +DELETE FROM waterway.stretches WHERE +staging_done AND name = ( + SELECT name + FROM waterway.stretches WHERE + id = ( + SELECT key from waterway.track_imports + WHERE import_id = $1 AND + relation = 'waterway.stretches'::regclass) + AND NOT staging_done +)` + + stStageDoneSQL = ` +UPDATE waterway.stretches SET staging_done = true +WHERE id IN ( + SELECT key from waterway.track_imports + WHERE import_id = $1 AND + relation = 'waterway.stretches'::regclass)` + + stInsertSQL = ` +WITH r AS ( + SELECT isrsrange( + ( $1::char(2), + $2::char(3), + $3::char(5), + $4::char(5), + $5::int), + ( $6::char(2), + $7::char(3), + $8::char(5), + $9::char(5), + $10::int)) AS r +) +INSERT INTO waterway.stretches ( + name, + stretch, + geom, + objnam, + nobjnam, + date_info, + source_organization +) VALUES ( + $11, + (SELECT r FROM r), + ISRSrange_area( + (SELECT r FROM r), + (SELECT ST_Union(CAST(area AS geometry)) + FROM waterway.waterway_area)), + $12, + $13, + $14, + $15) +RETURNING id` + + stInsertCountrySQL = ` +INSERT INTO waterway.stretch_countries ( + stretches_id, + country_code +) VALUES ( + $1, + $2 +)` +) + +// StageDone moves the imported stretch out of the staging area. +func (stJobCreator) StageDone( + ctx context.Context, + tx *sql.Tx, + id int64, +) error { + if _, err := tx.ExecContext(ctx, stDeleteSQL, id); err != nil { + return err + } + _, err := tx.ExecContext(ctx, stStageDoneSQL, id) + return err +} + +// CleanUp of a stretch import is a NOP. +func (*Stretch) CleanUp() error { return nil } + +// Do executes the actual stretch import. +func (st *Stretch) Do( + ctx context.Context, + importID int64, + conn *sql.Conn, + feedback Feedback, +) (interface{}, error) { + + start := time.Now() + + if st.Date.Time.IsZero() { + st.Date = models.Date{Time: start} + } + + feedback.Info("Storing stretch '%s'", st.Name) + + if len(st.Countries) == 0 { + return nil, errors.New("List of countries is empty") + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + insertCountryStmt, err := tx.PrepareContext(ctx, stInsertCountrySQL) + if err != nil { + return nil, err + } + + var nobjnm sql.NullString + if st.NObjNam != nil { + nobjnm = sql.NullString{String: *st.NObjNam, Valid: true} + } + + var id int64 + if err := tx.QueryRowContext( + ctx, + stInsertSQL, + st.From.CountryCode, + st.From.LoCode, + st.From.FairwaySection, + st.From.Orc, + st.From.Hectometre, + st.To.CountryCode, + st.To.LoCode, + st.To.FairwaySection, + st.To.Orc, + st.To.Hectometre, + st.Name, + st.ObjNam, + nobjnm, + st.Date.Time, + st.Source, + ).Scan(&id); err != nil { + return nil, err + } + + // store the associated countries. + for _, c := range st.Countries { + if _, err := insertCountryStmt.ExecContext(ctx, id, c); err != nil { + return nil, err + } + } + + if err := track(ctx, tx, importID, "waterway.stretches", id); err != nil { + return nil, err + } + + feedback.Info("Storing stretch '%s' took %s", st.Name, time.Since(start)) + if err := tx.Commit(); err != nil { + return nil, err + } + feedback.Info("Import of stretch was successful") + + summary := struct { + Stretch string `json:"stretch"` + }{ + Stretch: st.Name, + } + + return &summary, nil +}
--- a/pkg/imports/wa.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/imports/wa.go Tue Jan 22 11:22:17 2019 +0100 @@ -104,11 +104,11 @@ SELECT ST_Transform(clipped.geom, 4326)::geography, $3, $4 FROM ( SELECT (ST_Dump( ST_Intersection( - (SELECT a FROM resp), - ST_Transform( + (SELECT ST_Buffer(a, 0.0001) FROM resp), + ST_CollectionExtract(ST_MakeValid(ST_Transform( ST_GeomFromWKB($1, $2::integer), (SELECT t FROM resp) - ) + )),3) ) )).geom AS geom ) AS clipped
--- a/pkg/imports/wg.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/imports/wg.go Tue Jan 22 11:22:17 2019 +0100 @@ -382,7 +382,7 @@ ic.code.Orc, ic.code.Hectometre, string(*dr.Objname.Loc), - int64(*dr.Lat), int64(*dr.Lon), + int64(*dr.Lon), int64(*dr.Lat), from, to, &validity,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/misc/time.go Tue Jan 22 11:22:17 2019 +0100 @@ -0,0 +1,34 @@ +// This is Free Software under GNU Affero General Public License v >= 3.0.Reader. +// 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): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package misc + +import "time" + +// TimeGuesser is a list of time formats. +type TimeGuesser []string + +// Guess tries to parse a given string by the entries of the layout +// list one after another. The first matching time is returned. +// If no layout matches the last error is returned or time zero +// if the layout list is empty. +func (tg TimeGuesser) Guess(s string) (time.Time, error) { + var err error + var t time.Time + for _, layout := range tg { + if t, err = time.Parse(layout, s); err == nil { + break + } + } + return t, err +}
--- a/pkg/models/common.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/models/common.go Tue Jan 22 11:22:17 2019 +0100 @@ -13,7 +13,14 @@ package models -import "errors" +import ( + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "strings" + "time" +) var ( errNoString = errors.New("Not a string") @@ -22,3 +29,85 @@ // WGS84 is the EPSG of the World Geodetic System 1984. const WGS84 = 4326 + +const DateFormat = "2006-01-02" + +type ( + Date struct{ time.Time } + // Country is a valid country 2 letter code. + Country string + // UniqueCountries is a list of unique countries. + UniqueCountries []Country +) + +func (srd Date) MarshalJSON() ([]byte, error) { + return json.Marshal(srd.Format(DateFormat)) +} + +func (srd *Date) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + d, err := time.Parse(DateFormat, s) + if err == nil { + *srd = Date{d} + } + return err +} + +var ( + validCountries = []string{ + "AT", "BG", "DE", "HU", "HR", + "MD", "RO", "RS", "SK", "UA", + } + errNoValidCountry = errors.New("Not a valid country") +) + +// UnmarshalJSON ensures that the given string forms a valid +// two letter country code. +func (c *Country) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + s = strings.ToUpper(s) + for _, v := range validCountries { + if v == s { + *c = Country(v) + return nil + } + } + return errNoValidCountry +} + +// Value implements the driver.Valuer interface. +func (c Country) Value() (driver.Value, error) { + return string(c), nil +} + +// Scan implements the sql.Scanner interfaces. +func (c *Country) Scan(src interface{}) (err error) { + if s, ok := src.(string); ok { + *c = Country(s) + } else { + err = errNoString + } + return +} + +func (uc *UniqueCountries) UnmarshalJSON(data []byte) error { + var countries []Country + if err := json.Unmarshal(data, &countries); err != nil { + return err + } + unique := map[Country]struct{}{} + for _, c := range countries { + if _, found := unique[c]; found { + return fmt.Errorf("country '%s' is not unique", string(c)) + } + unique[c] = struct{}{} + } + *uc = countries + return nil +}
--- a/pkg/models/gauge.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/models/gauge.go Tue Jan 22 11:22:17 2019 +0100 @@ -14,9 +14,6 @@ package models import ( - "errors" - "fmt" - "strconv" "time" "gemma.intevation.de/gemma/pkg/common" @@ -36,41 +33,3 @@ Gauge Isrs LatestDateIssue time.Time } - -// Isrs represents the gauge identification data structure -type Isrs struct { - CountryCode string - LoCode string - FairwaySection string - Orc string - Hectometre int -} - -// IsrsFromString converts string representation of isrs code to type Isrs -func IsrsFromString(isrsCode string) (*Isrs, error) { - if len(isrsCode) < 20 { - return nil, errors.New("ISRS code too short") - } - hm, err := strconv.Atoi(isrsCode[15:20]) - if err != nil { - return nil, err - } - isrs := Isrs{ - CountryCode: isrsCode[0:2], - LoCode: isrsCode[2:5], - FairwaySection: isrsCode[5:10], - Orc: isrsCode[10:15], - Hectometre: hm, - } - return &isrs, nil -} - -// String creates a isrs code string from Isrs -func (isrs *Isrs) String() string { - return fmt.Sprintf("%s%s%s%s%05d", - isrs.CountryCode, - isrs.LoCode, - isrs.FairwaySection, - isrs.Orc, - isrs.Hectometre) -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/models/isrs.go Tue Jan 22 11:22:17 2019 +0100 @@ -0,0 +1,80 @@ +// 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): +// * Raimund Renkert <raimund.renkert@intevation.de> +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package models + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" +) + +// Isrs represents the gauge identification data structure +type Isrs struct { + CountryCode string + LoCode string + FairwaySection string + Orc string + Hectometre int +} + +func (isrs *Isrs) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + in, err := IsrsFromString(s) + if err != nil { + return err + } + *isrs = *in + return nil +} + +func (isrs *Isrs) MarshalJSON() ([]byte, error) { + if isrs == nil { + return nil, nil + } + return json.Marshal(isrs.String()) +} + +// IsrsFromString converts string representation of isrs code to type Isrs +func IsrsFromString(isrsCode string) (*Isrs, error) { + if len(isrsCode) < 20 { + return nil, errors.New("ISRS code too short") + } + hm, err := strconv.Atoi(isrsCode[15:20]) + if err != nil { + return nil, err + } + isrs := Isrs{ + CountryCode: isrsCode[0:2], + LoCode: isrsCode[2:5], + FairwaySection: isrsCode[5:10], + Orc: isrsCode[10:15], + Hectometre: hm, + } + return &isrs, nil +} + +// String creates a isrs code string from Isrs +func (isrs *Isrs) String() string { + return fmt.Sprintf("%s%s%s%s%05d", + isrs.CountryCode, + isrs.LoCode, + isrs.FairwaySection, + isrs.Orc, + isrs.Hectometre) +}
--- a/pkg/models/sr.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/models/sr.go Tue Jan 22 11:22:17 2019 +0100 @@ -21,19 +21,14 @@ "errors" "fmt" "io" - "time" ) -const SoundingResultDateFormat = "2006-01-02" - type ( - SoundingResultDate struct{ time.Time } - SoundingResultMeta struct { - Date SoundingResultDate `json:"date"` - Bottleneck string `json:"bottleneck"` - EPSG uint `json:"epsg"` - DepthReference string `json:"depth-reference"` + Date Date `json:"date"` + Bottleneck string `json:"bottleneck"` + EPSG uint `json:"epsg"` + DepthReference string `json:"depth-reference"` } ) @@ -50,22 +45,6 @@ WHERE bn.objnam = $1 AND sr.date_info = $2` ) -func (srd SoundingResultDate) MarshalJSON() ([]byte, error) { - return json.Marshal(srd.Format(SoundingResultDateFormat)) -} - -func (srd *SoundingResultDate) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - d, err := time.Parse(SoundingResultDateFormat, s) - if err == nil { - *srd = SoundingResultDate{d} - } - return err -} - func (m *SoundingResultMeta) Decode(r io.Reader) error { err := json.NewDecoder(r).Decode(m) if err == nil && m.EPSG == 0 {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/models/stretch.go Tue Jan 22 11:22:17 2019 +0100 @@ -0,0 +1,30 @@ +// 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): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package models + +import "gemma.intevation.de/gemma/pkg/common" + +type StretchImport struct { + Name string `json:"name"` + From Isrs `json:"from"` + To Isrs `json:"to"` + ObjNam string `json:"objnam"` + NObjNam *string `json:"nobjnam"` + Source string `json:"source-organization"` + Date Date `json:"date-info"` + Countries UniqueCountries `json:"countries"` + + SendEmail bool `json:"send-email"` + Attributes common.Attributes `json:"attributes,omitempty"` +}
--- a/pkg/models/user.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/models/user.go Tue Jan 22 11:22:17 2019 +0100 @@ -25,8 +25,6 @@ type ( // Email is a string formed by a valid EMail address. Email string - // Country is a valid country 2 letter code. - Country string // Role is a string with a valid gemma role. Role string // UserName is a string forming a valid user name. @@ -132,46 +130,6 @@ } var ( - validCountries = []string{ - "AT", "BG", "DE", "HU", "HR", - "MD", "RO", "RS", "SK", "UA", - } - errNoValidCountry = errors.New("Not a valid country") -) - -// UnmarshalJSON ensures that the given string forms a valid -// two letter country code. -func (c *Country) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - s = strings.ToUpper(s) - for _, v := range validCountries { - if v == s { - *c = Country(v) - return nil - } - } - return errNoValidCountry -} - -// Value implements the driver.Valuer interface. -func (c Country) Value() (driver.Value, error) { - return string(c), nil -} - -// Scan implements the sql.Scanner interfaces. -func (c *Country) Scan(src interface{}) (err error) { - if s, ok := src.(string); ok { - *c = Country(s) - } else { - err = errNoString - } - return -} - -var ( validRoles = []string{ "waterway_user", "waterway_admin",
--- a/pkg/wfs/download.go Fri Jan 18 06:51:49 2019 +0000 +++ b/pkg/wfs/download.go Tue Jan 22 11:22:17 2019 +0100 @@ -264,7 +264,7 @@ func DownloadURLs(urls []string, handler func(io.Reader) error) error { for _, url := range urls { if err := downloadURL(url, handler); err != nil { - return nil + return err } } return nil
--- a/schema/auth.sql Fri Jan 18 06:51:49 2019 +0000 +++ b/schema/auth.sql Tue Jan 22 11:22:17 2019 +0100 @@ -78,7 +78,7 @@ BEGIN FOREACH the_table IN ARRAY ARRAY[ 'gauge_measurements', - 'sections_stretches', + 'stretches', 'waterway_profiles', 'fairway_dimensions', 'bottlenecks', @@ -125,6 +125,9 @@ FOR ALL TO waterway_admin USING (utm_covers(area)); +CREATE POLICY responsibility_area ON waterway.stretches + FOR ALL TO sys_admin + USING (utm_covers(geom)); -- -- RLS policies for imports and import config @@ -138,22 +141,48 @@ ALTER table waterway.imports ENABLE ROW LEVEL SECURITY; -- The job running the import queue is running as sys_admin and login users --- with that role should see all imports anyhow -CREATE POLICY read_all ON waterway.imports - FOR SELECT TO sys_admin +-- with that role should be able to run imports without restrictions anyhow +CREATE POLICY import_all ON waterway.imports + FOR ALL TO sys_admin USING (true); -CREATE POLICY update_all ON waterway.imports - FOR UPDATE TO sys_admin - USING (true); + +-- For the given table, check whether the given value is used as primary key, +-- bypassing row level security. +CREATE OR REPLACE FUNCTION waterway.is_new_key( + tablename varchar, + kv anyelement) + RETURNS boolean +AS $$ +DECLARE columnname varchar; +DECLARE ret boolean; +BEGIN + columnname = (SELECT column_name + FROM information_schema.key_column_usage k + JOIN information_schema.table_constraints USING (constraint_name) + WHERE k.table_name = tablename and constraint_type = 'PRIMARY KEY'); + EXECUTE format('SELECT NOT $1 = ANY(SELECT %I FROM waterway.%I)', + columnname, tablename) + INTO ret + USING kv; + RETURN ret; +END; +$$ + LANGUAGE plpgsql + SECURITY DEFINER + STABLE PARALLEL SAFE; CREATE POLICY parent_allowed ON waterway.import_logs FOR ALL TO waterway_admin - USING (import_id IN (SELECT id FROM waterway.imports)); + USING (import_id IN (SELECT id FROM waterway.imports)) + WITH CHECK (waterway.is_new_key('imports', import_id) + OR import_id IN (SELECT id FROM waterway.imports)); ALTER table waterway.import_logs ENABLE ROW LEVEL SECURITY; CREATE POLICY parent_allowed ON waterway.track_imports FOR ALL TO waterway_admin - USING (import_id IN (SELECT id FROM waterway.imports)); + USING (import_id IN (SELECT id FROM waterway.imports)) + WITH CHECK (waterway.is_new_key('imports', import_id) + OR import_id IN (SELECT id FROM waterway.imports)); ALTER table waterway.track_imports ENABLE ROW LEVEL SECURITY; CREATE POLICY import_configuration_policy ON waterway.import_configuration @@ -172,7 +201,11 @@ CREATE POLICY parent_allowed ON waterway.import_configuration_attributes FOR ALL TO waterway_admin USING (import_configuration_id IN ( - SELECT id FROM waterway.import_configuration)); + SELECT id FROM waterway.import_configuration)) + WITH CHECK ( + waterway.is_new_key('import_configuration', import_configuration_id) + OR import_configuration_id IN ( + SELECT id FROM waterway.import_configuration)); ALTER table waterway.import_configuration_attributes ENABLE ROW LEVEL SECURITY; COMMIT;
--- a/schema/auth_tests.sql Fri Jan 18 06:51:49 2019 +0000 +++ b/schema/auth_tests.sql Tue Jan 22 11:22:17 2019 +0100 @@ -121,3 +121,94 @@ DELETE FROM users.templates WHERE template_name = 'RO' RETURNING * $$, 'Waterway admin cannot delete templates for other country'); + +-- import management +SELECT lives_ok($$ + WITH + job AS ( + INSERT INTO waterway.imports (kind, username, data) VALUES ( + 'test', current_user, 'test') RETURNING id), + log AS ( + INSERT INTO waterway.import_logs (import_id, msg) + SELECT id, 'test' FROM job) + INSERT INTO waterway.track_imports + SELECT id, 'waterway.bottlenecks', 0 FROM job + $$, + 'Waterway admin can add import job and related data'); + +SET SESSION AUTHORIZATION test_admin_at2; +SELECT bag_has($$ + SELECT username FROM users.list_users + $$, + $$ + WITH job AS ( + UPDATE waterway.imports SET state = 'accepted' + RETURNING id, username), + log AS ( + INSERT INTO waterway.import_logs (import_id, msg) + SELECT id, 'test continued' FROM job) + SELECT username FROM job + $$, + 'Waterway admin can edit import jobs from his country only'); + +SELECT lives_ok($$ + WITH + config AS ( + INSERT INTO waterway.import_configuration (kind, username) VALUES ( + 'test', current_user) RETURNING id) + INSERT INTO waterway.import_configuration_attributes + SELECT id, 'test key', 'test value' FROM config + $$, + 'Waterway admin can add import config and related data'); + +SET SESSION AUTHORIZATION test_admin_at; +SELECT bag_has($$ + SELECT username FROM users.list_users + $$, + $$ + WITH config AS ( + UPDATE waterway.import_configuration SET send_email = true + RETURNING id, username), + attrib AS ( + INSERT INTO waterway.import_configuration_attributes + SELECT id, 'test continued', 'test value' FROM config), + attrib_upd AS ( + UPDATE waterway.import_configuration_attributes SET v = 'test v' + WHERE import_configuration_id = (SELECT id FROM config)) + SELECT username FROM config + $$, + 'Waterway admin can edit import config from his country only'); + +SET SESSION AUTHORIZATION test_admin_ro; +SELECT throws_ok($$ + INSERT INTO waterway.import_logs (import_id, msg) + VALUES (currval(pg_get_serial_sequence('waterway.imports', 'id')), + 'test') + $$, + 42501, NULL, + 'Waterway admin cannot add log messages to other countries imports'); + +SELECT throws_ok($$ + DELETE FROM waterway.track_imports + WHERE import_id = currval( + pg_get_serial_sequence('waterway.imports', 'id')) + $$, + 42501, NULL, + 'Waterway admin cannot delete tracking data of other countries imports'); + +SELECT throws_ok($$ + INSERT INTO waterway.import_configuration_attributes + VALUES (currval(pg_get_serial_sequence( + 'waterway.import_configuration', 'id')), + 'test', 'test value') + $$, + 42501, NULL, + 'Waterway admin cannot add attributes to other countries import config'); + +SELECT throws_ok($$ + UPDATE waterway.import_configuration_attributes SET v = 'evil' + WHERE import_configuration_id = currval( + pg_get_serial_sequence('waterway.import_configuration', 'id')) + $$, + 42501, NULL, + 'Waterway admin cannot overwrite attributes of other countries config');
--- a/schema/demo-data/published_services.sql Fri Jan 18 06:51:49 2019 +0000 +++ b/schema/demo-data/published_services.sql Tue Jan 22 11:22:17 2019 +0100 @@ -12,6 +12,7 @@ -- * Tom Gottfried <tom@intevation.de> INSERT INTO sys_admin.published_services (name) VALUES + ('waterway.stretches_geoserver'), ('waterway.fairway_dimensions'), ('waterway.distance_marks_geoserver'), ('waterway.sounding_results_contour_lines_geoserver'),
--- a/schema/gemma.sql Fri Jan 18 06:51:49 2019 +0000 +++ b/schema/gemma.sql Tue Jan 22 11:22:17 2019 +0100 @@ -347,18 +347,49 @@ check (pk_policy in ('sequence', 'assigned', 'autogenerated')) ) - CREATE TABLE sections_stretches ( - id varchar PRIMARY KEY, - is_section boolean NOT NULL, -- maps 'function' from interface - stretch isrsrange, + CREATE TABLE stretches ( + id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, + name varchar NOT NULL, + stretch isrsrange NOT NULL, + -- TODO: make it a (MULTI)-LINESTRING. + -- POLYGON is chosen for the convinience + -- of re-using ISRSrange_area. + geom geography(POLYGON, 4326) NOT NULL, objnam varchar NOT NULL, nobjnam varchar, date_info timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, source_organization varchar NOT NULL, - staging_done boolean NOT NULL DEFAULT false + staging_done boolean NOT NULL DEFAULT false, + UNIQUE(name, staging_done) + ) + + CREATE TABLE stretch_countries ( + stretches_id int NOT NULL REFERENCES stretches(id) + ON DELETE CASCADE, + country_code char(2) NOT NULL REFERENCES countries(country_code), + UNIQUE(stretches_id, country_code) ) + + -- Published view for GeoServer + CREATE VIEW stretches_geoserver AS SELECT + id, + name, + (stretch).lower::varchar as lower, + (stretch).upper::varchar as upper, + geom::Geometry(POLYGON, 4326), + objnam, + nobjnam, + date_info, + source_organization, + (SELECT string_agg(country_code, ', ') + FROM stretch_countries + WHERE stretches_id = id) AS countries, + staging_done + FROM stretches + + CREATE TRIGGER sections_stretches_date_info - BEFORE UPDATE ON sections_stretches + BEFORE UPDATE ON stretches FOR EACH ROW EXECUTE PROCEDURE update_date_info() CREATE TABLE waterway_profiles (
--- a/schema/manage_users_tests.sql Fri Jan 18 06:51:49 2019 +0000 +++ b/schema/manage_users_tests.sql Tue Jan 22 11:22:17 2019 +0100 @@ -40,7 +40,7 @@ SELECT set_eq($$ SELECT count(*) FROM users.list_users $$, - ARRAY[4], + ARRAY[6], 'System admin can see all users'); --
--- a/schema/run_tests.sh Fri Jan 18 06:51:49 2019 +0000 +++ b/schema/run_tests.sh Tue Jan 22 11:22:17 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(47)' \ + -c 'SELECT plan(55)' \ -f isrs_tests.sql \ -f auth_tests.sql \ -f manage_users_tests.sql \
--- a/schema/tap_tests_data.sql Fri Jan 18 06:51:49 2019 +0000 +++ b/schema/tap_tests_data.sql Tue Jan 22 11:22:17 2019 +0100 @@ -29,6 +29,10 @@ INSERT INTO users.list_users VALUES ( 'waterway_admin', 'test_admin_at', 'admin_at1$', 'AT', NULL, 'yyy'); INSERT INTO users.list_users VALUES ( + 'waterway_admin', 'test_admin_at2', 'admin_at2$', 'AT', NULL, 'yyy'); +INSERT INTO users.list_users VALUES ( + 'waterway_admin', 'test_admin_ro', 'admin_ro1$', 'RO', NULL, 'yyx'); +INSERT INTO users.list_users VALUES ( 'sys_admin', 'test_sys_admin1', 'sys_admin1$', 'AT', NULL, 'zzz'); INSERT INTO limiting_factors VALUES ('depth'), ('width'); @@ -88,3 +92,20 @@ VALUES ('AT', '\x'), ('RO', '\x'); INSERT INTO users.user_templates VALUES ('test_user_at', 'AT'), ('test_user_ro', 'RO'); + +WITH +job AS ( + INSERT INTO waterway.imports (kind, username, data) VALUES ( + 'test', 'test_admin_ro', 'test') RETURNING id), +log AS ( + INSERT INTO waterway.import_logs (import_id, msg) + SELECT id, 'test' FROM job) +INSERT INTO waterway.track_imports + SELECT id, 'waterway.bottlenecks', 1 FROM job; + +WITH +config AS ( + INSERT INTO waterway.import_configuration (kind, username) VALUES ( + 'test', 'test_admin_ro') RETURNING id) +INSERT INTO waterway.import_configuration_attributes + SELECT id, 'test key', 'test value' FROM config;