# HG changeset patch # User Tom Gottfried # Date 1552068947 -3600 # Node ID dc4fae4bdb8f346ebe4a84a3b0ad361430d9ea8d # Parent ce39e9954e854ff518d2fe80949ee3ae19badf9e Expose axis snapping tolerance to users diff -r ce39e9954e85 -r dc4fae4bdb8f client/src/components/ImportStretches.vue --- a/client/src/components/ImportStretches.vue Fri Mar 08 18:57:58 2019 +0100 +++ b/client/src/components/ImportStretches.vue Fri Mar 08 19:15:47 2019 +0100 @@ -154,6 +154,27 @@
+ Tolerance for snapping of waterway axis + + + + + Please enter a tolerance value + + +
+
+
+
+ Object name + * Tom Gottfried */ import { mapState, mapGetters } from "vuex"; import { displayError, displayInfo } from "@/lib/errors.js"; @@ -266,6 +288,7 @@ funktion: "", startrhm: "", endrhm: "", + tolerance: "", objbn: "", nobjbn: "", countryCode: "", @@ -277,6 +300,7 @@ funktionError: false, startrhmError: false, endrhmError: false, + toleranceError: false, objbnError: false, nobjbnError: false, date_infoError: false, @@ -339,6 +363,7 @@ this.edit = true; this.startrhm = properties.lower; this.endrhm = properties.upper; + this.tolerance = properties.tolerance; this.idEditable = false; }, deleteStretch(stretch) { @@ -393,6 +418,7 @@ this.idEditable = true; this.funktion = ""; this.startrhm = ""; + this.tolerance = ""; this.endrhm = ""; this.objbn = ""; this.nobjbn = ""; @@ -405,6 +431,7 @@ this.funktionError = false; this.startrhmError = false; this.endrhmError = false; + this.toleranceError = false; this.objbnError = false; this.nobjbnError = false; this.date_infoError = false; @@ -430,6 +457,7 @@ "id", "funktion", "startrhm", + "tolerance", "endrhm", "objbn", "nobjbn", @@ -452,6 +480,7 @@ !this.id || !this.startrhm || !this.endrhm || + !this.tolerance || !this.source || !this.date_info || !this.objbn || @@ -462,6 +491,7 @@ name: this.id, from: this.startrhm, to: this.endrhm, + tolerance: this.tolerance, "source-organization": this.source, "date-info": this.date_info, objnam: this.objbn, diff -r ce39e9954e85 -r dc4fae4bdb8f client/src/components/importschedule/Importscheduledetail.vue --- a/client/src/components/importschedule/Importscheduledetail.vue Fri Mar 08 18:57:58 2019 +0100 +++ b/client/src/components/importschedule/Importscheduledetail.vue Fri Mar 08 19:15:47 2019 +0100 @@ -96,7 +96,9 @@ + * Tom Gottfried */ import { IMPORTTYPES, @@ -570,6 +573,14 @@ return false; } }, + isToleranceRequired() { + switch (this.import_) { + case this.$options.IMPORTTYPES.BOTTLENECK: + return true; + default: + return false; + } + }, isValid() { if (!this.import_) return false; if (this.directImport && !this.uploadFile) return false; @@ -577,6 +588,7 @@ if (this.isURLRequired && !this.url) return false; if (this.isSortbyRequired && !this.sortBy) return false; if (this.isFeatureTypeRequired && !this.featureType) return false; + if (this.isToleranceRequired && !this.tolerance) return false; if (this.isCredentialsRequired && (!this.username || !this.password)) return false; if (this.import_ == this.$options.IMPORTTYPES.FAIRWAYDIMENSION) { @@ -609,6 +621,9 @@ setSortBy(value) { this.sortBy = value; }, + setTolerance(value) { + this.tolerance = value; + }, setUsername(value) { this.username = value; }, @@ -673,6 +688,7 @@ this.cronString = this.currentSchedule.cronString; this.featureType = this.currentSchedule.featureType; this.sortBy = this.currentSchedule.sortBy; + this.tolerance = this.currentSchedule.tolerance; this.username = this.currentSchedule.username; this.password = this.currentSchedule.password; this.LOS = this.currentSchedule.LOS; @@ -765,6 +781,10 @@ if (!this.sortBy) return; data["sort-by"] = this.sortBy; } + if (this.isToleranceRequired) { + if (!this.tolerance) return; + data["tolerance"] = parseFloat(this.tolerance); + } if (this.isCredentialsRequired) { if (!this.username || !this.password) return; data["user"] = this.username; @@ -836,6 +856,10 @@ if (!this.featureType) return; config["feature-type"] = this.featureType; } + if (this.isToleranceRequired) { + if (!this.tolerance) return; + config["tolerance"] = parseFloat(this.tolerance); + } if (this.isCredentialsRequired) { if (!this.username || !this.password) return; config = { diff -r ce39e9954e85 -r dc4fae4bdb8f client/src/components/importschedule/importtypes/Bottleneck.vue --- a/client/src/components/importschedule/importtypes/Bottleneck.vue Fri Mar 08 18:57:58 2019 +0100 +++ b/client/src/components/importschedule/importtypes/Bottleneck.vue Fri Mar 08 19:15:47 2019 +0100 @@ -45,6 +45,30 @@ >Please enter a URL
+
+
+
+ + Tolerance for snapping of waterway axis + +
+
+ +
+
+ Please enter a tolerance value +
+
+
@@ -55,19 +79,23 @@ * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * - * Copyright (C) 2018 by via donau + * Copyright (C) 2018, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): * Thomas Junk + * Tom Gottfried */ export default { name: "bottleneckimport", - props: ["url"], + props: ["url", "tolerance"], methods: { urlChanged(e) { this.$emit("urlChanged", e.target.value); + }, + toleranceChanged(e) { + this.$emit("toleranceChanged", e.target.value); } } }; diff -r ce39e9954e85 -r dc4fae4bdb8f client/src/store/importschedule.js --- a/client/src/store/importschedule.js Fri Mar 08 18:57:58 2019 +0100 +++ b/client/src/store/importschedule.js Fri Mar 08 19:15:47 2019 +0100 @@ -77,6 +77,7 @@ triggerActive: true, featureType: null, sortBy: "hydro_scamin", + tolerance: 5, username: "", password: "", LOS: 3, @@ -181,6 +182,7 @@ let maxWidth = config["max-width"]; let sourceOrganization = config["source-organization"]; const featureType = config["feature-type"]; + const tolerance = config["tolerance"]; insecure = insecure == "true"; if (insecure) { Vue.set(state.currentSchedule, "insecure", insecure); @@ -191,6 +193,9 @@ if (sortBy) { Vue.set(state.currentSchedule, "sortBy", sortBy); } + if (tolerance) { + Vue.set(state.currentSchedule, "tolerance", tolerance); + } if (user) { Vue.set(state.currentSchedule, "username", user); } diff -r ce39e9954e85 -r dc4fae4bdb8f pkg/common/attributes.go --- a/pkg/common/attributes.go Fri Mar 08 18:57:58 2019 +0100 +++ b/pkg/common/attributes.go Fri Mar 08 19:15:47 2019 +0100 @@ -4,12 +4,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // -// Copyright (C) 2018 by via donau +// Copyright (C) 2018, 2019 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann +// * Tom Gottfried package common @@ -158,6 +159,24 @@ return ca.Set(key, v) } +func (ca Attributes) Float(key string) (float64, bool) { + s, found := ca.Get(key) + if !found { + return 0, false + } + f, err := strconv.ParseFloat(s, 64) + if err != nil { + log.Printf("error: %v\n", err) + return 0, false + } + return f, true +} + +func (ca Attributes) SetFloat(key string, value float64) bool { + v := strconv.FormatFloat(value, 'e', -1, 64) + return ca.Set(key, v) +} + func (ca Attributes) Duration(key string) (time.Duration, bool) { s, found := ca.Get(key) if !found { diff -r ce39e9954e85 -r dc4fae4bdb8f pkg/controllers/uploadedimports.go --- a/pkg/controllers/uploadedimports.go Fri Mar 08 18:57:58 2019 +0100 +++ b/pkg/controllers/uploadedimports.go Fri Mar 08 19:15:47 2019 +0100 @@ -92,7 +92,8 @@ imports.UBNJobKind, "data.xml", func(_ *http.Request, dir string) (imports.Job, error) { - return &imports.UploadedBottleneck{Dir: dir}, nil + // TODO expose tolerance to endpoint + return &imports.UploadedBottleneck{Dir: dir, Tolerance: 5}, nil }, ) } diff -r ce39e9954e85 -r dc4fae4bdb8f pkg/imports/bn.go --- a/pkg/imports/bn.go Fri Mar 08 18:57:58 2019 +0100 +++ b/pkg/imports/bn.go Fri Mar 08 19:15:47 2019 +0100 @@ -30,6 +30,8 @@ type Bottleneck struct { // URL is the URL of the SOAP service. URL string `json:"url"` + // Tolerance used for axis snapping + Tolerance float64 `json:"tolerance"` // Insecure indicates if HTTPS traffic // should validate certificates or not. Insecure bool `json:"insecure"` @@ -64,7 +66,7 @@ $4, isrsrange(isrs_fromText($5), isrs_fromText($6)), ISRSrange_area( - isrsrange(isrs_fromText($5), isrs_fromText($6)), + ISRSrange_axis(isrsrange(isrs_fromText($5), isrs_fromText($6)), $14), (SELECT ST_Collect(CAST(area AS geometry)) FROM waterway.waterway_area)), $7, @@ -162,7 +164,7 @@ return resp.Export_bn_by_isrsResult.BottleNeckType, nil } - return storeBottlenecks(ctx, fetch, importID, conn, feedback) + return storeBottlenecks(ctx, fetch, importID, conn, feedback, bn.Tolerance) } func storeBottlenecks( @@ -171,6 +173,7 @@ importID int64, conn *sql.Conn, feedback Feedback, + tolerance float64, ) (interface{}, error) { start := time.Now() @@ -202,7 +205,7 @@ for _, bn := range bns { if err := storeBottleneck( - ctx, importID, conn, feedback, bn, &nids, + ctx, importID, conn, feedback, bn, &nids, tolerance, hasStmt, insertStmt, trackStmt); err != nil { return nil, err } @@ -228,6 +231,7 @@ feedback Feedback, bn *ifbn.BottleNeckType, nids *[]string, + tolerance float64, hasStmt, insertStmt, trackStmt *sql.Stmt, ) error { @@ -278,6 +282,7 @@ limiting, bn.Date_Info, bn.Source, + tolerance, ).Scan(&nid) if err != nil { feedback.Warn("Failed to insert '%s' into database", bn.OBJNAM) diff -r ce39e9954e85 -r dc4fae4bdb8f pkg/imports/modelconvert.go --- a/pkg/imports/modelconvert.go Fri Mar 08 18:57:58 2019 +0100 +++ b/pkg/imports/modelconvert.go Fri Mar 08 19:15:47 2019 +0100 @@ -39,8 +39,9 @@ BNJobKind: func(input interface{}) interface{} { bi := input.(*models.BottleneckImport) return &Bottleneck{ - URL: bi.URL, - Insecure: bi.Insecure, + URL: bi.URL, + Tolerance: bi.Tolerance, + Insecure: bi.Insecure, } }, @@ -127,6 +128,7 @@ Name: sti.Name, From: sti.From, To: sti.To, + Tolerance: sti.Tolerance, ObjNam: sti.ObjNam, NObjNam: sti.NObjNam, Source: sti.Source, diff -r ce39e9954e85 -r dc4fae4bdb8f pkg/imports/st.go --- a/pkg/imports/st.go Fri Mar 08 18:57:58 2019 +0100 +++ b/pkg/imports/st.go Fri Mar 08 19:15:47 2019 +0100 @@ -26,6 +26,7 @@ Name string `json:"name"` From models.Isrs `json:"from"` To models.Isrs `json:"to"` + Tolerance float32 `json:"tolerance"` ObjNam string `json:"objnam"` NObjNam *string `json:"nobjnam"` Source string `json:"source-organization"` @@ -99,7 +100,7 @@ $11, (SELECT r FROM r), ISRSrange_area( - (SELECT r FROM r), + ISRSrange_axis((SELECT r FROM r), $16::double precision), (SELECT ST_Collect(CAST(area AS geometry)) FROM waterway.waterway_area)), $12, @@ -191,6 +192,7 @@ nobjnm, st.Date.Time, st.Source, + st.Tolerance, ).Scan(&id); err != nil { return nil, handleError(err) } diff -r ce39e9954e85 -r dc4fae4bdb8f pkg/imports/ubn.go --- a/pkg/imports/ubn.go Fri Mar 08 18:57:58 2019 +0100 +++ b/pkg/imports/ubn.go Fri Mar 08 19:15:47 2019 +0100 @@ -26,6 +26,8 @@ type UploadedBottleneck struct { Dir string `json:"dir"` + // Tolerance used for axis snapping + Tolerance float64 `json:"tolerance"` } // UBNJobKind is the import queue type identifier. @@ -84,5 +86,6 @@ return dst.Export_bn_by_isrsResult.BottleNeckType, nil } - return storeBottlenecks(ctx, fetch, importID, conn, feedback) + return storeBottlenecks( + ctx, fetch, importID, conn, feedback, ubn.Tolerance) } diff -r ce39e9954e85 -r dc4fae4bdb8f pkg/models/imports.go --- a/pkg/models/imports.go Fri Mar 08 18:57:58 2019 +0100 +++ b/pkg/models/imports.go Fri Mar 08 19:15:47 2019 +0100 @@ -4,12 +4,14 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // -// Copyright (C) 2018 by via donau +// Copyright (C) 2018, 2019 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann +// * Tom Gottfried + package models import ( @@ -27,6 +29,9 @@ BottleneckImport struct { ConfigurableURLImport + + // Tolerance used for axis snapping + Tolerance float64 `json:"tolerance"` } // GaugeMeasurementImport contains data used to define the endpoint @@ -95,6 +100,7 @@ Name string `json:"name"` From Isrs `json:"from"` To Isrs `json:"to"` + Tolerance float32 `json:"tolerance"` ObjNam string `json:"objnam"` NObjNam *string `json:"nobjnam"` Source string `json:"source-organization"` @@ -143,6 +149,26 @@ return nil } +func (bn *BottleneckImport) MarshalAttributes(attrs common.Attributes) error { + if err := bn.ConfigurableURLImport.MarshalAttributes(attrs); err != nil { + return err + } + attrs.SetFloat("tolerance", bn.Tolerance) + return nil +} + +func (bn *BottleneckImport) UnmarshalAttributes(attrs common.Attributes) error { + if err := bn.ConfigurableURLImport.UnmarshalAttributes(attrs); err != nil { + return err + } + tol, found := attrs.Float("tolerance") + if !found { + return errors.New("missing 'tolerance' attribute") + } + bn.Tolerance = tol + return nil +} + func (fdi *FairwayDimensionImport) MarshalAttributes(attrs common.Attributes) error { if err := fdi.WFSImport.MarshalAttributes(attrs); err != nil { return err diff -r ce39e9954e85 -r dc4fae4bdb8f schema/isrs_functions.sql --- a/schema/isrs_functions.sql Fri Mar 08 18:57:58 2019 +0100 +++ b/schema/isrs_functions.sql Fri Mar 08 19:15:47 2019 +0100 @@ -47,6 +47,7 @@ -- Uses the table waterway.distance_marks_virtual to map ISRS location codes -- to their geo-location. -- Distance marks are assumed to be near the axis. +-- Returns the axis geometry transformed to the best matching UTM zone. CREATE OR REPLACE FUNCTION ISRSrange_axis( stretch isrsrange, tolerance float @@ -135,32 +136,28 @@ LANGUAGE plpgsql STABLE PARALLEL SAFE; --- Clip an area to a stretch given by a pair of ISRS location codes. --- Uses ISRSrange_axis() to retrieve the respective clipped axis used to find --- perpendicular direction at geo-locations of ISRS codes. +-- Clip an area to a stretch given by a geometry representing an axis (e.g. +-- the output of ISRSrange_axis()). +-- Clipping is done by cutting the area in perpendicular direction at +-- the ends of the given axis. -- The area passed as argument is assumed to intersect with the axis -- (use e.g. waterway area or fairway dimensions). -- If a multipolygon is passed, the union of the polygons intersecting with the --- relevant part of the axis is used for clipping. +-- axis is used for clipping. CREATE OR REPLACE FUNCTION ISRSrange_area( - stretch isrsrange, + axis geometry, area geometry ) RETURNS geometry AS $$ WITH - axis_substring AS ( - SELECT ISRSrange_axis(stretch, 5) AS line), - utm_zone AS ( - SELECT best_utm(stretch) AS z), area_subset AS ( -- In case area is a multipolygon, process the union of those -- polygons, which intersect with the axis. The union is to avoid -- problems with invalid/self-intersecting multipolygons SELECT ST_Union(a_dmp.geom) AS area - FROM axis_substring, utm_zone, LATERAL ( - SELECT ST_MakeValid(ST_Transform(geom, z)) AS geom - FROM ST_Dump(area)) AS a_dmp - WHERE ST_Intersects(a_dmp.geom, axis_substring.line) + FROM (SELECT ST_MakeValid(ST_Transform(geom, ST_SRID(axis))) + FROM ST_Dump(area)) AS a_dmp (geom) + WHERE ST_Intersects(a_dmp.geom, axis) ), rotated_ends AS ( SELECT ST_Collect(ST_Scale( @@ -168,11 +165,11 @@ (ST_X(p1) - ST_X(p2)) / 2, (ST_Y(p1) - ST_Y(p2)) / 2), ST_Point(d, d), p1)) AS blade - FROM axis_substring, area_subset, - LATERAL (SELECT i, ST_PointN(line, i) AS p1 + FROM (SELECT i, ST_PointN(axis, i) AS p1 FROM (VALUES (1), (-1)) AS idx (i)) AS ep, - ST_Rotate(ST_PointN(line, i*2), pi()/2, p1) AS ep2 (p2), + ST_Rotate(ST_PointN(axis, i*2), pi()/2, p1) AS ep2 (p2), ST_Makeline(p1, p2) AS e (e), + area_subset, LATERAL (SELECT (ST_MaxDistance(p1, area) / ST_Length(e)) * 2) AS d (d)), range_area AS ( @@ -183,8 +180,7 @@ -- From the polygons returned by the last CTE, select only those -- around the clipped axis SELECT ST_Multi(ST_Transform(ST_Union(range_area.geom), ST_SRID(area))) - FROM axis_substring, range_area - WHERE ST_Intersects(ST_Buffer(range_area.geom, -0.0001), - axis_substring.line) + FROM range_area + WHERE ST_Intersects(ST_Buffer(range_area.geom, -0.0001), axis) $$ LANGUAGE sql; diff -r ce39e9954e85 -r dc4fae4bdb8f schema/isrs_tests.sql --- a/schema/isrs_tests.sql Fri Mar 08 18:57:58 2019 +0100 +++ b/schema/isrs_tests.sql Fri Mar 08 19:15:47 2019 +0100 @@ -4,7 +4,7 @@ -- SPDX-License-Identifier: AGPL-3.0-or-later -- License-Filename: LICENSES/AGPL-3.0.txt --- Copyright (C) 2018 by via donau +-- Copyright (C) 2018, 2019 by via donau -- – Österreichische Wasserstraßen-Gesellschaft mbH -- Software engineering by Intevation GmbH @@ -52,9 +52,10 @@ 'ISRSrange_axis fails if no contiguous axis can be constructed'); SELECT ok( - ISRSrange_area(isrsrange( - ('AT', 'XXX', '00001', '00000', 0)::isrs, - ('AT', 'XXX', '00001', '00000', 1)::isrs), + ISRSrange_area(ISRSrange_axis(isrsrange( + ('AT', 'XXX', '00001', '00000', 0)::isrs, + ('AT', 'XXX', '00001', '00000', 1)::isrs), + 5), ST_SetSRID('POLYGON((0 1, 0 2, 1 2, 1 1, 0 1))'::geometry, 4326) ) IS NULL, 'ISRSrange_area returns NULL, if given area does not intersect with axis'); @@ -62,9 +63,10 @@ SELECT results_eq($$ SELECT every(ST_DWithin( ST_Boundary(ISRSrange_area( - isrsrange( - ('AT', 'XXX', '00001', '00000', 0)::isrs, - ('AT', 'XXX', '00001', '00000', 1)::isrs), + ISRSrange_axis(isrsrange( + ('AT', 'XXX', '00001', '00000', 0)::isrs, + ('AT', 'XXX', '00001', '00000', 1)::isrs), + 5), (SELECT ST_Collect(CAST(area AS geometry)) FROM waterway.waterway_area))), geom, @@ -83,9 +85,10 @@ SELECT ok( 2 = ST_NumGeometries( ISRSrange_area( - isrsrange( - ('AT', 'XXX', '00001', '00000', 0)::isrs, - ('AT', 'XXX', '00001', '00000', 1)::isrs), + ISRSrange_axis(isrsrange( + ('AT', 'XXX', '00001', '00000', 0)::isrs, + ('AT', 'XXX', '00001', '00000', 1)::isrs), + 5), ST_SetSRID(ST_Collect( ST_Translate(:'test_area', (ST_XMax(:'test_area'::geometry) @@ -101,9 +104,10 @@ SELECT ok( 1 = ST_NumGeometries( ISRSrange_area( - isrsrange( - ('AT', 'XXX', '00001', '00000', 0)::isrs, - ('AT', 'XXX', '00001', '00000', 1)::isrs), + ISRSrange_axis(isrsrange( + ('AT', 'XXX', '00001', '00000', 0)::isrs, + ('AT', 'XXX', '00001', '00000', 1)::isrs), + 5), ST_SetSRID(ST_Collect( ST_Translate(:'test_area', (ST_XMax(:'test_area'::geometry) @@ -119,9 +123,10 @@ SELECT results_eq($$ SELECT every(ST_DWithin( ST_Boundary(ISRSrange_area( - isrsrange( - ('AT', 'XXX', '00001', '00000', 0)::isrs, - ('AT', 'XXX', '00001', '00000', 2)::isrs), + ISRSrange_axis(isrsrange( + ('AT', 'XXX', '00001', '00000', 0)::isrs, + ('AT', 'XXX', '00001', '00000', 2)::isrs), + 5), (SELECT ST_Collect(CAST(area AS geometry)) FROM waterway.waterway_area))), geom,