// Copyright (C) Stichting Deltares 2017. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Linq; using Core.Common.Base.Geometry; using Core.Common.Base.IO; using Core.Common.IO.Readers; using Ringtoets.Common.Data.AssessmentSection; using Ringtoets.Common.Data.FailureMechanism; using Ringtoets.Common.IO.Properties; namespace Ringtoets.Common.IO.FileImporters { /// /// Imports instances from a shapefile that contains /// one or more polylines and stores them in a . /// public class FailureMechanismSectionsImporter : FileImporterBase { /// /// The snapping tolerance in meters. /// private const double snappingTolerance = 1; /// /// The length tolerance between the reference line and the imported FailureMechanismSections in meters. /// private const double lengthDifferenceTolerance = 1; private readonly ReferenceLine referenceLine; /// /// Initializes a new instance of the class. /// /// The failure mechanism to update. /// The reference line used to check correspondence with. /// The path to the file to import from. /// Thrown when any input argument is null. /// public FailureMechanismSectionsImporter(IFailureMechanism importTarget, ReferenceLine referenceLine, string filePath) : base(filePath, importTarget) { if (referenceLine == null) { throw new ArgumentNullException(nameof(referenceLine)); } this.referenceLine = referenceLine; } protected override bool OnImport() { NotifyProgress(Resources.FailureMechanismSectionsImporter_ProgressText_Reading_file, 1, 3); ReadResult readResults = ReadFailureMechanismSections(); if (readResults.CriticalErrorOccurred || Canceled) { return false; } NotifyProgress(Resources.FailureMechanismSectionsImporter_ProgressText_Validating_imported_sections, 2, 3); IEnumerable readFailureMechanismSections = readResults.Items; if (HasStartOrEndPointsTooFarFromReferenceLine(referenceLine, readFailureMechanismSections)) { LogCriticalFileReadError(Resources.FailureMechanismSectionsImporter_Import_Imported_sections_too_far_from_current_referenceline); return false; } if (IsTotalLengthOfSectionsTooDifferentFromReferenceLineLength(referenceLine, readFailureMechanismSections)) { LogCriticalFileReadError(Resources.FailureMechanismSectionsImporter_Import_Imported_sections_too_different_from_referenceline_length); return false; } if (Canceled) { return false; } NotifyProgress(Resources.Importer_ProgressText_Adding_imported_data_to_data_model, 3, 3); IEnumerable orderedReadSections = OrderSections(readFailureMechanismSections, referenceLine); if (!HasMatchingEndPoints(referenceLine.Points.Last(), orderedReadSections.Last().EndPoint)) { string message = string.Format(Resources.FailureMechanismSectionsImporter_Import_Imported_sections_from_file_0_contains_unchained_sections, FilePath); LogCriticalFileReadError(message); return false; } AddImportedDataToModel(orderedReadSections); return true; } protected override void DoPostImportUpdates() { base.DoPostImportUpdates(); var failureMechanismWithSectionResults = ImportTarget as IHasSectionResults; failureMechanismWithSectionResults?.SectionResults.NotifyObservers(); } protected override void LogImportCanceledMessage() { Log.Info(Resources.FailureMechanismSectionsImporter_Import_canceled_No_data_changed); } private ReadResult ReadFailureMechanismSections() { using (FailureMechanismSectionReader reader = CreateFileReader()) { if (reader == null) { return new ReadResult(true); } return ReadFile(reader); } } private FailureMechanismSectionReader CreateFileReader() { try { return new FailureMechanismSectionReader(FilePath); } catch (ArgumentException e) { LogCriticalFileReadError(e); } catch (CriticalFileReadException e) { LogCriticalFileReadError(e); } return null; } private ReadResult ReadFile(FailureMechanismSectionReader reader) { try { int count = reader.GetFailureMechanismSectionCount(); if (count == 0) { LogCriticalFileReadError(Resources.FailureMechanismSectionsImporter_ReadFile_File_is_empty); return new ReadResult(true); } var importedSections = new FailureMechanismSection[count]; for (var i = 0; i < count; i++) { importedSections[i] = reader.ReadFailureMechanismSection(); } return new ReadResult(false) { Items = importedSections }; } catch (CriticalFileReadException e) { LogCriticalFileReadError(e); return new ReadResult(true); } } private void LogCriticalFileReadError(Exception exception) { LogCriticalFileReadError(exception.Message); } private void LogCriticalFileReadError(string message) { string errorMessage = string.Format(Resources.FailureMechanismSectionsImporter_CriticalErrorMessage_0_No_sections_imported, message); Log.Error(errorMessage); } private static bool HasStartOrEndPointsTooFarFromReferenceLine(ReferenceLine referenceLine, IEnumerable mechanismSections) { foreach (FailureMechanismSection failureMechanismSection in mechanismSections) { if (GetDistanceToReferenceLine(failureMechanismSection.StartPoint, referenceLine) > snappingTolerance) { return true; } if (GetDistanceToReferenceLine(failureMechanismSection.EndPoint, referenceLine) > snappingTolerance) { return true; } } return false; } private static double GetDistanceToReferenceLine(Point2D point, ReferenceLine referenceLine) { return GetLineSegments(referenceLine.Points) .Min(segment => segment.GetEuclideanDistanceToPoint(point)); } private static bool IsTotalLengthOfSectionsTooDifferentFromReferenceLineLength(ReferenceLine referenceLine, IEnumerable mechanismSections) { double totalSectionsLength = mechanismSections.Sum(s => s.Length); return Math.Abs(totalSectionsLength - referenceLine.Length) > lengthDifferenceTolerance; } private void AddImportedDataToModel(IEnumerable failureMechanismSections) { IEnumerable snappedSections = SnapReadSectionsToReferenceLine(failureMechanismSections, referenceLine); ImportTarget.ClearAllSections(); foreach (FailureMechanismSection section in snappedSections) { ImportTarget.AddSection(section); } } private static IEnumerable SnapReadSectionsToReferenceLine(IEnumerable failureMechanismSections, ReferenceLine referenceLine) { double[] orderedSectionLengths = GetReferenceLineCutoffLengths(referenceLine, failureMechanismSections); Point2D[][] splitResults = Math2D.SplitLineAtLengths(referenceLine.Points, orderedSectionLengths); return CreateFailureMechanismSectionsSnappedOnReferenceLine(failureMechanismSections, splitResults); } private static IEnumerable OrderSections(IEnumerable unorderedSections, ReferenceLine referenceLine) { List sourceList = unorderedSections.ToList(); Point2D referenceLineStartPoint = referenceLine.Points.First(); FailureMechanismSection startSection = GetClosestSectionToReferencePoint(referenceLineStartPoint, sourceList); sourceList.Remove(startSection); startSection = ClipSectionCoordinatesToReferencePoint(referenceLineStartPoint, startSection); var resultList = new List(sourceList.Count) { startSection }; GrowTowardsEnd(resultList, sourceList); return resultList; } private static void GrowTowardsEnd(List resultList, List sourceList) { var doneGrowingToEnd = false; while (!doneGrowingToEnd) { Point2D pointToConnect = resultList.Last().EndPoint; FailureMechanismSection closestSectionToConnectWith = GetClosestSectionToReferencePoint(pointToConnect, sourceList); if (closestSectionToConnectWith == null) { doneGrowingToEnd = true; } else { sourceList.Remove(closestSectionToConnectWith); closestSectionToConnectWith = ClipSectionCoordinatesToReferencePoint(pointToConnect, closestSectionToConnectWith); resultList.Add(closestSectionToConnectWith); } } } private static FailureMechanismSection GetClosestSectionToReferencePoint(Point2D referencePoint, IEnumerable sections) { double shortestDistance = double.MaxValue; FailureMechanismSection closestSectionToReferencePoint = null; Dictionary sectionReferenceLineDistances = sections.ToDictionary(s => s, s => Math.Min(referencePoint.GetEuclideanDistanceTo(s.StartPoint), referencePoint.GetEuclideanDistanceTo(s.EndPoint))); foreach (KeyValuePair sectionReferenceLineDistance in sectionReferenceLineDistances) { double distance = sectionReferenceLineDistance.Value; if (distance < shortestDistance && distance <= snappingTolerance) { shortestDistance = sectionReferenceLineDistance.Value; closestSectionToReferencePoint = sectionReferenceLineDistance.Key; } } return closestSectionToReferencePoint; } private static FailureMechanismSection ClipSectionCoordinatesToReferencePoint(Point2D referencePoint, FailureMechanismSection section) { if (referencePoint.GetEuclideanDistanceTo(section.EndPoint) < snappingTolerance) { return new FailureMechanismSection(section.Name, section.Points.Reverse()); } return section; } private static bool HasMatchingEndPoints(Point2D referenceLineEndPoint, Point2D sectionEndPoint) { return referenceLineEndPoint.GetEuclideanDistanceTo(sectionEndPoint) < snappingTolerance; } private static double[] GetReferenceLineCutoffLengths(ReferenceLine referenceLine, IEnumerable orderedReadSections) { double[] orderedSectionLengths = orderedReadSections.Select(section => section.Length).ToArray(); // Correct last section to fully match referenceLine length: double difference = referenceLine.Length - orderedSectionLengths.Sum(l => l); orderedSectionLengths[orderedSectionLengths.Length - 1] += difference; return orderedSectionLengths; } private static IEnumerable GetLineSegments(IEnumerable linePoints) { return Math2D.ConvertPointsToLineSegments(linePoints); } private static IEnumerable CreateFailureMechanismSectionsSnappedOnReferenceLine(IEnumerable orderedReadSections, IEnumerable splitResults) { return orderedReadSections.Select((t, i) => new FailureMechanismSection(t.Name, splitResults.ElementAt(i))).ToList(); } } }