概述
本文结合最新的疫情数据,打造属于自己的疫情地图。
效果
布局
布局采用一张图的思路,将疫情信息以浮动框的形式飘在地图上,分别为:图例,置于左下角;统计数据,置于右下角;汇总数据以及数据源说明,置于右上角;左上角区域,添加地图说明。
数据
制作一张这样的地图,需要两类数据:1、疫情数据;2、区划数据。其中,为了展示合理,在区划数据里面我又添加了一省会城市的数据。
1. 疫情数据
疫情数据从百度获取而来,将获取到的数据转换为CSV,并通过网页工具csv2json转换为格式,方便使用。
2. 区划数据
按照上述说明,区划数据有两份,一份是省级区划面数据,一份是省会城市数据,数据格式分别如下:
实现
html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>疫情地图</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="https://openlayers.org/en/v4.6.5/css/ol.css" type="text/css">
</head>
<body>
<div id="app">
<div class="title">
<img src="css/logo.jpg" alt="logo">
<span>疫情地图</span>
</div>
<div class="statics">
<div class="tips">
<b>数据来源:</b>国家卫健委
</div>
<table cellspacing="0" cellpadding="0">
<tr>
<th style="color: red;">
{{ getStatics.diagnosed }}
</th>
<th style="color: darkgreen;">
{{ getStatics.cure }}
</th>
<th style="color: grey;">
{{ getStatics.death }}
</th>
</tr>
<tr>
<td>确诊病例</td>
<td>治愈病例</td>
<td>死亡病例</td>
</tr>
</table>
</div>
<div id="map">
<div class="overlay" id="overlay">
<ul>
<li> {{ selected && selected.name }} </li>
<li>
<b>确诊:</b> <span style="color: red;">{{ selected && selected.diagnosed }}</span>
</li>
<li>
<b>治愈:</b> <span style="color: darkgreen;">{{ selected && selected.cure }}</span>
</li>
<li>
<b>死亡:</b> <span style="color: grey;">{{ selected && selected.death }}</span>
</li>
</ul>
</div>
</div>
<ul class="legend">
<li>
图例
</li>
<li
v-for="(item, index) in colorMap"
:key="index">
<span :style="{backgroundColor: item.color}"></span>
{{ item.label }}
</li>
</ul>
<div class="table">
<table cellspacing="0" cellpadding="0">
<tr>
<th width="60">地区</th>
<th width="45">确诊</th>
<th width="45">治愈</th>
<th width="45">死亡</th>
</tr>
</table>
<div class="tbody">
<table cellspacing="0" cellpadding="0">
<tr
v-for="(item, index) in epidemicArray"
:key="index">
<td width="60" :style="{color: index < 5 ? 'red' : 'black'}">{{ item.zone }}</td>
<td width="45" style="color: red;">{{ item.diagnosed }}</td>
<td width="45" style="color: darkgreen;">{{ item.cure }}</td>
<td width="45" style="color: grey;">{{ item.death }}</td>
</tr>
</table>
</div>
</div>
</div>
<script src="https://openlayers.org/en/v4.6.5/build/ol.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
js
var that, map;
var app = new Vue({
el: '#app',
data: {
sum: 0,
epidemicArray: [],
epidemicData: {},
colorMap: [
{
label: '≥10000',
color: '#660208'
},
{
label: '1000-9999',
color: '#8c0d0d'
},
{
label: '100-999',
color: '#cc2929'
},
{
label: '10-99',
color: '#ff7b69'
},
{
label: '1-9',
color: '#ffaa85'
}
],
vector: null,
overlay: null,
selected: null
},
mounted() {
that = this;
that.init();
},
computed: {
getStatics() {
let diagnosed = 0;
let cure = 0;
let death = 0;
for (var i = 0; i < this.epidemicArray.length; i++) {
const d = this.epidemicArray[i];
diagnosed += d.diagnosed;
cure += d.cure;
death += d.death;
}
return {
diagnosed: diagnosed,
cure: cure,
death: death
}
}
},
methods: {
init() {
$.get('data/data.json', res => {
that.epidemicArray = res;
for (var i = 0; i < res.length; i++) {
var r = res[i];
that.sum += r.diagnosed;
that.epidemicData[r.zone] = r;
}
$.get('data/capital.json', _res => {
for (var j = 0; j < res.length; j++) {
var _r = _res[j];
if (that.epidemicData[_r.name]) that.epidemicData[_r.name].center = [_r.lon, _r.lat];
}
that.initMap();
})
})
},
initMap() {
var vectorSource = new ol.source.Vector({
url: "data/china.json",
format: new ol.format.GeoJSON()
});
that.vector = new ol.layer.Vector({
source: vectorSource,
style: that.styleFunction
});
map = new ol.Map({
controls: ol.control.defaults({
attribution: false
}),
target: 'map',
layers: [
that.vector
],
view: new ol.View({
minZoom: 3,
maxZoom: 12,
center: [11760366.56, 4662347.84],
zoom: 5
})
});
that.addMapEvent();
that.addOverlay();
},
addMapEvent() {
map.on('pointermove', evt => {
map.getTargetElement().style.cursor = '';
that.selected = null;
that.overlay.setPosition(null);
if (map.hasFeatureAtPixel(evt.pixel)) {
map.getTargetElement().style.cursor = 'pointer';
const feature = map.getFeaturesAtPixel(evt.pixel)[0];
const name = feature.get('name');
that.selected = that.epidemicData[name];
that.selected.name = name;
const data = that.epidemicData[name];
const center = data.center;
that.overlay.setPosition(ol.proj.fromLonLat(center));
}
that.vector.setStyle(that.styleFunction);
});
},
addOverlay() {
const container = document.getElementById('overlay');
that.overlay = new ol.Overlay({
element: container,
position: null,
positioning: 'bottom-center',
offset: [0, -20]
});
map.addOverlay(that.overlay);
},
styleFunction(feat) {
const name = feat.get('name');
const data = that.epidemicData[name];
const num = data.diagnosed;
const center = data.center;
const color = that.getColor(num);
const selected = that.selected && name === that.selected.name;
let styles = [];
styles.push(new ol.style.Style({
fill: new ol.style.Fill({
color: color
}),
stroke: new ol.style.Stroke({
color: selected ? '#00ffff' : '#eeeeee',
width: selected ? 3 : 1
})
}));
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(ol.proj.fromLonLat(center)),
text: new ol.style.Text({
text: name,
fill: new ol.style.Fill({
color: '#ffffff'
})
})
}));
return styles;
},
getColor(num) {
for (var i = 0; i < that.colorMap.length; i++) {
var c = that.colorMap[i];
if (c.label.indexOf('-') !== -1) {
var nums = c.label.split('-').map(Number);
if (num >= nums[0] && num <= nums[1]) {
return c.color;
}
} else {
var nums = c.label.split('≥').map(Number);
if (num >= nums[1]) {
return c.color;
}
}
}
}
}
})
scss
@charset "utf-8";
/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar {
width: 5px;
height: 5px;
background-color: #F9F9F9;
}
/*定义滚动条轨道 内阴影+圆角*/
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
background-color: #F5F5F5;
}
/*定义滑块 内阴影+圆角*/
::-webkit-scrollbar-thumb {
border-radius: 3px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
background-color: #999;
}
#app,
#map,
body,
html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
font-size: 16px;
.ol-zoom {
display: none;
}
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
.overlay {
padding: 10px;
background-color: rgba(255, 255, 255, 0.85);
overflow: hidden;
border-radius: 5px;
box-shadow: 1px 1px 3px #eeeeee;
&:after {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
&:after {
border-top-color: rgba(255, 255, 255, 0.85);
border-width: 10px;
left: calc(50% - 10px);
}
ul {
li {
height: 25px;
line-height: 25px;
&:first-child {
font-weight: bold;
height: 30px;
line-height: 30px;
}
}
}
}
.title {
position: fixed;
top: 15px;
left: 15px;
z-index: 99;
text-align: left;
img {
width: 70px;
height: 70px;
vertical-align: middle;
}
span {
display: inline-block;
height: 80px;
line-height: 80px;
font-size: 24px;
font-weight: bold;
letter-spacing: 3px;
text-shadow: 2px 2px 5px #000;
color: white;
}
}
.statics {
position: fixed;
top: 20px;
right: 20px;
z-index: 99;
overflow: hidden;
.tips {
text-align: right;
height: 40px;
line-height: 40px;
}
table {
width: 100%;
background-color: rgba(200, 200, 200, 0.1);
border-radius: 5px;
th,
td {
width: calc(100% / 3);
padding: 3px 15px;
}
th {
font-size: 20px;
}
td {
text-align: center;
}
}
}
.legend {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 99;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.6);
border-radius: 5px;
box-shadow: 1px 1px 3px #eeeeee;
white-space: nowrap;
padding: 5px;
li {
height: 25px;
line-height: 25px;
padding: 0 10px;
&:first-child {
font-weight: bold;
}
span {
display: inline-block;
width: 24px;
height: 12px;
margin-right: 3px;
}
}
}
.table {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 99;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.6);
border-radius: 5px;
box-shadow: 1px 1px 3px #eeeeee;
white-space: nowrap;
padding: 10px 0 10px 5px;
.tbody {
max-height: 150px;
overflow-y: auto;
margin-top: 6px;
tr {
&:nth-child(2n) {
background-color: rgba(200, 200, 200, 0.1);
}
td {
text-align: right;
&:first-child {
text-align: left;
}
}
}
}
table {
td,
th {
padding: 5px 8px;
}
}
}
获取源代码
链接:https://pan.baidu.com/s/1yC1Eah_UDlpCeSPeRB8KZg
提取码:9r0v