/// <summary> /// Determines the direction of the line and flips it neccesary. /// </summary> /// <returns>Whether the direction could be determined</returns> private bool determineDirection() { double firstDist = double.NaN; foreach (StationInfo station in stations) { if (station.IntDistance != -1) { Station st = DatabaseLegacy.GetStation(station.Name); double dist; if (st != null && !double.IsNaN(dist = Polyline.GetProjectedDistance(WebMercator.DefaultMap.FromLatLon(st.GPSCoord), 0.05))) { if (double.IsNaN(firstDist)) { firstDist = dist; } else //if (double.IsNaN(secondDist)) { if (dist < firstDist) { Polyline = new Polyline(Polyline.Points.Reverse().ToList()); } return(true); } } } } return(true); }
/// <summary> /// Updates fields based on a TRAIN API JSON request /// </summary> /// <param name="apiResponse">JSON TRAIN MÁV API response</param> public void UpdateTRAIN_API(JObject apiResponse) { HtmlDocument trainHTML = new HtmlDocument(); trainHTML.LoadHtml(WebUtility.HtmlDecode((string)apiResponse["d"]["result"]["html"])); HtmlNode table; try { table = trainHTML.DocumentNode.Descendants("table").Where(tb => tb.HasClass("vt")).First(); } catch (InvalidOperationException e) { throw new MAVAPIException("Cannot parse train table."); } string fix2400Time(string s) { if (s.Trim() == "24:00") { return("0:00"); } else { return(s); } } try { // The header containing train type and name information IEnumerable <HtmlNode> tableHeader = table.Descendants("th").First().Descendants(); string[] name = tableHeader.First().InnerHtml.Split(new char[] { ' ', '\r', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries); Number = name[0]; // Figuring out train type if (name.Length > 1) { if (name[1].Trim().ToLower() == "sebesvonat" || name[1].Trim().ToLower() == "gyorsvonat" || name[1].Trim().ToLower() == "személyvonat" || name[1].Trim().ToLower() == "EuroRegio" || name[1].Trim().ToLower() == "InterCity" || name[1].Trim().ToLower() == "EuroCity" || name[1].Trim().ToLower() == "vonatpótló autóbusz" || name[1].Trim().ToLower() == "railjet") { Type = name[1].Trim().ToLower(); } else { Name = name[1]; } } bool ul = false; foreach (HtmlNode node in tableHeader) { // This is a sort of secondary train type for specific trains if (node.HasClass("viszszam2")) { NumberType = node.InnerHtml; } // Sometimes train type can only be decuded from the alt of an image else if (node.Name == "img" && !ul) { Type = node.Attributes["alt"].Value; } // Sometimes trains can be broken down into multiple types else if (node.Name == "ul") { ul = true; Type = ""; foreach (HtmlNode li in node.Descendants("li")) { string type = ""; type = Regex.Replace(li.Descendants().First().InnerHtml, @"\s+", " "); IEnumerable <HtmlNode> img = li.Descendants().Where(d => d.Name == "img"); if (img != null && img.Any()) { type += " " + img.First().Attributes["alt"].Value; } Type += (Type == "" ? "" : "\n") + type; } } } // Any information that might be helpful miscInfo.Clear(); IEnumerable <HtmlNode> miscInform = table.Descendants("th").Skip(1); foreach (HtmlNode node in miscInform) { // The actual table headers start which are uninteresting if (node.InnerHtml == "Km") { break; } // Anything starting with this text is redundant with another API's information if (node.InnerHtml.StartsWith("A pillanatnyi késés")) { continue; } // Anything containing the word delay is related to delays (this might fail) else if (node.InnerHtml.Contains("késés")) { DelayReason = node.InnerHtml; } // The truly misc info else { miscInfo.Add(node.InnerHtml); } } } catch (InvalidOperationException e) { throw new MAVAPIException("Cannot parse train header."); } if ((apiResponse["d"]["result"]["line"] as JArray).Count == 0) { throw new MAVAPIException("Cannot get train line."); } bool hasTrainData = HasTRAINData; // Polyline will be defined in a second so that would skew our method // Polyline of the train path List <Vector2> points = Polyline.DecodePoints(apiResponse["d"]["result"]["line"][0]["points"].ToString(), 1E5f); Polyline = new Polyline(points); // Station infos IEnumerable <HtmlNode> stationRows = table.Descendants("tr").Where(tr => tr.Attributes.Contains("class")); if (hasTrainData) // If we already have train data then stations will be updated { int stationCnt = 0; foreach (HtmlNode stationRow in stationRows) { bool highlighted = stationRow.Attributes["class"].Value.StartsWith("row_past"); // Skip invalid rows if (stationRow.Descendants("td").Count() < 4) { continue; } IEnumerator <HtmlNode> tdEnumerator = stationRow.Descendants("td").GetEnumerator(); tdEnumerator.MoveNext(); // On integer distance cell tdEnumerator.MoveNext(); // On station name cell tdEnumerator.MoveNext(); // On arrival time cell TimeSpan expectedArrival = stations[stationCnt].ExpectedArrival.TimeOfDay; //Don't change if there's an error parsing try { if (stationRows.First() != stationRow) { // Arrival time parsing block { IEnumerator <HtmlNode> timeEnumerator = tdEnumerator.Current.Descendants().GetEnumerator(); timeEnumerator.MoveNext(); // On the first text block contatining scheduled arrival if (timeEnumerator.MoveNext()) // On a <br> { timeEnumerator.MoveNext(); // On the second text block in a span containing expected arrival expectedArrival = TimeSpan.ParseExact(fix2400Time(timeEnumerator.Current.InnerHtml), "g", null); } } } } catch (FormatException e) { throw new MAVAPIException("Cannot parse train arrival time."); } tdEnumerator.MoveNext(); // On departure time cell TimeSpan expectedDeparture = stations[stationCnt].ExpectedDeparture.TimeOfDay; //Don't change if there's an error parsing try { if (stationRows.Last() != stationRow) { // Departure time parsing block { IEnumerator <HtmlNode> timeEnumerator = tdEnumerator.Current.Descendants().GetEnumerator(); timeEnumerator.MoveNext(); // On the first text block contatining scheduled departure if (timeEnumerator.MoveNext()) // On a <br> { timeEnumerator.MoveNext(); // On the second text block in a span containing expected departure expectedDeparture = TimeSpan.ParseExact(fix2400Time(timeEnumerator.Current.InnerHtml), "g", null); } } } } catch (FormatException e) { throw new MAVAPIException("Cannot parse train departure time."); } string platform = null; if (tdEnumerator.MoveNext()) // There might not even be a cell for platforms { // On platform cell platform = tdEnumerator.Current.InnerHtml.Trim(); } DateTime expectedArrivalDateTime = stations[stationCnt].Arrival.Date + expectedArrival, expectedDepartureDateTime = stations[stationCnt].Departure.Date + expectedDeparture; if (stations[stationCnt].Arrival.TimeOfDay > expectedArrival) // we are on the border of this day { expectedArrivalDateTime = stations[stationCnt].Arrival.Date.AddDays(1) + expectedArrival; } if (stations[stationCnt].Departure.TimeOfDay > expectedDeparture) // we are on the border of this day { expectedDepartureDateTime = stations[stationCnt].Departure.Date.AddDays(1) + expectedDeparture; } stations[stationCnt++].Update(expectedArrivalDateTime, expectedDepartureDateTime, highlighted || (expectedDepartureDateTime <= DateTime.Now), platform == "" ? null : platform); } } else // If we don't have train data already then stations must be added, and the distance information also has to be calculated { foreach (HtmlNode stationRow in stationRows) { bool highlighted = stationRow.Attributes["class"].Value.StartsWith("row_past"); // Skip invalid rows if (stationRow.Descendants("td").Count() < 4) { continue; } IEnumerator <HtmlNode> tdEnumerator = stationRow.Descendants("td").GetEnumerator(); tdEnumerator.MoveNext(); // On integer distance cell int intDistance = -1; if (tdEnumerator.Current.InnerHtml.Trim() != "") { try { intDistance = int.Parse(tdEnumerator.Current.InnerHtml); } catch (FormatException e) { throw new MAVAPIException("Cannot parse train station distance cell."); } } tdEnumerator.MoveNext(); // On station name cell string stationName = tdEnumerator.Current.InnerText; DateTime date = new DateTime(0); try { date = DateTime.ParseExact(Regex.Match(tdEnumerator.Current.Descendants("a").First().Attributes["onclick"].Value, "d: '(?<date>[^' ]*?)'").Groups["date"].Value, "yy.MM.dd", null); } catch (FormatException e) { throw new MAVAPIException("Cannot parse train date."); } tdEnumerator.MoveNext(); // On arrival time cell TimeSpan arrival = new TimeSpan(0, 0, 0), expectedArrival = new TimeSpan(0, 0, 0); try { if (stationRows.First() != stationRow) { // Arrival time parsing block { IEnumerator <HtmlNode> timeEnumerator = tdEnumerator.Current.Descendants().GetEnumerator(); timeEnumerator.MoveNext(); // On the first text block contatining scheduled arrival arrival = TimeSpan.ParseExact(fix2400Time(timeEnumerator.Current.InnerHtml), "g", null); if (timeEnumerator.MoveNext()) // On a <br> { timeEnumerator.MoveNext(); // On the second text block in a span containing expected arrival expectedArrival = TimeSpan.ParseExact(fix2400Time(timeEnumerator.Current.InnerHtml), "g", null); } else { expectedArrival = arrival; } } } } catch (FormatException e) { throw new MAVAPIException("Cannot parse train arrival time."); } tdEnumerator.MoveNext(); // On departure time cell TimeSpan departure = new TimeSpan(0, 0, 0), expectedDeparture = new TimeSpan(0, 0, 0); try { if (stationRows.Last() != stationRow) { // Departure time parsing block { IEnumerator <HtmlNode> timeEnumerator = tdEnumerator.Current.Descendants().GetEnumerator(); timeEnumerator.MoveNext(); // On the first text block contatining scheduled departure departure = TimeSpan.ParseExact(fix2400Time(timeEnumerator.Current.InnerHtml), "g", null); if (timeEnumerator.MoveNext()) // On a <br> { timeEnumerator.MoveNext(); // On the second text block in a span containing expected departure expectedDeparture = TimeSpan.ParseExact(fix2400Time(timeEnumerator.Current.InnerHtml), "g", null); } else { expectedDeparture = departure; } } } } catch (FormatException e) { throw new MAVAPIException("Cannot parse train departure time."); } string platform = null; if (tdEnumerator.MoveNext()) // There might not even be a cell for platforms { // On platform cell platform = tdEnumerator.Current.InnerHtml.Trim(); } DateTime arrivalDateTime = date + arrival, departureDateTime = date + departure, expectedArrivalDateTime = date + expectedArrival, expectedDepartureDateTime = date + expectedDeparture; if (arrival > departure) // we are on the border of this day { departureDateTime = date.AddDays(1) + departure; } if (arrival > expectedArrival) // we are on the border of this day { expectedArrivalDateTime = date.AddDays(1) + expectedArrival; } if (arrival > expectedDeparture) // we are on the border of this day { expectedDepartureDateTime = date.AddDays(1) + expectedDeparture; } StationInfo stationInfo = new StationInfo(stationName, intDistance, arrivalDateTime, departureDateTime, expectedArrivalDateTime, expectedDepartureDateTime, highlighted || (expectedDepartureDateTime <= DateTime.Now), platform == "" ? null : platform); stations.Add(stationInfo); } if (determineDirection()) { int firstFound = -1; for (int i = 0; i < stations.Count; i++) { if (stations[i].IntDistance != -1) { Station st = DatabaseLegacy.GetStation(stations[i].Name); if (st != null) { stations[i].UpdateStation(st); } double dist = 0; if (st != null && !double.IsNaN(dist = Polyline.GetProjectedDistance(WebMercator.DefaultMap.FromLatLon(st.GPSCoord), 0.25))) { stations[i].UpdateDistanceInformation(dist, StationPositionAccuracy.Precise); if (firstFound == -1) { firstFound = i; } } else if (!double.IsNaN(dist) && i > 0 && stations[i - 1].PositionAccuracy != StationPositionAccuracy.Missing) { stations[i].UpdateDistanceInformation(stations[i - 1].Distance + (stations[i].IntDistance - stations[i - 1].IntDistance), StationPositionAccuracy.IntegerPrecision); } } } for (int i = firstFound - 1; i >= 0; i--) // We do a reverse check as well so that we can figure out missing stations backwards from the first found { if (stations[i].IntDistance != -1 && (stations[i].Station != null && !double.IsNaN(Polyline.GetProjectedDistance(WebMercator.DefaultMap.FromLatLon(stations[i].Station.GPSCoord), 0.05))) && stations[i].PositionAccuracy == StationPositionAccuracy.Missing) { stations[i].UpdateDistanceInformation(stations[i + 1].Distance - (stations[i + 1].IntDistance - stations[i].IntDistance), StationPositionAccuracy.IntegerPrecision); } } } } }