// Copyright (C) Stichting Deltares 2016. 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.Exceptions; using Core.Common.IO.Readers; using log4net; 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 static readonly ILog log = LogManager.GetLogger(typeof(FailureMechanismSectionsImporter)); 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); ICollection 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_DataModel, 3, 3); AddImportedDataToModel(readFailureMechanismSections); return true; } protected override void LogImportCanceledMessage() { log.Info(Resources.FailureMechanismSectionsImporter_Import_canceled_no_data_read); } 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 { var count = reader.GetFailureMechanismSectionCount(); if (count == 0) { LogCriticalFileReadError(Resources.FailureMechanismSectionsImporter_ReadFile_File_is_empty); return new ReadResult(true); } var importedSections = new FailureMechanismSection[count]; for (int 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) { var errorMessage = string.Format(Resources.FailureMechanismSectionsImporter_CriticalErrorMessage_0_No_sections_imported, message); log.Error(errorMessage); } private static bool HasStartOrEndPointsTooFarFromReferenceLine(ReferenceLine referenceLine, ICollection mechanismSections) { foreach (var failureMechanismSection in mechanismSections) { if (GetDistanceToReferenceLine(failureMechanismSection.GetStart(), referenceLine) > snappingTolerance) { return true; } if (GetDistanceToReferenceLine(failureMechanismSection.GetLast(), 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, ICollection mechanismSections) { var totalSectionsLength = mechanismSections.Sum(s => GetSectionLength(s)); var referenceLineLength = GetLengthOfLine(referenceLine.Points); return Math.Abs(totalSectionsLength - referenceLineLength) > 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 readSections, ReferenceLine referenceLine) { IList orderedReadSections = OrderSections(readSections, referenceLine); double[] orderedSectionLengths = GetReferenceLineCutoffLengths(referenceLine, orderedReadSections); Point2D[][] splitResults = Math2D.SplitLineAtLengths(referenceLine.Points, orderedSectionLengths); return CreateFailureMechanismSectionsSnappedOnReferenceLine(orderedReadSections, splitResults); } private static IList OrderSections(IEnumerable unorderedSections, ReferenceLine referenceLine) { List sourceList = unorderedSections.ToList(); var startSection = GetStart(sourceList, referenceLine); var resultList = new List(sourceList.Count) { startSection }; sourceList.Remove(startSection); GrowTowardsEnd(resultList, sourceList); return resultList; } private static FailureMechanismSection GetStart(List sourceList, ReferenceLine referenceLine) { var shortestDistance = double.MaxValue; FailureMechanismSection closestSectionToReferenceLineStart = null; Dictionary sectionReferenceLineDistances = sourceList.ToDictionary(s => referenceLine.Points.First().GetEuclideanDistanceTo(s.GetStart()), s => s); foreach (var sectionReferenceLineDistance in sectionReferenceLineDistances) { double distance = sectionReferenceLineDistance.Key; if (distance < shortestDistance && distance <= snappingTolerance) { shortestDistance = sectionReferenceLineDistance.Key; closestSectionToReferenceLineStart = sectionReferenceLineDistance.Value; } } return closestSectionToReferenceLineStart; } private static void GrowTowardsEnd(List resultList, List sourceList) { bool doneGrowingToEnd = false; while (!doneGrowingToEnd) { Point2D endPointToConnect = resultList[resultList.Count - 1].GetLast(); var shortestDistance = double.MaxValue; FailureMechanismSection closestSectionToConnectWith = null; Dictionary sectionConnectionDistances = sourceList.ToDictionary(s => endPointToConnect.GetEuclideanDistanceTo(s.GetStart()), s => s); foreach (var sectionConnectionDistance in sectionConnectionDistances) { double distance = sectionConnectionDistance.Key; if (distance < shortestDistance && distance <= snappingTolerance) { shortestDistance = sectionConnectionDistance.Key; closestSectionToConnectWith = sectionConnectionDistance.Value; } } if (closestSectionToConnectWith == null) { doneGrowingToEnd = true; } else { resultList.Add(closestSectionToConnectWith); sourceList.Remove(closestSectionToConnectWith); } } } private static double[] GetReferenceLineCutoffLengths(ReferenceLine referenceLine, IList orderedReadSections) { double[] orderedSectionLengths = orderedReadSections.Select(GetSectionLength).ToArray(); // Correct last section to fully match referenceLine length: double difference = GetLengthOfLine(referenceLine.Points) - orderedSectionLengths.Sum(l => l); orderedSectionLengths[orderedSectionLengths.Length - 1] += difference; return orderedSectionLengths; } private static double GetSectionLength(FailureMechanismSection section) { return GetLengthOfLine(section.Points); } private static double GetLengthOfLine(IEnumerable linePoints) { return GetLineSegments(linePoints).Sum(segment => segment.Length); } private static IEnumerable GetLineSegments(IEnumerable linePoints) { return Math2D.ConvertLinePointsToLineSegments(linePoints); } private static List CreateFailureMechanismSectionsSnappedOnReferenceLine(IList orderedReadSections, Point2D[][] splitResults) { return orderedReadSections.Select((t, i) => new FailureMechanismSection(t.Name, splitResults[i])).ToList(); } } }