基于Qt实现的可视化地铁换乘系统

前言

这是大二头一学期的时候的C++大作业,要求是写2000行,觉得地铁换乘很好玩遂实现一下,主要的代码量还是在Qt里面,尤其是布局位置这一类的,当然底层的算法也是写了不少的。

当然大二的水平实在有限,当时配Qt没有留下来一个完整操作流程。

如果要看最后的效果,请跳转最终成果展示

全部代码发布在了lankoestee/MetroSearch上,但由于C++与Qt的环境配置过于困难了,故没有安装使用方法,不过所有的文件都在上面了。

系统功能介绍

本系统是基于Qt5.9实现的可视化地铁换乘系统,可以通过可视化控件的点按或滑动形式,实现全国各城市的地铁换乘线路指引,帮助异地者更加方便地换乘地铁线路。进入页面后,可以通过下拉选项框的方式选择城市、起点线路、起点站、终点线路、终点站,并通过查询按钮,点击查询地铁乘坐路线。初始页面如图1所示。

图1

在初始界面选择相应的信息并点击“查询”按钮后,如图2所示的图形化的换乘界面便会显示在右边的空位处。在图形化换乘界面中,通过纵向排列换乘方案的方式,清楚地标明了起点站、终点站、换乘站、乘坐线路、乘坐方向、乘坐站数和线路标识色等信息,方便使用者记忆路线。同时,点击“切换地图显示模式”按钮,如图3所示地图界面便会显示在右方、替代图形化换乘界面。地图界面以线路标识色绘制了换乘路径在地图上的显示,并以灰色线条绘制了乘坐路径周围的地铁线路。将鼠标放置在途径站点上,会在鼠标右侧显示站点名称,通过线路等相关信息,更为具体地展现了乘坐路径。

图2

图3

除此之外,系统还提供了许多额外功能。为更方便地了解线路走向,选择显示线路后点击“显示线路”按钮,便可在右方实现线路整体走向的显示,其显示风格与地图显示模式相同;通过下拉框选择历史记录后,点击“应用”按钮,便可将下拉框的历史记录成为当前的起点与终点,随后点击“查询”,可以快捷地回访历史路径;若想进行地铁图的更新,点击“更新”按钮,根据消息框的提示,在联网的情况下等待15秒左右,系统将自动更新至最新版本的线路图;在查询路线的过程中,可以通过拖动两个滑块,实现个性化换乘方案的选择,根据要求实现不同换乘方案。其具体操作流程如图4所示。

图4

功能实现思路

实现工具选择

在功能的实现过程中,选择了Qt5对上述功能进行实现。Qt是一个大型的跨平台C++应用程序开发框架,被广泛的用于应用程序GUI的开发。在选择方面,同样可以通过#include<windows.h>来进行应用程序的开发,但相较windows.h而言,Qt的开发流程更为简洁,控件的布局设置更为直观,同时支持更为复杂的操作和图形化编程。因此,在地铁换乘程序的开发上,最终选择使用Qt对其进行实现。

而在使用Qt的过程中,受限于电脑性能,图形化编程功能在自带的Qt Creator编辑器中响应较慢,同时由于Qt Creator不美观的操作界面和较复杂的类生成方式,在编辑器的选择方面,最终选择通过安装插件的方式在Visual Studio上进行功能的实现,同时放弃图形化的编程方式,通过自编代码的初始化和参数改变生成响应的控件和部件。

图形界面初始化

在确定使用Qt进行非图形化编程后,可以采用Qt自带的多种控件和其完备的功能函数对图形界面进行初始化,包括按钮QPushButton、下拉框QComboBox、标签QLabel等多种功能函数对界面进行初始化。在本程序中,窗口被设定为1600×9001600\times 900的大小,这与大多数显示器的显示比例相差无几,同时其分辨率不会超出如今大部分的电脑的屏幕分辨率。在这样的窗口大小下,直接通过每个实体控件均有的setGrometry函数对其进行位置编辑,其中一个重载函数接收左上角横坐标、左上角纵坐标、横向距离、纵向距离四个参数确定控件具体位置。

在图形的绘制方面,Qt提供了画家类QPainter以供绘制图形,通过对QPen,QBrush类创建、修改对象和应用,可以通过其绘制多种图形,以提供可视化解决方案。

线路数据获取与更新

为实现该系统在全国范围内的通用,在数据文档的生成方面,不能仅仅通过手动输入编写信息,这会带来巨额的工作量,通过代码的实现,可以直接从网络中爬取所需的相应数据。在爬取数据方面,C++提供了相应的库以完成爬取任务,然而相较于Python,其劣势也是明显的。Python语言在数据爬取方面拥有更好的封装,能够以更加简介的代码完成同样的任务,是目前主流的数据爬取工具。但其仍有缺点,即与计算机较慢的交互速度,这会增加其爬取时间。在本程序中,数据的获取是由Python语言文件进行实现的。

在爬取对象的确定方面,可以通过百度地图、高德地图等地图软件获取地铁线路站点数据,而由于百度地图网页代码并不开源,因此选择了高德地图作为数据的来源。在锁定高德地图地铁图功能网站后,通过解析其中json文件代码,可以从中得到需要的数据,选择了其中一部分进行储存,包括线路名称、线路标志色、站点名称、站点经纬度等。由于网站数据存在一定问题和不明晰,如连续出现两个站点、地铁主线和支线没有区分等不利于后续实现的问题存在,还需在Python文件中对错误数据进行满足程序需求的改良,最终形成各个城市的地铁数据文件,并以csv文件的形式为每个城市保存了一个文件。

更新方面,可以通过#include"Python.h",引入该头文件,实现在C++程序中执行Python程序。在本程序中,更新功能是通过执行Python程序,重新获取全部数据实现的。

类的设置与定义

若需实现根据站点搜寻路径的功能,建立类是一个很好的形式。在本程序中,建立了线路类Line和站点类Station两个类,其中Station类继承自Line类。对于线路类,需要明晰其线路名称、线路标识色、环线与否等情况,将这些要素设为了保护性数据成员,以便Staiton类使用。若要使用两者之间的继承关系,则需满足一个站点仅对应一条线路的情形,大部分站点均是满足这个条件的,而换乘站却是例外,故将换乘站是为多个不同线路上重名的几个站点。

Station类继承自Line类,但其仍然拥有其独特的数据成员,分别是站点编号、站点名称、站点经纬度、和连接站点。对于每一个数据成员,都采用设置函数的方式方便读取私有或保护性数据成员。其UML类图如图5所示。

图5

为实现换乘路径的搜寻,由于穷举法的不切实际性,必须采用构建网络(图)的形式以搜寻最佳路径。在网络的构建方面,选择采用邻接表的形式进行构建,即创立了新的数据结构Adjacent,其定义如下。

1
2
3
4
5
struct Adjacent {
int adjacentId;
double spacing;
Adjacent* next;
};

在Adjacent中,adjacentId用以表示邻接站点ID,spacing用以表示相邻站点距离(换乘站点距离为0),next则表示该站点的下一个邻接站点,若已表示了该站点的所有邻接站点,则将其设为空指针NULL。邻接表的赋值过程,即为地铁网络的构建过程。在为每一个站点赋值邻接表时、需要进行如下的操作:

  1. 连接同一线路相邻的两站,互相将站点ID写入对方邻接表中,通过两站的经纬度信息,计算相隔距离spacing,将其作为Adjacent指针写入站点邻接表的末尾;
  2. 遍历除本站外所有站点,寻找同名、同经度、同纬度的站点,将该站ID写入本站邻接表末尾中,其中相隔距离设置为0,以判断该站为换乘站。

通过上述的两步操作,便可在O(n2)O(n^2)的时间复杂度内填充每个站点的邻接表,构建城市地铁网络。在O(n2)O(n^2)时间复杂度下,该步骤运行时间极短。在打开程序时,程序默认以北京作为初始化城市,当城市选择复选框发生改变时,便会清楚原有数据,对新的选定城市进行初始化。

最短路径的搜寻

如今的图最短路径算法众多,最为常见的算法是广度优先搜索算法(Breadth First Search,简称BFS)其时间复杂度仅为O(V+E)O(|V|+|E|),能够较快的完成无权图的最短路径搜寻。而由于站点之间的距离存在,地铁网络可被视为一个有权图,BFS算法在有权图中的实现稍显复杂,故于此,选择使用迪杰斯特拉算法(Dijkstra’s Algorithm)是实现该无向带权图的单源最短路径搜寻。

迪杰斯特拉算法通过维护两个数组dist和pi进行单源最短路径的搜寻。其中dist值数组代表的是各个点到起点的最短路径长度,pi数组代表所有未求得最短路径的点到起点的局部最短路径长度。通过不断在pi数组中寻找最小值,赋入dist数组,并重新更新所有点的pi值,可以实现单源点到联通图中任一一点的距离。迪杰斯特拉算法的时间复杂度高于BFS,为O(V2+E)O(|V|^2+|E|),但在实际使用中,其复杂度不会产生任何延迟。

在站点的更新方面,不仅只将两站点之间距离distance作为唯一的判断标准,而是引入了换乘代价和途径站点代价两种不同的代价,将其以距离的形式表示,作为添加进入最短距离的计算中去。这两种代价可以通过滑动初始页面中的滑块进行自定义设置,为路径的搜寻提供了另外的可能。

在本程序中,由于要记录最短生成路径,创建并维护了一个updater数组,用以记录是何站点路径的确定引起了本站点pi值的改变,即是谁减小了本站点的pi值。除了起点站外,连通网中每个站点都拥有一个updater,用以表示从起点站到该站点的最短路径中,该站点的上一站是谁。可以使用标准模板库中的栈std::stack以储存站点数据。从终点站开始,依次对上一站点的updater进行进栈操作。直至进栈至起点站,便得到了一个以起点站为栈顶,终点站为栈底的方案栈scheme。这便产生了其最短路径,为下一步的处理奠定了基础。

可视化图形的绘制

可视化图形的绘制,主要是通过重写Qt中自带的paintEvent函数进行图形的绘制,paintEvent函数无需通过槽函数进行调用,而是可以在每次页面内容存在改变时自动调用,同时可以通过Qt自带的update()函数进行手动调用。

对于图2所示的路径绘图模式而言,其绘制的主要依靠是最短路径搜寻所得到的方案栈scheme,通过对其进行出栈操作。判断其是否为换乘站,并对途径站和换乘站绘制可视化图形进行表示,同时提取站点所在线路的标识色,在文字显示之余能够通过颜色更加直观的显示出换乘方案,方便记忆。由于图形绘制细节繁多,这部分内容是本程序实现代码中最长的部分。

对于图3的地图绘制模式而言,其依然通过对方案栈scheme的使用进行地图的绘制。首先确定最优路径站点的经纬度信息,确定绘制的比例尺和绘制区域,其次通过遍历所有站点,将所有站点绘制出来,随后仍是使用方案栈路径,将最优线路用站点所属线路颜色标志出来,最后通过删除指定画布外围区域,实现在指定区域内以合适比例尺大小展示方案路线。

历史记录提取与应用

与读取城市文件应用到复选框中相同,历史记录同样以csv文件的形式保存在文件夹中。每次点击查询按钮,便会生成一条新的历史记录,并将其放至文件夹的头部,为防止数据量过大,若此时历史记录的条数超过20条时,便会自动删除最后一条的历史记录。通过fstream读取历史记录并以逗号分割,通过复选框函数setCurrentText,便可将历史记录自动应用至设置起始站点的复选框内。

实现过程中所遇到的问题

使用Visual Studio实现Qt应用程序时,由于无法使用图形化编程,故各个控件只能通过多次调整位置的方式进行布放和实现,这样的布放较为严格地限制了窗口的大小,扩大和缩小窗口均有可能造成控件和图片位置的错乱。

由于本人电脑中所安装的Python是在x64平台上运行的,而非win32,这使得在建立解决方案的过程中,统一采用了x64平台进行编写与建立。又因为若将解决方案配置为debug模式,会导致release模式无法使用,故在程序编写和测试的过程中,统一采用了x64平台的release模式进行调试和发布。

最终结果展示

附上了程序运行示例视频,以便更换电脑无法正确运行时使用。

本程序的主要实现均在MetroSearch.cpp文件中,其余代码可以参考其余文件。

图6

图7