地圖上覆蓋物壓蓋的優化
- 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; } }