# 前言
之前專案上遇到地圖顯示問題,當所地圖上同時顯示太多點時,點位會非常密集。
如何避免點位重疊,導致不易閱讀的問題呢?
#### 套件選擇
功能目標:
1. 只有針對完全一樣的地址
2. 特定比例之下,才開始處理重疊的問題(也就是範圍太大時不處理)
3. 不希望使用 Google Maps 提供的聚合(cluster)功能
最後團隊找到 Overlapping Marker Spiderfier 這個套件,可以展開密集的點位。
# 套件介紹
#### 效果
安裝了以後,點擊 marker,如果位於很密集的地方,就會展開周遭的點位。
#### 寫法
0. 引入 Overlapping Marker Spiderfier
1. 準備好你的點資料
2. 創建一個套件定義的 OverlappingMarkerSpiderfier 物件
3. 把每個點資料創建成 google maps 的 marker 物件,放到 oms 而不是放到 google Maps 中
這樣就大功告成!點了 marker 以後就會展開。
``` html
<script src="https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier/1.0.3/oms.min.js"></script>
```
``` javascript
// 點資料
let pointList = [
{ name: '西門站1號出口', lat: 25.04213, lng: 121.50762 },
{ name: '西門站2號出口', lat: 25.04146, lng: 121.50837 },
{ name: '西門站3號出口', lat: 25.04185, lng: 121.50876 },
{ name: '西門站4號出口', lat: 25.04226, lng: 121.50888 },
{ name: '西門站5號出口', lat: 25.04257, lng: 121.50776 },
{ name: '西門站6號出口', lat: 25.04257, lng: 121.50776 }
];
// 創建 oms 物件
let oms = new OverlappingMarkerSpiderfier(map);
// 把 marker 物件,放到 oms 中
pointList.forEach(function (point, i) {
let marker = new google.maps.Marker({
position: { lat: point.lat, lng: point.lng }
});
oms.addMarker(marker);
});
```
#### 選項
以下列出常用的選項。
``` javascript
let oms = new OverlappingMarkerSpiderfier(map, {
keepSpiderfied: false, // 點擊展開標記時,標記不會收回:否
ignoreMapClick: false, // 忽略點空白處時關閉展開:否
nearbyDistance: 20, // 多少距離內的點位會一同展開:20 px
circleSpiralSwitchover: 9, // 當標記數量超過 9 個時,從圓形展開切換到螺旋展開
circleFootSeparation: 23, // 圓形展開時的展開幅度:23 px
spiralFootSeparation: 26, // 螺旋展開時的展開幅度:26 px
});
```
實際情況做了一點點微調。
1. 因為我希望展開後會保持展開狀態,所以 `keepSpiderfied` 與 `ignoreMapClick` 填「是」。
2. 我希望同一個座標才會展開,但因為 `nearbyDistance` 填了 0 會無法展開,因此填一個小小的數值。
3. 希望都用圓形的展開,所以填一個比較高的數值(目前實務上同地址最多大概 12 個)。
``` javascript
let oms = new OverlappingMarkerSpiderfier(map, {
keepSpiderfied: true, // 點擊展開標記時,標記不會收回:是
ignoreMapClick: true, // 忽略點空白處時關閉展開:是
nearbyDistance: 0.001, // 多少距離內的點位會一同展開:0.001 px
circleSpiralSwitchover: 100, // 當標記數量超過 9 個時,從圓形展開切換到螺旋展開
circleFootSeparation: 30, // 圓形展開時的展開幅度:30 px
});
```
OK!愈來愈接近想要的效果。
#### 套件原理
為什麼需要一個 oms 物件?因為原本是把 marker 種在 google maps 上,而展開功能是把 marker 種在 oms 物件上,點擊時 marker 時會攔截你的點擊事件,觸發套件的展開功能。
展開時會經過以下流程:
1. 確認 marker 是否要展開
1. 點擊 marker 的經緯度換算成其他 px(所以設定鄰近的時候是用 px 設定)
2. 遍歷所有 marker
1. 其他 marker 的經緯度換算成 px
5. 計算點擊 marker 與其他 marker 的距離是否小於設定的範圍
6. 如果是,帶入 nearbyMarkerData
1. 等於1 → 沒有鄰近點位 → 一般 click
8. 大於1 → 有鄰近點位 → spiderfy(展開)
9. 展開 marker
1. 尋找所有在重疊範圍內的點的圓心
2. 依照圓心、marker 數量算出每個 marker 展開後的位置(px)
3. px 轉成經緯度,依序帶入每個點位 → 帶回google map??(這邊不確定)
4. 同時每個點的「原本位置-展開後位置」加上一條線(leg)
# 實際情況
專案後期開始衍伸其他功能:
1. 只有針對完全一樣的地址
1. 完全一樣地址 = 用同個座標
1. 特定比例之下,才開始做點位重疊的處理
1. 處理 =
1. 必須自動展開「全部」有重疊的點位
1. 展開時中心需要有一個黑點
1. 未展開時要有醒目標示
這些延伸的功能,似乎本來套件也無法完全做到,所以還要多做處理。
#### 1. 自動展開所有「同個座標」點位
這個套件無法一次把所有重疊的地方都展開,一次只能展開一個地方。那想一次展開所有重疊的地方怎麼辦?
原本 API 是回傳所有的 marker,現在要把 marker 分類。如果一個經緯度有超過一筆資料就回傳,而且回傳的時候要重新調整結構,同一個經緯度變成一組放入 oms 物件,創建多個 oms 物件,並在特定比例下觸發 `spider_click`,達到展開所有點位的功能。
另外,切換比例尺時他一定會收合點位(因為是用畫面位置計算,不收合然無法計算展開位置),所以每次切換後都要再觸發動作。
```javascript
// 修改前
let pCases_differentCenter = [
[
{ name: '北門站1號出口', lat: 25.04898, lng: 121.51 },
{ name: '北門站2號出口', lat: 25.0494, lng: 121.51058 },
{ name: '北門站3號出口', lat: 25.04985, lng: 121.51009 },
{ name: '中山站1號出口', lat: 25.05239, lng: 121.52031 },
{ name: '中山站2號出口', lat: 25.05237, lng: 121.52116 },
{ name: '中山站3號出口', lat: 25.05262, lng: 121.52112 },
{ name: '中山站4號出口', lat: 25.05291, lng: 121.52032 },
{ name: '中山站5號出口', lat: 25.05307, lng: 121.51925 },
{ name: '中山站6號出口', lat: 25.05278, lng: 121.51914 }
],
];
// 修改後
let pCases_differentCenter = [
[
{ name: '北門站1號出口', lat: 25.04898, lng: 121.51 },
{ name: '北門站2號出口', lat: 25.0494, lng: 121.51058 },
{ name: '北門站3號出口', lat: 25.04985, lng: 121.51009 },
],
[
{ name: '中山站1號出口', lat: 25.05239, lng: 121.52031 },
{ name: '中山站2號出口', lat: 25.05237, lng: 121.52116 },
{ name: '中山站3號出口', lat: 25.05262, lng: 121.52112 },
{ name: '中山站4號出口', lat: 25.05291, lng: 121.52032 },
{ name: '中山站5號出口', lat: 25.05307, lng: 121.51925 },
{ name: '中山站6號出口', lat: 25.05278, lng: 121.51914 },
]
];
```
#### 2. 展開時中心需要有一個黑點
新增一個黑點,如果展開時才顯示,不展開時隱藏。
這個黑點不參與展開的動作,不然他會跟著炸開。
#### 3. 未展開時要有醒目標示
特定比例尺的時候不展開,這時候要有醒目標示。所以如果是未展開時,更換 icon。
但是!marker 圖層可以開關,所以如果圖層很少時,可能沒有需要展開。
每次開關圖層,判斷每個 oms 內 marker 數量,如果超過 1 才要更換 icon。
# 心得
1. 套件無法展開所有 marker,需要多一些開發手續。
3. 其實聚集顯示的效能較好,建議有特殊需求再採納此方案。
5. 這次整理文件的心得:
1. 其實是一年前就完成的功能,但當時沒有很理解套件的預設事件,都是觀察到好像有某些現象,繞開後就開發出功能。
2. 為了整理文件,做套件邏輯的段落,去看了套件的原始碼,發現寫法其實沒有很難,而且看了以後就可以確定他事件的生命週期,例如,Overlapping Marker Spiderfier 的一切觸發源頭都是從 click 事件開始,因此無法用其他方式觸發,同時切換 zoom 時需要重新計算所有展開的點位。
3. 這經驗告訴我們,使用任何工具或套件時,應該花時間仔細觀察其運作邏輯,甚至閱讀其原始碼。這樣不僅能避免開發中的問題,還能更靈活地進行客製化開發。