Как интегрировать Google Maps в Ruby on Rails приложение

Google Maps integration

Первая и главная вещь, которую нужно знать о Google Maps – они шикарны! Это быстрый, надёжный, хорошо настраиваемый и условно-бесплатный сервис, причём бесплатность его заканчивается где-то на уровне 2500 запросов в день, поэтому условностью бесплатности в случае подавляющего большинства стартапов можно пренебречь.

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

Так же случилось и с Google Maps: вместо поисков gem'ов, я начал с изучения документации на сайте, о чём сейчас нисколько не жалею. Выяснилось, что ничего особо сложного в интеграции Google Maps API нет.

Геокодирование

Прежде, чем перейти непосредственно к интеграции Google Maps, стоит упомянуть такую вещь, как Геокодирование (Geocoding).

Согласно определению с сайта Google, геокодирование это процесс преобразования адресов (наподобие "1600 Amphitheatre Parkway, Mountain View, CA") в географические координаты в формате latitude: 37.423021; longitude: -122.083739 (широта и долгота соответственно, доли градусов десятичные, без минут и секунд, северная или южная, западная или восточная определяются знаком).

Эти координаты можно использовать для расстановки маркеров или для позиционирования карты.

Для Rails существует отличный gem – Geocoder, который берёт на себя вопросы, связанные с геокодированием. Настраивать его очень просто: достаточно после установки добавить в таблицу, содержащую адреса, поля latitude:float и longitude:float, указать в модели:

geocoded_by :address
after_validation :geocode

и при сохранении записи координаты будут обновляться.

Кроме того, у Geocoder'a есть ещё несколько замечательных "географических" возможностей, вроде поиска каких-либо объектов в окрестностях указанного места.

Статические карты

Google предоставляет работу с картами двух видов – статическими и динамическими.

Казалось бы, область применения статических карт нулевая, ведь динамические намного более привлекательны и функциональны, однако есть два аргумента за использование статических карт:

  • во-первых, работа с ними, естественно, намного проще,
  • во-вторых и в-главных, они без проблем и лишних усилий могут быть добавлены в какой-либо генериуемый статический документ, например, PDF-файл.

API статических карт очень простой – HTTP запрос с параметрами возвращает цельное изображение. Всё. С полученным изображением можно делать всё, что угодно. Не стоит, разумеется, обрезать копирайт Google или делать что-нибудь подобное.

На этом примере можно увидеть знакомое многим нашим сотрудникам место - офис Анадеи в Днепре:

Офис Анадеи в Днепре

На этом – оно же, но немного поближе:

Офис Анадеи в Днепре

И снова оно же, только в виде фото (это тоже один из сервисов Google Maps – Street View):

Офис Анадеи в Днепре

Список возможных параметров можно найти здесь.

Очевидно, что вся интеграция статических карт в первом приближении состоит в написании одного метода одного хелпера, что-то вроде:

def google_map(center)
  "https://maps.googleapis.com/maps/api/staticmap?center=#{center}&size=300x300&zoom=17"
end

где в качестве параметра center можно передать адрес или пару координат.

Тогда в вёрстке это будет доступно в виде:

image_tag google_map(center: location.address)

или

image_tag google_map(center: [ location.latitude, location.longitude ].join(','))

Понятно, что в реальном приложении параметры не следует делать "магическими числами", они могут как передаваться в виде аргументов в хелпер, так и храниться в файле настроек.

Это, собственно, всё, что касается интеграции статических карт.

Встраиваемые карты

API встраиваемых карт очень похож на API статических.

При помощи всего лишь одного HTTP-запроса можно легко добавить интерактивную карту в приложение. Её можно встроить разместив на странице iframe и указав URL-адрес Google Maps Embed API в качестве атрибута src:

<iframe width="300" height="300" frameborder="0" style="border:0"
 src="https://www.google.com/maps/embed/v1/place?key=YOUR_API_KEY&q=ADDRESS_OR_COORDINATES"
 allowfullscreen>
</iframe>

Таким образом можно очень легко и очень быстро получить базовую функциональность Google Maps.

Естественно, в реальном приложении такой код должен быть помещён в хелпер, немного облагорожен и тому подобное, однако интеграция по-прежнему остаётся очень простой.

Дополнительную информацию о встраиваемых картах можно найти здесь.

Динамические карты (JS)

Всё по-прежнему просто!

Первое, что нужно сделать, это подключить Google Maps Scripts при помощи тэга <script>:

<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY async defer></script>

Второе: добавить на страницу div c идентификатором, например, map. По этому идентификатору JS-скрипты будут определять, где именно рисовать карту.

Третье, начинать изучать Google Maps Guide и добавлять возможности к интерактивной карте.

Рассмотрим пример: пусть в приложении существует несколько вкладок, каждой из которых соответствует карта одной и той же местности, но с разным набором маркеров на ней. По клику на любом из маркеров карта должна быть отцентрирована на этом и масштаб карты должен быть увеличен.

Ниже приведён код на CoffeeScript для реализации требуемого функционала.

Сначала созданием класс, реализующий необходимые процедуры работы с картой:

class GoogleMap 
  # исходные настройки
  zoom =
    initialView: 15
    closeView: 18
  markers = []
  map = undefined

  constructor: (home) ->
    # определяем центр карты и параметры отображения
    lat = home["lat"]
    lon = home["lon"]
    myLatlng = new google.maps.LatLng(lat, lon)
    mapOptions =
      zoom: zoom.initialView
      center: myLatlng

    # создаём карту
    map = new google.maps.Map(document.getElementById("map"), mapOptions)

  addMarker: (location, title) ->
    # создаём маркер и добавляем в массив маркеров
    marker = new google.maps.Marker(
      position: location,
      title: title,
      map: map
    )
    markers.push marker

    # добавляем обработчик события
    google.maps.event.addListener marker, "click", ->
      map.setZoom zoom.closeView
      map.setCenter marker.getPosition()

  addMarkers: (markerList) ->
    # добавляем все маркеры
    _.each markerList, (marker) =>
      position = new google.maps.LatLng marker["lat"], marker["lon"]
      title = "#{marker['full_address']}"
      @addMarker position, title
  
  drawMarkers: (map) ->
    # отрисовываем маркеры
    _.each markers, (marker) ->
      marker.setMap map
      # ВАЖНО: вызов метода setMap на маркере отрисовывает маркер, вызов с параметром null – стирает
  
  showMarkers: ->
    @setAllMap map

  hideMarkers: ->
    @setAllMap null

  removeListeners: ->
    _.each markers, (marker) ->
      google.maps.event.clearInstanceListeners(marker)

  deleteMarkers: ->
    @hideMarkers()
    @removeListeners()
    markers = []

app.google or= { classes: {} }
app.google.classes.GoogleMap = GoogleMap

Этот класс используется в коде следующим образом:

$ ->
  { GoogleMap } = app.google.classes
  
  googleMap = new GoogleMap($('[data-map]:eq(0)').data('home'))
  googleMap.placeMarkers($("[data-map]:eq(0)").data("markers-list"))

  $(document).on 'click', '[data-tab]', ->
    googleMap.deleteMarkers()
    googleMap.placeMarkers($("[data-map]:eq($(@).index())").data("markers-list"))

Всё уже не так просто, но все ещё понятно, не так ли?

Rails gem'ы и JS плагины

Естественно, один из главных вопросов о GoogleMaps, который интересует RoR разработчиков, а можно ли использовать все прелести GoogleMaps, без использования JavaScript вообще.

Что ж, существует пара gem'ов, пытающихся в этом помочь.

Первый и наиболее популярный – Google-Maps-for-Rails. Откровенно говоря, этот gem очень напоминает знаменитую "Кашу из топора". В роли топора выступает сам gem, в роли крупы, масла, соли – все те кусочки JS-кода, которые всё равно придётся писать руками, чтобы добавить и кастомизировать карту.

Другое дело – gem GoogleMaps, который, кстати, на удивление нелегко найти. Здесь всё рельсовое, добавление всех основных JS-скриптов берёт на себя сам gem. Но и он не без недостатков.

Сложности с подобным подходом начинаются, когда появляется необходимость в интерактивном изменении карт. Без JS-скриптов здесь не обойтись, что сильно понижает ценность попыток оформления подобного функционала в виде gem'ов.

Таким образом, окончательный ответ - нет, нельзя получить полностью функциональную динамическую карту без JS-кода.

Что касается JS-плагинов, подавляющее большинство просмотренных бесплатных вариантов сводятся к одному - JS-код, скопированный с Google Maps Guide, иногда немного реорганизованный.

Список плагинов, которые произвели наиболее приятное впечатление:

Плюсы использования gem'ов не найдены. Плюс использования плагинов - все необходимые скрипты уже подключены в плагин и вы можете начинать работать с картой без предварительной реализации базового набора скриптов.

К минусам как gem'ов, так и плагинов можно отнести необходимость построения логики приложения, опираясь на их синтаксис и этот синтаксис предварительно надо изучить. В случае изменения GoogleMap API (а подобное случалось), при отсутствии поддержки gem'а разработчиком (и такое случалось), это может привести к печальным последствиям.

Заключение

Интеграция статических и встраиваемых карт - вопрос написания одного метода. В поисках "готовых решений" просто нет смысла.

Интеграция динамических карт сложнее, но не настолько, чтобы не попробовать сделать это "с нуля", так как:

  • это поможет вам разобраться в их API и возможностях, что наверняка пригодится в будущем;
  • это не займёт больше времени, чем интеграция при помощи "готовых" решений;
  • это избавит вас от траты времени на поиски решения получше;
  • это избавит вас от изучения чужого (и потенциально неуклюжего) синтаксиса;
  • это добавит вам фронтендерского опыта;
  • это, в конце концов, просто интересно.

И да, написанный код может быть легко использован в новом проекте или даже вынесен в свой родной плагин. А следовательно, кто знает, возможно, в будущем появится статья о вашем готовом решении!

Связаться