// Copyright (C) Stichting Deltares 2025. 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.Linq;
using Deltares.Dam.Data.DataPlugins;
using Deltares.Dam.Data.DataPlugins.Configuration;
using Deltares.Geometry;
using Deltares.Geotechnics.GeotechnicalGeometry;
using Deltares.Geotechnics.Soils;
using Deltares.Geotechnics.SurfaceLines;
using Deltares.Maps;
using Deltares.Standard;
using Deltares.Standard.EventPublisher;
using Deltares.Standard.Logging;
using ProgressDelegate = Deltares.DamEngine.Data.Standard.Calculation.ProgressDelegate;
namespace Deltares.Dam.Data;
public class DikeImporterException : Exception
{
public DikeImporterException(string message)
: base(message) {}
}
///
/// Import all data into Dike
///
public class DikeImporter
{
//
// Members & Properties
//
private readonly IDataPlugin importer;
private readonly Dike dike = new();
///
/// Initializes a new instance of the class.
///
/// The data folder.
/// The data source container.
/// Type of the dam project.
private DikeImporter(string dataFolder, DataSourceContainer dataSourceContainer, DamProjectType damProjectType)
{
importer = CreateDataPlugin(dataFolder, dataSourceContainer);
importer.DamProjectType = damProjectType;
}
///
/// Imports the data for one dike ring.
///
/// The dam project folder.
/// The data folder.
/// The data source container.
/// Type of the dam project.
/// The progress.
/// The log messages.
///
public static Dike ImportDataForDikeRing(string damProjectFolder, string dataFolder,
DataSourceContainer dataSourceContainer,
DamProjectType damProjectType, ProgressDelegate progress,
out List logMessages)
{
var importer = new DikeImporter(dataFolder, dataSourceContainer, damProjectType);
logMessages = new List();
var dike = new Dike();
try
{
dike = importer.ImportDataForDikeRing(damProjectFolder, progress);
if (string.IsNullOrEmpty(dike.MapForSoilGeometries2D) && !string.IsNullOrEmpty(dataSourceContainer.MapSoilProfile2D))
{
dike.MapForSoilGeometries2D = dataSourceContainer.MapSoilProfile2D;
}
}
catch (Exception e)
{
string errorMessage = e.Message;
Exception innerException = e.InnerException;
while (innerException != null)
{
errorMessage = errorMessage + ";" + innerException.Message;
innerException = innerException.InnerException;
}
logMessages.Add(new LogMessage(LogMessageType.FatalError, null, errorMessage));
}
logMessages.AddRange(importer.ImportCsvLogMessages);
return dike;
}
///
/// Gets the import CSV log messages.
///
///
/// The import CSV log messages.
///
private List ImportCsvLogMessages { get; } = new();
///
/// Creates the data plugin.
///
/// The data folder.
/// The data source container.
///
private static DataPluginImporter CreateDataPlugin(string dataFolder, DataSourceContainer dataSourceContainer)
{
var plugin = new DataPluginImporter();
plugin.SetDataSources(dataFolder, dataSourceContainer.DataSourceList);
plugin.Attributes = dataSourceContainer.DataAttributes;
return plugin;
}
///
/// Imports the data for one dike ring.
///
/// The dam project folder.
/// The progress.
///
private Dike ImportDataForDikeRing(string damProjectFolder, ProgressDelegate progress)
{
importer.DamProjectFolder = damProjectFolder;
// Import dike
bool importOk = ImportDike(progress);
if (!importOk)
{
return dike;
}
// Add dike other data
List importedCsvSoils = importer.Dike.ImportedCsvSoils;
IEnumerable importedCsvAquifers = importer.Dike.Aquifers;
Dike importedDike = ImportDataForDikeRing(importedCsvSoils, importedCsvAquifers, "Dijkring");
ImportBackground();
return importedDike;
}
///
/// Imports the soils.
///
/// The dike.
private void ImportSoils(Dike dike)
{
List soils = GetSoils();
foreach (DamSoil soil in soils)
{
dike.AddDamSoil(soil);
}
}
///
/// Gets the soils.
///
///
private List GetSoils()
{
IEnumerable soilIdList = importer.GetSoilIdList();
List soils = soilIdList.Select(soilId => ImportSoil(soilId)).ToList();
return soils;
}
///
/// Imports a soil.
///
/// The soil identifier.
///
private DamSoil ImportSoil(string soilID)
{
var soil = new DamSoil
{
Name = soilID
};
IEnumerable soilDetails = importer.GetSoilDetails(soilID);
foreach (NameValueParameter soilDetail in soilDetails)
{
SoilUtils.SetParameterFromNameValuePair(soil, soilDetail.ParameterName, soilDetail.ParameterValue);
DataSourceManager.SetSource(soil, soilDetail.ParameterName, soilDetail.Source);
}
return soil;
}
///
/// Imports the data for dike ring.
///
/// The soils as imported from the csv file
/// The aquifers as imported from the csv file
/// The dike ring identifier.
///
private Dike ImportDataForDikeRing(List importedCsvSoils, IEnumerable importedCsvAquifers, string dikeRingId)
{
var dike = new Dike
{
Name = dikeRingId
};
ImportSoils(dike);
ImportSoilProfiles1D(dike);
ImportAquifers(importedCsvAquifers, dike);
ImportSegments(dike);
ImportSurfaceLines(dike);
ImportModelParameters(dike);
ImportLocations(dike);
ImportScenarios(dike);
// Use InvokeWithoutPublishingEvents for performance reasons after introducing SurfaceLine2
DataEventPublisher.InvokeWithoutPublishingEvents(() =>
{
var dikeCoordinateSystemConverter = new DikeCoordinateSystemConverter();
if (dike.Locations.Count > 0)
{
dikeCoordinateSystemConverter.CreateLocalXZObjects(dike);
}
});
ImportCsvLogMessages.AddRange(dike.MakeDataConsistent(importedCsvSoils));
DisposeImportedItem();
return dike;
}
private void DisposeImportedItem()
{
importer.DisposeImportedItem();
}
///
/// Imports the dike.
///
/// The progress.
///
private bool ImportDike(ProgressDelegate progress)
{
// Import dikes
importer.ImportDataForDike(progress);
// add log messages
ImportCsvLogMessages.AddRange(importer.ImportLogMessages);
// import is ok if no fatal messages
return ImportCsvLogMessages.All(message => message.MessageType != LogMessageType.FatalError);
}
///
/// Imports the background layer.
///
private void ImportBackground()
{
IEnumerable geometryFeatures = importer.MapGeometryIdList
.Select(id => importer.GetMapGeometry(id))
.Select(Feature.Create);
foreach (Feature geometry in geometryFeatures)
{
dike.AddBackgroundGeometry(geometry);
}
dike.FillFeatureList();
}
///
/// Imports the scenarios.
///
/// The dike.
private void ImportScenarios(Dike dike)
{
AddScenariosToLocations(dike);
}
///
/// Adds the scenarios to locations.
///
/// The dike.
private void AddScenariosToLocations(Dike dike)
{
foreach (Location location in dike.Locations)
{
IEnumerable scenarioList = importer.GetScenarioList(location.Name);
foreach (string scenarioId in scenarioList)
{
IEnumerable nameValues =
importer.GetScenarioDetails(location.Name, scenarioId);
var scenario = new Scenario
{
LocationScenarioID = scenarioId
};
foreach (NameValueParameter nameValueParameter in nameValues)
{
scenario.SetParameterFromNameValuePair(
nameValueParameter.ParameterName,
nameValueParameter.ParameterValue);
}
scenario.Location = location;
location.Scenarios.Add(scenario);
}
}
}
///
/// Imports the locations.
///
/// The dike.
private void ImportLocations(Dike dike)
{
IEnumerable locationIdList = importer.GetLocationIdList();
foreach (string locationId in locationIdList)
{
var location = new Location
{
Name = locationId
};
Info locationInfo = importer.GetLocationInfo(locationId);
location.Name = locationInfo.Name;
DataSourceManager.SetSource(location, "Name", locationInfo.Source);
location.Description = locationInfo.Description;
DataSourceManager.SetSource(location, "Description", locationInfo.Source);
IEnumerable locationDetails = importer.GetLocationDetails(locationId);
foreach (NameValueParameter locationDetail in locationDetails)
{
location.SetParameterFromNameValuePair(locationDetail.ParameterName, locationDetail.ParameterValue);
DataSourceManager.SetSource(location, locationDetail.ParameterName, locationDetail.Source);
if (locationDetail.ParameterName.Equals(LocationParameterNames.SegmentId))
{
Segment segment = dike.Segments.FirstOrDefault(x => x.Name.Equals(locationDetail.ParameterValue));
location.Segment = segment;
}
// See comment below, ID is still needed (this was proved with old tests)
if (locationDetail.ParameterName.Equals(LocationParameterNames.SurfaceLineId))
{
SurfaceLine2 surfaceLine = dike.SurfaceLines2.FirstOrDefault(x => x.Name.Equals(locationDetail.ParameterValue));
location.SurfaceLine2 = surfaceLine;
}
// TODO: Assign PL1-line
}
// According to Erik Vastenburg, locations and surfacelines must match for matching pairs! So to find proper surfaceline
// for location, just search for it by name in surfacelines. SurfaceLineId can and should be removed entirely! But see above!
if (location.SurfaceLine2 == null)
{
location.SurfaceLine2 = dike.SurfaceLines2.FirstOrDefault(s => s.Name == location.Name);
}
location.SoilList = dike.SoilList;
dike.Locations.Add(location);
}
dike.SortLocations();
}
///
/// Imports the model parameters.
///
/// The dike.
private void ImportModelParameters(Dike dike)
{
IEnumerable modelParameters = importer.GetDikeParameters();
foreach (NameValueParameter modelParameter in modelParameters)
{
dike.SetParameterFromNameValuePair(modelParameter.ParameterName, modelParameter.ParameterValue);
DataSourceManager.SetSource(dike, modelParameter.ParameterName, modelParameter.Source);
}
}
///
/// Imports the surface lines.
///
/// The dike.
private void ImportSurfaceLines(Dike dike)
{
// Use InvokeWithoutPublishingEvents for performance reasons after introducing SurfaceLine2
DataEventPublisher.InvokeWithoutPublishingEvents(() =>
{
IEnumerable surfaceLineIdList = importer.GetSurfaceLineIdList();
foreach (string surfaceLineId in surfaceLineIdList)
{
var surfaceLine = new SurfaceLine2
{
Name = surfaceLineId,
Geometry = new LocalizedGeometryPointString(),
CharacteristicPoints =
{
GeometryMustContainPoint = true
}
};
// load the surface line points
IList surfaceLinePoints = importer.GetSurfaceLinePoints(surfaceLineId);
foreach (GeometryPoint surfaceLinePoint in surfaceLinePoints)
{
surfaceLine.AddCharacteristicPoint(surfaceLinePoint);
}
// load the characteristic points
IEnumerable characteristicPoints = importer.GetSurfaceLineCharacteristicPoints(surfaceLineId);
foreach (DpCharacteristicPoint cp in characteristicPoints)
{
var cpType = (CharacteristicPointType) Enum.Parse(
typeof(CharacteristicPointType), cp.Id);
surfaceLine.EnsurePointOfType(cp.X, cp.Y, cp.Z, cpType);
}
// add the surface line to the dike
dike.SurfaceLines2.Add(surfaceLine);
}
});
}
///
/// Imports the segments.
///
/// The dike.
private void ImportSegments(Dike dike)
{
IEnumerable segmentIdList = importer.GetSegmentIdList();
foreach (string segmentID in segmentIdList)
{
var segment = new Segment
{
Name = segmentID
};
foreach (FailureMechanismSystemType failureMechanismSystemType in Enum.GetValues(typeof(FailureMechanismSystemType)))
{
AddSoilProfiles(segment, segmentID, failureMechanismSystemType, dike);
AddSoilGeometry(segment, segmentID, failureMechanismSystemType);
}
dike.Segments.Add(segment);
}
}
///
/// Adds the soil profiles.
///
/// The segment.
/// The segment identifier.
/// Type of the failure mechanism system.
/// The dike.
private void AddSoilProfiles(Segment segment, string segmentID, FailureMechanismSystemType failureMechanismSystemType, Dike dike)
{
IEnumerable profile1DIdListForSegment = importer.GetProfile1DIdListForSegment(segmentID, failureMechanismSystemType);
foreach (string soilProfile1DId in profile1DIdListForSegment)
{
var numberFormatInfo = new NumberFormatInfo
{
NumberDecimalSeparator = "."
};
// get soil profile
IEnumerable soilProfile1DDetails = importer.GetSegmentProfile1DDetails(segmentID, soilProfile1DId, failureMechanismSystemType);
SoilProfile1D soilProfile1D = dike.SoilProfiles.FirstOrDefault(x => x.Name.Equals(soilProfile1DId));
double probability = 0;
foreach (NameValueParameter nameValueparameter in soilProfile1DDetails)
{
if (nameValueparameter.ParameterName.Equals("Probability"))
{
probability = Convert.ToDouble(nameValueparameter.ParameterValue, numberFormatInfo);
}
}
segment.AddSoilProfileProbability(soilProfile1D, probability, failureMechanismSystemType);
}
}
///
/// Adds the soil geometry.
///
/// The segment.
/// The segment identifier.
/// Type of the failure mechanism system.
private void AddSoilGeometry(Segment segment, string segmentID, FailureMechanismSystemType failureMechanismSystemType)
{
IEnumerable profile2DIdListForSegment = importer.GetProfile2DIdListForSegment(segmentID, failureMechanismSystemType);
foreach (string soilGeometry2DName in profile2DIdListForSegment)
{
var numberFormatInfo = new NumberFormatInfo
{
NumberDecimalSeparator = "."
};
IEnumerable soilProfile2DDetails = importer.GetSegmentProfile2DDetails(segmentID, soilGeometry2DName, failureMechanismSystemType);
var probability = 0.0;
foreach (NameValueParameter nameValueparameter in soilProfile2DDetails)
{
if (nameValueparameter.ParameterName.Equals("Probability"))
{
probability = Convert.ToDouble(nameValueparameter.ParameterValue, numberFormatInfo);
}
}
segment.AddSoilGeometry2DProbability(soilGeometry2DName, probability, failureMechanismSystemType);
}
}
///
/// Imports the soil profiles1 d.
///
/// The dike.
private void ImportSoilProfiles1D(Dike dike)
{
IEnumerable soilProfile1DIdList = importer.GetSoilProfile1DIdList();
foreach (string soilProfile1DId in soilProfile1DIdList)
{
dike.SoilProfiles.Add(ImportProfile(soilProfile1DId, dike));
}
}
private static void ImportAquifers(IEnumerable importedCsvAquifers, Dike dike)
{
foreach (Aquifer aquifer in importedCsvAquifers)
{
dike.Aquifers.Add(aquifer);
}
}
///
/// Imports a profile.
///
/// The soil profile1 d identifier.
/// The dike.
///
///
///
private SoilProfile1D ImportProfile(string soilProfile1DId, Dike dike)
{
var soilProfile = new SoilProfile1D
{
Name = soilProfile1DId
};
var bottomLevel = 0.0;
DpSoilProfile dpSoilProfile = importer.GetSoilProfile1DDetails(soilProfile1DId);
for (var layerIndex = 0; layerIndex < dpSoilProfile.Layers.Count; layerIndex++)
{
DpLayer layer = dpSoilProfile.Layers[layerIndex];
if (layer.SoilName == SoilProfile1D.SoilProfileBottomLevelId)
{
// This is a hack to communicate the bottom level via a layer
bottomLevel = layer.TopLevel;
}
else
{
int soilIndex = dike.SoilList.GetSoilIndexByName(layer.SoilName);
if (soilIndex < 0)
{
throw new DikeImporterException($"Soil '{dpSoilProfile.Layers[layerIndex].SoilName}' not found in 1d-profile '{soilProfile1DId}'");
}
soilProfile.Layers.Add(new SoilLayer1D
{
TopLevel = layer.TopLevel,
Soil = dike.SoilList.Soils[soilIndex],
IsAquifer = layer.IsAquifer
});
}
}
soilProfile.EnsureUniqueLayerIds();
soilProfile.BottomLevel = bottomLevel;
if (soilProfile.LayerCount > 0)
{
soilProfile.EnsureLastLayerHasHeight();
}
else
{
throw new DikeImporterException($"Soil 1d-profile '{soilProfile.Name}' has no layers");
}
return soilProfile;
}
}