// 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();
}
}
}