前言
我是一名Python小白,两个礼拜之前还对Python一无所知,上次我利用举办社团活动的契机,请学校老师做了一次Python入门讲座。听闻Python功能强大,对初学者也很友好,于是抱着初生牛犊不怕虎的精神,尝试尝试用Python来解决一个复杂的难题。
问题
旅游爱好者小龙有钱又有时间,她决定制定一个详尽的旅行计划,游遍目前中国的热门景区。目前手边仅有的资料就是一份想要游览的景区的名单(如下图,总共有两百多个景点),其中主要是国家评定的5A级景区,制定怎样的旅行计划,才能最高效的游遍所有的景区呢?
问题解决的思路
首先这是一个非常具有现实意义的问题,它的核心是旅行商问题(Traveling Salesman Problem),也就是已知地点和地点之间的距离,找出游遍所有地点的最短路线。这是一个计算机领域的NP完全问题。TSP问题的核心算法有很多人已经研究过了。然而我们要解决的并不是一个纯数学的问题,而是一个实际问题。难点其实在于数据的搜集和整理。
经过一番艰难的思索和尝试,我列出了下图所示的程序设计思路。其主要思想在于利用百度提供的免费API接口,获取各个景点的地理位置、地址,附件机场、车站以及景点之间的路线等各种信息,再将获得的json文件进一步处理,得到格式化的数据,导入TSP路径求解器,获得最优路径,再将最优路径在地图中表示出来,并给出详细的形式指南。
这是一个尚在进行中的浩大工程,尤其对于一个Python新手来说。
学习Web API的调用
Web API是一种应用程序接口,通过一定的设置之后,程序可以通过发送不同网址查询所需的数据。API的使用需要申请密钥,本文中使用的到的密钥均可以免费获取。
地理服务相关的API有不少,比较成熟的是百度和谷歌两家。虽然很多时候谷歌的服务都显得高大上许多,但是经过一番尝试,在地图服务,尤其是国内的地图服务上,百度有很多优势。除去连接的稳定性之外,百度的所有Web API共享一个密钥,而谷歌需要为不同的API分别申请密钥,而且百度对中文支持完善,所以这里主要使用了百度地图的Web API服务。用户可以到这个地址申请:http://lbsyun.baidu.com/ 除了地图之外,百度还有一个API商店,提供了很多免费的API服务,网址:http://apistore.baidu.com/
Python程序
调用Web API的程序其实相当简单,笔者作为一个小白没花多长时间就捣鼓出了一段代码。
# -*- coding: utf-8 -*-
'''
Created on 17-June-2016 @Jerry
用于调用百度地图和去哪儿网的web api。
'''
import sys,urllib2,urllib,os
#===============================================================================
#读取API Key--------------------------------------------
f_key = open("API Key.txt",mode='r')
key = []
for line in f_key.readlines():
line = line.strip('\n')
key += [line.split(",")]
apikey = dict(key)
f_key.close()
#------------------------------------------------------
#读取API Url--------------------------------------------
f_url = open("API Url.txt",mode='r')
url = []
for line in f_url.readlines():
line = line.strip('\n')
url += [line.split(",")]
apiurl = dict(url)
f_url.close()
#------------------------------------------------------
#===============================================================================
#===============================================================================
#查询百度地图API-------------------------------------------
class BaiduQuery:
def __init__(self,api,args,name):
self.api = api #调用的API名称
self.args = args #查询变量
self.name = name #查询结果的命名方式
def getjson(self):
baseurl = apiurl[self.api]
self.args["ak"] = apikey["BaiduMap"]
self.args["output"] = "json"
encodeargs = urllib.urlencode(self.args)
url = urllib.unquote(baseurl + encodeargs)
routeDir = "Data/"+self.name+'/'+self.api+'.json'
urllib.urlretrieve(url, routeDir)
print self.name,self.api," -> Success"
def makedir(self): #文件夹是否已创建
if not os.path.exists("Data/"+self.name+"/"):
os.makedirs('Data/'+self.name+'/')
print "Make Dir Successfully!"
return 1
else:
return 0
#------------------------------------------------------
#===============================================================================
#查询百度API商店中去哪儿网火车票、景点门票--------------------------------------
class QunarQuery:
def __init__(self,api,args,name):
self.api = api #调用的API名称
self.args = args #查询变量
self.name = name #查询结果的命名方式
def getjson(self):
baseurl = apiurl[self.api]
encodeargs = urllib.urlencode(self.args)
url = urllib.unquote(baseurl + encodeargs)
routeDir = "Data/"+self.name+'/'+self.api+'.json'
req = urllib2.Request(url)
req.add_header("apikey", apikey["Qunar"])
resp = urllib2.urlopen(req)
content = resp.read()
if(content):
with open(routeDir, "wb") as code:
code.write(content)
print self.name,self.api," -> Success"
def makedir(self): #文件夹是否已创建
if not os.path.exists("Data/"+self.name+"/"):
os.makedirs('Data/'+self.name+'/')
print self.name," -> Make Dir"
return 1
else:
return 0
#------------------------------------------------------
#===============================================================================
#使用示例-----------------------------------------------
# values={
# "version":"1.0",
# "from":"上海",
# "to":"南京",
# "date":"2016-06-18"
# }
# ID23 = QunarQuery("QunarTrain",values,"23")
# ID23.makedir()
# ID23.getjson()
#===============================================================================
这段代码将需要查询的信息编码到网址中,其中两个本地txt文件中存放的是申请的API Key和各种不同的Web服务的网址。网址在官网都可以查询到,需要注意的是网址后面要手动加上一个“?”。
由于百度地图的API和百度API商店中的API调用略有不同,因此写了两个class,不知道大牛能不能将两个class合并成一个。
在调用不同的服务的时候,输入相关参数,将返回的json文件存储到本地相应景点编号下的文件夹中。
json文件的处理
本文中用到的API返回的都是json格式的文件(如下图)。看起来密密麻麻,作为小白的我当然是没听说过这是个啥啦。一番百度之后,找到了一个很好的在线工具json在线编辑器,可以将json文件重新排版,并且给出文件结构树,相当方便。
格式化之后的文件就好看了许多,可以看到里面包含了两地之间的火车票信息,这是通过qunar的接口获得的数据。
json文件的处理笔者还没有摸索出最优的方法,等搞清楚了再来写一写。
中国的景点分布图
在我之前的尝试中,我试着用Python的Basemap和GeoPy包解析处理这两百多个景点的具体经纬度信息,当然后来发现还是百度地图的API最直接最方便。获取的这些数据直接绘制出来就是下面的效果,图中的深蓝色小点就是中国5A景区所在地:
在通过flight100网站获得全世界所有机场和航线的数据之后,我又将其中中国的数据筛选出来,绘制出了中国的机场和航线分布图。
当然用Basemap只能绘制出简单的静态图,有没有办法让辛辛苦苦获得的这些地理数据发挥更大的用处呢,当然是可以的。
将地理数据导入Google Earth
输出上,你可以将地理位置数据导入到Google Earth中。只是在导入之前还需要一番处理。Google Earth使用的是一种特殊格式的kml文件,你可以在这个网站将可以在Excel中打开的csv格式文件在线转换成kml文件,然后直接拖入Google Earth软件,谷歌地球会帮你自动标注出来,效果就像下面这张图,里面的各个景点都用小图钉标注了出来。
为什么说这些数据能在谷歌地球中发挥更大价值呢?这是因为谷歌地球自带了很多图片。尤其是其中的Panoramio照片,能够将景点周边的真实面貌以高清照片的形式展现出来,让你提前预览美景之后,再决定要不要前往。