如何使用 React 和 Leaflet 創建地圖

已發表: 2022-03-10
快速總結↬ Leaflet 是一個非常強大的工具,我們可以創建很多不同類型的地圖。 本教程將幫助您了解如何在 React 和 Vanilla JS 的幫助下創建高級地圖。

從 CSV 或 JSON 文件中獲取信息不僅複雜,而且乏味。 以視覺輔助的形式表示相同的數據更簡單。 在本文中,我們將在地圖上表示 SF 消防部門響應的非醫療火災事件的位置。

對於本教程,我們將使用以下工具:

  • 傳單
    用於交互式地圖的 JavaScript 庫
  • 反應
    用於構建用戶界面的 JavaScript 庫
  • 反應傳單
    Leaflet 地圖的 React 組件

什麼是傳單?

Leaflet.js 擁有大約 27k 顆星,是用於移動友好型交互式地圖的領先開源 JavaScript 庫之一。 它在現代瀏覽器上利用 HTML5 和 CSS3,同時在舊瀏覽器上也可以訪問。 總而言之,它支持所有主要的桌面和移動平台。

Leaflet 重約 38KB,非常適合基本的東西。 對於其他擴展,可以使用大量插件對其進行擴展。

許多報紙,包括 NPR、華盛頓郵報、波士頓環球報等,以及其他組織都使用 Leaflet 進行深入的數據項目。

例如,《舊金山紀事報》開展了一個名為“加州火災追踪器”的項目——這是一個交互式地圖,使用 Leaflet 提供有關加州各地燃燒的野火的信息。 他們不僅查明了火災的起源,還向我們展示了火災的軌跡。

由於這是一個介紹性教程,我們將僅標記火災事件的位置並顯示有關它的一些詳細信息。

在進入 React 之前,讓我們了解 Leaflet 的基礎知識。 為此,我們將創建一個簡單的示例,我們將在其中設置傳單地圖,使用標記和彈出窗口。

跳躍後更多! 繼續往下看↓

首先,讓我們在/project文件夾中創建index.htmlapp.js文件,並將後者鏈接到我們的index.html文件。

要開始使用 Leaflet,我們需要在 head 標籤中鏈接 Leaflet CSS 和 Leaflet JS。 要記住的一件事是 Leaflet CSS 出現在 Leaflet JS 之前。 這就是傳單。

我們還需要在index.html文件中添加一件事——一個用於保存地圖的容器。

 <div></div>

在我們忘記之前,讓我們給我們的 div 指定高度。

 #mapid { height: 1000px; }

有趣的來了。 無論您決定創建一個新的 JavaScript 文件還是繼續使用腳本標籤,請確保在調用L.map('mapid')之前將<div id="mapid">添加到 dom 中。

您可能會問“但是,為什麼?” 好吧,這是因為如果您將地圖綁定到一個尚不存在的容器,它會給您一個錯誤。

 Uncaught Error: Map container not found

創建地圖

現在,進入有趣的部分。 為了初始化地圖,我們通過一些選項將 div 傳遞給L.map()

 const myMap = L.map('mapid', { center: [37.7749, -122.4194], zoom: 13 })

讓我們一步一步來了解剛剛發生的事情。 我們使用 Leaflet API 的 Map 類在頁面上創建地圖。 我們將兩個參數傳遞給這個類:

  1. 我們傳入一個代表DOM ID 的字符串變量
  2. 帶有映射選項的可選對象文字

我們可以將許多選項傳遞給我們的班級,但主要的兩個選項是中心和縮放。 中心定義了地圖的初始地理中心,而縮放指定了初始地圖縮放級別。 默認情況下,它們都是未定義的。

對於中心,我們傳入舊金山的坐標。 有很多地方我們可以執行正向和反向地理編碼,但是對於像這樣的基本搜索,我們可以穀歌它。

通常,縮放值取決於您要顯示的內容。 你想展示一個城市還是一個州? 國家還是大陸? 繼續嘗試縮放值以獲得更好的想法。 在這個例子中,我們選擇了 13,因為它顯示了整個城市。

另一種初始化地圖的方法是使用 setView()。 它採用坐標數組和縮放級別的整數。

 const myMap = L.map('map').setView([37.7749, -122.4194], 13);

默認情況下,地圖上的所有鼠標和触摸交互都已啟用,並且具有縮放和歸屬控制。

創建圖層

接下來,我們將向我們的地圖添加一個瓦片層; 在我們的例子中,它是一個 Mapbox Streets 瓦片層。 我們可以通過實例化 TileLayer 類來附加各種類型的瓦片層。

要創建切片圖層,我們需要設置切片圖像的 URL 模板、屬性文本和圖層的最大縮放級別。 URL 模板使我們能夠從服務提供商處訪問所需的切片圖層。 由於我們使用的是 Mapbox 的 Static Tiles API,我們需要請求一個訪問令牌。

 L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery (c) <a href="https://www.mapbox.com/">Mapbox</a>', maxZoom: 18, id: 'mapbox/streets-v11', accessToken: 'your.mapbox.access.token' }).addTo(mymap);

此時,如果我們在瀏覽器中打開我們的 index.html,我們應該能夠看到舊金山的地圖。 讓我們在地圖上放一個大頭針。

標記和圓圈

我們已經有了地圖和圖層,但它並沒有為我們指出任何具體的東西。 為了指向地圖上的特定位置,Leaflet 為我們提供了標記。

為了固定位置,我們使用 Marker 類實例化標記,傳入坐標,並將其添加到地圖中。 這裡我們使用的是城市中雙峰的坐標。

 const marker = L.marker([37.7544, -122.4477]).addTo(mymap);

同樣,我們可以使用Circle類將圓綁定到地圖。 我們傳入了一些可選的選項,例如半徑、顏色等。 對於circle標記,我們傳入 Point Bonita Lighthouse 的坐標。

 const circle = L.circle([37.8157, -122.5295], { color: 'gold', fillColor: '#f03', fillOpacity: 0.5, radius: 200 }).addTo(mymap);

彈出窗口

這一切都很好,但是如果我們想傳遞更多關於該位置的信息怎麼辦。 我們使用彈出窗口來做到這一點。

 circle.bindPopup("I am pointing to Point Bonita Lighthouse"); marker.bindPopup("I am pointing to Twin Peaks");

bindPopup 方法接受指定的 HTML 內容並將其附加到標記,因此當您單擊標記時會出現彈出窗口。

反應傳單

現在我們知道如何創建地圖,並使用 Leaflet 和 vanilla JavaScript 添加標記。 讓我們看看如何使用 React 實現相同的結果。 我們不會製作相同的應用程序,而是製作高級應用程序。

我們的首要任務是從舊金山開放數據門戶獲取訪問令牌。 這是一個在線門戶網站,我們可以在其中找到來自舊金山市和縣的數百個數據集。 我決定使用這個資源,但是我們可以使用很多其他資源。

訪問 API 密鑰

  1. 創建一個帳戶並登錄到門戶。
  2. 單擊右下角的管理鏈接。
  3. 單擊 Create New API Key 並為其命名。
  4. 複製您的密鑰 ID 和密鑰密鑰。 你需要這個來訪問數據。

為此,我們將使用 React-Leaflet – 用於 Leaflet 地圖的反應組件。 讓我們創建一個反應應用程序。

 npx create-react-app react-fire-incidents cd react-fire-incidents

然後讓我們通過在終端中運行以下命令來安裝react-leaflet和 Leaflet:

 npm install react-leaflet leaflet

應用程序.js

讓我們在src中創建一個文件夾/components 。 在組件內部,讓我們創建一個名為Map.js的文件。 這就是我們的地圖所在的地方。 現在讓我們通過刪除不必要的代碼並從react-leaflet axios和新創建的Map.js中導入模塊來編輯App.js。

 import React, { Component, Fragment } from 'react'; import axios from 'axios'; import Map from './components/Map'

在我們的 App 類中,我們將在我們的狀態中定義一個稱為事件的數組——當頁面加載時,我們會將我們的數據推送到這個數組中。

 class App extends Component { state = { incidents: [], } render() { return ( <div> </div> ); } } export default App;

接下來,我們將在組件掛載時發出 GET 請求。 我們有應用令牌,但我們仍然需要一個端點。 我們在哪裡找到端點?

讓我們前往門戶並單擊瀏覽數據。 在搜索欄中,讓我們搜索火災事件。 出現的第一個結果就是我們正在尋找的結果。 單擊鏈接後,我們可以通過單擊右上角的 API 按鈕獲取 URL。

我們將端點傳遞給我們的 GET 請求,並傳入一個限制和我們的應用程序令牌作為參數。 原始數據有數千條記錄,但為了簡單起見,我們將其限制為 500 條。我們使用結果更新事件數組。

一旦我們得到數據,我們就會更新我們的狀態。

 async componentDidMount() { const res = await axios.get('https://data.sfgov.org/resource/wr8u-xric.json', { params: { "$limit": 500, "$$app_token": YOUR_APP_TOKEN } }) const incidents = res.data; this.setState({incidents: incidents }); };

這就是我們的 App.js 應該是什麼樣子。

 class App extends Component { state = { incidents: [], } async componentDidMount() { const res = await axios.get('https://data.sfgov.org/resource/wr8u-xric.json', { params: { "$limit": 500, "$$app_token": YOUR_APP_TOKEN } }) const incidents = res.data; this.setState({incidents: incidents }); }; render() { return ( <Map incidents={this.state.incidents}/> ); } } export default App;

地圖.js

由於我們已經知道如何創建 Leaflet 地圖,這部分會相對容易。 我們將從react-leaflet導入MapTileLayerMarkerPopup組件。

 import React, { Component } from 'react' import { Map, TileLayer, Marker, Popup } from 'react-leaflet'

如果我們記得前面的例子,我們需要坐標和縮放級別來初始化地圖。 在我們的Map類中,我們使用latlngzoom變量在我們的狀態中定義它們。

 export default class Map extends Component { state = { lat: 37.7749, lng: -122.4194, zoom: 13, } render() { return ( <div></div> ) } }

然後我們將檢查我們的事件數組是否為空。 如果它是空的,我們將返回一條消息說“數據正在加載”; 否則,我們將返回一張地圖。

在我們的react-leafletMap組件中,我們將傳遞中心坐標和縮放級別以及一些樣式。 在我們的TileLayer組件中,我們將傳遞屬性和 URL,類似於我們之前的示例。

 render() { return ( this.props.incidents ? <Map center={[this.state.lat, this.state.lng]} zoom={this.state.zoom} style={{ width: '100%', height: '900px'}} > <TileLayer attribution='&copy <a href="https://osm.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> </Map> : 'Data is loading...' ) } }

接下來,我們遍歷我們的props.incident並將每個事件的坐標傳遞給 Marker 組件。 由於 React 警告我們為數組中的每個項目傳遞一個鍵,我們也將一個鍵傳遞給 Marker。

Marker組件中,我們傳入一個Popup組件。 我在彈出窗口中添加了一些有關事件的信息。

 <Map center={[this.state.lat, this.state.lng]} zoom={this.state.zoom} style={{ width: '100%', height: '900px'}}> <TileLayer attribution='&copy <a href="https://osm.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> { this.props.incidents.map(incident => { const point = [incident['point']['coordinates'][1], incident['point']['coordinates'][0]] return ( <Marker position={point} key={incident['incident_number']} > <Popup> <span>ADDRESS: {incident['address']}, {incident['city']} - {incident['zip_code']}</span> <br/> <span>BATTALION: {incident['battalion']}</span><br/> </Popup> </Marker> ) }) } </Map>

就是這樣。 如果我們運行我們的應用程序,並且一切正常,我們應該能夠看到舊金山的地圖,其中有 500 個標記將我們指向火災事件的位置。 如果我們單擊其中一個標記,則會出現一個彈出窗口,其中包含有關該事件的更多信息。

包起來

儘管我們涵蓋了很多內容,但這只是基礎知識。 Leaflet 是一個非常強大的工具,我們可以創建很多不同類型的地圖。 如果您想嘗試一下,請嘗試添加另一個圖層或自定義圖標。 或者,也許您想創建一個交互式 Choropleth Map。