地圖上覆蓋物壓蓋的優化

  • 2020 年 2 月 20 日
  • 筆記

概述

在做webgis的時候,會經常性的碰到地圖覆蓋物壓蓋的情況。本文講述一種基於聚類思路的解決辦法,實現使用的是openlayers4+。

效果

默認展示第一個點(第一個點可根據一些業務邏輯進行處理,文中簡單的做了處理,取了第一個點),鼠標經過第一個點的時候再將其他壓蓋的點展示出來。

實現

1. htm

<!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 id="map"></div>      <div        :style="{left: selectedX + 'px', top: selectedY + 'px'}"        :class="selectedCluster.length > 0 ? 'show': 'hide'"        class="overlays">        <div          v-for="(item, index) in selectedCluster"          :key="index"          class="circle-overlay cluster-overlay"          :style="{background: colorMap[item.level], marginLeft: padding / 2 + 'px'}">        </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/overlay.js"></script>  </body>  </html>

2. js

var that, map;    var app = new Vue({    el: '#app',    data: {      size: 20,      padding: 2,      overlays: [        {          id: 1,          coords: [11760366.56, 4662347.84],          level: 2        },        {          id: 2,          coords: [11760366.56, 4662347.84],          level: 1        },        {          id: 3,          coords: [11760366.56, 4662347.84],          level: 3        },        {          id: 4,          coords: [12760366.56, 4662347.84],          level: 2        },        {          id: 5,          coords: [12760366.56, 4662347.84],          level: 1        },        {          id: 6,          coords: [12760366.56, 4662347.84],          level: 3        }      ],      clusterData: [],      mapOverlays: [],      colorMap: {        1: 'blue',        2: 'orange',        3: 'red'      },      mapZoom: -1,      firstInit: false,      selectedX: 0,      selectedY: 0,      selectedCluster: []    },    mounted() {      that = this;      that.initMap();    },    watch: {      mapZoom(newVal, oldVal) {        if (oldVal === -1) that.initOverlays();      }    },    methods: {      initMap() {        var osm = new ol.layer.Tile({          source: new ol.source.OSM()        });        map = new ol.Map({          controls: ol.control.defaults({            attribution: false          }),          target: 'map',          layers: [osm],          view: new ol.View({            minZoom: 3,            maxZoom: 18,            center: [11760366.56, 4662347.84],            zoom: 4          })        });        map.on('moveend', e => {          that.mapZoom = map.getView().getZoom();        });      },      // 創建新的聚類      createCluster(d) {        that.clusterData.push({          p: d,          data: [d]        });      },      // 判斷距離      clusterTest(p1, p2) {        const pixel1 = map.getPixelFromCoordinate(p1.coords);        const pixel2 = map.getPixelFromCoordinate(p2.coords);        // 判斷兩個點的屏幕距離是否小於圖標大小:小於,是        const dis = Math.abs(pixel1[0] - pixel2[0]);        return dis < that.size;      },      // 處理聚類數據      clusterOverlays() {        for (var i = 0; i < that.overlays.length; i++) {          const d = that.overlays[i];          let _clustered = false;          for (var j = 0;j < that.clusterData.length;j++) {            const _d = that.clusterData[j].p;            const isNear = that.clusterTest(d, _d);            if (isNear) {              that.clusterData[j].data.push(d);              _clustered = true;              break;            }          }          if (!_clustered) that.createCluster(d);        }      },      initOverlays() {        that.clusterOverlays();        // that.showAllOverlays();        that.showFirstOverlay();      },      showFirstOverlay() {        console.log(that.clusterData);        for (var i = 0; i < that.clusterData.length; i++) {          const d = that.clusterData[i].p;          const dom = document.createElement('div');          dom.style.background = that.colorMap[d.level];          dom.setAttribute('class', 'circle-overlay');          dom.setAttribute('index', i);          const overlay = new ol.Overlay({            element: dom,            position: d.coords,            positioning: 'center-center',            offset: [0, 0]          });          map.addOverlay(overlay);            // 添加dom事件          dom.addEventListener('mouseover', evt => {            const index = evt.target.getAttribute("index");            const coords = that.clusterData[index].p.coords;            const pixel = map.getPixelFromCoordinate(coords);            that.selectedX = pixel[0] + that.size / 2 + that.padding;            that.selectedY = pixel[1] - that.size / 2;              // 刪除第一個div            const cData = that.clusterData[index].data.concat([]);            cData.splice(0, 1);            that.selectedCluster = cData;          });          dom.addEventListener('mouseout', evt => {            that.selectedCluster = [];          });        }      },      showAllOverlays() {        for (var i = 0; i < that.clusterData.length; i++) {          const d = that.clusterData[i].data;          const coords = that.clusterData[i].p.coords;          for (var j = 0; j < d.length; j++) {            const _d = d[j];            const _xOff = j * (that.size + that.padding);            const dom = document.createElement('div');            dom.style.background = that.colorMap[_d.level];            dom.setAttribute('class', 'circle-overlay');            const overlay = new ol.Overlay({              element: dom,              position: coords,              positioning: 'center-center',              offset: [_xOff, 0]            });            map.addOverlay(overlay);          }        }      }    }  });

3.css

.circle-overlay {    border-radius: 50%;    border: 2px solid #ffffff;    box-shadow: 1px 1px 4px #ccc;    width: 18px;    height: 18px;    line-height: 18px;    text-align: center;    cursor: pointer;  }  .overlays {    position: absolute;    z-index: 99;    white-space: nowrap;    overflow: hidden;    &.hide {      display: none;      max-width: 0;      transition: max-width 1s, display 1s;    }    &.show {      display: block;      max-width: 400px;      transition: max-width 1s, display 1s;    }    .cluster-overlay {      float: left;    }  }