刚好本地数据库有各省市县/区的数据,现在打算统计一下各个城市的县区数量,并以热力图在地图上显示。(部分数据如下)
本测试使用的是百度地图,首先先到百度地图开发平台注册一个个人开发账号。然后创建两个用于:服务端和浏览器端,每个应用都会有一个访问应用(AK),属于个人私钥。服务端应用主要用于根据城市读取经纬度,浏览器端应用主要用于地图显示,这两个的应用别搞错。(可能错误:百度未授权使用地图API)
对于经纬度根据地名的提取,接口地址如下:
http://api.map.baidu.com/geocoder/v2/?address=地址&output=json&ak=百度AK
返回结果:
{"status":0,"result":{"location":{"lng":114.0259736573215,"lat":22.546053546205248},"precise":0,"confidence":14,"level":"城市"}}
地图的选择,使用的是热力图,链接地址如下:
http://developer.baidu.com/map/jsdemo.htm#c1_15
地图上有个默认示例,其中“你的密钥”就是 个人的浏览器端AK,页面中还有一个 json 格式的变量 points,存储的是数据,格式如:
var points =[
{"lng":116.418261,"lat":39.921984,"count":50},
{"lng":116.423332,"lat":39.916532,"count":51},
{"lng":116.419787,"lat":39.930658,"count":15},
{"lng":116.42076,"lat":39.915251,"count":70},
{"lng":116.425867,"lat":39.918989,"count":8}];
使用的时候可以把中间这个页面的源码拷贝下来,创建一个 html 的文件,然后更改 ak 及 相关数据,双击打开即可关联到百度地图中显示。不过用python一键操作更方便一点。
再说说地图的显示级别及默认显示位置。因为是全国显示,可以把中心经纬度改为重庆,显示级别改为5。
var point = new BMap.Point(116.418261, 39.921984); #默认定位坐标天安门
map.centerAndZoom(point, 15); //显示级别默认为15
其中,显示级别可以从这确定:
http://api.map.baidu.com/lbsapi/getpoint/index.html
由于用 python 识别读取网页中的代码下载,使用的是 selenium ,selenium 模拟使用浏览器打开网页,脚本设置打开浏览器是在后台执行的。(更多参考:Python selenium 后台运行模拟登录操作)
完整脚本如下:(ak 改用自己的哈)
import os
import re
import time
import json
import sqlalchemy
import webbrowser
import pandas as pd
from selenium import webdriver
from urllib.request import urlopen, quote
centerAndZoom = 5 #地图显示级别(比例)
filepath = "./map.html" # html下载位置
ak_browser = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" #浏览器AK:用于地图显示
ak_server = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" #服务端AK:用于获取经纬度
chromedriver = 'D:/Python35/mypy/phantomjs/bin/phantomjs.exe'
driver = webdriver.PhantomJS(chromedriver)
engine = sqlalchemy.create_engine("mssql+pymssql://kk:kk@HYH0109-189\CAT2014/CSMS3")
#下载百度热力图HTML模板到本地
def download_baidu_map_html():
url = "http://developer.baidu.com/map/jsdemo.htm#c1_15" #热力图模板
driver.get(url)
driver.maximize_window()
time.sleep(2)
#driver.get_screenshot_as_file("./screen.png")
html = driver.find_element_by_xpath("//div[@class='CodeMirror-code']")
with open("./map.html", "w", encoding="utf-8") as f:
f.write(html.text)
f.close
#用自己的数据替换HTML原有的数据
def put_data_into_html(df):
#df.rename(columns={'Longitude': 'lng', 'Latitude': 'lat', 'DistrictCount': 'count'}, inplace=True)
points_json = df[['lng','lat','count']].to_json(orient='records')[1:-1].replace('},{', '},\n{')
f = open(filepath, "r", encoding="utf-8")
lines = f.readlines()
f.close()
contents = ""
for line in lines:
if not re.match("(.+)lng(.+)lat(.+)count(.+)", line):
if re.match("(.+)您的密钥(.+)", line):
line = line.replace('您的密钥', ak_browser) #将ak替换
if re.match("(.+)enableScrollWheelZoom(.+)", line):
#该行"var points =[" 从网页下载时没有,所以手动补全!
line = line + "\n var points =[\n" + points_json + "];\n" #将数据替换
if re.match("(.+)map.centerAndZoom(.+)", line):
line = re.sub("\d+", "%s" % centerAndZoom ,line) #地图级别
contents = contents + line
f2 = open(filepath,"w", encoding="utf-8")
f2.write(contents)
f2.close
#从数据库获取:省、市、市的地区数量
def get_data_from_db():
sql = """SELECT A.Name AS Provicne,B.Name AS City,COUNT(*) AS [count]
FROM SysRegion A
INNER JOIN SysRegion B ON A.Id=B.ParentId AND A.LevelType=1
INNER JOIN SysRegion C ON B.Id=C.ParentId AND B.LevelType=2
GROUP BY A.Name,B.Name"""
df = pd.read_sql_query(sql, engine)
return df
#根据省市从百度地图中获取经纬度
def get_lng_lat_from_baidu(address):
address = quote(address) #中文转码
url = "http://api.map.baidu.com/geocoder/v2/"
url = url + "?address=%s&output=json&ak=%s" %(address,ak_server)
res = json.loads(urlopen(url).read().decode())
return res
#经纬度添加到原 pd 中
def get_df_parse_json(df):
for index, row in df.iterrows():
js = get_lng_lat_from_baidu(row['Provicne']+row['City'])
if (js['status'] == 0):
df.loc[index, 'lng'] = js['result']['location']['lng']
df.loc[index, 'lat'] = js['result']['location']['lat']
return df
def open_html_on_browser():
htmlfile = os.path.abspath(filepath)
htmlfile = 'file:///' + htmlfile.replace("\\","/")
webbrowser.open_new_tab(htmlfile)
if __name__ == "__main__":
df = get_data_from_db() #从数据库获取:省、市、市的地区数量
df = get_df_parse_json(df) #经纬度添加到原 pd 中
download_baidu_map_html() #下载百度热力图HTML模板到本地
put_data_into_html(df) #用自己的数据替换HTML原有的数据
open_html_on_browser() #打开HTML地图预览
结果如上图,当几个地点靠的很近,且数据量加起来较多时,会形成一个重点区域,如图中的红色或绿色区域。