Table of content
- Mapbox
- Next.js
- Initializing the map
- Adding styles
- Adding the map controls
- Loading the data
- Adding the custom markers
- Adding the marker popup
- Conclusion
Mapbox
At the time of writing this article, to use Google Maps API, you need billing information (even though it is free for certain limits). So, Mapbox can be a better alternative to Google Maps which provides, 50,000 free map loads for the web (without billing credentials). It provides a range of APIs, SDKs, and developer tools for maps, navigation, and search across platforms.
Setting up the Mapbox key
- Goto Mapbox and create an account or login if you have one.
- In your account dashboard, under Access tokens section, select Create a token button.
- In Create an access token page, name your token and hit Create token.
- While creating token you can also add different types of token restrictions and url restriction. But for current app we will simply use the default settings.
Next.js
In this article, we are using Next.js 13 for our app. We opted for App Router for our app instead of Page router. Thus, the src/app directory will contain our root route example.com/. The page.js file will define a unique page for each route, and the layout.js file will define the UI shared between multiple pages like navigation, header, and footer. We can also nest layouts.
But for simplicity, we will use the root route only. Thus, our initial file structure looks like this:
src
├─ app/
│ ├─ globals.css
│ ├─ layout.js
│ ├─ page.js
package-lock.json
package.json
Dependencies
In this app, we will use react-map-gl and react-icons. We will use the react-map-gl package to use Mapbox components in the Next.js app.
To install the dependencies run:
npm install react-map-gl react-icons --save
Adding key in Next.js environment
We will add the access token we created in the above section as an environment variable in our Next.js app. In your Next.js root folder, create a .env.local file (if you don't have one) and use the prefix NEXT_PUBLIC_
with the variable name.
# .env.local
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN=<Your_Mapbox_Access_Token>
Initializing the map
In the page.js file, we will import the Map component from the react-map-gl package. The Map component will render the map. But first, we need to supply the following values to initialize the Map component:
-
mapboxAccessToken: This attribute takes in the Mapbox access token that we had stored in the .env.local file. We can access this token using the
process.env
global variable. - mapbox-gl.css: This file consists of all the styling for our Map component and its child elements. Failing to import this file will result in the inaccurate rendering of the Map component and its elements like Marker, Popup, Navigation Controls, etc.
- initialViewState: This attribute takes in the initial location (latitude, longitude, and zoom level). When the map first loads this location will be rendered.
// page.js
"use client";
import Map from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import classes from "./Page.module.css";
export default function Home() {
const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;
return (
<main className={classes.mainStyle}>
<Map
mapboxAccessToken={mapboxToken}
mapStyle="mapbox://styles/mapbox/streets-v12"
style={classes.mapStyle}
initialViewState={{ latitude: 35.668641, longitude: 139.750567, zoom: 10 }}
maxZoom={20}
minZoom={3}
></Map>
</main>
);
}
Adding styles
In src/app/globals.css add the following style:
/* globals.css */
body {
font-family: Arial, Helvetica, sans-serif;
}
/* Overrides the original padding of the Popup component in the Map */
.mapboxgl-popup-content {
padding: 0 !important;
}
Create a src/app/Page.module.css file and add the style for map components:
/* Page.module.css */
.mainStyle {
max-width: 100%;
height: 100vh;
}
.mapStyle {
width: 100%;
height: 100%;
}
.popupTitle {
background-color: #87bd41;
font-weight: 700;
font-size: medium;
color: ivory;
padding: 10px;
}
.popupInfo {
font-weight: 400;
font-size: 14px;
padding: 10px;
}
.popupLabel {
font-weight: bold;
}
.popupWebUrl {
color: dodgerblue;
}
.popupWebUrl:active,
.popupWebUrl:focus {
outline: 0;
}
Adding the map controls
The map controls generally include Navigation controls and Geo-location controls. Under Navigation controls, we get zoom-in, zoom-out, and compass controls. Under Geo-location controls, we get user location control.
// page.js
"use client";
import Map, { NavigationControl, GeolocateControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import classes from "./Page.module.css";
export default function Home() {
const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;
return (
<main className={classes.mainStyle}>
<Map
mapboxAccessToken={mapboxToken}
mapStyle="mapbox://styles/mapbox/streets-v12"
style={classes.mapStyle}
initialViewState={{ latitude: 35.668641, longitude: 139.750567, zoom: 10 }}
maxZoom={20}
minZoom={3}
>
<GeolocateControl position="top-left" />
<NavigationControl position="top-left" />
</Map>
</main>
);
}
Our initial map looks like this:
Loading the data
For illustration, we will use some airport data. It is not structured in standard geospatial formats like GeoJSON, KML, OSM etc. It is represented in simple JSON format, but it works for now. In the project root, create a dummyData folder with the airports.json file. We are using partial data on airports in Japan, which is available here. You can get complete data from here.
Import by adding import airports from "../../dummyData/airports.json";
in import section of the page.js file. Our project structure will look like this:
dummyData
├─ airports.json
src
├─ app/
│ ├─ globals.css
│ ├─ layout.js
│ ├─ page.js
│ ├─ Page.module.css
package-lock.json
package.json
Adding the custom markers
We will use the Array.map()
function to iterate through each airport's information from the airports.json file. In each iteration, we will return the Marker component from the react-map-gl package and each Marker will represent the airport data (point of interest) on the map.
To customize the map marker, we will provide icons from the react-icons package as button to the Marker component. The onClick()
event on the button will set the selectedMarker
state and add flyTo animation to the map. To animate the map, we will use the useRef
React hook.
// page.js
"use client";
import { useState, useRef } from "react";
import Map, { Marker, Popup, NavigationControl, GeolocateControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { IoMdAirplane } from "react-icons/io";
import airports from "../../dummyData/airports.json";
import classes from "./Page.module.css";
export default function Home() {
const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;
const [selectedMarker, setSelectedMarker] = useState(null);
const mapRef = useRef(null);
const zoomToSelectedLoc = (e, airport, index) => {
// stop event bubble-up which triggers unnecessary events
e.stopPropagation();
setSelectedMarker({ airport, index });
mapRef.current.flyTo({ center: [airport.lon, airport.lat], zoom: 10 });
};
return (
<main className={classes.mainStyle}>
<Map
ref={mapRef}
// attributes...
>
{/*Geolocate and Navigation controls...*/}
{airports.map((airport, index) => {
return (
<Marker key={index} longitude={airport.lon} latitude={airport.lat}>
<button
type="button"
className="cursor-pointer"
onClick={(e) => zoomToSelectedLoc(e, airport, index)}
>
{<IoMdAirplane size={30} color="tomato" />}
</button>
</Marker>
);
})}
</Map>
</main>
);
}
Adding the marker popup
Marker popup can be useful to provide comprehensive information about the point of interest in our map. When a marker is selected, we will use React state to toggle the popup for that particular marker.
// page.js
"use client";
import { useState, useRef } from "react";
import Link from "next/link";
import Map, { Marker, Popup, NavigationControl, GeolocateControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { IoMdAirplane } from "react-icons/io";
import airports from "../../dummyData/airports.json";
import classes from "./Page.module.css";
export default function Home() {
// functions...
return (
<main className={classes.mainStyle}>
<Map
// attributes...
>
{/*Geolocate and Navigation controls...*/}
{/*Airport Markers...*/}
{selectedMarker ? (
<Popup
offset={25}
latitude={selectedMarker.airport.lat}
longitude={selectedMarker.airport.lon}
onClose={() => {
setSelectedMarker(null);
}}
closeButton={false}
>
<h3 className={classes.popupTitle}>{selectedMarker.airport.name}</h3>
<div className={classes.popupInfo}>
<label className={classes.popupLabel}>Code: </label>
<span>{selectedMarker.airport.code}</span>
<br />
<label className={classes.popupLabel}>Country: </label>
<span>{selectedMarker.airport.country}</span>
<br />
<label className={classes.popupLabel}>Website: </label>
<Link
href={selectedMarker.airport.url === "" ? "#" : selectedMarker.airport.url}
target={selectedMarker.airport.url === "" ? null : "_blank"}
className={classes.popupWebUrl}
>
{selectedMarker.airport.url === "" ? "Nil" : selectedMarker.airport.url}
</Link>
</div>
</Popup>
) : null}
</Map>
</main>
);
}
Our final map looks like this:
Conclusion
In this article, we created a map application in Next.js using the Mapbox API. We rendered the map and plotted the airport location with additional information using the popup.
You can add many features to the application and create fascinating map applications. We will explore these features in the upcoming articles. Until then, happy coding.
Thanks for reading this article. Don't forget to check out my other articles. You can support this article by sharing it. Bye 👋
Top comments (0)