comparison cmd/isoareas/main.go @ 4552:7ed5a4a94efc iso-areas

Used fogleman's contourmap as a tracing alternative.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Tue, 01 Oct 2019 13:18:16 +0200
parents b5b23b6d76f1
children 4c476d65d1bb
comparison
equal deleted inserted replaced
4551:b5b23b6d76f1 4552:7ed5a4a94efc
13 13
14 package main 14 package main
15 15
16 import ( 16 import (
17 "bufio" 17 "bufio"
18 "flag"
18 "fmt" 19 "fmt"
19 "io" 20 "io"
20 "log" 21 "log"
21 "math" 22 "math"
22 "math/rand" 23 "math/rand"
23 "os" 24 "os"
25 "runtime"
24 "strconv" 26 "strconv"
25 "strings" 27 "strings"
28 "sync"
26 "time" 29 "time"
27 30
28 svg "github.com/ajstarks/svgo" 31 svg "github.com/ajstarks/svgo"
32 "github.com/fogleman/contourmap"
29 33
30 "gemma.intevation.de/gemma/pkg/octree" 34 "gemma.intevation.de/gemma/pkg/octree"
31 ) 35 )
32 36
33 const classBreaks = `1:#ff00dd,1.5,1.7,1.9,2.1,2.3,2.5:#f25f20,2.7,2.9,3.1:#f7e40e,3.3,3.5,4:#8ad51a,4.5,5,5.5,6,6.5,7:#1414ff` 37 const classBreaks = `1:#ff00dd,1.5,1.7,1.9,2.1,2.3,2.5:#f25f20,2.7,2.9,3.1:#f7e40e,3.3,3.5,4:#8ad51a,4.5,5,5.5,6,6.5,7:#1414ff`
62 } 66 }
63 points = append(points, octree.Vertex{X: x, Y: y, Z: z}) 67 points = append(points, octree.Vertex{X: x, Y: y, Z: z})
64 } 68 }
65 69
66 return points, nil 70 return points, nil
67 }
68
69 func minMax(points []octree.Vertex) (octree.Vertex, octree.Vertex) {
70 if len(points) == 0 {
71 return octree.Vertex{}, octree.Vertex{}
72 }
73
74 min, max := points[0], points[0]
75
76 for _, v := range points {
77 min.Minimize(v)
78 max.Maximize(v)
79 }
80
81 return min, max
82 } 71 }
83 72
84 func check(err error) { 73 func check(err error) {
85 if err != nil { 74 if err != nil {
86 log.Fatalf("error: %v\n", err) 75 log.Fatalf("error: %v\n", err)
104 a := (y1 - y2) / (x1 - x2) 93 a := (y1 - y2) / (x1 - x2)
105 b := y1 - x1*a 94 b := y1 - x1*a
106 return func(x float64) float64 { 95 return func(x float64) float64 {
107 return x*a + b 96 return x*a + b
108 } 97 }
98 }
99
100 func dumpContoursSVG(
101 w io.Writer,
102 contours [][]contourmap.Contour,
103 ) (err error) {
104
105 var (
106 minX = math.MaxFloat64
107 minY = math.MaxFloat64
108 maxX = -math.MaxFloat64
109 maxY = -math.MaxFloat64
110 )
111
112 for _, c := range contours {
113 for _, r := range c {
114 for _, p := range r {
115 if p.X < minX {
116 minX = p.X
117 }
118 if p.Y < minY {
119 minY = p.Y
120 }
121 if p.X > maxX {
122 maxX = p.X
123 }
124 if p.Y > maxY {
125 maxY = p.Y
126 }
127 }
128 }
129 }
130 ratio := (maxX - minX) / (maxY - minY)
131
132 log.Printf("ratio: %.2f\n", ratio)
133
134 const width = 50000
135 height := int(math.Ceil(width * ratio))
136
137 px := linear(minX, 0, maxX, width)
138 py := linear(minY, float64(height), maxY, 0)
139
140 out := bufio.NewWriter(w)
141 defer func() { err = out.Flush() }()
142
143 canvas := svg.New(out)
144 canvas.Start(width, height)
145
146 rnd := rand.New(rand.NewSource(42))
147
148 for _, c := range contours {
149
150 r := byte(rnd.Intn(256))
151 g := byte(rnd.Intn(256))
152 b := byte(rnd.Intn(256))
153 style := fmt.Sprintf("fill:#%02x%02x%02x", r, g, b)
154
155 for _, r := range c {
156 x := make([]int, len(r))
157 y := make([]int, len(r))
158 for i, p := range r {
159 x[i] = int(math.Round(px(p.X)))
160 y[i] = int(math.Round(py(p.Y)))
161 }
162
163 canvas.Polygon(x, y, style)
164 }
165 }
166
167 canvas.End()
168 return
109 } 169 }
110 170
111 func dumpPolygonsSVG( 171 func dumpPolygonsSVG(
112 w io.Writer, 172 w io.Writer,
113 min, max octree.Vertex, 173 min, max octree.Vertex,
216 return nil 276 return nil
217 } 277 }
218 278
219 func main() { 279 func main() {
220 280
281 cellSize := float64(0.5)
282
283 flag.Float64Var(&cellSize, "cellsize", 0.5, "width/height of raster cell [m]")
284
285 flag.Parse()
286
221 heights, err := octree.ParseClassBreaks(classBreaks) 287 heights, err := octree.ParseClassBreaks(classBreaks)
222 check(err) 288 check(err)
223 log.Printf("num classes: %d\n", len(heights)) 289 log.Printf("num classes: %d\n", len(heights))
224 290
225 start := time.Now() 291 start := time.Now()
227 check(err) 293 check(err)
228 log.Printf("loading took %v\n", time.Since(start)) 294 log.Printf("loading took %v\n", time.Since(start))
229 295
230 log.Printf("num vertices: %d\n", len(xyz)) 296 log.Printf("num vertices: %d\n", len(xyz))
231 297
232 min, max := minMax(xyz) 298 start = time.Now()
299 tri, err := octree.Triangulate(xyz)
300 check(err)
301 log.Printf("triangulation took %v\n", time.Since(start))
302 tooLongEdge := tri.EstimateTooLong()
303 log.Printf("Eliminate triangles with edges longer than %.2f meters.", tooLongEdge)
304
305 start = time.Now()
306 polygon, removed := tri.ConcaveHull(tooLongEdge)
307
308 polygonArea := polygon.Area()
309 if polygonArea < 0.0 { // counter clockwise
310 polygonArea = -polygonArea
311 polygon.Reverse()
312 }
313
314 var clippingPolygon octree.Polygon
315 clippingPolygon.FromLineStringZ(polygon)
316 clippingPolygon.Indexify()
317
318 tin := tri.Tin()
319 // tin.EPSG = epsg
320
321 var str octree.STRTree
322 str.Build(tin)
323
324 removed = str.Clip(&clippingPolygon)
325
326 builder := octree.NewBuilder(tin)
327 builder.Build(removed)
328
329 tree := builder.Tree()
330
331 min, max := tin.Min, tin.Max
233 332
234 log.Printf("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)\n", 333 log.Printf("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)\n",
235 min.X, min.Y, min.Z, 334 min.X, min.Y, min.Z,
236 max.X, max.Y, max.Z) 335 max.X, max.Y, max.Z)
237 336
238 start = time.Now() 337 width := max.X - min.X
239 tri, err := octree.Triangulate(xyz) 338 height := max.Y - min.Y
240 check(err) 339
241 log.Printf("triangulation took %v\n", time.Since(start)) 340 log.Printf("width/height: %.2f / %.2f\n", width, height)
242 341
243 log.Printf("number of triangles: %d\n", len(tri.Triangles)/3) 342 xcells := int(math.Ceil(width / cellSize))
244 343 ycells := int(math.Ceil(height / cellSize))
245 start = time.Now() 344
246 polygons := intersectTriangles(tri, heights, min, max) 345 log.Printf("raster size: (%d, %d)\n", xcells, ycells)
247 log.Printf("intersecting triangles took %v.\n", time.Since(start)) 346
248 347 raster := make([]float64, xcells*ycells)
249 check(dumpPolygonsSVG(os.Stdout, min, max, polygons)) 348
250 } 349 const closed = -math.MaxFloat64
350 for i := range raster {
351 raster[i] = closed
352 }
353
354 start = time.Now()
355
356 var wg sync.WaitGroup
357
358 rows := make(chan int)
359
360 rasterRow := func() {
361 defer wg.Done()
362 for i := range rows {
363 pos := i * xcells
364 row := raster[pos : pos+xcells]
365 //log.Printf("len: %d\n", len(row))
366 py := min.Y + float64(i)*cellSize + cellSize/2
367 px := min.X + cellSize/2
368 for j := range row {
369 v, ok := tree.Value(px, py)
370 if ok {
371 row[j] = v
372 }
373 px += cellSize
374 }
375 }
376 }
377
378 start = time.Now()
379
380 for n := runtime.NumCPU(); n >= 1; n-- {
381 wg.Add(1)
382 go rasterRow()
383 }
384
385 for i := 0; i < ycells; i++ {
386 rows <- i
387 }
388 close(rows)
389
390 wg.Wait()
391
392 log.Printf("Rasterizing took %v.\n", time.Since(start))
393
394 start = time.Now()
395 cm := contourmap.FromFloat64s(xcells, ycells, raster).Closed()
396
397 start = time.Now()
398
399 contours := make([][]contourmap.Contour, len(heights))
400
401 cnts := make(chan int)
402
403 doContours := func() {
404 defer wg.Done()
405 for i := range cnts {
406 contours[i] = cm.Contours(heights[i])
407 }
408 }
409
410 for n := runtime.NumCPU(); n >= 1; n-- {
411 wg.Add(1)
412 go doContours()
413 }
414
415 for i := range heights {
416 cnts <- i
417 }
418 close(cnts)
419
420 wg.Wait()
421
422 log.Printf("Calculating contours took %v.\n", time.Since(start))
423 check(dumpContoursSVG(os.Stdout, contours))
424
425 /*
426
427 start = time.Now()
428 polygons := intersectTriangles(tri, heights, min, max)
429 log.Printf("intersecting triangles took %v.\n", time.Since(start))
430
431 check(dumpPolygonsSVG(os.Stdout, min, max, polygons))
432 */
433 }