# HG changeset patch # User Sascha L. Teichmann # Date 1570626635 -7200 # Node ID bd2999cac246dfbb242f3a2d5688934841fb70c5 # Parent 998f4d7d962688944af1a4db57c940c1d3e10c8e# Parent 800119befa90afe2efd3661e8052b41807687d08 Merge 'iso-areas' branch back into default. diff -r 998f4d7d9626 -r bd2999cac246 client/src/components/layers/Layerselect.vue --- a/client/src/components/layers/Layerselect.vue Wed Oct 09 14:40:56 2019 +0200 +++ b/client/src/components/layers/Layerselect.vue Wed Oct 09 15:10:35 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", diff -r 998f4d7d9626 -r bd2999cac246 client/src/components/map/layers.js --- a/client/src/components/map/layers.js Wed Oct 09 14:40:56 2019 +0200 +++ b/client/src/components/map/layers.js Wed Oct 09 15:10:35 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 }, diff -r 998f4d7d9626 -r bd2999cac246 go.mod --- a/go.mod Wed Oct 09 14:40:56 2019 +0200 +++ b/go.mod Wed Oct 09 15:10:35 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 diff -r 998f4d7d9626 -r bd2999cac246 go.sum --- a/go.sum Wed Oct 09 14:40:56 2019 +0200 +++ b/go.sum Wed Oct 09 15:10:35 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= diff -r 998f4d7d9626 -r bd2999cac246 pkg/common/linear.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/common/linear.go Wed Oct 09 15:10:35 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 +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 + } +} diff -r 998f4d7d9626 -r bd2999cac246 pkg/controllers/diff.go --- a/pkg/controllers/diff.go Wed Oct 09 14:40:56 2019 +0200 +++ b/pkg/controllers/diff.go Wed Oct 09 15:10:35 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)) diff -r 998f4d7d9626 -r bd2999cac246 pkg/controllers/system.go --- a/pkg/controllers/system.go Wed Oct 09 14:40:56 2019 +0200 +++ b/pkg/controllers/system.go Wed Oct 09 15:10:35 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) diff -r 998f4d7d9626 -r bd2999cac246 pkg/geoserver/templates.go --- a/pkg/geoserver/templates.go Wed Oct 09 14:40:56 2019 +0200 +++ b/pkg/geoserver/templates.go Wed Oct 09 15:10:35 2019 +0200 @@ -35,7 +35,7 @@ func init() { RegisterStylePreprocessor( - "sounding_results_contour_lines_geoserver", + "sounding_results_areas_geoserver", templateContourLinesFunc("morphology_classbreaks")) RegisterStylePreprocessor( "sounding_differences", diff -r 998f4d7d9626 -r bd2999cac246 pkg/imports/isr.go --- a/pkg/imports/isr.go Wed Oct 09 14:40:56 2019 +0200 +++ b/pkg/imports/isr.go Wed Oct 09 15:10:35 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 + } } } diff -r 998f4d7d9626 -r bd2999cac246 pkg/imports/sr.go --- a/pkg/imports/sr.go Wed Oct 09 14:40:56 2019 +0200 +++ b/pkg/imports/sr.go Wed Oct 09 15:10:35 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 } diff -r 998f4d7d9626 -r bd2999cac246 pkg/models/colors.go --- a/pkg/models/colors.go Wed Oct 09 14:40:56 2019 +0200 +++ b/pkg/models/colors.go Wed Oct 09 15:10:35 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 diff -r 998f4d7d9626 -r bd2999cac246 pkg/octree/areas.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/octree/areas.go Wed Oct 09 15:10:35 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 + +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 +} diff -r 998f4d7d9626 -r bd2999cac246 pkg/octree/vertex.go --- a/pkg/octree/vertex.go Wed Oct 09 14:40:56 2019 +0200 +++ b/pkg/octree/vertex.go Wed Oct 09 15:10:35 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) diff -r 998f4d7d9626 -r bd2999cac246 schema/default_sysconfig.sql --- a/schema/default_sysconfig.sql Wed Oct 09 14:40:56 2019 +0200 +++ b/schema/default_sysconfig.sql Wed Oct 09 15:10:35 2019 +0200 @@ -30,13 +30,13 @@ ('waterway.gauges_geoserver'), ('waterway.distance_marks_ashore_geoserver'), ('waterway.distance_marks_geoserver'), - ('waterway.sounding_results_contour_lines_geoserver'), ('waterway.bottlenecks_geoserver'), ('waterway.bottleneck_overview'), ('waterway.waterway_axis'), ('waterway.waterway_area'), ('waterway.waterway_profiles'), - ('waterway.sounding_differences'); + ('waterway.sounding_differences'), + ('waterway.sounding_results_areas_geoserver'); -- -- Settings diff -r 998f4d7d9626 -r bd2999cac246 schema/gemma.sql --- a/schema/gemma.sql Wed Oct 09 14:40:56 2019 +0200 +++ b/schema/gemma.sql Wed Oct 09 15:10:35 2019 +0200 @@ -690,13 +690,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) ) -- @@ -884,11 +884,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) ) ; diff -r 998f4d7d9626 -r bd2999cac246 schema/geoserver_views.sql --- a/schema/geoserver_views.sql Wed Oct 09 14:40:56 2019 +0200 +++ b/schema/geoserver_views.sql Wed Oct 09 15:10:35 2019 +0200 @@ -182,14 +182,6 @@ ON isrs_fromtext(g.isrs_code) <@ s.section GROUP BY s.id; -CREATE OR REPLACE VIEW waterway.sounding_results_contour_lines_geoserver AS - SELECT bottleneck_id, - date_info, - height, - CAST(lines AS geometry(multilinestring, 4326)) AS lines - FROM waterway.sounding_results_contour_lines cl - JOIN waterway.sounding_results sr ON sr.id = cl.sounding_result_id; - CREATE OR REPLACE VIEW waterway.bottleneck_overview AS SELECT objnam AS name, @@ -205,17 +197,26 @@ WHERE bn.validity @> current_timestamp ORDER BY objnam; +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, - sdcl.height AS height, - CAST(sdcl.lines AS geometry(multilinestring, 4326)) AS lines + sdia.height AS height, + CAST(sdia.areas AS geometry(multipolygon, 4326)) 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 diff -r 998f4d7d9626 -r bd2999cac246 schema/updates/1204/01.create-iso-areas.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1204/01.create-iso-areas.sql Wed Oct 09 15:10:35 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; + diff -r 998f4d7d9626 -r bd2999cac246 schema/updates/1204/02.delete-contours.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1204/02.delete-contours.sql Wed Oct 09 15:10:35 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; + diff -r 998f4d7d9626 -r bd2999cac246 schema/updates/1204/03.geoserver-views.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1204/03.geoserver-views.sql Wed Oct 09 15:10:35 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; diff -r 998f4d7d9626 -r bd2999cac246 schema/updates/1204/04.publish.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1204/04.publish.sql Wed Oct 09 15:10:35 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; diff -r 998f4d7d9626 -r bd2999cac246 schema/version.sql --- a/schema/version.sql Wed Oct 09 14:40:56 2019 +0200 +++ b/schema/version.sql Wed Oct 09 15:10:35 2019 +0200 @@ -1,1 +1,1 @@ -INSERT INTO gemma_schema_version(version) VALUES (1203); +INSERT INTO gemma_schema_version(version) VALUES (1204); diff -r 998f4d7d9626 -r bd2999cac246 style-templates/sounding_differences.sld-template --- a/style-templates/sounding_differences.sld-template Wed Oct 09 14:40:56 2019 +0200 +++ b/style-templates/sounding_differences.sld-template Wed Oct 09 15:10:35 2019 +0200 @@ -50,74 +50,69 @@ {{- end }} - + 34e3 + + + {{ .Color }} + - {{ .Color }} + #404040 0.5 - + + + + {{- if not .HasLow }} + ≤ {{ printf "%g" .High }} + + + height + {{ printf "%f" .High }} + + + {{- else if not .HasHigh }} + > {{ printf "%g" .Low }} + + + height + {{ printf "%f" .Low }} + + + {{- else }} + ≤ {{ printf "%g" .High }} + + + + height + {{ printf "%f" .Low }} + + + height + {{ printf "%f" .High }} + + + + {{- end }} + 34e3 + + + {{ .Color }} + + {{ end }} - contour_lines_emph - - - FeatureTypeStyle for emphasized contour lines - - - - - - - - - - {{ range . -}} - {{ if .HasHigh -}} - - - 0.000000 - height - - {{ printf "%f" .High }} - - {{ end -}} - {{ end }} - - - 5e3 - - - 1.5 - - - - 0.000000 - height - - {{ range . -}} - {{ if .HasHigh -}} - {{ printf "%f" .High }} - {{ .Color }} - {{ end -}} - {{ end }} - - - - - - - contour_lines_label - FeatureTypeStyle for labels at contour lines + FeatureTypeStyle for labels at color areas 5e3 + 50 diff -r 998f4d7d9626 -r bd2999cac246 style-templates/sounding_results_areas_geoserver.sld-template --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/style-templates/sounding_results_areas_geoserver.sld-template Wed Oct 09 15:10:35 2019 +0200 @@ -0,0 +1,153 @@ + + + + sounding_results_areas + + sounding_results_areas + + area_colours + + + FeatureTypeStyle defining colour classes for height attribute + + + {{ range . -}} + + {{- if not .HasLow }} + ≤ {{ printf "%g" .High }} + + + height + {{ printf "%f" .High }} + + + {{- else if not .HasHigh }} + > {{ printf "%g" .Low }} + + + height + {{ printf "%f" .Low }} + + + {{- else }} + ≤ {{ printf "%g" .High }} + + + + height + {{ printf "%f" .Low }} + + + height + {{ printf "%f" .High }} + + + + {{- end }} + 34e3 + + + {{ .Color }} + + + #404040 + 0.5 + + + + + {{- if not .HasLow }} + ≤ {{ printf "%g" .High }} + + + height + {{ printf "%f" .High }} + + + {{- else if not .HasHigh }} + > {{ printf "%g" .Low }} + + + height + {{ printf "%f" .Low }} + + + {{- else }} + ≤ {{ printf "%g" .High }} + + + + height + {{ printf "%f" .Low }} + + + height + {{ printf "%f" .High }} + + + + {{- end }} + 34e3 + + + {{ .Color }} + + + + height + {{ end }} + + + area_labels + + + FeatureTypeStyle for labels at colour areas + + + + 5e3 + + 10 + + + + 0.000000 + height + + {{ range . -}} + {{ if .HasHigh -}} + + {{- printf "%f" .High -}} + + {{- printf "%g" .High -}} + + {{ end -}} + {{ end }} + + + + Avenir + Helvetica + Arial + sans-serif + + + + 5 + + + + #070707 + + + + + + + diff -r 998f4d7d9626 -r bd2999cac246 style-templates/sounding_results_contour_lines_geoserver.sld-template --- a/style-templates/sounding_results_contour_lines_geoserver.sld-template Wed Oct 09 14:40:56 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ - - - - sounding_results_contour_lines - - sounding_results_contour_lines - - contour_line_colours - - - FeatureTypeStyle defining colour classes for height attribute - - - {{ range . -}} - - {{- if not .HasLow }} - ≤ {{ printf "%g" .High }} - - - height - {{ printf "%f" .High }} - - - {{- else if not .HasHigh }} - > {{ printf "%g" .Low }} - - - height - {{ printf "%f" .Low }} - - - {{- else }} - ≤ {{ printf "%g" .High }} - - - - height - {{ printf "%f" .Low }} - - - height - {{ printf "%f" .High }} - - - - {{- end }} - - - {{ .Color }} - 0.5 - - - - {{ end }} - - - contour_lines_emph - - - FeatureTypeStyle for emphasized contour lines - - - - - - - - - - {{ range . -}} - {{ if .HasHigh -}} - - - 0.000000 - height - - {{ printf "%f" .High }} - - {{ end -}} - {{ end }} - - - 5e3 - - - 1.5 - - - - 0.000000 - height - - {{ range . -}} - {{ if .HasHigh -}} - {{ printf "%f" .High }} - {{ .Color }} - {{ end -}} - {{ end }} - - - - - - - - contour_lines_label - - - FeatureTypeStyle for labels at contour lines - - - - 5e3 - - - - - 0.000000 - height - - {{ range . -}} - {{ if .HasHigh -}} - - {{- printf "%f" .High -}} - - {{- printf "%g" .High -}} - - {{ end -}} - {{ end }} - - - - Avenir - Helvetica - Arial - sans-serif - - - - 5 - - - - #070707 - - - - - - -