Reverse Geocoding in Nuxtjs using Mapbox

Building a Reverse Geocoding app in Nuxtjs using Mapbox


In this article, we will have a general overview/insight into geocoding; forward geocoding, and reverse geocoding. We will go-ahead to build a mini-app that utilizes the concept of reverse geocoding to display specific locations. We would be using mapbox and Nuxtjs primarily to achieve this.

In clear terms, Geocoding involves transforming text-based locations into geographic coordinates (Usually latitude and longitude) which are symbolic for a location on earth.
Furthermore, the whole concept of geocoding can be divided into two;

Forward geocoding converts location text into geographic coordinates, turning   2 Lincoln Memorial Circle NW into –77.050,38.889.
On the other hand, Reverse geocoding turns geographic coordinates into place names, turning –77.050, 38.889 into 2 Lincoln Memorial Circle NW.
To give more perspective, our app is basically expected to do the following:

  • Loop through our response payload from our endpoint.
  • Display a list of Warehouses in a card form ( we obviously cant display all that we possibly need to on the card).
  • We need each card to dynamically re-route to a new page.
  • On the new page we’d want to display more information tied to a specific warehouse including a  geographical representation (Map) of its location.

To get this up and running, we will start by storing the Warehouse details in a JSON format. We would be using jsonbin to achieve this. 

Our App should look like this once we are done:

Learning prerequisites

Setup project using create-nuxt-app

Let’s start by creating a new folder for our application in any directory of our choice.

run the following command from your command tool:

npx create-nuxt-app warehouse

This would require us to set some project presets. Here’s what ours would look like:

Reverse Geocoding

Setup file structure for our application

So here’s yet another perk of NuxtJs; folder structuring. A bulk of what we would have needed to say configure has been handled for us. However we would have to add the following folders just to keep our work as tidy as possible.

Add the following folders inside the components folder in the root of our project:

  1. generic (Add a loading.vue file here)
  2. partials (add a side-nav.vue file here)

These on the other hand are needed but not within the components folder:

  1. The plugins folder already exist, add an element-ui.js file to it.
  2. Add a CSS folder inside the assets folder and add a main.css to it.

Our present folder structure should look as thus:

Reverse Geocoding

Install required packages and libraries

Next, we would need to install the required libraries. Here’s a list of the ones we would be using for project:

  1. Element Ui: Element Ui is basically a component based Ui library. We would be making use of a couple of its components in our project. See reference here.
  2. dotenv package:  This module would help us load environment variables from a .env file into process.env. This way we are able to separate our configurations from our code. See reference here
  3. Mapbox GL JS: Mapbox GL JS is a JavaScript library that uses WebGL to render interactive maps from vector tiles and Mapbox styles. See reference here.
  4. Axios: Basically aids us in making HTTP requests. We wouldn’t need to install this again as we added it as a preset while setting up the project

run the following command from your command tool to install:

npm i element-ui dotenv mapbox-gl --save 

To make them available on the app, go ahead and rewrite your nuxt.config.js like so:

nuxt.config.js

 if (process.env.NODE_ENV !== "production") require("dotenv").config();
export default {
    ssr: false,
    env: {
        API_SECRET_KEY: process.env.API_SECRET_KEY,
        MAP_ACCESS_TOKEN: process.env.MAP_ACCESS_TOKEN
    },
    head: {
        title: 'warehouse',
        meta: [
            { charset: 'utf-8' },
            { name: 'viewport', content: 'width=device-width, initial-scale=1' },
            { hid: 'description', name: 'description', content: '' }
        ],
        link: [
            { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
            {
                rel: 'stylesheet',
                href: 'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
            },
        ]
    },

    css: [
        '~/assets/css/main.css',
    ],
    loading: {
        color: '#223744',
        name: 'fading-circle',
        continuous: true,
        duration: 1200
    },
    plugins: [
        '@/plugins/element-ui',
    ],

    components: true,

    buildModules: [],
    modules: [
        'bootstrap-vue/nuxt',
        '@nuxtjs/axios',
    ],

    axios: {
        https: false,
        progress: true,
        retry: { retries: 3 }
    },
    // Build Configuration (https://go.nuxtjs.dev/config-build)
    build: {
        transpile: [/^element-ui/],
    }
}

The first line checks the environment before requiring the dotenv package. To give nuxt access to the environment variables, we added the env config.

   env: {
        API_SECRET_KEY: process.env.API_SECRET_KEY,
        MAP_ACCESS_TOKEN: process.env.MAP_ACCESS_TOKEN
    },

This simply registers our environment variable to the Nuxt context and will also tell Webpack to replace occurrences of our variables with the appropriate environment variable value.

PS: we are yet to add to actually add our .env file that houses the environment variable. So go ahead and add a .env file in the root of your project. For the purpose of our project, it would house just two variables.

.env

API_SECRET_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MAP_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The variable API_SECRET_KEY is one from jsonbin. Yes you guessed right, our json file holder. The MAP_ACCESS_TOKEN on the other hand is from Mapbox which we are using to serve our map tiles. See here to get one.

PS: These actually hold actual values other than the prepended xxxxxxxxxxxxx

Taking a step further in our whole dependency and library setup, add the following line to the element-ui.js file.

element-ui.js

import Vue from 'vue'
import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'
export default () => {
    Vue.use(Element, { locale })
}

We just basically imported the library here. Vue.use automatically prevents us from using the same plugin more than once, so calling it multiple times on the same plugin will install the plugin only once.

We also have it properly referenced as a plugin as well as part of the build property in our nuxt.config.js file.

   plugins: [
        '@/plugins/element-ui'
    ]


 build: {
        transpile: [/^element-ui/]
    }

Finally, we would add a basic scaffold for the side-nav and loading property. So go ahead and add the following to side-nav.vue and loading.vue files.

side-nav.vue

<template>
  <div>
    <el-menu
      default-active="2"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose"
      :collapse="isCollapse"
      background-color=' #223744'
         text-color="#ffffff"
         active-text-color='#fff'
    >
      <el-menu-item index="2" class="mt-3">
        <i class="el-icon-menu"></i>
        <span slot="title">Dashboard</span>
      </el-menu-item>
      <el-menu-item index="4" disabled>
        <i class="el-icon-setting"></i>
        <span slot="title">Settings</span>
      </el-menu-item>
    </el-menu>
  </div>
</template>
<style>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
</style>
<script>
export default {
  data() {
    return {
      isCollapse: false,
    };
  },
  methods: {
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
  },
};
</script>
<style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse){
    min-height: 100vh;
}
</style>

loading.vue

<template>
  <div class="overlay">
  </div>
</template>
<script>
export default {
  mounted() {
    this.$nextTick(() => {
      this.$nuxt.$loading.start();
    });
  },
  destroyed() {
    setTimeout(() => this.$nuxt.$loading.finish(), 100);
  }
};
</script>
<style>
.overlay {
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  position: fixed;
  background: rgba(0, 0, 0, 0.1);
  opacity: 0.1;
  z-index: 2;
}
</style>

Import and use the newly created side-nav component in the default layout. Go ahead and re-write the component as thus:

default.vue

 <template>
  <div class="d-flex no-gutters h-100">
    <div class="">
      <sideNav />
    </div>
    <div style="flex-grow: 1; background:#fbfbfd">
      <nuxt />
    </div>
  </div>
</template>
<script>
import sideNav from "~/components/partials/side-nav";
export default {
  components: {
    sideNav
  },
};
</script>

To enhance the aesthetics of our already gotten scaffold, add the following to our main.css file.

main.css

@import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800&display=swap');
html {
    font-family: 'Nunito Sans', sans-serif;
    -ms-text-size-adjust: 100%;
    -webkit-text-size-adjust: 100%;
    -moz-osx-font-smoothing: grayscale;
    -webkit-font-smoothing: antialiased;
    box-sizing: border-box;
}
h2 {
    color: #000000;
    font-weight: 600;
    font-size: 1.5rem;
}
.display_cards {
    cursor: pointer;
}
.registered {
    color: #67c23a;
}
.header {
    font-weight: bold;
}
.value {
    color: #a6a9ad;
    font-size: 0.75rem;
    font-weight: 600;
}
.mapboxgl-ctrl-attrib-inner {
    display: none !important;
}
button:focus {
    outline: 0;
}
p {
    font-size: 0.875rem;
}
.form-control {
    background: rgba(196, 196, 196, 0.15);
    border-radius: 30px;
    border: none;
    padding: 1.375rem 1.75rem;
}
.form-control:focus {
    border: 1px solid #223744;
    border-color: #223744;
    box-shadow: none;
}
.main {
    background: #fbfbfd;
    height: calc(100vh - 72px);
    overflow-y: scroll;
}
.border_bottom {
    border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.loader {
    border: 10px solid #f3f3f3;
    border-radius: 50%;
    border-top: 10px solid #223744;
    width: 72px;
    height: 72px;
    -webkit-animation: spin 2s linear infinite;
    animation: spin 2s linear infinite;
    margin: auto;
    margin-top: 60px;
}
@-webkit-keyframes spin {
    0% {
        -webkit-transform: rotate(0deg);
    }
    100% {
        -webkit-transform: rotate(360deg);
    }
}
@keyframes spin {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}

Run the following command to start the development server:

npm run dev

Get warehouse data info from our endpoint

Next, we would need to use the get method to fetch our data, loop through and display specific information for the user. We would first create a basic card layout for the information we need to display. Locate the pages folder and write the following in the index.vue file.

index.vue

<template>
  <div class="main">
    <main class="container mt-4">
      <h2>Hi, Good{{ greeting }}</h2>
      <!-- Cards here -->
      <transition name="el-fade-in">
        <div v-if="warehouses != ''" class="mt-4 row">
          <div
            class="col-lg-4 my-2 display_cards"
            v-for="warehouse in warehouses"
            :key="warehouse.id"
            @click="handleTempWarehouse(warehouse)"
          >
            <el-card class="box-card">
              <div slot="header" class="clearfix">
                <span class="header">{{ warehouse.name }}</span>
                <el-button style="float: right; padding: 3px 0" type="text">
                  <span
                    class="registered"
                    v-if="warehouse.is_registered === true"
                    >Registered</span
                  >
                  <span
                    class="not_registered"
                    v-if="warehouse.is_registered === false"
                    >Not Registered</span
                  >
                </el-button>
              </div>
              <div class="text item d-flex">
                <div>
                  <p>
                    Location: <span class="value">{{ warehouse.city }}</span>
                  </p>
                </div>
                <div class="ml-auto">
                  <p>
                    Cluster: <span class="value">{{ warehouse.cluster }}</span>
                  </p>
                </div>
              </div>
              <div class="text item d-flex">
                <div>
                  <p>
                    Type: <span class="value">{{ warehouse.type }}</span>
                  </p>
                </div>
                <div class="ml-auto">
                  <p>
                    Space Available:
                    <span class="value">{{ warehouse.space_available }}</span>
                  </p>
                </div>
              </div>
            </el-card>
          </div>
        </div>
        <div v-else-if="(loading = true)" class="loader text-center"></div>
      </transition>
    </main>
  </div>
</template>
<script>
import axios from "axios";
import loading from "~/components/generic/loading";
export default {
  components: {
    loading,
  },
  data() {
    return {
      loading: false,
      greeting: "",
      warehouses: [],
      secretKey: process.env.API_SECRET_KEY,
    };
  },
  created() {
    this.greet();
    this.fetchWarehouses();
  },
  methods: {
    greet() {
      let time = new Date().getHours();
      if (time < 10) {
        this.greeting = "morning";
      } else if (time < 20) {
        this.greeting = "day";
      } else {
        this.greeting = "evening";
      }
    },
    async fetchWarehouses() {
      try {
        const response = await axios.get(
          "https://api.jsonbin.io/b/5fa97d9b48818715939e40ff",
          {
            headers: {
              "secret-key": this.secretKey,
            },
          }
        );
        this.warehouses = response.data;
        this.$store.commit("warehouse/SAVE_WAREHOUSES_MUTATION", response.data);
      } catch (error) {}
    },
    handleTempWarehouse(warehouse) {
      this.$store.commit("warehouse/SAVE_TEMP_WAREHOUSE_MUTATION", warehouse);
      this.$router.push(`/${warehouse.name}`);
    },
  },
};
</script>

So yeah we have everything pretty much already loaded up. Lets gradually break them piece by piece.

A brief recap here, we need to fetch our data from our endpoint, store our data instance and loop through to display specific information. To do this, we have the method, fetchWarehouses() which is being called on mounted lifecycle hook to handle this.

  async fetchWarehouses() {
      try {
        const response = await axios.get(
          "https://api.jsonbin.io/b/5fa97d9b48818715939e40ff",
          {
            headers: {
              "secret-key": this.secretKey,
            },
          }
        );
        this.warehouses = response.data;
      } catch (error) {
        console.log(error)
      }
    }

With this, we have our payload within our data instance and hence we can use our v-for to loop and display appropriately as we have rightly done in our component.

Re-route card dynamically

Sequel to the loop, we also need to reroute to a new page, which we are dynamically creating. One that houses information tied to a particular clicked card. To do this we also have a method and this simply picks the specific warehouse, and commits its data to a mutation in our store, while also re-routing to the dynamic page.

   handleTempWarehouse(warehouse) {
      this.$store.commit("warehouse/SAVE_TEMP_WAREHOUSE_MUTATION", warehouse);
      this.$router.push(`/${warehouse.name}`);
    }

We are using an onClick event listener to trigger this method once a card is clicked by the user.

Dynamic routes are pretty easy to come by in Nuxt.Js The system recognizes files with an underscore as dynamic routes. These files however do need to be housed within the pages folder, which basically handles our routes. So basically files as _id.vue and _edit.vue are dynamic routes.

To create our route we would simply just add a _id.vue file in our pages folder and leave it blank for now.

To see what actually happens in our store, add a new file warehouse.js in the store folder. Go ahead and the following code to the newly created file.

warehouse.js

const defaultState = () => ({
    tempWarehouse: [],
});
export default {
    state: defaultState(),
    getters: {
        WAREHOUSE: state => state.tempWarehouse,
    },
    mutations: {
        SAVE_TEMP_WAREHOUSE_MUTATION(state, payload) {
            state.tempWarehouse = payload
        }
    }
}

Basically our store doesn’t do much really, the SAVE_TEMP_WAREHOUSE_MUTATION mutation accepts the warehouse data as a payload, and pushes into our state. To aid us in having access to the specific warehouse, we have a getter to help us handle that.

To increase the user experience, we also have a loader property that notifies the user that there’s an ongoing request while the user awaits the response as well as a method that gets the hour of the day and greets the user per the time frame. We also called this method on mounted as well.

  <div v-else-if="(loading = true)" class="loader text-center"></div>


    greet() {
      let time = new Date().getHours();
      if (time < 10) {
        this.greeting = "morning";
      } else if (time < 20) {
        this.greeting = "day";
      } else {
        this.greeting = "evening";
      }
    }

Reverse geocode location with mapbox

At this point, we can say we have gotten to the peak of our project. So basically what we want here is to grab details tied to the specific card that the user has clicked and display on our dynamic page.

Amongst the details present in the payload is the location of the specific warehouse. We want to grab the location, make a reverse geocode call to the mapbox api and display the specific location on the map. Pretty straightforward yeah?

Lets start by creating a scaffold to display the map and the extra information tied to the warehouse. Go ahead and add the following piece of code to the _id.vue file

_id.vue

<template>
  <div class="main">
    <div class="container">
      <div class="mt-4">
        <nuxt-link to="/">
          <el-button type="primary" icon="el-icon-arrow-left" plain
            >Back</el-button
          >
        </nuxt-link>
      </div>
      <div class="row mt-4">
        <!-- Map region here -->
        <div class="col-lg-7">
          <div v-if="loading" class="loader text-center"></div>
          <div id="map"></div>
        </div>
        <div class="col-lg-5">
          <!-- Cards here -->
          <transition name="el-fade-in">
            <div class="row" v-if="(WAREHOUSE = !'')">
              <div class="my-2 w-100">
                <el-card class="box-card">
                  <div slot="header" class="clearfix">
                    <span class="header">{{ WAREHOUSE.name }}</span>
                    <el-button style="float: right; padding: 3px 0" type="text">
                      <span
                        class="registered"
                        v-if="WAREHOUSE.is_registered === true"
                        >Registered</span
                      >
                      <span
                        class="not_registered"
                        v-if="WAREHOUSE.is_registered === false"
                        >Not Registered</span
                      >
                    </el-button>
                  </div>
                  <div class="text item d-flex">
                    <div>
                      <p>
                        Location:
                        <span class="value">{{ WAREHOUSE.city }}</span>
                      </p>
                    </div>
                    <div class="ml-auto">
                      <p>
                        Cluster:
                        <span class="value">{{ WAREHOUSE.cluster }}</span>
                      </p>
                    </div>
                  </div>
                  <div class="text item d-flex">
                    <div>
                      <p>
                        Type: <span class="value">{{ WAREHOUSE.type }}</span>
                      </p>
                    </div>
                    <div class="ml-auto">
                      <p>
                        Space Available:
                        <span class="value">{{
                          WAREHOUSE.space_available
                        }}</span>
                      </p>
                    </div>
                  </div>
                  <div class="text item d-flex">
                    <div>
                      <p>
                        Live Status:
                        <span class="value">{{ WAREHOUSE.is_live }}</span>
                      </p>
                    </div>
                    <div class="ml-auto">
                      <p>
                        Code:
                        <span class="value">{{ WAREHOUSE.code }}</span>
                      </p>
                    </div>
                  </div>
                </el-card>
              </div>
            </div>
          </transition>
        </div>
      </div>
    </div>
  </div>
</template>

So this basically should give us our layout. However, running this would throw errors as we have methods and references to our data instance which presently do not exist yet. Lets add the script section and define the methods to handle our displays.

<script>
import axios from "axios";
import mapboxgl from "mapbox-gl";
import { mapGetters } from "vuex";
import loading from "~/components/generic/loading";
export default {
  layout: "Default",
  data() {
    return {
      loading: false,
      access_token: process.env.MAP_ACCESS_TOKEN,
      map: {},
      center: [],
    };
  },
  components: {
    loading,
  },
  computed: {
    ...mapGetters("warehouse", ["WAREHOUSE"]),
  },
  mounted() {
    this.createMap();
  },
  methods: {
    async createMap() {
      try {
        this.loading = true;
        const response = await axios.get(
          `https://api.mapbox.com/geocoding/v5/mapbox.places/${this.WAREHOUSE.city}.json?access_token=${this.access_token}`
        );
        this.loading = false;
        this.center = response.data.features[0].center;
        mapboxgl.accessToken = this.access_token;
        this.map = new mapboxgl.Map({
          container: "map",
          style: "mapbox://styles/mapbox/streets-v11",
          center: this.center,
          zoom: 11,
        });
      } catch (error) {
        console.log(error);
      }
    },
  },
};
</script>

We basically imported the modules we would be using for our various requests. We also need the mapGetter module to grab the details tied to the specific card which we initially committed to our mutation in our store.

computed: {
    ...mapGetters("warehouse", ["WAREHOUSE"])
  }

So basically this gave us the will power to reference our getter (WAREHOUSE) as part of our data instance.

Next is for us to actually use the location to display the map. We first make a call to the reverse geocoding api by mapbox. This takes the city or location as well as the access token we initially generated as query parameters.

This call returns an object with geographical details tied to the location queried. We however need just the coordinates; longitude and latitude to make a second call which then puts together the map tiles and displays the specific location based on the coordinates provided.

So here’s a peek at the method that handles all this.

 async createMap() {
      try {
        this.loading = true;
        const response = await axios.get(
          `https://api.mapbox.com/geocoding/v5/mapbox.places/${this.WAREHOUSE.city}.json?access_token=${this.access_token}`
        );
        this.loading = false;
        this.center = response.data.features[0].center;
        mapboxgl.accessToken = this.access_token;
        this.map = new mapboxgl.Map({
          container: "map",
          style: "mapbox://styles/mapbox/streets-v11",
          center: this.center,
          zoom: 11,
        });
      } catch (error) {
        console.log(error);
      }
    }

So yes, we made the first call between lines 4-6. That returns the geographical details as our response payload.

We just extracted the coordinates from the response, assigned it to a property in our data instance which is an array and then used it to make the second call; between lines 9-14.

Yes, we also added the loading property here. To add to our aesthetics, we would add some css. Go ahead and make your _id.vue file to look as thus:

_id.vue

<template>
  <div class="main">
    <div class="container">
      <div class="mt-4">
        <nuxt-link to="/">
          <el-button type="primary" icon="el-icon-arrow-left" plain
            >Back</el-button
          >
        </nuxt-link>
      </div>
      <div class="row mt-4">
        <!-- Map region here -->
        <div class="col-lg-7">
          <div v-if="loading" class="loader text-center"></div>
          <div id="map"></div>
        </div>
        <div class="col-lg-5">
          <!-- Cards here -->
          <transition name="el-fade-in">
            <div class="row" v-if="(WAREHOUSE = !'')">
              <div class="my-2 w-100">
                <el-card class="box-card">
                  <div slot="header" class="clearfix">
                    <span class="header">{{ WAREHOUSE.name }}</span>
                    <el-button style="float: right; padding: 3px 0" type="text">
                      <span
                        class="registered"
                        v-if="WAREHOUSE.is_registered === true"
                        >Registered</span
                      >
                      <span
                        class="not_registered"
                        v-if="WAREHOUSE.is_registered === false"
                        >Not Registered</span
                      >
                    </el-button>
                  </div>
                  <div class="text item d-flex">
                    <div>
                      <p>
                        Location:
                        <span class="value">{{ WAREHOUSE.city }}</span>
                      </p>
                    </div>
                    <div class="ml-auto">
                      <p>
                        Cluster:
                        <span class="value">{{ WAREHOUSE.cluster }}</span>
                      </p>
                    </div>
                  </div>
                  <div class="text item d-flex">
                    <div>
                      <p>
                        Type: <span class="value">{{ WAREHOUSE.type }}</span>
                      </p>
                    </div>
                    <div class="ml-auto">
                      <p>
                        Space Available:
                        <span class="value">{{
                          WAREHOUSE.space_available
                        }}</span>
                      </p>
                    </div>
                  </div>
                  <div class="text item d-flex">
                    <div>
                      <p>
                        Live Status:
                        <span class="value">{{ WAREHOUSE.is_live }}</span>
                      </p>
                    </div>
                    <div class="ml-auto">
                      <p>
                        Code:
                        <span class="value">{{ WAREHOUSE.code }}</span>
                      </p>
                    </div>
                  </div>
                </el-card>
              </div>
            </div>
          </transition>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";
import mapboxgl from "mapbox-gl";
import { mapGetters } from "vuex";
import loading from "~/components/generic/loading";
export default {
  layout: "Default",
  data() {
    return {
      loading: false,
      access_token: process.env.MAP_ACCESS_TOKEN,
      map: {},
      center: [],
    };
  },
  components: {
    loading,
  },
  computed: {
    ...mapGetters("warehouse", ["WAREHOUSE"]),
  },
  mounted() {
    this.createMap();
  },
  methods: {
    async createMap() {
      try {
        this.loading = true;
        const response = await axios.get(
          `https://api.mapbox.com/geocoding/v5/mapbox.places/${this.WAREHOUSE.city}.json?access_token=${this.access_token}`
        );
        this.loading = false;
        this.center = response.data.features[0].center;
        mapboxgl.accessToken = this.access_token;
        this.map = new mapboxgl.Map({
          container: "map",
          style: "mapbox://styles/mapbox/streets-v11",
          center: this.center,
          zoom: 11,
        });
      } catch (error) {
        console.log(error);
      }
    },
  },
};
</script>
<style scoped>
.border_left {
  border-left: 1px solid rgba(0, 0, 0, 0.1);
}
#map {
  height: 70vh;
}
</style> 

This should give us a relatively good scaffold to work with. Go ahead and start the development server if it isn’t already running and explore the app.

Conclusion

In this guide, we have seen how to do a reverse geocoding function using mapbox. This we drove home by building a Nuxt app that displays basic warehouse information and on click, redirects to a page with more info and a map that shows the geographic location of the specific warehouse. This guide Is only a piece of the whole picture as a lot more could be achieved with the geocoding apis.

You can access the code source here.


Share on social media

//