How To Build A Geocoding App In Vue.js Using Mapbox
Pinpoint accuracy and modularity are among the perks that make geocodes the perfect means of finding a particular location.
In this guide, we’ll build a simple geocoding app from scratch, using Vue.js and Mapbox. We’ll cover the process from building the front-end scaffolding up to building a geocoder to handle forward geocoding and reverse geocoding. To get the most out of this guide, you’ll need a basic understanding of JavaScript and Vue.js and how to make API calls.
What Is Geocoding?
Geocoding is the transformation of text-based locations to geographic coordinates (typically, longitude and latitude) that indicate a location in the world.
Geocoding is of two types: forward and reverse. Forward geocoding converts location texts to geographic coordinates, whereas reverse geocoding converts coordinates to location texts.
In other words, reverse geocoding turns 40.714224, -73.961452 into “277 Bedford Ave, Brooklyn”, and forward geocoding does the opposite, turning “277 Bedford Ave, Brooklyn” into 40.714224, -73.961452.
To give more insight, we will build a mini web app that uses an interactive web map with custom markers to display location coordinates, which we will subsequently decode to location texts.
Our app will have the following basic functions:
- give the user access to an interactive map display with a marker;
- allow the user to move the marker at will, while displaying coordinates;
- return a text-based location or location coordinates upon request by the user.
Set Up Project Using Vue CLI
We’ll make use of the boilerplate found in this repository. It contains a new project with the Vue CLI and yarn
as a package manager. You’ll need to clone the repository. Ensure that you’re working from the geocoder/boilerplate
branch.
Set Up File Structure of Application
Next, we will need to set up our project’s file structure. Rename the Helloworld.vue
file in the component’s folder to Index.vue
, and leave it blank for now. Go ahead and copy the following into the App.vue
file:
<template>
<div id="app">
<!--Navbar Here -->
<div>
<nav>
<div class="header">
<h3>Geocoder</h3>
</div>
</nav>
</div>
<!--Index Page Here -->
<index />
</div>
</template>
<script>
import index from "./components/index.vue";
export default {
name: "App",
components: {
index,
},
};
</script>
Here, we’ve imported and then registered the recently renamed component locally. We’ve also added a navigation bar to lift our app’s aesthetics.
We need an .env
file to load the environment variables. Go ahead and add one in the root of your project folder.
Install Required Packages and Libraries
To kickstart the development process, we will need to install the required libraries. Here’s a list of the ones we’ll be using for this project:
- Mapbox GL JS
This JavaScript library uses WebGL to render interactive maps from vector tiles and Mapbox. - Mapbox-gl-geocoder
This geocoder control for Mapbox GL will help with our forward geocoding. - Dotenv
We won’t have to install this because it comes preinstalled with the Vue CLI. It helps us to load environment variables from an.env
file intoprocess.env
. This way, we can keep our configurations separate from our code. - Axios
This library will help us make HTTP requests.
Install the packages in your CLI according to your preferred package manager. If you’re using Yarn, run the command below:
cd geocoder && yarn add mapbox-gl @mapbox/mapbox-gl-geocoder axios
If you’re using npm, run this:
cd geocoder && npm i mapbox-gl @mapbox/mapbox-gl-geocoder axios --save
We first had to enter the geocoder
folder before running the installation command.
Scaffolding the Front End With Vue.js
Let’s go ahead and create a layout for our app. We will need an element to house our map, a region to display the coordinates while listening to the marker’s movement on the map, and something to display the location when we call the reverse geocoding API. We can house all of this within a card component.
Copy the following into your Index.vue
file:
<template>
<div class="main">
<div class="flex">
<!-- Map Display here -->
<div class="map-holder">
<div id="map"></div>
</div>
<!-- Coordinates Display here -->
<div class="dislpay-arena">
<div class="coordinates-header">
<h3>Current Coordinates</h3>
<p>Latitude:</p>
<p>Longitude:</p>
</div>
<div class="coordinates-header">
<h3>Current Location</h3>
<div class="form-group">
<input
type="text"
class="location-control"
:value="location"
readonly
/>
<button type="button" class="copy-btn">Copy</button>
</div>
<button type="button" class="location-btn">Get Location</button>
</div>
</div>
</div>
</div>
</template>
To see what we currently have, start your development server. For Yarn:
yarn serve
Or for npm:
npm run serve
Our app should look like this now:
The blank spot to the left looks off. It should house our map display. Let’s add that.
Interactive Map Display With Mapbox
The first thing we need to do is gain access to the Mapbox GL and Geocoder libraries. We’ll start by importing the Mapbox GL and Geocoder libraries in the Index.vue
file.
import axios from "axios";
import mapboxgl from "mapbox-gl";
import MapboxGeocoder from "@mapbox/mapbox-gl-geocoder";
import "@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css";
Mapbox requires a unique access token to compute map vector tiles. Get yours, and add it as an environmental variable in your .env
file.
.env
VUE_APP_MAP_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
We also need to define properties that will help with putting our map tiles together in our data instance. Add the following below the spot where we imported the libraries:
export default {
data() {
return {
loading: false,
location: "",
access_token: process.env.VUE_APP_MAP_ACCESS_TOKEN,
center: [0, 0],
map: {},
};
},
}
- The
location
property will be modeled to the input that we have in our scaffolding. We will use this to handle reverse geocoding (i.e. display a location from the coordinates). - The
center
property houses our coordinates (longitude and latitude). This is critical to putting our map tiles together, as we will see shortly. - The
access_token
property refers to our environmental variable, which we added earlier. - The
map
property serves as a constructor for our map component.
Let’s proceed to create a method that plots our interactive map with our forward geocoder embedded in it. This method is our base function, serving as an intermediary between our component and Mapbox GL; we will call this method createMap
. Add this below the data object:
mounted() {
this.createMap()
},
methods: {
async createMap() {
try {
mapboxgl.accessToken = this.access_token;
this.map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/streets-v11",
center: this.center,
zoom: 11,
});
} catch (err) {
console.log("map error", err);
}
},
},
To create our map, we’ve specified a container
that houses the map, a style
property for our map’s display format, and a center
property to house our coordinates. The center
property is an array type and holds the longitude and latitude.
Mapbox GL JS initializes our map based on these parameters on the page and returns a Map
object to us. The Map
object refers to the map on our page, while exposing methods and properties that enable us to interact with the map. We’ve stored this returned object in our data instance, this.map
.
Forward Geocoding With Mapbox Geocoder
Now, we will add the geocoder and custom marker. The geocoder handles forward geocoding by transforming text-based locations to coordinates. This will appear in the form of a search input box appended to our map.
Add the following below the this.map
initialization that we have above:
let geocoder = new MapboxGeocoder({
accessToken: this.access_token,
mapboxgl: mapboxgl,
marker: false,
});
this.map.addControl(geocoder);
geocoder.on("result", (e) => {
const marker = new mapboxgl.Marker({
draggable: true,
color: "#D80739",
})
.setLngLat(e.result.center)
.addTo(this.map);
this.center = e.result.center;
marker.on("dragend", (e) => {
this.center = Object.values(e.target.getLngLat());
});
});
Here, we’ve first created a new instance of a geocoder using the `MapboxGeocoder` constructor. This initializes a geocoder based on the parameters provided and returns an object, exposed to methods and events. The `accessToken` property refers to our Mapbox access token, and `mapboxgl` refers to the [map library](https://docs.mapbox.com/#maps) currently used.
Core to our app is the custom marker; the geocoder comes with one by default. This, however, wouldn’t give us all of the customization we need; hence, we’ve disabled it.
Moving along, we’ve passed our newly created geocoder as a parameter to the `addControl` method, exposed to us by our map object. `addControl` accepts a `control` as a parameter.
To create our custom marker, we’ve made use of an event exposed to us by our geocoder object. The `on` event listener enables us to subscribe to events that happen within the geocoder. It accepts various [events](https://github.com/mapbox/mapbox-gl-geocoder/blob/master/API.md#on) as parameters. We’re listening to the `result` event, which is fired when an input is set.
In a nutshell, on `result`, our marker constructor creates a marker, based on the parameters we have provided (a draggable attribute and color, in this case). It returns an object, with which we use the `setLngLat` method to get our coordinates. We append the custom marker to our existing map using the `addTo` method. Finally, we update the `center` property in our instance with the new coordinates.
We also have to track the movement of our custom marker. We’ve achieved this by using the `dragend` event listener, and we updated our `center` property with the current coordinates.
Let’s update the template to display our interactive map and forward geocoder. Update the coordinates display section in our template with the following:
<div class="coordinates-header">
<h3>Current Coordinates</h3>
<p>Latitude: {{ center[0] }}</p>
<p>Longitude: {{ center[1] }}</p>
</div>
Remember how we always updated our center
property following an event? We are displaying the coordinates here based on the current value.
To lift our app’s aesthetics, add the following CSS file in the head
section of the index.html
file. Put this file in the public folder.
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.css" rel="stylesheet" />
Our app should look like this now:
Reverse Geocode Location With Mapbox API
Now, we will handle reverse geocoding our coordinates to text-based locations. Let’s write a method that handles that and trigger it with the Get Location
button in our template.
Reverse geocoding in Mapbox is handled by the reverse geocoding API. This accepts longitude
, latitude
, and access token
as request parameters. This call returns a response payload — typically, with various details. Our concern is the first object in the features
array, where the reverse geocoded location is.
We’ll need to create a function that sends the longitude
, latitude
and access_token
of the location we want to get to the Mapbox API. We need to send them in order to get the details of that location.
Finally, we need to update the location
property in our instance with the value of the place_name
key in the object.
Below the createMap()
function, let’s add a new function that handles what we want. This is how it should look:
async getLocation() {
try {
this.loading = true;
const response = await axios.get(
https://api.mapbox.com/geocoding/v5/mapbox.places/${this.center[0]},${this.center[1]}.json?access_token=${this.access_token}
);
this.loading = false;
this.location = response.data.features[0].place_name;
} catch (err) {
this.loading = false;
console.log(err);
}
},
This function makes a GET
request to the Mapbox API. The response contains place_name
— the name of the selected location. We get this from the response and then set it as the value of this.location
.
With that done, we need to edit and set up the button that will call this function we have created. We’ll make use of a click
event listener — which will call the getLocation
method when a user clicks on it. Go ahead and edit the button component to this.
<button
type="button"
:disabled="loading"
:class="{ disabled: loading }"
class="location-btn"
@click="getLocation"
>
Get Location
</button>
As icing on the cake, let’s attach a function to copy the displayed location to the clipboard. Add this just below the getLocation
function:
copyLocation() {
if (this.location) {
navigator.clipboard.writeText(this.location);
alert("Location Copied")
}
return;
},
Update the Copy
button component to trigger this:
<button type="button" class="copy-btn" @click="copyLocation">
Conclusion
In this guide, we’ve looked at geocoding using Mapbox. We built a geocoding app that transforms text-based locations to coordinates, displaying the location on an interactive map, and that converts coordinates to text-based locations, according to the user’s request. This guide is just the beginning. A lot more could be achieved with the geocoding APIs, such as changing the presentation of the map using the various map styles provided by Mapbox.
- The source code is available on GitHub.
Resources
- “Geocoding”, Mapbox documentation
- “Styles”, Mapbox documentation
- “Using Env Variables in Client-Side Code”, in “Modes and Environment Variables”, Vue CLI