MapkickJS is a javascript adapter to display coordinates on Mapbox maps. It requires a Mapbox API key.

mapkick-rb is a Ruby on Rails adapter for MapkickJS. It allows you to easily feed a JSON with coordinates and display a map within your Rails app.

To display a marker on a map, you need to know latitude and longitude GPS coordinates. gem Geocoder allows you to get coordinates based on an address (house, street, city, state, country).

Basic usage #

After installing the gem, initialize your Mapkick API key.

# echo > config/initializers/mapbox.rb
# config/initializers/mapbox.rb
ENV["MAPBOX_ACCESS_TOKEN"] = "pk.eyJ1..."
# ENV["MAPBOX_ACCESS_TOKEN"] = Rails.application.credentials.dig(:mapkick_api_key)

Basic map with multiple options:

<%= js_map [{latitude: 37.7829,
             longitude: -122.4190,
             label: 'My home',
             tooltip: 'Hello!'
            }],
            id: "cities-map",
            width: "800px",
            height: "500px",
            markers: {color: "#00FF00"},
            tooltips: { hover: false, html: true},
            style: "mapbox://styles/mapbox/outdoors-v12",
            zoom: 15,
            controls: true,
            refresh: 60 %>

Result - display a marker on draggable map:

mapbox-map-all-params

HTML tooltips #

Create a helper with a link to the location page:

# app/helpers/locations_helper.rb
module LocationsHelper
  def html_link_to_location(location)
    link_to location.name,
            location_url(location),
            target: '_blank',
            style: 'font-weight: bold; color: green'
  end
end

To be able to click on the tooltip, use the option { hover: false, html: true}.

Render the helper method in the tooltip param:

<%= js_map [{latitude: location.latitude,
             longitude: location.longitude,
             label: location.name,
             tooltip: html_link_to_location(location)}],
             tooltips: { hover: false, html: true} %>

Result - map with clickable links to locations:

mapbox-map-clickable-link

Display multiple locations on the map, JSON #

For this, the best way will be to render /locations.json:

<%= js_map locations_path(format: :json) %>

Customize the JSON:

// app/views/locations/_location.json.jbuilder
json.extract! location, :latitude, :longitude
json.label location.name
json.tooltip html_link_to_location(location)
// json.tooltip "#{html_link_to_location(location)} <br> #{location.address}"

Result - @locations is rendered from app/views/locations/index.json.jbuilder:

mapbox-map-multiple-locations

JSON with search params #

In this final example, we will factor in having a search form for place and distance:

# app/controllers/locations_controller.rb
class LocationsController < ApplicationController
  before_action :set_location, only: %i[ show edit update destroy ]

  # GET /locations or /locations.json
  def index
    if params[:place].present?
      @locations = Location.near(params[:place], params[:distance] || 10, order: :distance)
      # distance 10 km => zoom 13x; distance 100 km => zoom 10x;
      # @zoom = params[:distance].eql?('10') ? 13 : 10
    else
      @locations = Location.all
    end
    respond_to do |format|
      format.html
      format.json
    end
  end

Be sure to add the query params to the path in the view:

<%= js_map locations_path(format: :json, place: params[:place], distance: params[:distance]), zoom: @zoom %>

Result - show only location within set distance from geocoded coordinates of place:

geocoder-search-zoom

Bonus: Search for locations that offer a specific product #

Business problem #1: Find hotels that have a SPA

Business problem #2: Find hotels that have a Massage

Here we are solving the problem: “find all parents with children that have a particular attribute”.

In the below example location has_many :products && Product.name = String.

Add product_name search field:

<%= form_with url: locations_path, method: :get do |form| %>
  <%= form.text_field :product_name, value: params[:product_name] %>
  <%= form.text_field :place, value: params[:place] %>
  <%= form.select :distance, [10, 100], selected: params[:distance] %>
  <%= form.submit %>
<% end %>

Find locations that have product you are searching for:

# app/controllers/locations_controller.rb
  def index
    locations = Location.joins(:products).includes(:products) # initially select only locations that have products

    if params[:product_name].present?
      products = Product.where('name ILIKE ?', "%#{params[:product_name]}%")
      location_ids = products.select(:location_id).distinct
      locations = locations.where(id: location_ids)
    end

    if params[:place].present?
      locations = locations.near(params[:place], params[:distance] || 10, order: :distance)
    end
    @locations = locations
  end

Don’t forget to add product_name: params[:product_name] to the JSON map path:

<%= js_map locations_path(format: :json, place: params[:place], product_name: params[:product_name], distance: params[:distance]) %>

Result: find locations that offer a specific product/service:

search-by-child.gif

That’s it! Now you can build your own AIRNBN search frontend!