主要内容:本项目是北京地铁出行指南程序,在北京,上海等大型一线城市交通复杂,出行方式多样,以此带来了不知如何换乘的问题(以地铁出行为例)。本程序为解决此类问题特提供相应的出行方案供用户使用。程序使用图形化交互方案,增强用户友好性。
亮点:本程序可以直接以图示形式显示北京地铁的所有线路,并在图像上进行相应操作。比纯文本方式更加直观,人性化。与此同时,对用户选择的出行方案也会形成相应的路线图。突出人机友好,增强用户体验。
技术水平:技术上采用C++语言和H5语言相结合,充分利用各语言特性,更加程序更加灵活。压缩程序无关文件。精简代码和工程,用户使用流畅无延迟现象。
推广前景:目前本程序为综合出行指南系统的功能之一,可以继续发展成为更加完善,功能更加丰富,策略更加多样的市场项目。由于市场的需求庞大,无论男女老少都可以使用,所以又很大的市场空间和进一步研发推广前景。
Windows10操作系统性Qt5.9的集成开发环境。使用C++语言,Html语言。
北京的地铁交通网络错综复杂,建成的地铁线近二十条,站点上百个,现需建立一个换乘指南系统,通过输入起点和终点站,打印出地铁换乘路线指导,指南内容包括起点站、换乘站、终点站。
该北京市地铁换乘服务系统,能够在离线的情况下可以自由选择起点与终点站。由程序自动给出一个最优的乘坐路线。
北京地铁建设很快,由于程序不联网,希望可以实现动态添加站点到地铁图中。这样自定义的更新地图。
首先要直观的以图形化形式给出所有线路站点的全貌。由用户选择起点、终点后,经过后台计算给出最优路线,并展示在界面上。
需求计算给出两种方案路线,分别按照所需时间最短和换乘次数最少两种策略得出。
该软件所涉及的数据大多数为站点的坐标,以经纬度表示。经纬度本身需要极高的精度,细小的差别,在地图上可能带来极大的差距。
软件的初始数据要求精确到小数点后6位,才能显示到理想的位置。
在用户动态添加线路方面,也须达到该精度。注意在地理上,由于北京的位置,全部的站点必须位于纬度39°61’N-40°25’N,经度116°10’E-116°75’E范围内。须控制用户输入。
由于软件离线,故响应时间、数据的转换和传送时间都在0.1s内。
后台运算主体使用Dijkstra算法,复杂度为O(n2),需要处理的数据量较少,可以在0.1s内得出解。
功能主要功能包含地铁网络线路的查看,地铁换乘指南查询,动态添加线路站点等。
线路图查看:
●查看上海地铁网络线路图,包括通过键盘、鼠标拖放、放大缩小等简易查看操作;
●查看地铁线路信息,包括线路段、包含站点等;
●查看地铁站的详细信息,包括站点地理坐标、所属线路等
换乘指南查询:
●提供地铁换乘查询,可通过视图方便的查看乘坐路线和换乘路线;
●提供最小出行时间的换乘策略指南
●提供最小换乘次数的换乘策略指南
动态添加线路:
●动态添加线路,可根据需要新增线路;
●动态添加站点,可根据需要新增站点;
●动态添加连接,可根据需要新增站点连接;
●文本方式简易添加,方便快捷;
帮助:
●提供软件使用帮助和说明;
关于:
●提供软件制作信息;
界面主要分为,图示框,文本框,指令框,菜单框,行为框。如下图所示
菜单栏中分布式包涵本程序的所有功能。
文件 :包含“退出程序”按钮,快捷键(Ctrl+E)
工具:包含“换乘指南”(命令框中具体呈现),“动态添加”功能。其中动态添加分为“线路”(Ctrl+Shift+L),“站点” ( Ctrl+Shift+S),“链接” ( Ctrl+Shift+C)和“文本方式” ( Ctrl+Shift+T)四种功能,及同时包涵这四种功能的“所有”( Ctrl+A)动作。
视图:包含“放大”( Ctrl+L)按钮和“缩小”( Ctrl+S)按钮。除此之外,程序也支持鼠标滚轮进行快速放大缩小,效果上与按钮功能等同。
查看:可以勾选是否选择显示行为框和状态栏
帮助:包含“使用帮助”( Ctrl+H)提供网络图,换乘指南,添加线路,添加站点,添加链接和文本添加的使用帮助说明。如下图所示
关于:包涵“关于Qt”(Ctrl+B)按钮和“制作人”按钮。分别用来查看Qt简介和程序开发人简介。
行为栏中包涵本程序的功能的快捷方式:
分别为“放大”,“缩小”,“所有”,“线路”,“站点”,“链接”,“文本方式”,“换乘指南”和“地铁网络线路图”。
指令框中为用户提供出行指南
用户可勾选换乘时间最少和所需时间最短两种出行方案。再选出自己出行的起始地址和目的地,点击“换乘”按钮本程序就可以自动为其在图示框中显示其所选的出行方案的地铁路线图,与此同时,文本框中会显示其换乘站路线的文字说明。也会为其提供另一种出行方案,以供其可以对比参考。
主窗口下点击“线路”,“站点”,“链接”或“文本方式”按钮会转到
等子ui框
主程序下点击“使用帮助”按钮会显示的提示窗口是H5代码窗口,非静态设计窗口。
MVC模式介绍:
MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。
Model(模型)- 模型代表一个存取数据的对象。它也可以带有逻辑,在数据变化时更新控制器。
View(视图) - 视图代表模型包含的数据的可视化。
Controller(控制器)- 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。
在本次系统结构中,大致采用此种模式,前后端实现分离,前端主要考虑与用户的交互,例如设计怎样的视图,地铁网络线路图如何展现等,后端主要是数据和算法的处理,将复杂的功能和数据交由后端处理,前后端实现函数接口,从而完成整个软件系统的架构设计。
为了使软件的使用更加人性化,对于误操作,我们需要提供一种类似“后悔药”的机制,让软件系统可以回到误操作前的状态,因此需要保存用户每一次操作时系统的状态,一旦出现误操作,可以把存储的历史状态取出即可回到之前的状态。
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式。
在该程序中,程序启动以后自动保存当前的状态,在后来的动态添加线路站点信息错误后(1.文本添加 2.按步骤添加),可以选择撤回到程序刚刚启动的状态。
多线程是本项目的一个突出亮点。其主要应用于计算路线方案的两种计算上。采用的是Window提供的线程API技术。
首先我们分析多线程的必要
本项目旨在快速地,人性化地为使用者提供便捷的出行方案计划。当用户所选择自己的出行方案时如(换乘次数最少)时。并不知道自己所选的出行方案是否是最优的出行策略,未解决这种困扰我们提供多线程并行计算的模式来优化用户的体验感和更加智能的提醒使用者是否有更加优秀的出行方案,方案是什么。
多线程的实现
在用户选择某种出行策略后,住线程会调用相关的计算函数来为其进行基本的用户需求计算。算出的结果用于后面的图形线路像显示。与此同时,系统创建另一个子线程用于计算另一个用户未选择的出行策略,运算出的结果会同时提供给用户,如此一来就在路线上增强了实用性和高效性。
互斥
在并行计算过程中,传入给进程函数的参数中包涵所有计算所需的用户变量,并且实参中包涵的数据是源数据的备份,并保证线程进行过程中源数据不会发生改变。这样在主子线程分布计算的过程中都不会访问源数据的临界资源区,所有也没有临界段。两者间无需互斥等待。最大限度提高系统速率。
实现代码
if(way==1)
{
hcalculate2 = CreateThread(NULL , 0, (LPTHREAD_START_ROUTINE)&SubwayGraph::queryTransferMinTransfer ,&argu2 , 0, &THcalculateID2);
flag=subwayGraph->queryTransferMinTime(&argu1);
}
else
{
hcalculate1 = CreateThread(NULL , 0, (LPTHREAD_START_ROUTINE)&subwayGraph->queryTransferMinTime ,&argu1 , 0, &THcalculateID1);
flag=subwayGraph->queryTransferMinTransfer(&argu2);
}
CloseHandle(hcalculate2);
CloseHandle(hcalculate1);
下面是 Outline.txt 文件的一号线信息,其中包括了:
id: 1
name: 1号线
colour: #E70012
fromTo: 苹果园 四惠东
totalStations: 23
1 苹果园 116.1861707,39.913603
2 古城 116.197072,39.913414
3 八角游乐园 116.21939,39.913076
4 八宝山 116.242277,39.913185
5 玉泉路 116.25947,39.913501
6 五棵松 116.280423,39.913833
7 万寿路 116.30155,39.91371
8 公主坟 116.316696,39.913509
9 军事博物馆 116.330349,39.913214
10 木樨地 116.344097,39.913125
11 南礼士路 116.359221,39.912979
12 复兴门 116.364381,39.913014
13 西单 116.383402,39.913357
14 天安门西 116.39805,39.913776
15 天安门东 116.407851,39.91408
16 王府井 116.417796,39.914398
17 东单 116.426004,39.914476
18 建国门 116.44084,39.914526
19 永安里 116.457089,39.914223
20 国贸 116.46667,39.914138
21 大望路 116.482387,39.91413
22 四惠 116.501945,39.91486
23 西惠东 116.522347,39.914943
文档必须严格遵守格式:其中“:”后一定要有一个空格符,两条线路之间一定要有不少于一个的空行。在最后一条线路结束的时候,必须只能有一个空行。否则会判断读入错误,读入失败。
//定义边类型
typedef QPair Edge;
//线路类
class Line
{
int id; //线路ID
QString name; //线路名称
QColor color; //线路颜色
QVector fromTo; //线路起始站点
QSet stationsSet; //线路站点集合
QSet edges; //线路站点连接关系
}
//地铁站点类定义
class Station
{
int id; //站点ID
QString name; //站点名称
double longitude, latitude; //站点经纬度
QSet linesInfo; //站点所属线路
//所有站点的边界位置
static double minLongitude, minLatitude, maxLongitude, maxLatitude;
}
//图的邻接点结构
class Node
{
int stationID; //邻接点ID
double distance; //两点距离
}
//后端管理类
class SubwayGraph
{
QVector stations; //存储所有站点
QVector lines; //存储所有线路
QHash stationsHash; //站点名到存储位置的hash
QHash linesHash; //线路名到存储位置的hash
QSet edges; //所有边的集合
QVector> graph; //地铁线路网络图
}
SubwayGraph类中包含了其他类,存储有所有站点、所有线路的hash表,以及整个地铁线路网图。对其中重要函数展开介绍。
4.3.1 void SubwayGraph::addStation(Station s)
用于动态添加站点。
参数:Station类的一个实体s。
返回值:void。用于实现添加功能,不设置返回值。
内部实现:获取站点容器的大小。更新hash表,将该站点s压入容器。并更新与s连接的所有线路,与经纬度的边界。
4.3.2 void SubwayGraph::makeGraph()
参数:void。
返回值:void。不设置返回值。
内部实现:为得到完整的二维结点数组graph。先将其清空,设置正确的容器大小。逐个将结点置入。
4.3.3 bool SubwayGraph::readFileData(QString fileName)
参数:QString fileName 输入文件的名字字符串。
返回值:bool型变量。若文件输入数据时异常,则返回false。输入正常时返回true.
内部实现:通过文件名字符串从设备打开文件。文件存储遵循严格的格式。按顺序输入地铁路线的id、名称、图形化路线的颜色、起点、终点、总站数、所有站点的名称和坐标。并更新站点和线路的容器。在格式错误时返回false,正确录入则返回true。
4.3.4 bool SubwayGraph::queryTransferMinTime(parameter* argu )
参数:parameter* argu结构指针。由于多线程并行计算,计算函数需要传入结构指针调用。结构中包含整型变量s1、s2用于标记起点终点。所需的所有容器:站点、线路、所有边的集合、线路网络二维数组。
返回值:bool型变量。若输入的起点终点无法到达(未链接),则返回false。正常解出路线返回true.
内部实现:每个站点可看做一个节点,站点的联系看做一条边,网络结构用图结构存储。
该核心计算程序得到最短时间路线,使用dijkstra算法,函数流程如下:
4.3.5 bool SubwayGraph::queryTransferMinTransfer(parameter * argu)
参数:parameter* argu结构指针。由于多线程并行计算,计算函数需要传入结构指针调用。结构中包含整型变量s1、s2用于标记起点终点。所需的所有容器:站点、线路、所有边的集合、线路网络二维数组。
返回值:bool型变量。若输入的起点终点无法到达(未链接),则返回false。正常解出路线返回true.
内部实现:同为核心计算函数,调用该函数计算路线中经过最少换乘。函数具体流程如下:
1.视图放大
地图可以自由放大缩小。
点击工具栏中放大按钮;
或选择“视图”下“放大”;
或按下快捷键“Ctrl+L”;
或直接在视图区域进行鼠标滚轮放大操作
可进行视图的放大,方便查看。
初始大小:
一定比例放大后:
2.视图缩小:
点击工具栏中缩小按钮;
或选择“视图”下“缩小”;或按下快捷键“Ctrl+S”;
或直接在视图区域进行鼠标滚轮缩小操作
可进行视图的缩小,方便查看。
效果如下:
初始大小;
一定比例缩小后:
3.鼠标操作
鼠标除能直接放大和缩小视图外,在查看视图信息有重要作用:
鼠标滚轮直接放大缩小视图,如前述所示;
通过鼠标点击后拖动,可直接进行视图移动;
相同比例下,通过鼠标点击拖动,来进行视图移动:
将鼠标悬停在站点上,会提示站点的详细信息,如悬停在“东直门”站上,会有东直门地铁站的详细信息:
将鼠标悬停在线路上,会提示线路的详细信息,如图,显示该段线路连接的站点名称及所属地铁线:
支持两种查询方式。
如图,我们以4号线中关村到2号线建国门为例:
在红框中位置,分别将起点站设置为4号线中关村,终点站设置为2号线建国门。
可以看到,在当前视图下,这两站分别位于左上角和右下角:
在选择换乘策略为所需时间最短后,单击换乘,可得到如下结果:
如图可以看到,以图形和文字两种方式,给出了所需时间最短的换乘方案。
软件中央是路线的图形化路线,右下角的文字框内,给出了文字版的换乘路线。
继续以上面的起点终点为例,选择换乘策略为换乘次数最少,单击换乘:
如图所示,在这种策略下,图形化路线仅给出起点站、终点站及换乘站。
文本方式添加及撤回功能
选择文本添加方式:
按要求输入要添加的线路内容:
点击“添加”,显示添加成功:
14号线(西)线路添加成功:
点击撤回可回到添加前的状态:
手动按步骤添加
首先添加线路信息:
添加站点信息:
张郭庄
以此类推,添加14号线(西)所有站点信息。
如图,14号线(西)站点全部添加完毕:
接着,将这些站点进行连接:
以此类推,将14号线(西)其他站点也连接起来:
以此类推,直到所有站点都被连接起来。此时手动添加线路添加完毕。
实践方面:在本次程序设计当中,我主要负责后端数据结构的实现,使用了在数据结构课上学习的数据结构知识,例如图和顺序表,复习并运用了以前学习的知识。还用到了QSet,QVector,QHash等C++中的容器,方便了操作。在暑假的时候,我购买了《大话设计模式》、《设计模式——可复用面向对象软件的基础》以及《重构——改善既有代码的设计》,学习并成功在该程序中运用了MVC模式以及Memento模式,增强了程序再次改良的可能性。在这次程序设计实践中,我们成功实现了数据结构和UI的解耦,降低耦合度以方便修改调试代码。在本次程序设计实践中,我还承担了分配队友任务,协调大家互相合作这一角色,例如代码分配,文档编写工作分配等。此次编写实验报告是我第二次使用markdown来进行编写,强化了markdown编写文档的能力。
问题解决:在程序设计当中,无论是在刚开始的构思阶段还是写代码调试代码阶段,又或者是最后的拼代码阶段,我们都遇到了各种各样的问题。我们在敲定编写地铁系统的时候,不知道怎么确定各个地铁站的位置,后来选用经纬度来确定,加入地球半径来立体建模解出A,B两个站点的实际距离。在调试代码阶段,读文件时文件没有按照正确的格式编写,导致图一直画不出来,所以加入了调试信息qDebug()输出每一步读到的信息来确定究竟是哪一步出现了问题。如果遇到了程序崩溃问题,则点击开始调试,会定点程序崩溃的位置。
收获:在本次程序设计当中,我学习到了构建大型程序的基本思路:先构思好全局的框架,这一步是最关键的;然后分工完成自己的部分,统一接口最后完成整个程序。当然会遇到许多难以想象的困难,这时候通过上网寻找解决问题的方法以及自己打断点找bug的方式就会解决问题。此次程序设计我还熟练掌握了markdown进行一些简单的文档编写:添加代码、添加流程图等操作,为以后我的学业生涯奠定了一定的基础。
技术方面:经过本次实验,我增强了对C++的理解,多数使用其内置的魔法容器来减少代码指针等操作错误,更加熟悉了Qt的使用和其UI界面的设计,在我设计的主窗口界面中一改以前的单一按钮的设计习惯。增加各种菜单栏来使界面更加干净,清晰。有了更加良好的代码编写习惯。方便队友阅读。
问题解决方面:当遇到未知bug时一定要沉住气,可以通过qDebug输出或端点调试等方法逐一筛选问题代码块,减少问题代码区域,然后再逐行逐句地分析代码,模拟其逻辑运行流程,借此来找出问题位置。同时脑子要清晰,自己在写代码前一定要规定好数据的格式,然后再操作不要再改否则可能会产生不必要的错误。比如在读取文件过程中最后一行数据后要加一个换行符不能多,这是读取格式,但是当我实际编写时却多加了空行导致文件读取错误,浪费了很多时间。
自我心态方面:一切困难都是纸老虎。在遇到难以解决的问题时,尤其是长时间未解决的情况下,最好能停顿一下,喝杯茶,打会儿台球,等等方式让自己从当前的问题中挣脱出来。不要怕浪费时间(据我亲身体会,事实证明这段时间太值了)。最重要的是要重新整理自己的思维,尝试换个角度,换个角度,再换个角度…我们都知道换个角度,但在被困难蒙住双眼的我们有时候真的很难跳出这个越陷越深的怪圈,直至自己最后被BUG干掉!
通过这次团队作业,对c++模式化编程有了全新的认识。并应用了多线程并行计算等平时可能会避免的略显困难的部分。我们同时通过这次机会应用了曾经学习的数据结构中的知识,对关键的图论部分的知识进行了温习。在组队题目发下来后,我们协商决定通过北京地铁这一媒介来作为项目的主体,在此之上进行雕琢。也感受到了紧张同时愉快的工作氛围。为以后的工作打下了基础。
编程的过程中经历了许许多多的困难,从数据的采集、数据文件格式的设计、函数实现设计,但最终克服了种种困难,每次解题的过程也都是一次成长。Qt的使用也有许多令我曾经感到棘手的部分。细枝末节的错误可能带来的是重大的错误。编程的过程有苦涩,但最终呈现出的效果是我们所期望的,成功的喜悦是其他活动所不能带来的。经过这次大作业,收获的经验令我受益匪浅。
徐维泽:总体架构的设计、后端的设计与实现、MVC模式的设计与实现、文档结构设计
王智:主窗口UI界面设计与实现、Mainwindow类的编写、多线程并行处理的设计与实现
聂佩轩:路线查询两大核心算法的设计与实现、Memento模式的实现、UI PS美工、图标素材整理
【1】霍亚飞.Qt Creator快速入门第二版[M].北京:北京航空航天大学出版社,2014-1
【2】设计模式 可复用面向对象软件的基础,Erich Gamma等著,机械工业出版社,2016年11月版。
【3】重构 改善既有代码的设计,[美]Martin Fowler著,人民邮电出版社,2015版
【4】Dijkstra E W. A Note on Two Problems in Connection with Graphs [j]. Numerical Mathematics, 1959(1)
【5】UI设计必修课 游戏+软件+网站+APP界面设计教程, 高金山 著;孙悦红 编,电子工业出版社, 2017版;
【6】北京地铁站经纬度数据(豆丁网)