// Copyright (C) Stichting Deltares 2016. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets 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 Core.Common.Base.Data; using Core.Common.Base.IO; using Core.Common.IO.Exceptions; using log4net; using Ringtoets.Common.IO.HydraRing; using Ringtoets.Common.Service; using Ringtoets.Common.Service.ValidationRules; using Ringtoets.HydraRing.Calculation.Calculator; using Ringtoets.HydraRing.Calculation.Calculator.Factory; using Ringtoets.HydraRing.Calculation.Data.Input.WaveConditions; using Ringtoets.HydraRing.Calculation.Exceptions; using Ringtoets.Revetment.Data; using Ringtoets.Revetment.Service.Properties; using RingtoetsCommonFormsResources = Ringtoets.Common.Forms.Properties.Resources; using RingtoetsCommonServiceResources = Ringtoets.Common.Service.Properties.Resources; namespace Ringtoets.Revetment.Service { /// /// Base class for calculating wave conditions for failure mechanisms. /// public abstract class WaveConditionsCalculationServiceBase { private static readonly ILog log = LogManager.GetLogger(typeof(WaveConditionsCalculationServiceBase)); public OnProgressChanged OnProgress; protected int TotalWaterLevelCalculations; private int currentStep = 1; private IWaveConditionsCosineCalculator calculator; /// /// Cancels any currently running wave conditions calculation. /// public void Cancel() { calculator?.Cancel(); Canceled = true; } /// /// Gets whether the calculation is canceled or not. /// protected bool Canceled { get; private set; } /// /// Performs validation over the values on the given . /// Error and status information is logged during the execution of the operation. /// /// The input of the calculation. /// The name of the calculation. /// The directory of the HLCD file that should be used for performing the calculation. /// The name of the design water level property. /// True if has no validation errors; False otherwise. /// Thrown when /// or is null. protected static bool ValidateWaveConditionsInput(WaveConditionsInput waveConditionsInput, string name, string hydraulicBoundaryDatabaseFilePath, string designWaterLevelName) { if (waveConditionsInput == null) { throw new ArgumentNullException(nameof(waveConditionsInput)); } if (designWaterLevelName == null) { throw new ArgumentNullException(nameof(designWaterLevelName)); } CalculationServiceHelper.LogValidationBeginTime(name); string[] messages = ValidateInput(hydraulicBoundaryDatabaseFilePath, waveConditionsInput, designWaterLevelName); CalculationServiceHelper.LogMessagesAsError(RingtoetsCommonServiceResources.Error_in_validation_0, messages); CalculationServiceHelper.LogValidationEndTime(name); return !messages.Any(); } /// /// Performs a wave conditions calculation based on the supplied and returns the output. /// Error and status information is logged during the execution of the operation. /// /// The name of the calculation. /// The that holds all the information required to perform the calculation. /// The 'a' factor decided on failure mechanism level. /// The 'b' factor decided on failure mechanism level. /// The 'c' factor decided on failure mechanism level. /// The norm to use as the target. /// The id of the assessment section for which calculations are performed. /// The filepath of the hydraulic boundary database. /// An of . /// Thrown when is null. /// Thrown when the /// contains invalid characters. /// Thrown when: /// /// No settings database file could be found at the location of /// with the same name. /// Unable to open settings database file. /// Unable to read required data from database file. /// /// /// Thrown when the target probability or /// calculated probability falls outside the [0.0, 1.0] range and is not . /// Thrown when an error occurs during parsing of the Hydra-Ring output. /// Thrown when an error occurs during the calculation. protected IEnumerable CalculateWaveConditions(string calculationName, WaveConditionsInput waveConditionsInput, RoundedDouble a, RoundedDouble b, RoundedDouble c, double norm, string ringId, string hrdFilePath) { if (waveConditionsInput == null) { throw new ArgumentNullException(nameof(waveConditionsInput)); } var outputs = new List(); var waterLevels = waveConditionsInput.WaterLevels.ToArray(); foreach (var waterLevel in waterLevels.TakeWhile(waterLevel => !Canceled)) { try { log.Info(string.Format(Resources.WaveConditionsCalculationService_OnRun_Subject_0_for_waterlevel_1_started, calculationName, waterLevel)); NotifyProgress(waterLevel, currentStep++, TotalWaterLevelCalculations); var output = CalculateWaterLevel(waterLevel, a, b, c, norm, waveConditionsInput, hrdFilePath, ringId, calculationName); if (output != null) { outputs.Add(output); } } finally { log.Info(string.Format(Resources.WaveConditionsCalculationService_OnRun_Subject_0_for_waterlevel_1_ended, calculationName, waterLevel)); } } return outputs; } private static string[] ValidateInput(string hydraulicBoundaryDatabaseFilePath, WaveConditionsInput input, string designWaterLevelName) { var validationResults = new List(); string validationProblem = HydraulicDatabaseHelper.ValidatePathForCalculation(hydraulicBoundaryDatabaseFilePath); if (!string.IsNullOrEmpty(validationProblem)) { validationResults.Add(validationProblem); } else { validationResults.AddRange(ValidateWaveConditionsInput(input, designWaterLevelName)); } return validationResults.ToArray(); } private void NotifyProgress(RoundedDouble waterLevel, int currentStepNumber, int totalStepsNumber) { var message = string.Format(Resources.WaveConditionsCalculationService_OnRun_Calculate_waterlevel_0_, waterLevel); OnProgress?.Invoke(message, currentStepNumber, totalStepsNumber); } /// /// Calculates values for a single water level. /// /// The level of the water. /// The 'a' factor decided on failure mechanism level. /// The 'b' factor decided on failure mechanism level. /// The 'c' factor decided on failure mechanism level. /// The norm to use as the target. /// The input that is different per calculation. /// The path which points to the hydraulic boundary database file. /// The id of the assessment section for which calculations are performed. /// The name used for logging. /// A if the calculation was successful; or null if it was canceled. /// Thrown when the /// contains invalid characters. /// Thrown when: /// /// No settings database file could be found at the location of /// with the same name. /// Unable to open settings database file. /// Unable to read required data from database file. /// /// Thrown when an error occurs while performing the calculation. private WaveConditionsOutput CalculateWaterLevel(RoundedDouble waterLevel, RoundedDouble a, RoundedDouble b, RoundedDouble c, double norm, WaveConditionsInput input, string hydraulicBoundaryDatabaseFilePath, string ringId, string name) { string hlcdDirectory = Path.GetDirectoryName(hydraulicBoundaryDatabaseFilePath); calculator = HydraRingCalculatorFactory.Instance.CreateWaveConditionsCosineCalculator(hlcdDirectory, ringId); WaveConditionsCosineCalculationInput calculationInput = CreateInput(waterLevel, a, b, c, norm, input, hydraulicBoundaryDatabaseFilePath); var exceptionThrown = false; try { calculator.Calculate(calculationInput); WaveConditionsOutput output = WaveConditionsService.Calculate(waterLevel, calculator.WaveHeight, calculator.WavePeakPeriod, calculator.WaveAngle, calculator.WaveDirection, norm, calculator.ReliabilityIndex); return output; } catch (HydraRingCalculationException) { if (!Canceled) { var lastErrorContent = calculator.LastErrorFileContent; if (string.IsNullOrEmpty(lastErrorContent)) { log.ErrorFormat(CultureInfo.CurrentCulture, Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_0_for_waterlevel_1_no_error_report, name, waterLevel); } else { log.ErrorFormat(CultureInfo.CurrentCulture, Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_0_for_waterlevel_1_click_details_for_last_error_report_2, name, waterLevel, lastErrorContent); } exceptionThrown = true; throw; } return null; } finally { var lastErrorFileContent = calculator.LastErrorFileContent; bool errorOccurred = CalculationServiceHelper.HasErrorOccurred(Canceled, exceptionThrown, lastErrorFileContent); if (errorOccurred) { log.ErrorFormat(CultureInfo.CurrentCulture, Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_0_for_waterlevel_1_click_details_for_last_error_report_2, name, waterLevel, lastErrorFileContent); } log.InfoFormat(Resources.WaveConditionsCalculationService_CalculateWaterLevel_Calculation_temporary_directory_can_be_found_on_location_0, calculator.OutputDirectory); if (errorOccurred) { throw new HydraRingCalculationException(lastErrorFileContent); } } } /// /// Creates the input for a calculation for the given . /// /// The level of the water. /// The 'a' factor decided on failure mechanism level. /// The 'b' factor decided on failure mechanism level. /// The 'c' factor decided on failure mechanism level. /// The norm to use as the target. /// The input that is different per calculation. /// The path to the hydraulic boundary database file. /// A . /// Thrown when the /// contains invalid characters. /// Thrown when: /// /// No settings database file could be found at the location of /// with the same name. /// Unable to open settings database file. /// Unable to read required data from database file. /// /// private static WaveConditionsCosineCalculationInput CreateInput(RoundedDouble waterLevel, RoundedDouble a, RoundedDouble b, RoundedDouble c, double norm, WaveConditionsInput input, string hydraulicBoundaryDatabaseFilePath) { var waveConditionsCosineCalculationInput = new WaveConditionsCosineCalculationInput( 1, input.Orientation, input.HydraulicBoundaryLocation.Id, norm, HydraRingInputParser.ParseForeshore(input), HydraRingInputParser.ParseBreakWater(input), waterLevel, a, b, c); HydraRingSettingsDatabaseHelper.AssignSettingsFromDatabase(waveConditionsCosineCalculationInput, hydraulicBoundaryDatabaseFilePath); return waveConditionsCosineCalculationInput; } private static IEnumerable ValidateWaveConditionsInput(WaveConditionsInput input, string designWaterLevelName) { var messages = new List(); if (input.HydraulicBoundaryLocation == null) { messages.Add(Resources.WaveConditionsCalculationService_ValidateInput_No_HydraulicBoundaryLocation_selected); } else if (double.IsNaN(input.HydraulicBoundaryLocation.DesignWaterLevel)) { messages.Add(string.Format(Resources.WaveConditionsCalculationService_ValidateInput_No_0_DesignWaterLevel_calculated, designWaterLevelName)); } else { if (!input.WaterLevels.Any()) { messages.Add(Resources.WaveConditionsCalculationService_ValidateInput_No_derived_WaterLevels); } messages.AddRange(new UseBreakWaterRule(input).Validate()); if (double.IsNaN(input.Orientation)) { messages.Add(string.Format(RingtoetsCommonServiceResources.Validation_ValidateInput_No_concrete_value_entered_for_ParameterName_0_, ParameterNameExtractor.GetFromDisplayName(RingtoetsCommonFormsResources.Orientation_DisplayName))); } } return messages; } } }