前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高质量编码-空气质量地图可视化

高质量编码-空气质量地图可视化

原创
作者头像
MiaoGIS
发布2022-01-07 15:24:41
5681
发布2022-01-07 15:24:41
举报
文章被收录于专栏:Python in AI-IOTPython in AI-IOT

平时采用百度地图api来开发地图可视化,除了使用JavaScript API GL来开发地图可视化应用。也可以使用百度地图提供的MapVGL库来开发。使用JavaScript API GL时利用overlayer(label,marker,line,polygon等)来完成地图叠加层添加实现可视化,MapVGL由于采用了图层(layer)来群组管理叠加层,从而代码更简洁,开发更高效。

百度地图JavaScript API GL v1.0是一套由JavaScript语言编写的应用程序接口,可帮助您在网站中构建功能丰富、交互性强的地图应用,支持PC端和移动端基于浏览器的地图应用开发,且支持HTML5特性的地图开发。

MapVGL,是一款基于WebGL的地理信息可视化库,可以用来展示大量基于3D的地理信息点线面数据。设计初衷主要是为了解决大数据量的三维地理数据展示问题及一些炫酷的三维效果。

下面我们采用MapVGL来实现全国空气质量的地图可视化效果。

下面介绍其前端代码实现:

html代码如下:

代码语言:html
复制
 <!DOCTYPE html>
 <html lang="zh-CN">
 <head>
    <meta charset="utf-8">
    <title>MapVGL</title>
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <link href="/static/css/loader.css" rel="stylesheet">
    <style>
        .loader {
            width: 200px;
            height: 200px;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;

            position: fixed;
            z-index: 99999;
            text-align: revert;
            margin: auto;
        }

        html,
        body {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            background: #000;
 
        }

        #map_container {
            width: 100%;
            height: 100%;
            margin: 0;
        }

        .map_grade {
            position: fixed;
            left: 0;
            bottom: 0;
            width: 252px;
            z-index: 1000;
        }

        .map_grade .map_shadowColor {
            display: inline-block;
            width: 100%;
            height: 8px;
            margin-bottom: -5px;
            background-image: linear-gradient(90deg, #43ce17 15%, #e1d72c 22%, #f72d0e 75%, #a7134c 90%);
        }

        .map_grade .map_leveltip {
            color: #fff;
            font-size: 12px;
            border-collapse: collapse;
            border-spacing: 0;
            z-index: 1001;
            background: rgba(0, 0, 0, .56);
        }

        .map_grade .map_leveltip td {
            display: inline-block;
            width: 42px;
            height: 44px;
            text-align: center;
            vertical-align: middle;
            line-height: 40px;
        }

        .map_grade .map_leveltip .map_level {
            background: transparent;
        }


        #mapgroup .radio-button-wrapper {
            width: 80px;
            padding: 0 !important;
            text-align: center;
            border: 0 !important;
            background: rgba(0, 0, 0, .6) !important;
            color: #fff !important;
            border-radius: 0 !important;
        }

        .mapgroup {
            position: fixed;
            right: 0;
            top: 23%;
            z-index: 999;
            width: 80px;
        }

        .radio-group {
            width: 80px;
            display: grid;
            cursor: pointer;
        }

        .white .radio-button,
        .radio-button {
            background-color: white;
            color: #000;

            line-height: 40px;
            text-align: center;

            text-decoration: none;
            border: 1px solid black;
            border-radius: 10px;
            caret-color: transparent;
        }

        .dark .radio-button {
            background-color: black;
            color: #fff;

            line-height: 40px;
            text-align: center;

            text-decoration: none;
            border: 1px solid white;
            border-radius: 10px;
            caret-color: transparent;
        }

        .radio-button-checked,
        .white .radio-button-checked,
        .dark .radio-button-checked {
            background-color: #337ab7;
            color: #fff;
            caret-color: transparent;
        }

        .timetip {
            position: fixed;
            left: 51px;
            top: 66px;
            z-index: 401;
            padding: 3px 10px;
            font-size: 15px;
            background: rgba(0, 0, 0, .56);
            color: #fff;
            border-radius: 4px;
        }
    </style>
    <script src="//api.map.baidu.com/api?v=2.0&type=webgl&ak=your baidu map ak"></script>
    <script src="../static/js/common.js"></script>
    <script src="//mapv.baidu.com/build/mapv.min.js"></script>
    <script src="https://code.bdstatic.com/npm/mapvgl@1.0.0-beta.139/dist/mapvgl.min.js"></script>

</head>

<body>

    <div class="loader"></div>

    <div id="map_container"></div>
    <div id="mapgroup" class="mapgroup" contenteditable="false">
        <div class="radio-group"><a class="radio-button radio-button-checked" data-factor="aqi" onclick="javascript:void(0);"><span class="radio-button-inner">AQI</span></a><a class="radio-button" data-factor="pm2_5" onclick="javascript:void(0);"><span class="radio-button-inner">PM2.5</span></a><a class="radio-button" data-factor="pm10" onclick="javascript:void(0);"><span class="radio-button-inner">PM10</span></a> <a class="radio-button" onclick="javascript:void(0);" data-factor="so2"><span class="radio-button-inner">SO2</span></a> <a class="radio-button" onclick="javascript:void(0);" data-factor="no2"><span class="radio-button-inner">NO2</span></a> <a onclick="javascript:void(0);" class="radio-button" data-factor="co"><span class="radio-button-inner">CO</span></a> <a class="radio-button" onclick="javascript:void(0);" data-factor="o3"><span class="radio-button-inner">O3</span></a> </div>
        <div class="radio-group"><a id="toggleLabel" data-label="name" onclick="javascript:void(0);" class="radio-button"><span class="radio-button-inner" style="color: magenta;">显数值</span> </a><a id="toggleStyle" class="radio-button" onclick="javascript:void(0);"><span class="radio-button-inner" style="color: orange;">暗夜系</span> </a> </div>
    </div>

    <div class="map_grade"><canvas class="map_shadowColor"></canvas>
        <table class="map_leveltip">
            <tbody>
                <tr style="white-space: nowrap;">
                    <td class="map_level">优</td>
                    <td class="map_level">良</td>
                    <td class="map_level">轻度</td>
                    <td class="map_level">中度</td>
                    <td class="map_level">重度</td>
                    <td class="map_level">严重</td>
                </tr>
            </tbody>
        </table>
    </div>
    <div class="timetip">
        <div id="timetip"></div>
        <div style="display: none;">数据源暂未更新数据!</div>
    </div>

    <script src="/static/lib/jquery/jquery-1.11.3.js"></script>
    <script src="/static/lib/underscore/underscore.js"></script>
    <script src="/static/lib/moment/moment.min.js"></script>
    <script src="/static/lib/moment/zh-cn.js"></script>
    <script src="/static/lib/crypto/crypto.js"></script>
    
    <script src="/static/js/mapv.js"></script>
</body></html>

common.js定义了地图的风格样式以及初始化地图函数

代码语言:javascript
复制
/**
 * 初始化地图
 */

/* global BMapGL */
/* global darkStyle */

function initMap(options) {
    options = Object.assign({
        tilt: 60,
        heading: 0
    }, options);
    var map = new BMapGL.Map('map_container', {
        restrictCenter: false,
        style: {styleJson: options.style || darkStyle }
    });
    map.enableKeyboard();
    map.enableScrollWheelZoom();
    map.enableInertialDragging();
    map.enableContinuousZoom();

    map.setDisplayOptions(options.displayOptions || {
            indoor: false,
            poi: true,
            skyColors: options.skyColors || [
                'rgba(5, 5, 30, 0.01)',
                'rgba(5, 5, 30, 1.0)'
            ]
        });
    if (options.center && options.zoom) {
        map.centerAndZoom(new BMapGL.Point(options.center[0], options.center[1]), options.zoom);
    }

    map.setTilt(options.tilt);
    map.setHeading(options.heading);
    return map;
}



var darkStyle = [{
    featureType: 'background',
    elementType: 'geometry',
    stylers: {
        color: '#070c17ff'
    }
},  ......
];

var whiteStyle = [{
    featureType: 'water',
    elementType: 'geometry',
    stylers: {
        visibility: 'on',
        color: '#ccd6d7ff'
    }
}, ......];

核心js代码mapv.js如下:

代码语言:javascript
复制
 
args=_.object(window.location.search.replace('?', '').split('&').map(x => x.split('=')));
 
var whiteBox=[
                    // 地面颜色
                    'rgba(226, 237, 248, 0)',
                    // 天空颜色
                    'rgba(186, 211, 252, 1)'
                ]; 
function initMapV() {
    var options = {
        tilt: 56,
        heading: 0.3,
        center: [113, 34],
        zoom: 9,
        style:whiteStyle,  
        skyColors:whiteBox

    };
    var hour=(new Date).getHours();
    if(hour>18||hour<6){
        options['style'] = darkStyle;
        delete options['skyColors'];
        $('body').addClass('dark');
        $('#toggleStyle span').text('白日风');
        
    }
    
    if (args['style'] == 'white') {
        
        options['style'] = whiteStyle;
        options['skyColors'] = whiteBox;
        $('body').removeClass('dark').addClass('white');
        $('#toggleStyle span').text('暗夜风');
    } else if(args['style'] == 'dark'){
        options['style'] = darkStyle;
        delete options['skyColors'];
        $('body').addClass('dark');
        $('#toggleStyle span').text('白日风');
    }
    map = initMap(options);


    points_station = [];
    points_city = [];
    texts_city = [];
    texts_station = [];


    citys.filter(x => x['stations']).forEach(x => {

        var cityCenter = mapv.utilCityCenter.getCenterByCityName(x['name']);
        var point_city = {
            geometry: {
                type: 'Point',
                coordinates: [cityCenter.lng, cityCenter.lat]
            },
            properties: {
                name: x['name']
            }
        };
        var text_city = {
            geometry: {
                type: 'Point',
                coordinates: [cityCenter.lng, cityCenter.lat]
            },
            properties: {
                name: x['name'],
                text: x.name,
                textColor: '#000',

                backgroundColor: '#fff',
            },
        };
        points_city.push(point_city);
        texts_city.push(text_city);
        x['stations'].forEach(y => {


            var point_station = {
                geometry: {
                    type: 'Point',
                    coordinates: [y.lng, y.lat]
                },
                properties: {
                    name: y.name,
                }
            };
            var text_station = {
                geometry: {
                    type: 'Point',
                    coordinates: [y.lng, y.lat],
                },
                properties: {
                    name: y.name,
                    text: y.name,
                    textColor: '#000',

                    backgroundColor: '#fff',
                },
            };

            points_station.push(point_station);
            texts_station.push(text_station);
        });

    });

    view = new mapvgl.View({
        
        map: map
    });

    cityLayer = new mapvgl.PointLayer({
        blend: 'lighter',
        size: 15,
        color: 'rgba(227, 29, 120, 0.6)'
    });

    view.addLayer(cityLayer);
    cityLayer.setData(points_city);
    view.hideLayer(cityLayer);
    stationLayer = new mapvgl.PointLayer({
        blend: 'lighter',
        size: 15,
        color: 'rgba(102, 0, 204, 0.6)'
    });

    stationLayer.setData(points_station);
    view.addLayer(stationLayer);
    
    stationTextLayer = new mapvgl.LabelLayer({
        textAlign: 'center',

        borderColor: '#fff',
        borderWidth: 0,
        padding: [2, 5],
        borderRadius: 5,
        fontSize: 12,
        lineHeight: 16,
        collides: true, // 是否开启碰撞检测, 数量较多时建议打开
        enablePicked: true,
        onClick: e => {
            // 点击事件
            console.log('click', e);
        },
    });
    stationTextLayer.setData(texts_station);
    view.addLayer(stationTextLayer);
    
    cityTextLayer = new mapvgl.LabelLayer({
        textAlign: 'center',
        borderColor: '#fff',
        borderWidth: 0,

        padding: [2, 5],
        borderRadius: 5,
        fontSize: 12,
        lineHeight: 16,
        collides: true, // 是否开启碰撞检测, 数量较多时建议打开
        enablePicked: true,
        onClick: e => {
            // 点击事件
            console.log('click', e);
        },
    });

    cityTextLayer.setData(texts_city);
    view.addLayer(cityTextLayer);
    view.hideLayer(cityTextLayer);
    map.on('zoomend', function () {
        var currentZoom = map.getZoom();
        if (currentZoom > toggleZoom) {
            view.showLayer(stationLayer);
            view.showLayer(stationTextLayer);
            view.hideLayer(cityLayer);
            view.hideLayer(cityTextLayer);
        } else {
            view.showLayer(cityLayer);
            view.showLayer(cityTextLayer);
            view.hideLayer(stationLayer);
            view.hideLayer(stationTextLayer);
        }
    });
}
var b = new Base64();
toggleZoom = 10;
async function loaddata(time) {

    toggleZoom = 8;
    method = "GETREALTIMEMAP";
    var points = [];

    var obj = {
        type: 'aqi',
    };
    var param = getParam(method, obj);
    return $.ajax({
        type: "post",
        url: "/api",
        data: {
            param: param
        },
        success: function (data) {

        }
    });
}
loaddata=_.memoize(loaddata);
dictRange = {

    'so2': [-1, 0, 150, 500, 650, 800,800,800,800],

    'no2': [-1, 0, 100, 200, 700, 1200, 2340, 3090, 3840],
    'pm10': [-1, 0, 50, 150, 250, 350, 420, 500, 600],

    'co': [-1, 0, 5, 10, 35, 60, 90, 120, 150],
    'o3': [-1, 0, 160, 200, 300, 400, 800, 1000, 1200],

    'pm2_5': [-1, 0, 35, 75, 115, 150, 250, 350, 500],

    'aqi': [-1, 0, 50, 100, 150, 200, 300, 400, 500],

};
_.mapObject(dictRange,(v,k)=>{v.pop();v.pop();v.push(Infinity)});
levelColors = ["#808080aa", "#43ce17aa", "#efdc31aa", "#ffaa00aa", "#ff401aaa", "#d20040aa", "#9c0a4eaa"];

function refreshMapV() {

    var mode = $('#toggleLabel').data('label');
    var factor = $('.radio-group [data-factor].radio-button-checked').data('factor');
    loaddata((new Date).getHours()).then(function (data) {
        if(data.search('Warning')>=0){
            data = data.split('<br />\n')[4]; 
        }
       
        data = eval('(' + b.decode(decryptData(data)) + ')');
        console.info(data);
        global_province = data.result.data.province.rows;
        global_city = data.result.data.city.rows;
        $('#timetip').text(`数据时间: ${global_city[0].time.slice(0,-3)}`);
        
        var dict_province = _.object(global_province.map(x => {
            return [x['provincename'], x]
        }));
        var dict_city = _.object(global_city.map(x => {
            return [x['cityname'], x]
        }));

        points_city.map(x => {
            var name = x.properties['name'];
            var properties = dict_province[name];
            var colorIndex = _.sortedIndex(dictRange[factor], +properties[factor]);
            var color = levelColors[colorIndex - 1].slice(0, 7);
            x['properties'] = {
                ...properties,
                ...{
                    'name': name
                },
                ...{
                    'color': color,
                }

            };
        });

        texts_city.map(x => {
            var name = x.properties['name'];

            var properties = dict_province[name];
            var value = +properties[factor];
            var text = (mode == 'value' ? value >> 0 : name).toString();
            var colorIndex = _.sortedIndex(dictRange[factor], value);
            var color = levelColors[colorIndex - 1].slice(0, 7);
            x['properties'] = {
                ...properties,
                ...{
                    'name': name
                },
                ...{
                    'text': text,
                    'backgroundColor': color,
                    'textColor': colorIndex < 5 ? '#000' : '#fff',
                }

            };
        });

        points_station.map(x => {
            var name = x.properties['name'];

            var properties = dict_city[name];
            var colorIndex = _.sortedIndex(dictRange[factor], +properties[factor]);
             
            var color = levelColors[colorIndex - 1].slice(0, 7);
            x['properties'] = {
                ...properties,
                ...{
                    'name': name
                },
                ...{
                    'color': color,
                }

            };
        });

        texts_station.map(x => {
            var name = x.properties['name'];

            var properties = dict_city[name];
            var value = +properties[factor];
            var text = (mode == 'value' ? value >> 0 : name).toString();
            var colorIndex = _.sortedIndex(dictRange[factor], value);
            var color = levelColors[colorIndex - 1].slice(0, 7);
            x['properties'] = {
                ...properties,
                ...{
                    'name': name
                },
                ...{
                    'text': text,
                    'backgroundColor': color,
                    'textColor': colorIndex < 5 ? '#000' : '#fff',
                }

            };
        });

        cityLayer.setData(points_city);
        cityTextLayer.setData(texts_city);

        stationLayer.setData(points_station);
        stationTextLayer.setData(texts_station);
        if (mode == 'none') {
            view.removeLayer(cityTextLayer);
            view.removeLayer(stationTextLayer);
             
        } else {
             
            view.addLayer(cityTextLayer);
            view.addLayer(stationTextLayer);
            
        }
    });
}

$(function () {
    
    $('#toggleStyle').click(function () {
         var text=$(this).find('span').text();
         location.href =(text == '白日风') ? location.pathname+'?style=white' : location.pathname+'?style=dark';
    })

    
    $('#toggleLabel').click(function () {
        var labelType = $(this).data('label');
        if (labelType == 'none') {
            $(this).data('label', 'name');
            $(this).find('span').text('显数值');
        }
        if (labelType == 'value') {
            $(this).data('label', 'none');
            $(this).find('span').text('显名字');
        }
        if (labelType == 'name') {
            $(this).data('label', 'value');
            $(this).find('span').text('无标注');
        }
        refreshMapV();

    });
    $('.radio-group [data-factor]').click(function () {
        $('.radio-group [data-factor]').removeClass('radio-button-checked');
        $(this).addClass('radio-button-checked');
        refreshMapV();
    })
    loaddata((new Date).getHours()).then(function (data) {
        if(data.search('Warning')>=0){
            data = data.split('<br />\n')[4]; 
        }
        data = eval('(' + b.decode(decryptData(data)) + ')');
        console.info(data);
        data = data.result.data;
        citys = _.chain(data.city.rows).groupBy('provincename').map((x, i) => {
            return {
                'code': i,
                'name': i,
                'stations': x.map(y => {
                    return {
                        'code': y['cityname'],
                        'name': y['cityname'],
                        'lng': y['longitude'],
                        'lat': y['latitude']

                    }
                })
            }
        }).value()
        initMapV();
        refreshMapV();
        $('.loader').remove();
        setInterval(refreshMapV,1000*60*10);
       
    })
})

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
大数据
全栈大数据产品,面向海量数据场景,帮助您 “智理无数,心中有数”!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档