Mercurial > gemma
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, |