From 2125d714e6b76931d26a7c3c990b5ddaf9efd13e Mon Sep 17 00:00:00 2001 From: Lee Lawlor Date: Tue, 5 Aug 2014 15:12:41 -0400 Subject: [PATCH] add geolocation search for public channels --- Gemfile | 1 + Gemfile.lock | 4 ++++ app/controllers/channels_controller.rb | 5 +++++ app/models/channel.rb | 17 +++++++++++++++++ app/views/docs/channels/_index.html.erb | 9 +++++++++ config/locales/en.yml | 1 + ...4223739_add_geolocation_index_to_channels.rb | 6 ++++++ db/schema.rb | 3 ++- spec/controllers/channels_controller_spec.rb | 7 +++++++ spec/models/channel_spec.rb | 12 ++++++++++++ 10 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20140804223739_add_geolocation_index_to_channels.rb diff --git a/Gemfile b/Gemfile index 5399825..c3a895d 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ gem 'em-http-request' gem 'tzinfo' gem 'tzinfo-data' gem 'turbolinks' +gem 'geokit-rails' # to use debugger # gem 'ruby-debug' diff --git a/Gemfile.lock b/Gemfile.lock index 77ba952..e40caf6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -144,6 +144,9 @@ GEM actionpack (>= 3.0) geokit (1.8.4) multi_json (>= 1.3.2) + geokit-rails (2.0.1) + geokit (~> 1.5) + rails (>= 3.0) gravatarify (3.1.0) has_scope (0.6.0.rc) actionpack (>= 3.2, < 5) @@ -362,6 +365,7 @@ DEPENDENCIES factory_girl_rails faker geokit + geokit-rails gravatarify i18n-tasks (~> 0.5.4) jquery-rails (= 3.0.4) diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_controller.rb index a6748ba..d9efcf5 100644 --- a/app/controllers/channels_controller.rb +++ b/app/controllers/channels_controller.rb @@ -6,6 +6,7 @@ class ChannelsController < ApplicationController layout 'application', :except => [:social_show, :social_feed] protect_from_forgery :except => [:realtime, :realtime_update, :post_data, :create, :destroy, :clear] require 'csv' + require 'will_paginate/array' # get list of all realtime channels def realtime @@ -55,6 +56,10 @@ class ChannelsController < ApplicationController elsif params[:tag].present? @header = "#{t(:tag).capitalize}: #{params[:tag]}" @channels = Channel.public_viewable.active.order('ranking desc, updated_at DESC').with_tag(params[:tag]).paginate :page => params[:page] + # get channels by location + elsif params[:latitude].present? && params[:longitude].present? && params[:distance].present? + @header = "#{t(:channels_near)}: [#{params[:latitude]}, #{params[:longitude]}]" + @channels = Channel.location_search(params).paginate :page => params[:page] # normal channel list else @header = t(:featured_channels) diff --git a/app/models/channel.rb b/app/models/channel.rb index bb8370e..325624c 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -52,6 +52,11 @@ class Channel < ActiveRecord::Base include KeyUtilities + # geolocation search: Channel.within(miles, :origin => [latitude, longitude]).to_a + # example: channels = Channel.within(4000, :origin => [4, 6]).to_a + # channels.sort_by{|s| s.distance_to([4, 6])} + acts_as_mappable :default_units => :kms, :default_formula => :sphere, + :distance_field_name => :distance, :lat_column_name => :latitude, :lng_column_name => :longitude belongs_to :user has_many :feeds @@ -85,6 +90,18 @@ class Channel < ActiveRecord::Base cattr_reader :per_page @@per_page = 15 + # search for public channels within a certain distance from the origin + # requires latitude, longitude, and distance to be present as options keys + # distance is in kilometers + def self.location_search(options = {}) + # set the origin + origin = [options[:latitude].to_f, options[:longitude].to_f] + # query the database + channels = Channel.public_viewable.within(options[:distance].to_f, :origin => origin) + # sort channels by distance + return channels.sort_by{|c| c.distance_to(origin)} + end + # how often the channel is updated def update_rate last_feeds = self.feeds.order('entry_id desc').limit(2) diff --git a/app/views/docs/channels/_index.html.erb b/app/views/docs/channels/_index.html.erb index 08d6247..1cd9864 100644 --- a/app/views/docs/channels/_index.html.erb +++ b/app/views/docs/channels/_index.html.erb @@ -16,6 +16,14 @@ Valid parameters:
  • username (string) Person's username that you want to search Channels for (optional)
  • +
    +You can also search for Channels within a certain distance of a location by including the following location parameters: + +
    Example GET: @@ -163,6 +171,7 @@ Valid parameters:
  • api_key (string) - Your Account API Key (this is different from a Channel API Key, and can be found in your Account settings). (required)
  • +
    Example GET: diff --git a/config/locales/en.yml b/config/locales/en.yml index 457ec97..d83f65e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -55,6 +55,7 @@ en: channel_url: "URL" channels: "Channels" channels_my: "My Channels" + channels_near: "Channels Near" channels_public: "Public Channels" channels_public_view: "View Public Channels" channel_video_type_blank: "Either Youtube, or Vimeo, is required if a Video ID is specified." diff --git a/db/migrate/20140804223739_add_geolocation_index_to_channels.rb b/db/migrate/20140804223739_add_geolocation_index_to_channels.rb new file mode 100644 index 0000000..01d51c2 --- /dev/null +++ b/db/migrate/20140804223739_add_geolocation_index_to_channels.rb @@ -0,0 +1,6 @@ +class AddGeolocationIndexToChannels < ActiveRecord::Migration + def change + add_index :channels, [:latitude, :longitude] + end +end + diff --git a/db/schema.rb b/db/schema.rb index 28eca3d..8ccfef6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20140801191621) do +ActiveRecord::Schema.define(version: 20140804223739) do create_table "active_admin_comments", force: true do |t| t.string "namespace" @@ -107,6 +107,7 @@ ActiveRecord::Schema.define(version: 20140801191621) do t.text "metadata" end + add_index "channels", ["latitude", "longitude"], name: "index_channels_on_latitude_and_longitude", using: :btree add_index "channels", ["public_flag", "last_entry_id", "updated_at"], name: "channels_public_viewable", using: :btree add_index "channels", ["ranking", "updated_at"], name: "index_channels_on_ranking_and_updated_at", using: :btree add_index "channels", ["realtime_io_serial_number"], name: "index_channels_on_realtime_io_serial_number", using: :btree diff --git a/spec/controllers/channels_controller_spec.rb b/spec/controllers/channels_controller_spec.rb index b46f3dd..c9ad6a1 100644 --- a/spec/controllers/channels_controller_spec.rb +++ b/spec/controllers/channels_controller_spec.rb @@ -125,6 +125,13 @@ describe ChannelsController do get :index, {:api_key => @user.api_key, :format => 'json'} response.should be_successful end + + it "searches nearby public channels" do + channel1 = Channel.create(name: 'channel1', latitude: 10, longitude: 10, public_flag: true) + channel2 = Channel.create(name: 'channel2', latitude: 60, longitude: 60, public_flag: true) + get :public, {api_key: @user.api_key, latitude: 59.8, longitude: 60.2, distance: 100, format: 'json'} + JSON.parse(response.body)['channels'][0]['name'].should eq("channel2") + end end describe "create channel" do diff --git a/spec/models/channel_spec.rb b/spec/models/channel_spec.rb index 6e4cb35..a531eb1 100644 --- a/spec/models/channel_spec.rb +++ b/spec/models/channel_spec.rb @@ -134,5 +134,17 @@ describe Channel do channels.count.should == 1 end end + + describe 'geolocation' do + it 'should find nearby channels' do + channel1 = Channel.create(latitude: 10, longitude: 10, public_flag: true) + channel2 = Channel.create(latitude: 60, longitude: 60, public_flag: true) + channel3 = Channel.create(latitude: 60, longitude: 60, public_flag: false) + Channel.location_search({latitude: 9.8, longitude: 10.2, distance: 100}).first.should eq(channel1) + Channel.location_search({latitude: 60.2, longitude: 59.8, distance: 100}).first.should eq(channel2) + Channel.location_search({latitude: 60.2, longitude: 59.8, distance: 100}).count.should eq(1) + Channel.location_search({latitude: 30.8, longitude: 30.2, distance: 100}).count.should eq(0) + end + end end