Mercurial > gemma
changeset 3320:1473e9e7cd0c
merge
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Mon, 20 May 2019 09:41:32 +0200 |
parents | 9dc7d803e51f (current diff) 753e7c8e8c95 (diff) |
children | bd1385c00b59 |
files | client/src/components/fairway/AvailableFairwayDepthDialogue.vue |
diffstat | 18 files changed, 691 insertions(+), 513 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/Sidebar.vue Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/Sidebar.vue Mon May 20 09:41:32 2019 +0200 @@ -12,21 +12,17 @@ </div> <div class="menu text-nowrap text-left"> <router-link to="/"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="map-marked-alt" - /> + <font-awesome-icon class="mr-2" fixed-width icon="map-marked-alt" /> <span class="fix-trans-space" v-translate>Map</span> </router-link> <router-link to="/bottlenecks"> - <font-awesome-icon class="fa-fw mr-2" fixed-width icon="ship" /> + <font-awesome-icon class="mr-2" fixed-width icon="ship" /> <span class="fix-trans-space" v-translate>Bottlenecks</span> </router-link> <div v-if="isWaterwayAdmin"> <router-link to="/imports/overview" class="position-relative"> <font-awesome-icon - class="fa-fw mr-2" + class="mr-2" fixed-width icon="clipboard-check" /> @@ -38,13 +34,19 @@ </div> <div v-if="isSysAdmin"> <router-link to="/stretches"> - <font-awesome-icon class="fa-fw mr-2" fixed-width icon="road" /> + <font-awesome-icon class="mr-2" fixed-width icon="road" /> <span class="fix-trans-space" v-translate>Define stretches</span> </router-link> </div> <div v-if="isWaterwayAdmin"> + <router-link to="/sections"> + <font-awesome-icon class="mr-2" fixed-width icon="road" /> + <span class="fix-trans-space" v-translate>Define sections</span> + </router-link> + </div> + <div v-if="isWaterwayAdmin"> <router-link to="/imports/configuration"> - <font-awesome-icon class="fa-fw mr-2" fixed-width icon="clock" /> + <font-awesome-icon class="mr-2" fixed-width icon="clock" /> <translate class="fix-trans-space">Imports</translate> </router-link> <small class="text-muted pl-3"> @@ -54,29 +56,25 @@ </div> <div v-if="isSysAdmin"> <router-link to="/usermanagement"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="users-cog" - /> + <font-awesome-icon class="mr-2" fixed-width icon="users-cog" /> <span class="fix-trans-space" v-translate>Users</span> </router-link> </div> <div v-if="isWaterwayAdmin"> <router-link to="/systemconfiguration"> - <font-awesome-icon class="fa-fw mr-2" fixed-width icon="wrench" /> + <font-awesome-icon class="mr-2" fixed-width icon="wrench" /> <span class="fix-trans-space" v-translate>Configuration</span> </router-link> </div> <div v-if="isSysAdmin"> <router-link to="/logs"> - <font-awesome-icon class="fa-fw mr-2" fixed-width icon="book" /> + <font-awesome-icon class="mr-2" fixed-width icon="book" /> <span class="fix-trans-space" v-translate>Logs</span> </router-link> </div> <hr class="m-0" /> <a @click="logoff" href="#" class="logout"> - <font-awesome-icon class="fa-fw mr-2" fixed-width icon="power-off" /> + <font-awesome-icon class="mr-2" fixed-width icon="power-off" /> <span class="fix-trans-space" v-translate>Logout</span> {{ user }} </a> </div>
--- a/client/src/components/fairway/AvailableFairwayDepthDialogue.vue Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/fairway/AvailableFairwayDepthDialogue.vue Mon May 20 09:41:32 2019 +0200 @@ -9,9 +9,7 @@ <UIBoxHeader icon="chart-line" :title="label" :closeCallback="close" /> <div class="box-body"> <UISpinnerOverlay v-if="loading" /> - <div - class="mb-3 d-flex flex-row justify-content-between align-items-center" - > + <div class="mb-2 d-flex justify-content-between align-items-center"> <div> <input :value="$options.BOTTLENECKS" type="radio" v-model="type" /> <small class="ml-1 text-muted"> @@ -31,111 +29,120 @@ </small> </div> </div> - <div class="d-flex flex-column"> - <select - :key="1" - v-if="type === $options.BOTTLENECKS" - @change="entrySelected" - class="form-control form-control-sm font-weight-bold" - v-model="selectedEntry" + <select + v-if="type === $options.BOTTLENECKS" + @change="entrySelected" + class="form-control font-weight-bold" + v-model="selectedEntry" + > + <option :value="null">{{ placeholder }}</option> + <optgroup + v-for="(bottlenecksForCountry, cc) in orderedBottlenecks" + :key="cc" + :label="cc" > - <option :value="null">{{ empty }}</option> - <optgroup - v-for="(bottlenecksForCountry, cc) in orderedBottlenecks" - :key="cc" - :label="cc" + <option + v-for="bn in bottlenecksForCountry" + :key="bn.properties.name" + :value="bn" + > + {{ bn.properties.name }} + </option> + </optgroup> + </select> + <select + v-else-if="type === $options.STRETCHES" + @change="entrySelected" + class="form-control font-weight-bold" + v-model="selectedEntry" + > + <option :value="null">{{ placeholder }}</option> + <option + v-for="stretch in stretches" + :value="stretch" + :key="stretch.id" + > + {{ stretch.properties.name }} + </option> + </select> + <select + v-else-if="type === $options.SECTIONS" + @change="entrySelected" + class="form-control font-weight-bold" + v-model="selectedEntry" + > + <option :value="null">{{ placeholder }}</option> + <option + v-for="section in sections" + :value="section" + :key="section.id" + > + {{ section.properties.name }} + </option> + </select> + <div class="d-flex mt-2"> + <div class="d-flex flex-column w-50 mr-1"> + <small class="my-auto text-muted"> + <translate>Type</translate> + </small> + <select + v-model="selectedFrequency" + class="form-control form-control-sm" > <option - v-for="bn in bottlenecksForCountry" - :key="bn.properties.name" - :value="bn" - > - {{ bn.properties.name }} - </option> - </optgroup> - </select> - <select - :key="2" - v-else - @change="entrySelected" - class="form-control form-control-sm font-weight-bold" - v-model="selectedEntry" - > - <option :value="null">{{ empty }}</option> - <option - v-for="(entry, index) in entries" - :value="entry" - :key="index" - >{{ entry.properties.name }}</option - > - </select> - </div> - <div class="d-flex flex-column mt-3"> - <div class="d-flex flex-row w-100"> - <div class="d-flex flex-column mb-3 w-50 mr-1"> - <small class="my-auto text-muted" - ><translate>Type</translate></small - > - <select - v-model="selectedFrequency" - class="form-control form-control-sm" + v-for="(option, index) in $options.FREQUENCIES" + :value="option" + :key="index" > - <option - v-for="(option, index) in $options.FREQUENCIES" - :value="option" - :key="index" - ><translate>{{ option }}</translate></option - > - </select> - </div> - <div class="d-flex flex-column mb-3 w-50 ml-1"> - <small class="my-auto text-muted" - ><translate>LOS</translate></small - > - <select v-model="los" class="form-control form-control-sm"> - <option value="1">1</option> - <option value="2">2</option> - <option value="3">3</option> - </select> - </div> + <translate>{{ option }}</translate> + </option> + </select> + </div> + <div class="d-flex flex-column w-50 ml-1"> + <small class="my-auto text-muted"><translate>LOS</translate></small> + <select v-model="los" class="form-control form-control-sm"> + <option value="1">1</option> + <option value="2">2</option> + <option value="3">3</option> + </select> </div> - <div class="d-flex flex-row w-100"> - <div class="d-flex flex-column w-50 mr-1"> - <small for="from" class="my-auto text-muted" - ><translate>Date from</translate></small - ><input - id="from" - v-model="fromDate" - class="form-control form-control-sm" - type="date" - /> - </div> - <div class="d-flex flex-column w-50 ml-1"> - <small for="to" class="my-auto text-muted" - ><translate>Date to</translate></small - ><input - id="to" - v-model="toDate" - class="form-control form-control-sm" - type="date" - /> - </div> + </div> + <div class="d-flex mt-2"> + <div class="d-flex flex-column w-50 mr-1"> + <small for="from" class="my-auto text-muted"> + <translate>Date from</translate> + </small> + <input + id="from" + v-model="fromDate" + class="form-control form-control-sm" + type="date" + /> + </div> + <div class="d-flex flex-column w-50 ml-1"> + <small for="to" class="my-auto text-muted"> + <translate>Date to</translate> + </small> + <input + id="to" + v-model="toDate" + class="form-control form-control-sm" + type="date" + /> </div> </div> <div class="mt-3"> <button - @click="openFairwaydepth" - :disabled="isComplete" - class="btn btn-info btn-sm w-100" + @click="openFairwaydepthDiagram" + :disabled="!isComplete" + class="btn btn-info btn-sm d-block w-100" > <translate>Available fairway depth</translate> </button> - </div> - <div class="mt-3"> <button - @click="openFairwaydepthLNWL" - :disabled="isInComplete" - class="btn btn-info btn-sm w-100" + @click="openFairwaydepthLNWLDiagram" + :disabled="!isComplete" + class="btn btn-info btn-sm d-block w-100 mt-2" > <translate>Available fairway depth vs LNWL</translate> </button> @@ -178,106 +185,6 @@ loading: false }; }, - methods: { - openFairwaydepthLNWL() { - this.loading = true; - this.$store - .dispatch("fairwayavailability/loadAvailableFairwayDepth", { - feature: this.selectedFairwayAvailabilityFeature, - from: this.from, - to: this.to, - frequency: this.frequency, - LOS: this.los - }) - .then(() => { - this.$store.commit( - "application/paneSetup", - "AVAILABLEFAIRWAYDEPTHLNWL" - ); - }) - .catch(error => { - console.log(error); - const { status, data } = error.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }) - .finally(() => { - this.loading = false; - }); - }, - openFairwaydepth() { - this.loading = true; - this.$store - .dispatch("fairwayavailability/loadAvailableFairwayDepth", { - feature: this.selectedFairwayAvailabilityFeature, - from: this.from, - to: this.to, - frequency: this.frequency, - LOS: this.los - }) - .then(() => { - this.$store.commit("application/paneSetup", "AVAILABLEFAIRWAYDEPTH"); - }) - .catch(error => { - console.log(error); - const { status, data } = error.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }) - .finally(() => { - this.loading = false; - }); - }, - close() { - this.$store.commit("application/showFairwayDepth", false); - this.$store.commit("application/showFairwayDepthLNWL", false); - }, - entrySelected() { - if (this.type === this.$options.BOTTLENECKS) { - this.openLayersMap() - .getLayer("BOTTLENECKS") - .setVisible(true); - if (this.showProfiles) { - this.$store.dispatch( - "bottlenecks/setSelectedBottleneck", - this.selectedFairwayAvailabilityFeature.properties.name - ); - } - } - if (this.type === this.$options.STRETCHES) { - this.openLayersMap() - .getLayer("STRETCHES") - .setVisible(true); - } - if (this.selectedFairwayAvailabilityFeature) { - this.$store.dispatch("map/moveToFeauture", { - feature: this.selectedFairwayAvailabilityFeature, - zoom: 17, - preventZoomOut: true - }); - } - }, - setSelectedBottleneck() { - const bn = this.bottlenecksList.filter( - x => x.properties.name === this.selectedBottleneck - )[0]; - this.$store.commit( - "fairwayavailability/setSelectedFairwayAvailability", - bn - ); - }, - setSelectedStretch() { - const stretch = this.stretches.find(x => x.id === this.selectedStretchId); - this.$store.commit( - "fairwayavailability/setSelectedFairwayAvailability", - stretch - ); - } - }, computed: { ...mapState("application", [ "showFairwayDepth", @@ -291,17 +198,22 @@ "frequency", "LOS" ]), - ...mapState("imports", ["stretches", "selectedStretchId"]), + ...mapState("imports", [ + "stretches", + "sections", + "selectedStretchId", + "selectedSectionId" + ]), ...mapState("bottlenecks", ["bottlenecksList", "selectedBottleneck"]), ...mapGetters("map", ["openLayersMap"]), ...mapGetters("bottlenecks", ["orderedBottlenecks"]), - isInComplete() { + isComplete() { return ( - this.from == null || - this.to == null || - this.frequency == null || - this.los == null || - this.selectedFairwayAvailabilityFeature == null + this.from !== null && + this.to !== null && + this.frequency !== null && + this.los !== null && + this.selectedFairwayAvailabilityFeature !== null ); }, type: { @@ -355,15 +267,10 @@ ); } }, - entries() { - if (this.type === this.$options.BOTTLENECKS) return this.bottlenecksList; - if (this.type === this.$options.STRETCHES) return this.stretches; - return []; - }, label() { return this.$gettext("Available fairway depth"); }, - empty() { + placeholder() { if (this.type === this.$options.BOTTLENECKS) return this.$gettext("Select bottleneck"); if (this.type === this.$options.STRETCHES) @@ -376,34 +283,162 @@ this.type = this.$options.BOTTLENECKS; this.setSelectedBottleneck(); }, - type() { - if (this.type === this.$options.BOTTLENECKS && this.selectedBottleneck) { + selectedStretchId() { + this.type = this.$options.STRETCHES; + this.setSelectedStretch(); + }, + selectedSectionId() { + this.type = this.$options.SECTIONS; + this.setSelectedSection(); + }, + type(type) { + if (type === this.$options.BOTTLENECKS && this.selectedBottleneck) { + this.openLayersMap() + .getLayer("BOTTLENECKS") + .setVisible(true); this.setSelectedBottleneck(); - } else if ( - this.type === this.$options.STRETCHES && - this.selectedStretchId - ) { + } else if (type === this.$options.STRETCHES && this.selectedStretchId) { + this.openLayersMap() + .getLayer("STRETCHES") + .setVisible(true); this.setSelectedStretch(); + } else if (type === this.$options.SECTIONS && this.selectedSectionId) { + this.openLayersMap() + .getLayer("SECTIONS") + .setVisible(true); + this.setSelectedSection(); } else { this.$store.commit( "fairwayavailability/setSelectedFairwayAvailability", null ); } - this.openLayersMap() - .getLayer("STRETCHES") - .setVisible(true); }, showFairwayDepth() { if (this.showFairwayDepth) { this.loading = true; - this.$store.dispatch("bottlenecks/loadBottlenecksList").then(() => { - this.$store.dispatch("imports/loadStretches").then(() => { - this.loading = false; + Promise.all([ + this.$store.dispatch("bottlenecks/loadBottlenecksList"), + this.$store.dispatch("imports/loadStretches"), + this.$store.dispatch("imports/loadSections") + ]) + .then(() => { if (this.selectedBottleneck) this.setSelectedBottleneck(); + }) + .finally(() => (this.loading = false)); + } + } + }, + methods: { + openFairwaydepthLNWLDiagram() { + this.loading = true; + this.$store + .dispatch("fairwayavailability/loadAvailableFairwayDepth", { + feature: this.selectedFairwayAvailabilityFeature, + from: this.from, + to: this.to, + frequency: this.frequency, + LOS: this.los + }) + .then(() => { + this.$store.commit( + "application/paneSetup", + "AVAILABLEFAIRWAYDEPTHLNWL" + ); + }) + .catch(error => { + console.log(error); + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` }); + }) + .finally(() => { + this.loading = false; + }); + }, + openFairwaydepthDiagram() { + this.loading = true; + this.$store + .dispatch("fairwayavailability/loadAvailableFairwayDepth", { + feature: this.selectedFairwayAvailabilityFeature, + from: this.from, + to: this.to, + frequency: this.frequency, + LOS: this.los + }) + .then(() => { + this.$store.commit("application/paneSetup", "AVAILABLEFAIRWAYDEPTH"); + }) + .catch(error => { + console.log(error); + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }) + .finally(() => { + this.loading = false; + }); + }, + close() { + this.$store.commit("application/showFairwayDepth", false); + this.$store.commit("application/showFairwayDepthLNWL", false); + }, + entrySelected() { + if (this.type === this.$options.BOTTLENECKS) { + this.openLayersMap() + .getLayer("BOTTLENECKS") + .setVisible(true); + if (this.showProfiles) { + this.$store.dispatch( + "bottlenecks/setSelectedBottleneck", + this.selectedFairwayAvailabilityFeature.properties.name + ); + } + } + if (this.type === this.$options.STRETCHES) { + this.openLayersMap() + .getLayer("STRETCHES") + .setVisible(true); + } + if (this.type === this.$options.SECTIONS) { + this.openLayersMap() + .getLayer("SECTIONS") + .setVisible(true); + } + if (this.selectedFairwayAvailabilityFeature) { + this.$store.dispatch("map/moveToFeauture", { + feature: this.selectedFairwayAvailabilityFeature, + zoom: 17, + preventZoomOut: true }); } + }, + setSelectedBottleneck() { + const bn = this.bottlenecksList.filter( + x => x.properties.name === this.selectedBottleneck + )[0]; + this.$store.commit( + "fairwayavailability/setSelectedFairwayAvailability", + bn + ); + }, + setSelectedStretch() { + const stretch = this.stretches.find(x => x.id === this.selectedStretchId); + this.$store.commit( + "fairwayavailability/setSelectedFairwayAvailability", + stretch + ); + }, + setSelectedSection() { + const section = this.sections.find(x => x.id === this.selectedSectionId); + this.$store.commit( + "fairwayavailability/setSelectedFairwayAvailability", + section + ); } }, BOTTLENECKS: "bottlenecks",
--- a/client/src/components/gauge/Waterlevel.vue Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/gauge/Waterlevel.vue Mon May 20 09:41:32 2019 +0200 @@ -55,7 +55,7 @@ </div> <div id="pdfContainer" - style="position: absolute; z-index: -1; top: 600px;" + style="position: absolute; z-index: -1; top: -9999px;" ></div> </div> </template> @@ -1191,7 +1191,15 @@ window.addEventListener("resize", debounce(this.drawDiagram), 100); }, mounted() { - this.drawDiagram(); + // Nasty but necessary if we don't want to use the updated hook to re-draw + // the diagram because this would re-draw it also for irrelevant reasons. + // In this case we need to wait for the child component (DiagramLegend) to + // render. According to the docs (https://vuejs.org/v2/api/#mounted) this + // should be possible with $nextTick() but it doesn't work because it does + // not guarantee that the DOM is not only updated but also re-painted on the + // screen. + setTimeout(this.drawDiagram, 50); + this.templates[0] = this.defaultTemplate; this.form.template = this.templates[0]; this.templateData = this.form.template;
--- a/client/src/components/identify/formatter.js Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/identify/formatter.js Mon May 20 09:41:32 2019 +0200 @@ -50,6 +50,9 @@ stretches_geoserver: { label: "Stretch" }, + sections_geoserver: { + label: "Section" + }, gauges_geoserver: { label: "Gauge" }
--- a/client/src/components/layers/Layers.vue Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/layers/Layers.vue Mon May 20 09:41:32 2019 +0200 @@ -22,6 +22,7 @@ <Layerselect layerId="INLANDECDIS" /> <Layerselect layerId="WATERWAYAREA" /> <Layerselect layerId="STRETCHES" /> + <Layerselect layerId="SECTIONS" /> <Layerselect layerId="FAIRWAYDIMENSIONSLOS3" /> <Layerselect layerId="FAIRWAYDIMENSIONSLOS2" /> <Layerselect layerId="FAIRWAYDIMENSIONSLOS1" />
--- a/client/src/components/map/Map.vue Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/map/Map.vue Mon May 20 09:41:32 2019 +0200 @@ -61,7 +61,7 @@ ...mapState("bottlenecks", ["selectedSurvey"]), ...mapState("fairwayprofile", ["additionalSurvey"]), ...mapState("application", ["paneSetup", "paneRotate"]), - ...mapState("imports", ["selectedStretchId"]), + ...mapState("imports", ["selectedStretchId", "selectedSectionId"]), layers() { return layers(); }, @@ -129,6 +129,18 @@ f.set("highlighted", true); } }); + }, + selectedSectionId(id) { + this.layers + .get("SECTIONS") + .getSource() + .getFeatures() + .forEach(f => { + f.set("highlighted", false); + if (id === f.getId()) { + f.set("highlighted", true); + } + }); } }, methods: {
--- a/client/src/components/map/MapPopup.vue Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/map/MapPopup.vue Mon May 20 09:41:32 2019 +0200 @@ -81,6 +81,7 @@ <button class="btn btn-xs btn-info" v-tooltip="fairwayAvailabilityLabel" + @click="openFairwayAvailabilityForSection(section)" > <font-awesome-icon icon="chart-line" fixed-width /> </button> @@ -229,6 +230,15 @@ zoom: 17 }); this.openFairwayAvailability(); + }, + openFairwayAvailabilityForSection(section) { + this.$store.commit("fairwayavailability/type", "sections"); + this.$store.commit("imports/selectedSectionId", section.getId()); + this.$store.dispatch("map/moveToFeauture", { + feature: section, + zoom: 17 + }); + this.openFairwayAvailability(); } }, mounted() {
--- a/client/src/components/map/layers.js Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/map/layers.js Mon May 20 09:41:32 2019 +0200 @@ -232,6 +232,32 @@ }); })(), (function() { + const source = new VectorSource({ strategy: bboxStrategy }); + source.setLoader( + buildVectorLoader( + { + featureTypes: ["sections_geoserver"], + geometryName: "area" + }, + source, + true, + (f, store) => { + if (f.getId() === store.state.imports.selectedSectionId) { + f.set("highlighted", true); + } + return f; + } + ) + ); + return new VectorLayer({ + id: "SECTIONS", + label: "Sections", + visible: false, + style: styles.sections, + source + }); + })(), + (function() { const source = new VectorSource(); source.setLoader( buildVectorLoader(
--- a/client/src/components/map/styles.js Mon May 20 09:39:45 2019 +0200 +++ b/client/src/components/map/styles.js Mon May 20 09:41:32 2019 +0200 @@ -61,6 +61,24 @@ color: "rgba(250, 240, 0, .7)" }) }), + orange1: new Style({ + stroke: new Stroke({ + color: "rgba(255, 150, 10, .8)", + width: 2 + }), + fill: new Fill({ + color: "rgba(255, 150, 0, .3)" + }) + }), + orange2: new Style({ + stroke: new Stroke({ + color: "rgba(255, 166, 10, .9)", + width: 5 + }), + fill: new Fill({ + color: "rgba(255, 166, 0, .7)" + }) + }), red1: new Style({ stroke: new Stroke({ color: "rgba(255, 0, 0, 1)", @@ -117,6 +135,13 @@ } return style; }, + sections(feature) { + let style = styles.orange1; + if (feature.get("highlighted")) { + style = styles.orange2; + } + return style; + }, fwd1() { return [styles.blue1, styles.textFW1]; },
--- a/client/src/store/map.js Mon May 20 09:39:45 2019 +0200 +++ b/client/src/store/map.js Mon May 20 09:41:32 2019 +0200 @@ -375,14 +375,33 @@ root: true }); } else { - commit("application/showFairwayDepth", true, { root: true }); - commit("fairwayavailability/type", "stretches", { root: true }); commit("imports/selectedStretchId", stretches[0].getId(), { root: true }); + commit("fairwayavailability/type", "stretches", { root: true }); + commit("application/showFairwayDepth", true, { root: true }); dispatch("moveToFeauture", { feature: stretches[0], zoom: 17 }); } } + if ( + sections.length === 1 && + !stretches.length && + !bottlenecks.length && + !gauges.length + ) { + if (rootState.imports.selectedSectionId === sections[0].getId()) { + commit("imports/selectedSectionId", null, { + root: true + }); + } else { + commit("imports/selectedSectionId", sections[0].getId(), { + root: true + }); + commit("fairwayavailability/type", "sections", { root: true }); + commit("application/showFairwayDepth", true, { root: true }); + dispatch("moveToFeauture", { feature: sections[0], zoom: 17 }); + } + } } // DEBUG output and example how to remove the GeometryName
--- a/pkg/controllers/bottlenecks.go Mon May 20 09:39:45 2019 +0200 +++ b/pkg/controllers/bottlenecks.go Mon May 20 09:41:32 2019 +0200 @@ -177,7 +177,8 @@ // log.Println("complete outside") continue pairs - case p1.when.After(from) && p2.when.Before(to): + case (p1.when.Before(from) || p1.when.Equal(from)) && (to.Before(p2.when) || to.Equal(p2.when)): + //case !p1.when.After(from) && !p2.when.Before(to): // (from-to) is complete inside segment. // invalid += p1.when.Sub(from) // invalid += to.Sub(p2.when) @@ -186,7 +187,7 @@ f, _ := v(from) t, _ := v(to) classify(common.InterpolateTimeByValue(from, f, to, t)) - start, end = p1.when, p2.when + start, end = from, to case p1.when.After(from): // from is inside segment @@ -218,8 +219,10 @@ p2.when, access(p2), )) start, end = p1.when, p2.when + default: - log.Println("warn: unexpected case. That should not happen.") + log.Printf("warn: unexpected case. That should not happen. %v - %v, %v - %v\n", + p1.when, p2.when, from, to) continue pairs }
--- a/pkg/geoserver/boot.go Mon May 20 09:39:45 2019 +0200 +++ b/pkg/geoserver/boot.go Mon May 20 09:41:32 2019 +0200 @@ -264,11 +264,20 @@ hasFeature = func(string) bool { return false } } + var already []string + + defer func() { + if len(already) > 0 { + log.Printf("info: already having featuretypes: %s\n", + strings.Join(already, ", ")) + } + }() + for i := range tables { table := tables[i].Name if hasFeature(table) { - log.Printf("info: featuretype %s already exists.\n", table) + already = append(already, table) continue } @@ -529,10 +538,19 @@ models.IntWMS, models.IntWithStyle)) + var already []string + + defer func() { + if len(already) > 0 { + log.Printf("info: already having styles: %s\n", + strings.Join(already, ", ")) + } + }() + for i := range entries { entry := &entries[i] if stls.hasStyle(entry.Name) { - log.Printf("warn: already has style for %s\n", entry.Name) + already = append(already, entry.Name) continue } if err := updateStyle(entry, true); err != nil {
--- a/pkg/imports/errors.go Mon May 20 09:39:45 2019 +0200 +++ b/pkg/imports/errors.go Mon May 20 09:41:32 2019 +0200 @@ -29,6 +29,7 @@ // Handle PostgreSQL error codes const ( + notNullViolation = "23502" foreignKeyViolation = "23503" noDataFound = "P0002" ) @@ -37,6 +38,21 @@ func (err dbError) Error() string { switch err.Code { + case notNullViolation: + switch err.SchemaName { + case "waterway": + switch err.TableName { + case "gauges": + switch err.ColumnName { + case "objname": + return "Missing objname" + case "geom": + return "Missing lat/lon" + case "zero_point": + return "Missing zeropoint" + } + } + } case foreignKeyViolation: switch err.SchemaName { case "waterway":
--- a/pkg/imports/sec.go Mon May 20 09:39:45 2019 +0200 +++ b/pkg/imports/sec.go Mon May 20 09:41:32 2019 +0200 @@ -101,7 +101,7 @@ SELECT ISRSrange_axis((SELECT r FROM r), $16::double precision) AS axs) INSERT INTO waterway.sections ( name, - stretch, + section, area, objnam, nobjnam, @@ -121,7 +121,7 @@ RETURNING id` ) -// StageDone moves the imported stretch out of the staging area. +// StageDone moves the imported section out of the staging area. func (secJobCreator) StageDone( ctx context.Context, tx *sql.Tx, @@ -134,10 +134,10 @@ return err } -// CleanUp of a stretch import is a NOP. +// CleanUp of a section import is a NOP. func (*Section) CleanUp() error { return nil } -// Do executes the actual stretch import. +// Do executes the actual section import. func (sec *Section) Do( ctx context.Context, importID int64,
--- a/pkg/imports/wg.go Mon May 20 09:39:45 2019 +0200 +++ b/pkg/imports/wg.go Mon May 20 09:41:32 2019 +0200 @@ -65,6 +65,12 @@ func (*WaterwayGauge) CleanUp() error { return nil } const ( + eraseObsoleteGaugesSQL = ` +UPDATE waterway.gauges SET erased = true +WHERE NOT erased AND isrs_astext(location) <> ALL($1) +RETURNING isrs_astext(location) +` + eraseGaugeSQL = ` UPDATE waterway.gauges SET erased = true, @@ -172,58 +178,6 @@ return nil, err } - var ignored int - - type idxCode struct { - jdx int - idx int - code *models.Isrs - } - - var gauges []idxCode - - for j, data := range responseData { - for i, dr := range data.RisdataReturn { - if dr.RisidxCode == nil { - ignored++ - continue - } - code, err := models.IsrsFromString(string(*dr.RisidxCode)) - if err != nil { - feedback.Warn("invalid ISRS code %v", err) - ignored++ - continue - } - - if dr.Objname.Loc == nil { - feedback.Warn("missing objname: %s", code) - ignored++ - continue - } - - if dr.Lat == nil || dr.Lon == nil { - feedback.Warn("missing lat/lon: %s", code) - ignored++ - continue - } - - if dr.Zeropoint == nil { - feedback.Warn("missing zeropoint: %s", code) - ignored++ - continue - } - - gauges = append(gauges, idxCode{jdx: j, idx: i, code: code}) - } - } - feedback.Info("Ignored gauges: %d", ignored) - feedback.Info("Further process %d gauges", len(gauges)) - - if len(gauges) == 0 { - return nil, UnchangedError("Nothing to do") - } - - // insert/update the gauges var eraseGaugeStmt, insertStmt, updateStmt, deleteReferenceWaterLevelsStmt, isNtSDepthRefStmt, insertWaterLevelStmt *sql.Stmt @@ -245,257 +199,291 @@ defer (*x.stmt).Close() } + var gauges []string var unchanged int - for i := range gauges { - ic := &gauges[i] - dr := responseData[ic.jdx].RisdataReturn[ic.idx] - - feedback.Info("Processing %s", ic.code) + for _, data := range responseData { + for _, dr := range data.RisdataReturn { - var from, to sql.NullInt64 - - if dr.Applicabilityfromkm != nil { - from = sql.NullInt64{ - Int64: int64(*dr.Applicabilityfromkm), - Valid: true, + isrs := string(*dr.RisidxCode) + code, err := models.IsrsFromString(isrs) + if err != nil { + feedback.Warn("Invalid ISRS code '%s': %v", isrs, err) + continue } - } - if dr.Applicabilitytokm != nil { - to = sql.NullInt64{ - Int64: int64(*dr.Applicabilitytokm), - Valid: true, - } - } + gauges = append(gauges, isrs) + feedback.Info("Processing %s", code) + + var from, to sql.NullInt64 - var tfrom, tto, dateInfo pgtype.Timestamptz - - if dr.Startdate != nil { - tfrom = pgtype.Timestamptz{ - Time: time.Time(*dr.Startdate), - Status: pgtype.Present, - } - } else { - tfrom = pgtype.Timestamptz{ - Status: pgtype.Null, + if dr.Applicabilityfromkm != nil { + from = sql.NullInt64{ + Int64: int64(*dr.Applicabilityfromkm), + Valid: true, + } } - } + if dr.Applicabilitytokm != nil { + to = sql.NullInt64{ + Int64: int64(*dr.Applicabilitytokm), + Valid: true, + } + } - if dr.Enddate != nil { - tto = pgtype.Timestamptz{ - Time: time.Time(*dr.Enddate), - Status: pgtype.Present, - } - } else { - tto = pgtype.Timestamptz{ - Status: pgtype.Null, - } - } + var tfrom, tto, dateInfo pgtype.Timestamptz - validity := pgtype.Tstzrange{ - Lower: tfrom, - Upper: tto, - LowerType: pgtype.Inclusive, - UpperType: pgtype.Exclusive, - Status: pgtype.Present, - } + if dr.Startdate != nil { + tfrom = pgtype.Timestamptz{ + Time: time.Time(*dr.Startdate), + Status: pgtype.Present, + } + } else { + tfrom = pgtype.Timestamptz{ + Status: pgtype.Null, + } + } - if dr.Infodate != nil { - dateInfo = pgtype.Timestamptz{ - Time: time.Time(*dr.Infodate), - Status: pgtype.Present, + if dr.Enddate != nil { + tto = pgtype.Timestamptz{ + Time: time.Time(*dr.Enddate), + Status: pgtype.Present, + } + } else { + tto = pgtype.Timestamptz{ + Status: pgtype.Null, + } } - } else { - dateInfo = pgtype.Timestamptz{ - Status: pgtype.Null, - } - } - var geodref sql.NullString - if dr.Geodref != nil { - geodref = sql.NullString{ - String: string(*dr.Geodref), - Valid: true, - } - } - - var source sql.NullString - if dr.Source != nil { - source = sql.NullString{ - String: string(*dr.Source), - Valid: true, + validity := pgtype.Tstzrange{ + Lower: tfrom, + Upper: tto, + LowerType: pgtype.Inclusive, + UpperType: pgtype.Exclusive, + Status: pgtype.Present, } - } - - tx, err := conn.BeginTx(ctx, nil) - if err != nil { - return nil, err - } - defer tx.Rollback() - // Mark old entries of gauge as erased, if applicable - if _, err := tx.StmtContext(ctx, eraseGaugeStmt).ExecContext(ctx, - ic.code.String(), - validity, - ); err != nil { - feedback.Warn(handleError(err).Error()) - if err2 := tx.Rollback(); err2 != nil { - return nil, err2 + if dr.Infodate != nil { + dateInfo = pgtype.Timestamptz{ + Time: time.Time(*dr.Infodate), + Status: pgtype.Present, + } + } else { + dateInfo = pgtype.Timestamptz{ + Status: pgtype.Null, + } + } + + var geodref sql.NullString + if dr.Geodref != nil { + geodref = sql.NullString{ + String: string(*dr.Geodref), + Valid: true, + } } - unchanged++ - continue - } + + var source sql.NullString + if dr.Source != nil { + source = sql.NullString{ + String: string(*dr.Source), + Valid: true, + } + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() - // Try to insert gauge entry - var dummy int - err = tx.StmtContext(ctx, insertStmt).QueryRowContext(ctx, - ic.code.CountryCode, - ic.code.LoCode, - ic.code.FairwaySection, - ic.code.Orc, - ic.code.Hectometre, - string(*dr.Objname.Loc), - float64(*dr.Lon), float64(*dr.Lat), - from, - to, - &validity, - float64(*dr.Zeropoint), - geodref, - &dateInfo, - source, - time.Time(*dr.Lastupdate), - ).Scan(&dummy) - switch { - case err == sql.ErrNoRows: - // Assume constraint conflict, try to update - err2 := tx.StmtContext(ctx, updateStmt).QueryRowContext(ctx, - ic.code.CountryCode, - ic.code.LoCode, - ic.code.FairwaySection, - ic.code.Orc, - ic.code.Hectometre, - string(*dr.Objname.Loc), - float64(*dr.Lon), float64(*dr.Lat), + // Mark old entries of gauge as erased, if applicable + if _, err := tx.StmtContext(ctx, eraseGaugeStmt).ExecContext(ctx, + code.String(), + validity, + ); err != nil { + feedback.Warn(handleError(err).Error()) + if err2 := tx.Rollback(); err2 != nil { + return nil, err2 + } + unchanged++ + continue + } + + // Try to insert gauge entry + var dummy int + err = tx.StmtContext(ctx, insertStmt).QueryRowContext(ctx, + code.CountryCode, + code.LoCode, + code.FairwaySection, + code.Orc, + code.Hectometre, + dr.Objname.Loc, + dr.Lon, dr.Lat, from, to, - float64(*dr.Zeropoint), + &validity, + dr.Zeropoint, geodref, &dateInfo, source, time.Time(*dr.Lastupdate), ).Scan(&dummy) switch { - case err2 == sql.ErrNoRows: - feedback.Info("unchanged") - if err3 := tx.Rollback(); err3 != nil { - return nil, err3 + case err == sql.ErrNoRows: + // Assume constraint conflict, try to update + err2 := tx.StmtContext(ctx, updateStmt).QueryRowContext(ctx, + code.CountryCode, + code.LoCode, + code.FairwaySection, + code.Orc, + code.Hectometre, + dr.Objname.Loc, + dr.Lon, dr.Lat, + from, + to, + dr.Zeropoint, + geodref, + &dateInfo, + source, + time.Time(*dr.Lastupdate), + ).Scan(&dummy) + switch { + case err2 == sql.ErrNoRows: + feedback.Info("unchanged") + if err3 := tx.Rollback(); err3 != nil { + return nil, err3 + } + unchanged++ + continue + case err2 != nil: + feedback.Warn(handleError(err2).Error()) + if err3 := tx.Rollback(); err3 != nil { + return nil, err3 + } + unchanged++ + continue + default: + feedback.Info("update") } - unchanged++ - continue - case err2 != nil: - feedback.Warn(handleError(err2).Error()) - if err3 := tx.Rollback(); err3 != nil { - return nil, err3 + + // Remove obsolete reference water levels + var currLevels pgtype.VarcharArray + currLevels.Set([]string{ + string(*dr.Reflevel1code), + string(*dr.Reflevel2code), + string(*dr.Reflevel3code), + }) + rwls, err := tx.StmtContext(ctx, + deleteReferenceWaterLevelsStmt).QueryContext(ctx, + code.String(), + &validity, + &currLevels, + ) + if err != nil { + return nil, err + } + defer rwls.Close() + for rwls.Next() { + var delRef string + if err := rwls.Scan(&delRef); err != nil { + return nil, err + } + feedback.Warn("Removed reference water level %s from %s", + delRef, code) + } + if err := rwls.Err(); err != nil { + return nil, err + } + case err != nil: + feedback.Warn(handleError(err).Error()) + if err2 := tx.Rollback(); err2 != nil { + return nil, err2 } unchanged++ continue default: - feedback.Info("update") + feedback.Info("insert new version") } - // Remove obsolete reference water levels - var currLevels pgtype.VarcharArray - currLevels.Set([]string{ - string(*dr.Reflevel1code), - string(*dr.Reflevel2code), - string(*dr.Reflevel3code), - }) - rwls, err2 := tx.StmtContext(ctx, - deleteReferenceWaterLevelsStmt).QueryContext(ctx, - ic.code.String(), - &validity, - &currLevels, - ) - if err2 != nil { - return nil, err2 - } - defer rwls.Close() - for rwls.Next() { - var delRef string - if err2 = rwls.Scan(&delRef); err2 != nil { - return nil, err2 + // "Upsert" reference water levels + for _, wl := range []struct { + level **erdms.RisreflevelcodeType + value **erdms.RisreflevelvalueType + }{ + {&dr.Reflevel1code, &dr.Reflevel1value}, + {&dr.Reflevel2code, &dr.Reflevel2value}, + {&dr.Reflevel3code, &dr.Reflevel3value}, + } { + if *wl.level == nil || *wl.value == nil { + continue + } + + var isNtSDepthRef bool + if err := tx.StmtContext( + ctx, isNtSDepthRefStmt).QueryRowContext(ctx, + string(**wl.level), + ).Scan( + &isNtSDepthRef, + ); err != nil { + return nil, err } - feedback.Warn("Removed reference water level %s from %s", - delRef, ic.code) - } - case err != nil: - feedback.Warn(handleError(err).Error()) - if err2 := tx.Rollback(); err2 != nil { - return nil, err2 - } - unchanged++ - continue - default: - feedback.Info("insert new version") - } + if !isNtSDepthRef { + feedback.Warn( + "Reference level code '%s' is not in line "+ + "with the NtS reference_code table", + string(**wl.level)) + } - // "Upsert" reference water levels - for _, wl := range []struct { - level **erdms.RisreflevelcodeType - value **erdms.RisreflevelvalueType - }{ - {&dr.Reflevel1code, &dr.Reflevel1value}, - {&dr.Reflevel2code, &dr.Reflevel2value}, - {&dr.Reflevel3code, &dr.Reflevel3value}, - } { - if *wl.level == nil || *wl.value == nil { - continue + if _, err := tx.StmtContext( + ctx, insertWaterLevelStmt).ExecContext(ctx, + code.CountryCode, + code.LoCode, + code.FairwaySection, + code.Orc, + code.Hectometre, + &validity, + string(**wl.level), + int64(**wl.value), + ); err != nil { + feedback.Warn(handleError(err).Error()) + tx.Rollback() + continue + } } - var isNtSDepthRef bool - if err := tx.StmtContext(ctx, isNtSDepthRefStmt).QueryRowContext( - ctx, - string(**wl.level), - ).Scan( - &isNtSDepthRef, - ); err != nil { + if err = tx.Commit(); err != nil { return nil, err } - if !isNtSDepthRef { - feedback.Warn( - "Reference level code '%s' is not in line "+ - "with the NtS reference_code table", - string(**wl.level)) - } + } + } + + if len(gauges) == 0 { + return nil, UnchangedError("No gauges returned from ERDMS") + } - if _, err := tx.StmtContext(ctx, insertWaterLevelStmt).ExecContext( - ctx, - ic.code.CountryCode, - ic.code.LoCode, - ic.code.FairwaySection, - ic.code.Orc, - ic.code.Hectometre, - &validity, - string(**wl.level), - int64(**wl.value), - ); err != nil { - feedback.Warn(handleError(err).Error()) - tx.Rollback() - continue - } - } - - if err = tx.Commit(); err != nil { + var pgGauges pgtype.VarcharArray + pgGauges.Set(gauges) + obsGauges, err := conn.QueryContext(ctx, eraseObsoleteGaugesSQL, &pgGauges) + if err != nil { + return nil, err + } + defer obsGauges.Close() + for obsGauges.Next() { + var isrs string + if err := obsGauges.Scan(&isrs); err != nil { return nil, err } + feedback.Info("Erased %s", isrs) + unchanged-- + } + if err := obsGauges.Err(); err != nil { + return nil, err + } + + if unchanged == len(gauges) { + return nil, UnchangedError("All gauges unchanged") } feedback.Info("Importing gauges took %s", time.Since(start)) - if unchanged == len(gauges) { - return nil, UnchangedError("All gauges unchanged") - } - return nil, err }
--- a/schema/demo-data/published_services.sql Mon May 20 09:39:45 2019 +0200 +++ b/schema/demo-data/published_services.sql Mon May 20 09:41:32 2019 +0200 @@ -12,6 +12,7 @@ -- * Tom Gottfried <tom@intevation.de> INSERT INTO sys_admin.published_services (name) VALUES + ('waterway.sections_geoserver'), ('waterway.stretches_geoserver'), ('waterway.fairway_dimensions'), ('waterway.gauges_geoserver'),
--- a/schema/gemma.sql Mon May 20 09:39:45 2019 +0200 +++ b/schema/gemma.sql Mon May 20 09:41:32 2019 +0200 @@ -424,7 +424,7 @@ CREATE TABLE sections ( id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, name varchar NOT NULL, - stretch isrsrange NOT NULL, + section isrsrange NOT NULL, area geography(MULTIPOLYGON, 4326) NOT NULL CHECK(ST_IsValid(CAST(area AS geometry))), objnam varchar NOT NULL, @@ -616,7 +616,8 @@ ('waterway', 'distance_marks_geoserver', 'location_code'), ('waterway', 'distance_marks_ashore_geoserver', 'id'), ('waterway', 'bottlenecks_geoserver', 'id'), - ('waterway', 'stretches_geoserver', 'id'); + ('waterway', 'stretches_geoserver', 'id'), + ('waterway', 'sections_geoserver', 'id'); -- -- Import queue and respective logging
--- a/schema/geoserver_views.sql Mon May 20 09:39:45 2019 +0200 +++ b/schema/geoserver_views.sql Mon May 20 09:41:32 2019 +0200 @@ -100,6 +100,20 @@ staging_done FROM waterway.stretches; +CREATE OR REPLACE VIEW waterway.sections_geoserver AS + SELECT + id, + name, + (section).lower::varchar as lower, + (section).upper::varchar as upper, + area::Geometry(MULTIPOLYGON, 4326), + objnam, + nobjnam, + date_info, + source_organization, + staging_done + FROM waterway.sections; + CREATE OR REPLACE VIEW waterway.sounding_results_contour_lines_geoserver AS SELECT bottleneck_id, date_info,