Геокодирование - это процесс взятия широты и долготы для определения адреса или взятия адреса и получения координат широты и долготы.

Существует множество причин, по которым приложению необходимо использовать геокодер. Для Сейчас обслуживается мы используем его во время процесса регистрации, а также упрощаем поиск ближайших ресторанов одним щелчком Найди меня.

Приступим к кодированию!

Rails API

Нам нужно взять mapbox-sdk и добавить его в Gemfile.

gem 'mapbox-sdk', '~>2'

Создайте простой инициализатор для установки токена доступа в вашем приложении (например, config/initializers/mapbox.rb)

Mapbox.access_token = MAPBOX_ACCESS_TOKEN

Далее добавим пару маршрутов:

namespace :address_search do
  get 'expand', to: 'expand'
  get 'parse', to: 'parse'
end

И 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

Метод expand принимает параметры запроса a и просит службу геокодера вернуть массив широты / долготы. Для получения адреса от lat / lng мы ожидаем хеш вида { latitude: 0, longitude: 0 }.

Возможно, вы не захотите отображать здесь шаблон, но в моем случае я хотел всегда возвращать массив, поэтому лучший способ убедиться, что это произошло, - это отрисовать его с помощью однострочника jbuilder:

json.array! @addresses

И спецификация запроса:

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

Внешний интерфейс

Мы используем великолепный фреймворк NuxtJS для нашего пользовательского интерфейса. Если вы не использовали его раньше, обязательно посмотрите. Если вы не можете его использовать, не волнуйтесь; этот код будет нормально работать без Nuxt.

Мы используем действия Vuex для вызова нашей серверной части, поэтому у нас есть хранилище для нашей конфигурации Mapbox.

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

Для презентации мы используем vue-i18n, vue-notify, bootstrap-vue и 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) => {
            vm.state = 2
            vm.$emit('result', { name: resp.data[0].features[0].place_name, center: resp.data[0].features[0].center })
          })
          .catch(() => {
            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>

Здесь много всего происходит, так что давайте разберемся во всем.

Кнопка местоположения имеет три состояния; состояние по умолчанию, активное состояние и состояние успеха. Вычисляемое свойство обрабатывает изменение классов css для каждого состояния.

Существует также всплывающая подсказка, которая отображается при наведении курсора, чтобы объяснить, что браузер запрашивает разрешение на отправку информации о местоположении в серверную часть.

Метод findMe вызывается при нажатии. В нем у нас есть два обратных вызова для успеха и ошибки, которые встроенный в getCurrentPosition браузер должен работать правильно. Когда браузер предоставляет широту и долготу для обратного вызова success, мы можем отправить их в серверную часть с помощью действия Vuex. Как только приходит ответ серверной части, компонент генерирует результирующее событие, содержащее имя адреса и координаты. Если в разрешении отказано, мы отображаем уведомление об ошибке. Также, если браузер не поддерживает службы определения местоположения, мы уведомляем пользователя об этом случае.

Заключение

Поздравляем, у вас есть полностью реализованный API для решения прямого и обратного геокодирования!