基于图论算法的南京大学仙林校区公共交通出行研究

写在前面

南京大学工科试验班有门平台课叫《城市文明与设计美学》期末要一篇城乡规划论文。

虽然上了一学期的课,我仍然不知道啥是城乡规划。有几个老师上课的时候扯了一堆人工智能啥的,于是我想,建个南京市地铁公交的图论模型应该也算城乡规划吧。

这里的爬虫对我来说算是第一次用的新技术。所幸我爬的是数据库,还比较简单。

后续代码写得其实很难看,原因是最开始设计算法的时候没想到高德地图的数据那么脏。把洗数据等内容加进去之后代码就变得相当丑了。

论文已经提交了,所以现在我发到博客上应该也没有问题,吧?

完整代码:
Part1 数据爬取:

import requests
import json

cityname = '南京'
data = open("data", "w")

def get_lineinfo(ind):
    """
    write the info of the line to file 'data'
    """
    line = str(ind)
    url = 'https://restapi.amap.com/v3/bus/linename?s=rsv3&extensions=all&key=316de36ac4e64d721ead78f094e52d0f&output=json&city={}&offset=1&keywords={}&platform=JS'.format(cityname, line)
    r = requests.get(url).text
    rt = json.loads(r)

    try:
        # the name of the line
        print(rt['buslines'][0]['name'], file = data)

        # number of the stations
        print(len(rt['buslines'][0]['busstops']), file = data)

        # station info: name, latitude, longtitude
        for st in rt['buslines'][0]['busstops']:
            print(st['name'], st['location'], file = data)
    except:
        pass

for ind in list(range(1, 342)):
    get_lineinfo(ind)

data.close()

特别地可以发现,这里用了一个 except: pass ,说来原因奇怪,有一条奇异的线路,尽管网上能找到信息,但是却实际不存在……
实在吓人,都市传说吗(
 
Part 2 模型建立与算法运行:

#include<bits/stdc++.h>
using namespace std;

class Station {
public:
    double lat, lon;
    string name;

    Station() {}
    Station(string x, double a, double b) {
        name = x, lat = a, lon = b;
    }

    //using Haversine Formula
    friend double operator - (Station a, Station b) {
        const double p = M_PI / 180;
        double res = 0.5 - cos((b.lat - a.lat) * p) / 2
            + cos(a.lat * p) * cos(b.lat * p) * (1 - cos((b.lon - a.lon) * p)) / 2;
        return 12742 * asin(sqrt(res));
    }

    friend istream &operator >> (istream &is, Station &obj) {
        char eater;
		is >> obj.name >> obj.lat >> eater >> obj.lon;
		return is;
	}

    friend bool operator < (Station a, Station b) {
        if (a.name == b.name && (a - b) > 0.5)
                return a.lat < b.lat;
        return a.name < b.name;
    }
};

class Line {
private:
    vector<Station> sta;

public:
    string name;
    bool cir, sub;
    Line() {
        cir = sub = false;
        sta.clear();
    }
    Line(string s, bool a, bool b) {
        name = s;
        cir = a, sub = b;
        sta.clear();
    }

    int size() {  return sta.size();  }
    Station fetch(int x) {  return sta[x];    }
    void append(Station x) {  sta.push_back(x);   }

};

template<int N, int M>
class Graph {
private:
    int beg[N], nex[M], tar[M], len = 1;
    double cst[M], dis[N], pre_dis[N];
    int pre[N];

    typedef pair<double, int> Node;
    priority_queue<Node, vector<Node>, greater<Node> > que;

public:
    Graph() {
        memset(beg, 0, sizeof(beg));
        memset(nex, 0, sizeof(nex));
        len = 1;
    }

    inline void add_edge(int a, int b, double c) {
        ++len;
        tar[len] = b, cst[len] = c;
        nex[len] = beg[a], beg[a] = len;
    }

    void dijkstra(int x) {
        memset(dis, 0x7f, sizeof(dis));
        que.push(Node(0, x));
        dis[x] = 0;

        while (!que.empty()) {
            Node cur = que.top();   que.pop();
            if (cur.first > dis[cur.second])    continue;
            for (int i = beg[cur.second]; i; i = nex[i]) {
                double tmp = dis[cur.second] + cst[i];
                if (tmp < dis[tar[i]]) {
                    dis[tar[i]] = tmp;
                    pre[tar[i]] = cur.second;
                    pre_dis[tar[i]] = cst[i];
                    que.push(Node(tmp, tar[i]));
                }
            }
        }
    }

    double query_dist(int t) {
        // remember to call dijkstra(s) first
        return dis[t];
    }

    vector<int> query_path(int s, int t) {
        vector<int> ret;
        int cur = t;
        double res = 0;
        pair<int, int> ans;
        while (cur != s) {
            if (res < pre_dis[cur]) {
                res = pre_dis[cur];
                ans = make_pair(cur, pre[cur]);
            }
            ret.push_back(cur);
            cur = pre[cur];
        }
        ret.push_back(s);
        reverse(ret.begin(), ret.end());

        cout << res << ": " << ans.first << "->" << ans.second << endl;

        return ret;
    }
};

map<Station, vector<int> > poi_dic;
string rev_dic[16384];
vector<Line> dat;
int cnt = 1;

Graph<16384, 1048576> G;

void add_line(int uid, int vid, double d, bool is_cir, bool is_sub) {
    // claim c_1=0.026, c_2 = 0.9 as fixed coefficient
    double dis = d * (is_sub? 0.9: 1) + 0.039;    
    G.add_edge(uid, vid, dis);
    if (!is_cir)
        G.add_edge(vid, uid, dis);
}

void add_stop(int uid, int vid) {
    // claim c_3 = 0.5
    double dis = 0.5;
    if (uid * vid < 0)  dis = 1;
    uid = abs(uid), vid = abs(vid);

    G.add_edge(uid, vid, dis);
    G.add_edge(vid, uid, dis);
}

int main() {
    ifstream fin_lineinfo("data");
    ifstream fin_source_station("source_station");
    ifstream fin_target_station1("target_station1");
    ifstream fin_target_station2("target_station2");

    string str;
    while (fin_lineinfo >> str) {
        int siz;
        fin_lineinfo >> siz;
        dat.push_back(Line(str, 
                    (str.find("环") != string::npos), 
                    (str.find("地铁") != string::npos)) );

        Station obj;
        for (int i = 0; i != siz; ++i) {
            fin_lineinfo >> obj;
            dat.back().append(obj);
        }
    }

    for (auto line: dat) {
        int siz = line.size();
        int f = (line.sub)? -1: 1;

        Station u = line.fetch(0);
        poi_dic[u].push_back(f * (cnt++));
        for (int i = 1; i != siz; ++i) {
            Station v = line.fetch(i);
            poi_dic[v].push_back(f * (cnt++));
            add_line(
                abs(poi_dic[u].back()),
                abs(poi_dic[v].back()),
                (u - v), line.cir, line.sub   );
            u = v;
        }
    }
    int s = 12000, t1 = 12001, t2 = 12002;

    Station obj;
    while (fin_source_station >> obj) {
        for (auto id: poi_dic[obj]) {
            G.add_edge(s, abs(id), 0);
        }
    }

    while (fin_target_station1 >> obj) {
        for (auto id: poi_dic[obj]) {
            G.add_edge(abs(id), t1, 0);
        }
    }

    while (fin_target_station2 >> obj) {
        for (auto id: poi_dic[obj]) {
            G.add_edge(abs(id), t2, 0);
        }
    }

    for (auto i: poi_dic) {
        for (auto j: i.second) {
            rev_dic[abs(j)] = i.first.name;
        }

        for (auto j: i.second) {
            for (auto k: i.second) {
                if (j == k) continue;
                add_stop(j, k);
            }
        }
    }

    G.dijkstra(s);
    cout << G.query_dist(t1) << endl;
    cout << G.query_dist(t2) << endl;

    vector<int> p1 = G.query_path(s, t1),
                p2 = G.query_path(s, t2);

    for (auto i: p1) {
        cout << rev_dic[i] << " ";
    }
    cout << endl;
    for (auto i: p2) {
        cout << rev_dic[i] << " ";
    }
    cout << endl;

    return 0;
}

 
 
 
 
 

基于图论算法的南京大学仙林校区公共交通出行研究

摘要:为了考察仙林大学城的公共交通状况,本文基于高德地图的数据库,提取了南京主城区内地铁公交线路的主要信息,建立了南京市地铁公交网络的图论模型。并通过 Dijkstra算法评估了学生从南京大学仙林校区出发,乘坐公共交通出行的质量与瓶颈,具体地对已经建成、规划在建的地铁线路带来的优化进行了量化评估。

关键词:南京大学仙林校区;公共交通;图论;Dijkstra

引言

​ 南京大学仙林校区位于南京主城局东北部的仙林大学城,距离市中心新街口有约15公里距离。每逢周末等节假日,南京大学有相当一部分的学生会从仙林出发,乘坐公共交通工具前往市中心。工作日也有许多学生、教职工通过地铁公交往返于南大仙林、鼓楼两校区。有些同学甚至会选择假日出游,乘坐公共交通工具前往南京的一些风景区。有研究表明,在上述出行情景中,多数大学生都会偏向选择公共交通方式[1]。而大学城处于城市末端,公共交通状况往往不尽人意。部分地区的大学城公交线路过长,车辆班次在周末等高峰期间往往不能满足乘客需求[2],这在一定程度上反映了国内大学城公共交通问题的通病。

​ 在这种背景下,我们不禁要问:南大仙林校区在南京公共交通网络中的通达性如何,又该如何进一步改善呢?下面,笔者将依据南京市地铁、公交车数据建立图论模型,对学生的出行过程进行数学抽象。且将使用 Dijkstra 算法研究与评估学生出行的质量,并依照结果提供一些线路规划的改进意见。

数据采集与处理

数据采集

​ 笔者使用了高德地图开发者平台提供的数据库 API ,用 python 语言编写了一个网络爬虫程序,获取了南京市公交、地铁的线路信息。

# 代码块1: 笔者编写的python爬虫程序

import requests
import json

cityname = '南京'
data = open("data", "w")

def get_lineinfo(ind):
    """
    write the info of the line to file 'data'
    """
    line = str(ind)
    url = 'https://restapi.amap.com/v3/bus/linename?s=rsv3&extensions=all&key=316de36ac4e64d721ead78f094e52d0f&output=json&city={}&offset=1&keywords={}&platform=JS'.format(cityname, line)
    r = requests.get(url).text
    rt = json.loads(r)

    try:
        # the name of the line
        print(rt['buslines'][0]['name'], file = data)

        # number of the stations
        print(len(rt['buslines'][0]['busstops']), file = data)

        # station info: name, latitude, longtitude
        for st in rt['buslines'][0]['busstops']:
            print(st['name'], st['location'], file = data)
    except:
        pass

for ind in list(range(1, 342)):
    get_lineinfo(ind)

data.close()

​ 面对高德地图数据库中提供的各种数据,笔者选取了其中的路线名称、站点名称、站点经纬度坐标作为有效数据提取出来,并将其结构化地存放在data文件中。

​ 其中,路线名称提示了该线路的编号与起点站、终点站,标明了该线路是公共汽车线路还是地铁线路,该线路是否属于起点与终点相接的环线等信息;统一化的站点名称也提供了各公共交通线路相交情况的信息;站点经纬度坐标则为分析各站点之间的距离带来了便利。

​ 同样基于高德地图的API,我们若将得到的数据点在地图上可视化,可以得到一个更直观的表示图:

数据处理

站点间距计算

​ 数据库给出的经纬坐标是地球球面上的坐标。因此,考虑计算两站点之间的距离,应当使用特殊的球面几何方法。这里我们使用半正矢公式进行计算。


首先引入半正矢函数 \(hav(\theta) = \frac{1-\cos\theta}{2} = sin^2(\frac{\theta}{2})\)

半正矢公式(Haversine Formula)指出[3]

​ 考虑将经纬度坐标分别为 \((\varphi_1,\lambda_1), (\varphi_2,\lambda_2)\) 的两点,我们有:

\[hav(\frac{d}{R}) = hav(\varphi_2-\varphi_1)+\cos\varphi_1 \cos\varphi_2\cdot hav(\lambda_2-\lambda_1) \]

​ 其中, \(d\) 是两地间的距离, \(R\) 是地球半径。

​ 为了便于计算,我们可以将此式化为:

\[d = 2R\arcsin(\sqrt{\sin^2{(\frac{\varphi_2-\varphi_1}{2})}} + \cos\varphi_1 \cos\varphi_2\cdot \sin^2(\frac{\lambda_2-\lambda_1}{2})) \]

在编程实现时稍作修改,将数据代入计算即可得到所求距离。

//代码块2:半正矢公式计算两站点之间的距离 (C++ 语言)
friend double operator - (Station a, Station b) {
    const double p = M_PI / 180;
    double res = 0.5 - cos((b.lat - a.lat)*p) / 2
        + cos(a.lat*p) * cos(b.lat*p) * (1 - cos((b.lon - a.lon)*p)) / 2;
    return 12742 * asin(sqrt(res));
}

​ 求解站点距离的最大作用是进行时间估计。假定各公交车均以速度 \(v\) 行驶,由 \(t = \frac{s}{v}\) 容易得到时间与路程成正比。为简化考虑,模型假设相邻两个站点直接以直线相连接。注意到到公交车会在站点停靠一段时间,同样简化考虑,模型给求出的距离加上一个常数 \(c_1\) 来修正停靠带来的影响。

​ 接下来考虑 \(c_1\) 数值的设置。

​ 基于已有的关于公交车停靠时间损失的研究[4],与公交车运行速度的研究[5],我们选取常数 \(c_1=\overline{t} \cdot \overline{v} = 3 \cdot \frac{46.5}{3600} \approx 0.039\) 。此处的估算忽略了城郊与市中心公交车运行与停靠的不同情况。但考虑到 \(c_1\) 的数值相对较小,且已有研究中提示的差距并不会给 \(c_1\) 的数值带来特别大的影响,故这种估计的误差是可以接受的。

环线、地铁处理

​ 观察数据容易发现,凡是地铁线路,其名称中必然会含有汉字“地铁”,例如有:地铁1号线(迈皋桥--中国药科大学)。类似地,凡线路是起点站、终点站相接的环线,其线路名称中必然会含有汉字“环”,例如有:341路环线(燕江新城总站--燕江新城总站)。

​ 因此在数据录入时应当特别处理这两种情况,以便在建模时特殊考虑。

​ 此外,要考虑到地铁与公交车快捷程度之间的差距。同样由于 \(t=\frac{s}{v}\) ,我们只考虑地铁与公交车的速度差距,给求出的距离乘上一个常数 \(c_2\) 作为最终的权值。

​ 基于已有的关于南京地铁对城市公共交通网络通达性影响的研究[6],发现由这一表格中的数据:

网络 到所有商服中心的总成本 到所有商服中心的平均成本 到最近商服的总成本 到最近商服的平均成本 到所有对外交通点的总成本 到所有对外交通中心的平均成本 到最近对外交通中心的总成本 到最近对外交通中心的平均成本 OD矩阵时间距离 居民单元彼此通行平均时间成本
地铁前 15975 99 2761 17 17955 111 2720 17 772955 4771
地铁后 14285 88 2544 16 16270 100 2397 15 719134 4439
比率 0.894 0.889 0.921 0.941 0.906 0.900 0.881 0.882 0.930 0.930

​ 通地铁后节约的时间平均略少于 \(0.1\) 。同样根据该研究,仙林大学城所在的栖霞区是通地铁后节约时间相对较多的地区,因此本文选择 \(c_2=0.9\) 作为一个比较合理的估计值。

转车处理

​ 在现实生活中,转车显然需要消耗相应的时间。我们同样需要设置一个常数 \(c_3\) 刻画转车带来的时间损耗。

​ 基于已有的公交换乘模型研究[7],我们转车的平均耗时约为 \(38.8s\) 。虽然考虑到城市外围区域转车时间明显偏长,但大学生出行中转车行为一般不会发生在城市外围,因此这样的估计在一定程度上仍然是合理的。与上述处理类似地,我们可以取 \(c_3=t\cdot v = 38.8 \times \frac{46.5}{3600} = 0.5\) 作为换乘带来的影响系数。

​ 特别地,考虑到公交与地铁线路之间的换乘需要步行时间。在这种情形下,额外设置 \(c_3 = 1\) 作为影响系数。

站点重复性考察

​ 高德数据库中的站点、线路数据实际上是很脏的。比如同一个站点在不同线路中的经纬度坐标存在差异。更如,南京市有一些名称相同的不同站点,“岔路口站”就是一个典型的例子。这都给模型建立带来了不小的困难。

​ 为了辨识不同站点的位置,笔者设置了以下判别机制:

  1. 名称不同的视为不同站点
  2. 名称相同的且距离在 500 米以外的,视为不同站点
  3. 其余情况的视作相同站点

​ 同时,笔者并不试图将相同站点的坐标取均值作为最终坐标。实际上,在利用站点坐标计算距离时,仅会计算该点与同一线路上两相邻点的距离。而数据库中站点坐标的差异正是由于线路不同导致的。在确定的线路上,原数据库中的数值反而是最准确的。因此不必要对各坐标取均值。

模型建立与问题求解

模型建立

​ 这里,我们将公共交通站点抽象为图论模型中的点。若有一条公交线路连续地经过两个站点,则在两站点间设置一条无向边。特别地,若该线路为环线,则设置一条有向边。所设置边的权值(里程数指标)为:

\[w = \cases{ d+c_1 & ,该边因公交车线路连接 \\ c_2(d+c_1) & ,该边因地铁线路连接 \\ c_3 & ,该边因同站点换乘连接 } \]

​ 其中 \(d\) 是经由半正矢公式(Haversine Formula)计算的两站点间的球面距离, \(c_1,c_2\) 的数值与意义如上文所示。

​ 此外需要注意的是,应当考虑从某站点转车所需的平均额外时间。假如直接按照上述方式连边的话,得到的模型很可能类似于左图。在这样的模型中,很难发现 \(1, 4, 5;~~2, 4, 6;~~3,4,7\) 分别是三条不同的线路。

​ 因此笔者采用采用拆点的办法。若某个站点 \(u\)\(k\) 条线路经过了,则将 \(u\) 拆成 \(u_1,u_2,\dots,u_k\)\(k\) 个点。之后,我们将 \(k\) 个点两两相连成一个完全子图,边权设置为转车的平均时间 \(c_3\)。得到的模型如右图所示。

​ 接下来,由于要考虑南大仙林校区周边各站台的情况,我们不妨设置一个超级源点 \(s\) ,与南大仙林校区周边各站点连边,且边权为 \(0\) 。这样,从 \(s\) 点出发考虑图上路径就与从各点出发的结果等价了。

​ 同样根据高德地图的数据,周边的可能站点有:

站点名称 属性
九乡河东路 公交站
九乡河东路北 公交站
纬地路 公交站
南大科学园 公交站
仙林南大东门 公交站
元化路南 公交站
元化路 公交站
仙林金陵小学 公交站
南大仙林校区 公交站、地铁站

​ 接下来我们考虑几个可能的路径终点.类似地,我们可以设置一个超级汇点 \(t\) ,与表中站点连边,且边权为 \(0\)

​ 首先的一个可能地点是南大鼓楼校区、市中心周边,附近站点有:

站点名称 属性
鼓楼 地铁站
珠江路 地铁站
云南路 地铁站
鼓楼公园 公交站
鼓楼医院 公交站
中央路·鼓楼 公交站
鼓楼公交总站 公交站
北京东路·鼓楼 公交站
广州路·中山路 公交站
中山路·珠江路北 公交站

​ 大学生也可能会选择到风景区出行,如:

站点名称 属性
牛首山风景区 公交站
牛首山风景区东 公交站

​ 针对这两种比较具体的情况,我们分别建立两个超级汇点 \(t1,t2\) 意义如上文所示。

问题求解

​ 最短路是在图论模型中,从 \(s\)\(t\) 的一条边权之和最小的路径。正常情况下,模型中的最短路径也就是现实生活中出行的时间最短路径。通常来说,瓶颈路是值最短路径上权值最大的一条边,瓶颈路的数值能在一定程度上反映该最短路径的可优化情况。

​ 考虑到本文构造的图论模型规模比较庞大(共5985个结点,54439条连边),且图中的边没有负权值,笔者使用了二项堆优化的Dijkstra算法[8]求出了从南大仙林周边出发到南京市主城区各公交站的最短路径。

​ Dijkstra算法是一种贪心算法,结合堆优化可以以 \(\Theta((E+V)\log V)\) 的时间复杂度解出图上的单源最短路径。是相对而言比较高效的一种最短路算法。然而初始版本的 Dijkstra 算法只能得出两点间最短距离的数值。在本模型中,我们同时想关注最短路径途径的各个站点名称与路径上的瓶颈路,因此笔者对算法进行了一些改进:

  1. 使用 pre[] 数组维护每个结点最后一次被松弛经由的点的序号。这样就可以保证 \(\delta(\rm{pre[}u\rm{]}, u)\) 一定是某条经过 \(u\) 的最短路上的边。那么,又已知最短路径一定会经过终点,只需要沿着终点与 pre[] 向前回溯至起点就得到了整条最短路径。
  2. 相似的,由于 \(\delta(\rm{pre[}u\rm{]}, u)\) 一定是某条经过 \(u\) 的最短路上的边,只需在回溯过程中,求出权值 \(|\delta(\rm{pre[}u\rm{]}, u)|\) 的最值即可。

​ 具体地,笔者实现了一个 Graph class 来实现上述算法:

template<int N, int M>
class Graph {
private:
    int beg[N], nex[M], tar[M], len;
    double cst[M], dis[N], pre_dis[N];
    int pre[N];

    typedef pair<double, int> Node;
    priority_queue<Node, vector<Node>, greater<Node> > que;

public:
    Graph() {
        memset(beg, 0, sizeof(beg));
        memset(nex, 0, sizeof(nex));
        len = 1;
    }

    inline void add_edge(int a, int b, double c) {
        ++len;
        tar[len] = b, cst[len] = c;
        nex[len] = beg[a], beg[a] = len;
    }

    void dijkstra(int x) {
        memset(dis, 0x7f, sizeof(dis));
        que.push(Node(0, x));
        dis[x] = 0;

        while (!que.empty()) {
            Node cur = que.top();   que.pop();
            if (cur.first > dis[cur.second])    continue;
            for (int i = beg[cur.second]; i; i = nex[i]) {
                double tmp = dis[cur.second] + cst[i];
                if (tmp < dis[tar[i]]) {
                    dis[tar[i]] = tmp;
                    pre[tar[i]] = cur.second;
                    pre_dis[tar[i]] = cst[i];
                    que.push(Node(tmp, tar[i]));
                }
            }
        }
    }

    double query_dist(int t) {
        // remember to call dijkstra(s) first
        return dis[t];
    }

    vector<int> query_path(int s, int t) {
        vector<int> ret;
        int cur = t;
        double res = 0;
        pair<int, int> ans;
        
        while (cur != s) {
            cur = pre[cur];
            if (res < pre_dis[cur]) {
                res = pre_dis[cur];
                ans = make_pair(cur, pre[cur]);
            }
            ret.push_back(cur);
        }
        ret.push_back(s);
        reverse(ret.begin(), ret.end());
        
        cout << res << ": " << ans.first << "->" << ans.second << endl;
        
        return ret;
    }
};

结果评价

​ 运行程序之后可以得到,若要到达鼓楼附近车站,里程数指标约为 \(19.9141 km\) ,比两地直线距离多出近 \(2km\) 。考虑到换乘时间、实际路线是曲线,这样的结果是合情合理的。我们同时可以得到,最优路径依次经过:

线路序号 线路名称 途径站点
1 地铁2号线 南大仙林校区 羊山公园 仙林中心 学则路 仙鹤门 金马路 马群 钟灵街 孝陵卫 下马坊 苜蓿园 明故宫
2 地铁13号线 明故宫 马标 浮桥 珠江路

​ 其中在明故宫有一次换乘。这条最短路径上的瓶颈路是 钟灵街-马群 一段,这是线路上间隔最远的两个站点。整条线路经过的站点数量也不多,是比较合理的。不过,值得注意的是,这里最短路选择了在明故宫换乘地铁13号线——这是一条规划中的地铁线路,还没有实际建成。

​ 如果除去数据库中的未建成线路,算法则会选择在金马路换乘地铁4号线到鼓楼,里程数指标为 \(20.0473km\);另一种选择是在新街口换乘地铁1号线到珠江路,里程数指标为 \(20.4647km\) 。这条两种路径与师生现有的日常通勤的路线吻合得比较好。根据算法得到的数据,比较之下笔者认为,地铁十三号线的规划是能够显著提高师生通勤效率的。至少对南大师生的通勤活动来说,13号线的建设将是快捷、方便、有益的。

​ 然而,前往牛首山的线路则复杂得多。算法得出的最短路径需要经过以下几条线路:

线路序号 线路名称 途经站点
1 地铁2号线 南大仙林校区 羊山公园 仙林中心 学则路 仙鹤门 金马路 马群 钟灵街 孝陵卫 下马坊 苜蓿园 明故宫 西安门 大行宫 新街口
2 地铁1号线 新街口 张府园 三山街 中华门 安德门
3 75路公交 安德门 菊花台公园 安德门大街·东向花 天隆寺地铁站
4 755路公交 天隆寺地铁站 宁双路 铁心桥 铁心桥南 银杏山庄 王燕街 大定坊 韩府山庄 省人武学校 将军山 普觉寺 牛首山风景区

​ 本条路线里程数指标为 \(33.1534km\) ,比两地直线距离多出了约 \(3km\) 。最短路上的瓶颈路是 羊山公园-南大仙林校区 一段。分析发现,瓶颈路就在起点城市边缘位置,且绝对长度较小,但路径总长度确较大,这意味着线路上的站点数目太多,有相当时间消耗在了到站停靠上。且本条路径有三次换乘操作,中间等待时间也较长。

​ 这是一条起止点都是城市边缘地带的路线。其中,南大仙林校区因为地铁2号线,更兼因其他站点与公交线路,与城市的整体交通网络连接情况相对较好的。然而,牛首山风景区区域则不然。其中牛首山风景区站只有755路公交经过,而牛首山风景区东站只有754路公交经过,且公交线路孤立延伸,整体路线过长。这成为了这条线路欠佳的主要原因。

​ 观察上述结论可以发现,地铁2号线的修成使得南大仙林校区与整个南京市公共交通网络的连接情况有了很大的改善。具体地说,倘若从数据库中删除地铁2号线再运行算法,从南大仙林校区前往上述两个地点的里程数指标会分别增长至 \(23.428km\)\(35.3861km\) 。在这个意义上,可以认为地铁线路是有效、快捷、方便的,沟通城市边缘地区与城市公共交通网络的办法。

结论与评价

​ 城市规划是关于合理设计城市设施,使得人居环境更加美好的科学。它不仅需要感性的认识、诗意的栖居,也需要数据、理论、运算的支持。本文使用图论方法与网络数据库,从南京大学仙林校区出发,研究评价了南京市主城区的公共交通网络情况。

​ 城市公共交通网络的现实情况是纷繁、复杂、多变的,因而往往难以直接进行研究。本文通过查证文献、估算、取平均等方式,对现实情况进行了简化。又通过高德地图的数据库,获得了南京市公共交通网络的基本信息。使用计算机、数学手段处理了原数据库中不够直观、存在误差的信息,建立了一个表现良好的经典图论模型。并成功地使用了 Dijkstra 算法求解了一些问题。

​ 在模型上使用 Dijkstra 算法进行运算研究,笔者发现:首先,南京大学仙林校区虽然处于城市中心区边缘地带,但因为地铁2号线的修建,与城市公共交通网络的连通性良好,出行情况基本令人满意。随着地铁13号线规划的最终建成,师生前往城市中心区域新街口、鼓楼将更加便利。其次,在现有的公交网络上,从仙林南大到前往城市边缘另一端的牛首山风景区的路线虽不尽人意,但此条路线基本属于使用城市公共交通网络的极端情况,因此也是可以理解的。这条路径表现相对较差的主要原因是牛首山周边的城市公共交通网络不发达,与主体网络连通性较差。

​ 本文提供的模型与方法有较好的可复制性。首先,本文附带的代码也具有良好的可拓展性。笔者通过面向对象编程方法,设计的程序接口易于使用、表现良好。其次,通过修改几个关键字,很容易就可以对其他起点、终点的路径表现进行评估。同样地,容易对其他城市的公共交通网络进行评估。甚至可以说,对全国的公共交通网络进行分析也不存在太大困难。总而言之,本文提供了一个良好的刻画城市公共交通网络的模型,具有较好的可复制、可拓展性。

参考文献


  1. 刘梦茜, 蔡雷. 大学城微循环公交运行特征研究——以南京321路为例[C]// 2016中国城市规划年会. 0. ↩︎

  2. 尹应梅. 广州大学城公共交通现状调查分析与对策[J]. 交通与运输, 2009(1). ↩︎

  3. Robusto, C. C. "The Cosine-Haversine Formula." The American Mathematical Monthly 64, no. 1 (1957): 38-40. ↩︎

  4. 刘建荣,邓卫,张兵. 公交停靠站公交车损失时间研究[A]. 交通信息与安全,2001(04) ↩︎

  5. 陈峻, 王涛, 李春燕,等. 城市公交车与社会车辆混合流速度模型及交通运行状态分析[J]. 中国公路学报, 2012(01):128-134. ↩︎

  6. 李志, 代婉莹, 吕立刚, et al. 南京地铁对城市公共交通网络通达性的影响及地价增值响应[J]. 地理学报, 2014, 69(2). ↩︎

  7. 安实, 崔文, 王健. 基于换乘时间窗的公交区域时刻表优化方法[J]. 公路与汽运, 2015(05):61-65. ↩︎

  8. Dijkstra E.W. A note on two problems in connexion with graphs. Numerische Mathematik, 1 (1959), pp. 269-271 ↩︎

posted @ 2021-01-03 18:36  Jane_leaves  阅读(347)  评论(0编辑  收藏  举报