# 前言 之前專案上遇到地圖顯示問題,當所地圖上同時顯示太多點時,點位會非常密集。 如何避免點位重疊,導致不易閱讀的問題呢? #### 套件選擇 功能目標: 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. 這經驗告訴我們,使用任何工具或套件時,應該花時間仔細觀察其運作邏輯,甚至閱讀其原始碼。這樣不僅能避免開發中的問題,還能更靈活地進行客製化開發。