Геокодирование - это процесс взятия широты и долготы для определения адреса или взятия адреса и получения координат широты и долготы.
Существует множество причин, по которым приложению необходимо использовать геокодер. Для Сейчас обслуживается мы используем его во время процесса регистрации, а также упрощаем поиск ближайших ресторанов одним щелчком Найди меня.
Приступим к кодированию!
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 для решения прямого и обратного геокодирования!