Mercurial > gemma
comparison pkg/imports/sr.go @ 3658:1c3df921361d single-beam
Handle th case that a boundary polygon is uploaded along side with the single beam scan.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Fri, 14 Jun 2019 11:59:14 +0200 |
parents | a6c671abbc35 |
children | 66f2cb789905 |
comparison
equal
deleted
inserted
replaced
3655:a6c671abbc35 | 3658:1c3df921361d |
---|---|
134 best_utm(area), | 134 best_utm(area), |
135 ST_AsBinary(ST_Transform(area::geometry, best_utm(area))) | 135 ST_AsBinary(ST_Transform(area::geometry, best_utm(area))) |
136 ` | 136 ` |
137 | 137 |
138 reprojectPointsSQL = ` | 138 reprojectPointsSQL = ` |
139 SELECT ST_AsBinary(ST_Transform(ST_GeomFromWKB($1, $2::integer), $3::integer)) | 139 SELECT ST_AsBinary(ST_Transform(ST_GeomFromWKB($1, $2::integer), $3::integer))` |
140 ` | 140 |
141 reprojectPointsBufferedSQL = ` | |
142 SELECT | |
143 ST_AsBinary(ST_Transform(ST_GeomFromWKB($1, $2::integer), $3::integer)), | |
144 ST_AsBinary(ST_Buffer(ST_Transform(ST_GeomFromWKB($1, $2::integer), $3::integer), 0.1)), | |
145 ST_Area(ST_Transform(ST_GeomFromWKB($1, $2::integer), $3::integer))` | |
146 | |
141 insertOctreeSQL = ` | 147 insertOctreeSQL = ` |
142 UPDATE waterway.sounding_results SET | 148 UPDATE waterway.sounding_results SET |
143 octree_checksum = $2, octree_index = $3 | 149 octree_checksum = $2, octree_index = $3 |
144 WHERE id = $1` | 150 WHERE id = $1` |
145 | 151 |
336 xyz octree.MultiPointZ, | 342 xyz octree.MultiPointZ, |
337 boundary polygonSlice, | 343 boundary polygonSlice, |
338 ) (interface{}, error) { | 344 ) (interface{}, error) { |
339 | 345 |
340 feedback.Info("Processing as single beam scan.") | 346 feedback.Info("Processing as single beam scan.") |
347 feedback.Info("Reproject XYZ data.") | |
341 | 348 |
342 start := time.Now() | 349 start := time.Now() |
343 | 350 |
344 xyzWKB := xyz.AsWKB() | 351 xyzWKB := xyz.AsWKB() |
345 var reproj []byte | 352 var reproj []byte |
359 } | 366 } |
360 | 367 |
361 feedback.Info("Reprojecting points to EPSG %d took %v.", | 368 feedback.Info("Reprojecting points to EPSG %d took %v.", |
362 epsg, time.Since(start)) | 369 epsg, time.Since(start)) |
363 feedback.Info("Number of reprojected points: %d", len(xyz)) | 370 feedback.Info("Number of reprojected points: %d", len(xyz)) |
371 feedback.Info("Triangulate XYZ data.") | |
364 | 372 |
365 start = time.Now() | 373 start = time.Now() |
366 tri, err := octree.Triangulate(xyz) | 374 tri, err := octree.Triangulate(xyz) |
367 if err != nil { | 375 if err != nil { |
368 return nil, err | 376 return nil, err |
369 } | 377 } |
370 feedback.Info("Triangulation took %v.", time.Since(start)) | 378 feedback.Info("Triangulation took %v.", time.Since(start)) |
371 feedback.Info("Number triangles: %d.", len(tri.Triangles)/3) | 379 feedback.Info("Number triangles: %d.", len(tri.Triangles)/3) |
372 | 380 |
373 var clippingPolygon octree.Polygon | 381 var ( |
382 clippingPolygon octree.Polygon | |
383 clippingPolygonBuffered octree.Polygon | |
384 removed map[int32]struct{} | |
385 polygonArea float64 | |
386 clippingPolygonWKB []byte | |
387 ) | |
374 | 388 |
375 if boundary == nil { | 389 if boundary == nil { |
376 feedback.Info("No boundary given. Calulating from XYZ data.") | 390 feedback.Info("No boundary given. Calulate from XYZ data.") |
377 feedback.Info("Eliminate triangles with edges longer than %.2f meters.", tooLongEdge) | 391 feedback.Info("Eliminate triangles with edges longer than %.2f meters.", tooLongEdge) |
378 | 392 |
379 polygon, removed := tri.ConcaveHull(tooLongEdge) | 393 var polygon octree.LineStringZ |
380 | 394 start = time.Now() |
381 polygonArea := polygon.Area() | 395 polygon, removed = tri.ConcaveHull(tooLongEdge) |
396 | |
397 polygonArea = polygon.Area() | |
382 if polygonArea < 0.0 { // counter clockwise | 398 if polygonArea < 0.0 { // counter clockwise |
383 polygonArea = -polygonArea | 399 polygonArea = -polygonArea |
384 polygon.Reverse() | 400 polygon.Reverse() |
385 } | 401 } |
386 | 402 |
387 clippingPolygon.FromLineStringZ(polygon) | 403 clippingPolygon.FromLineStringZ(polygon) |
388 | 404 |
389 var repaired, buffered []byte | 405 var buffered []byte |
390 | |
391 if err := tx.QueryRowContext( | 406 if err := tx.QueryRowContext( |
392 ctx, | 407 ctx, |
393 repairBoundarySQL, | 408 repairBoundarySQL, |
394 clippingPolygon.AsWKB(), | 409 clippingPolygon.AsWKB(), |
395 epsg, | 410 epsg, |
396 ).Scan(&repaired, &buffered); err != nil { | 411 ).Scan(&clippingPolygonWKB, &buffered); err != nil { |
397 return nil, err | 412 return nil, err |
398 } | 413 } |
399 | 414 |
400 if err := clippingPolygon.FromWKB(repaired); err != nil { | 415 if err := clippingPolygon.FromWKB(clippingPolygonWKB); err != nil { |
401 return nil, err | 416 return nil, err |
402 } | 417 } |
403 var clippingPolygonBuffered octree.Polygon | |
404 if err := clippingPolygonBuffered.FromWKB(buffered); err != nil { | 418 if err := clippingPolygonBuffered.FromWKB(buffered); err != nil { |
405 return nil, err | 419 return nil, err |
406 } | 420 } |
407 | 421 |
408 firstTin := tri.Tin() | 422 feedback.Info("Calculating took %v.", time.Since(start)) |
409 builder := octree.NewBuilder(firstTin) | 423 |
410 builder.Build(removed) | 424 } else { // has Boundary |
411 | 425 feedback.Info("Using uploaded boundary polygon.") |
412 firstTree := builder.Tree() | 426 var buffered []byte |
413 | |
414 feedback.Info("Boundary area: %.2fm²", polygonArea) | |
415 | |
416 numPoints := int(math.Ceil(polygonArea * pointsPerSquareMeter)) | |
417 | |
418 feedback.Info("Generating %d random points for an average density of ~%d points/m².", | |
419 numPoints, pointsPerSquareMeter) | |
420 | |
421 start := time.Now() | |
422 | |
423 generated := make(octree.LineStringZ, 0, numPoints+len(polygon)-1) | |
424 | |
425 firstTree.GenerateRandomVertices(numPoints, func(vertices []octree.Vertex) { | |
426 generated = append(generated, vertices...) | |
427 }) | |
428 | |
429 feedback.Info("Generating %d points took %v.", len(generated), time.Since(start)) | |
430 | |
431 // Add the boundary to new point cloud. | |
432 generated = append(generated, polygon[:len(polygon)-1]...) | |
433 | |
434 feedback.Info("Triangulate new point cloud.") | |
435 | |
436 start = time.Now() | |
437 | |
438 xyz = octree.MultiPointZ(generated) | |
439 | |
440 tri, err := octree.Triangulate(xyz) | |
441 if err != nil { | |
442 return nil, err | |
443 } | |
444 feedback.Info("Second triangulation took %v.", time.Since(start)) | |
445 feedback.Info("Number triangles: %d.", len(tri.Triangles)/3) | |
446 feedback.Info("Clipping triangles from new mesh.") | |
447 | |
448 start = time.Now() | |
449 secondTin := tri.Tin() | |
450 secondTin.EPSG = uint32(epsg) | |
451 clippingPolygonBuffered.Indexify() | |
452 | |
453 var str octree.STRTree | |
454 str.Build(secondTin) | |
455 feedback.Info("Building STR tree took %v", time.Since(start)) | |
456 | |
457 start = time.Now() | |
458 | |
459 removed = str.Clip(&clippingPolygonBuffered) | |
460 feedback.Info("Clipping STR tree took %v.", time.Since(start)) | |
461 feedback.Info("Number of triangles to clip %d.", len(removed)) | |
462 | |
463 start = time.Now() | |
464 | |
465 feedback.Info("Building final octree index") | |
466 | |
467 builder = octree.NewBuilder(secondTin) | |
468 builder.Build(removed) | |
469 octreeIndex, err := builder.Bytes() | |
470 if err != nil { | |
471 return nil, err | |
472 } | |
473 feedback.Info("Building octree took %v.", time.Since(start)) | |
474 | |
475 start = time.Now() | |
476 | |
477 var ( | |
478 id int64 | |
479 dummy uint32 | |
480 lat, lon float64 | |
481 ) | |
482 | |
483 var hull []byte | |
484 | |
485 if err := tx.QueryRowContext( | |
486 ctx, | |
487 insertHullSQL, | |
488 m.Bottleneck, | |
489 m.Date.Time, | |
490 m.DepthReference, | |
491 nil, | |
492 repaired, | |
493 epsg, | |
494 ).Scan( | |
495 &id, | |
496 &lat, | |
497 &lon, | |
498 &dummy, | |
499 &hull, | |
500 ); err != nil { | |
501 return nil, err | |
502 } | |
503 | |
504 h := sha1.New() | |
505 h.Write(octreeIndex) | |
506 checksum := hex.EncodeToString(h.Sum(nil)) | |
507 _, err = tx.ExecContext(ctx, insertOctreeSQL, id, checksum, octreeIndex) | |
508 if err != nil { | |
509 return nil, err | |
510 } | |
511 feedback.Info("Storing octree index took %s.", time.Since(start)) | |
512 | |
513 tree := builder.Tree() | |
514 | |
515 start = time.Now() | |
516 err = generateContours(ctx, tx, tree, id) | |
517 if err != nil { | |
518 return nil, err | |
519 } | |
520 feedback.Info("Generating and storing contour lines took %s.", | |
521 time.Since(start)) | |
522 | |
523 // Store for potential later removal. | |
524 if err = track(ctx, tx, importID, "waterway.sounding_results", id); err != nil { | |
525 return nil, err | |
526 } | |
527 | |
528 summary := struct { | |
529 Bottleneck string `json:"bottleneck"` | |
530 Date models.Date `json:"date"` | |
531 Lat float64 `json:"lat"` | |
532 Lon float64 `json:"lon"` | |
533 }{ | |
534 Bottleneck: m.Bottleneck, | |
535 Date: m.Date, | |
536 Lat: lat, | |
537 Lon: lon, | |
538 } | |
539 | |
540 return &summary, nil | |
541 | |
542 } else { | |
543 var hull []byte | |
544 if err = tx.QueryRowContext( | 427 if err = tx.QueryRowContext( |
545 ctx, | 428 ctx, |
546 reprojectPointsSQL, | 429 reprojectPointsBufferedSQL, |
547 boundary.asWKB(), | 430 boundary.asWKB(), |
548 m.EPSG, | 431 m.EPSG, |
549 epsg, | 432 epsg, |
550 ).Scan(&hull); err != nil { | 433 ).Scan(&clippingPolygonWKB, &buffered, &polygonArea); err != nil { |
551 return nil, err | 434 return nil, err |
552 } | 435 } |
553 if err := clippingPolygon.FromWKB(hull); err != nil { | 436 if err := clippingPolygon.FromWKB(clippingPolygonWKB); err != nil { |
554 return nil, err | 437 return nil, err |
555 } | 438 } |
556 clippingPolygon.Indexify() | 439 if err := clippingPolygonBuffered.FromWKB(buffered); err != nil { |
557 } | 440 return nil, err |
558 | 441 } |
559 // TODO: Implement me! | 442 |
560 return nil, errors.New("Not implemented, yet!") | 443 tin := tri.Tin() |
444 tin.EPSG = uint32(epsg) | |
445 | |
446 var str octree.STRTree | |
447 str.Build(tin) | |
448 | |
449 removed = str.Clip(&clippingPolygon) | |
450 } | |
451 | |
452 // Build the first mesh to generate random points on. | |
453 | |
454 feedback.Info("Build virtual DEM based on original XYZ data.") | |
455 | |
456 start = time.Now() | |
457 | |
458 var tree *octree.Tree | |
459 { | |
460 builder := octree.NewBuilder(tri.Tin()) | |
461 builder.Build(removed) | |
462 tree = builder.Tree() | |
463 } | |
464 | |
465 feedback.Info("Building took %v", time.Since(start)) | |
466 | |
467 feedback.Info("Boundary area: %.2fm²", polygonArea) | |
468 | |
469 numPoints := int(math.Ceil(polygonArea * pointsPerSquareMeter)) | |
470 | |
471 feedback.Info("Generate %d random points for an average density of ~%d points/m².", | |
472 numPoints, pointsPerSquareMeter) | |
473 | |
474 start = time.Now() | |
475 | |
476 generated := make(octree.LineStringZ, 0, numPoints+clippingPolygon.NumVertices(0)) | |
477 | |
478 tree.GenerateRandomVertices(numPoints, func(vertices []octree.Vertex) { | |
479 generated = append(generated, vertices...) | |
480 }) | |
481 | |
482 feedback.Info("Generating %d points took %v.", len(generated), time.Since(start)) | |
483 | |
484 // Add the boundary to new point cloud. | |
485 dupes := map[[2]float64]struct{}{} | |
486 clippingPolygon.Vertices(0, func(x, y float64) { | |
487 key := [2]float64{x, y} | |
488 if _, found := dupes[key]; found { | |
489 return | |
490 } | |
491 dupes[key] = struct{}{} | |
492 if z, ok := tree.Value(x, y); ok { | |
493 generated = append(generated, octree.Vertex{X: x, Y: y, Z: z}) | |
494 } | |
495 }) | |
496 | |
497 feedback.Info("Triangulate new point cloud.") | |
498 | |
499 start = time.Now() | |
500 | |
501 xyz = octree.MultiPointZ(generated) | |
502 | |
503 tri, err = octree.Triangulate(xyz) | |
504 if err != nil { | |
505 return nil, err | |
506 } | |
507 feedback.Info("Second triangulation took %v.", time.Since(start)) | |
508 feedback.Info("Number triangles: %d.", len(tri.Triangles)/3) | |
509 feedback.Info("Clipping triangles from new mesh.") | |
510 | |
511 start = time.Now() | |
512 tin := tri.Tin() | |
513 tin.EPSG = uint32(epsg) | |
514 | |
515 var str octree.STRTree | |
516 str.Build(tin) | |
517 feedback.Info("Building STR tree took %v", time.Since(start)) | |
518 | |
519 start = time.Now() | |
520 | |
521 clippingPolygonBuffered.Indexify() | |
522 removed = str.Clip(&clippingPolygonBuffered) | |
523 feedback.Info("Clipping STR tree took %v.", time.Since(start)) | |
524 feedback.Info("Number of triangles to clip %d.", len(removed)) | |
525 | |
526 start = time.Now() | |
527 | |
528 feedback.Info("Build final octree index") | |
529 | |
530 builder := octree.NewBuilder(tin) | |
531 builder.Build(removed) | |
532 octreeIndex, err := builder.Bytes() | |
533 if err != nil { | |
534 return nil, err | |
535 } | |
536 feedback.Info("Building octree took %v.", time.Since(start)) | |
537 feedback.Info("Store octree.") | |
538 | |
539 start = time.Now() | |
540 | |
541 var ( | |
542 id int64 | |
543 dummy uint32 | |
544 lat, lon float64 | |
545 ) | |
546 | |
547 var hull []byte | |
548 | |
549 if err := tx.QueryRowContext( | |
550 ctx, | |
551 insertHullSQL, | |
552 m.Bottleneck, | |
553 m.Date.Time, | |
554 m.DepthReference, | |
555 nil, | |
556 clippingPolygonWKB, | |
557 epsg, | |
558 ).Scan( | |
559 &id, | |
560 &lat, | |
561 &lon, | |
562 &dummy, | |
563 &hull, | |
564 ); err != nil { | |
565 return nil, err | |
566 } | |
567 | |
568 h := sha1.New() | |
569 h.Write(octreeIndex) | |
570 checksum := hex.EncodeToString(h.Sum(nil)) | |
571 _, err = tx.ExecContext(ctx, insertOctreeSQL, id, checksum, octreeIndex) | |
572 if err != nil { | |
573 return nil, err | |
574 } | |
575 feedback.Info("Storing octree index took %s.", time.Since(start)) | |
576 feedback.Info("Generate contour lines") | |
577 | |
578 start = time.Now() | |
579 err = generateContours(ctx, tx, builder.Tree(), id) | |
580 if err != nil { | |
581 return nil, err | |
582 } | |
583 feedback.Info("Generating contour lines took %s.", | |
584 time.Since(start)) | |
585 | |
586 // Store for potential later removal. | |
587 if err = track(ctx, tx, importID, "waterway.sounding_results", id); err != nil { | |
588 return nil, err | |
589 } | |
590 | |
591 summary := struct { | |
592 Bottleneck string `json:"bottleneck"` | |
593 Date models.Date `json:"date"` | |
594 Lat float64 `json:"lat"` | |
595 Lon float64 `json:"lon"` | |
596 }{ | |
597 Bottleneck: m.Bottleneck, | |
598 Date: m.Date, | |
599 Lat: lat, | |
600 Lon: lon, | |
601 } | |
602 | |
603 return &summary, nil | |
561 } | 604 } |
562 | 605 |
563 func (sr *SoundingResult) multiBeamScan( | 606 func (sr *SoundingResult) multiBeamScan( |
564 ctx context.Context, | 607 ctx context.Context, |
565 tx *sql.Tx, | 608 tx *sql.Tx, |