Mercurial > gemma
comparison cmd/isoareas/algo.go @ 4551:b5b23b6d76f1 iso-areas
Move own algorith to separate file.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Tue, 01 Oct 2019 11:07:33 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
4550:aa2d0006e742 | 4551:b5b23b6d76f1 |
---|---|
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 main | |
15 | |
16 import ( | |
17 "bufio" | |
18 "container/list" | |
19 "log" | |
20 "math" | |
21 "math/bits" | |
22 "os" | |
23 "runtime" | |
24 "sync" | |
25 | |
26 "gemma.intevation.de/gemma/pkg/octree" | |
27 ) | |
28 | |
29 type indexedArc struct { | |
30 arc int32 | |
31 index int32 | |
32 } | |
33 | |
34 func glue(a, b octree.LineStringZ) octree.LineStringZ { | |
35 | |
36 a1, a2 := a[0], a[len(a)-1] | |
37 b1, b2 := b[0], b[len(b)-1] | |
38 | |
39 if a1.EpsEquals2D(b2) { | |
40 c := make(octree.LineStringZ, len(a)-1+len(b)) | |
41 copy(c, b) | |
42 copy(c[len(b):], a[1:]) | |
43 return c | |
44 } | |
45 | |
46 if a2.EpsEquals2D(b1) { | |
47 c := make(octree.LineStringZ, len(a)-1+len(b)) | |
48 copy(c, a) | |
49 copy(c[len(a):], b[1:]) | |
50 return c | |
51 } | |
52 | |
53 if a1.EpsEquals2D(b1) { | |
54 c := make(octree.LineStringZ, len(a)-1+len(b)) | |
55 copy(c, b) | |
56 c[:len(b)].Reverse() | |
57 copy(c[len(b):], a[1:]) | |
58 return c | |
59 } | |
60 | |
61 if a2.EpsEquals2D(b2) { | |
62 c := make(octree.LineStringZ, len(a)-1+len(b)) | |
63 copy(c, a) | |
64 copy(c[len(a):], b[:len(b)-1]) | |
65 c[len(a):].Reverse() | |
66 return c | |
67 } | |
68 | |
69 return nil | |
70 } | |
71 | |
72 func connectArcs(tri *octree.Triangulation, cuts []indexedArc, arcs *[]octree.LineStringZ) { | |
73 | |
74 unique := map[int32]struct{}{} | |
75 for i := range cuts { | |
76 unique[cuts[i].arc] = struct{}{} | |
77 } | |
78 before := len(unique) | |
79 | |
80 origLen := int32(len(*arcs)) | |
81 | |
82 for i := range cuts { | |
83 if cuts[i].arc >= origLen { | |
84 // already has a connected arc assigned. | |
85 continue | |
86 } | |
87 | |
88 line := (*arcs)[cuts[i].arc] | |
89 | |
90 var modified []int | |
91 visited := map[int32]struct{}{} | |
92 | |
93 var recursive func(int32) | |
94 recursive = func(idx int32) { | |
95 visited[idx] = struct{}{} | |
96 | |
97 ns := neighbors(tri, idx) | |
98 for _, n := range ns { | |
99 n /= 3 | |
100 if _, already := visited[n]; already { | |
101 continue | |
102 } | |
103 | |
104 arcIdx := findArc(n, cuts) | |
105 if arcIdx == -1 { | |
106 continue | |
107 } | |
108 arc := cuts[arcIdx].arc | |
109 if arc >= origLen { | |
110 // already a new arc. | |
111 continue | |
112 } | |
113 oline := (*arcs)[arc] | |
114 | |
115 nline := glue(line, oline) | |
116 if nline == nil { | |
117 // not joinable | |
118 continue | |
119 } | |
120 line = nline | |
121 modified = append(modified, arcIdx) | |
122 recursive(cuts[arcIdx].index) | |
123 } | |
124 } | |
125 recursive(cuts[i].index) | |
126 if len(modified) > 0 { | |
127 // alloc a new line an fix the references. | |
128 nidx := int32(len(*arcs)) | |
129 *arcs = append(*arcs, line) | |
130 cuts[i].arc = nidx | |
131 for _, j := range modified { | |
132 cuts[j].arc = nidx | |
133 } | |
134 } | |
135 } | |
136 | |
137 unique = map[int32]struct{}{} | |
138 for i := range cuts { | |
139 unique[cuts[i].arc] = struct{}{} | |
140 } | |
141 log.Printf("unique arcs: before: %d after %d\n", | |
142 before, len(unique)) | |
143 } | |
144 | |
145 func intersectTriangles( | |
146 tri *octree.Triangulation, | |
147 heights []float64, | |
148 min, max octree.Vertex, | |
149 ) [][]octree.LineStringZ { | |
150 | |
151 cuts := make([][]indexedArc, len(heights)) | |
152 | |
153 classes := make([][]int32, len(heights)+1) | |
154 | |
155 var arcs []octree.LineStringZ | |
156 | |
157 all: | |
158 for i := int32(0); i < int32(len(tri.Triangles)); i += 3 { | |
159 ti := tri.Triangles[i : i+3] | |
160 v0 := tri.Points[ti[0]] | |
161 v1 := tri.Points[ti[1]] | |
162 v2 := tri.Points[ti[2]] | |
163 min := math.Min(v0.Z, math.Min(v1.Z, v2.Z)) | |
164 | |
165 if min > heights[len(heights)-1] { | |
166 classes[len(classes)-1] = append(classes[len(classes)-1], i/3) | |
167 continue | |
168 } | |
169 max := math.Max(v0.Z, math.Max(v1.Z, v2.Z)) | |
170 | |
171 for j, h := range heights { | |
172 | |
173 var l float64 | |
174 if j > 0 { | |
175 l = heights[j-1] | |
176 } else { | |
177 l = -math.MaxFloat64 | |
178 } | |
179 | |
180 if l < min && max < h { | |
181 classes[j] = append(classes[j], i/3) | |
182 continue all | |
183 } | |
184 if min > h || max < h { | |
185 continue | |
186 } | |
187 t := octree.Triangle{v0, v1, v2} | |
188 cut := t.IntersectHorizontal(h) | |
189 if len(cut) >= 2 { | |
190 arc := int32(len(arcs)) | |
191 arcs = append(arcs, cut) | |
192 cuts[j] = append(cuts[j], indexedArc{arc: arc, index: i / 3}) | |
193 } | |
194 } | |
195 } | |
196 | |
197 // connect the arcs in a cut list to longer arcs. | |
198 | |
199 for _, c := range cuts { | |
200 connectArcs(tri, c, &arcs) | |
201 } | |
202 | |
203 func() { | |
204 out, err := os.Create("arcs.svg") | |
205 if err != nil { | |
206 log.Printf("err: %v\n", err) | |
207 return | |
208 } | |
209 defer func() { | |
210 out.Close() | |
211 log.Println("writing arcs done") | |
212 }() | |
213 | |
214 buf := bufio.NewWriter(out) | |
215 | |
216 dumpArcsSVG( | |
217 buf, | |
218 min, max, | |
219 cuts, | |
220 arcs) | |
221 buf.Flush() | |
222 }() | |
223 | |
224 result := make([][]octree.LineStringZ, len(classes)) | |
225 | |
226 jobs := make(chan int) | |
227 | |
228 var wg sync.WaitGroup | |
229 | |
230 worker := func() { | |
231 defer wg.Done() | |
232 | |
233 pb := polygonBuilder{open: list.New()} | |
234 | |
235 for i := range jobs { | |
236 usedArcs := map[int32]struct{}{} | |
237 var dupes int | |
238 var isolated, inside, found int | |
239 c := classes[i] | |
240 | |
241 allInClass: | |
242 for _, idx := range c { | |
243 ns := neighbors(tri, idx) | |
244 mask := where(ns, c) | |
245 switch bits.OnesCount8(mask) { | |
246 case 3: | |
247 // Totally insides do not contribute to the geometries. | |
248 inside++ | |
249 continue allInClass | |
250 case 0: | |
251 // Isolated are areas w/o connections to the rest. | |
252 isolated++ | |
253 ti := tri.Triangles[idx*3 : idx*3+3] | |
254 pb.addTriangle( | |
255 tri.Points[ti[0]], | |
256 tri.Points[ti[1]], | |
257 tri.Points[ti[2]]) | |
258 continue allInClass | |
259 default: | |
260 ti := tri.Triangles[idx*3 : idx*3+3] | |
261 for j := 0; j < 3; j++ { | |
262 if (mask & (1 << j)) == 0 { | |
263 | |
264 var curr octree.LineStringZ | |
265 | |
266 for l := i - 1; l <= i; l++ { | |
267 if l < 0 || l >= len(cuts) { | |
268 continue | |
269 } | |
270 arcIdx := findArc(ns[j]/3, cuts[l]) | |
271 if arcIdx == -1 { | |
272 continue | |
273 } | |
274 found++ | |
275 aIdx := cuts[l][arcIdx].arc | |
276 if _, already := usedArcs[aIdx]; already { | |
277 dupes++ | |
278 continue | |
279 } | |
280 usedArcs[aIdx] = struct{}{} | |
281 | |
282 curr = arcs[aIdx] | |
283 break | |
284 } | |
285 | |
286 if curr == nil { | |
287 k := (j + 1) % 3 | |
288 front := tri.Points[ti[j]] | |
289 back := tri.Points[ti[k]] | |
290 curr = octree.LineStringZ{front, back} | |
291 } | |
292 | |
293 segment: | |
294 for e := pb.open.Front(); e != nil; e = e.Next() { | |
295 line := e.Value.(octree.LineStringZ) | |
296 if nline := glue(curr, line); nline != nil { | |
297 curr = nline | |
298 pb.open.Remove(e) | |
299 goto segment | |
300 } | |
301 } // all open | |
302 | |
303 // check if closed | |
304 if curr[0].EpsEquals2D(curr[len(curr)-1]) { | |
305 if !curr.CCW() { | |
306 curr.Reverse() | |
307 } | |
308 pb.polygons = append(pb.polygons, curr) | |
309 } else { | |
310 pb.open.PushFront(curr) | |
311 } | |
312 } // for all border parts | |
313 } | |
314 } | |
315 } | |
316 | |
317 log.Printf("\t%d: inside: %d / isolated: %d open: %d closed: %d dupes: %d found: %d\n", | |
318 i, inside, isolated, pb.open.Len(), len(pb.polygons), dupes, found) | |
319 /* | |
320 for e := pb.open.Front(); e != nil; e = e.Next() { | |
321 line := e.Value.(octree.LineStringZ) | |
322 if !line.CCW() { | |
323 line.Reverse() | |
324 } | |
325 pb.polygons = append(pb.polygons, line) | |
326 } | |
327 */ | |
328 | |
329 result[i] = pb.polygons | |
330 pb.reset() | |
331 } | |
332 } | |
333 | |
334 for n := runtime.NumCPU(); n >= 0; n-- { | |
335 wg.Add(1) | |
336 go worker() | |
337 } | |
338 | |
339 log.Println("inside classes:") | |
340 for i := range classes { | |
341 jobs <- i | |
342 } | |
343 close(jobs) | |
344 | |
345 wg.Wait() | |
346 | |
347 log.Println("cuts:") | |
348 for i, c := range cuts { | |
349 log.Printf("\t%.3f: %d\n", heights[i], len(c)) | |
350 } | |
351 | |
352 return result | |
353 | |
354 // TODO: sew segments together. | |
355 | |
356 } | |
357 | |
358 type polygonBuilder struct { | |
359 polygons []octree.LineStringZ | |
360 | |
361 open *list.List | |
362 } | |
363 | |
364 func (pb *polygonBuilder) addTriangle(v0, v1, v2 octree.Vertex) { | |
365 polygon := octree.LineStringZ{v0, v1, v2, v0} | |
366 pb.polygons = append(pb.polygons, polygon) | |
367 } | |
368 | |
369 func (pb *polygonBuilder) reset() { | |
370 pb.polygons = pb.polygons[:0] | |
371 pb.open.Init() | |
372 } | |
373 | |
374 func neighbors(t *octree.Triangulation, idx int32) []int32 { | |
375 idx *= 3 | |
376 return t.Halfedges[idx : idx+3] | |
377 } | |
378 | |
379 func findArc(needle int32, haystack []indexedArc) int { | |
380 lo, hi := 0, len(haystack)-1 | |
381 for lo <= hi { | |
382 mid := (hi-lo)/2 + lo | |
383 switch v := haystack[mid].index; { | |
384 case v < needle: | |
385 lo = mid + 1 | |
386 case v > needle: | |
387 hi = mid - 1 | |
388 default: | |
389 return mid | |
390 } | |
391 } | |
392 return -1 | |
393 } | |
394 | |
395 func contains(needle int32, haystack []int32) bool { | |
396 lo, hi := 0, len(haystack)-1 | |
397 for lo <= hi { | |
398 mid := (hi-lo)/2 + lo | |
399 switch v := haystack[mid]; { | |
400 case v < needle: | |
401 lo = mid + 1 | |
402 case v > needle: | |
403 hi = mid - 1 | |
404 default: | |
405 return true | |
406 } | |
407 } | |
408 return false | |
409 } | |
410 | |
411 func where(neighbors, indices []int32) byte { | |
412 var mask byte | |
413 for i, n := range neighbors { | |
414 if n >= 0 && contains(n/3, indices) { | |
415 mask |= 1 << i | |
416 } | |
417 } | |
418 return mask | |
419 } |