基于图结构实现地铁乘坐线路查询

基于图结构实现地铁乘坐线路查询


问题描述

编写一个程序实现地铁最短乘坐(站)线路查询,输入为起始站名和目的站名,输出为从起始站到目的站的最短乘坐站换乘线路。

1.采用Dijkstra算法实现,使用优先队列对性能进行了优化;
2.如果两站间存在多条最短路径,则输出其中的一条即可;

  • 本次项目实现采用了flask作为后端提供接口服务,使用androidApp进行get请求获得数据,显示在Textview中

设计需求

  • 确定储存地铁站文件的格式文件 (已确认使用json格式和文本格式)
  • 确定读取地铁站数据的方式 (使用python的file打开命令)
  • 确定获取两站点最小站点数的算法方式
  • 进行外表封装
  • 进行输出格式的确定
  • 性能测试
  • 最后结果检查

数据存储格式

stationline.txt文本为存储的地图数据文本,格式如下图所示:

  • 使用0与1来分别表示是否需要换乘
地铁线路条数
线路x 线路x站数
站名1 是否需要换乘
站名2 是否需要换乘
...

  • 数据示例
2 20
曹庄 0 
卞兴 0
芥园西道 0
咸阳路 0
长虹公园 1
广开四马路 0
西南角 1
鼓楼 0
东南角 0
建国道 0
天津站 1
远洋国际中心 0
顺驰桥 0
靖江路 1
翠阜新村 0
屿东城 0
登州路 0
国山路 0

数据文本读取代码

with open(os.path.join(os.path.abspath('..'), "station/stationLine.txt"), "r") as f:
    TOTAL = f.readline()
    for line in f.readlines():
        if line != '\n':
            line = line.rstrip('\n')
            line = line.split(' ')
            if line[0] in LINEDATA:
                linei = line[0]
                continue
            line[1] = linei
            line0 = line[0]

            intline = int(line[1])
            if intline not in data.keys():
                data[intline] = []
                data[intline].append(line0)
            else:
                data[intline].append(line0)
            if line0 not in datanum.keys():
                datanum[line0] = [intline]
            else:
                datanum[line0].append(intline)
  • 打印结果
stationline {"1": ["刘园", "西横堤", "果酒厂", "本溪路", "勤俭道", "洪湖里", "西站", "西北角", "西南角", "二纬路", "海光寺", "鞍山道", "营口道", "小白楼", "下瓦房", "南楼", "土城", "陈塘庄", "复兴门", "华山里", "财经大学", "双林", "李楼"], "2": ["曹庄", "卞兴", "芥园西道", "咸阳路", "长虹公园", "广开四马路", "西南角", "鼓楼", "东南角", "建国道", "天津站", "远洋国际中心", "顺驰桥", "靖江路", "翠阜新村", "屿东城", "登州路", "国山路", "空港经济区", "滨海国际机场"], "3": ["南站", "杨伍庄", "学府工业区", "高新区", "大学城", "华苑", "王顶堤", "红旗南路", "周邓纪念馆", "天塔", "吴家窑", "西康路", "营口道", "和平路", "津湾广场", "天津站", "金狮桥", "中山路", "北站", "铁东路", "张兴庄", "宜兴埠", "天士力", "华北集团", "丰产河", "小淀"], "5": ["北辰科技园北", "丹河北道", "北辰道", "职业大学", "淮河道", "辽河北道", "宜兴埠北", "张兴庄", "志成路", "思源道", "建昌道", "金钟河大街", "月牙河", "幸福公园", "靖江路", "成林道", "津塘路", "直沽", "下瓦房", "西南楼", "文化中心", "天津宾馆", "肿瘤医院", "体育中心", "凌宾路", "昌凌路", "中医一附院", "李七庄南"], "6": ["南孙庄", "南何庄", "大毕庄", "金钟街", "徐庄子", "金钟河大街", "民权门", "北宁公园", "北站", "新开河", "外院附中", "天泰路", "北运河", "北竹林", "西站", "复兴路", "人民医院", "长虹公园", "宜宾道", "鞍山西道", "天拖", "一中心医院", "红旗南路", "迎风道", "南翠屏", "水上公园东路", "肿瘤医院", "天津宾馆", "文化中心", "乐园道", "尖山路", "黑牛城道", "梅江道", "左江道", "梅江公园", "梅江会展中心", "解放南路", "洞庭路", "梅林路"], "9": ["天津站", "大王庄", "十一经路", "直沽", "东兴路", "中山门", "一号桥", "二号桥", "张贵庄", "新立", "东丽开发区", "小东庄", "军粮城", "钢管公司", "胡家园", "塘沽", "泰达", "市民广场", "太湖路", "会展中心", "东海路"]}
linesdata {"刘园": [1], "西横堤": [1], "果酒厂": [1], "本溪路": [1], "勤俭道": [1], "洪湖里": [1], "西站": [1, 6], "西北角": [1], "西南角": [1, 2], "二纬路": [1], "海光寺": [1], "鞍山道": [1], "营口道": [1, 3], "小白楼": [1], "下瓦房": [1, 5], "南楼": [1], "土城": [1], "陈塘庄": [1], "复兴门": [1], "华山里": [1], "财经大学": [1], "双林": [1], "李楼": [1], "曹庄": [2], "卞兴": [2], "芥园西道": [2], "咸阳路": [2], "长虹公园": [2, 6], "广开四马路": [2], "鼓楼": [2], "东南角": [2], "建国道": [2], "天津站": [2, 3, 9], "远洋国际中心": [2], "顺驰桥": [2], "靖江路": [2, 5], "翠阜新村": [2], "屿东城": [2], "登州路": [2], "国山路": [2], "空港经济区": [2], "滨海国际机场": [2], "南站": [3], "杨伍庄": [3], "学府工业区": [3], "高新区": [3], "大学城": [3], "华苑": [3], "王顶堤": [3], "红旗南路": [3, 6], "周邓纪念馆": [3], "天塔": [3], "吴家窑": [3], "西康路": [3], "和平路": [3], "津湾广场": [3], "金狮桥": [3], "中山路": [3], "北站": [3, 6], "铁东路": [3], "张兴庄": [3, 5], "宜兴埠": [3], "天士力": [3], "华北集团": [3], "丰产河": [3], "小淀": [3], "北辰科技园北": [5], "丹河北道": [5], "北辰道": [5], "职业大学": [5], "淮河道": [5], "辽河北道": [5], "宜兴埠北": [5], "志成路": [5], "思源道": [5], "建昌道": [5], "金钟河大街": [5, 6], "月牙河": [5], "幸福公园": [5], "成林道": [5], "津塘路": [5], "直沽": [5, 9], "西南楼": [5], "文化中心": [5, 6], "天津宾馆": [5, 6], "肿瘤医院": [5, 6], "体育中心": [5], "凌宾路": [5], "昌凌路": [5], "中医一附院": [5], "李七庄南": [5], "南孙庄": [6], "南何庄": [6], "大毕庄": [6], "金钟街": [6], "徐庄子": [6], "民权门": [6], "北宁公园": [6], "新开河": [6], "外院附中": [6], "天泰路": [6], "北运河": [6], "北竹林": [6], "复兴路": [6], "人民医院": [6], "宜宾道": [6], "鞍山西道": [6], "天拖": [6], "一中心医院": [6], "迎风道": [6], "南翠屏": [6], "水上公园东路": [6], "乐园道": [6], "尖山路": [6], "黑牛城道": [6], "梅江道": [6], "左江道": [6], "梅江公园": [6], "梅江会展中心": [6], "解放南路": [6], "洞庭路": [6], "梅林路": [6], "大王庄": [9], "十一经路": [9], "东兴路": [9], "中山门": [9], "一号桥": [9], "二号桥": [9], "张贵庄": [9], "新立": [9], "东丽开发区": [9], "小东庄": [9], "军粮城": [9], "钢管公司": [9], "胡家园": [9], "塘沽": [9], "泰达": [9], "市民广场": [9], "太湖路": [9], "会展中心": [9], "东海路": [9]}
station_num {"刘园": 0, "西横堤": 1, "果酒厂": 2, "本溪路": 3, "勤俭道": 4, "洪湖里": 5, "西站": 6, "西北角": 7, "西南角": 8, "二纬路": 9, "海光寺": 10, "鞍山道": 11, "营口道": 12, "小白楼": 13, "下瓦房": 14, "南楼": 15, "土城": 16, "陈塘庄": 17, "复兴门": 18, "华山里": 19, "财经大学": 20, "双林": 21, "李楼": 22, "曹庄": 23, "卞兴": 24, "芥园西道": 25, "咸阳路": 26, "长虹公园": 27, "广开四马路": 28, "鼓楼": 29, "东南角": 30, "建国道": 31, "天津站": 32, "远洋国际中心": 33, "顺驰桥": 34, "靖江路": 35, "翠阜新村": 36, "屿东城": 37, "登州路": 38, "国山路": 39, "空港经济区": 40, "滨海国际机场": 41, "南站": 42, "杨伍庄": 43, "学府工业区": 44, "高新区": 45, "大学城": 46, "华苑": 47, "王顶堤": 48, "红旗南路": 49, "周邓纪念馆": 50, "天塔": 51, "吴家窑": 52, "西康路": 53, "和平路": 54, "津湾广场": 55, "金狮桥": 56, "中山路": 57, "北站": 58, "铁东路": 59, "张兴庄": 60, "宜兴埠": 61, "天士力": 62, "华北集团": 63, "丰产河": 64, "小淀": 65, "北辰科技园北": 66, "丹河北道": 67, "北辰道": 68, "职业大学": 69, "淮河道": 70, "辽河北道": 71, "宜兴埠北": 72, "志成路": 73, "思源道": 74, "建昌道": 75, "金钟河大街": 76, "月牙河": 77, "幸福公园": 78, "成林道": 79, "津塘路": 80, "直沽": 81, "西南楼": 82, "文化中心": 83, "天津宾馆": 84, "肿瘤医院": 85, "体育中心": 86, "凌宾路": 87, "昌凌路": 88, "中医一附院": 89, "李七庄南": 90, "南孙庄": 91, "南何庄": 92, "大毕庄": 93, "金钟街": 94, "徐庄子": 95, "民权门": 96, "北宁公园": 97, "新开河": 98, "外院附中": 99, "天泰路": 100, "北运河": 101, "北竹林": 102, "复兴路": 103, "人民医院": 104, "宜宾道": 105, "鞍山西道": 106, "天拖": 107, "一中心医院": 108, "迎风道": 109, "南翠屏": 110, "水上公园东路": 111, "乐园道": 112, "尖山路": 113, "黑牛城道": 114, "梅江道": 115, "左江道": 116, "梅江公园": 117, "梅江会展中心": 118, "解放南路": 119, "洞庭路": 120, "梅林路": 121, "大王庄": 122, "十一经路": 123, "东兴路": 124, "中山门": 125, "一号桥": 126, "二号桥": 127, "张贵庄": 128, "新立": 129, "东丽开发区": 130, "小东庄": 131, "军粮城": 132, "钢管公司": 133, "胡家园": 134, "塘沽": 135, "泰达": 136, "市民广场": 137, "太湖路": 138, "会展中心": 139, "东海路": 140}
  • 获得点与点之间的最短路径:
def find_shortest_path(graph, start, end, path=[]):
    # 查找最短路径
    path = path + [start]
    if start == end:
        return path
    if not start in graph.keys():
        return None
    shortest = None
    for node in graph[start]:
        if node not in path:
            newpath = find_shortest_path(graph, node, end, path)
            if newpath:
                if not shortest or len(newpath) < len(shortest):
                    shortest = newpath
    return shortest

def find_all_paths(graph, start, end, path):
    # 查找所有的路径
    path = path + [start]
    if start == end:
        return [path]
    if not start in graph.keys():
        return []
    paths = []
    for node in graph[start]:
        if node not in path:
            newpaths = find_all_paths(graph, node, end, path)
            for newpath in newpaths:
                paths.append(newpath)
    return paths
    
pathss = {}
for i in range(I):
    for j in range(I):
        if RouteGraph.get_edge(i, j) == 1:
            start = STATIO[i]
            end = STATIO[j]
            if i not in pathss.keys():
                pathss[i] = [j]
            else:
                pathss[i].append(j)
# pathss是记录每个站点接触的站点list
# print(pathss)

  • dijkstra算法具体分析
def dijkstra_shortest_pathS(graph, v0, endpos):
    vnum = 0
    for i in pathss.keys():
        vnum += 1
        
    assert 0 <= v0 < vnum
    # 长为vnum的表记录路径
    paths = [None] * vnum
    count = 0
    
    cands = PrioQueue([(0, v0, v0)])
    while count < vnum and not cands.is_empty():
        plen, u, vmin = cands.dequeue()  
        if paths[vmin]:  
            continue
        paths[vmin] = (u, plen)  
        # print(u, plen)
        for v in graph[vmin]:  
            if not paths[v]:  
                cands.enqueue((plen + 1, vmin, v))
        count += 1
    return paths
  • stationController 部分
# encoding=utf-8
import os
import json

from stationplan import computefshortestpath
from stationplan import getInfo


def getShort(start, end):
    stationnum, s = computefshortestpath(start, end)
    return stationnum, s


def getInfoStation():
    stationnumlist, stationlist = getInfo()
    return stationnumlist, stationlist


if __name__ == "__main__":
    a, b = getInfoStation()
    print(type(a))
    print(type(b))
  • stationController中具体使用的函数分析
# 确定出发点和最后的站点
def computefshortestpath(startpos, endpos):
    s1 = STATION_NUM[startpos]
    e1 = STATION_NUM[endpos]
    # print(s1,e1)
    paths = dijkstra_shortest_pathS(pathss, s1, e1)
    # print(paths)
    b = []
    p = paths[e1][0]
    # print(paths[e1])
    b.append(STATIO[p])
    while True:
        p1 = paths[p][0]
        p = p1
        b.append(STATIO[p])
        if p == s1:
            break
    b.reverse()
    if s1 != e1:
        b.append(STATIO[e1])
    stationnumo = len(b)

    s = ""
    s += b[0]
    for i in range(1, len(b) - 1):
        a1 = set(datanum[b[i - 1]])
        b1 = set(datanum[b[i + 1]])
        c1 = set(datanum[b[i]])
        # 如果没有交集,说明不是同一条路,对当前站点前后站点进行分析,如果两个站点属于
        # 的站点线号没有发生重叠,说明当前线路在该站点没有进行换乘
        if not len(a1 & b1):
            if len(datanum[b[i + 1]]) != 0:
                s += "-" + str((list(set(datanum[b[i]]) & b1)[0])) + "号线"
            s += "-" + b[i]
        else:
            s += "-" + b[i]
    s += "-" + b[len(b) - 1]
    return stationnumo, s

def getInfo():
    return data, STATION_NUM

flask app的分析:

  • flask具体作用类似与springboot,是python后端的一个框架,对新手及其友好,而json包则是用来处理json输出格式的一个工具
  • 具体详情查看flask官方中文文档
# encoding=utf-8
from flask import Flask, request
from stationController import getShort
from stationController import getInfoStation
import json

app = Flask(__name__)

@app.route('/getStationInfo', methods=['GET'])
def getStationInfo():
    num = request.args["num"]
    num = int(num)
    data, stationlist = getInfoStation()
    if not num:
        result = {
            "code": 500,
            "msg": "the service make a mistake -.-"
        }
    else:
        strmsg = data[num]
        print(strmsg)
        result = {
            "code": 0,
            "msg": strmsg
        }
    return json.dumps(result)


@app.route('/getShortestPath', methods=['GET'])
def getShortestPath():
    start = request.args['start']
    end = request.args['end']
    data, stationlist = getInfoStation()
    print(start not in stationlist.keys() and end not in stationlist.keys)
    if (not start or not end) or (start not in stationlist.keys() or end not in stationlist.keys()):
        result = {
            "code": 501,
            "msg": "please input the correct start and end station -.-"
        }
    else:
        stationnum, strmsg = getShort(start, end)
        result = {
            "code": 0,
            "msg": strmsg,
            "stationnum": stationnum
        }

    return json.dumps(result)


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=80)

  • flask具体demo已经部署在服务器上,返回信息,请求方式等具体请查看接口文档:传送门

安卓部分

  • 编译器使用AS进行开发
  • 使用友好的流线式布局进行开发
  • 布局代码如下:
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="10dp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:paddingTop="30dp"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请选择站点线路起点线" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="141dp"
        android:layout_gravity="center"
        android:orientation="vertical">

        <Spinner
            android:id="@+id/spinner1"
            android:layout_width="match_parent"
            android:layout_height="50dp" />

        <Spinner
            android:id="@+id/spinner2"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_marginTop="20dp" />
    </LinearLayout>

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请选择站点线路终点线" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="141dp"
        android:layout_gravity="center"
        android:orientation="vertical">

        <Spinner
            android:id="@+id/spinner3"
            android:layout_width="match_parent"
            android:layout_height="50dp" />

        <Spinner
            android:id="@+id/spinner4"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_marginTop="20dp"

            />
    </LinearLayout>
    <Button
        android:id="@+id/searchButton"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:text="搜索最短路线">
    </Button>

    <TextView
        android:id="@+id/showShortestPath"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="">
    </TextView>
</LinearLayout>
  • 对与spinner(下拉框的集联操作使用xml进行了储存)
  • 当选中相应的station值时进行选择第二个spinner中的应该显示的值
  • 格式如下
<?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string-array name="station">
            <item>-站点线路-</item>
            <item>1</item>
            <item>2</item>
            <item>3</item>
            <item>5</item>
            <item>6</item>
            <item>9</item>
         </string-array>
         <string-array name="station1">
             <item>-站点-</item>
             <item>刘园</item>
             <item>西横堤</item>
             <item>果酒厂</item>
             <item>本溪路</item>
             <item>勤俭道</item>
             <item>洪湖里</item>
             <item>西站</item>
             <item>西北角</item>
             <item>西南角</item>
             <item>二纬路</item>
             <item>海光寺</item>
             <item>鞍山道</item>
             <item>营口道</item>
             <item>小白楼</item>
             <item>下瓦房</item>
             <item>南楼</item>
             <item>土城</item>
             <item>陈塘庄</item>
             <item>复兴门</item>
             <item>华山里</item>
             <item>财经大学</item>
             <item>双林</item>
             <item>李楼</item>
         </string-array>
   			......
     </resources>
  • 代码控制:
	....
	if (pro.equals("1")) {
	     cityAdapter = ArrayAdapter.createFromResource(
	             MainActivity.this, R.array.station1,
	             android.R.layout.simple_spinner_dropdown_item);
	     sr4.setAdapter(cityAdapter);
	     sr4.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
	         @Override
	         public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
	
	             String strstation = MainActivity.this.getResources().getStringArray(R.array.station1)[position];
	             sr4Val = strstation;
	         }
	
	         @Override
	         public void onNothingSelected(AdapterView<?> parent) {
	
	         }
	
	     });
	} 
	.....

  • demo图
    在这里插入图片描述
  • 使用okhttps获得json数据,get方式
  • 相应的as添加jar包方式:
  • 打开路径:file->project Structure->Depndences->app->+号 搜索相应的包即可
  • 博主用的是 okhttp:2.7.5的包

在这里插入图片描述

public void SendGetRequest(final String url,final String startpos,final  String endpos){

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象
                    Request request = new Request.Builder()
                            .url("http://139.9.90.185/getShortestPath?start="+startpos+"&end="+endpos)//请求接口。如果需要传参拼接到接口后面。
                            .build();//创建Request 对象
                    Response response = null;
                    response = client.newCall(request).execute();//得到Response 对象
                    if (response.isSuccessful()) {
                        Log.d("kwwl","response.code()=="+response.code());
                        Log.d("kwwl","response.message()=="+response.message());
//                        Log.d("kwwl","res=="+response.body().string());
                        String resdata = response.body().string();
                        System.out.println(resdata);

                        //此时的代码执行在子线程,修改UI的操作请使用handler跳转到UI线程。
                            JSONObject jsonObject = new JSONObject(resdata);
                            String code = (jsonObject.getString("code"));
                            if(Integer.parseInt(code)==0) {
                                String resultpath = (jsonObject.getString("msg"));
                                String resultnum = (jsonObject.getString("stationnum"));
                                show("站点数:" + resultnum + "\n" + "站点最短路径:" + resultpath);
                                Toast.makeText(MainActivity.this,"正在搜索中,请稍后",Toast.LENGTH_SHORT).show();
                            }else{
                                String msg = (jsonObject.getString("msg"));
                                show("提示信息:"+msg);
                                Toast.makeText(MainActivity.this,"提示信息:"+msg,Toast.LENGTH_SHORT).show();
                            }
//                            System.out.println(result);
                    }else{
                        show("请求出错,请选择正确的站点请求");
                        Toast.makeText(MainActivity.this,"请求出错,请选择正确的站点请求",Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();


    }
 //显示在下方的TextView中
    private void show(final String result) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                textShowPaths.setText(result);
            }
        });
    }
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值