发布于 2026-01-06 4 阅读
0

将 MapBox 与 Next.js 集成——比 Google Maps 更便宜的替代方案!让我们开始编码吧!

将 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
Enter fullscreen mode Exit fullscreen mode

然后,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
Enter fullscreen mode Exit fullscreen mode

接下来,我们将创建一个 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>
}
Enter fullscreen mode Exit fullscreen mode

目前这样做行不通。Next.js 的最大特性之一就是它提供的服务器端渲染功能。然而,MapBox 需要全局 window 对象才能正常工作。如果你的应用采用服务器端渲染,你需要将其动态导入到页面中。这意味着你不能像导入普通组件那样导入它,

import Map from '../components/Map'
Enter fullscreen mode Exit fullscreen mode

我们需要动态导入它。我们将使用 Next.js 来实现这一点dynamic imports

在您的pages/index.js文件中(或您渲染 Map 组件的任何位置),添加以下代码。

const Map = dynamic(() => import("../components/Map"), {
  loading: () => "Loading...",
  ssr: false
});
Enter fullscreen mode Exit fullscreen mode

这意味着我们的 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>
Enter fullscreen mode Exit fullscreen mode

完美!您的地图现在应该可以正常显示了!让我们更进一步,尝试在地图上渲染一些可点击的图钉。

使用 MapBox 的搜索 API 获取地标列表

Mapbox 提供了一个非常便捷的地理编码 API,可用于获取包含经纬度的地点列表。我们将获取伦敦所有 Greggs(一家外卖快餐咖啡店)的列表,并将它们以图钉的形式显示在地图上。
首先,让我们通过向 Mapbox 地理编码 API 添加一个简单的 fetch 调用来查询列表。我们希望在伦敦的地理范围内进行搜索,并将搜索结果限制在 10 个以内(伦敦面积很大,而且伦敦人非常喜欢 Greggs 的素食香肠卷。我们不想被所有可能的结果淹没!)。Mapbox 的地理编码地点搜索 API 接受以下参数以及一些额外的查询字符串。

/geocoding/v5/mapbox.places/{search_text}.json
Enter fullscreen mode Exit fullscreen mode

我们将使用 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

Enter fullscreen mode Exit fullscreen mode

我们可以使用这个 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>);
}
Enter fullscreen mode Exit fullscreen mode

我们现在列出了10家Greggs门店!

利用搜索结果在地图上显示图钉

现在我们有了地点列表,就可以将它们渲染到地图上了。地图react-map-gl自带一个方便的<Marker>组件,可以让我们轻松完成这项任务。首先,我们需要将这些地点传递给我们的<Map>组件。

return (<Container>
    <Map locations={locations} />
  </Container>);
Enter fullscreen mode Exit fullscreen mode

现在,在我们的地图组件中,我们需要将每个位置的纬度和经度传递给<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>
}
Enter fullscreen mode Exit fullscreen mode

使引脚可点击

我们快完成了!为了让这些地图功能齐全且具有交互性,我们最后要做的就是添加一个显示地点名称的弹出窗口。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
  });
......
Enter fullscreen mode Exit fullscreen mode

我们还将修改渲染块,添加 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>
}
Enter fullscreen mode Exit fullscreen mode

<Popup>组件接受一个 onClose 处理程序,该处理程序将 selectedLocation 设置为 {}。

就这样!我们成功渲染了地图,在地图上渲染了一些图钉,并且通过弹出窗口实现了点击功能!这就是我们的最终成果:

地图

文章来源:https://dev.to/niharikak101/integrating-mapbox-with-next-js-the-cheaper-alternative-to-google-maps-g39