Geocoding With Vue.js And Mapbox

Geocoding converts text-based locations into geographic coordinates, and reverse geocoding does the opposite. A typical reverse geocoding call turns 40.714224, -73.961452 into "277 Bedford Ave, Brooklyn"; forward geocoding takes that address and returns the coordinates.

Building a small geocoding app with Vue.js and Mapbox demonstrates both directions in one interface. The app we'll construct provides an interactive map with a draggable marker, displays the marker's coordinates as it moves, and returns a text location on request.

Project Setup

Start from the boilerplate repository (the geocoder/boilerplate branch), which contains a Vue CLI project using Yarn. Rename Helloworld.vue in the components folder to Index.vue. Register it locally in a cleaned-up App.vue, and include a navigation bar for basic styling:

<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>

Add an .env file at the project root for environment variables.

Install these packages:

  • Mapbox GL JS — WebGL-based rendering of interactive maps from vector tiles.
  • Mapbox-gl-geocoder — geocoder control that handles forward geocoding.
  • Dotenv — loads .env variables into process.env (preinstalled with Vue CLI).
  • Axios — HTTP request helper.

With Yarn:

cd geocoder && yarn add mapbox-gl @mapbox/mapbox-gl-geocoder axios

With npm:

cd geocoder && npm i mapbox-gl @mapbox/mapbox-gl-geocoder axios --save

Application Scaffolding

The layout in Index.vue needs a card containing a map container, a coordinates readout that updates with marker movement, and an area for the reverse-geocoded location:

<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>

Start the dev server (Yarn: yarn serve; npm: npm run serve). The left side is blank; that's where the map goes next.

Interactive Map With Custom Marker

Import Mapbox GL and the geocoder library at the top of Index.vue:

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. Obtain one and store it in the .env file:

.env
VUE_APP_MAP_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Add these data properties:

export default {
  data() {
    return {
      loading: false,
      location: "",
      access_token: process.env.VUE_APP_MAP_ACCESS_TOKEN,
      center: [0, 0],
      map: {},
    };
  },
}
  • location holds the text result for reverse geocoding.
  • center stores longitude/latitude coordinates for the map and marker.
  • access_token references the environmental variable.
  • map will hold the Mapbox Map instance.

Below the data object, define createMap:

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);
    }
  },
},

This initializes the map with a container, a style, and a center array of [longitude, latitude]. Mapbox returns a Map object stored in this.map, exposing methods for further interaction.

Forward Geocoding With Geocoder Control

Add a search-input geocoder and custom draggable marker after the map initialization:

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());
  });
});

The MapboxGeocoder constructor takes accessToken and mapboxgl. The default marker is disabled because a custom one is needed. The geocoder object gets added to the map via addControl.

The geocoder's on event listener subscribes to events such as result, which fires when an input is selected. In that handler, a marker is constructed with draggable and color options, positioned with setLngLat, added to the map with addTo, and the center property is updated.

Marker movement is tracked through the dragend listener, which also refreshes center. The coordinates displayed in the template come directly from this property:

<div class="coordinates-header">
  <h3>Current Coordinates</h3>
  <p>Latitude: {{ center[0] }}</p>
  <p>Longitude: {{ center[1] }}</p>
</div>

Add the geocoder stylesheet to the head of index.html in the public folder:

<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.css" rel="stylesheet" />

Reverse Geocoding With The Mapbox API

Reverse geocoding uses the Mapbox API, which accepts longitude, latitude, and an access_token as request parameters. The response contains a features array; the first object carries the reverse-geocoded location under place_name.

Add a getLocation method below createMap():

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 Mapbox, extracts place_name from the response, and assigns it to this.location.

Wire the button in the template to call getLocation on click:

<button
  type="button"
  :disabled="loading"
  :class="{ disabled: loading }"
  class="location-btn"
  @click="getLocation"
>
  Get Location
</button>

Optionally add a clipboard copy function for the displayed location:

copyLocation() {
  if (this.location) {
    navigator.clipboard.writeText(this.location);
    alert("Location Copied")
  }
  return;
},

Update the Copy button to trigger it:

<button type="button" class="copy-btn" @click="copyLocation">

Summary

The completed app demonstrates both geocoding directions with Mapbox: forward geocoding through the geocoder control's search box, and reverse geocoding through direct API calls based on the marker's current coordinates. The source code is available on GitHub.

Further exploration could include Mapbox's map styles for different visual presentations. Reference materials: Mapbox Geocoding API documentation and Vue CLI environment variable guide.