前言
我是一名Python小白喳篇,兩個(gè)禮拜之前還對(duì)Python一無所知迅矛,上次我利用舉辦社團(tuán)活動(dòng)的契機(jī)型宝,請(qǐng)學(xué)校老師做了一次Python入門講座臭挽。聽聞Python功能強(qiáng)大捂襟,對(duì)初學(xué)者也很友好,于是抱著初生牛犢不怕虎的精神欢峰,嘗試嘗試用Python來解決一個(gè)復(fù)雜的難題葬荷。
問題
旅游愛好者小龍有錢又有時(shí)間,她決定制定一個(gè)詳盡的旅行計(jì)劃纽帖,游遍目前中國(guó)的熱門景區(qū)宠漩。目前手邊僅有的資料就是一份想要游覽的景區(qū)的名單(如下圖,總共有兩百多個(gè)景點(diǎn))懊直,其中主要是國(guó)家評(píng)定的5A級(jí)景區(qū)扒吁,制定怎樣的旅行計(jì)劃,才能最高效的游遍所有的景區(qū)呢室囊?
問題解決的思路
首先這是一個(gè)非常具有現(xiàn)實(shí)意義的問題雕崩,它的核心是旅行商問題(Traveling Salesman Problem)魁索,也就是已知地點(diǎn)和地點(diǎn)之間的距離,找出游遍所有地點(diǎn)的最短路線晨逝。這是一個(gè)計(jì)算機(jī)領(lǐng)域的NP完全問題蛾默。TSP問題的核心算法有很多人已經(jīng)研究過了。然而我們要解決的并不是一個(gè)純數(shù)學(xué)的問題捉貌,而是一個(gè)實(shí)際問題支鸡。難點(diǎn)其實(shí)在于數(shù)據(jù)的搜集和整理。
經(jīng)過一番艱難的思索和嘗試趁窃,我列出了下圖所示的程序設(shè)計(jì)思路牧挣。其主要思想在于利用百度提供的免費(fèi)API接口,獲取各個(gè)景點(diǎn)的地理位置醒陆、地址瀑构,附件機(jī)場(chǎng)、車站以及景點(diǎn)之間的路線等各種信息刨摩,再將獲得的json文件進(jìn)一步處理寺晌,得到格式化的數(shù)據(jù)往堡,導(dǎo)入TSP路徑求解器水援,獲得最優(yōu)路徑已慢,再將最優(yōu)路徑在地圖中表示出來营曼,并給出詳細(xì)的形式指南。
這是一個(gè)尚在進(jìn)行中的浩大工程雁芙,尤其對(duì)于一個(gè)Python新手來說肥照。
學(xué)習(xí)Web API的調(diào)用
Web API是一種應(yīng)用程序接口鸠信,通過一定的設(shè)置之后嚷闭,程序可以通過發(fā)送不同網(wǎng)址查詢所需的數(shù)據(jù)攒岛。API的使用需要申請(qǐng)密鑰,本文中使用的到的密鑰均可以免費(fèi)獲取胞锰。
地理服務(wù)相關(guān)的API有不少灾锯,比較成熟的是百度和谷歌兩家。雖然很多時(shí)候谷歌的服務(wù)都顯得高大上許多胜蛉,但是經(jīng)過一番嘗試挠进,在地圖服務(wù),尤其是國(guó)內(nèi)的地圖服務(wù)上誊册,百度有很多優(yōu)勢(shì)。除去連接的穩(wěn)定性之外暖璧,百度的所有Web API共享一個(gè)密鑰案怯,而谷歌需要為不同的API分別申請(qǐng)密鑰,而且百度對(duì)中文支持完善澎办,所以這里主要使用了百度地圖的Web API服務(wù)嘲碱。用戶可以到這個(gè)地址申請(qǐng):http://lbsyun.baidu.com/ 除了地圖之外金砍,百度還有一個(gè)API商店,提供了很多免費(fèi)的API服務(wù)麦锯,網(wǎng)址:http://apistore.baidu.com/
Python程序
調(diào)用Web API的程序其實(shí)相當(dāng)簡(jiǎn)單恕稠,筆者作為一個(gè)小白沒花多長(zhǎng)時(shí)間就搗鼓出了一段代碼。
# -*- 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)火車票鹅巍、景點(diǎn)門票--------------------------------------
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)址中,其中兩個(gè)本地txt文件中存放的是申請(qǐng)的API Key和各種不同的Web服務(wù)的網(wǎng)址料祠。網(wǎng)址在官網(wǎng)都可以查詢到骆捧,需要注意的是網(wǎng)址后面要手動(dòng)加上一個(gè)“?”髓绽。
由于百度地圖的API和百度API商店中的API調(diào)用略有不同敛苇,因此寫了兩個(gè)class,不知道大牛能不能將兩個(gè)class合并成一個(gè)顺呕。
在調(diào)用不同的服務(wù)的時(shí)候枫攀,輸入相關(guān)參數(shù),將返回的json文件存儲(chǔ)到本地相應(yīng)景點(diǎn)編號(hào)下的文件夾中株茶。
json文件的處理
本文中用到的API返回的都是json格式的文件(如下圖)来涨。看起來密密麻麻忌卤,作為小白的我當(dāng)然是沒聽說過這是個(gè)啥啦扫夜。一番百度之后,找到了一個(gè)很好的在線工具json在線編輯器驰徊,可以將json文件重新排版笤闯,并且給出文件結(jié)構(gòu)樹,相當(dāng)方便棍厂。
格式化之后的文件就好看了許多颗味,可以看到里面包含了兩地之間的火車票信息,這是通過qunar的接口獲得的數(shù)據(jù)牺弹。
json文件的處理筆者還沒有摸索出最優(yōu)的方法浦马,等搞清楚了再來寫一寫。
中國(guó)的景點(diǎn)分布圖
在我之前的嘗試中张漂,我試著用Python的Basemap和GeoPy包解析處理這兩百多個(gè)景點(diǎn)的具體經(jīng)緯度信息晶默,當(dāng)然后來發(fā)現(xiàn)還是百度地圖的API最直接最方便。獲取的這些數(shù)據(jù)直接繪制出來就是下面的效果航攒,圖中的深藍(lán)色小點(diǎn)就是中國(guó)5A景區(qū)所在地:
在通過flight100網(wǎng)站獲得全世界所有機(jī)場(chǎng)和航線的數(shù)據(jù)之后磺陡,我又將其中中國(guó)的數(shù)據(jù)篩選出來,繪制出了中國(guó)的機(jī)場(chǎng)和航線分布圖。
當(dāng)然用Basemap只能繪制出簡(jiǎn)單的靜態(tài)圖币他,有沒有辦法讓辛辛苦苦獲得的這些地理數(shù)據(jù)發(fā)揮更大的用處呢坞靶,當(dāng)然是可以的。
將地理數(shù)據(jù)導(dǎo)入Google Earth
輸出上蝴悉,你可以將地理位置數(shù)據(jù)導(dǎo)入到Google Earth中彰阴。只是在導(dǎo)入之前還需要一番處理。Google Earth使用的是一種特殊格式的kml文件拍冠,你可以在這個(gè)網(wǎng)站將可以在Excel中打開的csv格式文件在線轉(zhuǎn)換成kml文件尿这,然后直接拖入Google Earth軟件,谷歌地球會(huì)幫你自動(dòng)標(biāo)注出來倦微,效果就像下面這張圖妻味,里面的各個(gè)景點(diǎn)都用小圖釘標(biāo)注了出來。
為什么說這些數(shù)據(jù)能在谷歌地球中發(fā)揮更大價(jià)值呢欣福?這是因?yàn)楣雀璧厍蜃詭Я撕芏鄨D片责球。尤其是其中的Panoramio照片,能夠?qū)⒕包c(diǎn)周邊的真實(shí)面貌以高清照片的形式展現(xiàn)出來拓劝,讓你提前預(yù)覽美景之后雏逾,再?zèng)Q定要不要前往。