view pkg/octree/contours.go @ 2529:45d51a49f191

SR import: Use own triangulation and clipping when importing sounding results.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 06 Mar 2019 17:51:58 +0100
parents c85b16db8a02
children 647a58ee9ae9
line wrap: on
line source

// 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 <sascha.teichmann@intevation.de>
//  * Tom Gottfried <tom.gottfried@intevation.de>

package octree

import (
	"runtime"
	"sync"
)

// ContourResult stores an calculated iso line for a given height.
// Is used as a future variable in the concurrent iso line calculation.
type ContourResult struct {
	Height float64
	Lines  MultiLineStringZ

	done bool
	mu   sync.Mutex
	cond *sync.Cond
}

// NewContourResult prepares a future variable to later hold
// the result of the iso line calculation.
func NewContourResult(height float64) *ContourResult {
	cr := ContourResult{Height: height}
	cr.cond = sync.NewCond(&cr.mu)
	return &cr
}

func (cr *ContourResult) wait() {
	cr.cond.L.Lock()
	for !cr.done {
		cr.cond.Wait()
	}
	cr.cond.L.Unlock()
}

func (cr *ContourResult) get() float64 {
	cr.cond.L.Lock()
	defer cr.cond.L.Unlock()
	return cr.Height
}

func (cr *ContourResult) set(lines MultiLineStringZ) {
	cr.cond.L.Lock()
	defer cr.cond.L.Unlock()
	cr.Lines = lines
	cr.done = true
	cr.cond.Signal()
}

// DoContours calculates the iso line for the given heights.
// This is done concurrently.
// It is guaranteed that the results are given to the store
// function in order of the original heights values.
func DoContours(tree *Tree, heights []float64, store func(*ContourResult)) {

	if len(heights) == 0 {
		return
	}

	contours := make([]*ContourResult, len(heights))

	for i, h := range heights {
		contours[i] = NewContourResult(h)
	}

	jobs := make(chan *ContourResult)

	var wg sync.WaitGroup
	for i, n := 0, runtime.NumCPU(); i < n; i++ {
		wg.Add(1)
		go processLevels(tree, jobs, &wg)
	}

	done := make(chan struct{})
	go func() {
		defer close(done)
		for _, cr := range contours {
			cr.wait()
			store(cr)
		}
	}()

	for _, cr := range contours {
		jobs <- cr
	}
	close(jobs)

	wg.Wait()
	<-done
}

func processLevels(
	tree *Tree,
	jobs <-chan *ContourResult,
	wg *sync.WaitGroup,
) {
	defer wg.Done()
	for cr := range jobs {
		var lines MultiLineStringZ
		h := cr.get()
		tree.Horizontal(h, func(t *Triangle) {
			line := t.IntersectHorizontal(h)
			if len(line) > 1 {
				lines = append(lines, line)
			}
		})
		cr.set(lines.Merge())
	}
}