comparison cmd/isoareas/main.go @ 4550:aa2d0006e742 iso-areas

Write iso lines between classes to SVG for debugging.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 30 Sep 2019 16:34:55 +0200
parents 9c65cef72753
children b5b23b6d76f1
comparison
equal deleted inserted replaced
4549:9c65cef72753 4550:aa2d0006e742
21 "log" 21 "log"
22 "math" 22 "math"
23 "math/bits" 23 "math/bits"
24 "math/rand" 24 "math/rand"
25 "os" 25 "os"
26 "runtime"
26 "strconv" 27 "strconv"
27 "strings" 28 "strings"
29 "sync"
28 "time" 30 "time"
29 31
30 svg "github.com/ajstarks/svgo" 32 svg "github.com/ajstarks/svgo"
31 33
32 "gemma.intevation.de/gemma/pkg/octree" 34 "gemma.intevation.de/gemma/pkg/octree"
97 func glue(a, b octree.LineStringZ) octree.LineStringZ { 99 func glue(a, b octree.LineStringZ) octree.LineStringZ {
98 100
99 a1, a2 := a[0], a[len(a)-1] 101 a1, a2 := a[0], a[len(a)-1]
100 b1, b2 := b[0], b[len(b)-1] 102 b1, b2 := b[0], b[len(b)-1]
101 103
102 if a1.EpsEquals(b2) { 104 if a1.EpsEquals2D(b2) {
103 c := make(octree.LineStringZ, len(a)-1+len(b)) 105 c := make(octree.LineStringZ, len(a)-1+len(b))
104 copy(c, b) 106 copy(c, b)
105 copy(c[len(b):], a[1:]) 107 copy(c[len(b):], a[1:])
106 return c 108 return c
107 } 109 }
108 110
109 if a2.EpsEquals(b1) { 111 if a2.EpsEquals2D(b1) {
110 c := make(octree.LineStringZ, len(a)-1+len(b)) 112 c := make(octree.LineStringZ, len(a)-1+len(b))
111 copy(c, a) 113 copy(c, a)
112 copy(c[len(a):], b[1:]) 114 copy(c[len(a):], b[1:])
113 return c 115 return c
114 } 116 }
115 117
116 if a1.EpsEquals(b1) { 118 if a1.EpsEquals2D(b1) {
117 c := make(octree.LineStringZ, len(a)-1+len(b)) 119 c := make(octree.LineStringZ, len(a)-1+len(b))
118 copy(c, b) 120 copy(c, b)
119 c[:len(b)].Reverse() 121 c[:len(b)].Reverse()
120 copy(c[len(b):], a[1:]) 122 copy(c[len(b):], a[1:])
121 return c 123 return c
122 } 124 }
123 125
124 if a2.EpsEquals(b2) { 126 if a2.EpsEquals2D(b2) {
125 c := make(octree.LineStringZ, len(a)-1+len(b)) 127 c := make(octree.LineStringZ, len(a)-1+len(b))
126 copy(c, a) 128 copy(c, a)
127 copy(c[len(a):], b[:len(b)-1]) 129 copy(c[len(a):], b[:len(b)-1])
128 c[len(a):].Reverse() 130 c[len(a):].Reverse()
129 return c 131 return c
203 } 205 }
204 log.Printf("unique arcs: before: %d after %d\n", 206 log.Printf("unique arcs: before: %d after %d\n",
205 before, len(unique)) 207 before, len(unique))
206 } 208 }
207 209
208 func intersectTriangles(tri *octree.Triangulation, heights []float64) [][]octree.LineStringZ { 210 func intersectTriangles(
211 tri *octree.Triangulation,
212 heights []float64,
213 min, max octree.Vertex,
214 ) [][]octree.LineStringZ {
209 215
210 cuts := make([][]indexedArc, len(heights)) 216 cuts := make([][]indexedArc, len(heights))
211 217
212 classes := make([][]int32, len(heights)+1) 218 classes := make([][]int32, len(heights)+1)
213 219
257 263
258 for _, c := range cuts { 264 for _, c := range cuts {
259 connectArcs(tri, c, &arcs) 265 connectArcs(tri, c, &arcs)
260 } 266 }
261 267
268 func() {
269 out, err := os.Create("arcs.svg")
270 if err != nil {
271 log.Printf("err: %v\n", err)
272 return
273 }
274 defer func() {
275 out.Close()
276 log.Println("writing arcs done")
277 }()
278
279 buf := bufio.NewWriter(out)
280
281 dumpArcsSVG(
282 buf,
283 min, max,
284 cuts,
285 arcs)
286 buf.Flush()
287 }()
288
262 result := make([][]octree.LineStringZ, len(classes)) 289 result := make([][]octree.LineStringZ, len(classes))
263 290
291 jobs := make(chan int)
292
293 var wg sync.WaitGroup
294
295 worker := func() {
296 defer wg.Done()
297
298 pb := polygonBuilder{open: list.New()}
299
300 for i := range jobs {
301 usedArcs := map[int32]struct{}{}
302 var dupes int
303 var isolated, inside, found int
304 c := classes[i]
305
306 allInClass:
307 for _, idx := range c {
308 ns := neighbors(tri, idx)
309 mask := where(ns, c)
310 switch bits.OnesCount8(mask) {
311 case 3:
312 // Totally insides do not contribute to the geometries.
313 inside++
314 continue allInClass
315 case 0:
316 // Isolated are areas w/o connections to the rest.
317 isolated++
318 ti := tri.Triangles[idx*3 : idx*3+3]
319 pb.addTriangle(
320 tri.Points[ti[0]],
321 tri.Points[ti[1]],
322 tri.Points[ti[2]])
323 continue allInClass
324 default:
325 ti := tri.Triangles[idx*3 : idx*3+3]
326 for j := 0; j < 3; j++ {
327 if (mask & (1 << j)) == 0 {
328
329 var curr octree.LineStringZ
330
331 for l := i - 1; l <= i; l++ {
332 if l < 0 || l >= len(cuts) {
333 continue
334 }
335 arcIdx := findArc(ns[j]/3, cuts[l])
336 if arcIdx == -1 {
337 continue
338 }
339 found++
340 aIdx := cuts[l][arcIdx].arc
341 if _, already := usedArcs[aIdx]; already {
342 dupes++
343 continue
344 }
345 usedArcs[aIdx] = struct{}{}
346
347 curr = arcs[aIdx]
348 break
349 }
350
351 if curr == nil {
352 k := (j + 1) % 3
353 front := tri.Points[ti[j]]
354 back := tri.Points[ti[k]]
355 curr = octree.LineStringZ{front, back}
356 }
357
358 segment:
359 for e := pb.open.Front(); e != nil; e = e.Next() {
360 line := e.Value.(octree.LineStringZ)
361 if nline := glue(curr, line); nline != nil {
362 curr = nline
363 pb.open.Remove(e)
364 goto segment
365 }
366 } // all open
367
368 // check if closed
369 if curr[0].EpsEquals2D(curr[len(curr)-1]) {
370 if !curr.CCW() {
371 curr.Reverse()
372 }
373 pb.polygons = append(pb.polygons, curr)
374 } else {
375 pb.open.PushFront(curr)
376 }
377 } // for all border parts
378 }
379 }
380 }
381
382 log.Printf("\t%d: inside: %d / isolated: %d open: %d closed: %d dupes: %d found: %d\n",
383 i, inside, isolated, pb.open.Len(), len(pb.polygons), dupes, found)
384 /*
385 for e := pb.open.Front(); e != nil; e = e.Next() {
386 line := e.Value.(octree.LineStringZ)
387 if !line.CCW() {
388 line.Reverse()
389 }
390 pb.polygons = append(pb.polygons, line)
391 }
392 */
393
394 result[i] = pb.polygons
395 pb.reset()
396 }
397 }
398
399 for n := runtime.NumCPU(); n >= 0; n-- {
400 wg.Add(1)
401 go worker()
402 }
403
264 log.Println("inside classes:") 404 log.Println("inside classes:")
265 for i, c := range classes { 405 for i := range classes {
266 406 jobs <- i
267 pb := polygonBuilder{open: list.New()} 407 }
268 408 close(jobs)
269 usedArcs := map[int32]struct{}{} 409
270 var dupes int 410 wg.Wait()
271
272 var isolated, inside, found int
273 allInClass:
274 for _, idx := range c {
275 ns := neighbors(tri, idx)
276 mask := where(ns, c)
277 switch bits.OnesCount8(mask) {
278 case 3:
279 // Totally insides do not contribute to the geometries.
280 inside++
281 continue allInClass
282 case 0:
283 // Isolated are areas w/o connections to the rest.
284 isolated++
285 ti := tri.Triangles[idx*3 : idx*3+3]
286 pb.addTriangle(
287 tri.Points[ti[0]],
288 tri.Points[ti[1]],
289 tri.Points[ti[2]])
290 continue allInClass
291 default:
292 ti := tri.Triangles[idx*3 : idx*3+3]
293 for j := 0; j < 3; j++ {
294 if (mask & (1 << j)) == 0 {
295
296 var curr octree.LineStringZ
297
298 for l := i - 1; l <= i; l++ {
299 if l < 0 || l >= len(cuts) {
300 continue
301 }
302 arcIdx := findArc(ns[j]/3, cuts[l])
303 if arcIdx == -1 {
304 continue
305 }
306 found++
307 aIdx := cuts[l][arcIdx].arc
308 if _, already := usedArcs[aIdx]; already {
309 dupes++
310 continue
311 }
312 usedArcs[aIdx] = struct{}{}
313
314 curr = arcs[aIdx]
315 break
316 }
317
318 if curr == nil {
319 k := (j + 1) % 3
320 front := tri.Points[ti[j]]
321 back := tri.Points[ti[k]]
322 curr = octree.LineStringZ{front, back}
323 }
324
325 segment:
326 for e := pb.open.Front(); e != nil; e = e.Next() {
327 line := e.Value.(octree.LineStringZ)
328 if nline := glue(curr, line); nline != nil {
329 curr = nline
330 pb.open.Remove(e)
331 goto segment
332 }
333 } // all open
334
335 // check if closed
336 if curr[0].EpsEquals(curr[len(curr)-1]) {
337 if !curr.CCW() {
338 curr.Reverse()
339 }
340 pb.polygons = append(pb.polygons, curr)
341 } else {
342 pb.open.PushFront(curr)
343 }
344 } // for all border parts
345 }
346 }
347 }
348
349 log.Printf("\t%d: inside: %d / isolated: %d open: %d closed: %d dupes: %d found: %d\n",
350 i, inside, isolated, pb.open.Len(), len(pb.polygons), dupes, found)
351
352 /*
353 for e := pb.open.Front(); e != nil; e = e.Next() {
354 line := e.Value.(octree.LineStringZ)
355 if !line.CCW() {
356 line.Reverse()
357 }
358 pb.polygons = append(pb.polygons, line)
359 }
360 */
361
362 result[i] = pb.polygons
363 }
364 411
365 log.Println("cuts:") 412 log.Println("cuts:")
366 for i, c := range cuts { 413 for i, c := range cuts {
367 log.Printf("\t%.3f: %d\n", heights[i], len(c)) 414 log.Printf("\t%.3f: %d\n", heights[i], len(c))
368 } 415 }
380 } 427 }
381 428
382 func (pb *polygonBuilder) addTriangle(v0, v1, v2 octree.Vertex) { 429 func (pb *polygonBuilder) addTriangle(v0, v1, v2 octree.Vertex) {
383 polygon := octree.LineStringZ{v0, v1, v2, v0} 430 polygon := octree.LineStringZ{v0, v1, v2, v0}
384 pb.polygons = append(pb.polygons, polygon) 431 pb.polygons = append(pb.polygons, polygon)
432 }
433
434 func (pb *polygonBuilder) reset() {
435 pb.polygons = pb.polygons[:0]
436 pb.open.Init()
385 } 437 }
386 438
387 func neighbors(t *octree.Triangulation, idx int32) []int32 { 439 func neighbors(t *octree.Triangulation, idx int32) []int32 {
388 idx *= 3 440 idx *= 3
389 return t.Halfedges[idx : idx+3] 441 return t.Halfedges[idx : idx+3]
500 canvas.End() 552 canvas.End()
501 553
502 return nil 554 return nil
503 } 555 }
504 556
557 func dumpArcsSVG(
558 w io.Writer,
559 min, max octree.Vertex,
560 cuts [][]indexedArc,
561 arcs []octree.LineStringZ,
562 ) (err error) {
563
564 ratio := (max.X - min.X) / (max.Y - min.Y)
565
566 log.Printf("ratio: %.2f\n", ratio)
567
568 const width = 50000
569 height := int(math.Ceil(width * ratio))
570
571 px := linear(min.X, 0, max.X, width)
572 py := linear(min.Y, float64(height), max.Y, 0)
573
574 out := bufio.NewWriter(w)
575 defer func() { err = out.Flush() }()
576
577 canvas := svg.New(out)
578 canvas.Start(width, height)
579
580 rnd := rand.New(rand.NewSource(42))
581
582 for _, cut := range cuts {
583
584 usedArcs := map[int32]struct{}{}
585
586 r := byte(rnd.Intn(256))
587 g := byte(rnd.Intn(256))
588 b := byte(rnd.Intn(256))
589
590 style := fmt.Sprintf("fill:none;stroke:#%02x%02x%02x", r, g, b)
591
592 for i := range cut {
593 idx := cut[i].arc
594 if _, already := usedArcs[idx]; already {
595 continue
596 }
597 usedArcs[idx] = struct{}{}
598 arc := arcs[idx]
599 x := make([]int, len(arc))
600 y := make([]int, len(arc))
601 for i, p := range arc {
602 x[i] = int(math.Round(px(p.X)))
603 y[i] = int(math.Round(py(p.Y)))
604 }
605
606 canvas.Polyline(x, y, style)
607 }
608 }
609
610 canvas.End()
611
612 return nil
613 }
614
505 func main() { 615 func main() {
506 616
507 heights, err := octree.ParseClassBreaks(classBreaks) 617 heights, err := octree.ParseClassBreaks(classBreaks)
508 check(err) 618 check(err)
509 log.Printf("num classes: %d\n", len(heights)) 619 log.Printf("num classes: %d\n", len(heights))
527 log.Printf("triangulation took %v\n", time.Since(start)) 637 log.Printf("triangulation took %v\n", time.Since(start))
528 638
529 log.Printf("number of triangles: %d\n", len(tri.Triangles)/3) 639 log.Printf("number of triangles: %d\n", len(tri.Triangles)/3)
530 640
531 start = time.Now() 641 start = time.Now()
532 polygons := intersectTriangles(tri, heights) 642 polygons := intersectTriangles(tri, heights, min, max)
533 log.Printf("intersecting triangles took %v.\n", time.Since(start)) 643 log.Printf("intersecting triangles took %v.\n", time.Since(start))
534 644
535 check(dumpPolygonsSVG(os.Stdout, min, max, polygons)) 645 check(dumpPolygonsSVG(os.Stdout, min, max, polygons))
536 } 646 }