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