如何使用 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。