Mercurial > gemma
comparison pkg/octree/raster.go @ 4768:a2f16bbcc846 direct-diff
Morph differences: Directly raster A and subtract B as a raster.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 21 Oct 2019 02:01:56 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
4767:f66e5b2fa894 | 4768:a2f16bbcc846 |
---|---|
1 // This is Free Software under GNU Affero General Public License v >= 3.0 | |
2 // without warranty, see README.md and license for details. | |
3 // | |
4 // SPDX-License-Identifier: AGPL-3.0-or-later | |
5 // License-Filename: LICENSES/AGPL-3.0.txt | |
6 // | |
7 // Copyright (C) 2019 by via donau | |
8 // – Österreichische Wasserstraßen-Gesellschaft mbH | |
9 // Software engineering by Intevation GmbH | |
10 // | |
11 // Author(s): | |
12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de> | |
13 | |
14 package octree | |
15 | |
16 import ( | |
17 "log" | |
18 "math" | |
19 "runtime" | |
20 "sync" | |
21 "time" | |
22 | |
23 "gemma.intevation.de/gemma/pkg/common" | |
24 "gemma.intevation.de/gemma/pkg/wkb" | |
25 "github.com/fogleman/contourmap" | |
26 ) | |
27 | |
28 type Raster struct { | |
29 BBox Box2D | |
30 CellSize float64 | |
31 XCells int | |
32 YCells int | |
33 Cells []float64 | |
34 } | |
35 | |
36 const noData = -math.MaxFloat64 | |
37 | |
38 func NewRaster(bbox Box2D, cellSize float64) *Raster { | |
39 | |
40 width, height := bbox.Size() | |
41 | |
42 log.Printf("info raster extent: %.2f / %.2f", width, height) | |
43 | |
44 xCells := int(math.Ceil(width / cellSize)) | |
45 yCells := int(math.Ceil(height / cellSize)) | |
46 | |
47 log.Printf("info raster size: %d / %d\n", xCells, yCells) | |
48 | |
49 size := (xCells + 2) * (yCells + 2) | |
50 cells := make([]float64, size) | |
51 for i := range cells { | |
52 cells[i] = noData | |
53 } | |
54 return &Raster{ | |
55 BBox: bbox, | |
56 CellSize: cellSize, | |
57 XCells: xCells, | |
58 YCells: yCells, | |
59 Cells: cells, | |
60 } | |
61 } | |
62 | |
63 func (r *Raster) Rasterize(eval func(float64, float64) (float64, bool)) { | |
64 var wg sync.WaitGroup | |
65 | |
66 rows := make(chan int) | |
67 | |
68 rasterRow := func() { | |
69 defer wg.Done() | |
70 quat := 0.25 * r.CellSize | |
71 for i := range rows { | |
72 pos := (i+1)*(r.XCells+2) + 1 | |
73 row := r.Cells[pos : pos+r.XCells] | |
74 py := r.BBox.Y1 + float64(i)*r.CellSize + r.CellSize/2 | |
75 px := r.BBox.X1 + r.CellSize/2 | |
76 y1 := py - quat | |
77 y2 := py + quat | |
78 for j := range row { | |
79 var n int | |
80 var sum float64 | |
81 | |
82 if v, ok := eval(px-quat, y1); ok { | |
83 sum = v | |
84 n = 1 | |
85 } | |
86 if v, ok := eval(px-quat, y2); ok { | |
87 sum += v | |
88 n++ | |
89 } | |
90 if v, ok := eval(px+quat, y1); ok { | |
91 sum += v | |
92 n++ | |
93 } | |
94 if v, ok := eval(px+quat, y2); ok { | |
95 sum += v | |
96 n++ | |
97 } | |
98 | |
99 if n > 0 { | |
100 row[j] = sum / float64(n) | |
101 } | |
102 px += r.CellSize | |
103 } | |
104 } | |
105 } | |
106 | |
107 for n := runtime.NumCPU(); n >= 1; n-- { | |
108 wg.Add(1) | |
109 go rasterRow() | |
110 } | |
111 | |
112 for i := 0; i < r.YCells; i++ { | |
113 rows <- i | |
114 } | |
115 close(rows) | |
116 } | |
117 | |
118 func (r *Raster) Diff(eval func(float64, float64) (float64, bool)) { | |
119 var wg sync.WaitGroup | |
120 | |
121 rows := make(chan int) | |
122 | |
123 rasterRow := func() { | |
124 defer wg.Done() | |
125 quat := 0.25 * r.CellSize | |
126 for i := range rows { | |
127 pos := (i+1)*(r.XCells+2) + 1 | |
128 row := r.Cells[pos : pos+r.XCells] | |
129 py := r.BBox.Y1 + float64(i)*r.CellSize + r.CellSize/2 | |
130 px := r.BBox.X1 + r.CellSize/2 | |
131 y1 := py - quat | |
132 y2 := py + quat | |
133 for j, old := range row { | |
134 // only diff where values | |
135 if old == noData { | |
136 px += r.CellSize | |
137 continue | |
138 } | |
139 var n int | |
140 var sum float64 | |
141 | |
142 if v, ok := eval(px-quat, y1); ok { | |
143 sum = v | |
144 n = 1 | |
145 } | |
146 if v, ok := eval(px-quat, y2); ok { | |
147 sum += v | |
148 n++ | |
149 } | |
150 if v, ok := eval(px+quat, y1); ok { | |
151 sum += v | |
152 n++ | |
153 } | |
154 if v, ok := eval(px+quat, y2); ok { | |
155 sum += v | |
156 n++ | |
157 } | |
158 | |
159 if n > 0 { | |
160 row[j] -= sum / float64(n) | |
161 } else { | |
162 row[j] = noData | |
163 } | |
164 | |
165 px += r.CellSize | |
166 } | |
167 } | |
168 } | |
169 | |
170 for n := runtime.NumCPU(); n >= 1; n-- { | |
171 wg.Add(1) | |
172 go rasterRow() | |
173 } | |
174 | |
175 for i := 0; i < r.YCells; i++ { | |
176 rows <- i | |
177 } | |
178 close(rows) | |
179 } | |
180 | |
181 func (r *Raster) ZExtent() (float64, float64, bool) { | |
182 min, max := math.MaxFloat64, -math.MaxFloat64 | |
183 for _, v := range r.Cells { | |
184 if v == noData { | |
185 continue | |
186 } | |
187 if v < min { | |
188 min = v | |
189 } | |
190 if v > max { | |
191 max = v | |
192 } | |
193 } | |
194 return min, max, min != math.MaxFloat64 | |
195 } | |
196 | |
197 func (r *Raster) Trace(heights []float64) []wkb.MultiPolygonGeom { | |
198 start := time.Now() | |
199 | |
200 tracer := contourmap.FromFloat64s(r.XCells+2, r.YCells+2, r.Cells) | |
201 | |
202 areas := make([]wkb.MultiPolygonGeom, len(heights)) | |
203 | |
204 reprojX := common.Linear(0.5, r.BBox.X1, 1.5, r.BBox.X1+r.CellSize) | |
205 reprojY := common.Linear(0.5, r.BBox.Y1, 1.5, r.BBox.Y1+r.CellSize) | |
206 | |
207 var wg sync.WaitGroup | |
208 | |
209 cnts := make(chan int) | |
210 | |
211 doContours := func() { | |
212 defer wg.Done() | |
213 for hIdx := range cnts { | |
214 if c := tracer.Contours(heights[hIdx]); len(c) > 0 { | |
215 areas[hIdx] = buildMultipolygon(c, reprojX, reprojY) | |
216 } | |
217 } | |
218 } | |
219 | |
220 for n := runtime.NumCPU(); n >= 1; n-- { | |
221 wg.Add(1) | |
222 go doContours() | |
223 } | |
224 | |
225 for i := range heights { | |
226 cnts <- i | |
227 } | |
228 close(cnts) | |
229 | |
230 wg.Wait() | |
231 log.Printf("info: Tracing areas took %v\n", time.Since(start)) | |
232 | |
233 return areas | |
234 } | |
235 | |
236 type contour []contourmap.Point | |
237 | |
238 type bboxNode struct { | |
239 box Box2D | |
240 cnt contour | |
241 children []*bboxNode | |
242 } | |
243 | |
244 func (cnt contour) contains(o contour) bool { | |
245 return contains(cnt, o[0].X, o[0].Y) || | |
246 contains(cnt, o[len(o)/2].X, o[len(o)/2].Y) | |
247 } | |
248 | |
249 func (cnt contour) length() int { | |
250 return len(cnt) | |
251 } | |
252 | |
253 func (cnt contour) point(i int) (float64, float64) { | |
254 return cnt[i].X, cnt[i].Y | |
255 } | |
256 | |
257 func (cnt contour) bbox() Box2D { | |
258 minX, minY := math.MaxFloat64, math.MaxFloat64 | |
259 maxX, maxY := -math.MaxFloat64, -math.MaxFloat64 | |
260 | |
261 for _, p := range cnt { | |
262 if p.X < minX { | |
263 minX = p.X | |
264 } | |
265 if p.X > maxX { | |
266 maxX = p.X | |
267 } | |
268 if p.Y < minY { | |
269 minY = p.Y | |
270 } | |
271 if p.Y > maxY { | |
272 maxY = p.Y | |
273 } | |
274 } | |
275 return Box2D{X1: minX, X2: maxX, Y1: minY, Y2: maxY} | |
276 } | |
277 | |
278 func (bn *bboxNode) insert(cnt contour, box Box2D) { | |
279 // check if children are inside new | |
280 var nr *bboxNode | |
281 | |
282 for i, r := range bn.children { | |
283 if r.box.Inside(box) && cnt.contains(r.cnt) { | |
284 if nr == nil { | |
285 nr = &bboxNode{box: box, cnt: cnt} | |
286 } | |
287 nr.children = append(nr.children, r) | |
288 bn.children[i] = nil | |
289 } | |
290 } | |
291 | |
292 // we have a new child | |
293 if nr != nil { | |
294 // compact the list | |
295 for i := len(bn.children) - 1; i >= 0; i-- { | |
296 if bn.children[i] == nil { | |
297 if i < len(bn.children)-1 { | |
298 copy(bn.children[i:], bn.children[i+1:]) | |
299 } | |
300 bn.children[len(bn.children)-1] = nil | |
301 bn.children = bn.children[:len(bn.children)-1] | |
302 } | |
303 } | |
304 bn.children = append(bn.children, nr) | |
305 return | |
306 } | |
307 | |
308 // check if new is inside an old | |
309 for _, r := range bn.children { | |
310 if box.Inside(r.box) && r.cnt.contains(cnt) { | |
311 r.insert(cnt, box) | |
312 return | |
313 } | |
314 } | |
315 | |
316 // its a new child node. | |
317 nr = &bboxNode{box: box, cnt: cnt} | |
318 bn.children = append(bn.children, nr) | |
319 } | |
320 | |
321 func (bn *bboxNode) insertRoot(cnt contour) { | |
322 bn.insert(cnt, cnt.bbox()) | |
323 } | |
324 | |
325 type bboxOutFunc func(contour, []contour) | |
326 | |
327 func (bn *bboxNode) generate(out bboxOutFunc) { | |
328 | |
329 var grands []*bboxNode | |
330 | |
331 holes := make([]contour, len(bn.children)) | |
332 | |
333 for i, ch := range bn.children { | |
334 holes[i] = ch.cnt | |
335 grands = append(grands, ch.children...) | |
336 } | |
337 out(bn.cnt, holes) | |
338 | |
339 // the grand children are new polygons. | |
340 for _, grand := range grands { | |
341 grand.generate(out) | |
342 } | |
343 } | |
344 | |
345 func (bn *bboxNode) generateRoot(out bboxOutFunc) { | |
346 for _, r := range bn.children { | |
347 r.generate(out) | |
348 } | |
349 } | |
350 | |
351 func buildMultipolygon( | |
352 cnts []contourmap.Contour, | |
353 reprojX, reprojY func(float64) float64, | |
354 ) wkb.MultiPolygonGeom { | |
355 | |
356 var forest bboxNode | |
357 | |
358 for _, cnt := range cnts { | |
359 forest.insertRoot(contour(cnt)) | |
360 } | |
361 | |
362 //log.Printf("cnts: %d roots: %d\n", len(cnts), len(bf.roots)) | |
363 | |
364 var mp wkb.MultiPolygonGeom | |
365 | |
366 out := func(sh contour, hls []contour) { | |
367 | |
368 polygon := make(wkb.PolygonGeom, 1+len(hls)) | |
369 | |
370 // Handle shell | |
371 shell := make(wkb.LinearRingGeom, len(sh)) | |
372 for j, pt := range sh { | |
373 shell[j] = wkb.PointGeom{ | |
374 X: reprojX(pt.X), | |
375 Y: reprojY(pt.Y), | |
376 } | |
377 } | |
378 if shell.CCW() { | |
379 shell.Reverse() | |
380 } | |
381 polygon[0] = shell | |
382 | |
383 // handle holes | |
384 for i, hl := range hls { | |
385 hole := make(wkb.LinearRingGeom, len(hl)) | |
386 for j, pt := range hl { | |
387 hole[j] = wkb.PointGeom{ | |
388 X: reprojX(pt.X), | |
389 Y: reprojY(pt.Y), | |
390 } | |
391 } | |
392 if !hole.CCW() { | |
393 hole.Reverse() | |
394 } | |
395 polygon[1+i] = hole | |
396 } | |
397 | |
398 mp = append(mp, polygon) | |
399 } | |
400 | |
401 forest.generateRoot(out) | |
402 | |
403 return mp | |
404 } |