Why?

Location-based search can bring user experience value to modern websites and mobile apps. Such as allowing users to find nearby drivers/doctors/restaurants/lawyers/etc. This post will talk about adding geo-search ability to Rails API backend that enabled the location-based searching.

Business example

Let’s say we want to build a mobile app where users can find doctors nearby. So this is the core business requirement:

Users want to find doctors within 5 miles nearby

Technical specs

  • we need to build a mobile app and a backend server (Rails API)
  • the mobile app will make AJAX call to backend with current location (latitude + longitude) and searching radius
  • Rails the API backend will return a list of doctors (in JSON format) that match the location search query

We will focus on Rails backend development in this post.

Available tools in Rails community

Apparently there are two common ways to go:

  • Option 1: Using gem geokit-rails (easy to setup & use, ideal for simple use cases)
  • Option 2: Using PostGIS extension in PostgreSQL (more work on setup but suitable for complex SQL query and better performance)

We will talk about Option 1 in this post.
Option 2 will be covered in Part 2 - using PostGIS extension.

Setup

Follow geokit-rails' github for first time setup. It's fairly easy to incorporate geokit into Rails project.

Model / Migration

Create model for doctor model like below.
Note the index created on clinic_latitude and clinic_longitude for performance purpose.

class CreateDoctors < ActiveRecord::Migration[5.1]
  def change
    create_table :doctors do |t|
      t.decimal :clinic_latitude, precision: 10, scale: 6
      t.decimal :clinic_longitude, precision: 10, scale: 6
      t.string :clinic_name

      t.timestamps
    end
    add_index :doctors, [:clinic_latitude, :clinic_longitude]
  end
end

Hook up GeoKit

Simply by adding acts_as_mappable in the model. lat_column_name and lng_column_name should match the latitude and longitude columns created in the table.

# models/doctor.rb
class Doctor < ApplicationRecord
  acts_as_mappable :default_units => :miles,
                   :default_formula => :sphere,
                   :distance_field_name => :distance,
                   :lat_column_name => :clinic_latitude,
                   :lng_column_name => :clinic_longitude
end

Quick Test

> rails console

# create some doctors record
> Doctor.create!(clinic_name: "Doc 1", clinic_latitude: 22.3129115, clinic_longitude: 114.2219923)
> Doctor.create!(clinic_name: "Doc 2", clinic_latitude: 22.3129125, clinic_longitude: 114.2219993)
> Doctor.create!(clinic_name: "Doc 3 (too far)", clinic_latitude: 22.9429125, clinic_longitude: 114.5019993)
> Doctor.within(5, :units => :miles, :origin => [22.3129115, 114.2219923])

# #<ActiveRecord::Relation [#<Doctor id: 9, clinic_latitude: 0.22312912e2, clinic_longitude: 0.114221992e3, clinic_name: "Doc 1", created_at: "2017-09-15 11:47:24", updated_at: "2017-09-15 11:47:24">, #<Doctor id: 10, clinic_latitude: 0.22312913e2, clinic_longitude: 0.114221999e3, clinic_name: "Doc 2", created_at: "2017-09-15 11:47:24", updated_at: "2017-09-15 11:47:24">]> 
  • NOTE: Under the hood, geokit handles the geo-search by some mathetical formulas, you can tell from the generated SQL query

Controller

Last step is to replace latitude, longitude and radius with the parameters sent from the app.

# controllers/doctors_controller.rb

def search
  ... other stuff ...

  Doctor.within(
    params[:radius],
    :units => :miles,
    :origin => [
      params[:lat],
      params[:lng]
    ]
  )

  ... other stuff ...
end

Continue in Part 2 (using PostGIS extension)