/// <summary> /// создание нового интерполятора с заданными значениями на основе существующего базового ряда наблюдения /// </summary> /// <param name="func">известные значения функции в заданной точке</param> /// <param name="baseRange">базовый ряд (с ближайшей МС), на основе которого будет происходить восстановление</param> /// <param name="parameterType">тип мтеорологического параметра</param> /// <param name="replaceExistMeasurements">Заменять существующие измерения в исходном ряде на расчетные</param> public NearestMSInterpolateMethod(Dictionary <double, double> func, RawRange baseRange, MeteorologyParameters parameterType, bool replaceExistMeasurements) { if (func.Keys.Count == 0) { Empty = true; return; } Empty = false; this.parameterType = parameterType; this.func = func; this.replaceExistMeasurements = replaceExistMeasurements; this.nearestRange = baseRange; //расчет диапазона сделанных измерений baseRange = new RawRange(baseRange.OrderBy(x => x.Date).ToList()); interpolationDiapason.From = Math.Max(baseRange[0].DateArgument, func.Keys.Min()); //максимальную дату из начал каждой функции interpolationDiapason.To = Math.Min(baseRange[baseRange.Count - 1].DateArgument, func.Keys.Max()); //минимальную дату из концов каждой функции double a = 0, b = 0, r = 0; //коэффициенты прямой и коэффициент корреляции Dictionary <double, double> baseFunc = baseRange.GetFunction(parameterType); //функция базового ряда List <double>[] tableCoeff = calcTableCoeff(func, baseFunc); //таблица для расчёта коэффициентов a, b, r a = getParameterA(tableCoeff); //коэффициенты прямой b = getParameterB(tableCoeff, a); r = getParameterR(tableCoeff); //коэффициент корреляции //проверка попадания коэфф корреляции в допустимый диапазон (если для этого параметра надо проверять диапазон) if (r < Vars.Options.MinimalCorrelationCoeff && Vars.Options.MinimalCorrelationControlParametres.Contains(parameterType)) { throw new Exception("Недостаточное коррелирование функций"); } //ФУНКЦИЯ ПОЛУЧЕНИЯ ЗНАЧЕНИЯ getRes = new Func <double, double>((x) => { //если в исходном ряде есть это значение и не надо заменять исходное, то его и возвращаем if (func.ContainsKey(x) && !replaceExistMeasurements) { return(func[x]); } //Если в базовой функции нет измерения за этой время, то возвращаем NaN //иначе расчитываем скорость по полученной зависимости. if (!baseFunc.ContainsKey(x)) { return(double.NaN); } else { if (baseFunc[x] == 0) //если в базовой функции это значение равно нулю, то возвращаем NaN, чтоб не завышать результат прибавлением b { return(double.NaN); } else { return(a * baseFunc[x] + b); } } }); }
/// <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="fromDate">с какой даты</param> /// <param name="toDate">до какой даты</param> /// <param name="info">Метеостанция, с которой загружается ряд</param> /// <param name="onPercentChange"></param> /// <param name="checkStop"></param> /// <returns></returns> public RawRange GetRange(DateTime fromDate, DateTime toDate, RP5MeteostationInfo info, Action <double> onPercentChange = null, Func <bool> checkStop = null) { if (toDate < fromDate) { throw new WindEnergyException("Даты указаны неверно"); } switch (Vars.Options.RP5SourceEngine) { case RP5SourceType.LocalDBSearch: return(Vars.RP5Database.GetRange(fromDate, toDate, info, onPercentChange, checkStop)); case RP5SourceType.OnlineAPI: if (toDate - fromDate > TimeSpan.FromDays(365 * LOAD_STEP_YEARS)) // если надо скачать больше трёх лет, то скачиваем по частям { TimeSpan span = toDate - fromDate; RawRange res1 = new RawRange(); DateTime dt; int i = 0; int total = (int)(span.TotalDays / (365 * LOAD_STEP_YEARS)) + 1; for (dt = fromDate; dt <= toDate; dt += TimeSpan.FromDays(365 * LOAD_STEP_YEARS)) { if (checkStop != null) { if (checkStop.Invoke()) { break; } } if (onPercentChange != null) { double pc = ((i / (double)total) * 100d); onPercentChange.Invoke(pc); } RawRange r = GetRange(dt, dt + TimeSpan.FromDays(365 * LOAD_STEP_YEARS), info, onPercentChange, checkStop); res1.Add(r); res1.Name = r.Name; res1.Position = r.Position; res1.Meteostation = r.Meteostation; res1.Height = 10; //высота всегда 10м i++; } //DateTime fr = dt - TimeSpan.FromDays(365 * LOAD_STEP_YEARS); //RawRange r1 = GetRange(fr, toDate, info); return(res1); } #region отправка статистики загрузки string dataS, linkS; switch (info.MeteoSourceType) { case MeteoSourceType.Airport: dataS = "cc_id={0}&cc_str={1}&stat_p=1&s_date1={2}&s_ed3={4}&s_ed4={4}&s_ed5={5}&s_date2={3}&s_ed9=0&s_ed10=-1&s_pe=1&lng_id=2&s_dtimehi=-Период---"; linkS = "https://rp5.ru/responses/reStatistMetar.php"; dataS = string.Format(dataS, info.ID, //cc_id info.CC_Code, //cc_str fromDate.Date.ToString("dd.MM.yyyy"), //from toDate.Date.ToString("dd.MM.yyyy"), //to DateTime.Now.Month, //f_ed3 - только месяц DateTime.Now.Day //f_ed5 - только день ); break; case MeteoSourceType.Meteostation: dataS = "wmo_id={0}&stat_p=1&s_date1={1}&s_ed3={3}&s_ed4={3}&s_ed5={4}&s_date2={2}&s_ed9=0&s_ed10=-1&s_pe=1&lng_id=2&s_dtimehi=-срок---"; linkS = "https://rp5.ru/responses/reStatistSynop.php"; dataS = string.Format(dataS, info.ID, //wmo_id fromDate.Date.ToString("dd.MM.yyyy"), //from toDate.Date.ToString("dd.MM.yyyy"), //to DateTime.Now.Month, //f_ed3 - только месяц DateTime.Now.Day //f_ed5 - только день ); break; default: throw new Exception("Этот тип метеостанций не реализован"); } string strS = this.SendStringPostRequest(linkS, dataS, referer: "https://rp5.ru/", cookies: this.CookieData, customHeaders: this.Headers); #endregion #region получение ссылки на файл string data, link; //получение ссылки на файл switch (info.MeteoSourceType) { case MeteoSourceType.Airport: data = "metar={0}&a_date1={1}&a_date2={2}&f_ed3={3}&f_ed4={4}&f_ed5={5}&f_pe={6}&f_pe1={7}&lng_id=2"; link = "https://rp5.ru/responses/reFileMetar.php"; break; case MeteoSourceType.Meteostation: data = "wmo_id={0}&a_date1={1}&a_date2={2}&f_ed3={3}&f_ed4={4}&f_ed5={5}&f_pe={6}&f_pe1={7}&lng_id=2"; link = "https://rp5.ru/responses/reFileSynop.php"; break; default: throw new Exception("Этот тип метеостанций не реализован"); } data = string.Format(data, info.ID, //id fromDate.Date.ToString("dd.MM.yyyy"), //from toDate.Date.ToString("dd.MM.yyyy"), //to DateTime.Now.Month, //f_ed3 - только месяц DateTime.Now.Month, //f_ed4 - только месяц DateTime.Now.Day, //f_ed5 - только день 1, //f_pe 2 //f_pe1- кодировка (1 - ansi, 2 - utf8, 3 - Unicode) ); string str = this.SendStringPostRequest(link, data, referer: "https://rp5.ru/", cookies: this.CookieData, customHeaders: this.Headers); //ОШИБКИ rp5.ru //запросы к reFileSynop.php //FS004 несуществующий wmo_id //FS002 ошибки в исходных данных (параметрах запроса) //FS000 Ошибка авторизации //FS001- //запросы к reStatistSynop.php //S000 Ошибка авторизации //FM000 Время жизни статистики истекло для этой сессии if (str.Contains("FS004")) { throw new WindEnergyException("Для этого id нет архива погоды", ErrorReason.FS004); } if (str.Contains("FS002")) { throw new WindEnergyException("Ошибка в исходных данных", ErrorReason.FS002); } if (str.Contains("FS000")) { throw new WindEnergyException("Ошибка авторизации", ErrorReason.FS000); } if (str.Contains("FS001-")) { throw new WindEnergyException("Неправильный метод запроса. Ожидается POST", ErrorReason.FS001); } if (str.Contains("FM000")) { throw new WindEnergyException("Время жизни статистики истекло для этой сессии", ErrorReason.FM000); } if (str.Contains("FM004")) { throw new WindEnergyException("Внутренняя ошибка. Архив недоступен или не существует", ErrorReason.FM004); } int start = str.IndexOf("href=") + 5; str = str.Substring(start); int end = str.IndexOf(">"); string lnk = str.Substring(0, end); lnk = lnk.Split(' ')[0] ?? ""; bool checkLink = Uri.TryCreate(lnk, UriKind.Absolute, out Uri uriResult) && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); if (!checkLink) { throw new WindEnergyException("В парсере ответа rp5 произошла ошибка. Скорее всего, парсер устарел, обратитесь к разработчику"); } #endregion #region загрузка файла с сервера string tmp_dl_file = Vars.LocalFileSystem.GetTempFileName(); WebClient webcl = new WebClient(); webcl.DownloadFile(lnk, tmp_dl_file); webcl.Dispose(); //распаковка string tmp_unpack_file = tmp_dl_file + ".csv"; LocalFileSystem.UnGZip(tmp_dl_file, tmp_unpack_file); //открытие файла RawRange res = LoadCSV(tmp_unpack_file, info); res = new RawRange(res.OrderBy(x => x.Date).ToList()); res.Name = info.Name; res.Position = info.Position; res.Meteostation = info; res.Height = 10; //высота всегда 10м new Task(() => Vars.RP5Meteostations.TryAddMeteostation(info)).Start(); //если такой метеостанции нет в БД, то добавляем return(res); #endregion default: throw new Exception("Этот тип БД не реализован"); } }