//----------------------------------------------------------------------- // // Copyright (c) 2009 Deltares. All rights reserved. // // B. Faassen // barry.faassen@deltares.nl // 2-9-2009 // Implements the IJkdijk (GageDike) application //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using Deltares.Geometry; using Deltares.Standard; using Deltares.Standard.Extensions; namespace Deltares.Dam.Data { /// /// IJkdijk /// public class GageDikeProcessor : TimeSeriesProcessor { #region Constants /// /// Defines the measured monitoring point piping state parameter id which will be used in the output series /// public const string MeasuredPipingStateParameterID = "measuredPipingState"; /// /// Defines the estimated monitoring point piping state parameter id which will be used in the output series /// public const string EstimatedPipingStateParameterID = "estimatedPipingState"; /// /// Defines the measured transect piping length parameter id which will be used in the output series /// public const string MeasuredPipingLengthParameterID = "measuredPipingLength"; /// /// Defines the estimated transect piping length parameter id which will be used in the output series /// public const string EstimatedPipingLengthParameterID = "estimatedPipingLength"; /// /// Defines the monitoring point parameter id used in the FEWS time serie /// /// /// private const double MonitoringPointStateMissingValue = -998; /// /// /// private const double TransectPipingLengthMissingValue = -997; #endregion /// /// Holds a reference to a lookup table containing all the monitoring points /// which are used for the calculations /// private IDictionary monitoringPoints; /// /// /// private TimeSerie waterLevelSerie; enum MonitoringPointState { PipingChannelPresent = 2, PipingChannelNotPresent = 1 } /// /// Gets the monitoring points which are used for the calculations /// public override IEnumerable OutputLocations { get { return this.monitoringPoints.Keys; } } private class Transect { public double ID; public List> Points; public Dictionary Results; } private class PipingLengthResult { public PolyLine PolyLine; public double? EstimatedLength; public double? MeasuredLength; } /// /// Holds a reference to a lookup table containing all the monitoring transects /// private readonly IDictionary transects; /// /// Holds a reference to the actual piping length calculator /// private readonly GageDikeCalculator calculator = new GageDikeCalculator(); // Holds a reference to the water level serie which will be extracted from // the input time series public GageDikeProcessor() { this.monitoringPoints = new Dictionary(); this.transects = new Dictionary(); OutputParameters = new List { MonitoringPointParameterID }; } /// /// Loads the water level time series from the "FEWS format" xml file /// /// The FEWS xml file containing the water level time series public void LoadWaterLevelTimeSerie(string waterLevelInputFile) { ThrowHelper.ThrowWhenConditionIsTrue( StringResourceNames.WaterLevelInputFileNullOrEmpty, () => string.IsNullOrEmpty(waterLevelInputFile) || waterLevelInputFile.Trim() == ""); LoadInputTimeSeries(waterLevelInputFile); // set the water level series waterLevelSerie = this.InputTimeSeriesCollection.FindSerie(WaterLevelInputParameterID, WaterLevelInputLocationID); // Load all the monitoring point parameter id's Initialize(); } /// /// Gets the water level from the water level serie /// /// The time entry to look for in the time serie /// A water level protected double GetWaterLevelOutside(DateTime dateTime) { var entry = waterLevelSerie.Entries.FirstOrDefault(e => e.DateTime == dateTime); return entry != null ? entry.Value : waterLevelSerie.MissVal; } /// /// Resets the monitoring point list /// public override void Initialize() { this.monitoringPoints.Clear(); this.transects.Clear(); if (InputTimeSeriesCollection == null) return; // No time series to process so end this method // Load all the monitoring point parameter id's and convert the ids to points // Result is a dictionary where the keys are the names/locationdId of the points in the time series this.monitoringPoints = this.InputTimeSeriesCollection.Series .Where(serie => serie.LocationId != WaterLevelInputLocationID) // <- filter out the water level serie .ToDictionary(key => key.LocationId, element => ConvertToPoint(element.LocationId)); // Groups the monitoring points into transects // ie. each "GeometryPoint 1.0/1.0" belongs to transect 1.0 and "GeometryPoint 1.0/2.5" to 2.5 var groupedMonitoringPoints = from point in monitoringPoints group point by point.Value.Y into pointGroup orderby pointGroup.Key select pointGroup; // Create a polyline result set table for the transects foreach (var group in groupedMonitoringPoints) { foreach (var point in group) { if (!this.transects.ContainsKey(group.Key)) { var transect = new Transect { ID = group.Key, Points = new List>() }; this.transects.Add(group.Key, transect); } this.transects[group.Key].Points.Add(point); this.transects[group.Key].Results = new Dictionary(); } } // Get a shallow copy of the input serie as a starting point this.OutputTimeSeriesCollection = InputTimeSeriesCollection.GetShallowCopy(); } public override void Process() { Initialize(); base.Process(); } /// /// Processes two new FEWS time series files (transects and monitoring points) from the /// time serie input file for the gage dike (IJkdijk). /// protected override void CreateOutputSeries() { // Process the time serie entries for each monitoring point and add them to the output AddMonitoringPointOutputSeries(); // Process the transect time serie entries series and add them to the output series AddTransectSeries(); } /// /// Adds the monitoring point series to the output collection /// private void AddMonitoringPointOutputSeries() { var functionsToProcess = new List> { // measured serie inputTimeSerie => { var monitoringPointId = inputTimeSerie.LocationId; var outputTimeSerie = inputTimeSerie.Map( MeasuredPipingStateParameterID, monitoringPoint => monitoringPoint.Map(MonitoringPointStateMissingValue, // pass the missing value in case the setter fails (see Map function) // the function to apply to each time serie entry entry => { double? result = GetMeasuredMonitoringPointResult(monitoringPointId, entry); return result ?? MonitoringPointStateMissingValue; })); outputTimeSerie.MissVal = MonitoringPointStateMissingValue; return outputTimeSerie; }, // estimated serie inputTimeSerie => { var monitoringPointId = inputTimeSerie.LocationId; var outputTimeSerie = inputTimeSerie.Map( EstimatedPipingStateParameterID, monitoringPoint => monitoringPoint.Map(MonitoringPointStateMissingValue, // The result is already (partialy) determined while // processing the measured head => { double? result = GetEstimatedMonitoringPointResult(monitoringPointId, head); return result ?? MonitoringPointStateMissingValue; })); outputTimeSerie.MissVal = MonitoringPointStateMissingValue; return outputTimeSerie; } }; // apply the map function for each location to each time serie data entry in the input serie this.OutputTimeSeriesCollection.Series.AddRange( InputTimeSeriesCollection.Map( OutputLocations, OutputParameters.Single(), // works only with one parameter needs to be refactored functionsToProcess )); } /// /// Adds the monitoring point transects to the output collection /// private void AddTransectSeries() { foreach (var transect in this.transects) { // measured transect serie var transectID = transect.Key; var transectSerieName = "Transect " + transect.Key; // parameter ids are irrelevant just select the water level // series because they the result will have exactly the same number of entries var inputSerie = this.InputTimeSeriesCollection.FindSerie(WaterLevelInputParameterID, WaterLevelInputLocationID); var measuredOutputSerie = inputSerie.Map( MeasuredPipingLengthParameterID, transectSerieName, entry => entry.Map( // the function to apply to each time serie entry // The result is already determined while processing the monitoring point series // so just do a look up head => transects[transectID].Results[head.DateTime].MeasuredLength ?? TransectPipingLengthMissingValue)); measuredOutputSerie.MissVal = TransectPipingLengthMissingValue; this.OutputTimeSeriesCollection.Series.Add(measuredOutputSerie); // estimated transect serie var estimatedOutputSerie = inputSerie.Map( EstimatedPipingLengthParameterID, transectSerieName, entry => entry.Map( // the function to apply to each time serie entry // The result is already determined while processing the monitoring point series // so just do a look up waterLevel => transects[transectID].Results[waterLevel.DateTime].EstimatedLength ?? TransectPipingLengthMissingValue)); estimatedOutputSerie.MissVal = TransectPipingLengthMissingValue; this.OutputTimeSeriesCollection.Series.Add(estimatedOutputSerie); } } /// /// Builds a poly line result structure from the input data /// /// /// private void ProcessTransect(double transectKey, DateTime dateTime) { var transect = this.transects[transectKey]; if (!transect.Results.ContainsKey(dateTime)) { var pipingLengthResult = new PipingLengthResult { PolyLine = new PolyLine() }; transect.Results.Add(dateTime, pipingLengthResult); foreach (var point in transect.Points) { // Construct the polyline and filter out monitoring points with // a non valid (missing) value var serie = this.InputTimeSeriesCollection.GetSeriesByLocationId(point.Key).Single(); point.Value.Z = serie.Entries.First(entry => entry.DateTime == dateTime).Value; if (!point.Value.Z.AlmostEquals(serie.MissVal)) pipingLengthResult.PolyLine.Points.Add(point.Value); } pipingLengthResult.MeasuredLength = calculator.CalculateMonitoredPipingLength(pipingLengthResult.PolyLine); var waterLevelOutside = GetWaterLevelOutside(dateTime); calculator.ijkdijkPipingLengthPredictionParameter = Properties.Settings.Default.IJkdijkPipingLengthPredictionParameter; pipingLengthResult.EstimatedLength = calculator.CalculatePredictedPipingLength(waterLevelOutside); } } /// /// Gets the measured monitoring point state result /// /// /// /// private double? GetMeasuredMonitoringPointResult(string monitoringPointId, TimeSerieEntry timeSerieEntry) { var point = this.monitoringPoints[monitoringPointId]; ProcessTransect(point.Y, timeSerieEntry.DateTime); var length = this.transects[point.Y].Results[timeSerieEntry.DateTime].MeasuredLength; if (length == null) return null; return (length > point.X ? (int)MonitoringPointState.PipingChannelPresent : (int)MonitoringPointState.PipingChannelNotPresent); } /// /// Gets the estimated monitoring point state result /// /// /// /// private double? GetEstimatedMonitoringPointResult(string monitoringPointId, TimeSerieEntry head) { var point = this.monitoringPoints[monitoringPointId]; var length = this.transects[point.Y] .Results[head.DateTime].EstimatedLength; if (length == null) return null; return (length > point.X ? (int)MonitoringPointState.PipingChannelPresent : (int)MonitoringPointState.PipingChannelNotPresent); } private GeometryPoint ConvertToPoint(string locationId) { var coordPart = locationId.Split(' '); var coords = coordPart[1].Split('/'); var x = coords[0].ToType(); var y = coords[1].ToType(); return new GeometryPoint() { X = x, Y = y }; } } }