結對項目——地鐵【項目說明及項目實現過程】
首先附上我們整個項目的github地址 https://github.com/shenyunhan/subway
【項目說明】
按照項目需求中的說明,在命令行中輸入“/b 起始站 終點站”命令,會輸出依次輸出經歷了多少站(換乘一站算作三站)、遍歷的站名、途中換乘的線路。
在命令行中輸入"\g"命令會彈出可視化GUI界面
在左側輸入起點和終點,點擊確定,如果輸入的站名不合法會出現提示框(如下圖1),否則會在右側標出乘客的路線(如下圖2)。
(圖1)
(圖2)
該界面支持拖動地圖位置及放大縮小路線圖的功能,在縮放或拖動後會重構路線圖,且路線圖還會明確標出(如下圖)。
【項目實現過程】
我二人的分工是一人寫GUI界面,一人寫實現最短路搜索算法,選用的語言是C#。
首先先把北京地鐵所有線路及其對應站點存入.json文件,以一定的方式讀取.json文件中的內容、建圖
public static void InitMap() { map = LoadMap("BeijingSubwayMap.json"); stationIds = new Dictionary<string, int>(); lineIds = new Dictionary<string, int>(); graph = new UndirectedGraph(); for (int i = 0; i < map.Stations.Count; i++) stationIds[map.Stations[i].Name] = i; for (int i = 0; i < map.Lines.Count; i++) lineIds[map.Lines[i].Name] = i; foreach (SubwayLine line inmap.Lines) { for (int i = 1; i < line.Path.Count; i++) { int from = stationIds[line.Path[i - 1]]; int to = stationIds[line.Path[i]]; graph.AddEdge(from, to, lineIds[line.Name]); } } }
然後在獲取起點終點後用SPFA算法搜索出最短路
public KeyValuePair<int, List<KeyValuePair<int, int>>> ShortestPath(int source, int target) { Dictionary<int, int> dist = new Dictionary<int, int>(); Dictionary<int, KeyValuePair<int, int>> pre = new Dictionary<int, KeyValuePair<int, int>>(); foreach (int id in adj.Keys) { dist[id] = 0x3f3f3f3f; } dist[source] = 0; Queue<Edge> q = new Queue<Edge>(); HashSet<Edge> vis = new HashSet<Edge>(); q.Enqueue(new Edge(source, -1)); vis.Add(new Edge(source, -1)); while (q.Count != 0) { Edge x = q.Dequeue(); foreach (Edge e in adj[x.To]) { int temp = dist[x.To] + 1; if (x.Line != -1 && e.Line != x.Line) temp += 3; if (dist[e.To] > temp) { dist[e.To] = temp; pre[e.To] = new KeyValuePair<int, int>(x.To, e.Line); if (!vis.Contains(e)) { q.Enqueue(e); vis.Add(e); } } } vis.Remove(x); } List<KeyValuePair<int, int>> path = new List<KeyValuePair<int, int>>(); for (int i = target; pre.ContainsKey(i); i = pre[i].Key) path.Add(pre[i]); return new KeyValuePair<int, List<KeyValuePair<int, int>>>(dist[target], path); }
返回的Dictionary保存了搜索出的最短路路徑,然後再進行輸出即可。
再說可視化界面。
搜索路徑的部分與上文基本一致,用兩個Textbox獲取用戶輸入,右側用一個PictureBox放置北京地鐵線路圖,用一個Panel放置這個PictureBox。
由於已經搜索出了最短路,所以難點不在於畫出路徑,而是在於如何在縮放圖片或拖動圖片位置後依舊能準確地畫出路線。
那麽這裏我們在解決這個問題時在MouseMove()中獲取鼠標移動的距離
moveX = Cursor.Position.X - mouseDownPoint.X; moveY = Cursor.Position.Y - mouseDownPoint.Y; x = pictureBox1.Location.X + moveX; y = pictureBox1.Location.Y + moveY;
在MouseWheel()中獲取鼠標滾輪的縮放大小同時乘以相應的步長然後改變PictureBox大小。
pictureBox1.Width += (int)(zoomStep * 1.5690440060698027314112291350531); pictureBox1.Height += zoomStep;
(註:1.5690440060698027314112291350531這個數字是原圖片長寬比,為了等比例縮放圖片而設定)
但是簡單的這樣改變PictureBox的位置會發生一些問題:拖動圖片後再縮放圖片圖片就不會被固定在左上角,而且有可能縮小到一個特別小的比例就“消失了”,所以我們解決這個問題的時候就強制固定了縮小後的圖片小於原圖比例時PictureBox的位置,且禁止用戶再縮小圖片。
VX = (int)((double)X * (ow - pictureBox1.Width) / ow); VY = (int)((double)Y * (oh - pictureBox1.Height) / oh); pictureBox1.Location = new Point(pictureBox1.Location.X + VX, pictureBox1.Location.Y + VY); int x = pictureBox1.Location.X; int y = pictureBox1.Location.Y; if (x > 0) x = 0; if (y > 0) y = 0; if (x + pictureBox1.Width < panel1.Width) x = panel1.Width - pictureBox1.Width; if (y + pictureBox1.Height < panel1.Height) y = panel1.Height - pictureBox1.Height; pictureBox1.Location = new Point(x, y);
獲取當前圖片大小,除以原圖大小即可得縮放的比例(scale),然後可以獲得縮放後地鐵站在圖中的新坐標,畫出路線即可
private void DrawPath() { Graphics g = pictureBox1.CreateGraphics(); double scale = (double)pictureBox1.Width / 1034; int r = (int)(radius * scale / 2); foreach (var i in path) { int x = (int)(i.X * scale); int y = (int)(i.Y * scale); g.FillEllipse(Brushes.Black, x - r, y - r, 2 * r, 2 * r); } }
DrawPath()放在MouseMove()中,故每次鼠標拖動圖片時都會重構路線圖。
以上就是我們結對項目的實現過程啦,有不足之處請各位大佬多多指教~
最後請一定要讓我吐槽一句,北京地鐵怎麽有那麽多站啊啊啊啊啊!我每一站的坐標都是辛辛苦苦的量出來的!!!真的好多站啊啊啊啊啊!!!
幸虧我想起來開發安卓的時候用過一個pxCook的軟件,標一下點可以顯示你這個點的位置要不然這個項目量這些站點位置不知道要量到猴年馬月去了QAQAQ
結對項目——地鐵【項目說明及項目實現過程】