制作自己的“疫情地图”

  • 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;      }    }  }