结对项目——地铁【项目说明及项目实现过程】

首先附上我们整个项目的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 = ; i < map.Stations.Count; i++)
                stationIds[map.Stations[i].Name] = i;

            for (int i = ; i < map.Lines.Count; i++)
                lineIds[map.Lines[i].Name] = i;

            foreach (SubwayLine line in map.Lines)
            {
                for (int i = ; i < line.Path.Count; i++)
                {
                    int from = stationIds[line.Path[i - ]];
                    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] = ;

            Queue<Edge> q = new Queue<Edge>();
            HashSet<Edge> vis = new HashSet<Edge>();
            q.Enqueue(new Edge(source, -));
            vis.Add(new Edge(source, -));

            while (q.Count != )
            {
                Edge x = q.Dequeue();
                foreach (Edge e in adj[x.To])
                {
                    int temp = dist[x.To] + ;
                    if (x.Line != - && e.Line != x.Line) temp += ;
                    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 > ) x = ;
            if (y > ) y = ;

            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 / ;
            int r = (int)(radius * scale / );
            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,  * r,  * r);
            }
        }

DrawPath()放在MouseMove()中,故每次鼠标拖动图片时都会重构路线图。

以上就是我们结对项目的实现过程啦,有不足之处请各位大佬多多指教~

最后请一定要让我吐槽一句,北京地铁怎么有那么多站啊啊啊啊啊!我每一站的坐标都是辛辛苦苦的量出来的!!!真的好多站啊啊啊啊啊!!!

结对项目——地铁【项目说明及项目实现过程】

幸亏我想起来开发安卓的时候用过一个pxCook的软件,标一下点可以显示你这个点的位置要不然这个项目量这些站点位置不知道要量到猴年马月去了QAQAQ