openlayer制作专题图

 

最近看了一份人民大学的报告,《中国城市政商关系排行榜2020》,https://new.qq.com/omn/20201230/20201230A0F3MY00.html。

讲的是中国各个城市的政商关系健康指数,决定拿这份报告中的数据,来做一份html的专题图。

数据是使用python脚本从腾讯地图webapi上获取的,前端页面使用的是高德地图瓦片。

效果如下:

openlayer制作专题图

 

一、数据获取

参考腾讯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制作专题图

 

二、数据转换

因为openlayer能直接识别geojson格式的数据,所以我们把这份数据转成geojson的就行。

打开QGIS,ctrl+L,打开数据源管理器,分隔文本文件,按照下面的设置,将province.txt文件添加到QGIS面板上。

openlayer制作专题图

 

因为openlayer默认的坐标系为EPSG:3857,既平面墨卡托投影坐标系,如果用EPSG:4326,既WGS84球面经纬度坐标系,会导致加载的底图瓦片变形。

所以我们要先将数据做个投影,转成平面的。

点选界面右下角,修改项目坐标系。

openlayer制作专题图

 

将项目坐标系修改为WGS 84/Pseudo-Mercator。

openlayer制作专题图

 

在图层面板上,选中图层province,右键导出,另存要素为。

openlayer制作专题图

 

 

矢量图层另存为中,将格式选为GeoJSON,坐标系参照系选为项目坐标参照系,点击ok,将文件存为GeoJSON格式。

openlayer制作专题图

 

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/

 

 

上一篇:WebGIS学习笔记(二)OpenLayer+geoserver+jquery实现时间滑块控制各个朝代图层的显示


下一篇:OpenLayer学习笔记——第一天