Mercurial > gemma
changeset 4623:30bb2d819d57 geoserver_sql_views
Merge default into geoserver_sql_views
author | Tom Gottfried <tom@intevation.de> |
---|---|
date | Wed, 09 Oct 2019 16:40:18 +0200 |
parents | b03aa1502736 (current diff) a63df1ac39ac (diff) |
children | 209b10f7bb2c |
files | schema/default_sysconfig.sql schema/gemma.sql schema/updates/1300/02.views_to_geoservers.sql schema/version.sql style-templates/sounding_results_contour_lines_geoserver.sld-template |
diffstat | 39 files changed, 893 insertions(+), 410 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgtags Fri Oct 04 17:26:56 2019 +0200 +++ b/.hgtags Wed Oct 09 16:40:18 2019 +0200 @@ -14,3 +14,5 @@ 5396581cf20334cbc5e69280e5d9b192640d96b9 v4-preview20190717 aececbc3d04798d905e65196ac0870d081776ca2 v4-preview20190726 b5619087e3e909645eeab9e3f198667692895382 v4-preview20190918 +8a6c410f6f03ca8f50022c11f17d0bb2b86215f5 v4-preview20190930 +a92239475590146dc6ad99fc4a3f4ced857c73f6 v4
--- a/client/package.json Fri Oct 04 17:26:56 2019 +0200 +++ b/client/package.json Wed Oct 09 16:40:18 2019 +0200 @@ -1,6 +1,6 @@ { "name": "gemmajs", - "version": "4.0.0-dev", + "version": "4.1.0-dev", "license": "AGPL-3.0-or-later", "repository": { "type": "hg",
--- a/client/src/components/fairway/AvailableFairwayDepth.vue Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/fairway/AvailableFairwayDepth.vue Wed Oct 09 16:40:18 2019 +0200 @@ -39,7 +39,7 @@ :href="dataLink" :download="csvFileName" class="mt-2 btn btn-sm btn-info w-100" - >Download CSV</a + ><translate>Download CSV</translate></a > </div> <div class="btn-group-toggle w-100 mt-2"> @@ -50,7 +50,7 @@ type="checkbox" v-model="showNumbers" autocomplete="off" - />Numbers + /><translate>Numbers</translate> </label> </div> </DiagramLegend> @@ -226,7 +226,7 @@ }, title() { if (!this.frequencyD) return; - return `Available Fairway Depth: ${ + return `${this.$gettext("Available Fairway Depth:")} ${ this.featureName } (${filters.surveyDate(this.fromDate)} - ${filters.surveyDate( this.toDate @@ -275,7 +275,9 @@ } }, downloadPDF() { - let title = `Available Fairway Depth: ${this.featureName}`; + let title = `${this.$gettext("Available Fairway Depth:")} ${ + this.featureName + }`; this.generatePDF({ templateData: this.templateData, diagramTitle: title
--- a/client/src/components/fairway/AvailableFairwayDepthLNWL.vue Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/fairway/AvailableFairwayDepthLNWL.vue Wed Oct 09 16:40:18 2019 +0200 @@ -39,7 +39,7 @@ :href="dataLink" :download="csvFileName" class="mt-2 btn btn-sm btn-info w-100" - >Download CSV</a + ><translate>Download CSV</translate></a > </div> <div class="btn-group-toggle w-100 mt-2"> @@ -50,7 +50,7 @@ type="checkbox" v-model="showNumbers" autocomplete="off" - />Numbers + /><translate>Numbers</translate> </label> </div> </DiagramLegend> @@ -217,7 +217,7 @@ }, title() { if (!this.frequencyD) return; - return `Available Fairway Depth vs LNWL: ${ + return `${this.$gettext("Available Fairway Depth vs LNWL:")} ${ this.featureName } (${filters.surveyDate(this.fromDate)} - ${filters.surveyDate( this.toDate @@ -285,7 +285,9 @@ } }, downloadPDF() { - let title = `Available Fairway Depth vs LNWL: ${this.featureName}`; + let title = `${this.$gettext("Available Fairway Depth vs LNWL:")} ${ + this.featureName + }`; this.generatePDF({ templateData: this.templateData, diagramTitle: title
--- a/client/src/components/fairway/BottleneckDialogue.vue Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/fairway/BottleneckDialogue.vue Wed Oct 09 16:40:18 2019 +0200 @@ -88,6 +88,7 @@ > </select> <button + v-if="isAllowedToDelete" class="btn btn-dark btn-xs ml-2" @click="deleteSelectedSurvey" > @@ -347,6 +348,9 @@ }; }, computed: { + ...mapState("user", ["user"]), + ...mapGetters("user", ["isWaterwayAdmin", "isSysAdmin"]), + ...mapGetters("usermanagement", ["userCountries"]), ...mapState("application", ["showProfiles", "paneSetup"]), ...mapState("map", ["openLayersMaps", "syncedMaps", "cutToolEnabled"]), ...mapState("bottlenecks", [ @@ -354,12 +358,29 @@ "surveys", "surveysLoading" ]), + isAllowedToDelete() { + const userCountryCode = this.userCountries[this.user]; + const bottleneck = this.bottlenecksList.find( + bn => bn.properties.name === this.selectedBottleneck + ); + if (!bottleneck) return; + if (this.isWaterwayAdmin || this.isSysAdmin) { + if ( + userCountryCode === "global" || + bottleneck.properties.responsible_country === userCountryCode + ) { + return true; + } + } + return false; + }, ...mapState("fairwayprofile", [ "previousCuts", "startPoint", "endPoint", "profileLoading", "differencesLoading", + "currentDifference", "waterLevels", "currentProfile" ]), @@ -520,7 +541,7 @@ if (cut) { if (cut.depth) { this.depth = cut.depth; - this.useCustomDepth = true; + this.useCustomDepth = cut.useCustomDepth; } this.applyCoordinates(cut.coordinates); } @@ -552,7 +573,30 @@ } } ) - .then() + .then(response => { + this.$store.commit( + "fairwayprofile/setCurrentDifference", + response.data.id + ); + if (this.openLayersMap(COMPARESURVEYS.compare.id)) { + this.openLayersMap(COMPARESURVEYS.compare.id) + .getLayer("DIFFERENCES") + .getSource() + .updateParams({ + LAYERS: "sounding_differences", + VERSION: "1.1.1", + TILED: true, + CQL_FILTER: "id=" + response.data.id + }); + this.openLayersMap(COMPARESURVEYS.compare.id) + .getLayer("DIFFERENCES") + .getSource() + .refresh(); + this.openLayersMap(COMPARESURVEYS.compare.id) + .getLayer("DIFFERENCES") + .setVisible(false); + } + }) .catch(error => { let status, data, message; if (error.response) { @@ -665,7 +709,8 @@ bottleneckName: this.selectedBottleneck, coordinates: [...this.startPoint, ...this.endPoint], timestamp: new Date().getTime(), - depth: this.depth + depth: this.depth, + useCustomDepth: this.useCustomDepth }; const existingEntry = previousCuts.find(cut => { return JSON.stringify(cut) === JSON.stringify(newEntry); @@ -757,6 +802,13 @@ } }, mounted() { + this.$store.dispatch("usermanagement/loadUsers").catch(error => { + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); this.$store.dispatch("bottlenecks/loadBottlenecksList"); } };
--- a/client/src/components/fairway/Fairwayprofile.vue Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/fairway/Fairwayprofile.vue Wed Oct 09 16:40:18 2019 +0200 @@ -7,7 +7,9 @@ <span style="background-color: #5995ff; width: 20px; height: 20px;" ></span> - Water + <span class="fix-trans-space" style="display:inline;" v-translate + >Water</span + > </div> <div class="legend"> <span @@ -20,7 +22,9 @@ '; background-clip: padding-box; box-sizing: content-box;' " ></span> - Fairway (LOS 1) + <span class="fix-trans-space" style="display:inline;" v-translate + >Fairway (LOS 1)</span + > </div> <div class="legend"> <span @@ -33,7 +37,9 @@ '; background-clip: padding-box; box-sizing: content-box;' " ></span> - Fairway (LOS 2) + <span class="fix-trans-space" style="display:inline;" v-translate + >Fairway (LOS 2)</span + > </div> <div class="legend"> <span @@ -46,19 +52,25 @@ '; background-clip: padding-box; box-sizing: content-box;' " ></span> - Fairway (LOS 3) + <span class="fix-trans-space" style="display:inline;" v-translate + >Fairway (LOS 3)</span + > </div> <div class="legend"> <span style="width: 14px; height: 14px; background-color: #4a2f06; border: solid 3px black; background-clip: padding-box; box-sizing: content-box;" ></span> - Sediment + <span class="fix-trans-space" style="display:inline;" v-translate + >Sediment</span + > </div> <div class="legend"> <span style="width: 14px; height: 14px; background-color: rgba(74, 47, 6, 0.6); border: solid 3px #943007; background-clip: padding-box; box-sizing: content-box;" ></span> - Sediment (Compare) + <span class="fix-trans-space" style="display:inline;" v-translate + >Sediment (Compare)</span + > </div> <div> <select @@ -284,20 +296,11 @@ let style = this.openLayersMap() .getLayer("FAIRWAYDIMENSIONSLOS" + los) .getStyle()()[0]; - // use spread operator to clone arrays let fillColor = style.getFill().getColor(); - let fillOpacity = - fillColor.length < 8 - ? 0.8 - : parseFloat(parseInt(fillColor.slice(7), 16) / 255).toFixed(2); let strokeColor = style.getStroke().getColor(); - let strokeOpacity = - strokeColor.length < 8 - ? 0.8 - : parseFloat(parseInt(strokeColor.slice(7), 16) / 255).toFixed(2); let strokeDash = style.getStroke().getLineDash(); - return { fillColor, fillOpacity, strokeColor, strokeOpacity, strokeDash }; + return { fillColor, strokeColor, strokeDash }; }, applyChange() { if (this.form.template.hasOwnProperty("properties")) { @@ -358,33 +361,33 @@ this.pdf.doc.setDrawColor("#5995ff"); this.pdf.doc.setFillColor("#5995ff"); this.pdf.doc.circle(x, y, 2, "FD"); - this.pdf.doc.text(x + 3, y + 1, "Water"); + this.pdf.doc.text(x + 3, y + 1, this.$gettext("Water")); this.pdf.doc.setLineDashPattern([0.8], 0); this.pdf.doc.setDrawColor("#0000ff"); this.pdf.doc.setFillColor("#fcfacc"); this.pdf.doc.circle(x, y + 5, 2, "FD"); - this.pdf.doc.text(x + 3, y + 6, "Fairway (LOS 1)"); + this.pdf.doc.text(x + 3, y + 6, this.$gettext("Fairway (LOS 1)")); this.pdf.doc.setLineDashPattern([1.8], 0); this.pdf.doc.setFillColor("#fdfce5"); this.pdf.doc.circle(x, y + 10, 2, "FD"); - this.pdf.doc.text(x + 3, y + 11, "Fairway (LOS 2)"); + this.pdf.doc.text(x + 3, y + 11, this.$gettext("Fairway (LOS 2)")); this.pdf.doc.setLineDashPattern([], 0); this.pdf.doc.setFillColor("#ffffff"); this.pdf.doc.circle(x, y + 15, 2, "FD"); - this.pdf.doc.text(x + 3, y + 16, "Fairway (LOS 3)"); + this.pdf.doc.text(x + 3, y + 16, this.$gettext("Fairway (LOS 3)")); this.pdf.doc.setDrawColor("black"); this.pdf.doc.setFillColor("#4a2e06"); this.pdf.doc.circle(x, y + 20, 2, "FD"); - this.pdf.doc.text(x + 3, y + 21, "Sediment"); + this.pdf.doc.text(x + 3, y + 21, this.$gettext("Sediment")); this.pdf.doc.setDrawColor("#943007"); this.pdf.doc.setFillColor("#928269"); this.pdf.doc.circle(x, y + 25, 2, "FD"); - this.pdf.doc.text(x + 3, y + 26, "Sediment (Compare)"); + this.pdf.doc.text(x + 3, y + 26, this.$gettext("Sediment (Compare)")); }, getPrintLayout(svgHeight, svgWidth) { return { @@ -479,9 +482,7 @@ { x: endPoint, y: this.useCustomDepth ? this.depth : depth } ]) .attr("fill", `${this.getLayerStyle(data.los).fillColor}`) - .attr("fill-opacity", this.getLayerStyle(data.los).fillOpacity) .attr("stroke", `${this.getLayerStyle(data.los).strokeColor}`) - .attr("stroke-opacity", this.getLayerStyle(data.los).strokeOpacity) .attr("stroke-dasharray", this.getLayerStyle(data.los).strokeDash) .attr("d", fairwayArea) .attr("transform", `translate(0 ${-offsetY})`); @@ -496,7 +497,7 @@ .attr("x", -dimensions.mainHeight / 2) .attr("fill", "black") .style("text-anchor", "middle") - .text("Depth [m]"); + .text(this.$gettext("Depth [m]")); graph .append("text") .attr("transform", ["rotate(-90)"]) @@ -504,7 +505,7 @@ .attr("x", -dimensions.mainHeight / 2) .attr("fill", "black") .style("text-anchor", "middle") - .text("Waterlevel [m]"); + .text(this.$gettext("Waterlevel [m]")); graph .append("text") .attr("y", 0) @@ -516,7 +517,7 @@ `translate(${dimensions.width / 2} ${dimensions.mainHeight})`, "rotate(0)" ]) - .text("Width [m]"); + .text(this.$gettext("Width [m]")); }, generateScalesAndGraph({ svg, height, width, dimensions, offsetY }) { let xScale = d3
--- a/client/src/components/gauge/HydrologicalConditions.vue Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/gauge/HydrologicalConditions.vue Wed Oct 09 16:40:18 2019 +0200 @@ -18,25 +18,33 @@ <span style="background-color: orange; width: 20px; height: 20px;" ></span> - Q25% + <span class="fix-trans-space" style="display:inline;" v-translate + >Q25%</span + > </div> <div class="legend"> <span style="background-color: black; width: 20px; height: 20px;" ></span> - Median + <span class="fix-trans-space" style="display:inline;" v-translate + >Median</span + > </div> <div class="legend"> <span style="background-color: purple; width: 20px; height: 20px;" ></span> - Q75% + <span class="fix-trans-space" style="display:inline;" v-translate + >Q75%</span + > </div> <div class="legend"> <span style="background-color: lightsteelblue; width: 20px; height: 20px;" ></span> - Long-term Amplitude + <span class="fix-trans-space" style="display:inline;" v-translate + >Long-term Amplitude</span + > </div> <select @change="applyChange" @@ -282,16 +290,20 @@ this.pdf.doc.text(x + padding, y + 1, "" + this.yearCompareD); this.pdf.doc.setFillColor("orange"); this.pdf.doc.circle(x, y + 5, 2, "FD"); - this.pdf.doc.text(x + padding, y + 6, "Q25%"); + this.pdf.doc.text(x + padding, y + 6, this.$gettext("Q25%")); this.pdf.doc.setFillColor("black"); this.pdf.doc.circle(x, y + 10, 2, "FD"); - this.pdf.doc.text(x + 3, y + 11, "Median "); + this.pdf.doc.text(x + 3, y + 11, this.$gettext("Median")); this.pdf.doc.setFillColor("purple"); this.pdf.doc.circle(x, y + 15, 2, "FD"); - this.pdf.doc.text(x + padding, y + 16, "Q75%"); + this.pdf.doc.text(x + padding, y + 16, this.$gettext("Q75%")); this.pdf.doc.setFillColor("lightsteelblue"); this.pdf.doc.circle(x, y + 20, 2, "FD"); - this.pdf.doc.text(x + padding, y + 21, "Long-term Amplitude"); + this.pdf.doc.text( + x + padding, + y + 21, + this.$gettext("Long-term Amplitude") + ); }, getPrintLayout(svgHeight, svgWidth) { return {
--- a/client/src/components/gauge/Waterlevel.vue Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/gauge/Waterlevel.vue Wed Oct 09 16:40:18 2019 +0200 @@ -12,19 +12,25 @@ <span style="background-color: steelblue; width: 20px; height: 20px;" ></span> - Waterlevel + <span class="fix-trans-space" style="display:inline;" v-translate + >Waterlevel</span + > </div> <div class="legend"> <span style="width: 8px; height: 8px; background-color: rgba(70, 130, 180, 0.6); border: solid 7px rgba(70, 130, 180, 0.2); background-clip: padding-box; box-sizing: content-box;" ></span> - Prediction + <span class="fix-trans-space" style="display:inline;" v-translate + >Prediction</span + > </div> <div class="legend"> <span style="background-color: rgba(0, 255, 0, 0.1); width: 20px; height: 20px;" ></span> - Navigable Range + <span class="fix-trans-space" style="display:inline;" v-translate + >Navigable Range</span + > </div> <div> <select @@ -282,16 +288,16 @@ this.pdf.doc.setDrawColor("white"); this.pdf.doc.setFillColor("steelblue"); this.pdf.doc.circle(x, y, 2, "FD"); - this.pdf.doc.text(x + padding, y + 1, "Waterlevel"); + this.pdf.doc.text(x + padding, y + 1, this.$gettext("Waterlevel")); this.pdf.doc.setFillColor("#dae6f0"); this.pdf.doc.circle(x, y + 5, 2, "FD"); this.pdf.doc.setFillColor("#e5ffe5"); this.pdf.doc.circle(x, y + 10, 2, "FD"); - this.pdf.doc.text(x + padding, y + 11, "Navigable Range"); + this.pdf.doc.text(x + padding, y + 11, this.$gettext("Navigable Range")); this.pdf.doc.setDrawColor("#90b4d2"); this.pdf.doc.setFillColor("#90b4d2"); this.pdf.doc.circle(x, y + 5, 0.6, "FD"); - this.pdf.doc.text(x + padding, y + 6, "Prediction"); + this.pdf.doc.text(x + padding, y + 6, this.$gettext("Prediction")); }, getPrintLayout(svgHeight, svgWidth) { return {
--- a/client/src/components/layers/Layerselect.vue Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/layers/Layerselect.vue Wed Oct 09 16:40:18 2019 +0200 @@ -78,7 +78,7 @@ refreshLegend() { if (this.layer.get("id") === "BOTTLENECKISOLINE") { this.loadLegendImage( - "sounding_results_contour_lines_geoserver", + "sounding_results_areas_geoserver", "isolinesLegendImgDataURL" ); } @@ -137,7 +137,7 @@ }, loadLegendImage(layer, storeTarget) { HTTP.get( - `/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=${layer}&legend_options=columns:4;fontAntiAliasing:true`, + `/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.3.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=${layer}&legend_options=columns:4;fontAntiAliasing:true&SCALE=5000`, { headers: { Accept: "image/png",
--- a/client/src/components/map/Map.vue Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/map/Map.vue Wed Oct 09 16:40:18 2019 +0200 @@ -201,23 +201,15 @@ } }, methods: { - colorLuminance(hex, lum) { - hex = String(hex).replace(/[^0-9a-f]/gi, ""); - if (hex.length < 6) { - hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; - } - lum = lum || 0; - let opacity = hex.substr(6, 2); - var rgb = "#", - c, - i; - for (i = 0; i < 3; i++) { - c = parseInt(hex.substr(i * 2, 2), 16); - c = Math.round(Math.min(Math.max(0, c + c * lum), 255)).toString(16); - rgb += ("00" + c).substr(c.length); - } - - return rgb + opacity; + colorLuminance(color, lum) { + let [r, g, b, a] = color + .substring(5, color.length - 1) + .split(",") + .map(e => Number(e)); + let [r1, g1, b1] = [r, g, b].map(e => + Math.round(Math.min(Math.max(0, e + e * lum), 255)) + ); + return `rgba(${r1},${g1},${b1},${a})`; }, updateBottleneckFilter(bottleneck_id, datestr) { if (!bottleneck_id) return; @@ -327,9 +319,8 @@ parseInt(color.slice(3, 5), 16) + ", " + parseInt(color.slice(5, 7), 16) + - (color.length > 7 - ? ", " + parseInt(color.slice(7, 9), 16) / 255 - : "") + + ", " + + (color.length > 7 ? parseInt(color.slice(7, 9), 16) / 255 : "1") + ")" ); };
--- a/client/src/components/map/layers.js Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/components/map/layers.js Wed Oct 09 16:40:18 2019 +0200 @@ -465,7 +465,7 @@ projection: "EPSG:3857", url: window.location.origin + "/api/internal/wms", params: { - LAYERS: "sounding_results_contour_lines_geoserver", + LAYERS: "sounding_results_areas_geoserver", VERSION: "1.1.1", TILED: true }, @@ -492,7 +492,8 @@ params: { LAYERS: "sounding_differences", VERSION: "1.1.1", - TILED: true + TILED: true, + CQL_FILTER: "id=" + store.state.fairwayprofile.currentDifference }, tileLoadFunction: function(tile, src) { HTTP.get(src, {
--- a/client/src/store/fairwayprofile.js Fri Oct 04 17:26:56 2019 +0200 +++ b/client/src/store/fairwayprofile.js Wed Oct 09 16:40:18 2019 +0200 @@ -36,6 +36,7 @@ profileLoading: false, selectedCut: null, differencesLoading: false, + currentDifference: null, depth: 2.5, useCustomDepth: true }; @@ -72,6 +73,9 @@ setDifferencesLoading: (state, value) => { state.differencesLoading = value; }, + setCurrentDifference: (state, value) => { + state.currentDifference = value; + }, profileLoaded: (state, answer) => { const { response, surveyDate } = answer; const { data } = response;
--- a/docker/Dockerfile.backend Fri Oct 04 17:26:56 2019 +0200 +++ b/docker/Dockerfile.backend Wed Oct 09 16:40:18 2019 +0200 @@ -5,6 +5,7 @@ RUN sed -i 's/\(deb.*\)$/\1 universe/' /etc/apt/sources.list +RUN apt-get update && apt-get install -y software-properties-common RUN add-apt-repository ppa:longsleep/golang-backports &&\ apt-get update &&\ apt-get -y install --no-install-recommends libxml2-utils\
--- a/docker/Dockerfile.geoserv Fri Oct 04 17:26:56 2019 +0200 +++ b/docker/Dockerfile.geoserv Wed Oct 09 16:40:18 2019 +0200 @@ -15,7 +15,7 @@ ENV GS_URL https://downloads.sourceforge.net/project/geoserver/GeoServer -ENV GS_VERSION 2.15.2 +ENV GS_VERSION 2.16.0 ENV GS_DATADIR /opt/geoserver/data ENV CATALINA_OPTS="-DGEOSERVER_DATA_DIR=$GS_DATADIR"
--- a/go.mod Fri Oct 04 17:26:56 2019 +0200 +++ b/go.mod Wed Oct 09 16:40:18 2019 +0200 @@ -5,6 +5,7 @@ require ( github.com/cockroachdb/apd v1.1.0 // indirect github.com/etcd-io/bbolt v1.3.3 + github.com/fogleman/contourmap v0.0.0-20190814184649-9f61d36c4199 github.com/gofrs/uuid v3.2.0+incompatible // indirect github.com/golang/snappy v0.0.1 github.com/gorilla/mux v1.7.3
--- a/go.sum Fri Oct 04 17:26:56 2019 +0200 +++ b/go.sum Wed Oct 09 16:40:18 2019 +0200 @@ -2,6 +2,7 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -25,6 +26,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fogleman/contourmap v0.0.0-20190814184649-9f61d36c4199 h1:kufr0u0RIG5ACpjFsPRbbuHa0FhMWsS3tnSFZ2hf07s= +github.com/fogleman/contourmap v0.0.0-20190814184649-9f61d36c4199/go.mod h1:mqaaaP4j7nTF8T/hx5OCljA7BYWHmrH2uh+Q023OchE= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -61,8 +64,6 @@ github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.6.0+incompatible h1:bJeo4JdVbDAW8KB2m8XkFeo8CPipREoG37BwEoKGz+Q= github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jonas-p/go-shp v0.1.1 h1:LY81nN67DBCz6VNFn2kS64CjmnDo9IP8rmSkTvhO9jE= -github.com/jonas-p/go-shp v0.1.1/go.mod h1:MRIhyxDQ6VVp0oYeD7yPGr5RSTNScUFKCDsI5DR7PtI= github.com/jonas-p/go-shp v0.1.2-0.20190401125246-9fd306ae10a6 h1:h5O7ee4tlSPVjdC75eSLX7jXZiHftthuHio/GtrhaSM= github.com/jonas-p/go-shp v0.1.2-0.20190401125246-9fd306ae10a6/go.mod h1:MRIhyxDQ6VVp0oYeD7yPGr5RSTNScUFKCDsI5DR7PtI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/common/linear.go Wed Oct 09 16:40:18 2019 +0200 @@ -0,0 +1,36 @@ +// 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) 2019 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> +package common + +// Linear constructs a function which maps x1 to y1 and x2 to y2. +// All other values are interpolated linearly. +func Linear(x1, y1, x2, y2 float64) func(float64) float64 { + // f(x1) = y1 + // f(x2) = y2 + // y1 = x1*a + b <=> b = y1 - x1*a + // y2 = x2*a + b + + // y1 - y2 = a*(x1 - x2) + // a = (y1-y2)/(x1 - x2) for x1 != x2 + + if x1 == x2 { + return func(float64) float64 { + return 0.5 * (y1 + y2) + } + } + a := (y1 - y2) / (x1 - x2) + b := y1 - x1*a + return func(x float64) float64 { + return x*a + b + } +}
--- a/pkg/controllers/diff.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/controllers/diff.go Wed Oct 09 16:40:18 2019 +0200 @@ -36,6 +36,12 @@ ) const ( + // isoCellSize is the side length of a raster cell when tracing + // iso areas. + isoCellSize = 0.5 +) + +const ( diffIDSQL = ` SELECT sd.id FROM caching.sounding_differences sd JOIN @@ -57,24 +63,24 @@ WHERE m.date_info = $2::date AND s.date_info = $3::date RETURNING id ` - insertDiffContourSQL = ` -INSERT INTO caching.sounding_differences_contour_lines ( + insertDiffIsoAreasQL = ` +INSERT INTO caching.sounding_differences_iso_areas ( sounding_differences_id, height, - lines + areas ) SELECT - $5, - $4, + $1, + $2, ST_Transform( ST_Multi( ST_CollectionExtract( ST_SimplifyPreserveTopology( ST_Multi(ST_Collectionextract( - ST_MakeValid(ST_GeomFromWKB($1, $2::integer)), 2)), - $3 + ST_MakeValid(ST_GeomFromWKB($4, $3::integer)), 3)), + $5 ), - 2 + 3 ) ), 4326 @@ -273,11 +279,11 @@ log.Printf("info: num heights: %d\n", len(heights)) - var stmt *sql.Stmt - if stmt, err = tx.PrepareContext(ctx, insertDiffContourSQL); err != nil { + var isoStmt *sql.Stmt + if isoStmt, err = tx.PrepareContext(ctx, insertDiffIsoAreasQL); err != nil { return } - defer stmt.Close() + defer isoStmt.Close() if err = tx.QueryRowContext( ctx, @@ -291,18 +297,21 @@ heights = common.DedupFloat64s(heights) - octree.DoContours(tree, heights, func(res *octree.ContourResult) { - if err == nil && len(res.Lines) > 0 { - _, err = stmt.ExecContext( - ctx, - res.Lines.AsWKB2D(), - minuendTree.EPSG, - contourTolerance, - res.Height, - id, - ) + areas := tree.TraceAreas(heights, isoCellSize) + + for i, a := range areas { + if len(a) == 0 { + continue } - }) + if _, err = isoStmt.ExecContext( + ctx, + id, heights[i], minuendTree.EPSG, + a.AsWKB(), + contourTolerance, + ); err != nil { + return + } + } log.Printf("info: calculating and storing iso lines took %v\n", time.Since(start))
--- a/pkg/controllers/stretches.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/controllers/stretches.go Wed Oct 09 16:40:18 2019 +0200 @@ -31,14 +31,21 @@ "gemma.intevation.de/gemma/pkg/middleware" ) +// The following requests are taking _all_ bottlenecks into account, not only +// the currently valid ones. This is neccessary, as we are doing reports on +// arbitrary time ranges and bottlenecks currently active might have been in the +// selected time range. +// +// FIXME: the better solution would be to limit the bottlenecks to those with: +// b.validity && REQUESTED_TIME_RANGE + const ( selectSectionBottlenecks = ` SELECT distinct(b.objnam), b.limiting FROM waterway.sections s, waterway.bottlenecks b -WHERE b.validity @> current_timestamp - AND ST_Intersects(b.area, s.area) +WHERE ST_Intersects(b.area, s.area) AND s.name = $1` selectStretchBottlenecks = ` @@ -46,8 +53,7 @@ distinct(b.objnam), b.limiting FROM users.stretches s, waterway.bottlenecks b -WHERE b.validity @> current_timestamp - AND ST_Intersects(b.area, s.area) +WHERE ST_Intersects(b.area, s.area) AND s.name = $1` )
--- a/pkg/controllers/system.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/controllers/system.go Wed Oct 09 16:40:18 2019 +0200 @@ -239,7 +239,7 @@ func(old sql.NullString, curr string) (func(*http.Request), error) { return reconfigureClassBreaks( old, curr, - "sounding_results_contour_lines_geoserver", + "sounding_results_areas_geoserver", func(req *http.Request) { if s, ok := auth.GetSession(req); ok { triggerSoundingResultsContoursRecalc(s.User, curr)
--- a/pkg/geoserver/templates.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/geoserver/templates.go Wed Oct 09 16:40:18 2019 +0200 @@ -35,7 +35,7 @@ func init() { RegisterStylePreprocessor( - "sounding_results_contour_lines_geoserver", + "sounding_results_areas_geoserver", templateContourLinesFunc("morphology_classbreaks")) RegisterStylePreprocessor( "sounding_differences",
--- a/pkg/imports/fa.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/imports/fa.go Wed Oct 09 16:40:18 2019 +0200 @@ -292,6 +292,14 @@ return &summary, nil } +// defaultLOS defaults to LOS3 when no expicit LOS is given. +func defaultLOS(los *ifaf.LosEnum) ifaf.LosEnum { + if los == nil { + return ifaf.LosEnumLOS3 + } + return *los +} + func doForFAs( ctx context.Context, bnIds bottlenecks, @@ -371,7 +379,6 @@ if faRes.Effective_fairway_availability != nil { efaCount := 0 for _, efa := range faRes.Effective_fairway_availability.EffectiveFairwayAvailability { - los := efa.Level_of_Service fgt := efa.Forecast_generation_time if efa.Forecast_generation_time.Status == pgtype.Undefined { fgt = pgtype.Timestamp{ @@ -382,7 +389,7 @@ ctx, faID, efa.Measure_date, - string(*los), + string(defaultLOS(efa.Level_of_Service)), efa.Available_depth_value, efa.Available_width_value, efa.Water_level_value, @@ -410,7 +417,7 @@ res, err := insertFAVStmt.ExecContext( ctx, faID, - fav.Level_of_Service, + string(defaultLOS(fav.Level_of_Service)), fav.Fairway_depth, fav.Fairway_width, fav.Fairway_radius,
--- a/pkg/imports/isr.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/imports/isr.go Wed Oct 09 16:40:18 2019 +0200 @@ -44,10 +44,7 @@ } func (isrJobCreator) Depends() [2][]string { - return [2][]string{ - {"sounding_results", "sounding_results_contour_lines"}, - {}, - } + return srJobCreator{}.Depends() } const ( @@ -56,8 +53,8 @@ FROM waterway.sounding_results ORDER BY bottleneck_id ` - deleteContourLinesSQL = ` -DELETE FROM waterway.sounding_results_contour_lines + deleteIsoAreasSQL = ` +DELETE FROM waterway.sounding_results_iso_areas WHERE sounding_result_id = $1 ` ) @@ -166,10 +163,11 @@ } defer tx.Rollback() - insertStmt, err := tx.Prepare(insertContourSQL) + insertAreasStmt, err := tx.Prepare(insertIsoAreasSQL) if err != nil { return err } + defer insertAreasStmt.Close() // For all sounding results in bottleneck. for _, sr := range bn.srs { @@ -180,22 +178,25 @@ hs := octree.ExtrapolateClassBreaks(heights, tree.Min.Z, tree.Max.Z) hs = common.DedupFloat64s(hs) - // Delete the old contour lines. - if _, err := tx.ExecContext(ctx, deleteContourLinesSQL, sr); err != nil { + // Delete the old iso areas. + if _, err := tx.ExecContext(ctx, deleteIsoAreasSQL, sr); err != nil { return err } - octree.DoContours(tree, hs, func(res *octree.ContourResult) { - if err == nil && len(res.Lines) > 0 { - _, err = insertStmt.ExecContext( - ctx, - sr, res.Height, tree.EPSG, - res.Lines.AsWKB2D(), - contourTolerance) + // Calculate and store the iso areas. + areas := tree.TraceAreas(hs, isoCellSize) + for i, a := range areas { + if len(a) == 0 { + continue } - }) - if err != nil { - return err + if _, err := insertAreasStmt.ExecContext( + ctx, + sr, hs[i], tree.EPSG, + a.AsWKB(), + contourTolerance, + ); err != nil { + return err + } } }
--- a/pkg/imports/sr.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/imports/sr.go Wed Oct 09 16:40:18 2019 +0200 @@ -37,6 +37,7 @@ "gemma.intevation.de/gemma/pkg/common" "gemma.intevation.de/gemma/pkg/models" "gemma.intevation.de/gemma/pkg/octree" + "gemma.intevation.de/gemma/pkg/wkb" ) // SoundingResult is a Job to import sounding reults @@ -70,9 +71,17 @@ ) const ( + // pointsPerSquareMeter is the average number of points + // when generating a artifical height model for single beam scans. pointsPerSquareMeter = 2 ) +const ( + // isoCellSize is the side length of a raster cell when tracing + // iso areas. + isoCellSize = 0.5 +) + // SRJobKind is the unique name of this import job type. const SRJobKind JobKind = "sr" @@ -88,7 +97,7 @@ func (srJobCreator) Depends() [2][]string { return [2][]string{ - {"sounding_results", "sounding_results_contour_lines"}, + {"sounding_results", "sounding_results_iso_areas"}, {"bottlenecks"}, } } @@ -152,11 +161,11 @@ ST_AsBinary(ST_Buffer(ST_MakeValid(ST_GeomFromWKB($1, $2::integer)), 0.0)), ST_AsBinary(ST_Buffer(ST_MakeValid(ST_GeomFromWKB($1, $2::integer)), 0.1))` - insertContourSQL = ` -INSERT INTO waterway.sounding_results_contour_lines ( + insertIsoAreasSQL = ` +INSERT INTO waterway.sounding_results_iso_areas ( sounding_result_id, height, - lines + areas ) SELECT $1, @@ -166,10 +175,10 @@ ST_CollectionExtract( ST_SimplifyPreserveTopology( ST_Multi(ST_Collectionextract( - ST_MakeValid(ST_GeomFromWKB($4, $3::integer)), 2)), + ST_MakeValid(ST_GeomFromWKB($4, $3::integer)), 3)), $5 ), - 2 + 3 ) ), 4326 @@ -619,15 +628,10 @@ return nil, err } feedback.Info("Storing octree index took %s.", time.Since(start)) - feedback.Info("Generate contour lines") - - start = time.Now() - err = generateContours(ctx, tx, feedback, builder.Tree(), id) + err = generateIsos(ctx, tx, feedback, builder.Tree(), id) if err != nil { return nil, err } - feedback.Info("Generating contour lines took %s.", - time.Since(start)) // Store for potential later removal. if err = track(ctx, tx, importID, "waterway.sounding_results", id); err != nil { @@ -827,23 +831,19 @@ return shapeToPolygon(s) } -func generateContours( +func generateIsos( ctx context.Context, tx *sql.Tx, feedback Feedback, tree *octree.Tree, id int64, ) error { - stmt, err := tx.PrepareContext(ctx, insertContourSQL) - if err != nil { - return err - } - defer stmt.Close() heights, err := octree.LoadClassBreaks( ctx, tx, "morphology_classbreaks", ) + if err != nil { feedback.Warn("Loading class breaks failed: %v", err) feedback.Info("Using default class breaks") @@ -871,15 +871,66 @@ heights = common.DedupFloat64s(heights) - octree.DoContours(tree, heights, func(res *octree.ContourResult) { - if err == nil && len(res.Lines) > 0 { - _, err = stmt.ExecContext( - ctx, - id, res.Height, tree.EPSG, - res.Lines.AsWKB2D(), - contourTolerance) + return generateIsoAreas(ctx, tx, feedback, tree, heights, id) +} + +func generateIsoAreas( + ctx context.Context, + tx *sql.Tx, + feedback Feedback, + tree *octree.Tree, + heights []float64, + id int64, +) error { + feedback.Info("Generate iso areas") + total := time.Now() + defer func() { + feedback.Info("Generating iso areas took %s.", + time.Since(total)) + }() + + areas := tree.TraceAreas(heights, isoCellSize) + + return storeAreas( + ctx, tx, feedback, + areas, tree.EPSG, heights, id) +} + +func storeAreas( + ctx context.Context, + tx *sql.Tx, + feedback Feedback, + areas []wkb.MultiPolygonGeom, + epsg uint32, + heights []float64, + id int64, +) error { + feedback.Info("Store iso areas.") + total := time.Now() + defer func() { + feedback.Info("Storing iso areas took %v.", + time.Since(total)) + }() + + stmt, err := tx.PrepareContext(ctx, insertIsoAreasSQL) + if err != nil { + return err + } + defer stmt.Close() + + for i, a := range areas { + if len(a) == 0 { + continue } - }) + if _, err := stmt.ExecContext( + ctx, + id, heights[i], epsg, + a.AsWKB(), + contourTolerance, + ); err != nil { + return err + } + } - return err + return nil }
--- a/pkg/imports/st.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/imports/st.go Wed Oct 09 16:40:18 2019 +0200 @@ -58,10 +58,10 @@ const ( stDeleteSQL = ` DELETE FROM users.stretches WHERE -staging_done AND name = ( +staging_done AND name IN ( SELECT name FROM users.stretches WHERE - id = ( + id IN ( SELECT key from import.track_imports WHERE import_id = $1 AND relation = 'users.stretches'::regclass)
--- a/pkg/models/colors.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/models/colors.go Wed Oct 09 16:40:18 2019 +0200 @@ -71,6 +71,28 @@ return cbs } +func (cc ColorValues) Heights() []float64 { + heights := make([]float64, len(cc)) + for i := range cc { + heights[i] = cc[i].Value + } + return heights +} + +func (cc ColorValues) Clip(v float64) color.RGBA { + if len(cc) == 0 { + return color.RGBA{} + } + if v < cc[0].Value { + return cc[0].Color + } + if v > cc[len(cc)-1].Value { + return cc[len(cc)-1].Color + } + c, _ := cc.Interpolate(v) + return c +} + func (cc ColorValues) Interpolate(v float64) (color.RGBA, bool) { if len(cc) == 0 || v < cc[0].Value || v > cc[len(cc)-1].Value { return color.RGBA{}, false
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/octree/areas.go Wed Oct 09 16:40:18 2019 +0200 @@ -0,0 +1,168 @@ +// 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 octree + +import ( + "log" + "math" + "runtime" + "sync" + "time" + + "github.com/fogleman/contourmap" + + "gemma.intevation.de/gemma/pkg/common" + "gemma.intevation.de/gemma/pkg/wkb" +) + +func (tree *Tree) TraceAreas( + heights []float64, + cellSize float64, +) []wkb.MultiPolygonGeom { + min, max := tree.Min, tree.Max + + width := max.X - min.X + height := max.Y - min.Y + + log.Printf("info: Width/Height: %.2f / %.2f\n", width, height) + + xcells := int(math.Ceil(width / cellSize)) + ycells := int(math.Ceil(height / cellSize)) + + log.Printf("info: Raster size: (%d, %d)\n", xcells, ycells) + + start := time.Now() + + // Add border for closing + raster := make([]float64, (xcells+2)*(ycells+2)) + + // prefill for no data + const nodata = -math.MaxFloat64 + for i := range raster { + raster[i] = nodata + } + + // rasterize the height model + + var wg sync.WaitGroup + + rows := make(chan int) + + rasterRow := func() { + defer wg.Done() + quat := 0.25 * cellSize + for i := range rows { + pos := (i+1)*(xcells+2) + 1 + row := raster[pos : pos+xcells] + py := min.Y + float64(i)*cellSize + cellSize/2 + px := min.X + cellSize/2 + y1 := py - quat + y2 := py + quat + for j := range row { + var n int + var sum float64 + + if v, ok := tree.Value(px-quat, y1); ok { + sum = v + n = 1 + } + if v, ok := tree.Value(px-quat, y2); ok { + sum += v + n++ + } + if v, ok := tree.Value(px+quat, y1); ok { + sum += v + n++ + } + if v, ok := tree.Value(px+quat, y2); ok { + sum += v + n++ + } + + if n > 0 { + row[j] = sum / float64(n) + } + px += cellSize + } + } + } + + for n := runtime.NumCPU(); n >= 1; n-- { + wg.Add(1) + go rasterRow() + } + + for i := 0; i < ycells; i++ { + rows <- i + } + close(rows) + + wg.Wait() + log.Printf("info: Rastering took %v\n", time.Since(start)) + + start = time.Now() + + tracer := contourmap.FromFloat64s(xcells+2, ycells+2, raster) + + areas := make([]wkb.MultiPolygonGeom, len(heights)) + + // TODO: Check if this correct! + reprojX := common.Linear(0.5, min.X, 1.5, min.X+cellSize) + reprojY := common.Linear(0.5, min.Y, 1.5, min.Y+cellSize) + + cnts := make(chan int) + + doContours := func() { + defer wg.Done() + for hIdx := range cnts { + c := tracer.Contours(heights[hIdx]) + + if len(c) == 0 { + continue + } + + // We need to bring it back to the + // none raster coordinate system. + a := make(wkb.MultiPolygonGeom, len(c)) + + for i, pl := range c { + shell := make(wkb.LinearRingGeom, len(pl)) + for j, pt := range pl { + shell[j] = wkb.PointGeom{ + X: reprojX(pt.X), + Y: reprojY(pt.Y), + } + } + a[i] = wkb.PolygonGeom{shell} + } + + areas[hIdx] = a + } + } + + for n := runtime.NumCPU(); n >= 1; n-- { + wg.Add(1) + go doContours() + } + + for i := range heights { + cnts <- i + } + close(cnts) + + wg.Wait() + log.Printf("info: Tracing areas took %v\n", time.Since(start)) + + return areas +}
--- a/pkg/octree/strtree.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/octree/strtree.go Wed Oct 09 16:40:18 2019 +0200 @@ -18,16 +18,21 @@ "sort" ) -const numEntries = 8 +const STRTreeDefaultEntries = 8 type STRTree struct { - tin *Tin - index []int32 - bboxes []Box2D + Entries int + tin *Tin + index []int32 + bboxes []Box2D } func (s *STRTree) Build(t *Tin) { + if s.Entries == 0 { + s.Entries = STRTreeDefaultEntries + } + s.tin = t all := make([]int32, len(t.Triangles)) @@ -43,6 +48,31 @@ s.index[0] = root } +func (s *STRTree) BuildWithout(t *Tin, remove map[int32]struct{}) { + + if s.Entries == 0 { + s.Entries = STRTreeDefaultEntries + } + + s.tin = t + + all := make([]int32, 0, len(t.Triangles)-len(remove)) + + for i := range all { + idx := int32(i) + if _, found := remove[idx]; !found { + all = append(all, idx) + } + all[i] = int32(i) + } + + s.index = append(s.index, 0) + + root := s.build(all) + + s.index[0] = root +} + func (s *STRTree) Clip(p *Polygon) map[int32]struct{} { removed := make(map[int32]struct{}) @@ -113,12 +143,12 @@ return s.tin.Vertices[ti[0]].X < s.tin.Vertices[tj[0]].X }) - P := int(math.Ceil(float64(len(items)) / float64(numEntries))) + P := int(math.Ceil(float64(len(items)) / float64(s.Entries))) S := int(math.Ceil(math.Sqrt(float64(P)))) - slices := strSplit(items, S) + slices := s.strSplit(items, S) - leaves := strJoin( + leaves := s.strJoin( slices, S, func(i, j int32) bool { ti := s.tin.Triangles[i] @@ -133,7 +163,7 @@ func (s *STRTree) buildNodes(items []int32) int32 { - if len(items) <= numEntries { + if len(items) <= s.Entries { return s.allocNode(items) } @@ -141,12 +171,12 @@ return s.bbox(items[i]).X1 < s.bbox(items[j]).X1 }) - P := int(math.Ceil(float64(len(items)) / float64(numEntries))) + P := int(math.Ceil(float64(len(items)) / float64(s.Entries))) S := int(math.Ceil(math.Sqrt(float64(P)))) - slices := strSplit(items, S) + slices := s.strSplit(items, S) - nodes := strJoin( + nodes := s.strJoin( slices, S, func(i, j int32) bool { return s.bbox(i).Y1 < s.bbox(j).Y1 }, s.allocNode, @@ -162,8 +192,8 @@ return s.bboxes[s.index[idx]] } -func strSplit(items []int32, S int) [][]int32 { - sm := S * numEntries +func (s *STRTree) strSplit(items []int32, S int) [][]int32 { + sm := S * s.Entries slices := make([][]int32, S) for i := range slices { var n int @@ -178,7 +208,7 @@ return slices } -func strJoin( +func (s *STRTree) strJoin( slices [][]int32, S int, less func(int32, int32) bool, alloc func([]int32) int32, @@ -192,8 +222,8 @@ for len(slice) > 0 { var n int - if l := len(slice); l >= numEntries { - n = numEntries + if l := len(slice); l >= s.Entries { + n = s.Entries } else { n = l }
--- a/pkg/octree/vertex.go Fri Oct 04 17:26:56 2019 +0200 +++ b/pkg/octree/vertex.go Wed Oct 09 16:40:18 2019 +0200 @@ -350,9 +350,9 @@ } func (t *Triangle) Contains(x, y float64) bool { - v0 := t[2].Sub(t[0]) - v1 := t[1].Sub(t[0]) - v2 := Vertex{X: x, Y: y}.Sub(t[0]) + v0 := t[2].Sub2D(t[0]) + v1 := t[1].Sub2D(t[0]) + v2 := Vertex{X: x, Y: y}.Sub2D(t[0]) dot00 := v0.Dot2(v0) dot01 := v0.Dot2(v1) @@ -483,6 +483,14 @@ math.Abs(v.Y-w.Y) < eps && math.Abs(v.Z-w.Z) < eps } +// EpsEquals returns true if v and w are equal component-wise +// in the X/Y plane with the values within a epsilon range. +func (v Vertex) EpsEquals2D(w Vertex) bool { + const eps = 1e-5 + return math.Abs(v.X-w.X) < eps && + math.Abs(v.Y-w.Y) < eps +} + // JoinOnLine joins the the elements of a given multi line string // under the assumption that the segments are all on the line segment // from (x1, y1) to (x2, y2). @@ -629,6 +637,15 @@ return buf.Bytes() } +func (ls LineStringZ) CCW() bool { + var sum float64 + for i, v1 := range ls { + v2 := ls[(i+1)%len(ls)] + sum += (v2.X - v1.X) * (v2.Y + v1.Y) + } + return sum > 0 +} + // Join joins two lines leaving the first of the second out. func (ls LineStringZ) Join(other LineStringZ) LineStringZ { nline := make(LineStringZ, len(ls)+len(other)-1)
--- a/schema/default_sysconfig.sql Fri Oct 04 17:26:56 2019 +0200 +++ b/schema/default_sysconfig.sql Wed Oct 09 16:40:18 2019 +0200 @@ -184,13 +184,13 @@ (location_code).hectometre FROM waterway.distance_marks_virtual $$), - ('waterway', 'sounding_results_contour_lines_geoserver', 4326, $$ + ('waterway', 'sounding_results_areas_geoserver', 4326, $$ SELECT bottleneck_id, date_info, height, - lines - FROM waterway.sounding_results_contour_lines cl - JOIN waterway.sounding_results sr ON sr.id = cl.sounding_result_id + areas + FROM waterway.sounding_results_iso_areas ia + JOIN waterway.sounding_results sr ON sr.id = ia.sounding_result_id $$), ('waterway', 'bottlenecks_geoserver', 4326, $$ SELECT @@ -257,11 +257,11 @@ bn.objnam AS objnam, srm.date_info AS minuend, srs.date_info AS subtrahend, - sdcl.height AS height, - sdcl.lines AS lines + sdia.height AS height, + sdia.areas AS areas FROM caching.sounding_differences sd - JOIN caching.sounding_differences_contour_lines sdcl - ON sd.id = sdcl.sounding_differences_id + JOIN caching.sounding_differences_iso_areas sdia + ON sd.id = sdia.sounding_differences_id JOIN waterway.sounding_results srm ON sd.minuend = srm.id JOIN waterway.sounding_results srs
--- a/schema/gemma.sql Fri Oct 04 17:26:56 2019 +0200 +++ b/schema/gemma.sql Wed Oct 09 16:40:18 2019 +0200 @@ -720,13 +720,13 @@ AFTER INSERT OR UPDATE ON sounding_results FOR EACH ROW EXECUTE FUNCTION check_sr_in_bn_area() - CREATE TABLE sounding_results_contour_lines ( + CREATE TABLE sounding_results_iso_areas ( sounding_result_id int NOT NULL REFERENCES sounding_results ON DELETE CASCADE, height numeric NOT NULL, - lines geography(multilinestring, 4326) NOT NULL, + areas geography(MULTIPOLYGON, 4326) NOT NULL, -- TODO: generate valid simple features and add constraint: - -- CHECK(ST_IsSimple(CAST(lines AS geometry))), + -- CHECK(ST_IsSimple(CAST(areas AS geometry))), PRIMARY KEY (sounding_result_id, height) ) -- @@ -914,11 +914,11 @@ UNIQUE (minuend, subtrahend) ) - CREATE TABLE sounding_differences_contour_lines ( + CREATE TABLE sounding_differences_iso_areas ( sounding_differences_id int NOT NULL REFERENCES sounding_differences(id) ON DELETE CASCADE, height numeric NOT NULL, - lines geography(multilinestring, 4326) NOT NULL, + areas geography(MULTIPOLYGON, 4326) NOT NULL, PRIMARY KEY (sounding_differences_id, height) ) ;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1204/01.create-iso-areas.sql Wed Oct 09 16:40:18 2019 +0200 @@ -0,0 +1,27 @@ +CREATE TABLE waterway.sounding_results_iso_areas ( + sounding_result_id int NOT NULL REFERENCES waterway.sounding_results + ON DELETE CASCADE, + height numeric NOT NULL, + areas geography(MULTIPOLYGON, 4326) NOT NULL, + -- TODO: generate valid simple features and add constraint: + -- CHECK(ST_IsSimple(CAST(areas AS geometry))), + PRIMARY KEY (sounding_result_id, height) +); + +CREATE TABLE caching.sounding_differences_iso_areas ( + sounding_differences_id int NOT NULL REFERENCES caching.sounding_differences(id) + ON DELETE CASCADE, + height numeric NOT NULL, + areas geography(MULTIPOLYGON, 4326) NOT NULL, + PRIMARY KEY (sounding_differences_id, height) +); + +GRANT INSERT, UPDATE, DELETE ON waterway.sounding_results_iso_areas + TO waterway_admin; + +GRANT SELECT ON waterway.sounding_results_iso_areas + TO waterway_user; + +GRANT SELECT, UPDATE, DELETE, INSERT ON caching.sounding_differences_iso_areas + TO waterway_user; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1204/02.delete-contours.sql Wed Oct 09 16:40:18 2019 +0200 @@ -0,0 +1,8 @@ +DELETE FROM sys_admin.published_services WHERE name = 'waterway.sounding_differences'::regclass; +DELETE FROM sys_admin.published_services WHERE name = 'waterway.sounding_results_contour_lines_geoserver'::regclass; + +DROP VIEW waterway.sounding_results_contour_lines_geoserver; +DROP VIEW waterway.sounding_differences; +DROP TABLE caching.sounding_differences_contour_lines; +DROP TABLE waterway.sounding_results_contour_lines; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1204/03.geoserver-views.sql Wed Oct 09 16:40:18 2019 +0200 @@ -0,0 +1,28 @@ +CREATE OR REPLACE VIEW waterway.sounding_results_areas_geoserver AS + SELECT bottleneck_id, + date_info, + height, + CAST(areas AS geometry(multipolygon, 4326)) as areas + FROM waterway.sounding_results_iso_areas ia + JOIN waterway.sounding_results sr ON sr.id = ia.sounding_result_id; + +CREATE OR REPLACE VIEW waterway.sounding_differences AS + SELECT + sd.id AS id, + bn.objnam AS objnam, + srm.date_info AS minuend, + srs.date_info AS subtrahend, + sdia.height AS height, + CAST(sdia.areas AS geometry(multipolygon, 4326)) AS areas + FROM caching.sounding_differences sd + JOIN caching.sounding_differences_iso_areas sdia + ON sd.id = sdia.sounding_differences_id + JOIN waterway.sounding_results srm + ON sd.minuend = srm.id + JOIN waterway.sounding_results srs + ON sd.subtrahend = srs.id + JOIN waterway.bottlenecks bn + ON srm.bottleneck_id = bn.bottleneck_id + AND srm.date_info::timestamptz <@ bn.validity; + +GRANT SELECT ON ALL TABLES IN SCHEMA public, users, waterway TO waterway_user;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1204/04.publish.sql Wed Oct 09 16:40:18 2019 +0200 @@ -0,0 +1,6 @@ +INSERT INTO sys_admin.published_services (name) + VALUES ('waterway.sounding_results_areas_geoserver'::regclass) + ON CONFLICT (name) DO NOTHING; +INSERT INTO sys_admin.published_services (name) + VALUES ('waterway.sounding_differences'::regclass) + ON CONFLICT (name) DO NOTHING;
--- a/schema/updates/1300/02.views_to_geoservers.sql Fri Oct 04 17:26:56 2019 +0200 +++ b/schema/updates/1300/02.views_to_geoservers.sql Wed Oct 09 16:40:18 2019 +0200 @@ -154,13 +154,13 @@ (location_code).hectometre FROM waterway.distance_marks_virtual $$), - ('waterway', 'sounding_results_contour_lines_geoserver', 4326, $$ + ('waterway', 'sounding_results_areas_geoserver', 4326, $$ SELECT bottleneck_id, date_info, height, - lines - FROM waterway.sounding_results_contour_lines cl - JOIN waterway.sounding_results sr ON sr.id = cl.sounding_result_id + areas + FROM waterway.sounding_results_iso_areas ia + JOIN waterway.sounding_results sr ON sr.id = ia.sounding_result_id $$), ('waterway', 'bottlenecks_geoserver', 4326, $$ SELECT @@ -227,11 +227,11 @@ bn.objnam AS objnam, srm.date_info AS minuend, srs.date_info AS subtrahend, - sdcl.height AS height, - sdcl.lines AS lines + sdia.height AS height, + sdia.areas AS areas FROM caching.sounding_differences sd - JOIN caching.sounding_differences_contour_lines sdcl - ON sd.id = sdcl.sounding_differences_id + JOIN caching.sounding_differences_iso_areas sdia + ON sd.id = sdia.sounding_differences_id JOIN waterway.sounding_results srm ON sd.minuend = srm.id JOIN waterway.sounding_results srs @@ -252,6 +252,6 @@ waterway.bottlenecks_geoserver, waterway.stretches_geoserver, waterway.sections_geoserver, - waterway.sounding_results_contour_lines_geoserver, waterway.bottleneck_overview, + waterway.sounding_results_areas_geoserver, waterway.sounding_differences;
--- a/style-templates/sounding_differences.sld-template Fri Oct 04 17:26:56 2019 +0200 +++ b/style-templates/sounding_differences.sld-template Wed Oct 09 16:40:18 2019 +0200 @@ -50,74 +50,69 @@ </ogc:And> </ogc:Filter> {{- end }} - <se:LineSymbolizer> + <se:MaxScaleDenominator>34e3</se:MaxScaleDenominator> + <se:PolygonSymbolizer> + <se:Fill> + <se:SvgParameter name="fill">{{ .Color }}</se:SvgParameter> + </se:Fill> <se:Stroke> - <se:SvgParameter name="stroke">{{ .Color }}</se:SvgParameter> + <se:SvgParameter name="stroke">#404040</se:SvgParameter> <se:SvgParameter name="stroke-width">0.5</se:SvgParameter> </se:Stroke> - </se:LineSymbolizer> + </se:PolygonSymbolizer> + </se:Rule> + <se:Rule> + {{- if not .HasLow }} + <se:Name>≤ {{ printf "%g" .High }}</se:Name> + <ogc:Filter> + <ogc:PropertyIsLessThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> + </ogc:PropertyIsLessThanOrEqualTo> + </ogc:Filter> + {{- else if not .HasHigh }} + <se:Name>> {{ printf "%g" .Low }}</se:Name> + <ogc:Filter> + <ogc:PropertyIsGreaterThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .Low }}</ogc:Literal> + </ogc:PropertyIsGreaterThanOrEqualTo> + </ogc:Filter> + {{- else }} + <se:Name>≤ {{ printf "%g" .High }}</se:Name> + <ogc:Filter> + <ogc:And> + <ogc:PropertyIsGreaterThan> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .Low }}</ogc:Literal> + </ogc:PropertyIsGreaterThan> + <ogc:PropertyIsLessThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> + </ogc:PropertyIsLessThanOrEqualTo> + </ogc:And> + </ogc:Filter> + {{- end }} + <se:MinScaleDenominator>34e3</se:MinScaleDenominator> + <se:PolygonSymbolizer> + <se:Fill> + <se:SvgParameter name="fill">{{ .Color }}</se:SvgParameter> + </se:Fill> + </se:PolygonSymbolizer> </se:Rule> {{ end }} </se:FeatureTypeStyle> <se:FeatureTypeStyle> - <se:Name>contour_lines_emph</se:Name> - <se:Description> - <se:Abstract> - FeatureTypeStyle for emphasized contour lines - </se:Abstract> - </se:Description> - <se:Rule> - <se:LegendGraphic> - <se:Graphic> - </se:Graphic> - </se:LegendGraphic> - <ogc:Filter> - <ogc:Or> - {{ range . -}} - {{ if .HasHigh -}} - <ogc:PropertyIsEqualTo> - <ogc:Function name="numberFormat"> - <ogc:Literal>0.000000</ogc:Literal> - <ogc:PropertyName>height</ogc:PropertyName> - </ogc:Function> - <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> - </ogc:PropertyIsEqualTo> - {{ end -}} - {{ end }} - </ogc:Or> - </ogc:Filter> - <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator> - <se:LineSymbolizer> - <se:Stroke> - <se:SvgParameter name="stroke-width">1.5</se:SvgParameter> - <se:SvgParameter name="stroke"> - <ogc:Function name="Recode"> - <ogc:Function name="numberFormat"> - <ogc:Literal>0.000000</ogc:Literal> - <ogc:PropertyName>height</ogc:PropertyName> - </ogc:Function> - {{ range . -}} - {{ if .HasHigh -}} - <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> - <ogc:Literal>{{ .Color }}</ogc:Literal> - {{ end -}} - {{ end }} - </ogc:Function> - </se:SvgParameter> - </se:Stroke> - </se:LineSymbolizer> - </se:Rule> - </se:FeatureTypeStyle> - <se:FeatureTypeStyle> <se:Name>contour_lines_label</se:Name> <se:Description> <se:Abstract> - FeatureTypeStyle for labels at contour lines + FeatureTypeStyle for labels at color areas </se:Abstract> </se:Description> <se:Rule> <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator> <se:TextSymbolizer> + <se:VendorOption name="spaceAround">50</se:VendorOption> <se:Label> <ogc:Function name="Recode"> <ogc:Function name="numberFormat">
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/style-templates/sounding_results_areas_geoserver.sld-template Wed Oct 09 16:40:18 2019 +0200 @@ -0,0 +1,153 @@ +<?xml version="1.0" encoding="UTF-8"?> +<StyledLayerDescriptor + xmlns="http://www.opengis.net/sld" + xmlns:se="http://www.opengis.net/se" + xmlns:ogc="http://www.opengis.net/ogc" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" + version="1.1.0"> + <NamedLayer> + <se:Name>sounding_results_areas</se:Name> + <UserStyle> + <se:Name>sounding_results_areas</se:Name> + <se:FeatureTypeStyle> + <se:Name>area_colours</se:Name> + <se:Description> + <se:Abstract> + FeatureTypeStyle defining colour classes for height attribute + </se:Abstract> + </se:Description> + {{ range . -}} + <se:Rule> + {{- if not .HasLow }} + <se:Name>≤ {{ printf "%g" .High }}</se:Name> + <ogc:Filter> + <ogc:PropertyIsLessThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> + </ogc:PropertyIsLessThanOrEqualTo> + </ogc:Filter> + {{- else if not .HasHigh }} + <se:Name>> {{ printf "%g" .Low }}</se:Name> + <ogc:Filter> + <ogc:PropertyIsGreaterThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .Low }}</ogc:Literal> + </ogc:PropertyIsGreaterThanOrEqualTo> + </ogc:Filter> + {{- else }} + <se:Name>≤ {{ printf "%g" .High }}</se:Name> + <ogc:Filter> + <ogc:And> + <ogc:PropertyIsGreaterThan> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .Low }}</ogc:Literal> + </ogc:PropertyIsGreaterThan> + <ogc:PropertyIsLessThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> + </ogc:PropertyIsLessThanOrEqualTo> + </ogc:And> + </ogc:Filter> + {{- end }} + <se:MaxScaleDenominator>34e3</se:MaxScaleDenominator> + <se:PolygonSymbolizer> + <se:Fill> + <se:SvgParameter name="fill">{{ .Color }}</se:SvgParameter> + </se:Fill> + <se:Stroke> + <se:SvgParameter name="stroke">#404040</se:SvgParameter> + <se:SvgParameter name="stroke-width">0.5</se:SvgParameter> + </se:Stroke> + </se:PolygonSymbolizer> + </se:Rule> + <se:Rule> + {{- if not .HasLow }} + <se:Name>≤ {{ printf "%g" .High }}</se:Name> + <ogc:Filter> + <ogc:PropertyIsLessThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> + </ogc:PropertyIsLessThanOrEqualTo> + </ogc:Filter> + {{- else if not .HasHigh }} + <se:Name>> {{ printf "%g" .Low }}</se:Name> + <ogc:Filter> + <ogc:PropertyIsGreaterThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .Low }}</ogc:Literal> + </ogc:PropertyIsGreaterThanOrEqualTo> + </ogc:Filter> + {{- else }} + <se:Name>≤ {{ printf "%g" .High }}</se:Name> + <ogc:Filter> + <ogc:And> + <ogc:PropertyIsGreaterThan> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .Low }}</ogc:Literal> + </ogc:PropertyIsGreaterThan> + <ogc:PropertyIsLessThanOrEqualTo> + <ogc:PropertyName>height</ogc:PropertyName> + <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> + </ogc:PropertyIsLessThanOrEqualTo> + </ogc:And> + </ogc:Filter> + {{- end }} + <se:MinScaleDenominator>34e3</se:MinScaleDenominator> + <se:PolygonSymbolizer> + <se:Fill> + <se:SvgParameter name="fill">{{ .Color }}</se:SvgParameter> + </se:Fill> + </se:PolygonSymbolizer> + </se:Rule> + <se:VendorOption name="sortBy">height</se:VendorOption> + {{ end }} + </se:FeatureTypeStyle> + <se:FeatureTypeStyle> + <se:Name>area_labels</se:Name> + <se:Description> + <se:Abstract> + FeatureTypeStyle for labels at colour areas + </se:Abstract> + </se:Description> + <se:Rule> + <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator> + <se:TextSymbolizer> + <se:VendorOption name="spaceAround">10</se:VendorOption> + <se:Label> + <ogc:Function name="Recode"> + <ogc:Function name="numberFormat"> + <ogc:Literal>0.000000</ogc:Literal> + <ogc:PropertyName>height</ogc:PropertyName> + </ogc:Function> + {{ range . -}} + {{ if .HasHigh -}} + <ogc:Literal> + {{- printf "%f" .High -}} + </ogc:Literal><ogc:Literal> + {{- printf "%g" .High -}} + </ogc:Literal> + {{ end -}} + {{ end }} + </ogc:Function> + </se:Label> + <se:Font> + <se:SvgParameter name="font-family">Avenir</se:SvgParameter> + <se:SvgParameter name="font-family">Helvetica</se:SvgParameter> + <se:SvgParameter name="font-family">Arial</se:SvgParameter> + <se:SvgParameter name="font-family">sans-serif</se:SvgParameter> + </se:Font> + <se:LabelPlacement> + <se:LinePlacement> + <se:PerpendicularOffset>5</se:PerpendicularOffset> + </se:LinePlacement> + </se:LabelPlacement> + <se:Fill> + <se:SvgParameter name="fill">#070707</se:SvgParameter> + </se:Fill> + </se:TextSymbolizer> + </se:Rule> + </se:FeatureTypeStyle> + </UserStyle> + </NamedLayer> +</StyledLayerDescriptor>
--- a/style-templates/sounding_results_contour_lines_geoserver.sld-template Fri Oct 04 17:26:56 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<StyledLayerDescriptor - xmlns="http://www.opengis.net/sld" - xmlns:se="http://www.opengis.net/se" - xmlns:ogc="http://www.opengis.net/ogc" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" - version="1.1.0"> - <NamedLayer> - <se:Name>sounding_results_contour_lines</se:Name> - <UserStyle> - <se:Name>sounding_results_contour_lines</se:Name> - <se:FeatureTypeStyle> - <se:Name>contour_line_colours</se:Name> - <se:Description> - <se:Abstract> - FeatureTypeStyle defining colour classes for height attribute - </se:Abstract> - </se:Description> - {{ range . -}} - <se:Rule> - {{- if not .HasLow }} - <se:Name>≤ {{ printf "%g" .High }}</se:Name> - <ogc:Filter> - <ogc:PropertyIsLessThanOrEqualTo> - <ogc:PropertyName>height</ogc:PropertyName> - <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> - </ogc:PropertyIsLessThanOrEqualTo> - </ogc:Filter> - {{- else if not .HasHigh }} - <se:Name>> {{ printf "%g" .Low }}</se:Name> - <ogc:Filter> - <ogc:PropertyIsGreaterThanOrEqualTo> - <ogc:PropertyName>height</ogc:PropertyName> - <ogc:Literal>{{ printf "%f" .Low }}</ogc:Literal> - </ogc:PropertyIsGreaterThanOrEqualTo> - </ogc:Filter> - {{- else }} - <se:Name>≤ {{ printf "%g" .High }}</se:Name> - <ogc:Filter> - <ogc:And> - <ogc:PropertyIsGreaterThan> - <ogc:PropertyName>height</ogc:PropertyName> - <ogc:Literal>{{ printf "%f" .Low }}</ogc:Literal> - </ogc:PropertyIsGreaterThan> - <ogc:PropertyIsLessThanOrEqualTo> - <ogc:PropertyName>height</ogc:PropertyName> - <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> - </ogc:PropertyIsLessThanOrEqualTo> - </ogc:And> - </ogc:Filter> - {{- end }} - <se:LineSymbolizer> - <se:Stroke> - <se:SvgParameter name="stroke">{{ .Color }}</se:SvgParameter> - <se:SvgParameter name="stroke-width">0.5</se:SvgParameter> - </se:Stroke> - </se:LineSymbolizer> - </se:Rule> - {{ end }} - </se:FeatureTypeStyle> - <se:FeatureTypeStyle> - <se:Name>contour_lines_emph</se:Name> - <se:Description> - <se:Abstract> - FeatureTypeStyle for emphasized contour lines - </se:Abstract> - </se:Description> - <se:Rule> - <se:LegendGraphic> - <se:Graphic> - </se:Graphic> - </se:LegendGraphic> - <ogc:Filter> - <ogc:Or> - {{ range . -}} - {{ if .HasHigh -}} - <ogc:PropertyIsEqualTo> - <ogc:Function name="numberFormat"> - <ogc:Literal>0.000000</ogc:Literal> - <ogc:PropertyName>height</ogc:PropertyName> - </ogc:Function> - <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> - </ogc:PropertyIsEqualTo> - {{ end -}} - {{ end }} - </ogc:Or> - </ogc:Filter> - <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator> - <se:LineSymbolizer> - <se:Stroke> - <se:SvgParameter name="stroke-width">1.5</se:SvgParameter> - <se:SvgParameter name="stroke"> - <ogc:Function name="Recode"> - <ogc:Function name="numberFormat"> - <ogc:Literal>0.000000</ogc:Literal> - <ogc:PropertyName>height</ogc:PropertyName> - </ogc:Function> - {{ range . -}} - {{ if .HasHigh -}} - <ogc:Literal>{{ printf "%f" .High }}</ogc:Literal> - <ogc:Literal>{{ .Color }}</ogc:Literal> - {{ end -}} - {{ end }} - </ogc:Function> - </se:SvgParameter> - </se:Stroke> - </se:LineSymbolizer> - </se:Rule> - </se:FeatureTypeStyle> - <se:FeatureTypeStyle> - <se:Name>contour_lines_label</se:Name> - <se:Description> - <se:Abstract> - FeatureTypeStyle for labels at contour lines - </se:Abstract> - </se:Description> - <se:Rule> - <se:MaxScaleDenominator>5e3</se:MaxScaleDenominator> - <se:TextSymbolizer> - <se:Label> - <ogc:Function name="Recode"> - <ogc:Function name="numberFormat"> - <ogc:Literal>0.000000</ogc:Literal> - <ogc:PropertyName>height</ogc:PropertyName> - </ogc:Function> - {{ range . -}} - {{ if .HasHigh -}} - <ogc:Literal> - {{- printf "%f" .High -}} - </ogc:Literal><ogc:Literal> - {{- printf "%g" .High -}} - </ogc:Literal> - {{ end -}} - {{ end }} - </ogc:Function> - </se:Label> - <se:Font> - <se:SvgParameter name="font-family">Avenir</se:SvgParameter> - <se:SvgParameter name="font-family">Helvetica</se:SvgParameter> - <se:SvgParameter name="font-family">Arial</se:SvgParameter> - <se:SvgParameter name="font-family">sans-serif</se:SvgParameter> - </se:Font> - <se:LabelPlacement> - <se:LinePlacement> - <se:PerpendicularOffset>5</se:PerpendicularOffset> - </se:LinePlacement> - </se:LabelPlacement> - <se:Fill> - <se:SvgParameter name="fill">#070707</se:SvgParameter> - </se:Fill> - </se:TextSymbolizer> - </se:Rule> - </se:FeatureTypeStyle> - </UserStyle> - </NamedLayer> -</StyledLayerDescriptor>