// Copyright (C) Stichting Deltares 2019. All rights reserved. // // This file is part of the application DAM - Clients Library. // // DAM - UI 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.Globalization; using System.Linq; using Deltares.Dam.Data.DataPlugins.Configuration; using Deltares.Soilbase; using Deltares.Standard.EventPublisher; using Deltares.Standard.IO.DtoAssembler; using Deltares.Standard.Reflection; using System.IO; using System.Xml; using Deltares.Dam.Data.IO; using Deltares.Geotechnics.Soils; using Deltares.Standard; using Deltares.Standard.IO; using Deltares.Standard.IO.Xml; using Deltares.Standard.Logging; namespace Deltares.Dam.Data { public class DamProjectException : ApplicationException { public DamProjectException(string message) : base(message) { } } public class DamProject : IDisposable { private string projectFileName; private static string testProgramVersion; private DamProjectData damProjectData; public static ProjectPathLocation ProjectWorkingPathLocation = ProjectPathLocation.InApplicationMap; public static string UserWorkingPath = DamProject.GetNewTempDirectory(); public static string ProjectMapWorkingPath = DamProject.GetNewTempDirectory(); public DamProject() { this.damProjectData = new DamProjectData(); } public static string GetNewTempDirectory() { int counter = 1; while (true) { string dir = Path.GetTempPath() + Path.DirectorySeparatorChar + "DAM" + counter.ToString(); if (!Directory.Exists(dir) && !File.Exists(dir)) { return dir; } else { counter++; } } } //[XmlIgnore] public static string ProjectMap { get; set; } //[XmlIgnore] public static string CalculationMap { get; set; } public string ProjectFileName { get { return this.projectFileName; } set { DataEventPublisher.BeforeChange(this, "ProjectFileName"); this.projectFileName = value; ProjectMap = Path.GetDirectoryName(Path.GetFullPath(value)); CalculationMap = Path.GetFileName(Path.ChangeExtension(value, "Calc")); ProjectMapWorkingPath = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(value)), CalculationMap); //damProjectFolder = Path.GetDirectoryName(value); DataEventPublisher.AfterChange(this, "ProjectFileName"); } } //[XmlIgnore] public DamProjectData DamProjectData { get { return damProjectData; } set { var propertyName = this.GetMemberName(x => x.DamProjectData); DataEventPublisher.BeforeChange(this, propertyName); damProjectData = value; DataEventPublisher.AfterChange(this, propertyName); } } public object ImportWithDefinitionFile(string fileName, DamProjectType damProjectType, DamEngine.Data.Standard.Calculation.ProgressDelegate progress) { var dataSourceContainer = DataSourceContainer.Deserialize(fileName); ProjectFileName = fileName; var definitionFileProjectFolder = Path.GetDirectoryName(fileName); List logMessages; // import var waterBoard = WaterBoardImporter.ImportAllData( definitionFileProjectFolder, dataSourceContainer, damProjectType, progress, out logMessages); //var importer = new WaterBoardImporter(); //var waterBoard = importer.ImportAllData( // definitionFileProjectFolder, dataSourceContainer, progress); //logMessages = importer.importLogMessages.ToList(); // create data DamProjectData = new DamProjectData { WaterBoard = waterBoard }; // handle log messages if (logMessages.Count > 0) { LogManager.Messages.AddRange(logMessages); } // Add stability as standard mechanism when not yet available. if (damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Count == 0) { damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications .Add(new DamFailureMechanismeCalculationSpecification()); } return DamProjectData; } /// /// Imports all the data /// /// The data folder. /// The data source container. /// The dike ring ids. /// Type of the dam project. /// The progress. /// public object Import(string dataFolder, DataSourceContainer dataSourceContainer, IEnumerable dikeRingIds, DamProjectType damProjectType, DamEngine.Data.Standard.Calculation.ProgressDelegate progress) { var damProjectFolder = Path.GetDirectoryName(projectFileName); DataSourceManager.DataSources = DamProjectData.DataSources; List logMessages; // import var waterBoard = WaterBoardImporter.ImportDataForDikeRings( damProjectFolder, dataFolder, dataSourceContainer, dikeRingIds, damProjectType, progress, out logMessages); // create data DamProjectData = new DamProjectData { WaterBoard = waterBoard, DataSourceEsriProjection = dataSourceContainer.DataSourceEsriProjection }; // Make sure Soil properties are same as in database, also build IsAquifer dictionary UpdateSoilPropertiesToSoilDatabase(); // handle logs if (logMessages.Any()) { LogManager.Messages.Clear(); LogManager.Messages.AddRange(logMessages); } // Add stability as standard mechanism when not yet available. if (damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Count == 0) { this.damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications. Add(new DamFailureMechanismeCalculationSpecification()); } return this.DamProjectData; } /// /// Loads DAM project data from the given .damx file. /// /// Name of the file. /// The project data /// /// public static DamProjectData LoadData(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentException("fileName"); if (!File.Exists(fileName)) throw new FileNotFoundException(fileName); if (!fileName.EndsWith("damx")) throw new ArgumentException("No damx file", "fileName"); using (var damProject = new DamProject()) { return damProject.OpenXMLProject(fileName) as DamProjectData; } } /// /// Saves DAM project data to the given .damx file. /// /// If the file already exists it will be overriden /// Name of the file. /// The DAM project data to be saved. public static void SaveData(string fileName, DamProjectData data) { if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentException("fileName"); var project = new DamProject() { DamProjectData = data }; project.SaveXMLProject(fileName, null); } /// /// Gets the main version (first two numbers of full version). /// /// The full version. /// private string GetMainVersion(string fullVersion) { if (fullVersion.Contains(".")) { string[] currentVer = fullVersion.Split(".".ToCharArray()); var main = currentVer[0]; var sec = currentVer[1]; return main + "." + sec; } return fullVersion; } /// /// Sets the test program version. /// Note: To be used by unit tests only. /// /// The test version. public static void SetTestProgramVersion(string testVersion) { testProgramVersion = testVersion; } /// /// Opens the XML project. /// /// Name of the file. /// public object OpenXMLProject(string fileName) { // To retrieve file version from xml, add handler. var damProjectVersionXmlHandler = new DamProjectVersionXmlHandler(); XmlHandler.RegisterObjectHandler(damProjectVersionXmlHandler); ClearProject(); this.ProjectFileName = fileName; DataEventPublisher.InvokeWithoutPublishingEvents(() => { string damProjectFolder = Path.GetDirectoryName(fileName); var xmlSerializer = new XmlDeserializer(); object project = xmlSerializer.XmlDeserialize(fileName, typeof(DamProjectData), new DefaultClassFactory()); this.damProjectData = (DamProjectData)project; if (damProjectData != null) { damProjectData.VersionInfo.InitVersionInfoAfterRead(); } if (damProjectData.DamProjectType == DamProjectType.AssessOld) { ClearProject(); damProjectData.DamProjectType = DamProjectType.AssessOld; } else { var damProjectVersion = damProjectVersionXmlHandler.GetDamProjectVersion(); damProjectVersion = GetMainVersion(damProjectVersion); if (damProjectVersion != null) { var currentProgramVersion = About.Version; if (currentProgramVersion == null) { currentProgramVersion = testProgramVersion; } currentProgramVersion = GetMainVersion(currentProgramVersion); if (!currentProgramVersion.Equals(damProjectVersion)) { damProjectData.ClearResults(); } } } // Project still needs a reference to soilmaterials database; to be resolved later. // This will become obsolete as soon as Delphi DGeoStability version is no longer used. ResolveBackwardCompatibility(); UpdateSoilDatabaseReferences(damProjectFolder); UpdateSoilPropertiesToSoilDatabase(); }); // Add stability as standard mechanism when not yet available. if ((damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Count == 0) && (damProjectData.DamProjectType != DamProjectType.AssessOld)) { damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Add(new DamFailureMechanismeCalculationSpecification()); UpdateForOlderProjects(); } return damProjectData; } private void ResolveBackwardCompatibility() { if (DamProjectData.VersionInfo.FileVersionAsRead == 0) { XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(this.ProjectFileName); } } /// /// Updates older projects for missing data or changed data model. /// private void UpdateForOlderProjects() { UpdateMStabParametersSlipCircleDefinition(); } /// /// Updates the soil properties to the values in the soil database, to avoid discrepancies. /// If the use of the soil database is not necessary anymore (and the soil parameters can be edited in the UI) /// then this method is not needed anymore. /// Also all references of soil in the layers are forced to point to a soil in the SoilList. /// /// After this method every soil has only 1 instance, and is present in the soillist /// private void UpdateSoilPropertiesToSoilDatabase() { foreach (Dike dike in DamProjectData.WaterBoard.Dikes) { UpdateSoilListFromDatabase(dike); Dictionary aquiferDictionary = dike.SoilList.AquiferDictionary; foreach (Location location in dike.Locations) { location.SoilList = dike.SoilList; if (location.Segment != null) { foreach (SoilGeometryProbability soilGeometryProbability in location.Segment.SoilProfileProbabilities) { if (soilGeometryProbability.SoilProfileType == SoilProfileType.SoilProfile1D) { foreach (SoilLayer1D layer in soilGeometryProbability.SoilProfile.Layers) { bool isAquifer; // Assign correct soil to layer Soil newSoil; var index = dike.SoilList.GetSoilIndexByName(layer.Soil.Name); if (index < 0) { Soil databaseSoil = dike.SoilBaseDB.GetSoil(layer.Soil.Name, out isAquifer); newSoil = databaseSoil; dike.SoilList.Soils.Add(newSoil); } else { newSoil = dike.SoilList.Soils[index]; isAquifer = aquiferDictionary[newSoil]; } // Set correct IsAquifer property in layer layer.IsAquifer = isAquifer; layer.Soil = newSoil; } } } } } } } /// /// Update all soil parameters to the data read from database /// /// private static void UpdateSoilListFromDatabase(Dike dike) { using (var geoDatabase = new GeoDatabase(dike.SoilDatabaseName)) { geoDatabase.ReUseSoils = true; // read list from old DB and put them in a dictonary by name var soilList = geoDatabase.ReadSoils(dike.SoilList.Soils); Dictionary soils = soilList.Soils.ToDictionary(s => s.Name); CleanDikeAquiferDictionary(dike); foreach (Soil soil in dike.SoilList.Soils) { var newSoil = soils[soil.Name]; // if the current soil was found in the db list, update the params for the current soil if (newSoil != null) { soil.Assign(newSoil); } // update aquifer dictionary too if (soilList.AquiferDictionary.ContainsKey(newSoil)) { if (dike.SoilList.AquiferDictionary.ContainsKey(soil)) { dike.SoilList.AquiferDictionary[soil] = soilList.AquiferDictionary[newSoil]; } else { dike.SoilList.AquiferDictionary.Add(soil, soilList.AquiferDictionary[newSoil]); } } } } } private static void CleanDikeAquiferDictionary(Dike dike) { var newDict = new Dictionary(); foreach (var soil in dike.SoilList.Soils) { if (dike.SoilList.AquiferDictionary.ContainsKey(soil)) { newDict[soil] = dike.SoilList.AquiferDictionary[soil]; } } dike.SoilList.AquiferDictionary.Clear(); dike.SoilList.AquiferDictionary = newDict; } /// /// Update soil database references: /// Soildatabase should always be in the project path /// Make sure the file reference is adjusted to this /// /// private void UpdateSoilDatabaseReferences(string damProjectFolder) { if (!string.IsNullOrWhiteSpace(damProjectFolder)) { foreach (Dike dike in damProjectData.WaterBoard.Dikes) { if (!string.IsNullOrWhiteSpace(dike.SoilDatabaseName)) { dike.SoilDatabaseName = Path.Combine(damProjectFolder, Path.GetFileName(dike.SoilDatabaseName)); } foreach (Location location in dike.Locations) { location.SoildatabaseName = dike.SoilDatabaseName; } } } } /// /// Updates the MStab parameters slip circle definition. /// For older projects, when slip circle definition was not yet defined we have to provide the default values /// private void UpdateMStabParametersSlipCircleDefinition() { foreach (DamFailureMechanismeCalculationSpecification damCalculationSpecification in this.damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications) { if (damCalculationSpecification.FailureMechanismSystemType == FailureMechanismSystemType.StabilityInside) { if (damCalculationSpecification.IsSlipCircleDefinitionUndefined()) { damCalculationSpecification.ReadUserSettingsSlipCircleDefinition(); } } } } /// /// Assigns the specified 2D-geometry mapname for all dikes where it is not assigned. /// /// Name of the map. public void AssignGeometry2DMapnameIfNotAssigned(string mapName) { foreach (var dike in damProjectData.WaterBoard.Dikes) { if (String.IsNullOrEmpty(dike.MapForSoilGeometries2D)) { dike.MapForSoilGeometries2D = mapName; foreach (Location location in dike.Locations) { location.MapForSoilGeometries2D = mapName; } } } } /// /// Create a destination filename for new location of soilmaterials database /// /// /// /// /// public static string CreateNewGeometry2DMapname(string projectFilename, string orgMapName, int dikeIndex) { string destMapname = Path.GetFileNameWithoutExtension(projectFilename) + ".geometries2D." + dikeIndex.ToString(CultureInfo.InvariantCulture) + @"\"; return destMapname; } /// /// Copies the 2D-geometries. /// /// The original mapname. /// The destination mapname. private void CopyGeometries(string orgMapname, string destMapname) { if (Directory.Exists(orgMapname)) { if (!Directory.Exists(destMapname)) { Directory.CreateDirectory(destMapname); } foreach (var file in Directory.GetFiles(orgMapname)) { string fileName = Path.GetFileName(file); if (fileName != null) { var destFile = Path.Combine(destMapname, fileName); if (File.Exists(destFile)) { File.Delete(destFile); } File.Copy(file, destFile); } } } } /// /// Make sure the map with 2d_geometries is copied besides the project file /// and reference to the map is set to it /// /// the filename of the project /// The org project map in case of file save as. /// The directory in which the .defx file resides. private void EnsureGeometries2DMapWithProject(string fileName, string projectDirectory, string dataSourceDirectory) { for (int dikeIndex = 0; dikeIndex < damProjectData.WaterBoard.Dikes.Count; dikeIndex++) { if (!String.IsNullOrEmpty(damProjectData.WaterBoard.Dikes[dikeIndex].MapForSoilGeometries2D)) { string mapForSoilGeometries = damProjectData.WaterBoard.Dikes[dikeIndex].MapForSoilGeometries2D; string sourceMapName = DetermineGeometriesSource(mapForSoilGeometries, projectDirectory, dataSourceDirectory); // Only copy files if map of geometries is assigned and exists if (!String.IsNullOrEmpty(sourceMapName) && Directory.Exists(sourceMapName)) { string destMapname = CreateNewGeometry2DMapname(fileName, sourceMapName, dikeIndex); string fullFilename = Path.GetFullPath(fileName); string destFullMapname = Path.Combine(Path.GetDirectoryName(fullFilename), destMapname); string orgFullMapname = Path.GetFullPath(sourceMapName); if (!orgFullMapname.Equals(destFullMapname)) { CopyGeometries(orgFullMapname, destFullMapname); } // Store the relative path to the 2d-geometries damProjectData.WaterBoard.Dikes[dikeIndex].MapForSoilGeometries2D = destMapname; foreach (Location location in damProjectData.WaterBoard.Dikes[dikeIndex].Locations) { location.MapForSoilGeometries2D = destMapname; } } } } } /// /// Determines from which folder the soil geometry definitions should be retrieved from. /// /// The directory of the soil geometries as specified by the .defx file /// The directory of the .damx project file. /// The directory of the .defx file. /// The directory of where the soil geometries reside. private static string DetermineGeometriesSource(string soilGeometryDirectory, string projectDirectory, string dataSourceDirectory) { if (Directory.Exists(soilGeometryDirectory)) { return soilGeometryDirectory; } var directory = Path.Combine(projectDirectory, soilGeometryDirectory); return Directory.Exists(directory) ? directory : Path.Combine(dataSourceDirectory, soilGeometryDirectory); } /// /// Create a destination filename for new location of soilmaterials database /// /// /// /// public static string CreateNewSoilMaterialsFilename(string projectFilename, string soilmaterialsFilename, int dikeIndex) { string destsoilmaterialsFilename = Path.GetFileNameWithoutExtension(projectFilename) + dikeIndex.ToString(CultureInfo.InvariantCulture) + ".soilmaterials.mdb"; return destsoilmaterialsFilename; } /// /// Make sure soilamterials file is copied besides the project file /// and reference to the file is set to it /// /// the filename of the project private void EnsureSoilmaterialsFileWithProject(string fileName) { for (int dikeIndex = 0; dikeIndex < damProjectData.WaterBoard.Dikes.Count; dikeIndex++) { string path = Path.GetFullPath(damProjectData.WaterBoard.Dikes[dikeIndex].SoilDatabaseName); // ThrowIfMoreThanOneDikeInProject(); Dike dike = this.DamProjectData.WaterBoard.Dikes[dikeIndex]; string sourceSoilmaterialsFilename = damProjectData.WaterBoard.Dikes[dikeIndex].SoilDatabaseName; string destSoilmaterialsFilename = CreateNewSoilMaterialsFilename(fileName, sourceSoilmaterialsFilename, dikeIndex); string fullFilename = Path.GetFullPath(fileName); string destSoilmaterialsFullFilename = Path.Combine(Path.GetDirectoryName(fullFilename), destSoilmaterialsFilename); string orgSoilmaterialsFullFilename = Path.GetFullPath(sourceSoilmaterialsFilename); if (!orgSoilmaterialsFullFilename.Equals(destSoilmaterialsFullFilename)) { if (File.Exists(destSoilmaterialsFullFilename)) { File.Delete(destSoilmaterialsFullFilename); } if (File.Exists(orgSoilmaterialsFullFilename)) { File.Copy(orgSoilmaterialsFullFilename, destSoilmaterialsFullFilename); } } damProjectData.WaterBoard.Dikes[dikeIndex].SoilDatabaseName = destSoilmaterialsFullFilename; foreach (var location in damProjectData.WaterBoard.Dikes[dikeIndex].Locations) { location.SoildatabaseName = destSoilmaterialsFullFilename; } } } /// /// Save the project in XML format /// /// /// public void SaveXMLProject(string fileName, object project) { // Try to retrieve the data source folder from this object. string dataSourceFolder = null; if (DamProjectData != null) { string damDataSourceFilePath = DamProjectData.DamDataSourceFileName; dataSourceFolder = string.IsNullOrEmpty(damDataSourceFilePath) ? damDataSourceFilePath : Path.GetDirectoryName(damDataSourceFilePath); } // If the data source is not available, try to retrieve it from // the argument. The data source folder must be set in order to // create the geometries 2D.0 folder var damProject = project as DamProject; if (damProject != null && dataSourceFolder == null) { dataSourceFolder = Path.GetDirectoryName(damProject.DamProjectData.DamDataSourceFileName); } fileName = Path.ChangeExtension(fileName, "damx"); string savedProjectMap = ProjectMap; ProjectFileName = fileName; if (savedProjectMap == null) { savedProjectMap = ProjectMap; } damProjectData.VersionInfo.InitVersionInfo(); DataEventPublisher.InvokeWithoutPublishingEvents(() => { EnsureSoilmaterialsFileWithProject(fileName); EnsureGeometries2DMapWithProject(fileName, savedProjectMap, dataSourceFolder); var xmlSerializer = new XmlSerializer(); xmlSerializer.Serialize(damProjectData, fileName); }); } public void ImportWaterLevelTimeSeries() { if (DamProjectData.DamProjectCalculationSpecification == null) return; var file = DamProjectData.WaterBoard.WaterLevelTimeSeriesFileName; // check if file exists if (!file.HasValidStringValue()) return; if (!File.Exists(file)) return; // load file var fewsInputTimeSerieCollection = TimeSerieCollection.LoadFromFile(file); var waterBoardJob = DamProjectData.WaterBoardJob as WaterBoardJob; WaterBoardJobImporter.ImportWaterlevels(waterBoardJob, fewsInputTimeSerieCollection); } public static string ProjectWorkingPath { get { switch (ProjectWorkingPathLocation) { case ProjectPathLocation.InUserMap: return UserWorkingPath; case ProjectPathLocation.InProjectMap: return ProjectMapWorkingPath; default: return Path.GetDirectoryName(typeof(DamProject).Assembly.Location) + @"\DamCalculation"; } } } private void DeleteResultData() { damProjectData.DeleteResults(); } public void DeleteResults() { DeleteResultData(); RemoveCalculationFolder(); } public void ClearProject() { // DeleteResultData(); damProjectData = null; DamProject.CalculationMap = ""; DamProject.ProjectMap = ""; damProjectData = new DamProjectData(); LogManager.Clear(); } private static void RemoveCalculationFolder() { if (!Directory.Exists(ProjectWorkingPath)) return; try { Directory.Delete(ProjectWorkingPath, true); } catch (IOException e) { throw new DamProjectException(e.Message); } } public void Dispose() { DamProjectData.Dispose(); } } }