Mercurial > gemma
comparison pkg/octree/areas.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 | 5c80a33edd44 |
children |
comparison
equal
deleted
inserted
replaced
4767:f66e5b2fa894 | 4768:a2f16bbcc846 |
---|---|
12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de> | 12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de> |
13 | 13 |
14 package octree | 14 package octree |
15 | 15 |
16 import ( | 16 import ( |
17 "log" | |
18 "math" | |
19 "runtime" | 17 "runtime" |
20 "sync" | 18 "sync" |
21 "time" | |
22 | |
23 "github.com/fogleman/contourmap" | |
24 | 19 |
25 "gemma.intevation.de/gemma/pkg/common" | 20 "gemma.intevation.de/gemma/pkg/common" |
26 "gemma.intevation.de/gemma/pkg/wkb" | |
27 ) | 21 ) |
28 | 22 |
29 func GenerateRandomVertices( | 23 func GenerateRandomVertices( |
30 n int, | 24 n int, |
31 min, max Vertex, | 25 min, max Vertex, |
90 close(jobs) | 84 close(jobs) |
91 wg.Wait() | 85 wg.Wait() |
92 close(out) | 86 close(out) |
93 <-done | 87 <-done |
94 } | 88 } |
95 | |
96 func TraceAreas( | |
97 heights []float64, | |
98 cellSize float64, | |
99 min, max Vertex, | |
100 eval func(float64, float64) (float64, bool), | |
101 ) []wkb.MultiPolygonGeom { | |
102 | |
103 width := max.X - min.X | |
104 height := max.Y - min.Y | |
105 | |
106 log.Printf("info: Width/Height: %.2f / %.2f\n", width, height) | |
107 | |
108 xcells := int(math.Ceil(width / cellSize)) | |
109 ycells := int(math.Ceil(height / cellSize)) | |
110 | |
111 log.Printf("info: Raster size: (%d, %d)\n", xcells, ycells) | |
112 | |
113 start := time.Now() | |
114 | |
115 // Add border for closing | |
116 raster := make([]float64, (xcells+2)*(ycells+2)) | |
117 | |
118 // prefill for no data | |
119 const nodata = -math.MaxFloat64 | |
120 for i := range raster { | |
121 raster[i] = nodata | |
122 } | |
123 | |
124 // rasterize the height model | |
125 | |
126 var wg sync.WaitGroup | |
127 | |
128 rows := make(chan int) | |
129 | |
130 rasterRow := func() { | |
131 defer wg.Done() | |
132 quat := 0.25 * cellSize | |
133 for i := range rows { | |
134 pos := (i+1)*(xcells+2) + 1 | |
135 row := raster[pos : pos+xcells] | |
136 py := min.Y + float64(i)*cellSize + cellSize/2 | |
137 px := min.X + cellSize/2 | |
138 y1 := py - quat | |
139 y2 := py + quat | |
140 for j := range row { | |
141 var n int | |
142 var sum float64 | |
143 | |
144 if v, ok := eval(px-quat, y1); ok { | |
145 sum = v | |
146 n = 1 | |
147 } | |
148 if v, ok := eval(px-quat, y2); ok { | |
149 sum += v | |
150 n++ | |
151 } | |
152 if v, ok := eval(px+quat, y1); ok { | |
153 sum += v | |
154 n++ | |
155 } | |
156 if v, ok := eval(px+quat, y2); ok { | |
157 sum += v | |
158 n++ | |
159 } | |
160 | |
161 if n > 0 { | |
162 row[j] = sum / float64(n) | |
163 } | |
164 px += cellSize | |
165 } | |
166 } | |
167 } | |
168 | |
169 for n := runtime.NumCPU(); n >= 1; n-- { | |
170 wg.Add(1) | |
171 go rasterRow() | |
172 } | |
173 | |
174 for i := 0; i < ycells; i++ { | |
175 rows <- i | |
176 } | |
177 close(rows) | |
178 | |
179 wg.Wait() | |
180 log.Printf("info: Rastering took %v\n", time.Since(start)) | |
181 | |
182 start = time.Now() | |
183 | |
184 tracer := contourmap.FromFloat64s(xcells+2, ycells+2, raster) | |
185 | |
186 areas := make([]wkb.MultiPolygonGeom, len(heights)) | |
187 | |
188 // TODO: Check if this correct! | |
189 reprojX := common.Linear(0.5, min.X, 1.5, min.X+cellSize) | |
190 reprojY := common.Linear(0.5, min.Y, 1.5, min.Y+cellSize) | |
191 | |
192 cnts := make(chan int) | |
193 | |
194 doContours := func() { | |
195 defer wg.Done() | |
196 for hIdx := range cnts { | |
197 c := tracer.Contours(heights[hIdx]) | |
198 | |
199 if len(c) == 0 { | |
200 continue | |
201 } | |
202 | |
203 areas[hIdx] = buildMultipolygon(c, reprojX, reprojY) | |
204 } | |
205 } | |
206 | |
207 for n := runtime.NumCPU(); n >= 1; n-- { | |
208 wg.Add(1) | |
209 go doContours() | |
210 } | |
211 | |
212 for i := range heights { | |
213 cnts <- i | |
214 } | |
215 close(cnts) | |
216 | |
217 wg.Wait() | |
218 log.Printf("info: Tracing areas took %v\n", time.Since(start)) | |
219 | |
220 return areas | |
221 } | |
222 | |
223 type contour []contourmap.Point | |
224 | |
225 type bboxNode struct { | |
226 box Box2D | |
227 cnt contour | |
228 children []*bboxNode | |
229 } | |
230 | |
231 func (cnt contour) contains(o contour) bool { | |
232 return contains(cnt, o[0].X, o[0].Y) || | |
233 contains(cnt, o[len(o)/2].X, o[len(o)/2].Y) | |
234 } | |
235 | |
236 func (cnt contour) length() int { | |
237 return len(cnt) | |
238 } | |
239 | |
240 func (cnt contour) point(i int) (float64, float64) { | |
241 return cnt[i].X, cnt[i].Y | |
242 } | |
243 | |
244 func (cnt contour) bbox() Box2D { | |
245 minX, minY := math.MaxFloat64, math.MaxFloat64 | |
246 maxX, maxY := -math.MaxFloat64, -math.MaxFloat64 | |
247 | |
248 for _, p := range cnt { | |
249 if p.X < minX { | |
250 minX = p.X | |
251 } | |
252 if p.X > maxX { | |
253 maxX = p.X | |
254 } | |
255 if p.Y < minY { | |
256 minY = p.Y | |
257 } | |
258 if p.Y > maxY { | |
259 maxY = p.Y | |
260 } | |
261 } | |
262 return Box2D{X1: minX, X2: maxX, Y1: minY, Y2: maxY} | |
263 } | |
264 | |
265 func (bn *bboxNode) insert(cnt contour, box Box2D) { | |
266 // check if children are inside new | |
267 var nr *bboxNode | |
268 | |
269 for i, r := range bn.children { | |
270 if r.box.Inside(box) && cnt.contains(r.cnt) { | |
271 if nr == nil { | |
272 nr = &bboxNode{box: box, cnt: cnt} | |
273 } | |
274 nr.children = append(nr.children, r) | |
275 bn.children[i] = nil | |
276 } | |
277 } | |
278 | |
279 // we have a new child | |
280 if nr != nil { | |
281 // compact the list | |
282 for i := len(bn.children) - 1; i >= 0; i-- { | |
283 if bn.children[i] == nil { | |
284 if i < len(bn.children)-1 { | |
285 copy(bn.children[i:], bn.children[i+1:]) | |
286 } | |
287 bn.children[len(bn.children)-1] = nil | |
288 bn.children = bn.children[:len(bn.children)-1] | |
289 } | |
290 } | |
291 bn.children = append(bn.children, nr) | |
292 return | |
293 } | |
294 | |
295 // check if new is inside an old | |
296 for _, r := range bn.children { | |
297 if box.Inside(r.box) && r.cnt.contains(cnt) { | |
298 r.insert(cnt, box) | |
299 return | |
300 } | |
301 } | |
302 | |
303 // its a new child node. | |
304 nr = &bboxNode{box: box, cnt: cnt} | |
305 bn.children = append(bn.children, nr) | |
306 } | |
307 | |
308 func (bn *bboxNode) insertRoot(cnt contour) { | |
309 bn.insert(cnt, cnt.bbox()) | |
310 } | |
311 | |
312 type bboxOutFunc func(contour, []contour) | |
313 | |
314 func (bn *bboxNode) generate(out bboxOutFunc) { | |
315 | |
316 var grands []*bboxNode | |
317 | |
318 holes := make([]contour, len(bn.children)) | |
319 | |
320 for i, ch := range bn.children { | |
321 holes[i] = ch.cnt | |
322 grands = append(grands, ch.children...) | |
323 } | |
324 out(bn.cnt, holes) | |
325 | |
326 // the grand children are new polygons. | |
327 for _, grand := range grands { | |
328 grand.generate(out) | |
329 } | |
330 } | |
331 | |
332 func (bn *bboxNode) generateRoot(out bboxOutFunc) { | |
333 for _, r := range bn.children { | |
334 r.generate(out) | |
335 } | |
336 } | |
337 | |
338 func buildMultipolygon( | |
339 cnts []contourmap.Contour, | |
340 reprojX, reprojY func(float64) float64, | |
341 ) wkb.MultiPolygonGeom { | |
342 | |
343 var forest bboxNode | |
344 | |
345 for _, cnt := range cnts { | |
346 forest.insertRoot(contour(cnt)) | |
347 } | |
348 | |
349 //log.Printf("cnts: %d roots: %d\n", len(cnts), len(bf.roots)) | |
350 | |
351 var mp wkb.MultiPolygonGeom | |
352 | |
353 out := func(sh contour, hls []contour) { | |
354 | |
355 polygon := make(wkb.PolygonGeom, 1+len(hls)) | |
356 | |
357 // Handle shell | |
358 shell := make(wkb.LinearRingGeom, len(sh)) | |
359 for j, pt := range sh { | |
360 shell[j] = wkb.PointGeom{ | |
361 X: reprojX(pt.X), | |
362 Y: reprojY(pt.Y), | |
363 } | |
364 } | |
365 if shell.CCW() { | |
366 shell.Reverse() | |
367 } | |
368 polygon[0] = shell | |
369 | |
370 // handle holes | |
371 for i, hl := range hls { | |
372 hole := make(wkb.LinearRingGeom, len(hl)) | |
373 for j, pt := range hl { | |
374 hole[j] = wkb.PointGeom{ | |
375 X: reprojX(pt.X), | |
376 Y: reprojY(pt.Y), | |
377 } | |
378 } | |
379 if !hole.CCW() { | |
380 hole.Reverse() | |
381 } | |
382 polygon[1+i] = hole | |
383 } | |
384 | |
385 mp = append(mp, polygon) | |
386 } | |
387 | |
388 forest.generateRoot(out) | |
389 | |
390 return mp | |
391 } |