DEV Community

Cover image for OSM + Leaflet 學習筆記 3:定位、全螢幕、小地圖、列印、客製選單
Let's Write
Let's Write

Posted on • Edited on • Originally published at letswrite.tw

1

OSM + Leaflet 學習筆記 3:定位、全螢幕、小地圖、列印、客製選單

本篇要解決的問題

之前寫過二篇 OSM + Leaflet.js 的筆記文,當時總覺得跟 Google Maps 比起來有點陽春。前陣子有一次搭客運到宜蘭,瞄到客運員工開了一個網頁在看各車次到了哪裡,使用的就是 OpenStreetMap(OSM),當時看到就覺得,原來 OSM 還可以這樣子用呀(筆記)~

前幾天回頭看官方文件,發現文件裡有一頁是「Plugins」,裡面洋洋灑灑地列出了一堆套件,讓使用地圖的人可以直接套用,減少開發時間。

本篇就是先看了幾個套件,選出幾個覺得未來可能會到的,並實作出一頁 Demo 來,把這些套件都套進地圖裡。

選出的套件有:

  • 定位
  • 全螢幕
  • 右下方同步小地圖
  • 列印
  • 含 collapse 功能的客製選單

還有幾個套件也覺得好用,但五個套件在一篇文章裡有點多了,就看以後有沒有機會再寫第四篇學習筆記文。

OSM + Leaflet.js 的基本使用,像是繪製地圖、放 markder 等,請看前二篇筆記文,這邊不會有詳細的解釋。


SHP 轉 GeoJSON,座標轉 WGS84

這次先從政府資料開放平臺裡,選出「國家公園、國家森林遊樂區及國家風景區範圍內之觀光景點(本島)」當地圖上的點。

結果,抓出來的資料格式是 SHP,需要轉成 GeoJSON 後才能使用。

然後就踩坑了。

主要的坑就是,當用了 mapshaper 轉成 GeoJSON 後,因為政府提供的資料裡,座標是真的座標,但我們用的 OSM 是使用麥卡托投影法,座標必須轉成 WGS84 的格式,放上地圖時才會正確。

之前有寫過一篇「D3.js、Vue 畫一個台灣地圖」,裡面是靠 D3.js 原有的 function 去處理,而這邊是用 Leaflet.js,需要另尋出路。

Leaflet.js 文件上有看到一個「coordsToLatLng」的 function,但不論試了各種寫法都沒有轉成功。

後來再 Google 查了一下,發現地圖投影法真的是另一個小宇宙,有夠複雜,投影法的介紹可以看這篇「地理小課堂:那些被錯付的地圖投影法」,這邊就用最簡單的方式來做轉換。

這個簡單的方式就是:在 mapshaper 轉成 GeoJSON 時,輸出前先將座標轉為 WGS84

這個方式主要來自於這篇:How to make your coordinates WGS84 with mapshaper.org

1 將要轉出的檔案拉進 mapshaper 裡:

將要轉出的檔案拉進 mapshaper

2 點擊右上角的 Console,輸入 -proj wgs84 後按下 enter:

console 裡輸入指令

3 點右上角的 Export,格式選 GeoJSON:

Export 格式選 GeoJSON

以上,這樣匯出的 JSON 檔,裡面的座標就會是 WGS84 格式,可以正確放上地圖。


定位:Leaflet.Locate

官方文件:https://github.com/domoritz/leaflet-locatecontrol

官方示例:https://domoritz.github.io/leaflet-locatecontrol/demo/

這個套件蠻好用的,除了有定位光點的樣式,還給了目前朝哪的方向。

不過遇到一個很奇怪的狀況,JS 檔如果是引用 CDN 的,就會一直抓不準定位,誤差距離會到二公里以上,只有直接從 GitHub 上把 JS 下載下來使用,定位才正常。

實測了好幾次都是這樣,所以大家如果要用,建議直接從 GitHub 裡抓 JS。

CSS 則可以引用 CDN,CSS 裡引用圖檔的部份就不用再另外下載。

Leaflet.Locate 使用方式很簡單,當地圖渲染完成後,JS 如下:

L.control.locate({
  position: 'topleft',
  locateOptions: {
    enableHighAccuracy: true
  },
  strings: {
    title: '定位我的位置',
    metersUnit: '公尺',
    feetUnit: '英尺',
    popup: '距離誤差:{distance}{unit}以內'
  },
  clickBehavior: {
    inView: 'stop',
    outOfView: 'setView',
    inViewNotFollowing: 'inView'
  }
}).addTo(map);
Enter fullscreen mode Exit fullscreen mode

position:定位按鈕要放地圖的哪個位置。

enableHighAccuracy:要不要啟用精準定位。

strings :顯示文字的部份。這邊是 August 自行翻譯的,其中 popup 內容跟原本的不太一樣,但覺得說明誤差距離會比較好懂意思。

clickBehavior :點擊執行按鈕時,不同狀況要給的不同動作。

這套使用起來很簡單,文件中也列出了一堆參數,想看還有什麼可以使用的參數請自行上官方文件查看。


全螢幕:Leaflet.Control.FullScreen

官方文件:https://github.com/brunob/leaflet.fullscreen

官方示例:https://brunob.github.io/leaflet.fullscreen/

一般嵌入地圖在網頁上,很少會是全螢幕的嵌入,因為當地圖是全螢幕時,滑鼠或手勢的操作就會全被地圖的操作給吃掉,頁面的其它部份就不容易被看到。

不過如果是只嵌在網頁上的一小塊,桌機時還好,畢竟螢幕夠大的話還是可以看見地圖內容。

但手機就不方便了,像是本篇的 Demo 加了一堆按鈕上去,地圖本身的內容就會被擋住一大部份。

這時有個全螢幕按鈕讓地圖是全螢幕呈現,就很方便。

使用方式很簡單,當地圖渲染完成後,JS 如下:

L.control.fullscreen({
  position: 'topleft',
  title: '進入全螢幕',
  titleCancel: '離開全螢幕',
  content: '<img class="p-1" src="dist/size-fullscreen.svg">',
  forceSeparateButton: true,
  forcePseudoFullscreen: true,
  fullscreenElement: false
}).addTo(map);
Enter fullscreen mode Exit fullscreen mode

position :全螢幕按鈕要放地圖的哪個位置。

content :全螢幕按鈕裡的 HTML,這邊是放一張 svg 的圖檔。

forceSeparateButton :全螢幕按鈕要不要跟縮放按鈕分開來。

forcePseudoFullscreen :全螢幕要玩真的還是玩假的 XD~

true 代表要執行的是假的全螢幕、false 代表是真的全螢幕。

假的 就是讓地圖的寬高塞滿頁面而已,瀏覽器上的那些網址列或書籤列都還看得到。

真的 就是像 Youtube 上我們點全螢幕一樣,會看到一個滿滿的地圖大平台。

fullscreenElement :看了文件看不懂意思,只知道當設為 true 時,就必須要在點擊按鈕後再執行某個 callback,不然全螢幕功能會失效。

套件裡還給了二個事件:enterFullscreen(進入全螢幕)、exitFullscreen(離開全螢幕)。

本篇的 Demo 有使用到這二個事件,拿來做全螢幕按鈕上的圖片切換:

const fullscreenBtn =
    document.querySelector('.leaflet-control-zoom-fullscreen');
map.on('enterFullscreen', () => {
  fullscreenBtn.innerHTML =
    '<img src="dist/fullscreen-exit.svg">';
});
map.on('exitFullscreen', () => {
  fullscreenBtn.innerHTML =
    '<img src="dist/size-fullscreen.svg">';
});
Enter fullscreen mode Exit fullscreen mode

小地圖:Leaflet.MiniMap

官方文件:https://github.com/Norkart/Leaflet-MiniMap

這個套件就是在地圖的右下角再放一個小地圖,可以同步大地圖顯示目前我們選擇的地方。

小地圖功能示例

它的作法其實就是嵌入第二個地圖,因此我們在寫渲染地圖的部份,可以把相同的參數設成變數。

// 相同的參數存成變數
const center = {地圖中心點};
const zoom = {縮放值};
const map = L.map('map').setView(center, zoom);
const osmUri = {Tiles 的檔按路徑};
const attribution = {版權宣告內容};

// 主要地圖
L.tileLayer(osmUri, {
  attribution: attribution
}).addTo(map);

// 小地圖
const miniOSM = new L.TileLayer(osmUri, {
  minZoom: 0, maxZoom: 13,
  attribution: attribution
});
Enter fullscreen mode Exit fullscreen mode

列印:leaflet-easyPrint

官方文件:https://github.com/rowanwins/leaflet-easyPrint

官方示例:https://rowanwins.github.io/leaflet-easyPrint/

這個……其實也不確定功能的實用性,但 Google Maps 有分享地圖的功能,而 OSM 目前還沒看到分享功能之類的套件,就先拿列印功能來充數,至少可以讓其它人知道我們看到的地圖長什麼樣子。

L.easyPrint({
  title: '列印地圖',
  position: 'topleft',
  sizeModes: ['Current', 'A4Portrait', 'A4Landscape']
}).addTo(map);
Enter fullscreen mode Exit fullscreen mode

sizeModes 是要列印成哪些格式,預設有三種,也可以另外自己去設定寬高。

完整的參數就請自行看文件囉。


客製選單:sidebar-v2

官方文件:https://github.com/turbo87/sidebar-v2/

官方示例:https://turbo87.github.io/sidebar-v2/examples/index.html

這套用起來稍微麻煩的地方,就是必須要調整 HTML,它的作法就是另外寫好一個 collapse 的 HTML,然後塞進地圖裡。

如果想要自己寫一個不一樣的選單,是可以自己花時間寫,用套件單純就是為了節省開發時間。

程式碼的部份,可以看官方提供的:

https://github.com/Turbo87/sidebar-v2/blob/master/examples/index.html

本篇 Demo 因為有寫了讓地圖維持在 16:9、4:3 的比例,所以 HTML、CSS 都有再做修改,跟官方提供的程式碼有不同,各位如果要使用,建議是看原本的程式碼,複製貼上後再針對各自的頁面去修改樣式。

除了 sidebar-v2,也有蠻多客製按鈕的套件很不錯,以下列出,可自行看示例選擇要不要使用:


本篇 Demo 及原始碼

最後附上本篇的 Demo 網址,及原始碼的 Github 網址。

Demo:https://letswritetw.github.io/letswrite-leaflet-plugins/

GitHub:https://github.com/letswritetw/letswrite-leaflet-plugins


OSM + Leaflet 學習筆記系列

OSM + Leaflet 學習筆記 1:建地圖、marker、事件、換圖層

OSM + Leaflet 學習筆記 2:移動中心點、抓目前地點

OSM + Leaflet 學習筆記 3:定位、全螢幕、小地圖、列印、客製選單

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay