acts_as_geocodable 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/CHANGELOG +13 -0
- data/MIT-LICENSE +25 -0
- data/README +104 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/about.yml +7 -0
- data/acts_as_geocodable.gemspec +69 -0
- data/generators/geocodable_migration/USAGE +12 -0
- data/generators/geocodable_migration/geocodable_migration_generator.rb +7 -0
- data/generators/geocodable_migration/templates/migration.rb +40 -0
- data/install.rb +1 -0
- data/lib/acts_as_geocodable.rb +286 -0
- data/lib/acts_as_geocodable/geocode.rb +61 -0
- data/lib/acts_as_geocodable/geocoding.rb +12 -0
- data/lib/acts_as_geocodable/remote_location.rb +18 -0
- data/lib/acts_as_geocodable/tasks/acts_as_geocodable_tasks.rake +4 -0
- data/rails/init.rb +5 -0
- data/test/acts_as_geocodable_test.rb +340 -0
- data/test/db/database.yml +18 -0
- data/test/db/schema.rb +60 -0
- data/test/fixtures/cities.yml +12 -0
- data/test/fixtures/geocodes.yml +51 -0
- data/test/fixtures/geocodings.yml +15 -0
- data/test/fixtures/vacations.yml +15 -0
- data/test/geocode_test.rb +97 -0
- data/test/test_helper.rb +46 -0
- data/uninstall.rb +1 -0
- metadata +86 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
0.2.1 - 2008-8-8
|
2
|
+
* Results are now WillPaginate compatible
|
3
|
+
|
4
|
+
0.2.0 - 2007-10-27
|
5
|
+
* Added validates_as_geocodable (Mark Van Holstyn)
|
6
|
+
* Allow address mapping to be a single field (Mark Van Holstyn)
|
7
|
+
|
8
|
+
0.1.0
|
9
|
+
* Added remote_location to get a users location based on their remote_ip
|
10
|
+
* renamed :city to :locality in address mapping to be consistent with Graticule 0.2
|
11
|
+
create a migration with:
|
12
|
+
rename_column :geocodes, :city, :locality
|
13
|
+
* replace #full_address with #to_location
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2006 Daniel Morrison, Collective Idea
|
2
|
+
http://collectiveidea.com
|
3
|
+
|
4
|
+
Testing code was heavily influenced by Rick Olson. Thanks, Rick!
|
5
|
+
Testing Portions Copyright (c) 2006 Rick Olson
|
6
|
+
http://techno-weenie.net
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
9
|
+
a copy of this software and associated documentation files (the
|
10
|
+
"Software"), to deal in the Software without restriction, including
|
11
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
12
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
13
|
+
permit persons to whom the Software is furnished to do so, subject to
|
14
|
+
the following conditions:
|
15
|
+
|
16
|
+
The above copyright notice and this permission notice shall be
|
17
|
+
included in all copies or substantial portions of the Software.
|
18
|
+
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
20
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
21
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
22
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
23
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
24
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
25
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
= acts_as_geocodable
|
2
|
+
|
3
|
+
acts_as_geocodable is a plugin to help build geo-aware applications. It automatically geocodes your models when they are saved, giving you the ability to search by location and calculate distances between records.
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
event = Event.create :street => "777 NE Martin Luther King, Jr. Blvd.",
|
8
|
+
:locality => "Portland", :region => "Oregon", :postal_code => 97232
|
9
|
+
|
10
|
+
event.geocode.latitude #=> 45.529100000000
|
11
|
+
event.geocode.longitude #=> -122.644200000000
|
12
|
+
|
13
|
+
event.distance_to "49423" #=> 1807.66560483205
|
14
|
+
|
15
|
+
Event.find(:all, :within => 50, :origin => "97232")
|
16
|
+
|
17
|
+
Event.find(:nearest, :origin => "Portland, OR")
|
18
|
+
|
19
|
+
== Upgrading
|
20
|
+
|
21
|
+
If you're upgrading from a previous version of this plugin, note that :city has been renamed to :locality to be consistent with Graticule 0.2. Create a migration that has:
|
22
|
+
|
23
|
+
rename_column :geocodes, :city, :locality
|
24
|
+
|
25
|
+
Also remember to change your mapping in your geocodable classes to use the :locality key instead of :city:
|
26
|
+
|
27
|
+
class Event < ActiveRecord::Base
|
28
|
+
acts_as_geocodable :address => {:street => :address1, :locality => :city,
|
29
|
+
:region => :state, :postal_code => :zip}
|
30
|
+
end
|
31
|
+
|
32
|
+
== Installation
|
33
|
+
|
34
|
+
Graticule[link:http://rubyforge.org/projects/graticule] is used for all the heavy lifting.
|
35
|
+
|
36
|
+
gem install graticule --include-dependencies
|
37
|
+
|
38
|
+
Install as a plugin
|
39
|
+
|
40
|
+
script/plugin install git://github.com/collectiveidea/acts_as_geocodable.git
|
41
|
+
|
42
|
+
Or, install as a gem
|
43
|
+
|
44
|
+
gem install acts_as_geocodable --source=http://gemcutter.org
|
45
|
+
|
46
|
+
|
47
|
+
== Upgrading
|
48
|
+
|
49
|
+
Before October 2008, precision wasn't included in the Geocode model. Make sure you add a string precision column to your geocode table if you're upgrading from an older version, and update Graticule.
|
50
|
+
|
51
|
+
== Configuration
|
52
|
+
|
53
|
+
Create the required tables
|
54
|
+
|
55
|
+
script/generate geocodable_migration add_geocodable_tables
|
56
|
+
rake db:migrate
|
57
|
+
|
58
|
+
Set the default geocoder in your environment.rb file.
|
59
|
+
|
60
|
+
Geocode.geocoder = Graticule.service(:yahoo).new 'your_api_key'
|
61
|
+
|
62
|
+
Then, in each model you want to make geocodable, add acts_as_geocodable.
|
63
|
+
|
64
|
+
class Event < ActiveRecord::Base
|
65
|
+
acts_as_geocodable
|
66
|
+
end
|
67
|
+
|
68
|
+
The only requirement is that your model must have address fields. By default, acts_as_geocodable looks for attributes called +street+, +locality+, +region+, +postal_code+, and +country+. To change these, you can provide a mapping in the <tt>:address</tt> option:
|
69
|
+
|
70
|
+
class Event < ActiveRecord::Base
|
71
|
+
acts_as_geocodable :address => {:street => :address1, :locality => :city, :region => :state, :postal_code => :zip}
|
72
|
+
end
|
73
|
+
|
74
|
+
If that doesn't meet your needs, simply override the default +to_location+ method in your model, and return a Graticule::Location with those attributes set.
|
75
|
+
|
76
|
+
acts_as_geocodable can also update your address fields with the data returned from the geocoding service:
|
77
|
+
|
78
|
+
class Event < ActiveRecord::Base
|
79
|
+
acts_as_geocodable :normalize_address => true
|
80
|
+
end
|
81
|
+
|
82
|
+
== IP-based Geocoding
|
83
|
+
|
84
|
+
acts_as_geocodable adds a remote_location method in your controllers that uses http://hostip.info to guess remote users location based on their IP address.
|
85
|
+
|
86
|
+
def index
|
87
|
+
@nearest = Store.find(:nearest, :origin => remote_location) if remote_location
|
88
|
+
@stores = Store.find(:all)
|
89
|
+
end
|
90
|
+
|
91
|
+
Keep in mind that IP-based geocoding is not always accurate, and often will not return any results.
|
92
|
+
|
93
|
+
== Development
|
94
|
+
|
95
|
+
The source code is available at:
|
96
|
+
http://github.com/collectiveidea/acts_as_geocodable
|
97
|
+
git://github.com/collectiveidea/acts_as_geocodable.git
|
98
|
+
|
99
|
+
Patches and suggestions are welcome!
|
100
|
+
|
101
|
+
== To Do
|
102
|
+
|
103
|
+
* Documentation!!!
|
104
|
+
* configurable formulas
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'load_multi_rails_rake_tasks'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc 'Test the acts_as_geocodable plugin.'
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the acts_as_geocodable plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'ActsAsGeocodable'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'jeweler'
|
27
|
+
Jeweler::Tasks.new do |gemspec|
|
28
|
+
gemspec.name = 'acts_as_geocodable'
|
29
|
+
gemspec.summary = 'Simple geocoding for Rails ActiveRecord models'
|
30
|
+
gemspec.description = 'Simple geocoding for Rails ActiveRecord models. See the README for more details.'
|
31
|
+
gemspec.email = 'info@collectiveidea.com'
|
32
|
+
gemspec.homepage = 'http://github.com/collectiveidea/acts_as_geocodable'
|
33
|
+
gemspec.authors = ['Daniel Morrison', 'Brandon Keepers']
|
34
|
+
end
|
35
|
+
Jeweler::GemcutterTasks.new
|
36
|
+
rescue LoadError
|
37
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
38
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/about.yml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
author:
|
2
|
+
name: Brandon Keepers and Daniel Morrison, Collective Idea
|
3
|
+
homepage: http://collectiveidea.com
|
4
|
+
summary: A plugin to help build geo-aware applications
|
5
|
+
homepage: http://opensoul.org/2007/2/13/geocoding-as-easy-as-1-2
|
6
|
+
plugin: http://source.collectiveidea.com/public/rails/plugins/acts_as_geocodable
|
7
|
+
license: MIT
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{acts_as_geocodable}
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Daniel Morrison", "Brandon Keepers"]
|
12
|
+
s.date = %q{2009-10-21}
|
13
|
+
s.description = %q{Simple geocoding for Rails ActiveRecord models. See the README for more details.}
|
14
|
+
s.email = %q{info@collectiveidea.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"CHANGELOG",
|
21
|
+
"MIT-LICENSE",
|
22
|
+
"README",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"about.yml",
|
26
|
+
"acts_as_geocodable.gemspec",
|
27
|
+
"generators/geocodable_migration/USAGE",
|
28
|
+
"generators/geocodable_migration/geocodable_migration_generator.rb",
|
29
|
+
"generators/geocodable_migration/templates/migration.rb",
|
30
|
+
"install.rb",
|
31
|
+
"lib/acts_as_geocodable.rb",
|
32
|
+
"lib/acts_as_geocodable/geocode.rb",
|
33
|
+
"lib/acts_as_geocodable/geocoding.rb",
|
34
|
+
"lib/acts_as_geocodable/remote_location.rb",
|
35
|
+
"lib/acts_as_geocodable/tasks/acts_as_geocodable_tasks.rake",
|
36
|
+
"rails/init.rb",
|
37
|
+
"test/acts_as_geocodable_test.rb",
|
38
|
+
"test/db/database.yml",
|
39
|
+
"test/db/schema.rb",
|
40
|
+
"test/fixtures/cities.yml",
|
41
|
+
"test/fixtures/geocodes.yml",
|
42
|
+
"test/fixtures/geocodings.yml",
|
43
|
+
"test/fixtures/vacations.yml",
|
44
|
+
"test/geocode_test.rb",
|
45
|
+
"test/test_helper.rb",
|
46
|
+
"uninstall.rb"
|
47
|
+
]
|
48
|
+
s.homepage = %q{http://github.com/collectiveidea/acts_as_geocodable}
|
49
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.rubygems_version = %q{1.3.5}
|
52
|
+
s.summary = %q{Simple geocoding for Rails ActiveRecord models}
|
53
|
+
s.test_files = [
|
54
|
+
"test/acts_as_geocodable_test.rb",
|
55
|
+
"test/db/schema.rb",
|
56
|
+
"test/geocode_test.rb",
|
57
|
+
"test/test_helper.rb"
|
58
|
+
]
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
65
|
+
else
|
66
|
+
end
|
67
|
+
else
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Description:
|
2
|
+
The geocodable migration generator creates a migration which you can use to generate two tables:
|
3
|
+
|
4
|
+
geocodes - Contains the latitude and longitude coordinates, with the address they refer to.
|
5
|
+
geocodings - The polymorphic join table.
|
6
|
+
|
7
|
+
|
8
|
+
Example:
|
9
|
+
./script/generate geocodable_migration add_geocodable_tables
|
10
|
+
|
11
|
+
With 4 existing migrations, this will create an AddGeocodableTables migration in the
|
12
|
+
file db/migrate/5_add_geocodable_tables.rb
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class <%= class_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table "geocodes" do |t|
|
4
|
+
t.column "latitude", :decimal, :precision => 15, :scale => 12
|
5
|
+
t.column "longitude", :decimal, :precision => 15, :scale => 12
|
6
|
+
t.column "query", :string
|
7
|
+
t.column "street", :string
|
8
|
+
t.column "locality", :string
|
9
|
+
t.column "region", :string
|
10
|
+
t.column "postal_code", :string
|
11
|
+
t.column "country", :string
|
12
|
+
t.column "precision", :string
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index "geocodes", ["longitude"], :name => "geocodes_longitude_index"
|
16
|
+
add_index "geocodes", ["latitude"], :name => "geocodes_latitude_index"
|
17
|
+
add_index "geocodes", ["query"], :name => "geocodes_query_index", :unique => true
|
18
|
+
add_index "geocodes", ["locality"], :name => "geocodes_locality_index"
|
19
|
+
add_index "geocodes", ["region"], :name => "geocodes_region_index"
|
20
|
+
add_index "geocodes", ["postal_code"], :name => "geocodes_postal_code_index"
|
21
|
+
add_index "geocodes", ["country"], :name => "geocodes_country_index"
|
22
|
+
add_index "geocodes", ["precision"], :name => "geocodes_precision_index"
|
23
|
+
|
24
|
+
|
25
|
+
create_table "geocodings" do |t|
|
26
|
+
t.column "geocodable_id", :integer
|
27
|
+
t.column "geocode_id", :integer
|
28
|
+
t.column "geocodable_type", :string
|
29
|
+
end
|
30
|
+
|
31
|
+
add_index "geocodings", ["geocodable_type"], :name => "geocodings_geocodable_type_index"
|
32
|
+
add_index "geocodings", ["geocode_id"], :name => "geocodings_geocode_id_index"
|
33
|
+
add_index "geocodings", ["geocodable_id"], :name => "geocodings_geocodable_id_index"
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.down
|
37
|
+
drop_table :geocodes
|
38
|
+
drop_table :geocodings
|
39
|
+
end
|
40
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'acts_as_geocodable/geocoding'
|
2
|
+
require 'acts_as_geocodable/geocode'
|
3
|
+
require 'acts_as_geocodable/remote_location'
|
4
|
+
|
5
|
+
module CollectiveIdea #:nodoc:
|
6
|
+
module Acts #:nodoc:
|
7
|
+
module Geocodable #:nodoc:
|
8
|
+
|
9
|
+
def self.included(mod)
|
10
|
+
mod.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
# Make a model geocodable.
|
16
|
+
#
|
17
|
+
# class Event < ActiveRecord::Base
|
18
|
+
# acts_as_geocodable
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# == Options
|
22
|
+
# * <tt>:address</tt>: A hash that maps geocodable attirbutes (<tt>:street</tt>,
|
23
|
+
# <tt>:locality</tt>, <tt>:region</tt>, <tt>:postal_code</tt>, <tt>:country</tt>)
|
24
|
+
# to your model's address fields, or a symbol to store the entire address in one field
|
25
|
+
# * <tt>:normalize_address</tt>: If set to true, you address fields will be updated
|
26
|
+
# using the address fields returned by the geocoder. (Default is +false+)
|
27
|
+
# * <tt>:units</tt>: Default units-<tt>:miles</tt> or <tt>:kilometers</tt>-used for
|
28
|
+
# distance calculations and queries. (Default is <tt>:miles</tt>)
|
29
|
+
#
|
30
|
+
def acts_as_geocodable(options = {})
|
31
|
+
options = {
|
32
|
+
:address => {
|
33
|
+
:street => :street, :locality => :locality, :region => :region,
|
34
|
+
:postal_code => :postal_code, :country => :country},
|
35
|
+
:normalize_address => false,
|
36
|
+
:distance_column => 'distance',
|
37
|
+
:units => :miles
|
38
|
+
}.merge(options)
|
39
|
+
|
40
|
+
write_inheritable_attribute :acts_as_geocodable_options, options
|
41
|
+
class_inheritable_reader :acts_as_geocodable_options
|
42
|
+
|
43
|
+
define_callbacks :after_geocoding
|
44
|
+
|
45
|
+
has_one :geocoding, :as => :geocodable, :include => :geocode, :dependent => :destroy
|
46
|
+
|
47
|
+
after_save :attach_geocode
|
48
|
+
|
49
|
+
include CollectiveIdea::Acts::Geocodable::InstanceMethods
|
50
|
+
extend CollectiveIdea::Acts::Geocodable::SingletonMethods
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
module SingletonMethods
|
56
|
+
|
57
|
+
# Extends ActiveRecord's find method to be geo-aware.
|
58
|
+
#
|
59
|
+
# Model.find(:all, :within => 10, :origin => "Chicago, IL")
|
60
|
+
#
|
61
|
+
# Whenever find is called with an <tt>:origin</tt>, a +distance+ attribute
|
62
|
+
# indicating the distance to the origin is added to each of the results:
|
63
|
+
#
|
64
|
+
# Model.find(:first, :origin => "Portland, OR").distance #=> 388.383
|
65
|
+
#
|
66
|
+
# +acts_as_geocodable+ adds 2 other retrieval approaches to ActiveRecord's default
|
67
|
+
# find by id, find <tt>:first</tt>, and find <tt>:all</tt>:
|
68
|
+
#
|
69
|
+
# * <tt>:nearest</tt>: find the nearest location to the given origin
|
70
|
+
# * <tt>:farthest</tt>: find the farthest location from the given origin
|
71
|
+
#
|
72
|
+
# Model.find(:nearest, :origin => "Grand Rapids, MI")
|
73
|
+
#
|
74
|
+
# == Options
|
75
|
+
#
|
76
|
+
# * <tt>:origin</tt>: A Geocode, String, or geocodable model that specifies
|
77
|
+
# the origin
|
78
|
+
# * <tt>:within</tt>: Limit to results within this radius of the origin
|
79
|
+
# * <tt>:beyond</tt>: Limit to results outside of this radius from the origin
|
80
|
+
# * <tt>:units</tt>: Units to use for <tt>:within</tt> or <tt>:beyond</tt>.
|
81
|
+
# Default is <tt>:miles</tt> unless specified otherwise in the +acts_as_geocodable+
|
82
|
+
# declaration.
|
83
|
+
#
|
84
|
+
def find(*args)
|
85
|
+
options = args.extract_options!
|
86
|
+
origin = location_to_geocode options.delete(:origin)
|
87
|
+
if origin
|
88
|
+
options[:units] ||= acts_as_geocodable_options[:units]
|
89
|
+
add_distance_to_select!(origin, options)
|
90
|
+
with_proximity!(args, options) do
|
91
|
+
geocode_conditions!(options, origin) do
|
92
|
+
join_geocodes { super *args.push(options) }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
else
|
96
|
+
super *args.push(options)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Extends ActiveRecord's count method to be geo-aware.
|
101
|
+
#
|
102
|
+
# Model.count(:within => 10, :origin => "Chicago, IL")
|
103
|
+
#
|
104
|
+
# == Options
|
105
|
+
#
|
106
|
+
# * <tt>:origin</tt>: A Geocode, String, or geocodable model that specifies
|
107
|
+
# the origin
|
108
|
+
# * <tt>:within</tt>: Limit to results within this radius of the origin
|
109
|
+
# * <tt>:beyond</tt>: Limit to results outside of this radius from the origin
|
110
|
+
# * <tt>:units</tt>: Units to use for <tt>:within</tt> or <tt>:beyond</tt>.
|
111
|
+
# Default is <tt>:miles</tt> unless specified otherwise in the +acts_as_geocodable+
|
112
|
+
# declaration.
|
113
|
+
#
|
114
|
+
def count(*args)
|
115
|
+
options = args.extract_options!
|
116
|
+
origin = location_to_geocode options.delete(:origin)
|
117
|
+
if origin
|
118
|
+
options[:units] ||= acts_as_geocodable_options[:units]
|
119
|
+
with_proximity!(args, options) do
|
120
|
+
geocode_conditions!(options, origin) do
|
121
|
+
join_geocodes { super *args.push(options) }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
else
|
125
|
+
super *args.push(options)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Convert the given location to a Geocode
|
130
|
+
def location_to_geocode(location)
|
131
|
+
case location
|
132
|
+
when Geocode then location
|
133
|
+
when InstanceMethods then location.geocode
|
134
|
+
when String, Fixnum then Geocode.find_or_create_by_query(location)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def validates_as_geocodable(options = {})
|
139
|
+
options = options.reverse_merge :message => "Address could not be geocoded.", :allow_nil => false
|
140
|
+
validate do |geocodable|
|
141
|
+
if !(options[:allow_nil] && geocodable.to_location.attributes.all?(&:blank?)) &&
|
142
|
+
!Geocode.find_or_create_by_location(geocodable.to_location)
|
143
|
+
geocodable.errors.add_to_base options[:message]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def add_distance_to_select!(origin, options)
|
151
|
+
(options[:select] ||= "#{table_name}.*") <<
|
152
|
+
", #{sql_for_distance(origin, options[:units])} AS
|
153
|
+
#{acts_as_geocodable_options[:distance_column]}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def with_proximity!(args, options)
|
157
|
+
if [:nearest, :farthest].include?(args.first)
|
158
|
+
raise ArgumentError, ":include cannot be specified with :nearest and :farthest" if options[:include]
|
159
|
+
direction = args.first == :nearest ? "ASC" : "DESC"
|
160
|
+
args[0] = :first
|
161
|
+
with_scope :find => { :order => "#{acts_as_geocodable_options[:distance_column]} #{direction}"} do
|
162
|
+
yield
|
163
|
+
end
|
164
|
+
else
|
165
|
+
yield
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def join_geocodes(&block)
|
170
|
+
with_scope :find => { :joins => "JOIN geocodings ON
|
171
|
+
#{table_name}.#{primary_key} = geocodings.geocodable_id AND
|
172
|
+
geocodings.geocodable_type = '#{class_name}'
|
173
|
+
JOIN geocodes ON geocodings.geocode_id = geocodes.id" } do
|
174
|
+
yield
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def geocode_conditions!(options, origin)
|
179
|
+
units = options.delete(:units)
|
180
|
+
conditions = []
|
181
|
+
conditions << "#{sql_for_distance(origin, units)} <= #{options.delete(:within)}" if options[:within]
|
182
|
+
conditions << "#{sql_for_distance(origin, units)} > #{options.delete(:beyond)}" if options[:beyond]
|
183
|
+
if conditions.empty?
|
184
|
+
yield
|
185
|
+
else
|
186
|
+
with_scope(:find => { :conditions => conditions.join(" AND ") }) { yield }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def sql_for_distance(origin, units = acts_as_geocodable_options[:units])
|
191
|
+
Graticule::Distance::Spherical.to_sql(
|
192
|
+
:latitude => origin.latitude,
|
193
|
+
:longitude => origin.longitude,
|
194
|
+
:latitude_column => "geocodes.latitude",
|
195
|
+
:longitude_column => "geocodes.longitude",
|
196
|
+
:units => units
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
module InstanceMethods
|
203
|
+
|
204
|
+
# Get the geocode for this model
|
205
|
+
def geocode
|
206
|
+
geocoding.geocode if geocoding
|
207
|
+
end
|
208
|
+
|
209
|
+
# Create a Graticule::Location
|
210
|
+
def to_location
|
211
|
+
returning Graticule::Location.new do |location|
|
212
|
+
[:street, :locality, :region, :postal_code, :country].each do |attr|
|
213
|
+
location.send "#{attr}=", geo_attribute(attr)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Get the distance to the given destination. The destination can be an
|
219
|
+
# acts_as_geocodable model, a Geocode, or a string
|
220
|
+
#
|
221
|
+
# myhome.distance_to "Chicago, IL"
|
222
|
+
# myhome.distance_to "49423"
|
223
|
+
# myhome.distance_to other_model
|
224
|
+
#
|
225
|
+
# == Options
|
226
|
+
# * <tt>:units</tt>: <tt>:miles</tt> or <tt>:kilometers</tt>
|
227
|
+
# * <tt>:formula</tt>: The formula to use to calculate the distance. This can
|
228
|
+
# be any formula supported by Graticule. The default is <tt>:haversine</tt>.
|
229
|
+
#
|
230
|
+
def distance_to(destination, options = {})
|
231
|
+
units = options[:units] || acts_as_geocodable_options[:units]
|
232
|
+
formula = options[:formula] || :haversine
|
233
|
+
|
234
|
+
geocode = self.class.location_to_geocode(destination)
|
235
|
+
self.geocode.distance_to(geocode, units, formula)
|
236
|
+
end
|
237
|
+
|
238
|
+
protected
|
239
|
+
|
240
|
+
# Perform the geocoding
|
241
|
+
def attach_geocode
|
242
|
+
new_geocode = Geocode.find_or_create_by_location self.to_location unless self.to_location.blank?
|
243
|
+
if new_geocode && self.geocode != new_geocode
|
244
|
+
self.geocoding = Geocoding.new :geocode => new_geocode
|
245
|
+
self.update_address self.acts_as_geocodable_options[:normalize_address]
|
246
|
+
callback :after_geocoding
|
247
|
+
elsif !new_geocode && self.geocoding
|
248
|
+
self.geocoding.destroy
|
249
|
+
end
|
250
|
+
rescue Graticule::Error => e
|
251
|
+
logger.warn e.message
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
def update_address(force = false)
|
256
|
+
unless self.geocode.blank?
|
257
|
+
if self.acts_as_geocodable_options[:address].is_a? Symbol
|
258
|
+
method = self.acts_as_geocodable_options[:address]
|
259
|
+
if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
|
260
|
+
self.send "#{method}=", self.geocode.to_location.to_s
|
261
|
+
end
|
262
|
+
else
|
263
|
+
self.acts_as_geocodable_options[:address].each do |attribute,method|
|
264
|
+
if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
|
265
|
+
self.send "#{method}=", self.geocode.send(attribute)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
update_without_callbacks
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def geo_attribute(attr_key)
|
275
|
+
if self.acts_as_geocodable_options[:address].is_a? Symbol
|
276
|
+
attr_name = self.acts_as_geocodable_options[:address]
|
277
|
+
attr_key == :street ? self.send(attr_name) : nil
|
278
|
+
else
|
279
|
+
attr_name = self.acts_as_geocodable_options[:address][attr_key]
|
280
|
+
attr_name && self.respond_to?(attr_name) ? self.send(attr_name) : nil
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|