Index: DamClients/DamUI/trunk/src/DamClientsLibrary/Deltares.Dam.Data/DamEngineIo/FillXmlInputFromDamUi.cs =================================================================== diff -u -r2954 -r3072 --- DamClients/DamUI/trunk/src/DamClientsLibrary/Deltares.Dam.Data/DamEngineIo/FillXmlInputFromDamUi.cs (.../FillXmlInputFromDamUi.cs) (revision 2954) +++ DamClients/DamUI/trunk/src/DamClientsLibrary/Deltares.Dam.Data/DamEngineIo/FillXmlInputFromDamUi.cs (.../FillXmlInputFromDamUi.cs) (revision 3072) @@ -21,14 +21,19 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Deltares.Dam.Data.Properties; using Deltares.Dam.Data.Sensors; +using Deltares.Dam.Data.StiImporter; +using Deltares.DamEngine.Data.Standard; using Deltares.DamEngine.Io; using Deltares.DamEngine.Io.XmlInput; using Deltares.Geotechnics.Soils; using Deltares.Geotechnics.SurfaceLines; +using Deltares.Standard.Logging; using Soil = Deltares.Geotechnics.Soils.Soil; +using SoilProfile2D = Deltares.DamEngine.Io.XmlInput.SoilProfile2D; using SurfaceLine = Deltares.DamEngine.Io.XmlInput.SurfaceLine; namespace Deltares.Dam.Data.DamEngineIo @@ -60,19 +65,22 @@ TransferStabilityParameters(damProjectData, input); // Process locations - List localLocations = new List(); - foreach (LocationJob locationJob in damProjectData.SelectedLocationJobs) - { - localLocations.Add(locationJob.Location); - } - input.Locations = new DamEngine.Io.XmlInput.Location[localLocations.Count]; - TransferLocations(localLocations, input.Locations); + string soilProfileDirectory = Path.Combine(damProjectData.ProjectPath, dike.MapForSoilGeometries2D); + var soilProfiles2DToImport = new List(); + List localLocations = damProjectData.SelectedLocationJobs.Select(job => job.Location).ToList(); + var filteredJobs = FilterLocationJobsWithSoilProfiles(damProjectData.SelectedLocationJobs, soilProfileDirectory, dike.SoilList, soilProfiles2DToImport) + .ToList(); + var filteredLocations = filteredJobs.Select(loc => loc.Location).ToList(); + input.Locations = new DamEngine.Io.XmlInput.Location[filteredJobs.Count]; + TransferLocations(filteredLocations, input.Locations); + // Process surfacelines + List localSurfaceLines = new List(); - for (int i = 0; i < localLocations.Count; i++) + for (int i = 0; i < filteredLocations.Count; i++) { - var crtSurfaceLine = localLocations[i].LocalXZSurfaceLine2; + var crtSurfaceLine = filteredLocations[i].LocalXZSurfaceLine2; if (!localSurfaceLines.Any(sl => sl.Name.Equals(crtSurfaceLine.Name))) { localSurfaceLines.Add(crtSurfaceLine); @@ -87,6 +95,7 @@ TransferAquiferSoils(dike.SoilList.AquiferDictionary, input); // Process soilprofiles + var localSegments = filteredLocations.Select(loc => loc.Segment).ToList(); if (damProjectData.DamProjectCalculationSpecification.SelectedStabilityKernelType == StabilityKernelType.DamClassicStability) { // Fill 1D profiles in case of DamClassicStability @@ -95,11 +104,12 @@ var profilesCount = dike.SoilProfiles.Count; input.SoilProfiles1D = new DamEngine.Io.XmlInput.SoilProfile1D[profilesCount]; TransferSoilProfiles1D(dike.SoilProfiles, input.SoilProfiles1D); - input.SoilProfiles2D = new DamEngine.Io.XmlInput.SoilProfile2D[dike.SoilProfiles.Count]; +// input.SoilProfiles2D = new DamEngine.Io.XmlInput.SoilProfile2D[dike.SoilProfiles.Count]; } + + input.SoilProfiles2D = soilProfiles2DToImport.ToArray(); } - var localSegments = EnsureInputLocationsSegmentsAreInSegments(damProjectData.WaterBoard.Segments, localLocations); if (damProjectData.DamProjectCalculationSpecification.SelectedStabilityKernelType == StabilityKernelType.WtiMacroStabilityKernel) { // fill 2D profiles in case of WtiMacroStabilityKernel @@ -115,8 +125,8 @@ var soilProfile1D = new Deltares.Geotechnics.Soils.SoilProfile1D(); soilProfile1D.Assign(localSegmentSoilProfileProbability.SoilProfile); soilSurfaceProfile.SoilProfile = soilProfile1D; - soilSurfaceProfile.SurfaceLine2 = localLocations[i].LocalXZSurfaceLine2; - var dikeSoil = localLocations[i].GetDikeEmbankmentSoil(); + soilSurfaceProfile.SurfaceLine2 = filteredLocations[i].LocalXZSurfaceLine2; + var dikeSoil = filteredLocations[i].GetDikeEmbankmentSoil(); soilSurfaceProfile.DikeEmbankmentMaterial = dikeSoil; foreach (SoilLayer2D layer in soilSurfaceProfile.Surfaces) { @@ -753,5 +763,79 @@ } } } + + /// + /// Filters the collection of on whether they have soil profiles to perform calculations with. + /// + /// The collection of to filter. + /// The directory to retrieve the soil profiles from. + /// The containing the available soils. + /// The collection of that is contained by the . + /// A collection of on which calculations can be performed. + private static IEnumerable FilterLocationJobsWithSoilProfiles(IEnumerable locationJobs, + string soilProfileDirectory, + SoilList availableSoils, + List soilProfile) + { + var validJobs = new List(); + foreach (LocationJob locationJob in locationJobs) + { + Location location = locationJob.Location; + try + { + soilProfile.AddRange(GetSoilProfiles(soilProfileDirectory, location.Segment, availableSoils)); + validJobs.Add(locationJob); + } + catch (Exception e) when (e is ConversionException || e is SoilProfileImporterException) + { +// string locationName = location.Name; +// LogManager.Messages.Add(new LogMessage(LogMessageType.Error, null, +// $"Location '{locationName}': The calculations failed with the error message '{e.Message}'")); + LogInvalidLocationJobErrorMessages(location, e.Message); + } + } + + return validJobs; + } + + private static void LogInvalidLocationJobErrorMessages(Location location, string errorMessage) + { + if (location.Scenarios.Any()) + { + foreach (Scenario scenario in location.Scenarios) + { + string message = $"Location '{location.Name}', design scenario '{scenario.LocationScenarioID}': The calculation failed with error message: '{errorMessage}'"; + LogManager.Messages.Add(new LogMessage(LogMessageType.Error, null, message)); + } + } + else + { + string message = $"Location '{location.Name}': The calculation failed with error message: '{errorMessage}'"; + LogManager.Messages.Add(new LogMessage(LogMessageType.Error, null, message)); + } + } + + /// + /// Gets the soil profiles based on the input arguments. + /// + /// The directory to retrieve the soil profiles from. + /// The to retrieve the soil profiles for. + /// The containing the available soils. + /// An array of . + /// Thrown when cannot be accessed. + /// Thrown when the soil profiles could not be successfully imported. + /// Thrown when the soil profiles could not be successfully converted. + private static IEnumerable GetSoilProfiles(string soilProfileDirectory, Segment segment, SoilList soils) + { + + var soilProfiles = new List(); + + var importedSoilProfiles = SoilProfile2DImporter.Import(soilProfileDirectory, segment, soils); + var convertedSoilProfiles = importedSoilProfiles.Select(XmlSoilProfile2DConverter.Convert); + soilProfiles.AddRange(convertedSoilProfiles); + + + return soilProfiles; + } } } Index: DamClients/DamUI/trunk/src/Dam/Tests/DamEngineIo/FillXmlInputFromDamUiTests.cs =================================================================== diff -u -r2957 -r3072 --- DamClients/DamUI/trunk/src/Dam/Tests/DamEngineIo/FillXmlInputFromDamUiTests.cs (.../FillXmlInputFromDamUiTests.cs) (revision 2957) +++ DamClients/DamUI/trunk/src/Dam/Tests/DamEngineIo/FillXmlInputFromDamUiTests.cs (.../FillXmlInputFromDamUiTests.cs) (revision 3072) @@ -21,6 +21,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Deltares.Dam.Data; using Deltares.Dam.Data.DamEngineIo; using Deltares.DamEngine.Io; @@ -37,6 +38,9 @@ using Soil = Deltares.Geotechnics.Soils.Soil; using SoilProfile1D = Deltares.Geotechnics.Soils.SoilProfile1D; using Deltares.Dam.Data.Sensors; +using Deltares.DamEngine.Data.Standard; +using Deltares.Standard.Logging; +using NUnit.Framework.Constraints; namespace Deltares.Dam.Tests.DamEngineIo { @@ -86,8 +90,318 @@ CompareDamProjectData(actualDamProjectData, expectedDamProjectData); } + [Test] + public void GivenDamProjectWithValidLocationsWith2DProfiles_WhenCreatingInput_ThenSelectedLocationsSerialized() + { + // Given + LogManager.Messages.Clear(); + + const string selectedLocationOneName = "SelectedLocationOne"; + const string selectedSegmentOneName = "SelectedSegmentOne"; + const string segmentOneSoilProfileName = "SimpleProfile.sti"; + const string selectedLocationTwoName = "SelectedLocationTwo"; + const string selectedSegmentTwoName = "SelectedSegmentTwo"; + const string segmentTwoSoilProfileName = "Tutorial-1a 10.1.4.3.sti"; + + var selectedLocations = new HashSet(new[] + { + selectedLocationOneName, + selectedLocationTwoName + }); + + // Add the locations with the segments + var dike = new Dike + { + MapForSoilGeometries2D = @"TestData\StiImporter", + SoilList = CreateSoilList(new[] + { + "Soft Clay", + "Muck", + "Sand", + "Peat" + }) + }; + dike.Locations.AddRange(new [] + { + CreateSimpleLocationWithSoilProfile2DSegment(selectedLocationOneName, selectedSegmentOneName, segmentOneSoilProfileName), + CreateSimpleLocationWithSoilProfile2DSegment("LocationTwo", "Segment", "Tutorial-1a 16.1.2.1,sti"), + CreateSimpleLocationWithSoilProfile2DSegment(selectedLocationTwoName, selectedSegmentTwoName, segmentTwoSoilProfileName) + }); + + // Create the project to serialize + var waterBoard = new WaterBoard + { + Dikes = new List + { + dike + } + }; + + var projectData = new DamProjectData(); + FillAnalysisSpecification(projectData); + FillStabilityParameters(projectData); + DamProject.ProjectMap = ""; // Set the folder to be empty so that it runs in the current test directory + projectData.WaterBoard = waterBoard; + foreach (LocationJob locationJob in projectData.LocationJobs) + { + if (selectedLocations.Contains(locationJob.Name)) + { + locationJob.Run = true; + } + } + + // Preconditions + // Assert that the "NotCalculated" calculations are not selected to verify that these: + // - Locations and segments do not end up in the input + // - Do not generate an error message in case something goes wrong. + CollectionAssert.AreEquivalent(selectedLocations, projectData.SelectedLocationJobs.Select(job => job.Name)); + + // When + Input input = FillXmlInputFromDamUi.CreateInput(projectData); + + // Then + // Note that the original test setup of DAMProject --> XML --> DAMProject does not + // work in this context, as there is NO field within DAMProject to contain the SoilProfile2D + // The only way to assert that the selected locations, segments and their profiles are present, + // is to verify the XMLInput objects that are generated by the CreateInput() call. + CollectionAssert.AreEquivalent(selectedLocations, input.Locations.Select(loc => loc.Name)); + CollectionAssert.AreEquivalent(new[] + { + selectedSegmentOneName, + selectedSegmentTwoName + }, input.Segments.Select(segment => segment.Name)); + CollectionAssert.AreEquivalent(new[] + { + segmentOneSoilProfileName, + segmentTwoSoilProfileName + }, input.SoilProfiles2D.Select(profile => profile.Name)); + } + + [Test] + public void GivenDamProjectWithInvalidLocationsWithoutScenariosWith2DProfiles_WhenCreatingInput_ThenOnlyValidLocationsSerializedAndLogsErrorMessage() + { + // Given + LogManager.Messages.Clear(); + + const string selectedLocationOneName = "SelectedLocationOne"; + const string selectedSegmentOneName = "SelectedSegmentOne"; + const string segmentOneSoilProfileName = "SimpleProfile.sti"; + const string selectedLocationTwoName = "SelectedLocationTwo"; + const string selectedSegmentTwoName = "SelectedSegmentTwo"; + const string segmentTwoSoilProfileName = "Tutorial-1a 10.1.4.3.sti"; // Soil profile also contains peat and sand for its layers + + // Add the locations with the segments + var dike = new Dike + { + MapForSoilGeometries2D = @"TestData\StiImporter", + SoilList = CreateSoilList(new[] + { + "Soft Clay", + "Muck" + }) + }; + dike.Locations.AddRange(new[] + { + CreateSimpleLocationWithSoilProfile2DSegment(selectedLocationOneName, selectedSegmentOneName, segmentOneSoilProfileName), + // Soil profile Tutorial-1a 16.1.2.1.sti also contains peat and sand for its layers. + // However, this will not be visible in the log, as it is not selected to be calculated. + CreateSimpleLocationWithSoilProfile2DSegment("LocationTwo", "Segment", "Tutorial-1a 16.1.2.1.sti"), + CreateSimpleLocationWithSoilProfile2DSegment(selectedLocationTwoName, selectedSegmentTwoName, segmentTwoSoilProfileName) + }); + + var selectedLocations = new HashSet(new[] + { + selectedLocationOneName, + selectedLocationTwoName + }); + + // Create the project to serialize + var waterBoard = new WaterBoard + { + Dikes = new List + { + dike + } + }; + + var projectData = new DamProjectData(); + FillAnalysisSpecification(projectData); + FillStabilityParameters(projectData); + DamProject.ProjectMap = ""; // Set the folder to be empty so that it runs in the current test directory + projectData.WaterBoard = waterBoard; + foreach (LocationJob locationJob in projectData.LocationJobs) + { + if (selectedLocations.Contains(locationJob.Name)) + { + locationJob.Run = true; + } + } + + // Preconditions + // Assert that only the selected calculations are present to verify that the unselected jobs: + // - Locations and segments do not end up in the input + // - Do not generate an error message in case something goes wrong. + CollectionAssert.AreEquivalent(selectedLocations, projectData.SelectedLocationJobs.Select(job => job.Name)); + + // When + Input input = FillXmlInputFromDamUi.CreateInput(projectData); + + // Then + // Note that the original test setup of DAMProject --> XML --> DAMProject does not + // work in this context, as there is NO field within DAMProject to contain the SoilProfile2D + // The only way to assert that the selected locations, segments and their profiles are present, + // is to verify the XMLInput objects that are generated by the CreateInput() call. + CollectionAssert.AreEquivalent(new[] + { + selectedLocationOneName + }, input.Locations.Select(loc => loc.Name)); + CollectionAssert.AreEquivalent(new[] + { + selectedSegmentOneName + }, input.Segments.Select(segment => segment.Name)); + CollectionAssert.AreEquivalent(new[] + { + segmentOneSoilProfileName + }, input.SoilProfiles2D.Select(profile => profile.Name)); + + Assert.That(LogManager.Messages, Has.Count.EqualTo(1)); + Assert.That(LogManager.Messages[0].Message, + new StartsWithConstraint($"Location '{selectedLocationTwoName}': The calculation failed with error message:")); + + Assert.That(LogManager.Messages, Has.All.Property(nameof(LogMessage.MessageType)).EqualTo(LogMessageType.Error)); + Assert.That(LogManager.Messages, Has.All.Property(nameof(LogMessage.Subject)).EqualTo(null)); + } + + [Test] + public void GivenDamProjectWithInvalidLocationsWithScenariosWith2DProfiles_WhenCreatingInput_ThenOnlyValidLocationsSerializedAndLogsErrorMessage() + { + // Given + LogManager.Messages.Clear(); + + // Add the locations with the segments + const string selectedLocationOneName = "SelectedLocationOne"; + const string selectedSegmentOneName = "SelectedSegmentOne"; + const string segmentOneSoilProfileName = "SimpleProfile.sti"; + Location validLocation = CreateSimpleLocationWithSoilProfile2DSegment(selectedLocationOneName, selectedSegmentOneName, segmentOneSoilProfileName); + validLocation.Scenarios.AddRange(new[] + { + CreateValidScenario("ValidScenarioOne"), + CreateValidScenario("ValidScenarioTwo") + }); + + const string selectedLocationTwoName = "SelectedLocationTwo"; + const string selectedSegmentTwoName = "SelectedSegmentTwo"; + const string segmentTwoSoilProfileName = "Tutorial-1a 10.1.4.3.sti"; // Soil profile also contains peat and sand for its layers + Location invalidLocation = CreateSimpleLocationWithSoilProfile2DSegment(selectedLocationTwoName, selectedSegmentTwoName, segmentTwoSoilProfileName); + invalidLocation.Scenarios.AddRange(new [] + { + CreateValidScenario("InvalidScenarioOne"), + CreateValidScenario("InvalidScenarioTwo") + }); + + var dike = new Dike + { + MapForSoilGeometries2D = @"TestData\StiImporter", + SoilList = CreateSoilList(new[] + { + "Soft Clay", + "Muck" + }) + }; + dike.Locations.AddRange(new[] + { + validLocation, + invalidLocation + }); + + // Create the project to serialize + var waterBoard = new WaterBoard + { + Dikes = new List + { + dike + } + }; + + var projectData = new DamProjectData(); + FillAnalysisSpecification(projectData); + FillStabilityParameters(projectData); + DamProject.ProjectMap = ""; // Set the folder to be empty so that it runs in the current test directory + projectData.WaterBoard = waterBoard; + foreach (LocationJob locationJob in projectData.LocationJobs) + { + locationJob.Run = true; + } + + // When + Input input = FillXmlInputFromDamUi.CreateInput(projectData); + + // Then + // Note that the original test setup of DAMProject --> XML --> DAMProject does not + // work in this context, as there is NO field within DAMProject to contain the SoilProfile2D + // The only way to assert that the selected locations, segments and their profiles are present, + // is to verify the XMLInput objects that are generated by the CreateInput() call. + CollectionAssert.AreEquivalent(new[] + { + selectedLocationOneName + }, input.Locations.Select(loc => loc.Name)); + CollectionAssert.AreEquivalent(new[] + { + selectedSegmentOneName + }, input.Segments.Select(segment => segment.Name)); + CollectionAssert.AreEquivalent(new[] + { + segmentOneSoilProfileName + }, input.SoilProfiles2D.Select(profile => profile.Name)); + + Assert.That(LogManager.Messages, Has.Count.EqualTo(2)); + Assert.That(LogManager.Messages[0].Message, + new StartsWithConstraint($"Location '{selectedLocationTwoName}', design scenario 'InvalidScenarioOne': The calculation failed with error message:")); + Assert.That(LogManager.Messages[1].Message, + new StartsWithConstraint($"Location '{selectedLocationTwoName}', design scenario 'InvalidScenarioTwo': The calculation failed with error message:")); + + Assert.That(LogManager.Messages, Has.All.Property(nameof(LogMessage.MessageType)).EqualTo(LogMessageType.Error)); + Assert.That(LogManager.Messages, Has.All.Property(nameof(LogMessage.Subject)).EqualTo(null)); + } + #region CreateTestData + private Location CreateSimpleLocationWithSoilProfile2DSegment(string locationName, + string segmentName, + string soilProfile2DName) + { + var surfaceLine = new SurfaceLine2 + { + Name = $"SurfaceLine - {locationName}" + }; + surfaceLine.CharacteristicPoints.Geometry = surfaceLine.Geometry; + AddPointsToSurfaceLines(surfaceLine); + + var random = new Random(21); + var segment = new Segment + { + Name = segmentName + }; + segment.AddSoilGeometry2DProbability(soilProfile2DName, random.NextDouble(), null); + + var location = new Location(locationName) + { + LocalXZSurfaceLine2 = surfaceLine, + Segment = segment + }; + return location; + } + + private static Scenario CreateValidScenario(string scenarioName) + { + var random = new Random(21); + return new Scenario + { + LocationScenarioID = scenarioName, + RiverLevel = random.NextDouble() + }; + } + private DamProjectData CreateExampleDamProjectData() { var damProjectData = new DamProjectData(); @@ -521,7 +835,21 @@ damProjectData.WaterBoard.Segments.Add(segment); } } - + + private static SoilList CreateSoilList(IEnumerable soilNames) + { + var soilList = new SoilList(); + foreach (string soilName in soilNames) + { + soilList.Add(new Soil + { + Name = soilName + }); + } + + return soilList; + } + #endregion CreateTestData [Test]