如何使用 React 和 Leaflet 創建地圖
已發表: 2022-03-10從 CSV 或 JSON 文件中獲取信息不僅複雜,而且乏味。 以視覺輔助的形式表示相同的數據更簡單。 在本文中,我們將在地圖上表示 SF 消防部門響應的非醫療火災事件的位置。
對於本教程,我們將使用以下工具:
- 傳單
用於交互式地圖的 JavaScript 庫 - 反應
用於構建用戶界面的 JavaScript 庫 - 反應傳單
Leaflet 地圖的 React 組件
什麼是傳單?
Leaflet.js 擁有大約 27k 顆星,是用於移動友好型交互式地圖的領先開源 JavaScript 庫之一。 它在現代瀏覽器上利用 HTML5 和 CSS3,同時在舊瀏覽器上也可以訪問。 總而言之,它支持所有主要的桌面和移動平台。
Leaflet 重約 38KB,非常適合基本的東西。 對於其他擴展,可以使用大量插件對其進行擴展。
許多報紙,包括 NPR、華盛頓郵報、波士頓環球報等,以及其他組織都使用 Leaflet 進行深入的數據項目。
例如,《舊金山紀事報》開展了一個名為“加州火災追踪器”的項目——這是一個交互式地圖,使用 Leaflet 提供有關加州各地燃燒的野火的信息。 他們不僅查明了火災的起源,還向我們展示了火災的軌跡。
由於這是一個介紹性教程,我們將僅標記火災事件的位置並顯示有關它的一些詳細信息。
在進入 React 之前,讓我們了解 Leaflet 的基礎知識。 為此,我們將創建一個簡單的示例,我們將在其中設置傳單地圖,使用標記和彈出窗口。
首先,讓我們在/project
文件夾中創建index.html和app.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 類在頁面上創建地圖。 我們將兩個參數傳遞給這個類:
- 我們傳入一個代表
DOM
ID 的字符串變量 - 帶有映射選項的可選對象文字
我們可以將許多選項傳遞給我們的班級,但主要的兩個選項是中心和縮放。 中心定義了地圖的初始地理中心,而縮放指定了初始地圖縮放級別。 默認情況下,它們都是未定義的。
對於中心,我們傳入舊金山的坐標。 有很多地方我們可以執行正向和反向地理編碼,但是對於像這樣的基本搜索,我們可以穀歌它。
通常,縮放值取決於您要顯示的內容。 你想展示一個城市還是一個州? 國家還是大陸? 繼續嘗試縮放值以獲得更好的想法。 在這個例子中,我們選擇了 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 密鑰
- 創建一個帳戶並登錄到門戶。
- 單擊右下角的管理鏈接。
- 單擊 Create New API Key 並為其命名。
- 複製您的密鑰 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
導入Map
、 TileLayer
、 Marker
、 Popup
組件。
import React, { Component } from 'react' import { Map, TileLayer, Marker, Popup } from 'react-leaflet'
如果我們記得前面的例子,我們需要坐標和縮放級別來初始化地圖。 在我們的Map
類中,我們使用lat
、 lng
和zoom
變量在我們的狀態中定義它們。
export default class Map extends Component { state = { lat: 37.7749, lng: -122.4194, zoom: 13, } render() { return ( <div></div> ) } }
然後我們將檢查我們的事件數組是否為空。 如果它是空的,我們將返回一條消息說“數據正在加載”; 否則,我們將返回一張地圖。
在我們的react-leaflet
的Map
組件中,我們將傳遞中心坐標和縮放級別以及一些樣式。 在我們的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='© <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='© <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。