Mercurial > gemma
changeset 1034:4299f9c1f191
merge
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Wed, 24 Oct 2018 15:38:07 +0200 |
parents | fd7059f7cbdc (current diff) 4c0c4dd393de (diff) |
children | b0364b8226e0 |
files | client/src/morphtool/store.js |
diffstat | 16 files changed, 436 insertions(+), 153 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/application/Main.vue Wed Oct 24 15:35:50 2018 +0200 +++ b/client/src/application/Main.vue Wed Oct 24 15:38:07 2018 +0200 @@ -1,8 +1,28 @@ <template> <div class="main d-flex flex-column"> - <Maplayer :drawMode="drawMode" :split="isSplitscreen" :lat="6155376" :long="1819178" :zoom="11"></Maplayer> + <Maplayer + :drawMode="drawMode" + :split="isSplitscreen" + :lat="6155376" + :long="1819178" + :zoom="11" + ></Maplayer> <div v-if="isSplitscreen" class="profile d-flex flex-row"> - <FairwayProfile :additionalSurveys="additionalSurveys" :minAlt="minAlt" maxAlt="maxAlt" :selectedWaterLevel="selectedWaterLevel" :fairwayCoordinates="fairwayCoordinates" :waterLevels="waterLevels" :data="currentProfile" :height="height" :width="width" :xScale="xAxis" :yScaleLeft="yAxisLeft" :yScaleRight="yAxisRight" :margin="margins" :totalLength="totalLength"></FairwayProfile> + <FairwayProfile + :additionalSurveys="additionalSurveys" + :minAlt="minAlt" + maxAlt="maxAlt" + :selectedWaterLevel="selectedWaterLevel" + :fairwayCoordinates="fairwayCoordinates" + :waterLevels="waterLevels" + :height="height" + :width="width" + :xScale="xAxis" + :yScaleLeft="yAxisLeft" + :yScaleRight="yAxisRight" + :margin="margins" + :totalLength="totalLength" + ></FairwayProfile> </div> </div> </template> @@ -65,7 +85,7 @@ "selectedWaterLevel", "availableSurveys" ]), - ...mapState("morphstore", ["selectedMorph"]), + ...mapState("fairwayprofile", ["selectedMorph"]), additionalSurveys() { if (!this.availableSurveys) return []; return this.availableSurveys.surveys.filter(x => {
--- a/client/src/application/lib/geo.js Wed Oct 24 15:35:50 2018 +0200 +++ b/client/src/application/lib/geo.js Wed Oct 24 15:38:07 2018 +0200 @@ -22,6 +22,12 @@ import { GeoJSON } from "ol/format.js"; import Feature from "ol/Feature"; +import distance from "@turf/distance"; +import { + lineString as turfLineString, + polygon as turfPolygon +} from "@turf/helpers"; +import lineIntersect from "@turf/line-intersect"; const EARTHRADIUS = 6378137.0; @@ -163,13 +169,40 @@ }; }; -const generateFeatureRequest = (profileLine, survey) => { +const generateFeatureRequest = (profileLine, bottleneck_id, date_info) => { const feature = new Feature({ geometry: profileLine, - bottleneck: survey.bottleneck_id, - date: survey.date_info + bottleneck: bottleneck_id, + date: date_info }); return new GeoJSON({ geometryName: "geometry" }).writeFeature(feature); }; -export { generateFeatureRequest, prepareProfile }; +const calculateFairwayCoordinates = (profileLine, fairwayGeometry, depth) => { + // both geometries have to be in EPSG:4326 + // uses turfjs distance() function + let fairwayCoordinates = []; + var line = turfLineString(profileLine.getCoordinates()); + var polygon = turfPolygon(fairwayGeometry.getCoordinates()); + var intersects = lineIntersect(line, polygon); + var l = intersects.features.length; + if (l % 2 != 0) { + console.log("Ignoring fairway because profile only intersects once."); + } else { + for (let i = 0; i < l; i += 2) { + let pStartPoint = profileLine.getCoordinates()[0]; + let fStartPoint = intersects.features[i].geometry.coordinates; + let fEndPoint = intersects.features[i + 1].geometry.coordinates; + let opts = { units: "kilometers" }; + + fairwayCoordinates.push([ + distance(pStartPoint, fStartPoint, opts) * 1000, + distance(pStartPoint, fEndPoint, opts) * 1000, + depth + ]); + } + } + return fairwayCoordinates; +}; + +export { generateFeatureRequest, prepareProfile, calculateFairwayCoordinates };
--- a/client/src/fairway/Fairwayprofile.vue Wed Oct 24 15:35:50 2018 +0200 +++ b/client/src/fairway/Fairwayprofile.vue Wed Oct 24 15:38:07 2018 +0200 @@ -1,22 +1,28 @@ <template> <div class="profiledisplay d-flex flex-row"> - <div class="fairwayprofile"> - </div> + <div class="fairwayprofile"></div> <div class="additionalsurveys d-flex flex-column"> <small class="label">Available Additional Surveys</small> <select v-model="additionalSurvey" @change="selectAdditionalSurveyData"> <option value="">None</option> - <option v-for="survey in additionalSurveys" :key="survey.date_info"> - {{survey.date_info}} - </option> + <option + v-for="survey in additionalSurveys" + :key="survey.date_info" + >{{survey.date_info}}</option> </select> <small class="mt-2"> - <b>Start:</b><br> - Lat: {{ startPoint[1] }}<br> - Lon: {{ startPoint[0] }}<br> - <b>End:</b><br> - Lat: {{ endPoint[1] }}<br> - Lon: {{ endPoint[0] }}<br> + <b>Start:</b> + <br> + Lat: {{ startPoint[1] }} + <br> + Lon: {{ startPoint[0] }} + <br> + <b>End:</b> + <br> + Lat: {{ endPoint[1] }} + <br> + Lon: {{ endPoint[0] }} + <br> </small> </div> </div> @@ -79,7 +85,6 @@ export default { name: "fairwayprofile", props: [ - "data", "width", "height", "xScale", @@ -95,7 +100,16 @@ "additionalSurveys" ], computed: { - ...mapState("fairwayprofile", ["startPoint", "endPoint"]), + ...mapState("fairwayprofile", [ + "startPoint", + "endPoint", + "currentProfile", + "selectedMorph" + ]), + currentData() { + const currentSurveyDate = this.selectedMorph.date_info; + return this.currentProfile[currentSurveyDate]; + }, waterColor() { const result = this.waterLevels.find( x => x.level === this.selectedWaterLevel @@ -109,7 +123,7 @@ }; }, watch: { - data() { + currentProfile() { this.drawDiagram(); }, width() { @@ -140,7 +154,7 @@ svg.attr("height", this.height); const width = this.width - this.margin.right - 1.5 * this.margin.left; const height = this.height - this.margin.top - 2 * this.margin.bottom; - const currentData = this.data; + const currentData = this.currentData; const { xScale, yScaleRight,
--- a/client/src/fairway/store.js Wed Oct 24 15:35:50 2018 +0200 +++ b/client/src/fairway/store.js Wed Oct 24 15:38:07 2018 +0200 @@ -12,8 +12,10 @@ * Author(s): * Thomas Junk <thomas.junk@intevation.de> */ - +import { HTTP } from "../application/lib/http"; import { prepareProfile } from "../application/lib/geo"; +import LineString from "ol/geom/LineString.js"; +import { generateFeatureRequest } from "../application/lib/geo.js"; const DEMOLEVEL = 149.345; @@ -24,12 +26,13 @@ totalLength: 0, minAlt: 0, maxAlt: 0, - currentProfile: [], + currentProfile: {}, waterLevels: [{ year: "2016", level: DEMOLEVEL, color: "#005DFF" }], selectedWaterLevel: DEMOLEVEL, fairwayCoordinates: [], startPoint: null, - endPoint: null + endPoint: null, + selectedMorph: null }, getters: { length: state => { @@ -37,13 +40,17 @@ } }, mutations: { + setSelectedMorph: (state, selectedMorph) => { + state.selectedMorph = selectedMorph; + }, setAvailableSurveys: (state, surveys) => { state.availableSurveys = surveys; }, setSelectedWaterLevel: (state, level) => { state.selectedWaterLevel = level; }, - profileLoaded: (state, response) => { + profileLoaded: (state, answer) => { + const { response, surveyDate } = answer; const { data } = response; const coordinates = data.geometry.coordinates; if (!coordinates) return; @@ -51,7 +58,7 @@ const endPoint = state.endPoint; const geoJSON = data; const result = prepareProfile({ geoJSON, startPoint, endPoint }); - state.currentProfile = result.points; + state.currentProfile[surveyDate] = result.points; state.minAlt = result.minAlt; state.maxAlt = result.maxAlt; state.totalLength = result.lengthPolyLine; @@ -66,7 +73,33 @@ state.fairwayCoordinates = coordinates; }, clearCurrentProfile: state => { - state.currentProfile = []; + state.currentProfile = {}; + } + }, + actions: { + loadProfile({ commit, state }, date_info) { + return new Promise((resolve, reject) => { + const profileLine = new LineString([state.startPoint, state.endPoint]); + const geoJSON = generateFeatureRequest( + profileLine, + state.selectedMorph.bottleneck_id, + date_info + ); + HTTP.post("/cross", geoJSON, { + headers: { "X-Gemma-Auth": localStorage.getItem("token") } + }) + .then(response => { + commit("profileLoaded", { + response: response, + surveyDate: date_info + }); + resolve(response); + }) + .catch(error => { + commit("clear_auth"); + reject(error); + }); + }); } } };
--- a/client/src/linetool/Linetool.vue Wed Oct 24 15:35:50 2018 +0200 +++ b/client/src/linetool/Linetool.vue Wed Oct 24 15:38:07 2018 +0200 @@ -51,8 +51,8 @@ }, computed: { ...mapGetters("application", ["drawMode"]), - ...mapState("identifystore", ["identifiedFeatures", "selectedMorph"]), - ...mapState("morphstore", ["selectedMorph"]), + ...mapState("identifystore", ["identifiedFeatures"]), + ...mapState("fairwayprofile", ["selectedMorph"]), icon() { return { fa: true,
--- a/client/src/map/Maplayer.vue Wed Oct 24 15:35:50 2018 +0200 +++ b/client/src/map/Maplayer.vue Wed Oct 24 15:38:07 2018 +0200 @@ -50,7 +50,6 @@ import { Vector as VectorSource } from "ol/source.js"; import { getLength } from "ol/sphere.js"; import { Icon, Stroke, Style, Fill } from "ol/style.js"; -import { generateFeatureRequest } from "../application/lib/geo.js"; import distance from "@turf/distance"; import { @@ -59,6 +58,7 @@ } from "@turf/helpers"; import lineIntersect from "@turf/line-intersect"; import { displayError } from "../application/lib/errors.js"; +import { calculateFairwayCoordinates } from "../application/lib/geo.js"; const DEMODATA = 2.5; @@ -78,7 +78,7 @@ computed: { ...mapGetters("mapstore", ["layers", "getLayerByName"]), ...mapState("mapstore", ["openLayersMap"]), - ...mapState("morphstore", ["selectedMorph"]), + ...mapState("fairwayprofile", ["selectedMorph"]), mapStyle() { return { mapfull: !this.split, @@ -176,80 +176,52 @@ const [start, end] = inputLineString.getCoordinates(); this.$store.commit("fairwayprofile/setStartPoint", start); this.$store.commit("fairwayprofile/setEndPoint", end); - this.requestProfile(start, end, this.selectedMorph); + const profileLine = new LineString([start, end]); + this.$store + .dispatch("fairwayprofile/loadProfile", this.selectedMorph.date_info) + .then(() => { + var vectorSource = this.getLayerByName( + "Fairway Dimensions" + ).data.getSource(); + this.calculateIntersection(vectorSource, profileLine); + }) + .then(() => { + this.$store.commit("application/openSplitScreen"); + }) + .catch(error => { + const { status, data } = error.response; + displayError({ + title: "Backend Error", + message: `${status}: ${data.message || data}` + }); + }); } }, - requestProfile(start, end, survey) { - // survey has to have the properties bottleneck_id and date_info - // prepare to send the first line seqment to the server as GeoJSON - const profileLine = new LineString([start, end]); - const geoJSON = generateFeatureRequest(profileLine, survey); - HTTP.post("/cross", geoJSON, { - headers: { "X-Gemma-Auth": localStorage.getItem("token") } - }) - .then(response => { - this.$store.commit("fairwayprofile/profileLoaded", response); - }) - .then(() => { - var vectorSource = this.getLayerByName( - "Fairway Dimensions" - ).data.getSource(); - vectorSource.forEachFeatureIntersectingExtent( - // need to use EPSG:3857 which is the proj of vectorSource - profileLine - .clone() - .transform("EPSG:4326", "EPSG:3857") - .getExtent(), - feature => { - // transform back to prepare for usage - var intersectingPolygon = feature - .getGeometry() - .clone() - .transform("EPSG:3857", "EPSG:4326"); - this.addToFairwayRectangle( - profileLine, - intersectingPolygon, - DEMODATA - ); - } - ); - this.$store.commit("application/openSplitScreen"); - }) - .catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` - }); - }); - }, - addToFairwayRectangle(profileLine, fairwayGeometry, depth) { - // both geometries have to be in EPSG:4326 - // uses turfjs distance() function - let fairwayCoordinates = []; - var line = turfLineString(profileLine.getCoordinates()); - var polygon = turfPolygon(fairwayGeometry.getCoordinates()); - var intersects = lineIntersect(line, polygon); - var l = intersects.features.length; - if (l % 2 != 0) { - console.log("Ignoring fairway because profile only intersects once."); - } else { - for (let i = 0; i < l; i += 2) { - let pStartPoint = profileLine.getCoordinates()[0]; - let fStartPoint = intersects.features[i].geometry.coordinates; - let fEndPoint = intersects.features[i + 1].geometry.coordinates; - let opts = { units: "kilometers" }; - - fairwayCoordinates.push([ - distance(pStartPoint, fStartPoint, opts) * 1000, - distance(pStartPoint, fEndPoint, opts) * 1000, - depth - ]); - } - } - this.$store.commit( - "fairwayprofile/setFairwayCoordinates", - fairwayCoordinates + calculateIntersection(vectorSource, profileLine) { + const transformedLine = profileLine + .clone() + .transform("EPSG:4326", "EPSG:3857") + .getExtent(); + const featureCallback = feature => { + // transform back to prepare for usage + var intersectingPolygon = feature + .getGeometry() + .clone() + .transform("EPSG:3857", "EPSG:4326"); + const fairwayCoordinates = calculateFairwayCoordinates( + profileLine, + intersectingPolygon, + DEMODATA + ); + this.$store.commit( + "fairwayprofile/setFairwayCoordinates", + fairwayCoordinates + ); + }; + vectorSource.forEachFeatureIntersectingExtent( + // need to use EPSG:3857 which is the proj of vectorSource + transformedLine, + featureCallback ); }, activateInteraction() {
--- a/client/src/morphtool/Morphtool.vue Wed Oct 24 15:35:50 2018 +0200 +++ b/client/src/morphtool/Morphtool.vue Wed Oct 24 15:38:07 2018 +0200 @@ -5,13 +5,21 @@ <div class="headline"> <h4>{{bottleneckName}}</h4> <hr> - <div @click="clearSelection" class="float-left ui-element d-flex morphtoolminus"> + <div + @click="clearSelection" + class="float-left ui-element d-flex morphtoolminus" + > <i class="fa fa-close morphtoolsminus"></i> </div> </div> <ul class="list-group surveylist"> - <li v-for="survey of surveyList.surveys" :key="survey.data_info" class="list-group-item" @click.prevent="selectSurvey(survey)"> - <a href="#" @click.prevent="">{{survey.date_info}}</a> + <li + v-for="survey of surveyList.surveys" + :key="survey.data_info" + class="list-group-item" + @click.prevent="selectSurvey(survey)" + > + <a href="#" @click.prevent>{{survey.date_info}}</a> </li> </ul> </div> @@ -20,7 +28,10 @@ <div class="d-flex flex-row justify-content-between"> <i class="fa fa-close text-danger"></i> <small>Bottleneck: </small> - <h6>{{bottleneckName}} <small>( {{selectedMorph.date_info}} )</small></h6> + <h6> + {{bottleneckName}} + <small>( {{selectedMorph.date_info}} )</small> + </h6> </div> </div> </div> @@ -111,14 +122,14 @@ computed: { ...mapGetters("application", ["drawMode"]), ...mapState("identifystore", ["identifiedFeatures"]), - ...mapState("morphstore", ["selectedMorph"]), + ...mapState("fairwayprofile", ["selectedMorph"]), selectedBottleneck: function() { if (this.identifiedFeatures && !this.drawMode) { for (let feature of this.identifiedFeatures) { let id = feature.getId(); // RegExp.prototype.test() works with number, str and undefined if (/^bottlenecks\./.test(id)) { - this.$store.commit("morphstore/setSelectedMorph", null); + this.$store.commit("fairwayprofile/setSelectedMorph", null); return feature; } } @@ -163,12 +174,12 @@ }); }, selectSurvey(survey) { - this.$store.commit("morphstore/setSelectedMorph", survey); + this.$store.commit("fairwayprofile/setSelectedMorph", survey); this.surveyList = null; }, clearSelection() { this.$store.commit("identifystore/setIdentifiedFeatures", []); - this.$store.commit("morphstore/setSelectedMorph", null); + this.$store.commit("fairwayprofile/setSelectedMorph", null); this.surveyList = null; if (this.drawMode) { this.$store.commit("application/toggleDrawModeLine");
--- a/client/src/morphtool/store.js Wed Oct 24 15:35:50 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -/* - * 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): - * Thomas Junk <thomas.junk@intevation.de> - */ -const MorphStore = { - namespaced: true, - state: { - selectedMorph: null - }, - getters: { - selectedMorph: state => { - return state.selectedMorph; - } - }, - mutations: { - setSelectedMorph: (state, selectedMorph) => { - state.selectedMorph = selectedMorph; - } - } -}; - -export default MorphStore;
--- a/client/src/store.js Wed Oct 24 15:35:50 2018 +0200 +++ b/client/src/store.js Wed Oct 24 15:38:07 2018 +0200 @@ -21,7 +21,6 @@ import mapstore from "./map/store"; import FairwayProfile from "./fairway/store"; import IdentifyStore from "./identify/store"; -import MorphStore from "./morphtool/store"; Vue.use(Vuex); @@ -31,7 +30,6 @@ fairwayprofile: FairwayProfile, identifystore: IdentifyStore, mapstore: mapstore, - morphstore: MorphStore, user: user, usermanagement: usermanagement }
--- a/cmd/gemma/main.go Wed Oct 24 15:35:50 2018 +0200 +++ b/cmd/gemma/main.go Wed Oct 24 15:38:07 2018 +0200 @@ -62,7 +62,7 @@ m.PathPrefix("/").Handler(http.FileServer(http.Dir(web))) addr := fmt.Sprintf("%s:%d", config.WebHost(), config.WebPort()) - log.Printf("listen on %s\n", addr) + log.Printf("info: listen on %s\n", addr) var h http.Handler
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/controllers/importqueue.go Wed Oct 24 15:38:07 2018 +0200 @@ -0,0 +1,163 @@ +// 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 controllers + +import ( + "database/sql" + "fmt" + "net/http" + "strconv" + + "gemma.intevation.de/gemma/pkg/models" + "github.com/gorilla/mux" +) + +const ( + selectImportsUnpagedSQL = ` +SELECT + id, + state::varchar, + enqueued, + kind, + username +FROM waterway.imports +ORDER BY id` + + selectImportPagedSQL = selectImportsUnpagedSQL + ` +LIMIT $1 OFFSET $2` + + selectHasImportSQL = ` +SELECT true FROM Waterway.imports WHERE id = $1` + + selectImportLogsSQL = ` +SELECT + time, + kind::varchar, + msg +FROM waterway.import_logs +WHERE import_id = $1 +ORDER BY time` +) + +func listImports( + _ interface{}, + req *http.Request, + conn *sql.Conn, +) (jr JSONResult, err error) { + vars := mux.Vars(req) + + off, of := vars["offset"] + lim, lf := vars["limit"] + + var rows *sql.Rows + + if of && lf { + offset, _ := strconv.ParseInt(off, 10, 64) + limit, _ := strconv.ParseInt(lim, 10, 64) + rows, err = conn.QueryContext( + req.Context(), selectImportPagedSQL, limit, offset) + } else { + rows, err = conn.QueryContext( + req.Context(), selectImportsUnpagedSQL) + } + if err != nil { + return + } + defer rows.Close() + + imports := make([]*models.Import, 0, 20) + + for rows.Next() { + var it models.Import + if err = rows.Scan( + &it.ID, + &it.State, + &it.Enqueued, + &it.Kind, + &it.User, + ); err != nil { + return + } + imports = append(imports, &it) + } + + if err = rows.Err(); err != nil { + return + } + + jr = JSONResult{ + Result: struct { + Imports []*models.Import `json:"imports"` + }{ + Imports: imports, + }, + } + return +} + +func importLogs( + _ interface{}, + req *http.Request, + conn *sql.Conn, +) (jr JSONResult, err error) { + + ctx := req.Context() + + id, _ := strconv.ParseInt(mux.Vars(req)["id"], 10, 64) + + // Check if he have such a import job first. + var dummy bool + err = conn.QueryRowContext(ctx, selectHasImportSQL, id).Scan(&dummy) + switch { + case err == sql.ErrNoRows: + err = JSONError{ + Code: http.StatusNotFound, + Message: fmt.Sprintf("Cannot find import #%d.", id), + } + return + case err != nil: + return + } + + // We have it -> generate log entries. + var rows *sql.Rows + rows, err = conn.QueryContext(ctx, selectImportLogsSQL, id) + if err != nil { + return + } + defer rows.Close() + + entries := make([]*models.ImportLogEntry, 0, 10) + + for rows.Next() { + var entry models.ImportLogEntry + if err = rows.Scan(&entry.Time, &entry.Kind, &entry.Message); err != nil { + return + } + entries = append(entries, &entry) + } + + if err = rows.Err(); err != nil { + return + } + + jr = JSONResult{ + Result: struct { + Entries []*models.ImportLogEntry `json:"entries"` + }{ + Entries: entries, + }, + } + return +}
--- a/pkg/controllers/routes.go Wed Oct 24 15:35:50 2018 +0200 +++ b/pkg/controllers/routes.go Wed Oct 24 15:38:07 2018 +0200 @@ -158,6 +158,25 @@ api.Handle("/imports/soundingresult", waterwayAdmin(http.HandlerFunc(importSoundingResult))).Methods(http.MethodPost) + // Import queue + lsImports := waterwayAdmin(&JSONHandler{ + Handle: listImports, + }) + + api.Handle("/imports", lsImports). + Methods(http.MethodGet). + Queries( + "offset", "{offset:[0-9]+}", + "limit", "{limit:[0-9]+}") + + api.Handle("/imports", lsImports). + Methods(http.MethodGet) + + api.Handle("/imports/{id:[0-9]+}", waterwayAdmin(&JSONHandler{ + Handle: importLogs, + })). + Methods(http.MethodGet) + // Token handling: Login/Logout. api.HandleFunc("/login", login). Methods(http.MethodPost)
--- a/pkg/controllers/search.go Wed Oct 24 15:35:50 2018 +0200 +++ b/pkg/controllers/search.go Wed Oct 24 15:38:07 2018 +0200 @@ -26,12 +26,13 @@ const ( searchHectometreSQL = `SELECT COALESCE(json_agg(r),'[]') FROM (SELECT (location_code).hectometre || ' rhm' AS name, - ST_AsGeoJSON(geom)::json AS geom + ST_AsGeoJSON(geom)::json AS geom, 'rhm' AS type FROM waterway.distance_marks_virtual WHERE (location_code).hectometre = $1) r` searchBottleneckSQL = `SELECT COALESCE(json_agg(r),'[]') FROM (SELECT objnam AS name, - ST_AsGeoJSON(ST_Centroid(area))::json AS geom + ST_AsGeoJSON(ST_Centroid(area))::json AS geom, + 'bottleneck' AS type FROM waterway.bottlenecks WHERE objnam ILIKE $1) r` )
--- a/pkg/geoserver/boot.go Wed Oct 24 15:35:50 2018 +0200 +++ b/pkg/geoserver/boot.go Wed Oct 24 15:38:07 2018 +0200 @@ -51,7 +51,7 @@ if err := json.NewEncoder(&buf).Encode(x); err != nil { // Should not happen - log.Printf("bad JSON: %v\n", err) + log.Printf("warn: bad JSON: %v\n", err) } return bytes.NewReader(buf.Bytes()) } @@ -379,7 +379,7 @@ func updateStyle(entry *models.IntEntry, create bool) error { - log.Printf("creating style %s\n", entry.Name) + log.Printf("info: creating style %s\n", entry.Name) // Try to load the style data. data, err := entry.LoadStyle() @@ -525,7 +525,7 @@ for i := range entries { entry := &entries[i] if stls.hasStyle(entry.Name) { - log.Printf("already has style for %s\n", entry.Name) + log.Printf("warn: already has style for %s\n", entry.Name) continue } if err := updateStyle(entry, true); err != nil {
--- a/pkg/geoserver/reconf.go Wed Oct 24 15:35:50 2018 +0200 +++ b/pkg/geoserver/reconf.go Wed Oct 24 15:38:07 2018 +0200 @@ -47,7 +47,7 @@ } func reconfigure(fn func() error) error { - log.Println("Configure GeoServer...") + log.Println("info: configure GeoServer...") const ( maxTries = 10 sleep = time.Second * 5
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/models/import.go Wed Oct 24 15:38:07 2018 +0200 @@ -0,0 +1,51 @@ +// This is Free Software under GNU Affero General Public License v >= 3.0 +// without warranty, see README.md and license for details. +// +// SPDX-License-Identifier: AGPL-3.0-or-later +// License-Filename: LICENSES/AGPL-3.0.txt +// +// Copyright (C) 2018 by via donau +// – Österreichische Wasserstraßen-Gesellschaft mbH +// Software engineering by Intevation GmbH +// +// Author(s): +// * Sascha L. Teichmann <sascha.teichmann@intevation.de> + +package models + +import ( + "encoding/json" + "time" +) + +type ( + ImportTime struct{ time.Time } + + Import struct { + ID int64 `json:"id"` + State string `json:"state"` + Enqueued ImportTime `json:"enqueued"` + Kind string `json:"kind"` + User string `json:"user"` + } + + ImportLogEntry struct { + Time ImportTime `json:"time"` + Kind string `json:"kind"` + Message string `json:"message"` + } +) + +func (it ImportTime) MarshalJSON() ([]byte, error) { + return json.Marshal(it.Format("2006-01-02T15:04:05")) +} + +func (it *ImportTime) Scan(x interface{}) error { + t, ok := x.(time.Time) + if !ok { + *it = ImportTime{} + } else { + *it = ImportTime{t} + } + return nil +}