Geocoding using Mapbox, Rails 5 and NuxtJS/VueJS

Geocoding using Mapbox, Rails 5 and NuxtJS/VueJS

<strong>Geocoding is the process of taking latitude and longitude to determine an address, or taking an address and producing latitude and longitude coordinates.</strong>

Geocoding is the process of taking latitude and longitude to determine an address, or taking an address and producing latitude and longitude coordinates.

There are numerous reasons an application needs to use a geocoder. For Now Serving, we use it during the sign up process, as well as making it easy to find nearby restaurants with a single click of ‘Find Me’.

Let’s get to coding!

Rails API

We’ll need to grab the mapbox-sdk and add it to the Gemfile.

_gem_ **'mapbox-sdk'**, **'~>2'**

Create a simple initializer to set the access token in your app (e.g config/initializers/mapbox.rb)

**_Mapbox_**.access_token = MAPBOX_ACCESS_TOKEN

Next, let’s add a couple routes:

_namespace_ **:address_search do** _get_ **'expand'**, **to**: **'expand'** _get_ **'parse'**, **to**: **'parse'
end**

And an address_search_controller.rb:

class AddressSearchController < ApplicationController

Take an addresss and return lat/lng

def expand begin @addresses = Mapbox::Geocoder.geocode_forward(address_params[:a]) unless address_params[:a].nil? render template: 'address_search/result' rescue StandardError render json: { errors: ['Unable to perform forward geocoding'] } end end

Take lat/lng array and return a postal address

def parse begin @location = { latitude: address_params[:latitude].to_f, longitude: address_params[:longitude].to_f } @addresses = Mapbox::Geocoder.geocode_reverse(@location) render template: 'address_search/result' rescue StandardError render json: { errors: ['Unable to perform reverse geocoding'] } end end

private

def address_params params.permit(:a, :latitude, :longitude) end end

The expand method takes the a query params and asks the geocoder service to return a latitude/longitude array. For getting an address from lat/lng we are expecting a hash like { latitude: 0, longitude: 0 }.

You may not want to render a template here but in my case I wanted to always return an array, so the best way to ensure that happened was rendering it with jbuilder one-liner:

json.array! @addresses

And a request spec:

RSpec.describe 'Address Search' do

it 'parses an address and returns latitude and longitude' do get '/api/v1/address_search/expand', params: { a: '401 B St, San Diego CA' } expect(response).to be_successful end

it 'parses latitude and longitude and returns an address' do get '/api/v1/address_search/parse', params: { longitude: 127.0, latitude: -43.64} expect(response).to be_successful end end

Front End

We’re using the awesome NuxtJS framework for our UI. If you haven’t used it before, definitely give it a look. If you can’t use it, don’t worry; this code will work fine without Nuxt.

We use Vuex actions to call our back end, so we have a store for our Mapbox configuration.

export const actions = {
  locate({ commit }, { longitude, latitude }) {
    return this.$axios.get('/address_search/parse', { params: { longitude: longitude, latitude: latitude } })
  },
  coordinate({ commit }, params) {
    return this.$axios.get('/address_search/expand', { params: { a: params } })
  }
}

For presentation, we use vue-i18n, vue-notify, bootstrap-vue and vue-fontawesome.

<template>
  <b-btn
    v-b-tooltip.hover="true"
    :data-state="state"
    :variant="btnVariant"
    :title="locationLabel"
    type="button"
    @click="findMe">
    <font-awesome-icon v-if="state === 1" :icon="['far', 'spinner']" spin />
    <font-awesome-icon v-else :icon="['far', 'location-arrow']" />
  </b-btn>
</template>
<script>
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

export default { components: { FontAwesomeIcon }, props: { locationLabel: { default: 'Find my current location', type: String } }, data() { return { state: 0 } }, computed: { btnVariant() { switch (this.state) { case 0: return 'outline-primary' case 1: return 'info' case 2: return 'success' default: return 'outline-primary' } } }, methods: { findMe() { const vm = this this.state = 1 if (!navigator.geolocation) { vm.$notify({ text: vm.$t('geolocation.not_supported'), group: 'alerts' }) return }

  function success(position) {
    const accuracy = position.coords.accuracy
    vm.$store.dispatch('mapbox/locate', {
      latitude: position.coords.latitude,
      longitude: position.coords.longitude,
      accuracy: accuracy
    })
      .then((resp) =&gt; {
        vm.state = 2
        vm.$emit('result', { name: resp.data[0].features[0].place_name, center: resp.data[0].features[0].center })
      })
      .catch(() =&gt; {
        vm.state = 0
        vm.$notify({ text: vm.$t('geolocation.not_found'), type: 'warning', group: 'alerts' })
      })
  }

  function error() {
    vm.$notify({ text: vm.$t('geolocation.not_found'), group: 'alerts', type: 'warning' })
  }

  navigator.geolocation.getCurrentPosition(success, error)
}

} } </script>

There’s a lot going on here, so lets break it all down.

The location button has three states; default state, active state, and a success state. The computed property handles changing out the css classes for each state.

There is also a tooltip that displays on hover to explain that the browser will ask for permission to send location information to the back end.

The findMe method is called on click. In it we have two callbacks for success and error that the browser’s built in [getCurrentPosition](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition)needs to work correctly. When the browser provides the latitude and longitude to the success callback, we can send that to the back end using the Vuex action. Once the back end response comes, the component emits a result event containing the address name and coordinates. If permission is denied, we display an error notification. Also if the browser doesn’t support location services, we notify the user of that case.

Conclusion

Congrats you have a fully implemented API for forward and reverse geocoding solution!

Originally published by Todd Baur at https://itnext.io

Learn more

☞ The Complete Ruby on Rails Developer Course

☞ Testing Ruby with RSpec: The Complete Guide

☞ How to build Instagram using Ruby on Rails

☞ Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

☞ Nuxt JS with Laravel - Build API driven SSR Vue JS Apps

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

Brave, Chrome, Firefox, Opera or Edge: Which is Better and Faster?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

How to build a Ruby on Rails Application with Vue.js using JSX

This is a step-by-step tutorial on how to build a Ruby on Rails application with Vue.js using JSX.

How to build enterprise Vue.js applications with Nuxt?

Learn How to build enterprise Vue.js applications with Nuxt: Nuxt is a progressive framework based on Vue.js to create modern web applications. It is based on Vue.js official libraries (vue, vue-router and vuex) and powerful development tools (webpack, Babel and PostCSS). Nuxt goal is to make web development powerful and performant with a great developer experience in mind.

Responsive ecommerce template built with Vue.js and Nuxt.js

Responsive ecommerce template built with Vue.js and Nuxt.js