Skip to content

Ruby Quickstart

Server-side: tile proxy, geocoding. For client-side maps, embed MapLibre JS in your views.

Gemfile

ruby
gem 'http'   # or use Net::HTTP from stdlib

Tile proxy (Rails)

ruby
# config/routes.rb
get '/tiles/:z/:x/:y.pbf', to: 'tiles#show', constraints: { z: /\d+/, x: /\d+/, y: /\d+/ }

# app/controllers/tiles_controller.rb
require 'openssl'
require 'http'

class TilesController < ApplicationController
  KEY    = ENV.fetch('MFD_SRV_KEY')
  SECRET = ENV.fetch('MFD_SRV_SECRET')

  def show
    z, x, y = params.values_at(:z, :x, :y)
    path    = "/tiles/#{z}/#{x}/#{y}.pbf"
    ts      = Time.now.to_i.to_s
    sig     = OpenSSL::HMAC.hexdigest('SHA256', SECRET, "GET\n#{path}\n#{ts}")

    upstream = HTTP.headers(
      'Authorization'    => "Bearer #{KEY}",
      'X-MFD-Timestamp'  => ts,
      'X-MFD-Signature'  => sig
    ).get("https://tiles.mapsfordevs.com#{path}")

    response.headers['Cache-Control'] = 'public, max-age=86400'
    send_data upstream.body.to_s,
              type: 'application/x-protobuf',
              status: upstream.status,
              disposition: 'inline'
  end
end

Geocoding service

ruby
require 'http'
require 'json'

class MapsForDevs
  BASE = 'https://api.mapsfordevs.com'

  def initialize(api_key)
    @api_key = api_key
  end

  def geocode(query, country: nil, limit: 5)
    params = { q: query, limit: limit }
    params[:country] = country if country

    resp = HTTP.headers(authorization: "Bearer #{@api_key}")
               .get("#{BASE}/geocode/forward", params: params)

    body = JSON.parse(resp.body.to_s, symbolize_names: true)
    raise body[:error] unless body[:ok]
    body[:items]
  end

  def reverse(lat, lng)
    resp = HTTP.headers(authorization: "Bearer #{@api_key}")
               .get("#{BASE}/geocode/reverse", params: { lat: lat, lng: lng, limit: 1 })
    body = JSON.parse(resp.body.to_s, symbolize_names: true)
    raise body[:error] unless body[:ok]
    body[:items].first
  end
end

mfd = MapsForDevs.new(ENV['MFD_PUB_KEY'])
mfd.geocode('80 Sturdee Ave Rosebank', country: 'za').each do |hit|
  puts "#{hit[:lat]}, #{hit[:lng]}#{hit[:label]}"
end

Retry with backoff

ruby
def fetch_with_retry(req, attempts: 4)
  attempts.times do |i|
    resp = req.call
    return resp if resp.status < 500 && resp.status != 429
    delay = (resp.headers['Retry-After'] || (2**i)).to_i
    sleep delay
  end
  raise 'failed after retries'
end