/// <summary> /// Calculates the metalimnion boundaries /// </summary> /// <param name="dataset">The dataset to use</param> /// <param name="thermoclineDepths">The precalculated thermocline depths for the dataset</param> /// <param name="minimumMetalimionSlope">The minimum metalimnion slope</param> /// <returns></returns> public static Dictionary<DateTime, MetalimnionBoundariesDetails> CalculateMetalimnionBoundaries(Dataset dataset, Dictionary<DateTime, ThermoclineDepthDetails> thermoclineDepths, float minimumMetalimionSlope = 0.1f) { var metalimnionBoundaries = new Dictionary<DateTime, MetalimnionBoundariesDetails>(); foreach (var timestamp in thermoclineDepths.Keys) { var depths = dataset.Sensors.Where(x => x.SensorType == "Water_Temperature" && x.CurrentState.Values.ContainsKey(timestamp)).Select(x => x.Elevation).Distinct().OrderBy(x => x).ToArray(); var meanDepths = new float[depths.Length - 1]; for (var i = 0; i < depths.Length - 1; i++) { meanDepths[i] = (depths[i] + depths[i + 1]) / 2; } var metalimnionBoundary = new MetalimnionBoundariesDetails(); var sortedDepths = meanDepths.Union(new[] { thermoclineDepths[timestamp].ThermoclineDepth }).OrderBy(x => x).ToArray(); var sortedDepthsParent = meanDepths.Union(new[] { thermoclineDepths[timestamp].SeasonallyAdjustedThermoclineDepth }).OrderBy(x => x).ToArray(); var points = new Point[meanDepths.Length]; for (var i = 0; i < points.Length; i++) { points[i] = new Point(meanDepths[i], thermoclineDepths[timestamp].DrhoDz[i]); } var slopes = Interpolate(points, sortedDepths).ToArray(); var slopesParent = Interpolate(points, sortedDepthsParent).ToArray(); var thermoclineIndex = Array.IndexOf(slopes.Select(x => x.X).ToArray(), thermoclineDepths[timestamp].ThermoclineDepth); var thermoclineIndexParent = Array.IndexOf(slopesParent.Select(x => x.X).ToArray(), thermoclineDepths[timestamp].SeasonallyAdjustedThermoclineDepth); #region Top metalimnionBoundary.Top = meanDepths[0]; int k; for (k = thermoclineIndex; k > -1; k--) { if (slopes[k].Y < minimumMetalimionSlope) { metalimnionBoundary.Top = sortedDepths[k]; break; } } if (k == -1) k = 0; if (thermoclineIndex - k > 1 && slopes[thermoclineIndex].Y > minimumMetalimionSlope) { var outsidePoints = new List<Point>(); for (var j = k; j <= thermoclineIndex; j++) { outsidePoints.Add(new Point(slopes[j].Y, sortedDepths[j])); } metalimnionBoundary.Top = (float)Interpolate(outsidePoints.ToArray(), new[] { minimumMetalimionSlope })[0].Y; } #endregion #region Bottom metalimnionBoundary.Bottom = meanDepths.Last(); for (k = thermoclineIndex; k < slopes.Length; k++) { if (slopes[k].Y < minimumMetalimionSlope) { metalimnionBoundary.Bottom = sortedDepths[k]; break; } } if (k == slopes.Length) k--; if (k - thermoclineIndex > 1 && slopes[thermoclineIndex].Y > minimumMetalimionSlope) { var outsidePoints = new List<Point>(); for (var j = thermoclineIndex; j <= k; j++) { outsidePoints.Add(new Point(slopes[j].Y, sortedDepths[j])); } metalimnionBoundary.Bottom = (float)Interpolate(outsidePoints.ToArray(), new[] { minimumMetalimionSlope })[0].Y; } #endregion #region IfParent if (thermoclineDepths[timestamp].HasSeaonallyAdjusted) { #region Top metalimnionBoundary.SeasonallyAdjustedTop = meanDepths[0]; int m; for (m = thermoclineIndexParent; m > -1; m--) { if (slopesParent[m].Y < minimumMetalimionSlope) { metalimnionBoundary.SeasonallyAdjustedTop = sortedDepthsParent[m]; break; } } if (m == -1) m = 0; if (thermoclineIndexParent - m > 0 && slopesParent[thermoclineIndexParent].Y > minimumMetalimionSlope) { var outsidePoints = new List<Point>(); for (var j = m; j <= thermoclineIndexParent; j++) { outsidePoints.Add(new Point(slopesParent[j].Y, sortedDepthsParent[j])); } metalimnionBoundary.SeasonallyAdjustedTop = (float)Interpolate(outsidePoints.ToArray(), new[] { minimumMetalimionSlope })[0].Y; } #endregion #region Bottom metalimnionBoundary.SeasonallyAdjustedBottom = meanDepths.Last(); for (m = thermoclineIndexParent; m < slopesParent.Length; m++) { if (slopesParent[m].Y < minimumMetalimionSlope) { metalimnionBoundary.SeasonallyAdjustedBottom = sortedDepthsParent[m]; break; } } if (m == slopes.Length) m--; if (m - thermoclineIndexParent > 0 && slopesParent[thermoclineIndexParent].Y > minimumMetalimionSlope) { var outsidePoints = new List<Point>(); for (var j = thermoclineIndexParent; j <= m; j++) { outsidePoints.Add(new Point(slopesParent[j].Y, sortedDepthsParent[j])); } metalimnionBoundary.SeasonallyAdjustedBottom = (float)Interpolate(outsidePoints.ToArray(), new[] { minimumMetalimionSlope })[0].Y; } #endregion } else { metalimnionBoundary.NoSeasonalFound(); } #endregion metalimnionBoundaries[timestamp] = metalimnionBoundary; } return metalimnionBoundaries; }
/// <summary> /// Calculates the metalimnion boundaries /// </summary> /// <param name="dataset">The dataset to use</param> /// <param name="thermoclineDepths">The precalculated thermocline depths for the dataset</param> /// <param name="minimumMetalimionSlope">The minimum metalimnion slope</param> /// <returns></returns> public static Dictionary <DateTime, MetalimnionBoundariesDetails> CalculateMetalimnionBoundaries(Dataset dataset, Dictionary <DateTime, ThermoclineDepthDetails> thermoclineDepths, float minimumMetalimionSlope = 0.1f) { var metalimnionBoundaries = new Dictionary <DateTime, MetalimnionBoundariesDetails>(); foreach (var timestamp in thermoclineDepths.Keys) { var depths = dataset.Sensors.Where(x => x.SensorType == "Water_Temperature" && x.CurrentState.Values.ContainsKey(timestamp)).Select(x => x.Elevation).Distinct().OrderBy(x => x).ToArray(); var meanDepths = new float[depths.Length - 1]; for (var i = 0; i < depths.Length - 1; i++) { meanDepths[i] = (depths[i] + depths[i + 1]) / 2; } var metalimnionBoundary = new MetalimnionBoundariesDetails(); var sortedDepths = meanDepths.Union(new[] { thermoclineDepths[timestamp].ThermoclineDepth }).OrderBy(x => x).ToArray(); var sortedDepthsParent = meanDepths.Union(new[] { thermoclineDepths[timestamp].SeasonallyAdjustedThermoclineDepth }).OrderBy(x => x).ToArray(); var points = new Point[meanDepths.Length]; for (var i = 0; i < points.Length; i++) { points[i] = new Point(meanDepths[i], thermoclineDepths[timestamp].DrhoDz[i]); } var slopes = Interpolate(points, sortedDepths).ToArray(); var slopesParent = Interpolate(points, sortedDepthsParent).ToArray(); var thermoclineIndex = Array.IndexOf(slopes.Select(x => x.X).ToArray(), thermoclineDepths[timestamp].ThermoclineDepth); var thermoclineIndexParent = Array.IndexOf(slopesParent.Select(x => x.X).ToArray(), thermoclineDepths[timestamp].SeasonallyAdjustedThermoclineDepth); #region Top metalimnionBoundary.Top = meanDepths[0]; int k; for (k = thermoclineIndex; k > -1; k--) { if (slopes[k].Y < minimumMetalimionSlope) { metalimnionBoundary.Top = sortedDepths[k]; break; } } if (k == -1) { k = 0; } if (thermoclineIndex - k > 1 && slopes[thermoclineIndex].Y > minimumMetalimionSlope) { var outsidePoints = new List <Point>(); for (var j = k; j <= thermoclineIndex; j++) { outsidePoints.Add(new Point(slopes[j].Y, sortedDepths[j])); } metalimnionBoundary.Top = (float)Interpolate(outsidePoints.ToArray(), new[] { minimumMetalimionSlope })[0].Y; } #endregion #region Bottom metalimnionBoundary.Bottom = meanDepths.Last(); for (k = thermoclineIndex; k < slopes.Length; k++) { if (slopes[k].Y < minimumMetalimionSlope) { metalimnionBoundary.Bottom = sortedDepths[k]; break; } } if (k == slopes.Length) { k--; } if (k - thermoclineIndex > 1 && slopes[thermoclineIndex].Y > minimumMetalimionSlope) { var outsidePoints = new List <Point>(); for (var j = thermoclineIndex; j <= k; j++) { outsidePoints.Add(new Point(slopes[j].Y, sortedDepths[j])); } metalimnionBoundary.Bottom = (float)Interpolate(outsidePoints.ToArray(), new[] { minimumMetalimionSlope })[0].Y; } #endregion #region IfParent if (thermoclineDepths[timestamp].HasSeaonallyAdjusted) { #region Top metalimnionBoundary.SeasonallyAdjustedTop = meanDepths[0]; int m; for (m = thermoclineIndexParent; m > -1; m--) { if (slopesParent[m].Y < minimumMetalimionSlope) { metalimnionBoundary.SeasonallyAdjustedTop = sortedDepthsParent[m]; break; } } if (m == -1) { m = 0; } if (thermoclineIndexParent - m > 0 && slopesParent[thermoclineIndexParent].Y > minimumMetalimionSlope) { var outsidePoints = new List <Point>(); for (var j = m; j <= thermoclineIndexParent; j++) { outsidePoints.Add(new Point(slopesParent[j].Y, sortedDepthsParent[j])); } metalimnionBoundary.SeasonallyAdjustedTop = (float)Interpolate(outsidePoints.ToArray(), new[] { minimumMetalimionSlope })[0].Y; } #endregion #region Bottom metalimnionBoundary.SeasonallyAdjustedBottom = meanDepths.Last(); for (m = thermoclineIndexParent; m < slopesParent.Length; m++) { if (slopesParent[m].Y < minimumMetalimionSlope) { metalimnionBoundary.SeasonallyAdjustedBottom = sortedDepthsParent[m]; break; } } if (m == slopes.Length) { m--; } if (m - thermoclineIndexParent > 0 && slopesParent[thermoclineIndexParent].Y > minimumMetalimionSlope) { var outsidePoints = new List <Point>(); for (var j = thermoclineIndexParent; j <= m; j++) { outsidePoints.Add(new Point(slopesParent[j].Y, sortedDepthsParent[j])); } metalimnionBoundary.SeasonallyAdjustedBottom = (float)Interpolate(outsidePoints.ToArray(), new[] { minimumMetalimionSlope })[0].Y; } #endregion } else { metalimnionBoundary.NoSeasonalFound(); } #endregion metalimnionBoundaries[timestamp] = metalimnionBoundary; } return(metalimnionBoundaries); }