Mercurial > gemma
comparison pkg/imports/sr.go @ 3742:879c297c47e9 concave-hull
SR import: Use home-brew concave hull algorithm in multi-beam scan case, too.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Mon, 24 Jun 2019 12:54:25 +0200 |
parents | ec86a7155377 |
children | 4bb5dfa0b7e3 |
comparison
equal
deleted
inserted
replaced
3733:ec86a7155377 | 3742:879c297c47e9 |
---|---|
304 defer tx.Rollback() | 304 defer tx.Rollback() |
305 | 305 |
306 var summary interface{} | 306 var summary interface{} |
307 | 307 |
308 if sr.isSingleBeam() { | 308 if sr.isSingleBeam() { |
309 summary, err = sr.singleBeamScan( | 309 summary, err = sr.processScan( |
310 ctx, | 310 ctx, |
311 tx, | 311 tx, |
312 feedback, | 312 feedback, |
313 true, | |
313 importID, | 314 importID, |
314 m, | 315 m, |
315 xyz, | 316 xyz, |
316 boundary, | 317 boundary, |
317 ) | 318 ) |
318 } else { | 319 } else { |
319 summary, err = sr.multiBeamScan( | 320 summary, err = sr.processScan( |
320 ctx, | 321 ctx, |
321 tx, | 322 tx, |
322 feedback, | 323 feedback, |
324 false, | |
323 importID, | 325 importID, |
324 m, | 326 m, |
325 xyz, | 327 xyz, |
326 boundary, | 328 boundary, |
327 ) | 329 ) |
340 "Storing sounding result was successful after %v.", time.Since(start)) | 342 "Storing sounding result was successful after %v.", time.Since(start)) |
341 | 343 |
342 return summary, nil | 344 return summary, nil |
343 } | 345 } |
344 | 346 |
345 func (sr *SoundingResult) singleBeamScan( | 347 func (sr *SoundingResult) processScan( |
346 ctx context.Context, | 348 ctx context.Context, |
347 tx *sql.Tx, | 349 tx *sql.Tx, |
348 feedback Feedback, | 350 feedback Feedback, |
351 singleBeam bool, | |
349 importID int64, | 352 importID int64, |
350 m *models.SoundingResultMeta, | 353 m *models.SoundingResultMeta, |
351 xyz octree.MultiPointZ, | 354 xyz octree.MultiPointZ, |
352 boundary polygonSlice, | 355 boundary polygonSlice, |
353 ) (interface{}, error) { | 356 ) (interface{}, error) { |
354 | 357 |
355 feedback.Info("Processing as single beam scan.") | 358 if singleBeam { |
359 feedback.Info("Processing as single beam scan.") | |
360 } else { | |
361 feedback.Info("Processing as multi beam scan.") | |
362 } | |
363 | |
356 feedback.Info("Reproject XYZ data.") | 364 feedback.Info("Reproject XYZ data.") |
357 | 365 |
358 start := time.Now() | 366 start := time.Now() |
359 | 367 |
360 xyzWKB := xyz.AsWKB() | 368 xyzWKB := xyz.AsWKB() |
361 var reproj []byte | 369 var reproj []byte |
362 var epsg uint | 370 var epsg uint32 |
363 | 371 |
364 if err := tx.QueryRowContext( | 372 if err := tx.QueryRowContext( |
365 ctx, | 373 ctx, |
366 reprojectPointsSingleBeamSQL, | 374 reprojectPointsSingleBeamSQL, |
367 xyzWKB, | 375 xyzWKB, |
391 clippingPolygon octree.Polygon | 399 clippingPolygon octree.Polygon |
392 clippingPolygonBuffered octree.Polygon | 400 clippingPolygonBuffered octree.Polygon |
393 removed map[int32]struct{} | 401 removed map[int32]struct{} |
394 polygonArea float64 | 402 polygonArea float64 |
395 clippingPolygonWKB []byte | 403 clippingPolygonWKB []byte |
404 tin *octree.Tin | |
396 ) | 405 ) |
397 | 406 |
398 if boundary == nil { | 407 if boundary == nil { |
399 feedback.Info("No boundary given. Calulate from XYZ data.") | 408 feedback.Info("No boundary given. Calulate from XYZ data.") |
400 tooLongEdge := tri.EstimateTooLong() | 409 tooLongEdge := tri.EstimateTooLong() |
449 if err := clippingPolygonBuffered.FromWKB(buffered); err != nil { | 458 if err := clippingPolygonBuffered.FromWKB(buffered); err != nil { |
450 return nil, err | 459 return nil, err |
451 } | 460 } |
452 | 461 |
453 tin := tri.Tin() | 462 tin := tri.Tin() |
454 tin.EPSG = uint32(epsg) | 463 tin.EPSG = epsg |
455 | 464 |
456 var str octree.STRTree | 465 var str octree.STRTree |
457 str.Build(tin) | 466 str.Build(tin) |
458 | 467 |
459 removed = str.Clip(&clippingPolygon) | 468 removed = str.Clip(&clippingPolygon) |
460 } | 469 } |
461 | 470 |
462 // Build the first mesh to generate random points on. | 471 if singleBeam { |
463 | 472 |
464 feedback.Info("Build virtual DEM based on original XYZ data.") | 473 // Build the first mesh to generate random points on. |
474 | |
475 feedback.Info("Build virtual DEM based on original XYZ data.") | |
476 | |
477 start = time.Now() | |
478 | |
479 var tree *octree.Tree | |
480 { | |
481 builder := octree.NewBuilder(tri.Tin()) | |
482 builder.Build(removed) | |
483 tree = builder.Tree() | |
484 } | |
485 | |
486 feedback.Info("Building took %v", time.Since(start)) | |
487 | |
488 feedback.Info("Boundary area: %.2fm²", polygonArea) | |
489 | |
490 numPoints := int(math.Ceil(polygonArea * pointsPerSquareMeter)) | |
491 | |
492 feedback.Info("Generate %d random points for an average density of ~%d points/m².", | |
493 numPoints, pointsPerSquareMeter) | |
494 | |
495 start = time.Now() | |
496 | |
497 generated := make(octree.LineStringZ, 0, numPoints+clippingPolygon.NumVertices(0)) | |
498 | |
499 tree.GenerateRandomVertices(numPoints, func(vertices []octree.Vertex) { | |
500 generated = append(generated, vertices...) | |
501 }) | |
502 | |
503 feedback.Info("Generating %d points took %v.", len(generated), time.Since(start)) | |
504 | |
505 // Add the boundary to new point cloud. | |
506 dupes := map[[2]float64]struct{}{} | |
507 clippingPolygon.Vertices(0, func(x, y float64) { | |
508 key := [2]float64{x, y} | |
509 if _, found := dupes[key]; found { | |
510 return | |
511 } | |
512 dupes[key] = struct{}{} | |
513 if z, ok := tree.Value(x, y); ok { | |
514 generated = append(generated, octree.Vertex{X: x, Y: y, Z: z}) | |
515 } | |
516 }) | |
517 | |
518 feedback.Info("Triangulate new point cloud.") | |
519 xyz = octree.MultiPointZ(generated) | |
520 start = time.Now() | |
521 | |
522 tri, err = octree.Triangulate(xyz) | |
523 if err != nil { | |
524 return nil, err | |
525 } | |
526 feedback.Info("Second triangulation took %v.", time.Since(start)) | |
527 feedback.Info("Number triangles: %d.", len(tri.Triangles)/3) | |
528 feedback.Info("Clipping triangles from new mesh.") | |
529 | |
530 } else { // multi beam | |
531 // Nothing special | |
532 } | |
465 | 533 |
466 start = time.Now() | 534 start = time.Now() |
467 | 535 tin = tri.Tin() |
468 var tree *octree.Tree | 536 tin.EPSG = epsg |
469 { | |
470 builder := octree.NewBuilder(tri.Tin()) | |
471 builder.Build(removed) | |
472 tree = builder.Tree() | |
473 } | |
474 | |
475 feedback.Info("Building took %v", time.Since(start)) | |
476 | |
477 feedback.Info("Boundary area: %.2fm²", polygonArea) | |
478 | |
479 numPoints := int(math.Ceil(polygonArea * pointsPerSquareMeter)) | |
480 | |
481 feedback.Info("Generate %d random points for an average density of ~%d points/m².", | |
482 numPoints, pointsPerSquareMeter) | |
483 | |
484 start = time.Now() | |
485 | |
486 generated := make(octree.LineStringZ, 0, numPoints+clippingPolygon.NumVertices(0)) | |
487 | |
488 tree.GenerateRandomVertices(numPoints, func(vertices []octree.Vertex) { | |
489 generated = append(generated, vertices...) | |
490 }) | |
491 | |
492 feedback.Info("Generating %d points took %v.", len(generated), time.Since(start)) | |
493 | |
494 // Add the boundary to new point cloud. | |
495 dupes := map[[2]float64]struct{}{} | |
496 clippingPolygon.Vertices(0, func(x, y float64) { | |
497 key := [2]float64{x, y} | |
498 if _, found := dupes[key]; found { | |
499 return | |
500 } | |
501 dupes[key] = struct{}{} | |
502 if z, ok := tree.Value(x, y); ok { | |
503 generated = append(generated, octree.Vertex{X: x, Y: y, Z: z}) | |
504 } | |
505 }) | |
506 | |
507 feedback.Info("Triangulate new point cloud.") | |
508 | |
509 start = time.Now() | |
510 | |
511 xyz = octree.MultiPointZ(generated) | |
512 | |
513 tri, err = octree.Triangulate(xyz) | |
514 if err != nil { | |
515 return nil, err | |
516 } | |
517 feedback.Info("Second triangulation took %v.", time.Since(start)) | |
518 feedback.Info("Number triangles: %d.", len(tri.Triangles)/3) | |
519 feedback.Info("Clipping triangles from new mesh.") | |
520 | |
521 start = time.Now() | |
522 tin := tri.Tin() | |
523 tin.EPSG = uint32(epsg) | |
524 | 537 |
525 var str octree.STRTree | 538 var str octree.STRTree |
526 str.Build(tin) | 539 str.Build(tin) |
527 feedback.Info("Building STR tree took %v", time.Since(start)) | 540 feedback.Info("Building STR tree took %v", time.Since(start)) |
528 | 541 |
530 | 543 |
531 clippingPolygonBuffered.Indexify() | 544 clippingPolygonBuffered.Indexify() |
532 removed = str.Clip(&clippingPolygonBuffered) | 545 removed = str.Clip(&clippingPolygonBuffered) |
533 feedback.Info("Clipping STR tree took %v.", time.Since(start)) | 546 feedback.Info("Clipping STR tree took %v.", time.Since(start)) |
534 feedback.Info("Number of triangles to clip %d.", len(removed)) | 547 feedback.Info("Number of triangles to clip %d.", len(removed)) |
535 | |
536 start = time.Now() | |
537 | 548 |
538 feedback.Info("Build final octree index") | 549 feedback.Info("Build final octree index") |
539 | 550 |
540 builder := octree.NewBuilder(tin) | 551 builder := octree.NewBuilder(tin) |
541 builder.Build(removed) | 552 builder.Build(removed) |
617 } | 628 } |
618 | 629 |
619 return &summary, nil | 630 return &summary, nil |
620 } | 631 } |
621 | 632 |
622 func (sr *SoundingResult) multiBeamScan( | |
623 ctx context.Context, | |
624 tx *sql.Tx, | |
625 feedback Feedback, | |
626 importID int64, | |
627 m *models.SoundingResultMeta, | |
628 xyz octree.MultiPointZ, | |
629 boundary polygonSlice, | |
630 ) (interface{}, error) { | |
631 | |
632 feedback.Info("Processing as multi beam scan.") | |
633 var ( | |
634 id int64 | |
635 epsg uint32 | |
636 lat, lon float64 | |
637 ) | |
638 start := time.Now() | |
639 | |
640 var hull []byte | |
641 | |
642 xyzWKB := xyz.AsWKB() | |
643 | |
644 err := tx.QueryRowContext( | |
645 ctx, | |
646 insertHullSQL, | |
647 m.Bottleneck, | |
648 m.Date.Time, | |
649 m.DepthReference, | |
650 xyzWKB, | |
651 boundary.asWKB(), | |
652 m.EPSG, | |
653 ).Scan( | |
654 &id, | |
655 &lat, | |
656 &lon, | |
657 &epsg, | |
658 &hull, | |
659 ) | |
660 xyz, boundary = nil, nil // not need from now on. | |
661 feedback.Info("Calculating hull took %s.", time.Since(start)) | |
662 switch { | |
663 case err == sql.ErrNoRows: | |
664 return nil, fmt.Errorf( | |
665 "No matching bottleneck of given name or time available: %v", err) | |
666 case err != nil: | |
667 return nil, err | |
668 } | |
669 feedback.Info("Best suited UTM EPSG: %d", epsg) | |
670 | |
671 start = time.Now() | |
672 | |
673 var clippingPolygon octree.Polygon | |
674 | |
675 if err := clippingPolygon.FromWKB(hull); err != nil { | |
676 return nil, err | |
677 } | |
678 clippingPolygon.Indexify() | |
679 feedback.Info("Building clipping polygon took %v.", time.Since(start)) | |
680 | |
681 start = time.Now() | |
682 | |
683 var reproj []byte | |
684 | |
685 if err = tx.QueryRowContext( | |
686 ctx, | |
687 reprojectPointsSQL, | |
688 xyzWKB, | |
689 m.EPSG, | |
690 epsg, | |
691 ).Scan(&reproj); err != nil { | |
692 return nil, err | |
693 } | |
694 | |
695 if err := xyz.FromWKB(reproj); err != nil { | |
696 return nil, err | |
697 } | |
698 | |
699 feedback.Info("Reprojecting points took %v.", time.Since(start)) | |
700 feedback.Info("Number of reprojected points: %d", len(xyz)) | |
701 | |
702 start = time.Now() | |
703 | |
704 tri, err := octree.Triangulate(xyz) | |
705 if err != nil { | |
706 return nil, err | |
707 } | |
708 feedback.Info("Triangulation took %v.", time.Since(start)) | |
709 | |
710 start = time.Now() | |
711 | |
712 tin := tri.Tin() | |
713 | |
714 var str octree.STRTree | |
715 str.Build(tin) | |
716 feedback.Info("Building STR tree took %v", time.Since(start)) | |
717 | |
718 start = time.Now() | |
719 | |
720 removed := str.Clip(&clippingPolygon) | |
721 feedback.Info("Clipping STR tree took %v.", time.Since(start)) | |
722 feedback.Info("Number of triangles to clip %d.", len(removed)) | |
723 | |
724 start = time.Now() | |
725 | |
726 tin.EPSG = epsg | |
727 | |
728 builder := octree.NewBuilder(tin) | |
729 builder.Build(removed) | |
730 octreeIndex, err := builder.Bytes() | |
731 if err != nil { | |
732 return nil, err | |
733 } | |
734 feedback.Info("Building octree took %v.", time.Since(start)) | |
735 | |
736 start = time.Now() | |
737 h := sha1.New() | |
738 h.Write(octreeIndex) | |
739 checksum := hex.EncodeToString(h.Sum(nil)) | |
740 _, err = tx.ExecContext(ctx, insertOctreeSQL, id, checksum, octreeIndex) | |
741 if err != nil { | |
742 return nil, err | |
743 } | |
744 feedback.Info("Storing octree index took %s.", time.Since(start)) | |
745 | |
746 tree := builder.Tree() | |
747 | |
748 start = time.Now() | |
749 err = generateContours(ctx, tx, tree, id) | |
750 if err != nil { | |
751 return nil, err | |
752 } | |
753 feedback.Info("Generating and storing contour lines took %s.", | |
754 time.Since(start)) | |
755 | |
756 // Store for potential later removal. | |
757 if err = track(ctx, tx, importID, "waterway.sounding_results", id); err != nil { | |
758 return nil, err | |
759 } | |
760 | |
761 summary := struct { | |
762 Bottleneck string `json:"bottleneck"` | |
763 Date models.Date `json:"date"` | |
764 Lat float64 `json:"lat"` | |
765 Lon float64 `json:"lon"` | |
766 }{ | |
767 Bottleneck: m.Bottleneck, | |
768 Date: m.Date, | |
769 Lat: lat, | |
770 Lon: lon, | |
771 } | |
772 | |
773 return &summary, nil | |
774 } | |
775 | |
776 // CleanUp removes the folder containing the ZIP file with the | 633 // CleanUp removes the folder containing the ZIP file with the |
777 // the sounding result import. | 634 // the sounding result import. |
778 func (sr *SoundingResult) CleanUp() error { | 635 func (sr *SoundingResult) CleanUp() error { |
779 return os.RemoveAll(sr.Dir) | 636 return os.RemoveAll(sr.Dir) |
780 } | 637 } |