制作自己的“疫情地图”
- 2020 年 2 月 20 日
- 笔记
概述
本文结合最新的疫情数据,打造属于自己的疫情地图。
效果

布局
布局采用一张图的思路,将疫情信息以浮动框的形式飘在地图上,分别为:图例,置于左下角;统计数据,置于右下角;汇总数据以及数据源说明,置于右上角;左上角区域,添加地图说明。
数据
制作一张这样的地图,需要两类数据: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; } } }