broken-geocoder 1.3.4
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 +467 -0
- data/LICENSE +20 -0
- data/README.md +1193 -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 +21 -0
- data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +28 -0
- data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +28 -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/geocoder.rb +48 -0
- data/lib/geocoder/cache.rb +90 -0
- data/lib/geocoder/calculations.rb +431 -0
- data/lib/geocoder/cli.rb +121 -0
- data/lib/geocoder/configuration.rb +129 -0
- data/lib/geocoder/configuration_hash.rb +11 -0
- data/lib/geocoder/esri_token.rb +38 -0
- data/lib/geocoder/exceptions.rb +37 -0
- data/lib/geocoder/ip_address.rb +13 -0
- data/lib/geocoder/kernel_logger.rb +25 -0
- data/lib/geocoder/logger.rb +47 -0
- data/lib/geocoder/lookup.rb +110 -0
- data/lib/geocoder/lookups/baidu.rb +59 -0
- data/lib/geocoder/lookups/baidu_ip.rb +59 -0
- data/lib/geocoder/lookups/base.rb +325 -0
- data/lib/geocoder/lookups/bing.rb +80 -0
- data/lib/geocoder/lookups/dstk.rb +20 -0
- data/lib/geocoder/lookups/esri.rb +64 -0
- data/lib/geocoder/lookups/freegeoip.rb +51 -0
- data/lib/geocoder/lookups/geocoder_ca.rb +53 -0
- data/lib/geocoder/lookups/geocoder_us.rb +43 -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 +91 -0
- data/lib/geocoder/lookups/google_places_details.rb +50 -0
- data/lib/geocoder/lookups/google_premier.rb +47 -0
- data/lib/geocoder/lookups/here.rb +62 -0
- data/lib/geocoder/lookups/ipapi_com.rb +86 -0
- data/lib/geocoder/lookups/ipinfo_io.rb +55 -0
- data/lib/geocoder/lookups/latlon.rb +59 -0
- data/lib/geocoder/lookups/mapbox.rb +53 -0
- data/lib/geocoder/lookups/mapquest.rb +59 -0
- data/lib/geocoder/lookups/mapzen.rb +15 -0
- data/lib/geocoder/lookups/maxmind.rb +90 -0
- data/lib/geocoder/lookups/maxmind_geoip2.rb +69 -0
- data/lib/geocoder/lookups/maxmind_local.rb +65 -0
- data/lib/geocoder/lookups/nominatim.rb +52 -0
- data/lib/geocoder/lookups/okf.rb +44 -0
- data/lib/geocoder/lookups/opencagedata.rb +58 -0
- data/lib/geocoder/lookups/ovi.rb +62 -0
- data/lib/geocoder/lookups/pelias.rb +64 -0
- data/lib/geocoder/lookups/pointpin.rb +68 -0
- data/lib/geocoder/lookups/postcode_anywhere_uk.rb +51 -0
- data/lib/geocoder/lookups/smarty_streets.rb +50 -0
- data/lib/geocoder/lookups/telize.rb +55 -0
- data/lib/geocoder/lookups/test.rb +44 -0
- data/lib/geocoder/lookups/yandex.rb +58 -0
- data/lib/geocoder/models/active_record.rb +50 -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 +111 -0
- data/lib/geocoder/railtie.rb +26 -0
- data/lib/geocoder/request.rb +83 -0
- data/lib/geocoder/results/baidu.rb +79 -0
- data/lib/geocoder/results/baidu_ip.rb +62 -0
- data/lib/geocoder/results/base.rb +67 -0
- data/lib/geocoder/results/bing.rb +52 -0
- data/lib/geocoder/results/dstk.rb +6 -0
- data/lib/geocoder/results/esri.rb +75 -0
- data/lib/geocoder/results/freegeoip.rb +45 -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 +70 -0
- data/lib/geocoder/results/geoip2.rb +62 -0
- data/lib/geocoder/results/geoportail_lu.rb +69 -0
- data/lib/geocoder/results/google.rb +139 -0
- data/lib/geocoder/results/google_places_details.rb +35 -0
- data/lib/geocoder/results/google_premier.rb +6 -0
- data/lib/geocoder/results/here.rb +71 -0
- data/lib/geocoder/results/ipapi_com.rb +45 -0
- data/lib/geocoder/results/ipinfo_io.rb +48 -0
- data/lib/geocoder/results/latlon.rb +71 -0
- data/lib/geocoder/results/mapbox.rb +47 -0
- data/lib/geocoder/results/mapquest.rb +48 -0
- data/lib/geocoder/results/mapzen.rb +5 -0
- data/lib/geocoder/results/maxmind.rb +135 -0
- data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
- data/lib/geocoder/results/maxmind_local.rb +49 -0
- data/lib/geocoder/results/nominatim.rb +99 -0
- data/lib/geocoder/results/okf.rb +106 -0
- data/lib/geocoder/results/opencagedata.rb +90 -0
- data/lib/geocoder/results/ovi.rb +71 -0
- data/lib/geocoder/results/pelias.rb +58 -0
- data/lib/geocoder/results/pointpin.rb +40 -0
- data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
- data/lib/geocoder/results/smarty_streets.rb +106 -0
- data/lib/geocoder/results/telize.rb +45 -0
- data/lib/geocoder/results/test.rb +33 -0
- data/lib/geocoder/results/yandex.rb +92 -0
- data/lib/geocoder/sql.rb +107 -0
- data/lib/geocoder/stores/active_record.rb +305 -0
- data/lib/geocoder/stores/base.rb +116 -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 +38 -0
- data/lib/tasks/maxmind.rake +73 -0
- metadata +167 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'geocoder/results/base'
|
|
2
|
+
|
|
3
|
+
module Geocoder::Result
|
|
4
|
+
class Telize < Base
|
|
5
|
+
|
|
6
|
+
def address(format = :full)
|
|
7
|
+
s = state_code.to_s == "" ? "" : ", #{state_code}"
|
|
8
|
+
"#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, "")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def city
|
|
12
|
+
@data['city']
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def state
|
|
16
|
+
@data['region']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def state_code
|
|
20
|
+
@data['region_code']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def country
|
|
24
|
+
@data['country']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def country_code
|
|
28
|
+
@data['country_code']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def postal_code
|
|
32
|
+
@data['postal_code']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.response_attributes
|
|
36
|
+
%w[timezone isp dma_code area_code ip asn continent_code country_code3]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
response_attributes.each do |a|
|
|
40
|
+
define_method a do
|
|
41
|
+
@data[a]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'geocoder/results/base'
|
|
2
|
+
|
|
3
|
+
module Geocoder
|
|
4
|
+
module Result
|
|
5
|
+
class Test < Base
|
|
6
|
+
|
|
7
|
+
def self.add_result_attribute(attr)
|
|
8
|
+
begin
|
|
9
|
+
remove_method(attr) if method_defined?(attr)
|
|
10
|
+
rescue NameError # method defined on superclass
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
define_method(attr) do
|
|
14
|
+
@data[attr.to_s] || @data[attr.to_sym]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
%w[latitude longitude neighborhood city state state_code sub_state
|
|
19
|
+
sub_state_code province province_code postal_code country
|
|
20
|
+
country_code address street_address street_number route geometry].each do |attr|
|
|
21
|
+
add_result_attribute(attr)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(data)
|
|
25
|
+
data.each_key do |attr|
|
|
26
|
+
Test.add_result_attribute(attr)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'geocoder/results/base'
|
|
2
|
+
|
|
3
|
+
module Geocoder::Result
|
|
4
|
+
class Yandex < Base
|
|
5
|
+
|
|
6
|
+
def coordinates
|
|
7
|
+
@data['GeoObject']['Point']['pos'].split(' ').reverse.map(&:to_f)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def address(format = :full)
|
|
11
|
+
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['text']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def city
|
|
15
|
+
if state.empty? and address_details and address_details.has_key? 'Locality'
|
|
16
|
+
address_details['Locality']['LocalityName']
|
|
17
|
+
elsif sub_state.empty? and address_details and address_details.has_key? 'AdministrativeArea' and
|
|
18
|
+
address_details['AdministrativeArea'].has_key? 'Locality'
|
|
19
|
+
address_details['AdministrativeArea']['Locality']['LocalityName']
|
|
20
|
+
elsif not sub_state_city.empty?
|
|
21
|
+
sub_state_city
|
|
22
|
+
else
|
|
23
|
+
""
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def country
|
|
28
|
+
address_details['CountryName']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def country_code
|
|
32
|
+
address_details['CountryNameCode']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def state
|
|
36
|
+
if address_details and address_details['AdministrativeArea']
|
|
37
|
+
address_details['AdministrativeArea']['AdministrativeAreaName']
|
|
38
|
+
else
|
|
39
|
+
""
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def sub_state
|
|
44
|
+
if !state.empty? and address_details and address_details['AdministrativeArea']['SubAdministrativeArea']
|
|
45
|
+
address_details['AdministrativeArea']['SubAdministrativeArea']['SubAdministrativeAreaName']
|
|
46
|
+
else
|
|
47
|
+
""
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def state_code
|
|
52
|
+
""
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def postal_code
|
|
56
|
+
""
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def premise_name
|
|
60
|
+
address_details['Locality']['Premise']['PremiseName']
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def kind
|
|
64
|
+
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['kind']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def precision
|
|
68
|
+
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['precision']
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def viewport
|
|
72
|
+
envelope = @data['GeoObject']['boundedBy']['Envelope'] || fail
|
|
73
|
+
east, north = envelope['upperCorner'].split(' ').map(&:to_f)
|
|
74
|
+
west, south = envelope['lowerCorner'].split(' ').map(&:to_f)
|
|
75
|
+
[south, west, north, east]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private # ----------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def address_details
|
|
81
|
+
@data['GeoObject']['metaDataProperty']['GeocoderMetaData']['AddressDetails']['Country']
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def sub_state_city
|
|
85
|
+
if !sub_state.empty? and address_details and address_details['AdministrativeArea']['SubAdministrativeArea'].has_key? 'Locality'
|
|
86
|
+
address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']['LocalityName'] || ""
|
|
87
|
+
else
|
|
88
|
+
""
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/geocoder/sql.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Geocoder
|
|
2
|
+
module Sql
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Distance calculation for use with a database that supports POWER(),
|
|
7
|
+
# SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
|
|
8
|
+
# ATAN2(), DEGREES(), and RADIANS().
|
|
9
|
+
#
|
|
10
|
+
# Based on the excellent tutorial at:
|
|
11
|
+
# http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
|
|
12
|
+
#
|
|
13
|
+
def full_distance(latitude, longitude, lat_attr, lon_attr, options = {})
|
|
14
|
+
units = options[:units] || Geocoder.config.units
|
|
15
|
+
earth = Geocoder::Calculations.earth_radius(units)
|
|
16
|
+
|
|
17
|
+
"#{earth} * 2 * ASIN(SQRT(" +
|
|
18
|
+
"POWER(SIN((#{latitude.to_f} - #{lat_attr}) * PI() / 180 / 2), 2) + " +
|
|
19
|
+
"COS(#{latitude.to_f} * PI() / 180) * COS(#{lat_attr} * PI() / 180) * " +
|
|
20
|
+
"POWER(SIN((#{longitude.to_f} - #{lon_attr}) * PI() / 180 / 2), 2)" +
|
|
21
|
+
"))"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# Distance calculation for use with a database without trigonometric
|
|
26
|
+
# functions, like SQLite. Approach is to find objects within a square
|
|
27
|
+
# rather than a circle, so results are very approximate (will include
|
|
28
|
+
# objects outside the given radius).
|
|
29
|
+
#
|
|
30
|
+
# Distance and bearing calculations are *extremely inaccurate*. To be
|
|
31
|
+
# clear: this only exists to provide interface consistency. Results
|
|
32
|
+
# are not intended for use in production!
|
|
33
|
+
#
|
|
34
|
+
def approx_distance(latitude, longitude, lat_attr, lon_attr, options = {})
|
|
35
|
+
units = options[:units] || Geocoder.config.units
|
|
36
|
+
dx = Geocoder::Calculations.longitude_degree_distance(30, units)
|
|
37
|
+
dy = Geocoder::Calculations.latitude_degree_distance(units)
|
|
38
|
+
|
|
39
|
+
# sin of 45 degrees = average x or y component of vector
|
|
40
|
+
factor = Math.sin(Math::PI / 4)
|
|
41
|
+
|
|
42
|
+
"(#{dy} * ABS(#{lat_attr} - #{latitude.to_f}) * #{factor}) + " +
|
|
43
|
+
"(#{dx} * ABS(#{lon_attr} - #{longitude.to_f}) * #{factor})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def within_bounding_box(sw_lat, sw_lng, ne_lat, ne_lng, lat_attr, lon_attr)
|
|
47
|
+
spans = "#{lat_attr} BETWEEN #{sw_lat} AND #{ne_lat} AND "
|
|
48
|
+
# handle box that spans 180 longitude
|
|
49
|
+
if sw_lng.to_f > ne_lng.to_f
|
|
50
|
+
spans + "#{lon_attr} BETWEEN #{sw_lng} AND 180 OR " +
|
|
51
|
+
"#{lon_attr} BETWEEN -180 AND #{ne_lng}"
|
|
52
|
+
else
|
|
53
|
+
spans + "#{lon_attr} BETWEEN #{sw_lng} AND #{ne_lng}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Fairly accurate bearing calculation. Takes a latitude, longitude,
|
|
59
|
+
# and an options hash which must include a :bearing value
|
|
60
|
+
# (:linear or :spherical).
|
|
61
|
+
#
|
|
62
|
+
# Based on:
|
|
63
|
+
# http://www.beginningspatial.com/calculating_bearing_one_point_another
|
|
64
|
+
#
|
|
65
|
+
def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
|
|
66
|
+
degrees_per_radian = Geocoder::Calculations::DEGREES_PER_RADIAN
|
|
67
|
+
case options[:bearing] || Geocoder.config.distances
|
|
68
|
+
when :linear
|
|
69
|
+
"MOD(CAST(" +
|
|
70
|
+
"(ATAN2( " +
|
|
71
|
+
"((#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian}), " +
|
|
72
|
+
"((#{lat_attr} - #{latitude.to_f}) / #{degrees_per_radian})" +
|
|
73
|
+
") * #{degrees_per_radian}) + 360 " +
|
|
74
|
+
"AS decimal), 360)"
|
|
75
|
+
when :spherical
|
|
76
|
+
"MOD(CAST(" +
|
|
77
|
+
"(ATAN2( " +
|
|
78
|
+
"SIN( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian} ) * " +
|
|
79
|
+
"COS( (#{lat_attr}) / #{degrees_per_radian} ), (" +
|
|
80
|
+
"COS( (#{latitude.to_f}) / #{degrees_per_radian} ) * SIN( (#{lat_attr}) / #{degrees_per_radian})" +
|
|
81
|
+
") - (" +
|
|
82
|
+
"SIN( (#{latitude.to_f}) / #{degrees_per_radian}) * COS((#{lat_attr}) / #{degrees_per_radian}) * " +
|
|
83
|
+
"COS( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian})" +
|
|
84
|
+
")" +
|
|
85
|
+
") * #{degrees_per_radian}) + 360 " +
|
|
86
|
+
"AS decimal), 360)"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Totally lame bearing calculation. Basically useless except that it
|
|
92
|
+
# returns *something* in databases without trig functions.
|
|
93
|
+
#
|
|
94
|
+
def approx_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
|
|
95
|
+
"CASE " +
|
|
96
|
+
"WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
|
|
97
|
+
"#{lon_attr} >= #{longitude.to_f}) THEN 45.0 " +
|
|
98
|
+
"WHEN (#{lat_attr} < #{latitude.to_f} AND " +
|
|
99
|
+
"#{lon_attr} >= #{longitude.to_f}) THEN 135.0 " +
|
|
100
|
+
"WHEN (#{lat_attr} < #{latitude.to_f} AND " +
|
|
101
|
+
"#{lon_attr} < #{longitude.to_f}) THEN 225.0 " +
|
|
102
|
+
"WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
|
|
103
|
+
"#{lon_attr} < #{longitude.to_f}) THEN 315.0 " +
|
|
104
|
+
"END"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,305 @@
|
|
|
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
|
+
private # ----------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Get options hash suitable for passing to ActiveRecord.find to get
|
|
94
|
+
# records within a radius (in kilometers) of the given point.
|
|
95
|
+
# Options hash may include:
|
|
96
|
+
#
|
|
97
|
+
# * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
|
|
98
|
+
# for interpreting radius as well as the +distance+ attribute which
|
|
99
|
+
# is added to each found nearby object.
|
|
100
|
+
# Use Geocoder.configure[:units] to configure default units.
|
|
101
|
+
# * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
|
|
102
|
+
# the method to be used for calculating the bearing (direction)
|
|
103
|
+
# between the given point and each found nearby point;
|
|
104
|
+
# set to false for no bearing calculation. Use
|
|
105
|
+
# Geocoder.configure[:distances] to configure default calculation method.
|
|
106
|
+
# * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
|
|
107
|
+
# * +:select_distance+ - whether to include the distance alias in the
|
|
108
|
+
# SELECT SQL fragment (e.g. <formula> AS distance)
|
|
109
|
+
# * +:select_bearing+ - like +:select_distance+ but for bearing.
|
|
110
|
+
# * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
|
|
111
|
+
# set to false or nil to omit the ORDER BY clause
|
|
112
|
+
# * +:exclude+ - an object to exclude (used by the +nearbys+ method)
|
|
113
|
+
# * +:distance_column+ - used to set the column name of the calculated distance.
|
|
114
|
+
# * +:bearing_column+ - used to set the column name of the calculated bearing.
|
|
115
|
+
# * +:min_radius+ - the value to use as the minimum radius.
|
|
116
|
+
# ignored if database is sqlite.
|
|
117
|
+
# default is 0.0
|
|
118
|
+
#
|
|
119
|
+
def near_scope_options(latitude, longitude, radius = 20, options = {})
|
|
120
|
+
if options[:units]
|
|
121
|
+
options[:units] = options[:units].to_sym
|
|
122
|
+
end
|
|
123
|
+
latitude_attribute = options[:latitude] || geocoder_options[:latitude]
|
|
124
|
+
longitude_attribute = options[:longitude] || geocoder_options[:longitude]
|
|
125
|
+
options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
|
|
126
|
+
select_distance = options.fetch(:select_distance) { true }
|
|
127
|
+
options[:order] = "" if !select_distance && !options.include?(:order)
|
|
128
|
+
select_bearing = options.fetch(:select_bearing) { true }
|
|
129
|
+
bearing = bearing_sql(latitude, longitude, options)
|
|
130
|
+
distance = distance_sql(latitude, longitude, options)
|
|
131
|
+
distance_column = options.fetch(:distance_column) { 'distance' }
|
|
132
|
+
bearing_column = options.fetch(:bearing_column) { 'bearing' }
|
|
133
|
+
|
|
134
|
+
b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
|
|
135
|
+
args = b + [
|
|
136
|
+
full_column_name(latitude_attribute),
|
|
137
|
+
full_column_name(longitude_attribute)
|
|
138
|
+
]
|
|
139
|
+
bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
|
|
140
|
+
|
|
141
|
+
if using_sqlite?
|
|
142
|
+
conditions = bounding_box_conditions
|
|
143
|
+
else
|
|
144
|
+
min_radius = options.fetch(:min_radius, 0).to_f
|
|
145
|
+
conditions = [bounding_box_conditions + " AND (#{distance}) BETWEEN ? AND ?", min_radius, radius]
|
|
146
|
+
end
|
|
147
|
+
{
|
|
148
|
+
:select => select_clause(options[:select],
|
|
149
|
+
select_distance ? distance : nil,
|
|
150
|
+
select_bearing ? bearing : nil,
|
|
151
|
+
distance_column,
|
|
152
|
+
bearing_column),
|
|
153
|
+
:conditions => add_exclude_condition(conditions, options[:exclude]),
|
|
154
|
+
:order => options.include?(:order) ? options[:order] : "#{distance_column} ASC"
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
##
|
|
159
|
+
# SQL for calculating distance based on the current database's
|
|
160
|
+
# capabilities (trig functions?).
|
|
161
|
+
#
|
|
162
|
+
def distance_sql(latitude, longitude, options = {})
|
|
163
|
+
method_prefix = using_sqlite? ? "approx" : "full"
|
|
164
|
+
Geocoder::Sql.send(
|
|
165
|
+
method_prefix + "_distance",
|
|
166
|
+
latitude, longitude,
|
|
167
|
+
full_column_name(options[:latitude] || geocoder_options[:latitude]),
|
|
168
|
+
full_column_name(options[:longitude]|| geocoder_options[:longitude]),
|
|
169
|
+
options
|
|
170
|
+
)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
##
|
|
174
|
+
# SQL for calculating bearing based on the current database's
|
|
175
|
+
# capabilities (trig functions?).
|
|
176
|
+
#
|
|
177
|
+
def bearing_sql(latitude, longitude, options = {})
|
|
178
|
+
if !options.include?(:bearing)
|
|
179
|
+
options[:bearing] = Geocoder.config.distances
|
|
180
|
+
end
|
|
181
|
+
if options[:bearing]
|
|
182
|
+
method_prefix = using_sqlite? ? "approx" : "full"
|
|
183
|
+
Geocoder::Sql.send(
|
|
184
|
+
method_prefix + "_bearing",
|
|
185
|
+
latitude, longitude,
|
|
186
|
+
full_column_name(options[:latitude] || geocoder_options[:latitude]),
|
|
187
|
+
full_column_name(options[:longitude]|| geocoder_options[:longitude]),
|
|
188
|
+
options
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
# Generate the SELECT clause.
|
|
195
|
+
#
|
|
196
|
+
def select_clause(columns, distance = nil, bearing = nil, distance_column = 'distance', bearing_column = 'bearing')
|
|
197
|
+
if columns == :id_only
|
|
198
|
+
return full_column_name(primary_key)
|
|
199
|
+
elsif columns == :geo_only
|
|
200
|
+
clause = ""
|
|
201
|
+
else
|
|
202
|
+
clause = (columns || full_column_name("*"))
|
|
203
|
+
end
|
|
204
|
+
if distance
|
|
205
|
+
clause += ", " unless clause.empty?
|
|
206
|
+
clause += "#{distance} AS #{distance_column}"
|
|
207
|
+
end
|
|
208
|
+
if bearing
|
|
209
|
+
clause += ", " unless clause.empty?
|
|
210
|
+
clause += "#{bearing} AS #{bearing_column}"
|
|
211
|
+
end
|
|
212
|
+
clause
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
##
|
|
216
|
+
# Adds a condition to exclude a given object by ID.
|
|
217
|
+
# Expects conditions as an array or string. Returns array.
|
|
218
|
+
#
|
|
219
|
+
def add_exclude_condition(conditions, exclude)
|
|
220
|
+
conditions = [conditions] if conditions.is_a?(String)
|
|
221
|
+
if exclude
|
|
222
|
+
conditions[0] << " AND #{full_column_name(primary_key)} != ?"
|
|
223
|
+
conditions << exclude.id
|
|
224
|
+
end
|
|
225
|
+
conditions
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def using_sqlite?
|
|
229
|
+
connection.adapter_name.match(/sqlite/i)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def using_postgres?
|
|
233
|
+
connection.adapter_name.match(/postgres/i)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
##
|
|
237
|
+
# Use OID type when running in PosgreSQL
|
|
238
|
+
#
|
|
239
|
+
def null_value
|
|
240
|
+
using_postgres? ? 'NULL::text' : 'NULL'
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
##
|
|
244
|
+
# Value which can be passed to where() to produce no results.
|
|
245
|
+
#
|
|
246
|
+
def false_condition
|
|
247
|
+
using_sqlite? ? 0 : "false"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
##
|
|
251
|
+
# Prepend table name if column name doesn't already contain one.
|
|
252
|
+
#
|
|
253
|
+
def full_column_name(column)
|
|
254
|
+
column = column.to_s
|
|
255
|
+
column.include?(".") ? column : [table_name, column].join(".")
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
##
|
|
260
|
+
# Get nearby geocoded objects.
|
|
261
|
+
# Takes the same options hash as the near class method (scope).
|
|
262
|
+
# Returns nil if the object is not geocoded.
|
|
263
|
+
#
|
|
264
|
+
def nearbys(radius = 20, options = {})
|
|
265
|
+
return nil unless geocoded?
|
|
266
|
+
options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
|
|
267
|
+
self.class.near(self, radius, options)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
##
|
|
271
|
+
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
|
|
272
|
+
# (or other as specified in +geocoded_by+). Returns coordinates (array).
|
|
273
|
+
#
|
|
274
|
+
def geocode
|
|
275
|
+
do_lookup(false) do |o,rs|
|
|
276
|
+
if r = rs.first
|
|
277
|
+
unless r.latitude.nil? or r.longitude.nil?
|
|
278
|
+
o.__send__ "#{self.class.geocoder_options[:latitude]}=", r.latitude
|
|
279
|
+
o.__send__ "#{self.class.geocoder_options[:longitude]}=", r.longitude
|
|
280
|
+
end
|
|
281
|
+
r.coordinates
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
alias_method :fetch_coordinates, :geocode
|
|
287
|
+
|
|
288
|
+
##
|
|
289
|
+
# Look up address and assign to +address+ attribute (or other as specified
|
|
290
|
+
# in +reverse_geocoded_by+). Returns address (string).
|
|
291
|
+
#
|
|
292
|
+
def reverse_geocode
|
|
293
|
+
do_lookup(true) do |o,rs|
|
|
294
|
+
if r = rs.first
|
|
295
|
+
unless r.address.nil?
|
|
296
|
+
o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
|
|
297
|
+
end
|
|
298
|
+
r.address
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
alias_method :fetch_address, :reverse_geocode
|
|
304
|
+
end
|
|
305
|
+
end
|