diff --git a/TrainInfomationProviderService/Program.cs b/TrainInfomationProviderService/Program.cs index 6a12574..35320de 100644 --- a/TrainInfomationProviderService/Program.cs +++ b/TrainInfomationProviderService/Program.cs @@ -36,8 +36,20 @@ namespace TrainInfomationProviderService StationManager.Instance.Init(); TrainInfoManager.Instance.Init(); + SameStationManager.Init(); //搜索? - var searchProvider = new TrainInfoSearchProvider(TrainInfoManager.Instance.Storage); + var searchProvider = new TrainInfoSearchProvider(); + var lines = searchProvider.FindDirectTrains(DateTime.Parse("2014-12-10"), "NVH", "JJG").ToArray(); + var maxTimeRage = lines.Max(s => s.CalculatedMinutesBase); + var altLines = searchProvider.FindOnceTransitTrains(DateTime.Parse("2014-12-12"), "CCT", "ZHQ", new TrainTransitSearchOptions() { MaxExtraMinutes = int.MaxValue }).ToArray(); + + var availableLines = lines.Select(s => s.Train.Code + "," + s.FromStation.Name + "," + s.ToStation.Name + "," + s.ElapsedTime).ToArray(); + Array.ForEach(altLines.Select(s => + s.First.Train.Code + "," + s.First.FromStation.Name + "," + s.First.ToStation.Name + "," + s.First.From.Left.Value + " - " + s.First.To.Arrive.Value + " / " + s.First.ElapsedTime + + " -> " + s.Second.Train.Code + "," + s.Second.FromStation.Name + "," + s.Second.ToStation.Name + "," + s.Second.From.Left.Value + " - " + s.Second.To.Arrive.Value + " / " + s.Second.ElapsedTime + " / 等待 " + s.WaitElaspsedTime + " / 总耗时 " + s.TotalElapsedTime + + (s.NotRecommand ? " / 不推荐" : "") + ).ToArray(), _ => Trace.TraceInformation(_)); + //runtime mode MessageBox.Show(StationManager.Instance.Storage.Version.ToString()); } diff --git a/TrainInfomationProviderService/StationInfo/Entities/SameStationCollection.cs b/TrainInfomationProviderService/StationInfo/Entities/SameStationCollection.cs deleted file mode 100644 index 44de369..0000000 --- a/TrainInfomationProviderService/StationInfo/Entities/SameStationCollection.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; - -namespace TrainInfomationProviderService.StationInfo.Entities -{ - public class SameStationCollection : List> - { - private SameStationCollection() - { - - } - - public HashSet FindSameStations(string code) - { - return this.FirstOrDefault(s => s.Contains(code)); - } - - private static SameStationCollection _instance; - private static readonly object _lockObject = new object(); - - /// - /// 获得实例对象 - /// - public static SameStationCollection Instance - { - get - { - if (_instance == null) - { - lock (_lockObject) - { - if (_instance == null) - { - var file = Path.Combine(RunTimeContext.DataStorageRoot, "samestation.json"); - if (File.Exists(file)) - { - _instance = JsonConvert.DeserializeObject(File.ReadAllText(file)); - } - else - { - _instance = new SameStationCollection(); - } - } - } - } - - return _instance; - } - } - } -} diff --git a/TrainInfomationProviderService/StationInfo/SameStationManager.cs b/TrainInfomationProviderService/StationInfo/SameStationManager.cs new file mode 100644 index 0000000..9ec5b1e --- /dev/null +++ b/TrainInfomationProviderService/StationInfo/SameStationManager.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Policy; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms.VisualStyles; +using FSLib.Network.Http; +using Newtonsoft.Json; + +namespace TrainInfomationProviderService.StationInfo +{ + class SameStationManager + { + public static Dictionary> SameStationMap { get; private set; } + + private static Timer _timer; + private static string _cacheUrl; + + public static void Init() + { + _cacheUrl = PathUtility.Combine(RunTimeContext.DataStorageRoot, "samestation.json"); + SameStationMap = new Dictionary>(StringComparer.OrdinalIgnoreCase); + LoadCache(); + + _timer = new Timer(_ => Grabber(), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + Grabber(); + if (StationManager.Instance.Storage != null) + { + AutoRefreshFromSource(); + } + } + + static void LoadCache() + { + if (!File.Exists(_cacheUrl)) return; + + var list = JsonConvert.DeserializeObject>>(File.ReadAllText(_cacheUrl)); + foreach (var hashSet in list) + { + foreach (var s in hashSet) + { + SameStationMap.AddOrUpdate(s, hashSet); + } + } + } + + static void Grabber() + { + try + { + GrabberFromWeb(); + } + catch (Exception) + { + + } + + _timer.Change(new TimeSpan(0, 30, 0), Timeout.InfiniteTimeSpan); + } + + internal static void Save() + { + lock (_cacheUrl) + { + File.WriteAllText(_cacheUrl, JsonConvert.SerializeObject(SameStationMap.Values.Distinct().ToArray())); + } + } + + internal static void AutoRefreshFromSource() + { + var map = SameStationMap; + if (map == null) + return; + + var count = map.Count; + var stations = StationManager.Instance.Storage.StationNameMap.Values.OrderByDescending(s => s.Name.Length).ToList(); + + while (stations.Count > 0) + { + var st = stations.Last(); + stations.RemoveAt(stations.Count - 1); + + HashSet collection = null; + for (var i = stations.Count - 1; i >= 0; i--) + { + if (stations[i].Name.IndexOf(st.Name) != 0) + continue; + + (collection ?? (collection = new HashSet(StringComparer.OrdinalIgnoreCase) { st.Code })).Add(stations[i].Code); + stations.RemoveAt(i); + i--; + } + if (collection != null && collection.Count > 1) + { + var curSet = collection.Select(map.GetValue).FirstOrDefault(s => s != null) ?? new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var s1 in collection) + { + curSet.SafeAdd(s1); + } + + foreach (var s in collection) + { + map.AddOrUpdate(s, curSet); + } + } + } + if (SameStationMap.Count != count) + { + //有变化 + Save(); + } + } + + private static string _url = "http://service.fishlee.net/ss.json"; + + static bool GrabberFromWeb() + { + var ctx = new HttpClient().Create(HttpMethod.Get, _url).Send(); + if (!ctx.IsValid()) + return false; + + //缓存 + File.WriteAllText(_cacheUrl, ctx.Result); + + var list = JsonConvert.DeserializeObject>>(File.ReadAllText(_cacheUrl)); + var map = SameStationMap; + var count = SameStationMap.Count; + foreach (var hashSet in list) + { + var curSet = hashSet.Select(map.GetValue).FirstOrDefault(s => s != null) ?? new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var s1 in hashSet) + { + curSet.SafeAdd(s1); + } + + foreach (var s in hashSet) + { + map.AddOrUpdate(s, curSet); + } + } + if (SameStationMap.Count != count) + { + //有变化 + Save(); + } + + return true; + } + } +} diff --git a/TrainInfomationProviderService/StationInfo/StationManager.cs b/TrainInfomationProviderService/StationInfo/StationManager.cs index 74fa008..4422de2 100644 --- a/TrainInfomationProviderService/StationInfo/StationManager.cs +++ b/TrainInfomationProviderService/StationInfo/StationManager.cs @@ -60,10 +60,6 @@ namespace TrainInfomationProviderService.StationInfo { get { - if (RunTimeContext.IsWeb) - { - return HostingEnvironment.Cache["_12306stations"] as StationStorage; - } return _storage; } } @@ -75,54 +71,54 @@ namespace TrainInfomationProviderService.StationInfo _dataFilePath = PathUtility.Combine(RunTimeContext.DataStorageRoot, "stations.json"); Trace.TraceInformation("[STATION] 目标缓存文件路径:{0}", _dataFilePath); - if (RunTimeContext.IsWeb) - { - Trace.TraceInformation("[STATION] WEB模式"); + //if (RunTimeContext.IsWeb) + //{ + // Trace.TraceInformation("[STATION] WEB模式"); - InitWeb(); - } - else - { - Trace.TraceInformation("[STATION] 服务模式"); - InitService(); - } + // InitWeb(); + //} + //else + //{ + Trace.TraceInformation("[STATION] 服务模式"); + InitService(); + //} } #region web环境 - /// - /// web环境初始化 - /// - void InitWeb() - { - Trace.TraceInformation("[STATION] 初始化WEB模式"); - LoadCache(); - } + ///// + ///// web环境初始化 + ///// + //void InitWeb() + //{ + // Trace.TraceInformation("[STATION] 初始化WEB模式"); + // LoadCache(); + //} - void LoadCache() - { - Trace.TraceInformation("[STATION] 正在检查缓存刷新"); - _storage = null; - if (File.Exists(_dataFilePath)) - { - Trace.TraceInformation("[STATION] 正在加载缓存"); - _storage = JsonConvert.DeserializeObject(File.ReadAllText(_dataFilePath)); - Trace.TraceInformation("[STATION] 缓存加载完成"); - } + //void LoadCache() + //{ + // Trace.TraceInformation("[STATION] 正在检查缓存刷新"); + // _storage = null; + // if (File.Exists(_dataFilePath)) + // { + // Trace.TraceInformation("[STATION] 正在加载缓存"); + // _storage = JsonConvert.DeserializeObject(File.ReadAllText(_dataFilePath)); + // Trace.TraceInformation("[STATION] 缓存加载完成"); + // } - if (Storage != null) - { - Trace.TraceInformation("[STATION] 正在加入HttpRuntime缓存"); - HostingEnvironment.Cache.Add("_12306stations", Storage, new CacheDependency(_dataFilePath), - Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.High, - (_1, _2, _3) => - { - Trace.TraceInformation("[STATION] HttpRuntime缓存已被清除,原因:{0},正在重新加载", _3); - LoadCache(); - }); - Trace.TraceInformation("[STATION] HttpRuntime缓存加入完成"); - } - } + // if (Storage != null) + // { + // Trace.TraceInformation("[STATION] 正在加入HttpRuntime缓存"); + // HostingEnvironment.Cache.Add("_12306stations", Storage, new CacheDependency(_dataFilePath), + // Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.High, + // (_1, _2, _3) => + // { + // Trace.TraceInformation("[STATION] HttpRuntime缓存已被清除,原因:{0},正在重新加载", _3); + // LoadCache(); + // }); + // Trace.TraceInformation("[STATION] HttpRuntime缓存加入完成"); + // } + //} #endregion @@ -151,6 +147,7 @@ namespace TrainInfomationProviderService.StationInfo { Trace.TraceInformation("[STATION] 正在刷新缓存"); RefreshStationInfo(lastestVersion); + SameStationManager.AutoRefreshFromSource(); Trace.TraceInformation("[STATION] 缓存数据已刷新"); } _checkStationTimer = new Timer(_ => CheckStationVersion(), null, new TimeSpan(0, 30, 0), Timeout.InfiniteTimeSpan); diff --git a/TrainInfomationProviderService/TrainInfo/Entities/IndexStorage.cs b/TrainInfomationProviderService/TrainInfo/Entities/IndexStorage.cs index ff8dbb8..a75506a 100644 --- a/TrainInfomationProviderService/TrainInfo/Entities/IndexStorage.cs +++ b/TrainInfomationProviderService/TrainInfo/Entities/IndexStorage.cs @@ -11,6 +11,8 @@ namespace TrainInfomationProviderService.TrainInfo.Entities { public class IndexStorage { + private readonly object _lockObject = new object(); + /// /// 创建 的新实例(IndexStorage) /// @@ -30,17 +32,20 @@ namespace TrainInfomationProviderService.TrainInfo.Entities [OnDeserialized] void Init(StreamingContext ctx) { - var dataLoader = new WebDataProvider(); - TrainInfoStorages.Clear(); - - var baseDate = DateTime.Now.Date; - DateIndices = DateIndices.Where(s => s.ToDateTime() >= baseDate).ToHashSet(StringComparer.OrdinalIgnoreCase); - foreach (var dateIndex in DateIndices.ToArray()) + lock (_lockObject) { - var data = dataLoader.LoadCache(dateIndex); - if (data == null) - DateIndices.Remove(dateIndex); - else TrainInfoStorages.Add(dateIndex, data); + var dataLoader = new WebDataProvider(); + TrainInfoStorages.Clear(); + + var baseDate = DateTime.Now.Date; + DateIndices = DateIndices.Where(s => s.ToDateTime() >= baseDate).ToHashSet(StringComparer.OrdinalIgnoreCase); + foreach (var dateIndex in DateIndices.ToArray()) + { + var data = dataLoader.LoadCache(dateIndex); + if (data == null) + DateIndices.Remove(dateIndex); + else TrainInfoStorages.Add(dateIndex, data); + } } } @@ -49,19 +54,23 @@ namespace TrainInfomationProviderService.TrainInfo.Entities /// internal void RemoveOutdateStorage() { - var baseDate = DateTime.Now.Date; - var outdates = DateIndices.Where(s => s.ToDateTime() < baseDate).ToArray(); - - Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在检查过期数据"); - foreach (var outdate in outdates) + lock (_lockObject) { - Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在移除过期数据 {0}", outdate); - DateIndices.Remove(outdate); - if (TrainInfoStorages.ContainsKey(outdate)) - TrainInfoStorages.Remove(outdate); - Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 完成移除过期数据 {0}", outdate); + var baseDate = DateTime.Now.Date; + var outdates = DateIndices.Where(s => s.ToDateTime() < baseDate).ToArray(); + + Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在检查过期数据"); + foreach (var outdate in outdates) + { + Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在移除过期数据 {0}", outdate); + DateIndices.Remove(outdate); + if (TrainInfoStorages.ContainsKey(outdate)) + TrainInfoStorages.Remove(outdate); + Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 完成移除过期数据 {0}", outdate); + } + Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在移除过期数据"); } - Trace.TraceInformation("[TRAIN_INDEX_STORAGE] 正在移除过期数据"); } + } } diff --git a/TrainInfomationProviderService/TrainInfo/Entities/Train.cs b/TrainInfomationProviderService/TrainInfo/Entities/Train.cs index 0402bab..e529512 100644 --- a/TrainInfomationProviderService/TrainInfo/Entities/Train.cs +++ b/TrainInfomationProviderService/TrainInfo/Entities/Train.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; @@ -38,6 +39,15 @@ namespace TrainInfomationProviderService.TrainInfo.Entities [JsonProperty("s")] public List TrainStops { get; set; } + [JsonIgnore] + public Dictionary TrainStopMap { get; private set; } + + [OnDeserialized] + void Init(StreamingContext ctx) + { + TrainStopMap = TrainStops == null ? null : TrainStops.Where(s => !string.IsNullOrEmpty(s.Code)).ToDictionary(s => s.Code, StringComparer.OrdinalIgnoreCase); + } + #region Implementation of IEquatable /// @@ -93,5 +103,25 @@ namespace TrainInfomationProviderService.TrainInfo.Entities } #endregion + + char? _trainClass; + + /// + /// 列车等级 + /// + [JsonIgnore] + public char TrainClass + { + get + { + if (_trainClass == null) + { + if (Code[0] == 'G' || Code[0] == 'D' || Code[0] == 'Z' || Code[0] == 'T') + _trainClass = Code[0]; + } + + return _trainClass ?? '*'; + } + } } } diff --git a/TrainInfomationProviderService/TrainInfo/Entities/TrainLineSegment.cs b/TrainInfomationProviderService/TrainInfo/Entities/TrainLineSegment.cs new file mode 100644 index 0000000..17c61b0 --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/Entities/TrainLineSegment.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TrainInfomationProviderService.StationInfo; +using TrainInfomationProviderService.StationInfo.Entities; + +namespace TrainInfomationProviderService.TrainInfo.Entities +{ + public class TrainLineSegment + { + StationDetailInfo _toStation; + StationDetailInfo _fromStation; + TimeSpan? _elapsedTime; + + public Train Train { get; private set; } + + public TrainStop From { get; private set; } + + public TrainStop To { get; private set; } + + public StationDetailInfo FromStation + { + get + { + if (_fromStation == null) + _fromStation = StationManager.Instance.Storage.Stations.GetValue(From.Code); + return _fromStation; + } + } + + public StationDetailInfo ToStation + { + get + { + if (_toStation == null) + _toStation = StationManager.Instance.Storage.Stations.GetValue(To.Code); + return _toStation; + } + } + + /// + /// 历时 + /// + public TimeSpan ElapsedTime + { + get + { + if (_elapsedTime == null) + _elapsedTime = To.Arrive - From.Left; + return _elapsedTime ?? TimeSpan.Zero; + } + } + + int? _calculatedMinutesBase; + + /// + /// 进行时间对比的基础分钟数 + /// + public int CalculatedMinutesBase + { + get + { + if (_calculatedMinutesBase == null) + { + var minutes = ElapsedTime.TotalMinutes; + var rate = 1.0; + switch (Train.TrainClass) + { + case 'G': + rate = 1.0; + break; + case 'D': + rate = 0.71428; + break; + case 'Z': + rate = 0.45714; + break; + case 'T': + rate = 0.4; + break; + default: + rate = 0.28571; + break; + } + _calculatedMinutesBase = (int)(rate*minutes); + } + return _calculatedMinutesBase ?? 0; + } + } + + /// + /// 创建 的新实例(TrainLineSegment) + /// + /// + /// + /// + public TrainLineSegment(Train train, TrainStop from, TrainStop to) + { + Train = train; + From = from; + To = to; + } + + + } +} diff --git a/TrainInfomationProviderService/TrainInfo/Entities/TrainStop.cs b/TrainInfomationProviderService/TrainInfo/Entities/TrainStop.cs index d9cb508..5373758 100644 --- a/TrainInfomationProviderService/TrainInfo/Entities/TrainStop.cs +++ b/TrainInfomationProviderService/TrainInfo/Entities/TrainStop.cs @@ -4,11 +4,14 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; +using TrainInfomationProviderService.StationInfo; namespace TrainInfomationProviderService.TrainInfo.Entities { public class TrainStop { + private string _name; + [JsonProperty("c")] public string Code { get; set; } @@ -24,5 +27,34 @@ namespace TrainInfomationProviderService.TrainInfo.Entities [JsonProperty("d")] public int Days { get; set; } + /// + /// 停靠时间 + /// + [JsonIgnore] + public TimeSpan StopTime + { + get + { + if (Arrive == null || Left == null) + return TimeSpan.MaxValue; + + return Left.Value - Arrive.Value; + } + } + + [JsonIgnore] + public string Name + { + get + { + if (string.IsNullOrEmpty(_name) && StationManager.Instance.Storage != null) + { + if (!string.IsNullOrEmpty(Code)) + _name = StationManager.Instance.Storage.Stations.GetValue(Code).SelectValue(s => s.Name); + else _name = string.Empty; + } + return _name; + } + } } } diff --git a/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitOnceResult.cs b/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitOnceResult.cs new file mode 100644 index 0000000..d4421c6 --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitOnceResult.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TrainInfomationProviderService.TrainInfo.Entities +{ + public class TrainTransitOnceResult + { + public TrainLineSegment First { get; private set; } + + public TrainLineSegment Second { get; private set; } + + /// + /// 不推荐车次 + /// + public bool NotRecommand { get; private set; } + + /// + /// 创建 的新实例(TrainTransitOnceResult) + /// + /// + /// + public TrainTransitOnceResult(TrainLineSegment first, TrainLineSegment second, DateTime date, bool notRecommand=false) + { + First = first; + Second = second; + InitTimeInfo(date); + NotRecommand = notRecommand; + } + + void InitTimeInfo(DateTime date) + { + FirstTrainLeftDate = date.Add(First.From.Left.Value.AddDays(-First.From.Left.Value.Days)); + FirstTrainArriveDate = FirstTrainLeftDate.Add(First.ElapsedTime); + + SecondTrainLeftDate = FirstTrainArriveDate.Date.Add(Second.From.Left.Value.AddDays(-Second.From.Left.Value.Days)); + if (SecondTrainLeftDate < FirstTrainArriveDate) + SecondTrainLeftDate = SecondTrainLeftDate.AddDays(1); + SecondTrainArriveDate = SecondTrainLeftDate.Add(Second.ElapsedTime); + + WaitElaspsedTime = SecondTrainLeftDate - FirstTrainArriveDate; + TotalElapsedTime = SecondTrainArriveDate - FirstTrainLeftDate; + } + + /// + /// 前车出发时间 + /// + public DateTime FirstTrainLeftDate { get; private set; } + + /// + /// 前车到达时间 + /// + public DateTime FirstTrainArriveDate { get; private set; } + + /// + /// 后车出发时间 + /// + public DateTime SecondTrainLeftDate { get; private set; } + + /// + /// 后车到达时间 + /// + public DateTime SecondTrainArriveDate { get; private set; } + + /// + /// 前车历时 + /// + public TimeSpan FirstElapsedTime { get { return First.ElapsedTime; } } + + /// + /// 后车历时 + /// + public TimeSpan SecondElapsedTime { get { return Second.ElapsedTime; } } + + /// + /// 等待耗时 + /// + public TimeSpan WaitElaspsedTime { get; private set; } + + /// + /// 总时间 + /// + public TimeSpan TotalElapsedTime { get; private set; } + } +} diff --git a/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitOnceResultCollection.cs b/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitOnceResultCollection.cs new file mode 100644 index 0000000..be5107c --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitOnceResultCollection.cs @@ -0,0 +1,336 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TrainInfomationProviderService.StationInfo; +using TrainInfomationProviderService.StationInfo.Entities; + +namespace TrainInfomationProviderService.TrainInfo.Entities +{ + public class TrainTransitOnceResultCollection : ICollection + { + List _list = new List(); + TrainTransitSearchOptions _options; + + /// + /// 日期 + /// + public DateTime Date { get; private set; } + + /// + /// 出发城市编码 + /// + public string FromCode { get; private set; } + + /// + /// 到达城市编码 + /// + public string ToCode { get; private set; } + + /// + /// 出发城市信息 + /// + public StationDetailInfo From { get; private set; } + + /// + /// 到达城市信息 + /// + public StationDetailInfo To { get; private set; } + + /// + /// 创建 的新实例(TrainTransitOnceResultCollection) + /// + /// + /// + /// + /// + public TrainTransitOnceResultCollection(DateTime date, TrainTransitSearchOptions options, string fromCode, string toCode) + { + ToCode = toCode; + FromCode = fromCode; + Date = date; + _options = options; + + From = StationManager.Instance.Storage.Stations.GetValue(fromCode); + To = StationManager.Instance.Storage.Stations.GetValue(toCode); + } + + #region Implementation of IEnumerable + + /// + /// 返回一个循环访问集合的枚举器。 + /// + /// + /// 可用于循环访问集合的 。 + /// + public IEnumerator GetEnumerator() + { + return _list.GetEnumerator(); + } + + /// + /// 返回一个循环访问集合的枚举数。 + /// + /// + /// 一个可用于循环访问集合的 对象。 + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Implementation of ICollection + + /// + /// 将某项添加到 中。 + /// + /// 要添加到 的对象。 为只读。 + public void Add(TrainTransitOnceResult item) + { + if (item == null || !CheckTransitLineAvailable(item)) + return; + + _list.Add(item); + } + + /// + /// 从 中移除所有项。 + /// + /// 为只读。 + public void Clear() + { + _list.Clear(); + } + + /// + /// 确定 是否包含特定值。 + /// + /// + /// 如果在 中找到 ,则为 true;否则为 false。 + /// + /// 要在 中定位的对象。 + public bool Contains(TrainTransitOnceResult item) + { + return _list.Contains(item); + } + + /// + /// 从特定的 索引开始,将 的元素复制到一个 中。 + /// + /// 作为从 复制的元素的目标的一维 必须具有从零开始的索引。 中从零开始的索引,从此索引处开始进行复制。 为 null。 小于 0。 中的元素数目大于从 到目标 末尾之间的可用空间。 + public void CopyTo(TrainTransitOnceResult[] array, int arrayIndex) + { + _list.CopyTo(array, arrayIndex); + } + + /// + /// 从 中移除特定对象的第一个匹配项。 + /// + /// + /// 如果已从 中成功移除 ,则为 true;否则为 false。 如果在原始 中没有找到 ,该方法也会返回 false。 + /// + /// 要从 中移除的对象。 为只读。 + public bool Remove(TrainTransitOnceResult item) + { + return _list.Remove(item); + } + + /// + /// 获取 中包含的元素数。 + /// + /// + /// 中包含的元素个数。 + /// + public int Count { get { return _list.Count; } } + + /// + /// 获取一个值,该值指示 是否为只读。 + /// + /// + /// 如果 为只读,则为 true;否则为 false。 + /// + public bool IsReadOnly { get { return false; } } + + public TrainTransitSearchOptions Options + { + get { return _options; } + } + + #endregion + + #region 辅助函数 + + + static int GetMinimalSpace(Train train, bool notRecommand) + { + var extraTime = notRecommand ? 60 : 0; + + switch (train.TrainClass) + { + case 'C': + case 'G': + return 30+extraTime; + case 'D': + return 30 + extraTime; + case 'Z': + return 60 + extraTime; + case 'T': + return 60 + extraTime; + default: + return 60 + extraTime; + } + } + + + static int GetMaxiumSpace(Train train, bool notRecommand) + { + var extraTime = notRecommand ? 60 : 0; + + switch (train.TrainClass) + { + case 'C': + case 'G': + return 150 + extraTime; + case 'D': + return 150 + extraTime; + case 'Z': + return 180 + extraTime; + case 'T': + return 180 + extraTime; + default: + return 240 + extraTime; + } + } + + /// + /// 获得比较理想中位时间 + /// + /// + /// + static int GetHighQualityTimeSpace(Train train, bool notRecommand) + { + var extraTime = notRecommand ? 60 : 0; + + switch (train.TrainClass) + { + case 'C': + case 'G': + return 60 + extraTime; + case 'D': + return 60 + extraTime; + case 'Z': + return 120 + extraTime; + case 'T': + return 120 + extraTime; + default: + return 120 + extraTime; + } + } + + + #endregion + + #region 验证车次换乘是否有效 + + + bool CheckTransitLineAvailable(TrainTransitOnceResult item) + { + //总共时间 + if (item.First.CalculatedMinutesBase + item.Second.CalculatedMinutesBase > _options.MaxExtraMinutes) + //时间过久 + return false; + + //时间衔接 + var arriveFirst = Date.Add(item.First.To.Arrive.Value); + var leftSecond = arriveFirst.Date.Add(item.Second.From.Left.Value); + + //早于发车 + if (leftSecond <= arriveFirst) + return false; + var space = (int)(leftSecond - arriveFirst).TotalMinutes; + if (space < GetMinimalSpace(item.First.Train, item.NotRecommand) || space > GetMaxiumSpace(item.Second.Train, item.NotRecommand)) + return false; + + return true; + } + + + #endregion + + #region 对列表进行二次优化 + + /// + /// 二次优化,排除小站等。 + /// + internal void SecondaryAnalyze() + { + //对于前车进行分组优化 + RemoveSmallStation(); + //排序 + ProcessPriority(); + } + + /// + /// 移除小站 + /// + void RemoveSmallStation() + { + var groups = this.GroupBy(s => s.First.Train.Code).Select(s => new { s.Key, Array = s.ToArray() }).ToArray(); + foreach (var g in groups) + { + if (g.Array.Length < 2) + continue; + + Array.Sort(g.Array, (s1, s2) => + { + var st1 = s1.First.To.StopTime.TotalMinutes; + var st2 = s2.First.To.StopTime.TotalMinutes; + + if (st1 < st2) return 1; + if (st1 > st2) return -1; + return 0; + }); + foreach (var line in g.Array.Skip(Options.SameLineMaxKeepStations)) + { + _list.Remove(line); + } + } + + groups = this.GroupBy(s => s.Second.Train.Code).Select(s => new { s.Key, Array = s.ToArray() }).ToArray(); + foreach (var g in groups) + { + if (g.Array.Length < 2) + continue; + + Array.Sort(g.Array, (s1, s2) => + { + var st1 = s1.Second.From.StopTime.TotalMinutes; + var st2 = s2.Second.From.StopTime.TotalMinutes; + + if (st1 < st2) return 1; + if (st1 > st2) return -1; + return 0; + }); + foreach (var line in g.Array.Skip(Options.SameLineMaxKeepStations)) + { + _list.Remove(line); + } + } + } + + #endregion + + #region 分组排序 + + void ProcessPriority() + { + + } + + #endregion + } +} diff --git a/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitSearchOptions.cs b/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitSearchOptions.cs new file mode 100644 index 0000000..df2962c --- /dev/null +++ b/TrainInfomationProviderService/TrainInfo/Entities/TrainTransitSearchOptions.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TrainInfomationProviderService.TrainInfo.Entities +{ + public class TrainTransitSearchOptions + { + /// + /// 最大允许多出来的时间 + /// + public int MaxExtraMinutes { get; set; } + + /// + /// 同一个车次最高保持的换乘站数 + /// + public int SameLineMaxKeepStations { get; set; } + + /// + /// 创建 的新实例(TrainTransitSearchOptions) + /// + public TrainTransitSearchOptions() + { + SameLineMaxKeepStations = 3; + } + } +} diff --git a/TrainInfomationProviderService/TrainInfo/TrainInfoManager.cs b/TrainInfomationProviderService/TrainInfo/TrainInfoManager.cs index e0defa7..7fffdb4 100644 --- a/TrainInfomationProviderService/TrainInfo/TrainInfoManager.cs +++ b/TrainInfomationProviderService/TrainInfo/TrainInfoManager.cs @@ -57,10 +57,10 @@ namespace TrainInfomationProviderService.TrainInfo { get { - if (RunTimeContext.IsWeb) - { - return HostingEnvironment.Cache["_12306trains"] as IndexStorage; - } + //if (RunTimeContext.IsWeb) + //{ + // return HostingEnvironment.Cache["_12306trains"] as IndexStorage; + //} return _storage; } } @@ -72,55 +72,55 @@ namespace TrainInfomationProviderService.TrainInfo _dataFilePath = PathUtility.Combine(RunTimeContext.DataStorageRoot, "trains.json"); Trace.TraceInformation("[TAININFOMANGER] 目标缓存文件路径:{0}", _dataFilePath); - if (RunTimeContext.IsWeb) - { - Trace.TraceInformation("[TAININFOMANGER] WEB模式"); + //if (RunTimeContext.IsWeb) + //{ + // Trace.TraceInformation("[TAININFOMANGER] WEB模式"); - InitWeb(); - } - else - { - Trace.TraceInformation("[TAININFOMANGER] 服务模式"); - InitService(); - } + // InitWeb(); + //} + //else + //{ + Trace.TraceInformation("[TAININFOMANGER] 服务模式"); + InitService(); + //} } #region web环境 - /// - /// web环境初始化 - /// - void InitWeb() - { - Trace.TraceInformation("[TAININFOMANGER] 初始化WEB模式"); - LoadCache(); - } + ///// + ///// web环境初始化 + ///// + //void InitWeb() + //{ + // Trace.TraceInformation("[TAININFOMANGER] 初始化WEB模式"); + // LoadCache(); + //} - void LoadCache() - { - Trace.TraceInformation("[TAININFOMANGER] 正在检查缓存刷新"); - _storage = null; - if (File.Exists(_dataFilePath)) - { - Trace.TraceInformation("[TAININFOMANGER] 正在加载缓存"); - _storage = JsonConvert.DeserializeObject(File.ReadAllText(_dataFilePath)); - Trace.TraceInformation("[TAININFOMANGER] 缓存加载完成"); - } + //void LoadCache() + //{ + // Trace.TraceInformation("[TAININFOMANGER] 正在检查缓存刷新"); + // _storage = null; + // if (File.Exists(_dataFilePath)) + // { + // Trace.TraceInformation("[TAININFOMANGER] 正在加载缓存"); + // _storage = JsonConvert.DeserializeObject(File.ReadAllText(_dataFilePath)); + // Trace.TraceInformation("[TAININFOMANGER] 缓存加载完成"); + // } - if (Storage != null) - { - Trace.TraceInformation("[TAININFOMANGER] 正在加入HttpRuntime缓存"); - HostingEnvironment.Cache.Add("_12306trains", Storage, new CacheDependency(_dataFilePath), - Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.High, - (_1, _2, _3) => - { - Trace.TraceInformation("[TAININFOMANGER] HttpRuntime缓存已被清除,原因:{0},正在重新加载", _3); - LoadCache(); - }); - Trace.TraceInformation("[TAININFOMANGER] HttpRuntime缓存加入完成"); - } - } + // if (Storage != null) + // { + // Trace.TraceInformation("[TAININFOMANGER] 正在加入HttpRuntime缓存"); + // HostingEnvironment.Cache.Add("_12306trains", Storage, new CacheDependency(_dataFilePath), + // Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.High, + // (_1, _2, _3) => + // { + // Trace.TraceInformation("[TAININFOMANGER] HttpRuntime缓存已被清除,原因:{0},正在重新加载", _3); + // LoadCache(); + // }); + // Trace.TraceInformation("[TAININFOMANGER] HttpRuntime缓存加入完成"); + // } + //} #endregion @@ -130,11 +130,11 @@ namespace TrainInfomationProviderService.TrainInfo private Timer _checkStationTimer; private IndexStorage _storage; -#if DEBUG - private bool _ininitalized = true; -#else - private bool _ininitalized = false; -#endif + //#if DEBUG + // private bool _ininitalized = false; + //#else + // private bool _ininitalized = false; + //#endif /// /// 服务模式初始化 @@ -152,16 +152,14 @@ namespace TrainInfomationProviderService.TrainInfo _storage = new IndexStorage(); } var lastestVersion = GetLatestVersionFromWeb(); - var needUpdate = _storage == null || _storage.Version < lastestVersion; + //var needUpdate = _storage == null || _storage.Version < lastestVersion; Trace.TraceInformation("[TAININFOMANGER] 最新版本:{0}", lastestVersion); - if (needUpdate || !_ininitalized) - { - Trace.TraceInformation("[TAININFOMANGER] 正在刷新缓存"); - RefreshTrainInfo(lastestVersion); - Trace.TraceInformation("[TAININFOMANGER] 缓存数据已刷新"); - } - _ininitalized = true; + Trace.TraceInformation("[TAININFOMANGER] 正在刷新缓存"); + RefreshTrainInfo(lastestVersion); + Trace.TraceInformation("[TAININFOMANGER] 缓存数据已刷新"); + + //_ininitalized = true; _checkStationTimer = new Timer(_ => CheckTrainVersion(), null, new TimeSpan(0, 30, 0), Timeout.InfiniteTimeSpan); } @@ -173,6 +171,7 @@ namespace TrainInfomationProviderService.TrainInfo { Storage.Version = version; loader.LoadTrainInfo(Storage); + Storage.RemoveOutdateStorage(); } catch (Exception ex) { @@ -190,10 +189,13 @@ namespace TrainInfomationProviderService.TrainInfo void CheckTrainVersion() { var lastestVersion = GetLatestVersionFromWeb(); - if (Storage == null || Storage.Version < lastestVersion) + try { RefreshTrainInfo(lastestVersion); } + catch (Exception) + { + } _checkStationTimer.Change(new TimeSpan(0, 30, 0), Timeout.InfiniteTimeSpan); } diff --git a/TrainInfomationProviderService/TrainInfo/TrainInfoSearchProvider.cs b/TrainInfomationProviderService/TrainInfo/TrainInfoSearchProvider.cs index 5cd88f5..1606446 100644 --- a/TrainInfomationProviderService/TrainInfo/TrainInfoSearchProvider.cs +++ b/TrainInfomationProviderService/TrainInfo/TrainInfoSearchProvider.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; +using TrainInfomationProviderService.StationInfo; using TrainInfomationProviderService.TrainInfo.Entities; namespace TrainInfomationProviderService.TrainInfo @@ -12,17 +13,149 @@ namespace TrainInfomationProviderService.TrainInfo { public IndexStorage Storage { get; private set; } - public TrainInfoSearchProvider(IndexStorage storage) + public Dictionary> SameStationInfo { get; set; } + + public TrainInfoSearchProvider() { Trace.TraceInformation("[TRAIN_INFO_SEARCH_PROVIDER] 正在创建车站-车次信息索引对象"); - Storage = storage; + Storage = TrainInfoManager.Instance.Storage; - foreach (var trainInfoStorage in storage.TrainInfoStorages) + foreach (var trainInfoStorage in Storage.TrainInfoStorages) { Trace.TraceInformation("[TRAIN_INFO_SEARCH_PROVIDER] 正在初始化 {0} 车站-车次信息索引对象", trainInfoStorage.Key); trainInfoStorage.Value.InitStationTrainData(); } Trace.TraceInformation("[TRAIN_INFO_SEARCH_PROVIDER] 完成创建车站-车次信息索引对象"); } + + public IEnumerable FindDirectTrains(DateTime date, string from, string to) + { + var infoStorage = Storage.TrainInfoStorages.GetValue(date.ToString("yyyy-MM-dd")); + if (infoStorage == null) + yield break; + + for (int i = 0; i < infoStorage.Trains.Count; i++) + { + var train = infoStorage.Trains[i]; + var stf = FindStop(train, from); + if (stf == null) + continue; + + var stt = FindStop(train, to); + if (stt == null) + continue; + + if (stf.Index >= stt.Index) + continue; + + yield return new TrainLineSegment(train, stf, stt); + } + } + + public TrainTransitOnceResultCollection FindOnceTransitTrains(DateTime date, string from, string to, TrainTransitSearchOptions options) + { + var result = new TrainTransitOnceResultCollection(date, options, from, to); + var infoStorage = Storage.TrainInfoStorages.GetValue(date.ToString("yyyy-MM-dd")); + if (infoStorage == null) + return result; + + //发车经过车站 + var fromStations = GetSameStations(from); + var toStations = GetSameStations(to); + var fromTrains = fromStations.Select(s => infoStorage.StationLeftTrainData.GetValue(s)).ExceptNull().SelectMany(s => s).ToArray(); + var toTrains = toStations.Select(s => infoStorage.StationLeftTrainData.GetValue(s)).ExceptNull().SelectMany(s => s).ToArray(); + + foreach (var fromTrain in fromTrains) + { + var startStop = FindStop(fromTrain, from); + foreach (var fstop in fromTrain.TrainStops.SkipWhile(x => startStop != x).Skip(1)) + { + foreach (var toTrain in toTrains) + { + var toStop = FindStop(toTrain, to); + foreach (var tStop in toTrain.TrainStops.TakeWhile(x => toStop != x)) + { + if (fstop.Code == tStop.Code) + { + //一个中转点 + var line = new TrainTransitOnceResult( + new TrainLineSegment(fromTrain, startStop, fstop), + new TrainLineSegment(toTrain, tStop, toStop), + date + ); + result.Add(line); + } + else if (IsStopInclude(fstop.Code, tStop.Code)) + { + //一个中转点 + var line = new TrainTransitOnceResult( + new TrainLineSegment(fromTrain, startStop, fstop), + new TrainLineSegment(toTrain, tStop, toStop), + date, + true + ); + result.Add(line); + } + break; + } + } + } + } + result.SecondaryAnalyze(); + + return result; + } + + HashSet GetSameStations(string code) + { + var map = SameStationManager.SameStationMap; + if (map != null) + { + var hash = map.GetValue(code); + if (hash != null) + return hash; + } + + return new HashSet() { code }; + } + + bool IsStopInclude(string code1, string code2) + { + + var map = SameStationManager.SameStationMap; + if (map == null) + return false; + + var set = map.GetValue(code1); + + return set != null && set.Contains(code2); + } + + TrainStop FindStop(Train train, string code) + { + if (train == null || train.TrainStopMap == null) + return null; + + var stop = train.TrainStopMap.GetValue(code); + if (stop != null) + return stop; + + var map = SameStationManager.SameStationMap; + if (map == null) + return null; + + var hash = map.GetValue(code); + if (hash == null) + return null; + + foreach (var s in hash) + { + stop = train.TrainStopMap.GetValue(s); + if (stop != null) + return stop; + } + + return null; + } } } diff --git a/TrainInfomationProviderService/TrainInfo/WebDataProvider.cs b/TrainInfomationProviderService/TrainInfo/WebDataProvider.cs index f7581c6..234b879 100644 --- a/TrainInfomationProviderService/TrainInfo/WebDataProvider.cs +++ b/TrainInfomationProviderService/TrainInfo/WebDataProvider.cs @@ -152,12 +152,20 @@ namespace TrainInfomationProviderService.TrainInfo curTrainList.AddRange(alltrains); curStorage.Init(); } + if (alltrains.Count > 0) + { + curStorage.InitStationTrainData(); - //缓存 - Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 开始缓存车次数据 {0}", date); - var filepath = PathUtility.Combine(_dataFolder, date + ".json"); - File.WriteAllText(filepath, JsonConvert.SerializeObject(curStorage)); - Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 车次数据缓存成功!"); + //缓存 + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 开始缓存车次数据 {0}", date); + var filepath = PathUtility.Combine(_dataFolder, date + ".json"); + File.WriteAllText(filepath, JsonConvert.SerializeObject(curStorage)); + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 车次数据缓存成功!"); + } + else + { + Trace.TraceInformation("[TRAIN_DATA_WEB_PROVIDER] 车次 {0} 数据无变化", date); + } indexStorage.DateIndices.SafeAdd(date); indexStorage.TrainInfoStorages.AddOrUpdate(date, curStorage); diff --git a/TrainInfomationProviderService/TrainInfomationProviderService.csproj b/TrainInfomationProviderService/TrainInfomationProviderService.csproj index 8db9aca..59a1458 100644 --- a/TrainInfomationProviderService/TrainInfomationProviderService.csproj +++ b/TrainInfomationProviderService/TrainInfomationProviderService.csproj @@ -64,14 +64,18 @@ - + + + + +