really-broken-geocoder 1.5.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +557 -0
- data/LICENSE +20 -0
- data/README.md +3 -0
- data/bin/geocode +5 -0
- data/examples/autoexpire_cache_dalli.rb +62 -0
- data/examples/autoexpire_cache_redis.rb +28 -0
- data/examples/cache_bypass.rb +48 -0
- data/examples/reverse_geocode_job.rb +40 -0
- data/lib/generators/geocoder/config/config_generator.rb +14 -0
- data/lib/generators/geocoder/config/templates/initializer.rb +22 -0
- data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +30 -0
- data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +30 -0
- data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
- data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
- data/lib/generators/geocoder/migration_version.rb +15 -0
- data/lib/geocoder.rb +48 -0
- data/lib/geocoder/cache.rb +94 -0
- data/lib/geocoder/calculations.rb +420 -0
- data/lib/geocoder/cli.rb +121 -0
- data/lib/geocoder/configuration.rb +137 -0
- data/lib/geocoder/configuration_hash.rb +11 -0
- data/lib/geocoder/esri_token.rb +38 -0
- data/lib/geocoder/exceptions.rb +40 -0
- data/lib/geocoder/ip_address.rb +26 -0
- data/lib/geocoder/kernel_logger.rb +25 -0
- data/lib/geocoder/logger.rb +47 -0
- data/lib/geocoder/lookup.rb +118 -0
- data/lib/geocoder/lookups/amap.rb +63 -0
- data/lib/geocoder/lookups/baidu.rb +63 -0
- data/lib/geocoder/lookups/baidu_ip.rb +30 -0
- data/lib/geocoder/lookups/ban_data_gouv_fr.rb +130 -0
- data/lib/geocoder/lookups/base.rb +348 -0
- data/lib/geocoder/lookups/bing.rb +82 -0
- data/lib/geocoder/lookups/db_ip_com.rb +52 -0
- data/lib/geocoder/lookups/dstk.rb +22 -0
- data/lib/geocoder/lookups/esri.rb +95 -0
- data/lib/geocoder/lookups/freegeoip.rb +60 -0
- data/lib/geocoder/lookups/geocoder_ca.rb +53 -0
- data/lib/geocoder/lookups/geocoder_us.rb +51 -0
- data/lib/geocoder/lookups/geocodio.rb +42 -0
- data/lib/geocoder/lookups/geoip2.rb +45 -0
- data/lib/geocoder/lookups/geoportail_lu.rb +65 -0
- data/lib/geocoder/lookups/google.rb +95 -0
- data/lib/geocoder/lookups/google_places_details.rb +50 -0
- data/lib/geocoder/lookups/google_places_search.rb +33 -0
- data/lib/geocoder/lookups/google_premier.rb +57 -0
- data/lib/geocoder/lookups/here.rb +77 -0
- data/lib/geocoder/lookups/ip2location.rb +75 -0
- data/lib/geocoder/lookups/ipapi_com.rb +82 -0
- data/lib/geocoder/lookups/ipdata_co.rb +62 -0
- data/lib/geocoder/lookups/ipinfo_io.rb +44 -0
- data/lib/geocoder/lookups/ipstack.rb +63 -0
- data/lib/geocoder/lookups/latlon.rb +59 -0
- data/lib/geocoder/lookups/location_iq.rb +50 -0
- data/lib/geocoder/lookups/mapbox.rb +59 -0
- data/lib/geocoder/lookups/mapquest.rb +58 -0
- data/lib/geocoder/lookups/maxmind.rb +90 -0
- data/lib/geocoder/lookups/maxmind_geoip2.rb +70 -0
- data/lib/geocoder/lookups/maxmind_local.rb +65 -0
- data/lib/geocoder/lookups/nominatim.rb +64 -0
- data/lib/geocoder/lookups/opencagedata.rb +65 -0
- data/lib/geocoder/lookups/pelias.rb +63 -0
- data/lib/geocoder/lookups/pickpoint.rb +41 -0
- data/lib/geocoder/lookups/pointpin.rb +69 -0
- data/lib/geocoder/lookups/postcode_anywhere_uk.rb +50 -0
- data/lib/geocoder/lookups/postcodes_io.rb +31 -0
- data/lib/geocoder/lookups/smarty_streets.rb +63 -0
- data/lib/geocoder/lookups/telize.rb +75 -0
- data/lib/geocoder/lookups/tencent.rb +59 -0
- data/lib/geocoder/lookups/test.rb +44 -0
- data/lib/geocoder/lookups/yandex.rb +62 -0
- data/lib/geocoder/models/active_record.rb +51 -0
- data/lib/geocoder/models/base.rb +39 -0
- data/lib/geocoder/models/mongo_base.rb +62 -0
- data/lib/geocoder/models/mongo_mapper.rb +26 -0
- data/lib/geocoder/models/mongoid.rb +32 -0
- data/lib/geocoder/query.rb +125 -0
- data/lib/geocoder/railtie.rb +26 -0
- data/lib/geocoder/request.rb +114 -0
- data/lib/geocoder/results/amap.rb +87 -0
- data/lib/geocoder/results/baidu.rb +79 -0
- data/lib/geocoder/results/baidu_ip.rb +62 -0
- data/lib/geocoder/results/ban_data_gouv_fr.rb +257 -0
- data/lib/geocoder/results/base.rb +79 -0
- data/lib/geocoder/results/bing.rb +52 -0
- data/lib/geocoder/results/db_ip_com.rb +58 -0
- data/lib/geocoder/results/dstk.rb +6 -0
- data/lib/geocoder/results/esri.rb +75 -0
- data/lib/geocoder/results/freegeoip.rb +40 -0
- data/lib/geocoder/results/geocoder_ca.rb +60 -0
- data/lib/geocoder/results/geocoder_us.rb +39 -0
- data/lib/geocoder/results/geocodio.rb +78 -0
- data/lib/geocoder/results/geoip2.rb +76 -0
- data/lib/geocoder/results/geoportail_lu.rb +71 -0
- data/lib/geocoder/results/google.rb +150 -0
- data/lib/geocoder/results/google_places_details.rb +39 -0
- data/lib/geocoder/results/google_places_search.rb +52 -0
- data/lib/geocoder/results/google_premier.rb +6 -0
- data/lib/geocoder/results/here.rb +79 -0
- data/lib/geocoder/results/ip2location.rb +22 -0
- data/lib/geocoder/results/ipapi_com.rb +45 -0
- data/lib/geocoder/results/ipdata_co.rb +40 -0
- data/lib/geocoder/results/ipinfo_io.rb +48 -0
- data/lib/geocoder/results/ipstack.rb +60 -0
- data/lib/geocoder/results/latlon.rb +71 -0
- data/lib/geocoder/results/location_iq.rb +6 -0
- data/lib/geocoder/results/mapbox.rb +57 -0
- data/lib/geocoder/results/mapquest.rb +48 -0
- data/lib/geocoder/results/maxmind.rb +130 -0
- data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
- data/lib/geocoder/results/maxmind_local.rb +44 -0
- data/lib/geocoder/results/nominatim.rb +109 -0
- data/lib/geocoder/results/opencagedata.rb +100 -0
- data/lib/geocoder/results/pelias.rb +58 -0
- data/lib/geocoder/results/pickpoint.rb +6 -0
- data/lib/geocoder/results/pointpin.rb +40 -0
- data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
- data/lib/geocoder/results/postcodes_io.rb +40 -0
- data/lib/geocoder/results/smarty_streets.rb +142 -0
- data/lib/geocoder/results/telize.rb +40 -0
- data/lib/geocoder/results/tencent.rb +72 -0
- data/lib/geocoder/results/test.rb +33 -0
- data/lib/geocoder/results/yandex.rb +134 -0
- data/lib/geocoder/sql.rb +110 -0
- data/lib/geocoder/stores/active_record.rb +328 -0
- data/lib/geocoder/stores/base.rb +115 -0
- data/lib/geocoder/stores/mongo_base.rb +58 -0
- data/lib/geocoder/stores/mongo_mapper.rb +13 -0
- data/lib/geocoder/stores/mongoid.rb +13 -0
- data/lib/geocoder/version.rb +3 -0
- data/lib/hash_recursive_merge.rb +74 -0
- data/lib/maxmind_database.rb +109 -0
- data/lib/tasks/geocoder.rake +54 -0
- data/lib/tasks/maxmind.rake +73 -0
- metadata +186 -0
@@ -0,0 +1,328 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'geocoder/sql'
|
3
|
+
require 'geocoder/stores/base'
|
4
|
+
|
5
|
+
##
|
6
|
+
# Add geocoding functionality to any ActiveRecord object.
|
7
|
+
#
|
8
|
+
module Geocoder::Store
|
9
|
+
module ActiveRecord
|
10
|
+
include Base
|
11
|
+
|
12
|
+
##
|
13
|
+
# Implementation of 'included' hook method.
|
14
|
+
#
|
15
|
+
def self.included(base)
|
16
|
+
base.extend ClassMethods
|
17
|
+
base.class_eval do
|
18
|
+
|
19
|
+
# scope: geocoded objects
|
20
|
+
scope :geocoded, lambda {
|
21
|
+
where("#{table_name}.#{geocoder_options[:latitude]} IS NOT NULL " +
|
22
|
+
"AND #{table_name}.#{geocoder_options[:longitude]} IS NOT NULL")
|
23
|
+
}
|
24
|
+
|
25
|
+
# scope: not-geocoded objects
|
26
|
+
scope :not_geocoded, lambda {
|
27
|
+
where("#{table_name}.#{geocoder_options[:latitude]} IS NULL " +
|
28
|
+
"OR #{table_name}.#{geocoder_options[:longitude]} IS NULL")
|
29
|
+
}
|
30
|
+
|
31
|
+
# scope: not-reverse geocoded objects
|
32
|
+
scope :not_reverse_geocoded, lambda {
|
33
|
+
where("#{table_name}.#{geocoder_options[:fetched_address]} IS NULL")
|
34
|
+
}
|
35
|
+
|
36
|
+
##
|
37
|
+
# Find all objects within a radius of the given location.
|
38
|
+
# Location may be either a string to geocode or an array of
|
39
|
+
# coordinates (<tt>[lat,lon]</tt>). Also takes an options hash
|
40
|
+
# (see Geocoder::Store::ActiveRecord::ClassMethods.near_scope_options
|
41
|
+
# for details).
|
42
|
+
#
|
43
|
+
scope :near, lambda{ |location, *args|
|
44
|
+
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
|
45
|
+
if Geocoder::Calculations.coordinates_present?(latitude, longitude)
|
46
|
+
options = near_scope_options(latitude, longitude, *args)
|
47
|
+
select(options[:select]).where(options[:conditions]).
|
48
|
+
order(options[:order])
|
49
|
+
else
|
50
|
+
# If no lat/lon given we don't want any results, but we still
|
51
|
+
# need distance and bearing columns so you can add, for example:
|
52
|
+
# .order("distance")
|
53
|
+
select(select_clause(nil, null_value, null_value)).where(false_condition)
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
##
|
58
|
+
# Find all objects within the area of a given bounding box.
|
59
|
+
# Bounds must be an array of locations specifying the southwest
|
60
|
+
# corner followed by the northeast corner of the box
|
61
|
+
# (<tt>[[sw_lat, sw_lon], [ne_lat, ne_lon]]</tt>).
|
62
|
+
#
|
63
|
+
scope :within_bounding_box, lambda{ |*bounds|
|
64
|
+
sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
|
65
|
+
if sw_lat && sw_lng && ne_lat && ne_lng
|
66
|
+
where(Geocoder::Sql.within_bounding_box(
|
67
|
+
sw_lat, sw_lng, ne_lat, ne_lng,
|
68
|
+
full_column_name(geocoder_options[:latitude]),
|
69
|
+
full_column_name(geocoder_options[:longitude])
|
70
|
+
))
|
71
|
+
else
|
72
|
+
select(select_clause(nil, null_value, null_value)).where(false_condition)
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Methods which will be class methods of the including class.
|
80
|
+
#
|
81
|
+
module ClassMethods
|
82
|
+
|
83
|
+
def distance_from_sql(location, *args)
|
84
|
+
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
|
85
|
+
if Geocoder::Calculations.coordinates_present?(latitude, longitude)
|
86
|
+
distance_sql(latitude, longitude, *args)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Get options hash suitable for passing to ActiveRecord.find to get
|
92
|
+
# records within a radius (in kilometers) of the given point.
|
93
|
+
# Options hash may include:
|
94
|
+
#
|
95
|
+
# * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
|
96
|
+
# for interpreting radius as well as the +distance+ attribute which
|
97
|
+
# is added to each found nearby object.
|
98
|
+
# Use Geocoder.configure[:units] to configure default units.
|
99
|
+
# * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
|
100
|
+
# the method to be used for calculating the bearing (direction)
|
101
|
+
# between the given point and each found nearby point;
|
102
|
+
# set to false for no bearing calculation. Use
|
103
|
+
# Geocoder.configure[:distances] to configure default calculation method.
|
104
|
+
# * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
|
105
|
+
# * +:select_distance+ - whether to include the distance alias in the
|
106
|
+
# SELECT SQL fragment (e.g. <formula> AS distance)
|
107
|
+
# * +:select_bearing+ - like +:select_distance+ but for bearing.
|
108
|
+
# * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
|
109
|
+
# set to false or nil to omit the ORDER BY clause
|
110
|
+
# * +:exclude+ - an object to exclude (used by the +nearbys+ method)
|
111
|
+
# * +:distance_column+ - used to set the column name of the calculated distance.
|
112
|
+
# * +:bearing_column+ - used to set the column name of the calculated bearing.
|
113
|
+
# * +:min_radius+ - the value to use as the minimum radius.
|
114
|
+
# ignored if database is sqlite.
|
115
|
+
# default is 0.0
|
116
|
+
#
|
117
|
+
def near_scope_options(latitude, longitude, radius = 20, options = {})
|
118
|
+
if options[:units]
|
119
|
+
options[:units] = options[:units].to_sym
|
120
|
+
end
|
121
|
+
latitude_attribute = options[:latitude] || geocoder_options[:latitude]
|
122
|
+
longitude_attribute = options[:longitude] || geocoder_options[:longitude]
|
123
|
+
options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
|
124
|
+
select_distance = options.fetch(:select_distance) { true }
|
125
|
+
options[:order] = "" if !select_distance && !options.include?(:order)
|
126
|
+
select_bearing = options.fetch(:select_bearing) { true }
|
127
|
+
bearing = bearing_sql(latitude, longitude, options)
|
128
|
+
distance = distance_sql(latitude, longitude, options)
|
129
|
+
distance_column = options.fetch(:distance_column) { 'distance' }
|
130
|
+
bearing_column = options.fetch(:bearing_column) { 'bearing' }
|
131
|
+
|
132
|
+
# If radius is a DB column name, bounding box should include
|
133
|
+
# all rows within the maximum radius appearing in that column.
|
134
|
+
# Note: performance is dependent on variability of radii.
|
135
|
+
bb_radius = radius.is_a?(Symbol) ? maximum(radius) : radius
|
136
|
+
b = Geocoder::Calculations.bounding_box([latitude, longitude], bb_radius, options)
|
137
|
+
args = b + [
|
138
|
+
full_column_name(latitude_attribute),
|
139
|
+
full_column_name(longitude_attribute)
|
140
|
+
]
|
141
|
+
bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
|
142
|
+
|
143
|
+
if using_unextended_sqlite?
|
144
|
+
conditions = bounding_box_conditions
|
145
|
+
else
|
146
|
+
min_radius = options.fetch(:min_radius, 0).to_f
|
147
|
+
# if radius is a DB column name,
|
148
|
+
# find rows between min_radius and value in column
|
149
|
+
if radius.is_a?(Symbol)
|
150
|
+
c = "BETWEEN ? AND #{radius}"
|
151
|
+
a = [min_radius]
|
152
|
+
else
|
153
|
+
c = "BETWEEN ? AND ?"
|
154
|
+
a = [min_radius, radius]
|
155
|
+
end
|
156
|
+
conditions = [bounding_box_conditions + " AND (#{distance}) " + c] + a
|
157
|
+
end
|
158
|
+
{
|
159
|
+
:select => select_clause(options[:select],
|
160
|
+
select_distance ? distance : nil,
|
161
|
+
select_bearing ? bearing : nil,
|
162
|
+
distance_column,
|
163
|
+
bearing_column),
|
164
|
+
:conditions => add_exclude_condition(conditions, options[:exclude]),
|
165
|
+
:order => options.include?(:order) ? options[:order] : "#{distance_column} ASC"
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# SQL for calculating distance based on the current database's
|
171
|
+
# capabilities (trig functions?).
|
172
|
+
#
|
173
|
+
def distance_sql(latitude, longitude, options = {})
|
174
|
+
method_prefix = using_unextended_sqlite? ? "approx" : "full"
|
175
|
+
Geocoder::Sql.send(
|
176
|
+
method_prefix + "_distance",
|
177
|
+
latitude, longitude,
|
178
|
+
full_column_name(options[:latitude] || geocoder_options[:latitude]),
|
179
|
+
full_column_name(options[:longitude]|| geocoder_options[:longitude]),
|
180
|
+
options
|
181
|
+
)
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# SQL for calculating bearing based on the current database's
|
186
|
+
# capabilities (trig functions?).
|
187
|
+
#
|
188
|
+
def bearing_sql(latitude, longitude, options = {})
|
189
|
+
if !options.include?(:bearing)
|
190
|
+
options[:bearing] = Geocoder.config.distances
|
191
|
+
end
|
192
|
+
if options[:bearing]
|
193
|
+
method_prefix = using_unextended_sqlite? ? "approx" : "full"
|
194
|
+
Geocoder::Sql.send(
|
195
|
+
method_prefix + "_bearing",
|
196
|
+
latitude, longitude,
|
197
|
+
full_column_name(options[:latitude] || geocoder_options[:latitude]),
|
198
|
+
full_column_name(options[:longitude]|| geocoder_options[:longitude]),
|
199
|
+
options
|
200
|
+
)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
# Generate the SELECT clause.
|
206
|
+
#
|
207
|
+
def select_clause(columns, distance = nil, bearing = nil, distance_column = 'distance', bearing_column = 'bearing')
|
208
|
+
if columns == :id_only
|
209
|
+
return full_column_name(primary_key)
|
210
|
+
elsif columns == :geo_only
|
211
|
+
clause = ""
|
212
|
+
else
|
213
|
+
clause = (columns || full_column_name("*"))
|
214
|
+
end
|
215
|
+
if distance
|
216
|
+
clause += ", " unless clause.empty?
|
217
|
+
clause += "#{distance} AS #{distance_column}"
|
218
|
+
end
|
219
|
+
if bearing
|
220
|
+
clause += ", " unless clause.empty?
|
221
|
+
clause += "#{bearing} AS #{bearing_column}"
|
222
|
+
end
|
223
|
+
clause
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# Adds a condition to exclude a given object by ID.
|
228
|
+
# Expects conditions as an array or string. Returns array.
|
229
|
+
#
|
230
|
+
def add_exclude_condition(conditions, exclude)
|
231
|
+
conditions = [conditions] if conditions.is_a?(String)
|
232
|
+
if exclude
|
233
|
+
conditions[0] << " AND #{full_column_name(primary_key)} != ?"
|
234
|
+
conditions << exclude.id
|
235
|
+
end
|
236
|
+
conditions
|
237
|
+
end
|
238
|
+
|
239
|
+
def using_unextended_sqlite?
|
240
|
+
using_sqlite? && !using_sqlite_with_extensions?
|
241
|
+
end
|
242
|
+
|
243
|
+
def using_sqlite?
|
244
|
+
!!connection.adapter_name.match(/sqlite/i)
|
245
|
+
end
|
246
|
+
|
247
|
+
def using_sqlite_with_extensions?
|
248
|
+
connection.adapter_name.match(/sqlite/i) &&
|
249
|
+
defined?(::SqliteExt) &&
|
250
|
+
%W(MOD POWER SQRT PI SIN COS ASIN ATAN2).all?{ |fn_name|
|
251
|
+
connection.raw_connection.function_created?(fn_name)
|
252
|
+
}
|
253
|
+
end
|
254
|
+
|
255
|
+
def using_postgres?
|
256
|
+
connection.adapter_name.match(/postgres/i)
|
257
|
+
end
|
258
|
+
|
259
|
+
##
|
260
|
+
# Use OID type when running in PosgreSQL
|
261
|
+
#
|
262
|
+
def null_value
|
263
|
+
using_postgres? ? 'NULL::text' : 'NULL'
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# Value which can be passed to where() to produce no results.
|
268
|
+
#
|
269
|
+
def false_condition
|
270
|
+
using_unextended_sqlite? ? 0 : "false"
|
271
|
+
end
|
272
|
+
|
273
|
+
##
|
274
|
+
# Prepend table name if column name doesn't already contain one.
|
275
|
+
#
|
276
|
+
def full_column_name(column)
|
277
|
+
column = column.to_s
|
278
|
+
column.include?(".") ? column : [table_name, column].join(".")
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
##
|
283
|
+
# Get nearby geocoded objects.
|
284
|
+
# Takes the same options hash as the near class method (scope).
|
285
|
+
# Returns nil if the object is not geocoded.
|
286
|
+
#
|
287
|
+
def nearbys(radius = 20, options = {})
|
288
|
+
return nil unless geocoded?
|
289
|
+
options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
|
290
|
+
self.class.near(self, radius, options)
|
291
|
+
end
|
292
|
+
|
293
|
+
##
|
294
|
+
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
|
295
|
+
# (or other as specified in +geocoded_by+). Returns coordinates (array).
|
296
|
+
#
|
297
|
+
def geocode
|
298
|
+
do_lookup(false) do |o,rs|
|
299
|
+
if r = rs.first
|
300
|
+
unless r.latitude.nil? or r.longitude.nil?
|
301
|
+
o.__send__ "#{self.class.geocoder_options[:latitude]}=", r.latitude
|
302
|
+
o.__send__ "#{self.class.geocoder_options[:longitude]}=", r.longitude
|
303
|
+
end
|
304
|
+
r.coordinates
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
alias_method :fetch_coordinates, :geocode
|
310
|
+
|
311
|
+
##
|
312
|
+
# Look up address and assign to +address+ attribute (or other as specified
|
313
|
+
# in +reverse_geocoded_by+). Returns address (string).
|
314
|
+
#
|
315
|
+
def reverse_geocode
|
316
|
+
do_lookup(true) do |o,rs|
|
317
|
+
if r = rs.first
|
318
|
+
unless r.address.nil?
|
319
|
+
o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
|
320
|
+
end
|
321
|
+
r.address
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
alias_method :fetch_address, :reverse_geocode
|
327
|
+
end
|
328
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Geocoder
|
2
|
+
module Store
|
3
|
+
module Base
|
4
|
+
|
5
|
+
##
|
6
|
+
# Is this object geocoded? (Does it have latitude and longitude?)
|
7
|
+
#
|
8
|
+
def geocoded?
|
9
|
+
to_coordinates.compact.size == 2
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Coordinates [lat,lon] of the object.
|
14
|
+
#
|
15
|
+
def to_coordinates
|
16
|
+
[:latitude, :longitude].map{ |i| send self.class.geocoder_options[i] }
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Calculate the distance from the object to an arbitrary point.
|
21
|
+
# See Geocoder::Calculations.distance_between for ways of specifying
|
22
|
+
# the point. Also takes a symbol specifying the units
|
23
|
+
# (:mi or :km; can be specified in Geocoder configuration).
|
24
|
+
#
|
25
|
+
def distance_to(point, units = nil)
|
26
|
+
units ||= self.class.geocoder_options[:units]
|
27
|
+
return nil unless geocoded?
|
28
|
+
Geocoder::Calculations.distance_between(
|
29
|
+
to_coordinates, point, :units => units)
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :distance_from, :distance_to
|
33
|
+
|
34
|
+
##
|
35
|
+
# Calculate the bearing from the object to another point.
|
36
|
+
# See Geocoder::Calculations.distance_between for
|
37
|
+
# ways of specifying the point.
|
38
|
+
#
|
39
|
+
def bearing_to(point, options = {})
|
40
|
+
options[:method] ||= self.class.geocoder_options[:method]
|
41
|
+
return nil unless geocoded?
|
42
|
+
Geocoder::Calculations.bearing_between(
|
43
|
+
to_coordinates, point, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Calculate the bearing from another point to the object.
|
48
|
+
# See Geocoder::Calculations.distance_between for
|
49
|
+
# ways of specifying the point.
|
50
|
+
#
|
51
|
+
def bearing_from(point, options = {})
|
52
|
+
options[:method] ||= self.class.geocoder_options[:method]
|
53
|
+
return nil unless geocoded?
|
54
|
+
Geocoder::Calculations.bearing_between(
|
55
|
+
point, to_coordinates, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
|
60
|
+
# (or other as specified in +geocoded_by+). Returns coordinates (array).
|
61
|
+
#
|
62
|
+
def geocode
|
63
|
+
fail
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Look up address and assign to +address+ attribute (or other as specified
|
68
|
+
# in +reverse_geocoded_by+). Returns address (string).
|
69
|
+
#
|
70
|
+
def reverse_geocode
|
71
|
+
fail
|
72
|
+
end
|
73
|
+
|
74
|
+
private # --------------------------------------------------------------
|
75
|
+
|
76
|
+
##
|
77
|
+
# Look up geographic data based on object attributes (configured in
|
78
|
+
# geocoded_by or reverse_geocoded_by) and handle the results with the
|
79
|
+
# block (given to geocoded_by or reverse_geocoded_by). The block is
|
80
|
+
# given two-arguments: the object being geocoded and an array of
|
81
|
+
# Geocoder::Result objects).
|
82
|
+
#
|
83
|
+
def do_lookup(reverse = false)
|
84
|
+
options = self.class.geocoder_options
|
85
|
+
if reverse and options[:reverse_geocode]
|
86
|
+
query = to_coordinates
|
87
|
+
elsif !reverse and options[:geocode]
|
88
|
+
query = send(options[:user_address])
|
89
|
+
else
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
query_options = [:lookup, :ip_lookup, :language, :params].inject({}) do |hash, key|
|
94
|
+
if options.has_key?(key)
|
95
|
+
val = options[key]
|
96
|
+
hash[key] = val.respond_to?(:call) ? val.call(self) : val
|
97
|
+
end
|
98
|
+
hash
|
99
|
+
end
|
100
|
+
results = Geocoder.search(query, query_options)
|
101
|
+
|
102
|
+
# execute custom block, if specified in configuration
|
103
|
+
block_key = reverse ? :reverse_block : :geocode_block
|
104
|
+
if custom_block = options[block_key]
|
105
|
+
custom_block.call(self, results)
|
106
|
+
|
107
|
+
# else execute block passed directly to this method,
|
108
|
+
# which generally performs the "auto-assigns"
|
109
|
+
elsif block_given?
|
110
|
+
yield(self, results)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Geocoder::Store
|
2
|
+
module MongoBase
|
3
|
+
|
4
|
+
def self.included_by_model(base)
|
5
|
+
base.class_eval do
|
6
|
+
|
7
|
+
scope :geocoded, lambda {
|
8
|
+
where(geocoder_options[:coordinates].ne => nil)
|
9
|
+
}
|
10
|
+
|
11
|
+
scope :not_geocoded, lambda {
|
12
|
+
where(geocoder_options[:coordinates] => nil)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Coordinates [lat,lon] of the object.
|
19
|
+
# This method always returns coordinates in lat,lon order,
|
20
|
+
# even though internally they are stored in the opposite order.
|
21
|
+
#
|
22
|
+
def to_coordinates
|
23
|
+
coords = send(self.class.geocoder_options[:coordinates])
|
24
|
+
coords.is_a?(Array) ? coords.reverse : []
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
|
29
|
+
# (or other as specified in +geocoded_by+). Returns coordinates (array).
|
30
|
+
#
|
31
|
+
def geocode
|
32
|
+
do_lookup(false) do |o,rs|
|
33
|
+
if r = rs.first
|
34
|
+
unless r.coordinates.nil?
|
35
|
+
o.__send__ "#{self.class.geocoder_options[:coordinates]}=", r.coordinates.reverse
|
36
|
+
end
|
37
|
+
r.coordinates
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Look up address and assign to +address+ attribute (or other as specified
|
44
|
+
# in +reverse_geocoded_by+). Returns address (string).
|
45
|
+
#
|
46
|
+
def reverse_geocode
|
47
|
+
do_lookup(true) do |o,rs|
|
48
|
+
if r = rs.first
|
49
|
+
unless r.address.nil?
|
50
|
+
o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
|
51
|
+
end
|
52
|
+
r.address
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|