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