// 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 Core.Common.Base.IO; using Core.Common.IO.Readers; using Core.Common.Utils.Builders; using Ringtoets.Common.IO.Properties; using Ringtoets.Common.IO.SoilProfile.Schema; namespace Ringtoets.Common.IO.SoilProfile { /// /// This class reads a DSoil database file and reads 2d profiles from this database. /// public class SoilProfile2DReader : SqLiteDatabaseReaderBase, IRowBasedDatabaseReader { private IDataReader dataReader; /// /// 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; } base.Dispose(disposing); } private void PrepareReader() { string subQueryGetNumberOfLayerProfile2D = $"SELECT SP2D_ID, COUNT(*) as {SoilProfileTableDefinitions.LayerCount} " + "FROM SoilLayer2D " + "GROUP BY SP2D_ID"; string subQueryGetMaterialPropertiesOfLayer = "SELECT " + "mat.MA_ID, " + $"mat.MA_Name as {SoilProfileTableDefinitions.MaterialName}, " + $"max(case when pn.PN_Name = 'Color' then pv.PV_Value end) {SoilProfileTableDefinitions.Color}, " + $"max(case when pn.PN_Name = 'BelowPhreaticLevelStochast' then s.ST_Dist_Type end) {SoilProfileTableDefinitions.BelowPhreaticLevelDistribution}, " + $"max(case when pn.PN_Name = 'BelowPhreaticLevelStochast' then s.ST_Shift end) {SoilProfileTableDefinitions.BelowPhreaticLevelShift}, " + $"max(case when pn.PN_Name = 'BelowPhreaticLevelStochast' then s.ST_Mean end) {SoilProfileTableDefinitions.BelowPhreaticLevelMean}, " + $"max(case when pn.PN_Name = 'BelowPhreaticLevelStochast' then s.ST_Deviation end) {SoilProfileTableDefinitions.BelowPhreaticLevelDeviation}, " + $"max(case when pn.PN_Name = 'PermeabKxStochast' then s.ST_Dist_Type end) {SoilProfileTableDefinitions.PermeabilityDistribution}, " + $"max(case when pn.PN_Name = 'PermeabKxStochast' then s.ST_Shift end) {SoilProfileTableDefinitions.PermeabilityShift}, " + $"max(case when pn.PN_Name = 'PermeabKxStochast' then s.ST_Mean end) {SoilProfileTableDefinitions.PermeabilityMean}, " + $"max(case when pn.PN_Name = 'PermeabKxStochast' then s.ST_Variation end) {SoilProfileTableDefinitions.PermeabilityCoefficientOfVariation}, " + $"max(case when pn.PN_Name = 'DiameterD70Stochast' then s.ST_Dist_Type end) {SoilProfileTableDefinitions.DiameterD70Distribution}, " + $"max(case when pn.PN_Name = 'DiameterD70Stochast' then s.ST_Shift end) {SoilProfileTableDefinitions.DiameterD70Shift}, " + $"max(case when pn.PN_Name = 'DiameterD70Stochast' then s.ST_Mean end) {SoilProfileTableDefinitions.DiameterD70Mean}, " + $"max(case when pn.PN_Name = 'DiameterD70Stochast' then s.ST_Variation end) {SoilProfileTableDefinitions.DiameterD70CoefficientOfVariation} " + "FROM ParameterNames AS pn " + "LEFT JOIN ParameterValues AS pv USING(PN_ID) " + "LEFT JOIN Stochast AS s USING(PN_ID) " + "JOIN Materials AS mat " + "WHERE pv.MA_ID = mat.MA_ID OR s.MA_ID = mat.MA_ID " + "GROUP BY mat.MA_ID "; string subQueryGetLayerPropertiesOfLayer2D = "SELECT " + "SL2D_ID, " + $"PV_Value as {SoilProfileTableDefinitions.IsAquifer} " + "FROM ParameterNames " + "JOIN LayerParameterValues USING(PN_ID) " + $"WHERE PN_NAME = '{SoilProfileTableDefinitions.IsAquifer}'"; string querySoilProfile21D = "SELECT " + $"sp2d.SP2D_Name as {SoilProfileTableDefinitions.ProfileName}, " + $"layerCount.{SoilProfileTableDefinitions.LayerCount}, " + $"sl2d.GeometrySurface as {SoilProfileTableDefinitions.LayerGeometry}, " + $"mpl.X as {SoilProfileTableDefinitions.IntersectionX}, " + $"{SoilProfileTableDefinitions.MaterialName}, " + $"{SoilProfileTableDefinitions.IsAquifer}, " + $"{SoilProfileTableDefinitions.Color}, " + $"{SoilProfileTableDefinitions.BelowPhreaticLevelDistribution}, " + $"{SoilProfileTableDefinitions.BelowPhreaticLevelShift}, " + $"{SoilProfileTableDefinitions.BelowPhreaticLevelMean}, " + $"{SoilProfileTableDefinitions.BelowPhreaticLevelDeviation}, " + $"{SoilProfileTableDefinitions.DiameterD70Distribution}, " + $"{SoilProfileTableDefinitions.DiameterD70Shift}, " + $"{SoilProfileTableDefinitions.DiameterD70Mean}, " + $"{SoilProfileTableDefinitions.DiameterD70CoefficientOfVariation}, " + $"{SoilProfileTableDefinitions.PermeabilityDistribution}, " + $"{SoilProfileTableDefinitions.PermeabilityShift}, " + $"{SoilProfileTableDefinitions.PermeabilityMean}, " + $"{SoilProfileTableDefinitions.PermeabilityCoefficientOfVariation}, " + $"sp2d.SP2D_ID as {SoilProfileTableDefinitions.SoilProfileId} " + "FROM Mechanism AS m " + "JOIN Segment AS segment USING(ME_ID) " + "JOIN (SELECT SSM_ID, SP1D_ID, SP2D_ID FROM StochasticSoilProfile GROUP BY SSM_ID, SP1D_ID, SP2D_ID) ssp USING(SSM_ID) " + "JOIN SoilProfile2D sp2d USING (SP2D_ID) " + "JOIN (" + subQueryGetNumberOfLayerProfile2D + ") layerCount USING (SP2D_ID) " + "JOIN SoilLayer2D sl2d USING (SP2D_ID) " + "LEFT JOIN MechanismPointLocation mpl USING(ME_ID, SP2D_ID) " + "LEFT JOIN (" + subQueryGetMaterialPropertiesOfLayer + ") materialProperties USING(MA_ID) " + "LEFT JOIN (" + subQueryGetLayerPropertiesOfLayer2D + ") layerProperties USING(SL2D_ID) " + "GROUP BY sp2d.SP2D_ID, sl2d.SL2D_ID;"; try { dataReader = CreateDataReader(querySoilProfile21D); } catch (SQLiteException exception) { string message = new FileReaderErrorMessageBuilder(Path).Build(Resources.SoilProfileReader_Error_reading_soil_profile_from_database); throw new CriticalFileReadException(message, exception); } } private SoilProfile2D TryReadSoilProfile() { var properties = new RequiredProfileProperties(this); var soilLayers = new List(); if (properties.LayerCount == 0) { MoveNext(); } else { for (var i = 1; i <= properties.LayerCount; i++) { soilLayers.Add(ReadSoilLayerFrom(this, properties.ProfileName)); MoveNext(); } } return new SoilProfile2D(properties.ProfileId, properties.ProfileName, soilLayers); } /// /// Reads a from the given . /// /// Thrown when reading properties of the layers failed. private static SoilLayer2D ReadSoilLayerFrom(IRowBasedDatabaseReader reader, string profileName) { var properties = new Layer2DProperties(reader, profileName); return new SoilLayer2D(); } 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 = new FileReaderErrorMessageBuilder(reader.Path) .WithSubject(string.Format(Resources.SoilProfileReader_SoilProfileName_0_, profileName)) .Build(string.Format(Resources.SoilProfileReader_Profile_has_invalid_value_on_Column_0_, 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. /// Thrown when the values in the database could not be /// casted to the expected column types. internal RequiredProfileProperties(IRowBasedDatabaseReader reader) { string readColumn = SoilProfileTableDefinitions.ProfileName; try { ProfileName = reader.Read(SoilProfileTableDefinitions.ProfileName); readColumn = SoilProfileTableDefinitions.IntersectionX; IntersectionX = reader.Read(readColumn); readColumn = SoilProfileTableDefinitions.LayerCount; LayerCount = reader.Read(readColumn); readColumn = SoilProfileTableDefinitions.SoilProfileId; ProfileId = reader.Read(readColumn); } catch (InvalidCastException e) { string message = new FileReaderErrorMessageBuilder(reader.Path) .WithSubject(string.Format(Resources.SoilProfileReader_SoilProfileName_0_, ProfileName)) .Build(string.Format(Resources.SoilProfileReader_Profile_has_invalid_value_on_Column_0_, readColumn)); throw new SoilProfileReadException(message, ProfileName, e); } } /// /// The 1d intersection of the profile. /// public double IntersectionX { get; } /// /// The name of the profile to read. /// public string ProfileName { get; } /// /// The number of layers that the profile has to read. /// public long LayerCount { get; } /// /// Gets the database identifier of the profile. /// public long ProfileId { get; } } } }