This commit is contained in:
iFish 2014-11-27 23:25:36 +08:00
parent dceda08407
commit 3015fb0407
15 changed files with 1076 additions and 191 deletions

View File

@ -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());
}

View File

@ -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<HashSet<string>>
{
private SameStationCollection()
{
}
public HashSet<string> FindSameStations(string code)
{
return this.FirstOrDefault(s => s.Contains(code));
}
private static SameStationCollection _instance;
private static readonly object _lockObject = new object();
/// <summary>
/// 获得实例对象
/// </summary>
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<SameStationCollection>(File.ReadAllText(file));
}
else
{
_instance = new SameStationCollection();
}
}
}
}
return _instance;
}
}
}
}

View File

@ -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<string, HashSet<string>> 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<string, HashSet<string>>(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<List<HashSet<string>>>(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<string> collection = null;
for (var i = stations.Count - 1; i >= 0; i--)
{
if (stations[i].Name.IndexOf(st.Name) != 0)
continue;
(collection ?? (collection = new HashSet<string>(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<string>(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<string>(HttpMethod.Get, _url).Send();
if (!ctx.IsValid())
return false;
//缓存
File.WriteAllText(_cacheUrl, ctx.Result);
var list = JsonConvert.DeserializeObject<List<HashSet<string>>>(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<string>(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;
}
}
}

View File

@ -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环境
/// <summary>
/// web环境初始化
/// </summary>
void InitWeb()
{
Trace.TraceInformation("[STATION] 初始化WEB模式");
LoadCache();
}
///// <summary>
///// web环境初始化
///// </summary>
//void InitWeb()
//{
// Trace.TraceInformation("[STATION] 初始化WEB模式");
// LoadCache();
//}
void LoadCache()
{
Trace.TraceInformation("[STATION] 正在检查缓存刷新");
_storage = null;
if (File.Exists(_dataFilePath))
{
Trace.TraceInformation("[STATION] 正在加载缓存");
_storage = JsonConvert.DeserializeObject<StationStorage>(File.ReadAllText(_dataFilePath));
Trace.TraceInformation("[STATION] 缓存加载完成");
}
//void LoadCache()
//{
// Trace.TraceInformation("[STATION] 正在检查缓存刷新");
// _storage = null;
// if (File.Exists(_dataFilePath))
// {
// Trace.TraceInformation("[STATION] 正在加载缓存");
// _storage = JsonConvert.DeserializeObject<StationStorage>(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);

View File

@ -11,6 +11,8 @@ namespace TrainInfomationProviderService.TrainInfo.Entities
{
public class IndexStorage
{
private readonly object _lockObject = new object();
/// <summary>
/// 创建 <see cref="IndexStorage" /> 的新实例(IndexStorage)
/// </summary>
@ -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
/// </summary>
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] 正在移除过期数据");
}
}
}

View File

@ -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<TrainStop> TrainStops { get; set; }
[JsonIgnore]
public Dictionary<string, TrainStop> 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<Train>
/// <summary>
@ -93,5 +103,25 @@ namespace TrainInfomationProviderService.TrainInfo.Entities
}
#endregion
char? _trainClass;
/// <summary>
/// 列车等级
/// </summary>
[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 ?? '*';
}
}
}
}

View File

@ -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;
}
}
/// <summary>
/// 历时
/// </summary>
public TimeSpan ElapsedTime
{
get
{
if (_elapsedTime == null)
_elapsedTime = To.Arrive - From.Left;
return _elapsedTime ?? TimeSpan.Zero;
}
}
int? _calculatedMinutesBase;
/// <summary>
/// 进行时间对比的基础分钟数
/// </summary>
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;
}
}
/// <summary>
/// 创建 <see cref="TrainLineSegment" /> 的新实例(TrainLineSegment)
/// </summary>
/// <param name="train"></param>
/// <param name="from"></param>
/// <param name="to"></param>
public TrainLineSegment(Train train, TrainStop from, TrainStop to)
{
Train = train;
From = from;
To = to;
}
}
}

View File

@ -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; }
/// <summary>
/// 停靠时间
/// </summary>
[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;
}
}
}
}

View File

@ -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; }
/// <summary>
/// 不推荐车次
/// </summary>
public bool NotRecommand { get; private set; }
/// <summary>
/// 创建 <see cref="TrainTransitOnceResult" /> 的新实例(TrainTransitOnceResult)
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
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;
}
/// <summary>
/// 前车出发时间
/// </summary>
public DateTime FirstTrainLeftDate { get; private set; }
/// <summary>
/// 前车到达时间
/// </summary>
public DateTime FirstTrainArriveDate { get; private set; }
/// <summary>
/// 后车出发时间
/// </summary>
public DateTime SecondTrainLeftDate { get; private set; }
/// <summary>
/// 后车到达时间
/// </summary>
public DateTime SecondTrainArriveDate { get; private set; }
/// <summary>
/// 前车历时
/// </summary>
public TimeSpan FirstElapsedTime { get { return First.ElapsedTime; } }
/// <summary>
/// 后车历时
/// </summary>
public TimeSpan SecondElapsedTime { get { return Second.ElapsedTime; } }
/// <summary>
/// 等待耗时
/// </summary>
public TimeSpan WaitElaspsedTime { get; private set; }
/// <summary>
/// 总时间
/// </summary>
public TimeSpan TotalElapsedTime { get; private set; }
}
}

View File

@ -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<TrainTransitOnceResult>
{
List<TrainTransitOnceResult> _list = new List<TrainTransitOnceResult>();
TrainTransitSearchOptions _options;
/// <summary>
/// 日期
/// </summary>
public DateTime Date { get; private set; }
/// <summary>
/// 出发城市编码
/// </summary>
public string FromCode { get; private set; }
/// <summary>
/// 到达城市编码
/// </summary>
public string ToCode { get; private set; }
/// <summary>
/// 出发城市信息
/// </summary>
public StationDetailInfo From { get; private set; }
/// <summary>
/// 到达城市信息
/// </summary>
public StationDetailInfo To { get; private set; }
/// <summary>
/// 创建 <see cref="TrainTransitOnceResultCollection"/> 的新实例(TrainTransitOnceResultCollection)
/// </summary>
/// <param name="options"></param>
/// <param name="date"></param>
/// <param name="fromCode"></param>
/// <param name="toCode"></param>
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
/// <summary>
/// 返回一个循环访问集合的枚举器。
/// </summary>
/// <returns>
/// 可用于循环访问集合的 <see cref="T:System.Collections.Generic.IEnumerator`1"/>。
/// </returns>
public IEnumerator<TrainTransitOnceResult> GetEnumerator()
{
return _list.GetEnumerator();
}
/// <summary>
/// 返回一个循环访问集合的枚举数。
/// </summary>
/// <returns>
/// 一个可用于循环访问集合的 <see cref="T:System.Collections.IEnumerator"/> 对象。
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Implementation of ICollection<TrainTransitOnceResult>
/// <summary>
/// 将某项添加到 <see cref="T:System.Collections.Generic.ICollection`1"/> 中。
/// </summary>
/// <param name="item">要添加到 <see cref="T:System.Collections.Generic.ICollection`1"/> 的对象。</param><exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> 为只读。</exception>
public void Add(TrainTransitOnceResult item)
{
if (item == null || !CheckTransitLineAvailable(item))
return;
_list.Add(item);
}
/// <summary>
/// 从 <see cref="T:System.Collections.Generic.ICollection`1"/> 中移除所有项。
/// </summary>
/// <exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> 为只读。</exception>
public void Clear()
{
_list.Clear();
}
/// <summary>
/// 确定 <see cref="T:System.Collections.Generic.ICollection`1"/> 是否包含特定值。
/// </summary>
/// <returns>
/// 如果在 <see cref="T:System.Collections.Generic.ICollection`1"/> 中找到 <paramref name="item"/>,则为 true否则为 false。
/// </returns>
/// <param name="item">要在 <see cref="T:System.Collections.Generic.ICollection`1"/> 中定位的对象。</param>
public bool Contains(TrainTransitOnceResult item)
{
return _list.Contains(item);
}
/// <summary>
/// 从特定的 <see cref="T:System.Array"/> 索引开始,将 <see cref="T:System.Collections.Generic.ICollection`1"/> 的元素复制到一个 <see cref="T:System.Array"/> 中。
/// </summary>
/// <param name="array">作为从 <see cref="T:System.Collections.Generic.ICollection`1"/> 复制的元素的目标的一维 <see cref="T:System.Array"/>。 <see cref="T:System.Array"/> 必须具有从零开始的索引。</param><param name="arrayIndex"><paramref name="array"/> 中从零开始的索引,从此索引处开始进行复制。</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> 为 null。</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> 小于 0。</exception><exception cref="T:System.ArgumentException">源 <see cref="T:System.Collections.Generic.ICollection`1"/> 中的元素数目大于从 <paramref name="arrayIndex"/> 到目标 <paramref name="array"/> 末尾之间的可用空间。</exception>
public void CopyTo(TrainTransitOnceResult[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
/// <summary>
/// 从 <see cref="T:System.Collections.Generic.ICollection`1"/> 中移除特定对象的第一个匹配项。
/// </summary>
/// <returns>
/// 如果已从 <see cref="T:System.Collections.Generic.ICollection`1"/> 中成功移除 <paramref name="item"/>,则为 true否则为 false。 如果在原始 <see cref="T:System.Collections.Generic.ICollection`1"/> 中没有找到 <paramref name="item"/>,该方法也会返回 false。
/// </returns>
/// <param name="item">要从 <see cref="T:System.Collections.Generic.ICollection`1"/> 中移除的对象。</param><exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> 为只读。</exception>
public bool Remove(TrainTransitOnceResult item)
{
return _list.Remove(item);
}
/// <summary>
/// 获取 <see cref="T:System.Collections.Generic.ICollection`1"/> 中包含的元素数。
/// </summary>
/// <returns>
/// <see cref="T:System.Collections.Generic.ICollection`1"/> 中包含的元素个数。
/// </returns>
public int Count { get { return _list.Count; } }
/// <summary>
/// 获取一个值,该值指示 <see cref="T:System.Collections.Generic.ICollection`1"/> 是否为只读。
/// </summary>
/// <returns>
/// 如果 <see cref="T:System.Collections.Generic.ICollection`1"/> 为只读,则为 true否则为 false。
/// </returns>
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;
}
}
/// <summary>
/// 获得比较理想中位时间
/// </summary>
/// <param name="train"></param>
/// <returns></returns>
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
/// <summary>
/// 二次优化,排除小站等。
/// </summary>
internal void SecondaryAnalyze()
{
//对于前车进行分组优化
RemoveSmallStation();
//排序
ProcessPriority();
}
/// <summary>
/// 移除小站
/// </summary>
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
}
}

View File

@ -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
{
/// <summary>
/// 最大允许多出来的时间
/// </summary>
public int MaxExtraMinutes { get; set; }
/// <summary>
/// 同一个车次最高保持的换乘站数
/// </summary>
public int SameLineMaxKeepStations { get; set; }
/// <summary>
/// 创建 <see cref="TrainTransitSearchOptions" /> 的新实例(TrainTransitSearchOptions)
/// </summary>
public TrainTransitSearchOptions()
{
SameLineMaxKeepStations = 3;
}
}
}

View File

@ -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环境
/// <summary>
/// web环境初始化
/// </summary>
void InitWeb()
{
Trace.TraceInformation("[TAININFOMANGER] 初始化WEB模式");
LoadCache();
}
///// <summary>
///// web环境初始化
///// </summary>
//void InitWeb()
//{
// Trace.TraceInformation("[TAININFOMANGER] 初始化WEB模式");
// LoadCache();
//}
void LoadCache()
{
Trace.TraceInformation("[TAININFOMANGER] 正在检查缓存刷新");
_storage = null;
if (File.Exists(_dataFilePath))
{
Trace.TraceInformation("[TAININFOMANGER] 正在加载缓存");
_storage = JsonConvert.DeserializeObject<IndexStorage>(File.ReadAllText(_dataFilePath));
Trace.TraceInformation("[TAININFOMANGER] 缓存加载完成");
}
//void LoadCache()
//{
// Trace.TraceInformation("[TAININFOMANGER] 正在检查缓存刷新");
// _storage = null;
// if (File.Exists(_dataFilePath))
// {
// Trace.TraceInformation("[TAININFOMANGER] 正在加载缓存");
// _storage = JsonConvert.DeserializeObject<IndexStorage>(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
/// <summary>
/// 服务模式初始化
@ -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);
}

View File

@ -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<string, HashSet<string>> 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<TrainLineSegment> 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<string> GetSameStations(string code)
{
var map = SameStationManager.SameStationMap;
if (map != null)
{
var hash = map.GetValue(code);
if (hash != null)
return hash;
}
return new HashSet<string>() { 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;
}
}
}

View File

@ -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);

View File

@ -64,14 +64,18 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RunTimeContext.cs" />
<Compile Include="StationInfo\Entities\BasicStation.cs" />
<Compile Include="StationInfo\Entities\SameStationCollection.cs" />
<Compile Include="StationInfo\Entities\StationDetailInfo.cs" />
<Compile Include="StationInfo\Entities\StationStorage.cs" />
<Compile Include="StationInfo\SameStationManager.cs" />
<Compile Include="StationInfo\StationManager.cs" />
<Compile Include="TrainInfo\Entities\IndexStorage.cs" />
<Compile Include="TrainInfo\Entities\Train.cs" />
<Compile Include="TrainInfo\Entities\TrainInfoStorage.cs" />
<Compile Include="TrainInfo\Entities\TrainLineSegment.cs" />
<Compile Include="TrainInfo\Entities\TrainStop.cs" />
<Compile Include="TrainInfo\Entities\TrainTransitOnceResult.cs" />
<Compile Include="TrainInfo\Entities\TrainTransitOnceResultCollection.cs" />
<Compile Include="TrainInfo\Entities\TrainTransitSearchOptions.cs" />
<Compile Include="TrainInfo\TrainInfoManager.cs" />
<Compile Include="TrainInfo\TrainInfoSearchProvider.cs" />
<Compile Include="TrainInfo\WebDataProvider.cs" />