// 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.Data;
using System.Data.SQLite;
using System.Linq;
using Core.Common.Base.IO;
using Core.Common.IO.Readers;
using Core.Common.Utils.Builders;
using Ringtoets.Common.IO.Exceptions;
using Ringtoets.Common.IO.Properties;
using Ringtoets.Common.IO.SoilProfile.Schema;
namespace Ringtoets.Common.IO.SoilProfile
{
///
/// This class reads a D-Soil Model file and reads 2d profiles from this database.
///
public class SoilProfile2DReader : SqLiteDatabaseReaderBase, IRowBasedDatabaseReader
{
private IDataReader dataReader;
private PreconsolidationStressReader preconsolidationStressReader;
///
/// Creates a new instance of which will use the
/// as its source.
///
/// The path of the database file to open.
/// Thrown when:
///
/// - The contains invalid characters;
/// - No file could be found at .
///
///
public SoilProfile2DReader(string databaseFilePath) : base(databaseFilePath) {}
///
/// Gets a value indicating whether or not more soil profiles can be read using
/// the .
///
public bool HasNext { get; private set; }
///
/// Initializes the database reader.
///
public void Initialize()
{
PrepareReader();
MoveNext();
}
///
/// Reads the information for the next soil profile from the database and creates a
/// instance of the information.
///
/// The next from the database, or null
/// if no more soil profile can be read.
/// Thrown when reading properties of the profile failed.
/// Thrown when the database returned incorrect
/// values for required properties.
public SoilProfile2D ReadSoilProfile()
{
try
{
return TryReadSoilProfile();
}
catch (SystemException exception) when (exception is FormatException ||
exception is OverflowException ||
exception is InvalidCastException)
{
string message = new FileReaderErrorMessageBuilder(Path).Build(Resources.SoilProfileReader_Error_reading_soil_profile_from_database);
throw new CriticalFileReadException(message, exception);
}
}
public void MoveNext()
{
HasNext = MoveNext(dataReader);
}
public T Read(string columnName)
{
return (T) dataReader[columnName];
}
public T ReadOrDefault(string columnName)
{
object valueObject = dataReader[columnName];
if (valueObject.Equals(DBNull.Value))
{
return default(T);
}
return (T) valueObject;
}
protected override void Dispose(bool disposing)
{
if (dataReader != null)
{
dataReader.Close();
dataReader.Dispose();
dataReader = null;
}
if (preconsolidationStressReader != null)
{
preconsolidationStressReader.Dispose();
preconsolidationStressReader = null;
}
base.Dispose(disposing);
}
///
/// Steps through the result rows until a row is read which' profile id differs from .
///
/// The id of the profile to skip.
private void MoveToNextProfile(long soilProfileId)
{
while (HasNext && Read(SoilProfileTableDefinitions.SoilProfileId).Equals(soilProfileId))
{
MoveNext();
}
}
private void PrepareReader()
{
string soilProfile2DQuery = SoilDatabaseQueryBuilder.GetSoilProfile2DQuery();
try
{
dataReader = CreateDataReader(soilProfile2DQuery);
preconsolidationStressReader = new PreconsolidationStressReader(Path);
preconsolidationStressReader.Initialize();
}
catch (SQLiteException exception)
{
string message = new FileReaderErrorMessageBuilder(Path).Build(Resources.SoilProfileReader_Error_reading_soil_profile_from_database);
throw new CriticalFileReadException(message, exception);
}
}
///
/// Tries to read and create a .
///
/// The read .
/// Thrown when encountering an unrecoverable error
/// while reading the profile.
/// Thrown when reading properties of the profile failed.
private SoilProfile2D TryReadSoilProfile()
{
var criticalProperties = new CriticalProfileProperties(this);
var soilLayerGeometries = new List();
var stresses = new List();
long soilProfileId = criticalProperties.ProfileId;
RequiredProfileProperties properties;
try
{
properties = new RequiredProfileProperties(this, criticalProperties.ProfileName);
for (var i = 1; i <= criticalProperties.LayerCount; i++)
{
soilLayerGeometries.Add(ReadSoilLayerGeometryFrom(this, criticalProperties.ProfileName));
MoveNext();
}
stresses.AddRange(GetPreconsolidationStresses(soilProfileId));
}
catch (SoilProfileReadException)
{
MoveToNextProfile(soilProfileId);
throw;
}
try
{
return new SoilProfile2D(soilProfileId,
criticalProperties.ProfileName,
GetSoilLayers(soilLayerGeometries),
stresses)
{
IntersectionX = properties.IntersectionX
};
}
catch (ArgumentException exception)
{
MoveToNextProfile(soilProfileId);
throw new SoilProfileReadException(
Resources.SoilProfile1DReader_ReadSoilProfile_Failed_to_construct_profile_from_read_data,
criticalProperties.ProfileName,
exception);
}
}
///
/// Gets the preconsolidation stresses belonging to the .
///
/// The current soil profile id.
/// A collection of the read .
/// Thrown when the preconsolidation
/// stresses could not be read.
private PreconsolidationStress[] GetPreconsolidationStresses(long currentSoilProfileId)
{
if (!preconsolidationStressReader.HasNext || preconsolidationStressReader.ReadSoilProfileId() != currentSoilProfileId)
{
return new PreconsolidationStress[0];
}
return preconsolidationStressReader.ReadPreconsolidationStresses().ToArray();
}
private static IEnumerable GetSoilLayers(List soilLayerGeometries)
{
SoilLayer2DLoop[] innerLoops = soilLayerGeometries.SelectMany(slg => slg.InnerLoops).ToArray();
foreach (SoilLayer2DGeometry soilLayerGeometry in soilLayerGeometries)
{
if (IsNestedLayer(innerLoops, soilLayerGeometry))
{
continue;
}
SoilLayer2D soilLayer = CreateSoilLayer2D(soilLayerGeometry);
CreateNestedSoilLayersRecursively(soilLayerGeometries, soilLayerGeometry, soilLayer);
yield return soilLayer;
}
}
private static void CreateNestedSoilLayersRecursively(List soilLayerGeometries, SoilLayer2DGeometry soilLayerGeometry, SoilLayer2D soilLayer)
{
var nestedLayers = new List();
foreach (SoilLayer2DLoop innerLoop in soilLayerGeometry.InnerLoops)
{
SoilLayer2DGeometry nestedSoilLayerGeometry = soilLayerGeometries.First(slg => slg.OuterLoop.Segments.SequenceEqual(innerLoop.Segments));
SoilLayer2D nestedSoilLayer = CreateSoilLayer2D(nestedSoilLayerGeometry);
CreateNestedSoilLayersRecursively(soilLayerGeometries, nestedSoilLayerGeometry, nestedSoilLayer);
nestedLayers.Add(nestedSoilLayer);
}
soilLayer.NestedLayers = StripDuplicateNestedLayers(nestedLayers);
}
private static SoilLayer2D CreateSoilLayer2D(SoilLayer2DGeometry soilLayerGeometry)
{
var soilLayer = new SoilLayer2D(soilLayerGeometry.OuterLoop, soilLayerGeometry.InnerLoops);
SoilLayerHelper.SetSoilLayerBaseProperties(soilLayer, soilLayerGeometry.LayerProperties);
return soilLayer;
}
private static bool IsNestedLayer(IEnumerable innerLoops, SoilLayer2DGeometry soilLayerGeometry)
{
return innerLoops.Any(il => il.Segments.SequenceEqual(soilLayerGeometry.OuterLoop.Segments));
}
private static IEnumerable StripDuplicateNestedLayers(List nestedLayers)
{
return nestedLayers.Where(nl => !nestedLayers.Except(
new[]
{
nl
})
.SelectMany(GetLayersRecursively)
.Any(l => l.OuterLoop.Segments.SequenceEqual(nl.OuterLoop.Segments)));
}
private static IEnumerable GetLayersRecursively(SoilLayer2D soilLayer)
{
var layers = new List
{
soilLayer
};
foreach (SoilLayer2D nestedLayer in soilLayer.NestedLayers)
{
layers.AddRange(GetLayersRecursively(nestedLayer));
}
return layers;
}
///
/// Reads a from the given .
///
/// The reader to read the geometry from.
/// The name of the profile to read the geometry for.
/// A .
/// Thrown when reading properties of the geometry failed.
private static SoilLayer2DGeometry ReadSoilLayerGeometryFrom(IRowBasedDatabaseReader reader, string profileName)
{
var properties = new Layer2DProperties(reader, profileName);
try
{
SoilLayer2DGeometry soilLayerGeometry = new SoilLayer2DGeometryReader().Read(properties.GeometryValue);
soilLayerGeometry.LayerProperties = properties;
return soilLayerGeometry;
}
catch (SoilLayerConversionException e)
{
throw CreateSoilProfileReadException(reader.Path, profileName, e);
}
}
private static SoilProfileReadException CreateSoilProfileReadException(string filePath, string profileName, Exception innerException)
{
string message = new FileReaderErrorMessageBuilder(filePath)
.WithSubject(string.Format(Resources.SoilProfileReader_SoilProfileName_0_, profileName))
.Build(innerException.Message);
return new SoilProfileReadException(message, profileName, innerException);
}
private class Layer2DProperties : LayerProperties
{
///
/// Creates a new instance of which contains properties
/// that are required to create a complete . If these properties
/// cannot be read, then the reader can proceed to the next profile.
///
/// The to read the required layer property values from.
/// The profile name used in generating exceptions messages if casting failed.
/// Thrown when the values in the database could not be
/// casted to the expected column types.
internal Layer2DProperties(IRowBasedDatabaseReader reader, string profileName)
: base(reader, profileName)
{
const string readColumn = SoilProfileTableDefinitions.LayerGeometry;
try
{
GeometryValue = reader.Read(readColumn);
}
catch (InvalidCastException e)
{
string message = string.Format(Resources.SoilProfileReader_Profile_Name_0_has_invalid_value_on_Column_1,
profileName,
readColumn);
throw new SoilProfileReadException(message, profileName, e);
}
}
///
/// Gets the geometry for the layer.
///
public byte[] GeometryValue { get; }
}
private class RequiredProfileProperties
{
///
/// Creates a new instance of which contains properties
/// that are required to create a complete . If these properties
/// cannot be read, then the reader can proceed to the next profile.
///
/// The to read the required
/// profile property values from.
/// The profile name used in generating exceptions messages
/// if casting failed.
/// Thrown when the values in the database could not be
/// casted to the expected column types.
internal RequiredProfileProperties(IRowBasedDatabaseReader reader, string profileName)
{
try
{
IntersectionX = reader.ReadOrDefault(SoilProfileTableDefinitions.IntersectionX) ?? double.NaN;
}
catch (InvalidCastException e)
{
string message = string.Format(Resources.SoilProfileReader_Profile_Name_0_has_invalid_value_on_Column_1,
profileName,
SoilProfileTableDefinitions.IntersectionX);
throw new SoilProfileReadException(message, profileName, e);
}
}
///
/// The 1d intersection of the profile.
///
public double IntersectionX { get; }
}
}
}