平时采用百度地图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);
})
})