P0 事出有因
最近与夫人聊天时,夫人再次表达了她对即将登陆澳洲的强烈期盼。虽然我再三表示对一家人喝西北风的担忧,但我也知道,这种担忧根本挡不住一个自由的灵魂。由于听说猫本租房还是不太好找的,所以我打算提前做功课。
作为civil出身的工科男,我第一个关注的就是住所周边的公共设施分布;而作为Python的业余拥趸,我首先想到的就是用代码解决问题(毛毛……分明一上来先用的Excel表……)。
P1 画个草图
要找房子最好就是看周边设施齐全程度啦,最初我考虑用表格以此统计每个火车站点周边的基础设施情况,但无奈工作量实在太大,倒不如画个地图看看?
我的大体思路就是搜索一下各类设施,设定一个覆盖半径(从家走到该设施的距离),然后找那些离各个设施都比较方便的地方,那就是理想的住所啦~
说起画地图,我一开始想起的是前几天去Datathon时研究的plotly,这个库的可视化功能似乎非常强大,需要注册账号,画完的图直接在他们的网站上你的账户里,有兴趣的可以搜素尝试一下。但这个东西最蛋疼的地方在于,其地图功能里画的圈的size是基于像素点的,而无法基于实际距离(貌似官方没有做这个功能),也就是说,当我们缩放地图时,图上的圈看上去是不变的……除此之外,地图上也没有交通信息。好吧,只能放弃另找他家。
后来我找到了gmplot,这才是真正的救星~画出来的地图以html格式保存在本地,而且是Google地图的形式哦~
通过命令行pip install gmplot安装完gmplot之后,首先,最简单的功能,显示地图:
from gmplot import gmplot
import os
os.chdir('G:/MapRent')#指定地图保存的路径
gmap = gmplot.GoogleMapPlotter(-37.846388,144.991851, 13)#指明初始中心坐标点和缩放值
gmap.draw('./my_map.html')#指定地图文件的名字
随后我们就能在我们指定的路径下找到my_map.html,打开之后就是一张墨尔本的基本地图,和我们在Google Map上看到的一模一样,图上我们暂时还没加任何东西。代码中的-37.846388,144.991851是个经纬度坐标,是我们打开这个地图文件时的初始坐标(我选了Burnley车站的坐标),其实并没有什么讲究,因为我们之后可以像使用Google Map时一样移动和缩放地图。坐标后面的13代表地图初始的缩放值,这个值越小,你能看到的区域越大也越粗略;反之,这个值越大,你能看到的区域越小也越详细。听说东南区比较好,我们就暂时把视野定在东南区(CBD太贵啦)。
好了,现在我们来做些有意义的事情,比如在地图上画个半径200m的圈。我们只需要在gmap.draw这句语句之前加两句话就行。
new_lats, new_lons = zip(*[(-37.827675, 145.007736)])#设定要画的点
gmap.scatter(new_lats, new_lons, '#0000FF',size=200, marker=False)#画圈操作
第一句话用于设定数据,我们这里只用了一个点,当然,我们可以用很多个只要在[]内加入我们要画的点(x1, y1), (x2, y2), (x3, y3), …, (xn, yn) 这样就行。第二句话是实际的画圈操作,括号内的参数分别对应传入纬度、经度、颜色、圈的大小和是否显示圈,很诡异的是,此处marker=False为显示,marker=True为不显示……
按照上面说的,我们把Burnley车站经纬度new_lats, new_lons传入gmap.scatter函数,'#0000FF'为16进制RGB值,代表蓝色,size为200即200m,然后我们可以看一下效果,一个半透明的固定半径的圈(放心,我用Google Map量过,是200m),效果不错啊~
我们放大看:
我们再试试把第一句换成三个点:
new_lats, new_lons=zip(*[(-37.827675,145.007736),
(-37.826354,144.997239),
(-37.821951,145.023199)])
这三个分别是Burnley, East Richmond和Hawthorn车站,其他不变,看看效果。
我们如愿以偿地看到了三个篮圈,接下来我们再做一件事情,我们已经有了200m的范围圈,我觉得我也可以接受500m甚至1km(步行约15分钟),我们只需要更改第二句:
gmap.scatter(lats_wool, lons_wool,'#0000FF', size=200, marker=False)
gmap.scatter(lats_wool, lons_wool,'#3333FF', size=500, marker=False)
gmap.scatter(lats_wool, lons_wool,'#9999FF', size=1000, marker=False)
三句话分别对应200m,500m和1000m,颜色参数我依次增加了R和G的值,使蓝色变浅(其实还有隐藏的参数可以直接改alpha通道,从而改变透明度),我们再运行一下:
哇~这确实就是我想要的效果,我们发现原来Burnley和East Richmond两站之间只有1km左右的路程……通过上述方法,我们可以加入各种不同的要素:
现在我们已经知道如何显示效果了,那么接下来我们最需要解决的就是数据问题了。刚才图上那些点的坐标都是我手动点出来的,但你要我逐个去找超市、诊所、银行什么的那不简直要我命吗……我们当然希望有更简便的方法通过少量工作就能获取大量数据,这时候我们就要借助Google Map的API了。
P2 来点数据
Google Map API的使用方法可以参加以下这篇博文:
作者写的非常详细了,我的代码也是在他的基础上修改的。这里说明一点,使用Google的API是需要注册的,可以免费试用1年,注册后可以拿到API密钥,这个是使用后续功能所必需的。
按照我所设想的内容,我们只需要安装GooglePlaces这个库就可以了,然后此处我们的代码主要做两件事:1、搜索我们需要的信息(比如超市)并返回信息;2、从返回的信息中提取经纬度坐标。我们先做一下准备工作,然后用两个函数分别来完成我们所要做的事情:
from googleplaces import GooglePlaces
from time import sleep #后续有妙用
api_key = "********************"#密钥当然不能告诉别人啦
GGP = GooglePlaces(api_key)#实例化一个GooglePlaces类
首先是搜索字符串的函数:
def search_text(query = None, language = None, radius = 13000, location = None, pagetoken = None):
'''
根据搜索字符串,请求google API传回推荐的列表
:query: 搜索字符串
:language: 语言
:radius: 搜索半径
:location: 地区筛选
:pagetoken: 用于结果翻页用,可以返回60个,否则最多只返回20个结果
'''
text_query_result = GGP.text_search(query = query, language = language, radius = radius, location = location, pagetoken = pagetoken)
query_result_list = [text_query_result]
while text_query_result.has_next_page_token:
sleep(2)#一定要sleep,否则request反应不过来会报错
text_query_result = GGP.text_search(pagetoken = text_query_result.next_page_token)
query_result_list.append(text_query_result)
return query_result_list
我们默认采用13km搜索半径(为什么?没有为什么,随手定的……),这里写了一个循环,这是因为GooglePlaces的搜索默认返回20个结果,如果不手动翻页的话就只给你20个,当然,写了循环也只能达到60个,经查证这是调用API的上限值。这里要注意的是,如果不加2秒钟的sleep,循环就会报错,因为next_page_token这个玩意儿要反应一段时间,如果太快就反应不过来,就会出现INVALID_REQUEST,蛋疼吧……我们把返回的结果都放到一个list里,留着一会儿用。
第二个函数就是从返回的结果中提取我们所需要的坐标:
def extract_coord(res_list):
'''
从search_text返回的结果中提取坐标点
'''
res = []
for i in res_list:
res.extend(i.raw_response['results'])
coord = []
for j in range(len(res)):
lat = res[j]['geometry']['location']['lat']
lon = res[j]['geometry']['location']['lng']
coord.append((float(lat), float(lon)))#lat与lon为decimal类型,转成浮点数
return coord
刚才我们把结果都放在了一个list里,这里我们先用一个循环把list里的东西拿出来,拿出来之后通过.raw_response能得到一个字典,字典中'results'键对应的内容就是每个搜索结果的详细信息,包括地址、门牌号、坐标等等。我们再从'results'中抽丝剥茧地把我们需要的经纬度坐标提取出来,放在一个list里就好了,这个list可以用来画图。
P3 组合起来
现在我们把所有的代码组合起来:
#################### 获取数据 ####################
from googleplaces import GooglePlaces
from time import sleep
api_key = "********************"
GGP = GooglePlaces(api_key)
def search_text(query = None, language = None, radius = 13000, location = None, pagetoken = None):
'''
根据搜索字符串,请求google API传回推荐的列表
:query: 搜索字符串
:language: 语言
:radius: 搜索半径
:location: 地区筛选
:pagetoken: 用于结果翻页用,可以返回60个,否则最多只返回20个结果
'''
text_query_result = GGP.text_search(query = query, language = language, radius = radius, location = location, pagetoken = pagetoken)
query_result_list = [text_query_result]
while text_query_result.has_next_page_token:
sleep(2)#一定要sleep,否则request反应不过来会报错
text_query_result = GGP.text_search(pagetoken = text_query_result.next_page_token)
query_result_list.append(text_query_result)
return query_result_list
def extract_coord(res_list):
'''
从search_text返回的结果中提取坐标点
'''
res = []
for i in res_list:
res.extend(i.raw_response['results'])
coord = []
for j in range(len(res)):
lat = res[j]['geometry']['location']['lat']
lon = res[j]['geometry']['location']['lng']
coord.append((float(lat), float(lon)))
return coord
#################### 绘制地图 ####################
from gmplot import gmplot
import os
os.chdir('G:/MapRent')
#Burnley = (-37.827675, 145.007736)
#Clayton = (-37.924437, 145.120116)
gmap = gmplot.GoogleMapPlotter(-37.827675,145.007736, 13)#指定初始坐标点与zoom
wool_color = '#0000FF'#指定颜色
wool_lats,wool_lons = zip(*extract_coord(search_text('woolworths', location = '-37.924437,145.120116')))
#以Clayton为中心搜索woolworths
gmap.scatter(wool_lats, wool_lons,wool_color, size = 200, marker = False, edge_alpha = 0.50, face_alpha = 0.20)
gmap.scatter(wool_lats, wool_lons,wool_color, size = 500, marker = False, edge_alpha = 0.45, face_alpha = 0.15)
gmap.scatter(wool_lats, wool_lons,wool_color, size=1000, marker=False, edge_alpha = 0.40, face_alpha = 0.10)#范围逐渐增大,颜色逐渐淡化
gmap.draw('./my_map2.html')
这里我们使用了隐藏的透明度的参数,edge_alpha为圆圈边缘线条的透明度,而face_alpha为圆圈内部填充的透明度。此外,搜索信息所使用的location参数可以是字符串,比如‘Melbourne’,也可以是坐标文本,如'-37.924437,145.120116'。我们可以分别看一下效果:
最后我们再优化一下,把查询并画图的功能做在一个函数里:
(即gmap = gmplot.GoogleMapPlotter(-37.827675,145.007736, 13)之后的所有语句)
def draw_sth(search = None, location = '-37.924437, 145.120116', color = '#000000'):
lats, lons = zip(*extract_coord(search_text(search, location = location)))
gmap.scatter(lats, lons, color, size = 200, marker = False, edge_alpha = 0.50, face_alpha = 0.20)
gmap.scatter(lats, lons, color, size = 500, marker = False, edge_alpha = 0.45, face_alpha = 0.15)
gmap.scatter(lats, lons, color, size = 1000, marker = False,edge_alpha = 0.40, face_alpha = 0.10)
gmap.draw('./my_map_new.html')
我们之后只要反复调用这个函数就能画各种点了。
P4 试验时间
好了,现在我们来看看最终的效果如何,我们挑选了几个要素:首先woolworths和coles超市画蓝圈,Commonwealth和ANZ银行画灰色圈,GP诊所画红圈:
#################### 来一发! ####################
if __name__ == '__main__':
draw_sth('woolworths', color='#0055FF')
draw_sth('coles', color='#0055FF')
draw_sth('commonwealth bank', color='#222222')
draw_sth('ANZ', color='#222222')
draw_sth('GP', color='#FF0000')
哇~眼睛都花了……看来我们急需一个美工啊~不过大致可以看一下哪些地方基建比较方便咯,我们来举些栗子:
1、Camberwell车站附近(啥都看不清……),超市、银行、GP一应俱全,听说租金也很贵呢哈哈哈……
2、Blackburn,三个中心及与车站的距离都不超过1km,还不错哦~
3、Malvern、Carnegie、Oakleigh,两个靠近Monash大学,一个貌似是富人区。
4、Clayton与Springvale,同样靠近Monash的另一个校区,虽然稍微远了点,但有宜家,Springvale据说还有能买到活鱼的生鲜市场哦~
P5 一些后话
我兴高采烈地把成果发给夫人看,夫人欣慰地表示你搞这些有个蛋用,随后又安慰我说你这么帅说什么都对。虽然她对我的成果并不感冒,但毕竟承认我帅比什么都重要。
有兴趣的小伙伴欢迎随时指教~
领取专属 10元无门槛券
私享最新 技术干货