// Copyright (C) Stichting Deltares 2024. All rights reserved.
//
// This file is part of the application DAM - UI.
//
// 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.IO;
using System.Linq;
using Deltares.Dam.Data.DataPlugins.Configuration;
using Deltares.Dam.Data.IO;
using Deltares.Standard;
using Deltares.Standard.EventPublisher;
using Deltares.Standard.IO;
using Deltares.Standard.IO.DtoAssembler;
using Deltares.Standard.IO.Xml;
using Deltares.Standard.Language;
using Deltares.Standard.Logging;
using Deltares.Standard.Reflection;
using ProgressDelegate = Deltares.DamEngine.Data.Standard.Calculation.ProgressDelegate;
namespace Deltares.Dam.Data;
public class DamProjectException : Exception
{
public DamProjectException(string message)
: base(message) {}
}
public class DamProject : IDisposable
{
public static ProjectPathLocation ProjectWorkingPathLocation = ProjectPathLocation.InApplicationMap;
private static string testProgramVersion;
public static string UserWorkingPath = GetNewTempDirectory();
public static string ProjectMapWorkingPath = GetNewTempDirectory();
private const string CalculationLogFileExtension = "calclog.txt";
private string projectFileName;
private DamProjectData damProjectData;
public DamProject()
{
damProjectData = new DamProjectData();
}
//[XmlIgnore]
public static string ProjectMap { get; set; }
//[XmlIgnore]
public static string CalculationMap { get; set; }
public string ProjectFileName
{
get => projectFileName;
set
{
DataEventPublisher.BeforeChange(this, "ProjectFileName");
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
{
string propertyName = this.GetMemberName(x => x.DamProjectData);
DataEventPublisher.BeforeChange(this, propertyName);
damProjectData = value;
DataEventPublisher.AfterChange(this, propertyName);
}
}
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";
}
}
}
public static string GetNewTempDirectory()
{
var counter = 1;
while (true)
{
string dir = Path.GetTempPath() + Path.DirectorySeparatorChar + "DAM" + counter;
if (!Directory.Exists(dir) && !File.Exists(dir))
{
return dir;
}
counter++;
}
}
public object ImportWithDefinitionFile(string fileName, DamProjectType damProjectType, ProgressDelegate progress)
{
DataSourceContainer dataSourceContainer = DataSourceContainer.Deserialize(fileName);
ProjectFileName = fileName;
string definitionFileProjectFolder = Path.GetDirectoryName(fileName);
List logMessages;
// import
WaterBoard 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, ProgressDelegate progress)
{
string damProjectFolder = Path.GetDirectoryName(projectFileName);
DataSourceManager.DataSources = DamProjectData.DataSources;
List logMessages;
// import
WaterBoard waterBoard = WaterBoardImporter.ImportDataForDikeRings(
damProjectFolder, dataFolder, dataSourceContainer,
dikeRingIds, damProjectType, progress, out logMessages);
// create data
DamProjectData = new DamProjectData
{
WaterBoard = waterBoard,
DataSourceEsriProjection = dataSourceContainer.DataSourceEsriProjection
};
// 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)
{
damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Add(new DamFailureMechanismeCalculationSpecification());
}
return 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);
}
///
/// 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();
ProjectFileName = fileName;
DataEventPublisher.InvokeWithoutPublishingEvents(() =>
{
var xmlSerializer = new XmlDeserializer();
object project = xmlSerializer.XmlDeserialize(fileName, typeof(DamProjectData), new DefaultClassFactory());
damProjectData = (DamProjectData) project;
if (damProjectData != null)
{
damProjectData.VersionInfo.InitVersionInfoAfterRead();
string damProjectVersion = damProjectVersionXmlHandler.GetDamProjectVersion();
UpdateVersionInfoForNonVersionedFiles(damProjectVersion);
damProjectVersion = GetMainVersion(damProjectVersion);
if (damProjectVersion != null)
{
string currentProgramVersion = About.Version;
if (currentProgramVersion == null)
{
currentProgramVersion = testProgramVersion;
}
currentProgramVersion = GetMainVersion(currentProgramVersion);
if (!currentProgramVersion.Equals(damProjectVersion))
{
if (IsOutDatedFileSupported(damProjectData))
{
damProjectData.ClearResults();
}
else
{
ClearProject();
string message =
LocalizationManager.GetTranslatedText(this, "UnsupportedDamProjectFile");
LogManager.Messages.Add(new LogMessage(LogMessageType.Error, DamProjectData,
message));
}
}
}
}
});
// Add stability as standard mechanism when not yet available.
if (!damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Any())
{
damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Add(new DamFailureMechanismeCalculationSpecification());
UpdateForOlderProjects();
}
if (damProjectData != null)
{
DataEventPublisher.AfterChange(damProjectData, "MaxCalculationCores");
}
return damProjectData;
}
///
/// Assigns the specified 2D-geometry mapname for all dikes where it is not assigned.
///
/// Name of the map.
public void AssignGeometry2DMapnameIfNotAssigned(string mapName)
{
foreach (Dike 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;
}
///
/// 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(() =>
{
EnsureGeometries2DMapWithProject(fileName, savedProjectMap, dataSourceFolder);
var xmlSerializer = new XmlSerializer();
xmlSerializer.Serialize(damProjectData, fileName);
});
}
///
/// Saves the calculation log to text file.
///
/// The calculation messages.
/// Name of the file.
public void SaveCalculationLogToTextFile(List calculationMessages, string fileName)
{
if (calculationMessages != null && calculationMessages.Count > 0)
{
var messages = new List();
foreach (LogMessage calculationMessage in calculationMessages)
{
messages.Add(calculationMessage.MessageType + ": " + calculationMessage.Message + ", " +
calculationMessage.Detail + ", " + calculationMessage.SubjectName);
}
fileName = Path.ChangeExtension(fileName, DateTime.Now.ToFileTime() + CalculationLogFileExtension);
File.WriteAllLines(fileName, messages);
}
}
///
/// Imports the water level time series.
///
public void ImportWaterLevelTimeSeries()
{
if (DamProjectData.DamProjectCalculationSpecification == null)
{
return;
}
string file = DamProjectData.WaterBoard.WaterLevelTimeSeriesFileName;
// check if file exists
if (!file.HasValidStringValue())
{
return;
}
if (!File.Exists(file))
{
return;
}
// load file
TimeSerieCollection fewsInputTimeSerieCollection = TimeSerieCollection.LoadFromFile(file);
var waterBoardJob = DamProjectData.WaterBoardJob as WaterBoardJob;
WaterBoardJobImporter.ImportWaterlevels(waterBoardJob, fewsInputTimeSerieCollection);
}
public void DeleteResults()
{
DeleteResultData();
RemoveCalculationFolder();
}
public void ClearProject()
{
// DeleteResultData();
damProjectData = null;
CalculationMap = "";
ProjectMap = "";
damProjectData = new DamProjectData();
LogManager.Clear();
}
public void Dispose()
{
DamProjectData.Dispose();
}
///
/// 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());
string main = currentVer[0];
string sec = currentVer[1];
return main + "." + sec;
}
return fullVersion;
}
/// Updates the current file version for non-versioned files (projects created with DAM 19.1 or earlier)
///
/// - Set FileVersion to 0 (for DAM versions 18.1.3 and earlier).
/// - Set FileVersion to 1 (for DAM version 19.1.1)
/// - FileVersion 2 is the first versioned file (for DAM version 20.1.1)
///
///
private void UpdateVersionInfoForNonVersionedFiles(string fileVersion)
{
if (DamProjectData.VersionInfo.FileVersionAsRead == 0)
{
// If FileVersionAsRead = 0 then project was written with program version 19.1 or earlier.
// For projects created with versions < 19.1 (e.g. 18.1.3) we keep the FileVersionAsRead on 0
// For projects created with version = 19.1 set the FileVersionAsRead on 1
if (GetMainVersion(fileVersion) == "19.1")
{
DamProjectData.VersionInfo.FileVersionAsRead = 1;
}
}
}
private static bool IsOutDatedFileSupported(DamProjectData projectData)
{
List calculationSpecifications = projectData.DamProjectCalculationSpecification.DamCalculationSpecifications;
foreach (DamFailureMechanismeCalculationSpecification calculationSpecification in calculationSpecifications)
{
if (calculationSpecification.CalculationModel is MStabModelType &&
(calculationSpecification.StabilityModelType != MStabModelType.UpliftVan &&
calculationSpecification.StabilityModelType != MStabModelType.Bishop &&
calculationSpecification.StabilityModelType != MStabModelType.BishopUpliftVan))
{
return false;
}
}
return true;
}
///
/// Updates older projects for missing data or changed data model.
///
private void UpdateForOlderProjects()
{
UpdateMStabParametersSlipCircleDefinition();
}
///
/// 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 damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications)
{
if (damCalculationSpecification.FailureMechanismSystemType == FailureMechanismSystemType.StabilityInside)
{
if (damCalculationSpecification.IsSlipCircleDefinitionUndefined())
{
damCalculationSpecification.ReadUserSettingsSlipCircleDefinition();
}
}
}
}
///
/// 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 (string file in Directory.GetFiles(orgMapname))
{
string fileName = Path.GetFileName(file);
if (fileName != null)
{
string 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 (var 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;
}
string directory = Path.Combine(projectDirectory, soilGeometryDirectory);
return Directory.Exists(directory) ? directory : Path.Combine(dataSourceDirectory, soilGeometryDirectory);
}
private void DeleteResultData()
{
damProjectData.DeleteResults();
}
private static void RemoveCalculationFolder()
{
if (!Directory.Exists(ProjectWorkingPath))
{
return;
}
try
{
Directory.Delete(ProjectWorkingPath, true);
}
catch (IOException e)
{
throw new DamProjectException(e.Message);
}
}
}