---
# System prepended metadata

title: JS | 重疊點位套件
tags: [JS]

---

# 前言
之前專案上遇到地圖顯示問題，當所地圖上同時顯示太多點時，點位會非常密集。
如何避免點位重疊，導致不易閱讀的問題呢？

#### 套件選擇
功能目標：
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. 這經驗告訴我們，使用任何工具或套件時，應該花時間仔細觀察其運作邏輯，甚至閱讀其原始碼。這樣不僅能避免開發中的問題，還能更靈活地進行客製化開發。

