最近看了一份人民大学的报告,《中国城市政商关系排行榜2020》,https://new.qq.com/omn/20201230/20201230A0F3MY00.html。
讲的是中国各个城市的政商关系健康指数,决定拿这份报告中的数据,来做一份html的专题图。
数据是使用python脚本从腾讯地图webapi上获取的,前端页面使用的是高德地图瓦片。
效果如下:
一、数据获取
参考腾讯web api:https://lbs.qq.com/service/webService/webServiceGuide/webServiceDistrict
脚本:
import requests
import time
# 获取省code和省name列表
def getAllProvince(key):
url = 'http://apis.map.qq.com/ws/district/v1/list?key='+key
reponse = requests.get(url=url)
reponse.encoding = 'utf-8'
data = reponse.json()
provincelist = []
for r in data['result'][0]:
provincelist.append(r['id']+'\t'+r['name'])
return provincelist
# 获取省围栏
def getProvincePolygon(key,provinceCode):
url = 'https://apis.map.qq.com/ws/district/v1/search?&keyword='+provinceCode+'&key='+key+'&get_polygon=2&max_offset=3000'
print(url)
reponse = requests.get(url=url)
reponse.encoding = 'utf-8'
data = reponse.json()
print(data)
path = data['result'][0][0]['polygon']
polygonlist = []
# 对响应结果进行差分解压,lng lat,lng lat,lng lat|lng lat……格式
for p in path:
print(p)
ringlist = []
pointnum = int(len(p)/2)
for i in range(0,pointnum):
ringlist.append(str(p[i*2])+' '+str(p[i*2+1]))
polygonlist.append('POLYGON(('+','.join(ringlist)+'))')
return polygonlist
key = '腾讯key'
healthlist = {'北京':86.33,'上海':81.84,'天津':62.73,'海南':51.43,'浙江':49.42,'山东':48.91,
'广东':47.47,'江苏':45,'重庆':44.64,'福建':40.39,'贵州':38.4,'四川':36.74,'安徽':36.52,'广西':34.95,'江西':33.77,
'湖北':31.56,'宁夏':28.82,'湖南':28.03,'辽宁':27.7,'山西':25.73,'内蒙古':25.27,'陕西':23.95,'*':23.94,'甘肃':22.56,'*':21.99,
'青海':21.69,'河北':21.39,'吉林':21.16,'黑龙江':21.08,'河南':20.49,'云南':19.17}
f = open(r'province.txt','a',encoding='utf-8')
f.write('\t'.join(['code','name','health','polygon'])+'\n')
province_list = getAllProvince(key)
for p in province_list:
code,name = p.split('\t')
if healthlist.__contains__(name):
health = healthlist[name]
else:
health = 0.0
time.sleep(1)
polygonlist = getProvincePolygon(key,code)
for pl in polygonlist:
print(pl)
f.write('\t'.join([code,name,str(health),pl])+'\n')
f.close()
执行完脚本,获取的数据长这样:
二、数据转换
因为openlayer能直接识别geojson格式的数据,所以我们把这份数据转成geojson的就行。
打开QGIS,ctrl+L,打开数据源管理器,分隔文本文件,按照下面的设置,将province.txt文件添加到QGIS面板上。
因为openlayer默认的坐标系为EPSG:3857,既平面墨卡托投影坐标系,如果用EPSG:4326,既WGS84球面经纬度坐标系,会导致加载的底图瓦片变形。
所以我们要先将数据做个投影,转成平面的。
点选界面右下角,修改项目坐标系。
将项目坐标系修改为WGS 84/Pseudo-Mercator。
在图层面板上,选中图层province,右键导出,另存要素为。
矢量图层另存为中,将格式选为GeoJSON,坐标系参照系选为项目坐标参照系,点击ok,将文件存为GeoJSON格式。
GeoJSON文件可以拿文本文档打开。
三、前端
json.js:
var getFeatures = function(){
var featureJson=province.geojson中的内容
return featureJson;
}
openlayer_map.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>各省/直辖市政商关系健康指数</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css">
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
<script src="json.js" type="text/javascript"></script>
<style type="text/css">
#map,
html,
body {
height: 100%;
width: 100%;
}
.ol-popup {
position: absolute;
background-color: white;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
min-width: 100px;
}
.ol-popup:after,
.ol-popup:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
.ol-popup-closer {
text-decoration: none;
position: absolute;
top: 2px;
right: 8px;
}
.ol-popup-closer:after {
content: "✖";
}
</style>
</head>
<body>
<div id="map"></div>
<div id="popup" class="ol-popup">
<a href="#" id="popup-closer" class="ol-popup-closer"></a>
<div id="popup-content"></div>
</div>
</body>
<script type="text/javascript">
//弹窗
var container = document.getElementById('popup');
var content = document.getElementById('popup-content');
var closer = document.getElementById('popup-closer');
var overlay = new ol.Overlay({
element: container,
autoPan: true,
autoPanAnimation: {
duration: 50,
},
});
closer.onclick = function() {
overlay.setPosition(undefined);
closer.blur();
return false;
};
var polygons = getFeatures();
var colors = ['#000000', '#001133', '#002266', '#003399', '#0044cc', '#0055ff', '#3377ff', '#6699ff', '#99bbff', '#ccddff', '#ffffff'];
var styleFunction = function(feature) {
let colorHex = colors[10 - Math.round(parseFloat(feature.getProperties()['health']) / 10)];
let colorRgba = 'rgba(' + parseInt('0x' + colorHex.slice(1, 3)) + ',' + parseInt('0x' + colorHex.slice(3, 5)) + ',' +
parseInt('0x' + colorHex.slice(5, 7)) + ',' + 0.8 + ')'
let stylePolygon = new ol.style.Style({
fill: new ol.style.Fill({
color: colorRgba,
}),
});
return stylePolygon;
};
features = new ol.format.GeoJSON().readFeatures(polygons);
var vectorSource = new ol.source.Vector({
features: features,
});
var vectorLayer = new ol.layer.Vector({
source: vectorSource,
style: styleFunction
});
//页面
var view = new ol.View({
center: ol.proj.transform([112.273486, 35.719192], 'EPSG:4326', 'EPSG:3857'),
zoom: 4
});
var layers = [
// 加载高德瓦片底图
new ol.layer.Tile({
source: new ol.source.XYZ({
url: "http://webrd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8"
})
}), vectorLayer
];
//地图
var map = new ol.Map({
target: 'map', //指向div
layers: layers,
overlays: [overlay],
view: view
});
// 单击选择控件
var selectSingleClick = new ol.interaction.Select({ style: styleFunction });
map.addInteraction(selectSingleClick);
selectSingleClick.on('select', function(e) {
content.innerHTML = '<code>' + e.target.getFeatures().getArray()[0].getProperties()['name'] + '为:' + e.target.getFeatures().getArray()[0].getProperties()['health'] + '</code>';
overlay.setPosition(e['mapBrowserEvent']['coordinate']);
});
</script>
</html>
四、总结
使用openlayer的人不多,但它应该是最底层的、最严格符合OGC WMS规范的地图前端了。
用openlayer做专题图是不太合适,但用它做绘制页面工具的开发是挺好的。
Openlayer不够轻量级,上手也难,但用它的时候,会觉得很专业、很标准、很严格。
Openlayer的各种样例:https://openlayers.org/en/latest/examples/