前言
我是一名Python小白,兩個禮拜之前還對Python一無所知,上次我利用舉辦社團活動的契機,請學(xué)校老師做了一次Python入門講座。聽聞Python功能強大,對初學(xué)者也很友好,于是抱著初生牛犢不怕虎的精神,嘗試嘗試用Python來解決一個復(fù)雜的難題。
問題
旅游愛好者小龍有錢又有時間,她決定制定一個詳盡的旅行計劃,游遍目前中國的熱門景區(qū)。目前手邊僅有的資料就是一份想要游覽的景區(qū)的名單(如下圖,總共有兩百多個景點),其中主要是國家評定的5A級景區(qū),制定怎樣的旅行計劃,才能最高效的游遍所有的景區(qū)呢?

問題解決的思路
首先這是一個非常具有現(xiàn)實意義的問題,它的核心是旅行商問題(Traveling Salesman Problem),也就是已知地點和地點之間的距離,找出游遍所有地點的最短路線。這是一個計算機領(lǐng)域的NP完全問題。TSP問題的核心算法有很多人已經(jīng)研究過了。然而我們要解決的并不是一個純數(shù)學(xué)的問題,而是一個實際問題。難點其實在于數(shù)據(jù)的搜集和整理。
經(jīng)過一番艱難的思索和嘗試,我列出了下圖所示的程序設(shè)計思路。其主要思想在于利用百度提供的免費API接口,獲取各個景點的地理位置、地址,附件機場、車站以及景點之間的路線等各種信息,再將獲得的json文件進一步處理,得到格式化的數(shù)據(jù),導(dǎo)入TSP路徑求解器,獲得最優(yōu)路徑,再將最優(yōu)路徑在地圖中表示出來,并給出詳細的形式指南。

這是一個尚在進行中的浩大工程,尤其對于一個Python新手來說。
學(xué)習(xí)Web API的調(diào)用
Web API是一種應(yīng)用程序接口,通過一定的設(shè)置之后,程序可以通過發(fā)送不同網(wǎng)址查詢所需的數(shù)據(jù)。API的使用需要申請密鑰,本文中使用的到的密鑰均可以免費獲取。
地理服務(wù)相關(guān)的API有不少,比較成熟的是百度和谷歌兩家。雖然很多時候谷歌的服務(wù)都顯得高大上許多,但是經(jīng)過一番嘗試,在地圖服務(wù),尤其是國內(nèi)的地圖服務(wù)上,百度有很多優(yōu)勢。除去連接的穩(wěn)定性之外,百度的所有Web API共享一個密鑰,而谷歌需要為不同的API分別申請密鑰,而且百度對中文支持完善,所以這里主要使用了百度地圖的Web API服務(wù)。用戶可以到這個地址申請:http://lbsyun.baidu.com/ 除了地圖之外,百度還有一個API商店,提供了很多免費的API服務(wù),網(wǎng)址:http://apistore.baidu.com/
Python程序
調(diào)用Web API的程序其實相當(dāng)簡單,筆者作為一個小白沒花多長時間就搗鼓出了一段代碼。
# -*- coding: utf-8 -*-
'''
Created on 17-June-2016 @Jerry
用于調(diào)用百度地圖和去哪兒網(wǎng)的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 #調(diào)用的API名稱
self.args = args #查詢變量
self.name = name #查詢結(jié)果的命名方式
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): #文件夾是否已創(chuàng)建
if not os.path.exists("Data/"+self.name+"/"):
os.makedirs('Data/'+self.name+'/')
print "Make Dir Successfully!"
return 1
else:
return 0
#------------------------------------------------------
#===============================================================================
#查詢百度API商店中去哪兒網(wǎng)火車票、景點門票--------------------------------------
class QunarQuery:
def __init__(self,api,args,name):
self.api = api #調(diào)用的API名稱
self.args = args #查詢變量
self.name = name #查詢結(jié)果的命名方式
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): #文件夾是否已創(chuàng)建
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()
#===============================================================================
這段代碼將需要查詢的信息編碼到網(wǎng)址中,其中兩個本地txt文件中存放的是申請的API Key和各種不同的Web服務(wù)的網(wǎng)址。網(wǎng)址在官網(wǎng)都可以查詢到,需要注意的是網(wǎng)址后面要手動加上一個“?”。
由于百度地圖的API和百度API商店中的API調(diào)用略有不同,因此寫了兩個class,不知道大牛能不能將兩個class合并成一個。
在調(diào)用不同的服務(wù)的時候,輸入相關(guān)參數(shù),將返回的json文件存儲到本地相應(yīng)景點編號下的文件夾中。
json文件的處理
本文中用到的API返回的都是json格式的文件(如下圖)。看起來密密麻麻,作為小白的我當(dāng)然是沒聽說過這是個啥啦。一番百度之后,找到了一個很好的在線工具json在線編輯器,可以將json文件重新排版,并且給出文件結(jié)構(gòu)樹,相當(dāng)方便。

格式化之后的文件就好看了許多,可以看到里面包含了兩地之間的火車票信息,這是通過qunar的接口獲得的數(shù)據(jù)。

json文件的處理筆者還沒有摸索出最優(yōu)的方法,等搞清楚了再來寫一寫。
中國的景點分布圖
在我之前的嘗試中,我試著用Python的Basemap和GeoPy包解析處理這兩百多個景點的具體經(jīng)緯度信息,當(dāng)然后來發(fā)現(xiàn)還是百度地圖的API最直接最方便。獲取的這些數(shù)據(jù)直接繪制出來就是下面的效果,圖中的深藍色小點就是中國5A景區(qū)所在地:

在通過flight100網(wǎng)站獲得全世界所有機場和航線的數(shù)據(jù)之后,我又將其中中國的數(shù)據(jù)篩選出來,繪制出了中國的機場和航線分布圖。

當(dāng)然用Basemap只能繪制出簡單的靜態(tài)圖,有沒有辦法讓辛辛苦苦獲得的這些地理數(shù)據(jù)發(fā)揮更大的用處呢,當(dāng)然是可以的。
將地理數(shù)據(jù)導(dǎo)入Google Earth
輸出上,你可以將地理位置數(shù)據(jù)導(dǎo)入到Google Earth中。只是在導(dǎo)入之前還需要一番處理。Google Earth使用的是一種特殊格式的kml文件,你可以在這個網(wǎng)站將可以在Excel中打開的csv格式文件在線轉(zhuǎn)換成kml文件,然后直接拖入Google Earth軟件,谷歌地球會幫你自動標注出來,效果就像下面這張圖,里面的各個景點都用小圖釘標注了出來。

為什么說這些數(shù)據(jù)能在谷歌地球中發(fā)揮更大價值呢?這是因為谷歌地球自帶了很多圖片。尤其是其中的Panoramio照片,能夠?qū)⒕包c周邊的真實面貌以高清照片的形式展現(xiàn)出來,讓你提前預(yù)覽美景之后,再決定要不要前往。
