//-----------------------------------------------------------------------
//
// 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 };
}
}
}