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