Mapping users to location

26 views
Skip to first unread message

C Wilson

unread,
Jan 21, 2014, 2:09:59 PM1/21/14
to thinkin...@googlegroups.com
Hey Pat, you assisted me with the radians/degrees issue. I was looking for a clearer idea than what Nicholas suggested for the part I needed advice on.

I have TS setup to find locations based on latitude/longitude that are near one another. This part functions as expected.

The next step that I am not clear on is how to setup so that if I perform a location search using a zip code from the Locations table to have it return users who are within the  specified distance.

I am building a app so users can find nearby users using their zip code. I want users to enter any zip code and it will return users within 200km.

I am not sure if there is more required from me in the searches controller that will make the search perform how I would like when users a zip code within the search box.


Location.search :geo => [33.89 * Math::PI / 180, -83.95 * Math::PI / 180], :with => {:geodist => 0.0..200_000.0}, :order => "geodist ASC"
  Sphinx Query (338.5ms)  SELECT GEODIST(0.5914920835008783, -1.4652039070492398, latitude, longitude) AS geodist, * FROM `location_core` WHERE `geodist` BETWEEN 0.0 AND 200000.0 AND `sphinx_deleted` = 0 ORDER BY `geodist` ASC LIMIT 0, 20
  Sphinx  Found 983 results
  Location Load (0.5ms)  SELECT `locations`.* FROM `locations` WHERE `locations`.`id` IN (12828, 12848, 12882, 12851, 12852, 12853, 12854, 12855, 12859, 12830, 12856, 12857, 12822, 12834, 12836, 12838, 12898, 12899, 12900, 12901)
 => [#<Location id: 12828, zipcode: "30017", city: "Grayson", state: "GA", latitude: 33.89, longitude: -83.95, created_at: nil, updated_at: nil>, #<Location id: 12848, zipcode: "30039", city: "Snellville", state: "GA", latitude: 33.85, longitude: -84.0, created_at: nil, updated_at: nil>, #<Location id: 12882, zipcode: "30078", city: "Snellville", state: "GA", latitude: 33.85, longitude: -84.0, created_at: nil, updated_at: nil>, #<Location id: 12851, zipcode: "30042", city: "Lawrenceville", state: "GA", latitude: 33.94, longitude: -83.99, created_at: nil, updated_at: nil>, #<Location id: 12852, zipcode: "30043", city: "Lawrenceville", state: "GA", latitude: 33.94, longitude: -83.99, created_at: nil, updated_at: nil>, #<Location id: 12853, zipcode: "30044", city: "Lawrenceville", state: "GA", latitude: 33.94, longitude: -83.99, created_at: nil, updated_at: nil>, #<Location id: 12854, zipcode: "30045", city: "Lawrenceville", state: "GA", latitude: 33.94, longitude: -83.99, created_at: nil, updated_at: nil>, #<Location id: 12855, zipcode: "30046", city: "Lawrenceville", state: "GA", latitude: 33.94, longitude: -83.99, created_at: nil, updated_at: nil>, #<Location id: 12859, zipcode: "30052", city: "Loganville", state: "GA", latitude: 33.83, longitude: -83.89, created_at: nil, updated_at: nil>, #<Location id: 12830, zipcode: "30019", city: "Dacula", state: "GA", latitude: 33.98, longitude: -83.88, created_at: nil, updated_at: nil>, #<Location id: 12856, zipcode: "30047", city: "Lilburn", state: "GA", latitude: 33.88, longitude: -84.13, created_at: nil, updated_at: nil>, #<Location id: 12857, zipcode: "30048", city: "Lilburn", state: "GA", latitude: 33.88, longitude: -84.13, created_at: nil, updated_at: nil>, #<Location id: 12822, zipcode: "30011", city: "Auburn", state: "GA", latitude: 34.01, longitude: -83.83, created_at: nil, updated_at: nil>, #<Location id: 12834, zipcode: "30024", city: "Suwanee", state: "GA", latitude: 34.05, longitude: -84.07, created_at: nil, updated_at: nil>, #<Location id: 12836, zipcode: "30026", city: "North Metro", state: "GA", latitude: 34.0, longitude: -84.15, created_at: nil, updated_at: nil>, #<Location id: 12838, zipcode: "30029", city: "North Metro", state: "GA", latitude: 34.0, longitude: -84.15, created_at: nil, updated_at: nil>, #<Location id: 12898, zipcode: "30095", city: "Duluth", state: "GA", latitude: 34.0, longitude: -84.15, created_at: nil, updated_at: nil>, #<Location id: 12899, zipcode: "30096", city: "Duluth", state: "GA", latitude: 34.0, longitude: -84.15, created_at: nil, updated_at: nil>, #<Location id: 12900, zipcode: "30097", city: "Duluth", state: "GA", latitude: 34.0, longitude: -84.15, created_at: nil, updated_at: nil>, #<Location id: 12901, zipcode: "30098", city: "Duluth", state: "GA", latitude: 34.0, longitude: -84.15, created_at: nil, updated_at: nil>] 


user = User.first
  User Load (0.3ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
 => #<User id: 1, email: "ad...@admin.com", password_digest: "$2a$10$OuN0/fqWzy4ZTyIf4y94teIdnndAqwS.1V5zxgO9X7wW...", zip_code: "30052", birthday: "1986-08-25", name: nil, username: "admin", gender: "Male", ethnicity: nil, sexuality: "Straight", career: nil, education: nil, religion: nil, politics: nil, children: nil, height: nil, user_smoke: nil, user_drink: nil, about_me: nil, inches: nil, feet: nil, created_at: "2014-01-16 14:17:36", updated_at: "2014-01-16 14:17:36", auth_token: "8Zwh3HF6WnA8wthFZirkQw", password_reset_token: nil, password_reset_sent_at: nil, admin: nil, role: "admin", roles_mask: nil, age: nil, default_photo_id: nil, location_id: nil, time_zone: nil> 
2.0.0-p353 :004 > user.location
   (15.4ms)  SELECT COUNT(*) FROM `locations` WHERE (zipcode = '30052')
  Location Load (6.8ms)  SELECT `locations`.* FROM `locations` WHERE (zipcode = '30052') ORDER BY `locations`.`id` ASC LIMIT 1
 => #<Location id: 12859, zipcode: "30052", city: "Loganville", state: "GA", latitude: 33.83, longitude: -83.89, created_at: nil, updated_at: nil>  


Location columns:

      t.string :zipcode
      t.string :city
      t.string :state
      t.float :latitude
      t.float :longitude

User columns:
      t.string :email
      t.string :password_digest
      t.string :zip_code
      t.string :location_id
      t.string :time_zone
      t.string :birthday
      t.string :name
      t.string :username
      t.string :gender
      t.string :ethnicity
      t.string :sexuality
      t.string :career
      t.string :education
      t.string :religion
      t.string :politics
      t.string :children
      t.string :height
      t.string :user_smoke
      t.string :user_drink
      t.string :about_me
      t.string :inches
      t.string :feet 


class DistanceController < ApplicationController
  def search
    @latitude = params[:latitude].to_f * Math::PI / 180
    @longitude = params[:longitude].to_f * Math::PI / 180
    @users = Location.search :geo => [@latitude * Math::PI / 180, @longitude * Math::PI / 180], :with => {:geodist => 0.0..200_000.0}, :order => "geodist ASC"
  end
  
  def users
      @users = User.all
  end
  
  def index
    @users = User.all
  end
end


ThinkingSphinx::Index.define :location, :with => :active_record do 
  indexes city 
  
  has "RADIANS(locations.latitude)",  :as => :latitude,  :type => :float
  has "RADIANS(locations.longitude)", :as => :longitude, :type => :float
end 
ThinkingSphinx::Index.define :user, :with => :active_record do 
  indexes name, :as => :user, :sortable => true 
  indexes zip_code
  has created_at, updated_at 
  has location.id, :as => :location_id
end 
 

<%= form_tag searches_path, method: :get do %>
<p>
        <%= text_field_tag :search, params[:search] %>
        <%= button_tag "Search", name: nil %>
        </p>
<% end %>

Pat Allan

unread,
Jan 21, 2014, 6:26:26 PM1/21/14
to thinkin...@googlegroups.com
So, it seems this is the action that needs changing:

@latitude = params[:latitude].to_f * Math::PI / 180
@longitude = params[:longitude].to_f * Math::PI / 180
@users = Location.search :geo => [@latitude * Math::PI / 180, @longitude * Math::PI / 180], :with => {:geodist => 0.0..200_000.0}, :order => "geodist ASC"

I would switch it to:

latitude = params[:latitude].to_f * Math::PI / 180
longitude = params[:longitude].to_f * Math::PI / 180

location_ids = Location.search_for_ids(
:geo => [latitude, longitude],
:with => {:geodist => 0.0..200_000.0},
:order => "geodist ASC”
)
@users = User.where(:location_id => location_ids)

Note the following:
* I’ve removed the double radians conversion
* I’m using the search_for_ids method to avoid instantiating unnecessary location objects.
* Instance variables are only useful for objects used in the views, so I’ve made a presumption and just set @users, everything else is a local variable instead.

If all you want is the user to enter their zip code for the search, then it would be more like this:

location = Location.find_by_zipcode params[:zipcode]
# if lat/lng are stored as degrees
latitude = location.latitude * Math::PI / 180
longitude = location.longitude * Math::PI / 180

location_ids = Location.search_for_ids(
:geo => [latitude, longitude],
:with => {:geodist => 0.0..200_000.0},
:order => "geodist ASC”
)
@users = User.where(:location_id => location_ids)

Hope this helps!


Pat
> --
> You received this message because you are subscribed to the Google Groups "Thinking Sphinx" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to thinking-sphi...@googlegroups.com.
> To post to this group, send email to thinkin...@googlegroups.com.
> Visit this group at http://groups.google.com/group/thinking-sphinx.
> For more options, visit https://groups.google.com/groups/opt_out.

C Wilson

unread,
Jan 22, 2014, 10:21:07 AM1/22/14
to thinkin...@googlegroups.com
Thanks so much Pat for the response and clarifying exactly what you did. I understood the code you wrote and I can't believe I wasn't able to piece that together!

Quick question:

      :with  => {:geodist => 0.0..200_000.0}, 

The 200km is equivalent to 124.274 miles. However for the search it is pulling from a smaller range than that. It seems like from a 20 mile radius only.

As I am doing a search with zip code 30052, however it is only returning two locations that are near each other. A third location should be appearing also that's 50 miles away.

Pat Allan

unread,
Jan 22, 2014, 6:17:50 PM1/22/14
to thinkin...@googlegroups.com
I’m not really sure what the cause of the problem is… as far as I can see, you’re passing in the right parameters and range. If there was some mix-up somewhere between radians and degrees or lat/lng around the wrong way (I’ve had bad data imports before which have done that), then you wouldn’t even get the two closest results, I would think.

I guess I’d be debugging by firing up a mysql console to Sphinx (mysql —host 127.0.0.1 —port 9306) and running SphinxQL queries to confirm attribute values (SELECT * FROM location_core WHERE sphinx_internal_id = x).

C Wilson

unread,
Jan 24, 2014, 11:17:23 AM1/24/14
to thinkin...@googlegroups.com
I think the issue is something is limiting it to only 20 entries. When I run in the console it finds 979 results but the`ASC LIMIT 0, 20` has limited it to only showing 20 of the closest areas.

So I have to increase the ASC limit. I'm not sure why 20 is being used as the max.

 Location.search :geo => [33.83 * Math::PI / 180, -83.89 * Math::PI / 180], :with => {:geodist => 0.0..200_000.0}, :order => "geodist ASC"
  Sphinx Query (27.0ms)  SELECT GEODIST(0.5904448859496816, -1.464156709498043, latitude, longitude) AS geodist, * FROM `location_core` WHERE `geodist` BETWEEN 0.0 AND 200000.0 AND `sphinx_deleted` = 0 ORDER BY `geodist` ASC LIMIT 0, 20
  Sphinx  Found 979 results
  Location Load (0.6ms)  SELECT `locations`.* FROM `locations` WHERE `locations`.`id` IN (12859, 12828, 12848, 12882, 12851, 12852, 12853, 12854, 12855, 12829, 12830, 13285, 13286, 12822, 13261, 12823, 12824, 12897, 12856, 12857) 

Pat Allan

unread,
Jan 24, 2014, 2:09:57 PM1/24/14
to thinkin...@googlegroups.com
Sphinx paginates by default - indeed, you can’t disable pagination, but you can make the page sizes really big using :per_page or :limit.

Do keep in mind that Sphinx has an upper limit of whatever max_matches is set to (and the default for that is 1000).

Cheers

— 
Pat

Cornety

unread,
Jan 24, 2014, 3:06:36 PM1/24/14
to thinkin...@googlegroups.com
Thanks for all the assistance!
You received this message because you are subscribed to a topic in the Google Groups "Thinking Sphinx" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/thinking-sphinx/m6H_24ZSqvs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to thinking-sphi...@googlegroups.com.

C Wilson

unread,
Jan 29, 2014, 10:47:35 AM1/29/14
to thinkin...@googlegroups.com
Quick question. If I wanted to remove the search by location ids, so that it is only matching the User zip codes to the Location zip codes (currently matching the location ids). Do I need to remove the Location.search_for_ids?

I changed  @users = User.where(:location_id => location_ids)
 
to

        @users = User.where(zip_code: location_zips.map(&:zip_code))

With that change it thinks the location id is the zip code so I get `undefined method `zip_code' for 12859:Fixnum`
Pat
 
To unsubscribe from this group and stop receiving emails from it, send an email to thinking-sphinx+unsubscribe@googlegroups.com.
To post to this group, send email to thinking-sphinx@googlegroups.com.
--
You received this message because you are subscribed to a topic in the Google Groups "Thinking Sphinx" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/thinking-sphinx/m6H_24ZSqvs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to thinking-sphinx+unsubscribe@googlegroups.com.
To post to this group, send email to thinking-sphinx@googlegroups.com.

Pat Allan

unread,
Jan 29, 2014, 9:25:09 PM1/29/14
to thinkin...@googlegroups.com
Hard to get the full context of what you’re doing… but it seems location_zips is a collection of integers - the result of the search_for_ids? If so, you’ll probably want to change that to just a search call, so proper Location objects are returned.

C Wilson

unread,
Feb 3, 2014, 4:28:58 PM2/3/14
to thinkin...@googlegroups.com
Ok I got it to work with:

      locations = Location.search( 
          :geo   => [latitude, longitude], 
          :with  => {:geodist => 0.0..600_000.0}, 
          :order => 'geodist ASC',
          :per_page => 5_000
        ) 
        @users = User.where(zip_code: locations.map(&:zipcode))
Reply all
Reply to author
Forward
0 new messages