comparison pkg/imports/sr.go @ 4562:5cc4042cf07c iso-areas

Started with integrating iso area generation into gemma server. WIP
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 02 Oct 2019 18:35:09 +0200
parents 4ca884dfc470
children a4042ab02e05
comparison
equal deleted inserted replaced
4561:f7b57136c800 4562:5cc4042cf07c
26 "io" 26 "io"
27 "math" 27 "math"
28 "os" 28 "os"
29 "path" 29 "path"
30 "path/filepath" 30 "path/filepath"
31 "runtime"
31 "strconv" 32 "strconv"
32 "strings" 33 "strings"
34 "sync"
33 "time" 35 "time"
34 36
37 "github.com/fogleman/contourmap"
35 shp "github.com/jonas-p/go-shp" 38 shp "github.com/jonas-p/go-shp"
36 39
37 "gemma.intevation.de/gemma/pkg/common" 40 "gemma.intevation.de/gemma/pkg/common"
38 "gemma.intevation.de/gemma/pkg/models" 41 "gemma.intevation.de/gemma/pkg/models"
39 "gemma.intevation.de/gemma/pkg/octree" 42 "gemma.intevation.de/gemma/pkg/octree"
43 "gemma.intevation.de/gemma/pkg/wkb"
40 ) 44 )
41 45
42 // SoundingResult is a Job to import sounding reults 46 // SoundingResult is a Job to import sounding reults
43 // from a ZIP file into the database. 47 // from a ZIP file into the database.
44 type SoundingResult struct { 48 type SoundingResult struct {
68 contourStepWidth = 0.1 72 contourStepWidth = 0.1
69 contourTolerance = 0.1 73 contourTolerance = 0.1
70 ) 74 )
71 75
72 const ( 76 const (
77 // pointsPerSquareMeter is the average number of points
78 // when generating a artifical height model for single beam scans.
73 pointsPerSquareMeter = 2 79 pointsPerSquareMeter = 2
80 )
81
82 const (
83 // isoCellSize is the side length of a raster cell when tracing
84 // iso areas.
85 isoCellSize = 0.5
74 ) 86 )
75 87
76 // SRJobKind is the unique name of this import job type. 88 // SRJobKind is the unique name of this import job type.
77 const SRJobKind JobKind = "sr" 89 const SRJobKind JobKind = "sr"
78 90
175 4326 187 4326
176 ) 188 )
177 FROM waterway.sounding_results sr 189 FROM waterway.sounding_results sr
178 WHERE id = $1 190 WHERE id = $1
179 ` 191 `
192 insertIsoAreasSQL = `
193 INSERT INTO waterway.sounding_results_iso_areas (
194 sounding_result_id,
195 height,
196 areas
197 )
198 SELECT
199 $1,
200 $2,
201 ST_Transform(
202 ST_Multi(
203 ST_CollectionExtract(
204 ST_SimplifyPreserveTopology(
205 ST_Multi(ST_Collectionextract(
206 ST_MakeValid(ST_GeomFromWKB($4, $3::integer)), 2)),
207 $5
208 ),
209 2
210 )
211 ),
212 4326
213 )
214 FROM waterway.sounding_results sr
215 WHERE id = $1
216 `
180 217
181 selectGaugeLDCSQL = ` 218 selectGaugeLDCSQL = `
182 SELECT 219 SELECT
183 grwl.value, 220 grwl.value,
184 grwl.depth_reference 221 grwl.depth_reference
617 _, err = tx.ExecContext(ctx, insertOctreeSQL, id, checksum, octreeIndex) 654 _, err = tx.ExecContext(ctx, insertOctreeSQL, id, checksum, octreeIndex)
618 if err != nil { 655 if err != nil {
619 return nil, err 656 return nil, err
620 } 657 }
621 feedback.Info("Storing octree index took %s.", time.Since(start)) 658 feedback.Info("Storing octree index took %s.", time.Since(start))
622 feedback.Info("Generate contour lines") 659 err = generateIsos(ctx, tx, feedback, builder.Tree(), id)
623 660 if err != nil {
624 start = time.Now() 661 return nil, err
625 err = generateContours(ctx, tx, feedback, builder.Tree(), id) 662 }
626 if err != nil {
627 return nil, err
628 }
629 feedback.Info("Generating contour lines took %s.",
630 time.Since(start))
631 663
632 // Store for potential later removal. 664 // Store for potential later removal.
633 if err = track(ctx, tx, importID, "waterway.sounding_results", id); err != nil { 665 if err = track(ctx, tx, importID, "waterway.sounding_results", id); err != nil {
634 return nil, err 666 return nil, err
635 } 667 }
825 } 857 }
826 858
827 return shapeToPolygon(s) 859 return shapeToPolygon(s)
828 } 860 }
829 861
830 func generateContours( 862 func generateIsos(
831 ctx context.Context, 863 ctx context.Context,
832 tx *sql.Tx, 864 tx *sql.Tx,
833 feedback Feedback, 865 feedback Feedback,
834 tree *octree.Tree, 866 tree *octree.Tree,
835 id int64, 867 id int64,
836 ) error { 868 ) error {
837 stmt, err := tx.PrepareContext(ctx, insertContourSQL)
838 if err != nil {
839 return err
840 }
841 defer stmt.Close()
842 869
843 heights, err := octree.LoadClassBreaks( 870 heights, err := octree.LoadClassBreaks(
844 ctx, tx, 871 ctx, tx,
845 "morphology_classbreaks", 872 "morphology_classbreaks",
846 ) 873 )
874
847 if err != nil { 875 if err != nil {
848 feedback.Warn("Loading class breaks failed: %v", err) 876 feedback.Warn("Loading class breaks failed: %v", err)
849 feedback.Info("Using default class breaks") 877 feedback.Info("Using default class breaks")
850 heights = nil 878 heights = nil
851 h := contourStepWidth * math.Ceil(tree.Min.Z/contourStepWidth) 879 h := contourStepWidth * math.Ceil(tree.Min.Z/contourStepWidth)
869 log.Printf("%.2f - %.2f\n", tree.Min.Z, tree.Max.Z) 897 log.Printf("%.2f - %.2f\n", tree.Min.Z, tree.Max.Z)
870 */ 898 */
871 899
872 heights = common.DedupFloat64s(heights) 900 heights = common.DedupFloat64s(heights)
873 901
902 if err := generateContours(ctx, tx, feedback, tree, heights, id); err != nil {
903 return err
904 }
905
906 return generateIsoAreas(ctx, tx, feedback, tree, heights, id)
907 }
908
909 func generateIsoAreas(
910 ctx context.Context,
911 tx *sql.Tx,
912 feedback Feedback,
913 tree *octree.Tree,
914 heights []float64,
915 id int64,
916 ) error {
917 feedback.Info("Generate iso areas")
918 total := time.Now()
919 defer func() {
920 feedback.Info("Generating iso areas took %s.",
921 time.Since(total))
922 }()
923
924 min, max := tree.Min, tree.Max
925
926 width := max.X - min.X
927 height := max.Y - min.Y
928
929 feedback.Info("width/height: %.2f / %.2f", width, height)
930
931 xcells := int(math.Ceil(width / isoCellSize))
932 ycells := int(math.Ceil(height / isoCellSize))
933
934 feedback.Info("raster size: (%d, %d)", xcells, ycells)
935
936 // Add border for closing
937 raster := make([]float64, (xcells+2)*(ycells+2))
938
939 // prefill for no data
940 const nodata = -math.MaxFloat64
941 for i := range raster {
942 raster[i] = nodata
943 }
944
945 // rasterize the height model
946
947 var wg sync.WaitGroup
948
949 rows := make(chan int)
950
951 rasterRow := func() {
952 defer wg.Done()
953 for i := range rows {
954 pos := (i+1)*(xcells+2) + 1
955 row := raster[pos : pos+xcells]
956 py := min.Y + float64(i)*isoCellSize + isoCellSize/2
957 px := min.X + isoCellSize/2
958 for j := range row {
959 v, ok := tree.Value(px, py)
960 if ok {
961 row[j] = v
962 }
963 px += isoCellSize
964 }
965 }
966 }
967
968 start := time.Now()
969
970 for n := runtime.NumCPU(); n >= 1; n-- {
971 wg.Add(1)
972 go rasterRow()
973 }
974
975 for i := 0; i < ycells; i++ {
976 rows <- i
977 }
978 close(rows)
979
980 wg.Wait()
981
982 feedback.Info("Rasterizing took %v", time.Since(start))
983
984 // TODO: Implement me!
985 feedback.Info("Trace iso areas")
986
987 start = time.Now()
988
989 tracer := contourmap.FromFloat64s(xcells+2, ycells+2, raster)
990
991 areas := make([]wkb.MultiPolygonGeom, len(heights))
992
993 reprojX := common.Linear(1, min.X, float64(xcells+1), max.X)
994 reprojY := common.Linear(1, min.Y, float64(ycells+1), max.Y)
995
996 cnts := make(chan int)
997
998 doContours := func() {
999 defer wg.Done()
1000 for hIdx := range cnts {
1001 c := tracer.Contours(heights[hIdx])
1002 a := make(wkb.MultiPolygonGeom, len(c))
1003
1004 for i, pl := range c {
1005
1006 shell := make(wkb.LinearRingGeom, len(pl))
1007 for j, pt := range pl {
1008 x := reprojX(pt.X)
1009 y := reprojY(pt.Y)
1010 shell[j] = wkb.PointGeom{X: x, Y: y}
1011 }
1012
1013 a[i] = wkb.PolygonGeom{shell}
1014 }
1015 areas[hIdx] = a
1016 }
1017 }
1018
1019 for n := runtime.NumCPU(); n >= 1; n-- {
1020 wg.Add(1)
1021 go doContours()
1022 }
1023
1024 for i := range heights {
1025 cnts <- i
1026 }
1027 close(cnts)
1028
1029 wg.Wait()
1030
1031 feedback.Info("Tracing areas took %v", time.Since(start))
1032
1033 // Raster and tracer are not needed any more.
1034 raster, tracer = nil, nil
1035
1036 return storeAreas(ctx, tx, feedback, areas, heights)
1037 }
1038
1039 func storeAreas(
1040 ctx context.Context,
1041 tx *sql.Tx,
1042 feedback Feedback,
1043 areas []wkb.MultiPolygonGeom,
1044 heights []float64,
1045 ) error {
1046 feedback.Info("Storing iso areas")
1047 total := time.Now()
1048 defer func() {
1049 feedback.Info("Storing iso areas took %v",
1050 time.Since(total))
1051 }()
1052
1053 stmt, err := tx.PrepareContext(ctx, insertIsoAreasSQL)
1054 if err != nil {
1055 return err
1056 }
1057 defer stmt.Close()
1058
1059 // TODO: Implement me!
1060
1061 return nil
1062 }
1063
1064 func generateContours(
1065 ctx context.Context,
1066 tx *sql.Tx,
1067 feedback Feedback,
1068 tree *octree.Tree,
1069 heights []float64,
1070 id int64,
1071 ) error {
1072 feedback.Info("Generate contour lines")
1073 start := time.Now()
1074 defer func() {
1075 feedback.Info("Generating contour lines took %v",
1076 time.Since(start))
1077 }()
1078
1079 stmt, err := tx.PrepareContext(ctx, insertContourSQL)
1080 if err != nil {
1081 return err
1082 }
1083 defer stmt.Close()
1084
874 octree.DoContours(tree, heights, func(res *octree.ContourResult) { 1085 octree.DoContours(tree, heights, func(res *octree.ContourResult) {
875 if err == nil && len(res.Lines) > 0 { 1086 if err == nil && len(res.Lines) > 0 {
876 _, err = stmt.ExecContext( 1087 _, err = stmt.ExecContext(
877 ctx, 1088 ctx,
878 id, res.Height, tree.EPSG, 1089 id, res.Height, tree.EPSG,