将 MapBox 与 Next.js 集成——比 Google Maps 更经济实惠的替代方案
让我们开始编程吧!
最近,我一直在开发一个面向生态酒店和度假村的在线平台,需要渲染一张带有可点击标记的地图(我们将在本教程中演示)。在网上搜索各种解决方案后,我立刻想到了两个选择——谷歌地图和 Mapbox。虽然谷歌地图在我们生活中无处不在,我们大多数人都很熟悉它,但我发现将其集成到应用程序中并非理想之选。由于这是一个自筹资金的项目,我希望尽可能降低成本,而谷歌地图的定价结构意味着最终成本会相当可观。
进入 MapBox!
MapBox拥有极具竞争力的定价结构(网页端前 5 万次请求免费)以及易于使用且文档齐全的 API,是 Google Maps 的一个不错的替代方案。此外,它还基于开源地图项目OpenStreetMap构建。可谓双赢!
我们正在建造什么?
我们将查询 Mapbox 的搜索 API,获取一家名为 Greggs 的知名咖啡店的位置,搜索范围限定在大伦敦地区。然后,我们将这些位置渲染到 Mapbox<Map>组件中,并添加一系列可点击的标记。点击这些标记后,会弹出一些可关闭的窗口。
让我们开始编程吧!
创建 MapBox 帐户以获取访问令牌
首先,你需要创建一个 MapBox 账号,以便获取访问令牌。我们将使用此令牌向 MapBox 的各个 API 发送请求。
获得访问令牌后,就可以开始搭建你自己的 Next.js 项目,并集成 MapBox 提供的所有强大功能了。
创建一个新的 Next.js 项目(如果您已经有一个自己的项目,请跳过此步骤)
设置 Next.js 项目非常简单,您可以按照官方文档中的说明进行操作,也可以运行以下命令来设置一个新的 Next.js 项目(请确保您已安装 Node.js)。
npx create-next-app mapbox-project
然后,cd进入该目录并运行 `npm run dev`或 `npm run dev`mapbox-project来启动开发服务器。瞧!你的 Next.js 项目已经启动并运行了!npm run devyarn dev
设置 MapBox 地图
接下来,我们需要在项目中渲染 MapBox 地图。为此,我们需要添加Uber 团队编写的MapBox 库react-map-gl。该库包含一套用于 MapBox 的 React 组件。运行以下命令即可将此库添加到您的项目中:
yarn add react-mapbox-gl
接下来,我们将创建一个 Map 组件,它将位于components/Map.js。
创建 Map.js 文件,并将以下代码添加到其中:
import { useState } from "react";
import ReactMapGL from "react-map-gl";
export default function Map() {
const [viewport, setViewport] = useState({
width: "100%",
height: "100%",
// The latitude and longitude of the center of London
latitude: 51.5074,
longitude: -0.1278,
zoom: 10
});
return <ReactMapGL
mapStyle="mapbox://styles/mapbox/streets-v11"
mapboxApiAccessToken={process.env.MAPBOX_KEY}
{...viewport}
onViewportChange={(nextViewport) => setViewport(nextViewport)}
>
</ReactMapGL>
}
目前这样做行不通。Next.js 的最大特性之一就是它提供的服务器端渲染功能。然而,MapBox 需要全局 window 对象才能正常工作。如果你的应用采用服务器端渲染,你需要将其动态导入到页面中。这意味着你不能像导入普通组件那样导入它,
import Map from '../components/Map'
我们需要动态导入它。我们将使用 Next.js 来实现这一点dynamic imports。
在您的pages/index.js文件中(或您渲染 Map 组件的任何位置),添加以下代码。
const Map = dynamic(() => import("../components/Map"), {
loading: () => "Loading...",
ssr: false
});
这意味着我们的 MapBox 组件现在将选择性地在客户端渲染。完美!
现在我们只需要将 Mapbox 的 CSS 文件添加到项目中即可。最简单的方法是修改现有 CSS 文件_app.js或添加自定义_document.js文件。然后在渲染函数中添加指向该 CSS 文件的链接。您可以在 Mapbox 的API 文档<Head>中获取最新版本的 CSS 文件。
<head>
<link href='https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css' rel='stylesheet' />
</head>
完美!您的地图现在应该可以正常显示了!让我们更进一步,尝试在地图上渲染一些可点击的图钉。
使用 MapBox 的搜索 API 获取地标列表
Mapbox 提供了一个非常便捷的地理编码 API,可用于获取包含经纬度的地点列表。我们将获取伦敦所有 Greggs(一家外卖快餐咖啡店)的列表,并将它们以图钉的形式显示在地图上。
首先,让我们通过向 Mapbox 地理编码 API 添加一个简单的 fetch 调用来查询列表。我们希望在伦敦的地理范围内进行搜索,并将搜索结果限制在 10 个以内(伦敦面积很大,而且伦敦人非常喜欢 Greggs 的素食香肠卷。我们不想被所有可能的结果淹没!)。Mapbox 的地理编码地点搜索 API 接受以下参数以及一些额外的查询字符串。
/geocoding/v5/mapbox.places/{search_text}.json
我们将使用 limit 查询参数将结果数量限制为 10 条,并使用 bbox 参数指定伦敦的经纬度范围。
考虑到这些因素,我们的搜索 URL 将类似于这样:
https://api.mapbox.com/geocoding/v5/mapbox.places/greggs.json?access_token=${process.env.MAPBOX_KEY}&bbox=-0.227654%2C51.464102%2C0.060737%2C51.553421&limit=10
我们可以使用这个 URL,在页面中发起一个简单的 fetch 请求。修改后的页面现在看起来会像这样:
const Map = dynamic(() => import("../components/Map"), {
loading: () => "Loading...",
ssr: false
});
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/greggs.json?access_token=${process.env.MAPBOX_KEY}&bbox=-0.227654%2C51.464102%2C0.060737%2C51.553421&limit=10`;
export default function IndexPage() {
const [locations, setLocations] = useState([]);
useEffect(() => {
const fetchLocations = async () => {
await fetch(url).then((response) =>
response.text()).then((res) => JSON.parse(res))
.then((json) => {
setLocations(json.features);
}).catch((err) => console.log({ err }));
};
fetchLocations();
}, []);
return (<Container>
<Map />
</Container>);
}
我们现在列出了10家Greggs门店!
利用搜索结果在地图上显示图钉
现在我们有了地点列表,就可以将它们渲染到地图上了。地图react-map-gl自带一个方便的<Marker>组件,可以让我们轻松完成这项任务。首先,我们需要将这些地点传递给我们的<Map>组件。
return (<Container>
<Map locations={locations} />
</Container>);
现在,在我们的地图组件中,我们需要将每个位置的纬度和经度传递给<Marker>组件,从而为每个位置渲染一个图钉。
我们最终的地图组件看起来会像这样:
import { useState } from "react";
import ReactMapGL, { Marker } from "react-map-gl";
export default function Map({ locations }) {
const [viewport, setViewport] = useState({
width: "100%",
height: "100%",
// The latitude and longitude of the center of London
latitude: 51.5074,
longitude: -0.1278,
zoom: 10
});
return <ReactMapGL
mapStyle="mapbox://styles/mapbox/streets-v11"
mapboxApiAccessToken={process.env.MAPBOX_KEY}
{...viewport}
onViewportChange={(nextViewport) => setViewport(nextViewport)}
>
{locations.map((location) => (
<div key={location.id}>
<Marker
latitude={location.center[1]}
longitude={location.center[0]}
offsetLeft={-20}
offsetTop={-10}>
<span role="img" aria-label="push-pin">📌</span>
</Marker>
</div>
))}
</ReactMapGL>
}
使引脚可点击
我们快完成了!为了让这些地图功能齐全且具有交互性,我们最后要做的就是添加一个显示地点名称的弹出窗口。Mapbox 自带一个非常方便的 Popup 组件,可以轻松实现这一点。我们只需在图钉上添加一个 onClick 事件处理程序,用于捕获所选位置的详细信息,然后将所选位置的纬度和经度传递给我们的<Popup>组件。马上就明白了!
在 Map 组件中,添加一个useState钩子来捕获所选位置。
export default function Map({ locations }) {
// UseState hook
const [selectedLocation, setSelectedLocation] = useState({})
const [viewport, setViewport] = useState({
width: "100%",
height: "100%",
// The latitude and longitude of the center of London
latitude: 51.5074,
longitude: -0.1278,
zoom: 10
});
......
我们还将修改渲染块,添加 onClick 处理程序和<Popup>我们刚才提到的组件。
......
return <ReactMapGL
mapStyle="mapbox://styles/mapbox/streets-v11"
mapboxApiAccessToken={process.env.MAPBOX_KEY}
{...viewport}
onViewportChange={(nextViewport) => setViewport(nextViewport)}
>
{locations.map((location) => (
<div key={location.id}>
<Marker
latitude={location.center[1]}
longitude={location.center[0]}
offsetLeft={-20}
offsetTop={-10}>
<a onClick={() => {
setSelectedLocation(location);
}}>
<span role="img" aria-label="push-pin">📌</span>
</a>
</Marker>
{selectLocation.id === location.id ? (
<Popup
onClose={() => setSelectedLocation({})}
closeOnClick={true}
latitude={location.center[1]}
longitude={location.center[0]}>
{location.place_name}
</Popup>) : (false)}
</div>
))}
</ReactMapGL>
}
该<Popup>组件接受一个 onClose 处理程序,该处理程序将 selectedLocation 设置为 {}。
就这样!我们成功渲染了地图,在地图上渲染了一些图钉,并且通过弹出窗口实现了点击功能!这就是我们的最终成果:
文章来源:https://dev.to/niharikak101/integrating-mapbox-with-next-js-the-cheaper-alternative-to-google-maps-g39

