private void formRepairRange_Shown(object sender, EventArgs e) { comboBoxInterpolateMethod.SelectedIndex = 0; comboBoxRepairInterval.SelectedIndex = 0; rangeQuality = Qualifier.ProcessRange(range); if (rangeQuality == null) { _ = MessageBox.Show(this, "Произошла ошибка при открытии ряда. Возможно, ряд слишком короткий", "Открытие ряда", MessageBoxButtons.OK, MessageBoxIcon.Warning); this.Close(); return; } labelCompletness.Text = "Полнота ряда: " + (rangeQuality.Completeness * 100).ToString("0.00") + "%"; labelMaxEmptySpace.Text = "Максимальный перерыв в измерениях: " + rangeQuality.MaxEmptySpace.TotalDays.ToString("0.000") + " дней"; labelRangeLength.Text = "Длительность ряда: " + range.Length.ToText(); string dt; if (rangeQuality.Intervals.Count > 1) { StandartIntervals min = rangeQuality.Intervals.Min <RangeInterval, StandartIntervals>((i) => i.Interval); StandartIntervals max = rangeQuality.Intervals.Max <RangeInterval, StandartIntervals>((i) => i.Interval); dt = $"минимальный: {min.Description()}, максимальный: {max.Description()}"; } else { dt = rangeQuality.Intervals[0].Interval.Description(); } labelInterval.Text = "Δt: " + dt; }
/// <summary> /// объединяет два диапазона. Началом становится начало диапазона 1, а концом конец диапазона 2. За интервал принимается минимальный из двух /// </summary> /// <param name="rangeInterval1"></param> /// <param name="rangeInterval2"></param> /// <returns></returns> private static RangeInterval combineIntervals(RangeInterval rangeInterval1, RangeInterval rangeInterval2) { int i1 = (int)rangeInterval1.Interval; int i2 = (int)rangeInterval2.Interval; StandartIntervals inter = (StandartIntervals)Math.Min(i1, i2); return(new RangeInterval() { Diapason = new Diapason <DateTime>(rangeInterval1.Diapason.From, rangeInterval2.Diapason.To), Interval = inter }); }
/// <summary> /// обработать ряд и получить сведения о полноте, интервалах, количестве ошибок /// </summary> /// <returns></returns> public static QualityInfo ProcessRange2(RawRange Range) { if (Range.Count < Vars.Options.QualifierSectionLength * 1.5) { return(null); } TimeSpan maxemptyspace = TimeSpan.MinValue; TimeSpan tmp; //поиск наибольшего пустого пропуска for (int i = 1; i < Range.Count; i++) { tmp = Range[i].Date - Range[i - 1].Date; if (tmp > maxemptyspace) { maxemptyspace = tmp; } } //поиск всех интервалов измерений //весь ряд делится на отрезки по 10 измерений. //на каждом отрезке ищется минимальный интервал, он принимается основным для этого отрезка //если в ряде встречаются только такие интервалы, то весь ряд заносится в результат с одним интервалом //иначе границей раздела интервалов берётся гранича разделов отрезков с разными интервалами List <RawItem> range = new List <RawItem>(Range); range.Sort(new DateTimeComparerRawItem()); //делим ряд по 10 измерений и для каждого отрезка находим минимальный интервал List <Diapason <int> > diapasons = new List <Diapason <int> >(); List <StandartIntervals> intervals = new List <StandartIntervals>(); //int k = 0; for (int i = 0; i < range.Count - Vars.Options.QualifierSectionLength; i += Vars.Options.QualifierSectionLength) { Diapason <int> d = new Diapason <int>(i, i + Vars.Options.QualifierSectionLength); TimeSpan minSpan = TimeSpan.MaxValue; for (int j = i; j < i + Vars.Options.QualifierSectionLength; j++) { // k++; TimeSpan nsp = range[j].Date > range[j + 1].Date ? range[j].Date - range[j + 1].Date : range[j + 1].Date - range[j].Date; if (nsp.TotalMinutes == 0) { continue; } if (nsp < minSpan && Enum.IsDefined(typeof(StandartIntervals), (int)nsp.TotalMinutes)) { minSpan = nsp; } } int minutes = (int)minSpan.TotalMinutes; StandartIntervals interval = (StandartIntervals)minutes; diapasons.Add(d); intervals.Add(interval); } //поиск изменений в интервалах //все диапазоны дат, которые меньше Vars.Options.QualifierDaysToNewInterval присоединяются к левой части ряда List <RangeInterval> rangeIntervals = new List <RangeInterval>(); int s1 = 0, s2 = 0; //указатели на элементы в массиве intervals[] (начала первого и второго отрезка) for (int i = 0; i < intervals.Count - 1; i++) { if (intervals[i] != intervals[i + 1]) { int s2_min = (int)range[diapasons[s2].From].DateArgument; int s1_min = (int)range[diapasons[s1].To].DateArgument; int a1 = s1_min - s2_min; //длина первого отрезк в минутах int si_min = (int)range[diapasons[i].To].DateArgument; int a2 = si_min - s2_min; //длина второго отрезк в минутах int minimal = Vars.Options.QualifierDaysToNewInterval * 24 * 60; // длина минимального отрезка в минутах if (s1 == s2) { s2 = i + 1; continue; } if (a2 < minimal) { s2 = i + 1; continue; } if (a2 > minimal && a1 > minimal) //если первый и второй отрезок больше минимального { //добавление первого отрезка (от s1 до s2) DateTime sd = range[diapasons[s1].From].Date; DateTime ed = range[diapasons[s2].To].Date; StandartIntervals inter = intervals[s1]; //bool f = intervals[s1] == intervals[s2]; rangeIntervals.Add(new RangeInterval() { Diapason = new Diapason <DateTime>(sd, ed), Interval = inter }); s1 = s2; s2 = i + 1; } else //если второй отрезок меньше минимального, то переставляем начало второго отрезка (присоединяем второй отрезок к первому) { s2 = i + 1; } } } //добавление последнего DateTime end = range.Last().Date; //конец диапазона DateTime start; if (rangeIntervals.Count > 0) { start = rangeIntervals.Last().Diapason.To; // если последний диапазон с таким же интервалом, как предпоследний, то обновляем последний добавленный интервал if (intervals.Last() == rangeIntervals.Last().Interval) { rangeIntervals.Last().Diapason = new Diapason <DateTime>(start, end); } else { rangeIntervals.Add(new RangeInterval() { Diapason = new Diapason <DateTime>(start, end), Interval = intervals.Last() }); // добавление диапазона } } else { start = range[0].Date; rangeIntervals.Add(new RangeInterval() { Diapason = new Diapason <DateTime>(start, end), Interval = intervals.Last() }); // добавление диапазона } QualityInfo res = new QualityInfo(rangeIntervals, maxemptyspace, range.Count); return(res); }
/// <summary> /// обработать ряд и получить сведения о полноте, интервалах, количестве ошибок /// </summary> /// <returns></returns> public static QualityInfo ProcessRange(RawRange Range) { if (Range.Count < Vars.Options.QualifierSectionLength * 1.5) { return(null); } TimeSpan maxemptyspace = TimeSpan.MinValue; TimeSpan tmp; //поиск наибольшего пустого пропуска for (int i = 1; i < Range.Count; i++) { tmp = Range[i].Date - Range[i - 1].Date; if (tmp > maxemptyspace) { maxemptyspace = tmp; } } //поиск всех интервалов измерений //весь ряд делится на отрезки по 10 измерений. //на каждом отрезке ищется минимальный интервал, он принимается основным для этого отрезка //если в ряде встречаются только такие интервалы, то весь ряд заносится в результат с одним интервалом //иначе границей раздела интервалов берётся гранича разделов отрезков с разными интервалами List <RawItem> range = new List <RawItem>(Range); range.Sort(new DateTimeComparerRawItem()); //делим ряд по 10 измерений и для каждого отрезка находим минимальный интервал List <Diapason <int> > diapasons = new List <Diapason <int> >(); List <StandartIntervals> intervals = new List <StandartIntervals>(); //int k = 0; for (int i = 0; i < range.Count - Vars.Options.QualifierSectionLength; i += Vars.Options.QualifierSectionLength) { Diapason <int> d = new Diapason <int>(i, i + Vars.Options.QualifierSectionLength); TimeSpan minSpan = TimeSpan.MaxValue; for (int j = i; j < i + Vars.Options.QualifierSectionLength; j++) { // k++; TimeSpan nsp = range[j].Date > range[j + 1].Date ? range[j].Date - range[j + 1].Date : range[j + 1].Date - range[j].Date; if (nsp.TotalMinutes == 0) { continue; } if (nsp < minSpan && Enum.IsDefined(typeof(StandartIntervals), (int)nsp.TotalMinutes)) { minSpan = nsp; } if (nsp.TotalDays > Vars.Options.QualifierDaysToBeginMissing) { minSpan = TimeSpan.MaxValue; break; } } StandartIntervals interval; if (minSpan == TimeSpan.MaxValue) { interval = StandartIntervals.Missing; } else { int minutes = (int)minSpan.TotalMinutes; interval = (StandartIntervals)minutes; } diapasons.Add(d); intervals.Add(interval); } //поиск изменений в интервалах и добавление в общий список List <RangeInterval> rangeIntervals = new List <RangeInterval>(); int s1 = 0; for (int i = 0; i < intervals.Count - 1; i++) { if (intervals[i] != intervals[i + 1]) { DateTime sd = range[diapasons[s1].From].Date; DateTime ed = range[diapasons[i].To].Date; StandartIntervals inter = intervals[s1]; rangeIntervals.Add(new RangeInterval() { Diapason = new Diapason <DateTime>(sd, ed), Interval = inter }); s1 = i + 1; } } //добавление последнего DateTime end = range.Last().Date; //конец диапазона DateTime start; if (rangeIntervals.Count > 0) { start = rangeIntervals.Last().Diapason.To; } else { start = range[0].Date; } rangeIntervals.Add(new RangeInterval() { Diapason = new Diapason <DateTime>(start, end), Interval = intervals.Last() }); // добавление диапазона //обход всех найденных диапазонов и объединение маленьких или одинаковых до тех пор, пока будет совершено хоть одно объединение int minimal = Vars.Options.QualifierDaysToNewInterval * 24 * 60; bool hasCompl = true; while (hasCompl) { //если диапазонов нет или один, то выходим if (rangeIntervals.Count == 0 || rangeIntervals.Count == 1) { break; } //если диапазонов два, то ищем маленький диапазон в конце и в начале if (rangeIntervals.Count == 2) { if (rangeIntervals[0].LengthMinutes < minimal || rangeIntervals[1].LengthMinutes < minimal) //объединяем диапазоны и выходим { RangeInterval ri = combineIntervals(rangeIntervals[0], rangeIntervals[1]); rangeIntervals.Clear(); rangeIntervals.Add(ri); hasCompl = true; continue; } break; } //если больше двух диапазонов, то пробуем объединить все маленькие диапазоны hasCompl = false; for (int i = 1; i < rangeIntervals.Count - 1; i++) { RangeInterval r1 = rangeIntervals[i - 1]; RangeInterval ri = rangeIntervals[i]; RangeInterval r2 = rangeIntervals[i + 1]; int i1 = (int)r1.Interval; int ii = (int)ri.Interval; int i2 = (int)r2.Interval; int l1 = r1.LengthMinutes; int li = ri.LengthMinutes; int l2 = r2.LengthMinutes; #region объединение диапазонов с одинаковым интервалом if (i1 == ii) //если у левого и среднего диапазона интервалы одинаковые { RangeInterval rr = combineIntervals(r1, ri); rangeIntervals.Remove(ri); rangeIntervals.Remove(r1); rangeIntervals.Insert(i - 1, rr); hasCompl = true; break; } if (ii == i2) //если у правого и среднего диапазона интервалы одинаковые { RangeInterval rr = combineIntervals(ri, r2); rangeIntervals.Remove(ri); rangeIntervals.Remove(r2); rangeIntervals.Insert(i - 1, rr); hasCompl = true; break; } #endregion #region объединение диапазонов, меньших, чем минимальный if (l1 < minimal) //если левый меньше минимального, то объединяем с средним { RangeInterval rr = combineIntervals(r1, ri); rangeIntervals.Remove(ri); rangeIntervals.Remove(r1); rangeIntervals.Insert(i - 1, rr); hasCompl = true; break; } if (li < minimal) //если средний меньше минимального, то объединяем с тем, у кого меньше интервал { if (i1 < i2) //если левый интервал меньше правого { RangeInterval rr = combineIntervals(r1, ri); rangeIntervals.Remove(ri); rangeIntervals.Remove(r1); rangeIntervals.Insert(i - 1, rr); hasCompl = true; break; } if (i1 > i2) //если правый интервал меньше правого { RangeInterval rr = combineIntervals(ri, r2); rangeIntervals.Remove(ri); rangeIntervals.Remove(r2); rangeIntervals.Insert(i - 1, rr); hasCompl = true; break; } if (i1 == i2)//если интервалы равны, то объединяем всех троих { RangeInterval rr = combineIntervals(r1, r2); rangeIntervals.Remove(r1); rangeIntervals.Remove(ri); rangeIntervals.Remove(r2); rangeIntervals.Insert(i - 1, rr); hasCompl = true; break; } } if (l2 < minimal && i == rangeIntervals.Count - 1) //если правый меньше минимального и это последний элемент, то объединяем с средним { RangeInterval rr = combineIntervals(ri, r2); rangeIntervals.Remove(ri); rangeIntervals.Remove(r2); rangeIntervals.Insert(i - 1, rr); hasCompl = true; break; } #endregion } } QualityInfo res = new QualityInfo(rangeIntervals, maxemptyspace, range.Count); return(res); }
protected BaseInterpolateMethod(StandartIntervals from, StandartIntervals to) { FromInterval = from; ToInterval = to; }
/// <summary> /// восстановить ряд до нужного интревала наблюдений /// </summary> /// <param name="range">Ряд для восстановления</param> /// <param name="param">параметры восстановления</param> /// <param name="actionPercent">изменение процента выполнения</param> /// <param name="actionAfter">действие после обработки</param> /// <returns></returns> public Task <RawRange> ProcessRange(RawRange range, RestorerParameters param, Action <int, string> actionPercent, Action <RawRange, RawRange, double> actionAfter) { RawRange baseRange = null;//ряд, на основе которого будет идти восстановление range.PerformRefreshQuality(); Range = new RawRange(range.OrderBy(x => x.Date).ToList()); this.param = param; this.actionPercent = actionPercent; prepareIterpolators(); //расчет каждого значения Task <RawRange> tsk = new Task <RawRange>(() => { ConcurrentBag <RawItem> resultCollection = new ConcurrentBag <RawItem>(); Parallel.ForEach(newRangeX, (p) => { incrementCounter(); StandartIntervals currentInterval = getIntervalOfDateArgument(p, range); if (currentInterval == StandartIntervals.None) { throw new WindEnergyException("Что-то не так. Не удалось найти интервал наблюдений"); } double speed, direct, temp, wet, press; //если целевой интервал меньше текущего, то интерполируем как обычно if ((int)currentInterval >= (int)param.Interval || param.Method == InterpolateMethods.NearestMeteostation) { speed = methodSpeeds.GetValue(p); direct = methodDirects.GetValue(p); temp = methodTemp.GetValue(p); wet = methodWet.GetValue(p); press = methodPress.GetValue(p); } else //Если целевой интервал больше текущего, то надо осреднять значения за целевой интервал { DateTime from = new DateTime().AddMinutes(p - (int)param.Interval); DateTime to = new DateTime().AddMinutes(p); RawRange part = range.GetRange(true, false, from, to, null, null); if (part.Count == 0) { speed = methodSpeeds.GetValue(p); direct = methodDirects.GetValue(p); temp = methodTemp.GetValue(p); wet = methodWet.GetValue(p); press = methodPress.GetValue(p); } else { speed = part.Average(item => item.Speed); direct = part.Average(item => item.Direction > 0 ? item.Direction : 0); temp = part.Average(item => item.Temperature); wet = part.Average(item => item.Wetness); press = part.Average(item => item.Pressure); } } if (double.IsNaN(speed)) { return; } if (speed < 0) { speed = 0; } resultCollection.Add(new RawItem(p, speed, direct, temp, wet, press)); }); RawRange res = new RawRange(resultCollection.OrderBy(item => item.DateArgument)); res.Position = range.Position; res.Meteostation = range.Meteostation; res.Height = range.Height; if (actionAfter != null) { actionAfter.Invoke(res, baseRange, r); } return(res); }); tsk.Start(); return(tsk); }
/// <summary> /// кнопка восстановить ряд /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonRepairRange_Click(object sender, EventArgs e) { Cursor = Cursors.WaitCursor; InterpolateMethods method = (InterpolateMethods)(new EnumTypeConverter <InterpolateMethods>().ConvertFrom(comboBoxInterpolateMethod.SelectedItem)); StandartIntervals interval = (StandartIntervals)(new EnumTypeConverter <StandartIntervals>().ConvertFrom(comboBoxRepairInterval.SelectedItem)); Action <int, string> action = new Action <int, string>((percent, text) => { try { if (this.InvokeRequired) { _ = Invoke(new Action(() => { progressBarStatus.Value = percent; })); } else { progressBarStatus.Value = percent; } } catch (Exception) { } }); Action <RawRange, RawRange, double> actionAfter = new Action <RawRange, RawRange, double>((resultRange, baseRange, r) => { _ = this.Invoke(new Action(() => { string additionalText = ""; if (method == InterpolateMethods.NearestMeteostation) //для восстановления ряда выводим доп. информацию { //Информация о коэфф корреляции и базовом ряде if (double.IsNaN(r)) { additionalText += "Восстановление проводилось на основе ряда наблюдений, заданного пользователем\r\n"; } else { additionalText += $"Восстановление проводилось на основе ряда наблюдений {(baseRange.Meteostation != null ? $" на МС {baseRange.Meteostation.Name} " : "")}с коэффициентом корреляции {r:0.00} \r\n"; } //предупреждение, что не все данные восстановлены RangeInterval baseInterval = baseRange.Quality.Intervals.OrderByDescending((i) => i.LengthMinutes).First(); //выбираем самый длинный интервал наблюдений в базовом ряде if (baseInterval.LengthMinutes > (int)interval) //если максимальный интервал базового ряда больше, чем требуемый интервал восстановления { additionalText += $"\r\nВнимание!! Интервал наблюдений ряда, на основе которого производилось восстановление ({baseInterval.Interval.Description()}), больше, чем требуемый интервал. Поэтому не удалось восстановить все значения ряда до {interval.Description()}\r\n"; } } resultRange.Name = "Восстановленный ряд до интервала" + interval.Description(); _ = MessageBox.Show(this, $"Ряд восстановлен до интервала {interval.Description()}\r\n{additionalText}", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); if (resultRange == null) { DialogResult = DialogResult.Cancel; } else { DialogResult = DialogResult.OK; Result = resultRange; } Cursor = Cursors.Arrow; Result = resultRange; Close(); })); }); try { //если выбрана ступенчатый метод или линейная интерполяция и в ряде есть пропуски больше, чем 1 интервал, то надо уточнить у пользователя if ((method == InterpolateMethods.Linear || method == InterpolateMethods.Stepwise) && rangeQuality.MaxEmptySpace.TotalMinutes > ((int)interval)) { if (MessageBox.Show(this, "Ряд содержит пропуски данных больше, чем один выбранный интервал наблюдений.\r\nВ таком случае не рекомендуется использовать линейную интерполяцию и ступенчатое восстановление.\r\nВы уверены, что хотите продолжить восстановление?", "Восстановление ряда", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { return; } } RawRange baseRange = null; if (method == InterpolateMethods.NearestMeteostation) { if (radioButtonSelPoint.Checked) { if (range.Position.IsEmpty) { FormSelectMapPointDialog fsp = new FormSelectMapPointDialog("Выберите координаты ряда " + range.Name, PointLatLng.Empty, Vars.Options.CacheFolder, Resources.rp5_marker, Vars.Options.MapProvider); if (fsp.ShowDialog(this) == DialogResult.OK) { range.Position = fsp.Result; } else { Cursor = Cursors.Arrow; return; } } } else { baseRange = Program.winMain.mainHelper.OpenFile(this); if (baseRange == null) { Cursor = Cursors.Arrow; return; } } } _ = new Restorer().ProcessRange(range, new RestorerParameters() { Interval = interval, Method = method, Coordinates = range.Position, BaseRange = baseRange, ReplaceExistMeasurements = checkBoxReplaceExist.Checked }, action, actionAfter); } catch (WebException exc) { Cursor = Cursors.Arrow; _ = MessageBox.Show(this, exc.Message, "Восстановление ряда", MessageBoxButtons.OK, MessageBoxIcon.Warning); DialogResult = DialogResult.Cancel; } catch (WindEnergyException wex) { Cursor = Cursors.Arrow; _ = MessageBox.Show(this, wex.Message, "Восстановление ряда", MessageBoxButtons.OK, MessageBoxIcon.Warning); DialogResult = DialogResult.Cancel; } catch (ApplicationException exc) { Cursor = Cursors.Arrow; _ = MessageBox.Show(this, exc.Message + "\r\nПопробуйте уменьшить длину ряда", "Восстановление ряда", MessageBoxButtons.OK, MessageBoxIcon.Warning); DialogResult = DialogResult.Cancel; } }