北京地铁线路规划系统——总结

项目概况

Github项目源代码地址:https://github.com/NewWesternCEO/beijing_subway/
web端直接访问地址:http://54.162.90.89:8010

该项目在Python3.6环境下开发
若不通过web端进行访问,在下载源码后运行 app.py 代码即可
代码的运行需要先安装好Flask库与numpy库,可以在以下方式中二选一进行安装

  • Numpy库可以通过pip install Numpy进行安装
    Flask库可以通过pip install Flask进行安装

  • 在虚拟环境下建议使用pip install -r requirements.txt命令从requirements.txt文件中自动安装所有所需库

数据流分析

由于该工程所需要处理的数据较少,且数据处理过程为实时处理,因此不采用数据库进行存储。

分享图片


文件结构与存储结构

文件结构

└── SubwayApp
│ └── pycache
│ └── static //项目资源
│ │ └── css
│ │ └── images
│ │ └── js
│ │—— subway.txt //存储的站点信息
│ │—— routine.txt //存储的规划结果
│ │—— names.txt //站点编码对应的名称
│ │—— app.py //Flask 程序
│ │—— manager.py
│ │—— requirements.txt //程序运行所需要的环境
│ │—— subwayFunction.py //必要的业务逻辑模块(dijsktra算法等)

存储结构

在对线路的观察中发现北京市地铁线路的命名并不完全按数字来进行,存在特有的命名,导致不适宜直接在程序中进行处理。

分享图片

因此首先对各条线路进行编码,将编码后的线路信息存放在names.txt文件中。编码后的结果为

1 地铁一号线
2 地铁二号线
3 地铁八通线
...
17 地铁昌平线
18 地铁亦庄线
19 地铁燕房线
20 西郊线
21 S1线
22 机场线


对于各个站点,则通过

苹果园 1
古城 1
八角游乐园 1
...
2号航站楼 22
三元桥 22


的形式存储在subway.txt文件中,并不需要通过额外的方式对换乘站点进行标注。

业务逻辑实现(subwayFunction.py)

全局声明

import numpy as np

ERROR = -1
inf = 1e+8

数据预处理


数据预处理主要通过 readData()readNames()initGraph()几个功能模块来实现。
其具体功能为:

  • readData() 读取目录下的subway.txt文件,返回站点个数、线路数组、以字典的形式存储了不同线路所在的站点(若一个站点为换乘站,即属于多个线路,则在字典中它的键值对数量大于1),同时初始化了collect数组
  • readNames() 读取目录下的names.txt文件,返回一个存储了编码后的线路与线路名称键值对的字典
  • initGraph() 根据subway.txt文件构建一个无向图,相邻站点之间的距离均置为1

具体实现:
readData()

'''读取地铁站点信息'''
def readData(path):
    file = open(path,'r',encoding='GB2312')

    N = 0
    lineNum = []
    lineDict = {}
    nodes = []
    collected = []
    for each in file:
        # print(each)
        node,line = each.split(' ')
        if not node in nodes:
            N += 1
            nodes.append(node)
            collected.append(False)
    
        # 将线路分门别类
        line = eval(line)
        if not line in lineNum:
            lineDict[line] = []
            lineNum.append(line)
        lineDict[line].append(node)
        
    print('\n共有 %d 个站点' % N)
    file.close()

    return N,nodes,collected,lineNum,lineDict

readNames()

def readNames(path):
    file = open(path,encoding='GB2312')
    names = {}
    for each in file:
        line,name = each.split(' ')
        name = name.replace('\n','')
        names[eval(line)] = name

    return names

initGraph()

'''初始化'''
def initGraph(path,N,nodes):
    graph = np.ones([N,N]) * inf #不可达
    preIdx = 0 #Idx表示结点编号
    preLine = ERROR
    file = open(path,encoding='GB2312')
    for each in file:
        node,line = each.split(' ')
        
        if preLine == ERROR:
            preLine = eval(line)
                
        curIdx = nodes.index(node)
        if curIdx != preIdx and preLine == eval(line):
            graph[preIdx][curIdx] = graph[curIdx][preIdx] = 1
        
        preIdx = curIdx        
        preLine = eval(line)
            
    return graph

dijkstra算法

典型的dijkstra算法实现,包含findNextMin()Dijkstra()两个模块。
其思想及具体实现可以参考此处:最短路径:Dijkstra算法

具体实现:
dijkstra

'''Dijkstra算法'''
def findNextMin(graph,dist,N):
    minNode,minDist = ERROR,inf
    
    for i in range(0,N):
        if dist[i] < minDist and collected[i] == False:
            minDist = dist[i]
            minNode = i
    
    if minDist < inf:
        return minNode
    else:
        return ERROR
    

def Dijkstra(nodes,startNode,graph,lineDict):
    startIdx = nodes.index(startNode)
    #endIdx = nodes.index(endNode)    
    collected[startIdx] = True
    dist = np.ones(N) * inf
    path = np.ones(N)

    for i in range(0,N):
        dist[i] = graph[startIdx][i]
        if dist[i] < inf:
            path[i] = startIdx
        else:
            path[i] = ERROR
            
    while True:
        nextNodeIdx = findNextMin(graph=graph,dist=dist,collected=collected,N=N)

        lines1 = getLine(nextNodeIdx,lineDict)

        if nextNodeIdx == ERROR:
            break
        collected[nextNodeIdx] = True

        for i in range(0,N):
            if collected[i] == False and graph[nextNodeIdx][i] < inf:

                if dist[nextNodeIdx] + graph[nextNodeIdx][i] < dist[i]:
                    dist[i] = dist[nextNodeIdx] + graph[nextNodeIdx][i]
                    path[i] = nextNodeIdx

    return dist,path

输出文件

输出文件通过 getLine()output()两个模块来实现。
其具体功能为:

  • getLine() 输入一个站点,返回一个列表,包含该站点所在的所有线路
  • output() 根据要求进行输出,并将最终结果写入routine.txt文件中。为便于调试,同时在控制台中进行输出。
    对于线路换乘的处理分成了四种情况,造成代码非常冗余且不宜阅读,是可以改进的方向
'''获取站点所在的线路号'''
def getLine(node,lineDict):
    lines = []
    for key in lineDict.keys():
        if node in lineDict[key]:
            lines.append(key)

    return lines
    

'''整理结果并输出文件'''
def output(nodes,startLine,endNode,path,lineDict,names):
    listOut = []
    outputPath = r'./routine.txt'
    outputFile = open(outputPath,'w')
    tracePath = []
    tracePath.append(nodes.index(endNode))
    pos = int(path[nodes.index(endNode)])
    while pos != ERROR:
        tracePath.append(pos)
        pos = int(path[pos])

    tracePath.reverse()
    curLine = []
    # curLine.append(eval(startLine)
    curLine.append(startLine)
    temp = startLine
    first = True
    print(len(tracePath))
    listOut.append(str(len(tracePath)))
    outputFile.write(str(len(tracePath)))
    outputFile.write('\r\n')
    for each in tracePath:

        if first == False:
            lines = getLine(nodes[each],lineDict)
            if len(curLine) == 1:
                if len(lines) == 1:
                    if lines[0] != curLine[0]:
                        curLine[0] = lines[0]
                        name = names[curLine[0]]
                        print(name)
                        listOut.append(name)
                        outputFile.write(name)
                        outputFile.write('\r\n')
                        temp = curLine[0]
                elif len(lines) >= 2:
                    curLine = lines
            elif len(curLine) >= 2:
                if len(lines) == 1:
                    if lines[0] != temp:
                        curLine = []
                        curLine.append(lines[0])
                        name = names[curLine[0]]
                        print(name)
                        listOut.append(name)
                        outputFile.write(name)
                        outputFile.write('\r\n')
                        temp = curLine[0]

                elif len(lines) >= 2:
                    newLine = list(set(curLine).intersection(lines))[0]
                    if newLine != temp:
                        curLine = []
                        curLine.append(newLine)
                        name = names[curLine[0]]
                        print(name)
                        listOut.append(name)
                        outputFile.write(name)
                        outputFile.write('\r\n')
                        temp = curLine[0]
                    else:
                        curLine = lines

        print(nodes[each])
        listOut.append(nodes[each])
        outputFile.write(nodes[each])
        outputFile.write('\r\n')
        first = False
    
    outputFile.close()
    return listOut

异常处理

用户给与的输入信息在web的限制下已经变得非常有限。

分享图片

因此可以在调用业务函数之前通过判断来处理异常,在通过所有检查的情况下才将请求交给后台处理。

if startNode == endNode:
        flash('出发站与终点站不能相同')
        startNode = "error"
    elif startLine == '' or not eval(startLine) in lineNum:
        flash("请选择正确的出发线路")
        startNode = "error"
    elif endLine == '' or not eval(endLine) in lineNum:
        flash("请选择正确的终点线路")
        startNode = "error"
    elif not startNode in lineDict[eval(startLine)]:
        flash("请选择正确的出发站")
        startNode = "error"
    elif not endNode in lineDict[eval(endLine)]:
        flash("请选择正确的终点站")
        startNode = "error"
    else:
        return redirect(url_for('loadResult'))

在用户尝试提交非法的请求时,会产生如下的提示:

分享图片


分享图片


分享图片

前端搭建

通过Flask与Bootstrap结合的方式搭建web页面。

  • Flask实现对前后端信息的传递与路由的转发
  • Bootstrap实现html5中样式的调用
  • JavaScript实现对线路的监听,从而动态加载某个线路所对应的站点信息
    JS具体实现
<script type="text/javascript">
    //1、用户选哪条线
    var startLine = document.getElementById("startLine");
    var endLine = document.getElementById("endLine");
    //2、定位到对应的线路集合
    var startNode = document.getElementById("startNode");
    var endNode = document.getElementById("endNode");
    //动态-改进
    var nodes = {{ lineDict|tojson }}
        $(".lineDict").html(nodes)


    //3、动态的添加标签
    function showStart() {
        startNode.innerHTML = "--选择一个起始站--";
        var line = startLine.value
        for (var i in nodes[line]) {
            startNode.innerHTML += "<option>" + nodes[line][i] + "</option>";
        }
    }

    function showEnd() {
        endNode.innerHTML = "--选择一个起始站--";
        var line = endLine.value
        for (var i in nodes[line]) {
            endNode.innerHTML += "<option>" + nodes[line][i] + "</option>";
        }
    }
</script>

分享图片


随后将页面挂载到 AWS 的 EC2 实例上。
具体的挂载方法可以参考如下两篇博客,虽然博客中使用的是阿里云的服务器,但配置思路与AWS类似。

结果呈现

本地测试

输入

'''sample'''
startNode = '西直门'
endNode = '北京南站'
startLine = 13

运行结果

分享图片

web端测试

输入

分享图片


输出

分享图片


同时在本地也会产生routine.txt文件

分享图片

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Python中的函数(二) 在上一篇文章中提到了Python中函数的定义和使用,在这篇文章里我们来讨论下关于函数的一些更深的话题。在学习C语言函数的时候,遇到的问题主要有形参实参的区别、参数的传递和改变、变量的作用域。同样在Python中,关于对函数的理解和使用也存在这些问题。下面来逐一讲解。一.函
Python中的字符串 可能大多数人在学习C语言的时候,最先接触的数据类型就是字符串,因为大多教程都是以&quot;Hello world&quot;这个程序作为入门程序,这个程序中要打印的&quot;Hello world&quot;就是字符串。如果你做过自然语言处理方面的研究,并且用Python
Python 面向对象编程(一) 虽然Python是解释性语言,但是它是面向对象的,能够进行对象编程。下面就来了解一下如何在Python中进行对象编程。一.如何定义一个类 在进行python面向对象编程之前,先来了解几个术语:类,类对象,实例对象,属性,函数和方法。 类是对现实世界中一些事物的封装,
Python面向对象编程(二) 在前面一篇文章中谈到了类的基本定义和使用方法,这只体现了面向对象编程的三大特点之一:封装。下面就来了解一下另外两大特征:继承和多态。 在Python中,如果需要的话,可以让一个类去继承一个类,被继承的类称为父类或者超类、也可以称作基类,继承的类称为子类。并且Pytho
Python中的函数(一) 接触过C语言的朋友对函数这个词肯定非常熟悉,无论在哪门编程语言当中,函数(当然在某些语言里称作方法,意义是相同的)都扮演着至关重要的角色。今天就来了解一下Python中的函数用法。一.函数的定义 在某些编程语言当中,函数声明和函数定义是区分开的(在这些编程语言当中函数声明
在windows下如何快速搭建web.py开发框架 用Python进行web开发的话有很多框架供选择,比如最出名的Django,tornado等,除了这些框架之外,有一个轻量级的框架使用起来也是非常方便和顺手,就是web.py。它由一名黑客所创建,但是不幸的是这位创建者于2013年自杀了。据说现在由
将Sublime Text 2搭建成一个好用的IDE 说起编辑器,可能大部分人要推荐的是Vim和Emacs,本人用过Vim,功能确实强大,但是不是很习惯,之前一直有朋友推荐SUblime Text 2这款编辑器,然后这段时间就试了一下,就深深地喜欢上这款编辑器了...
Python中的模块 有过C语言编程经验的朋友都知道在C语言中如果要引用sqrt这个函数,必须用语句&quot;#include&lt;math.h&gt;&quot;引入math.h这个头文件,否则是无法正常进行调用的。那么在Python中,如果要引用一些内置的函数,该怎么处理呢?在Python中
Python的基础语法 在对Python有了基础的认识之后,下面来了解一下Python的基础语法,看看它和C语言、java之间的基础语法差异。一.变量、表达式和语句 Python中的语句也称作命令,比如print &quot;hello python&quot;这就是一条语句。 表达式,顾名思义,是
Eclipse+PyDevʽjango+Mysql搭建Python web开发环境 Python的web框架有很多,目前主流的有Django、Tornado、Web.py等,最流行的要属Django了,也是被大家最看好的框架之一。下面就来讲讲如何搭建Django的开发环境。一.准备工作 需要下载的
在windows下安装配置Ulipad 今天推荐一款轻便的文本编辑器Ulipad,用来写一些小的Python脚本非常方便。 Ulipad下载地址: https://github.com/limodou/ulipad http://files.cnblogs.com/dolphin0520/u...
Python中的函数(三) 在前面两篇文章中已经探讨了函数的一些相关用法,下面一起来了解一下函数参数类型的问题。在C语言中,调用函数时必须依照函数定义时的参数个数以及类型来传递参数,否则将会发生错误,这个是严格进行规定的。然而在Python中函数参数定义和传递的方式相比而言就灵活多了。一.函数参数的
在Notepad++中搭配Python开发环境 Python在最近几年一度成为最流行的语言之一,不仅仅是因为它简洁明了,更在于它的功能之强大。它不仅能够完成一般脚本语言所能做的事情,还能很方便快捷地进行大规模的项目开发。在学习Python之前我们来看一下Python的历史由来,&quot;Pytho
Python中的条件选择和循环语句 同C语言、Java一样,Python中也存在条件选择和循环语句,其风格和C语言、java的很类似,但是在写法和用法上还是有一些区别。今天就让我们一起来了解一下。一.条件选择语句 Python中条件选择语句的关键字为:if 、elif 、else这三个。其基本形式如
关于raw_input( )和sys.stdin.readline( )的区别 之前一直认为用raw_input( )和sys.stdin.readline( )来获取输入的效果完全相同,但是最近在写程序时有类似这样一段代码:import sysline = sys.stdin.readline()
初识Python 跟学习所有的编程语言一样,首先得了解这门语言的编程风格和最基础的语法。下面就让我们一起来了解一下Python的编程风格。1.逻辑行与物理行 在Python中有逻辑行和物理行这个概念,物理行是指在编辑器中实际看到的一行,逻辑行是指一条Python语句。在Python中提倡一个物理行只
当我们的代码是有访问网络相关的操作时,比如http请求或者访问远程数据库,经常可能会发生一些错误,有些错误可能重新去发送请求就会成功,本文分析常见可能需要重试的场景,并最后给出python代码实现。
1.经典迭代器 2.将Sentence中的__iter__改成生成器函数 改成生成器后用法不变,但更加简洁。 3.惰性实现 当列表比较大,占内存较大时,我们可以采用惰性实现,每次只读取一个元素到内存。 或者使用更简洁的生成器表达式 4.yield from itertools模块含有大量生成器函数可
本文介绍简单介绍socket的常用函数,并以python-kafka中的源码socketpair为例,来讲解python socket的运用
python实践中经常出现编码相关的异常,大多网上找资料而没有理解原理,导致一次次重复错误。本文对常用Unicode、UTF-8、GB2312编码的原理进行介绍,接着介绍了python字符类型unicode和str以及常见编解码错误UnicodeEncodeError和UnicodeDEcodeEr