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.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +467 -0
  3. data/LICENSE +20 -0
  4. data/README.md +1193 -0
  5. data/bin/geocode +5 -0
  6. data/examples/autoexpire_cache_dalli.rb +62 -0
  7. data/examples/autoexpire_cache_redis.rb +28 -0
  8. data/examples/cache_bypass.rb +48 -0
  9. data/examples/reverse_geocode_job.rb +40 -0
  10. data/lib/generators/geocoder/config/config_generator.rb +14 -0
  11. data/lib/generators/geocoder/config/templates/initializer.rb +21 -0
  12. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +28 -0
  13. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +28 -0
  14. data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
  15. data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
  16. data/lib/geocoder.rb +48 -0
  17. data/lib/geocoder/cache.rb +90 -0
  18. data/lib/geocoder/calculations.rb +431 -0
  19. data/lib/geocoder/cli.rb +121 -0
  20. data/lib/geocoder/configuration.rb +129 -0
  21. data/lib/geocoder/configuration_hash.rb +11 -0
  22. data/lib/geocoder/esri_token.rb +38 -0
  23. data/lib/geocoder/exceptions.rb +37 -0
  24. data/lib/geocoder/ip_address.rb +13 -0
  25. data/lib/geocoder/kernel_logger.rb +25 -0
  26. data/lib/geocoder/logger.rb +47 -0
  27. data/lib/geocoder/lookup.rb +110 -0
  28. data/lib/geocoder/lookups/baidu.rb +59 -0
  29. data/lib/geocoder/lookups/baidu_ip.rb +59 -0
  30. data/lib/geocoder/lookups/base.rb +325 -0
  31. data/lib/geocoder/lookups/bing.rb +80 -0
  32. data/lib/geocoder/lookups/dstk.rb +20 -0
  33. data/lib/geocoder/lookups/esri.rb +64 -0
  34. data/lib/geocoder/lookups/freegeoip.rb +51 -0
  35. data/lib/geocoder/lookups/geocoder_ca.rb +53 -0
  36. data/lib/geocoder/lookups/geocoder_us.rb +43 -0
  37. data/lib/geocoder/lookups/geocodio.rb +42 -0
  38. data/lib/geocoder/lookups/geoip2.rb +45 -0
  39. data/lib/geocoder/lookups/geoportail_lu.rb +65 -0
  40. data/lib/geocoder/lookups/google.rb +91 -0
  41. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  42. data/lib/geocoder/lookups/google_premier.rb +47 -0
  43. data/lib/geocoder/lookups/here.rb +62 -0
  44. data/lib/geocoder/lookups/ipapi_com.rb +86 -0
  45. data/lib/geocoder/lookups/ipinfo_io.rb +55 -0
  46. data/lib/geocoder/lookups/latlon.rb +59 -0
  47. data/lib/geocoder/lookups/mapbox.rb +53 -0
  48. data/lib/geocoder/lookups/mapquest.rb +59 -0
  49. data/lib/geocoder/lookups/mapzen.rb +15 -0
  50. data/lib/geocoder/lookups/maxmind.rb +90 -0
  51. data/lib/geocoder/lookups/maxmind_geoip2.rb +69 -0
  52. data/lib/geocoder/lookups/maxmind_local.rb +65 -0
  53. data/lib/geocoder/lookups/nominatim.rb +52 -0
  54. data/lib/geocoder/lookups/okf.rb +44 -0
  55. data/lib/geocoder/lookups/opencagedata.rb +58 -0
  56. data/lib/geocoder/lookups/ovi.rb +62 -0
  57. data/lib/geocoder/lookups/pelias.rb +64 -0
  58. data/lib/geocoder/lookups/pointpin.rb +68 -0
  59. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +51 -0
  60. data/lib/geocoder/lookups/smarty_streets.rb +50 -0
  61. data/lib/geocoder/lookups/telize.rb +55 -0
  62. data/lib/geocoder/lookups/test.rb +44 -0
  63. data/lib/geocoder/lookups/yandex.rb +58 -0
  64. data/lib/geocoder/models/active_record.rb +50 -0
  65. data/lib/geocoder/models/base.rb +39 -0
  66. data/lib/geocoder/models/mongo_base.rb +62 -0
  67. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  68. data/lib/geocoder/models/mongoid.rb +32 -0
  69. data/lib/geocoder/query.rb +111 -0
  70. data/lib/geocoder/railtie.rb +26 -0
  71. data/lib/geocoder/request.rb +83 -0
  72. data/lib/geocoder/results/baidu.rb +79 -0
  73. data/lib/geocoder/results/baidu_ip.rb +62 -0
  74. data/lib/geocoder/results/base.rb +67 -0
  75. data/lib/geocoder/results/bing.rb +52 -0
  76. data/lib/geocoder/results/dstk.rb +6 -0
  77. data/lib/geocoder/results/esri.rb +75 -0
  78. data/lib/geocoder/results/freegeoip.rb +45 -0
  79. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  80. data/lib/geocoder/results/geocoder_us.rb +39 -0
  81. data/lib/geocoder/results/geocodio.rb +70 -0
  82. data/lib/geocoder/results/geoip2.rb +62 -0
  83. data/lib/geocoder/results/geoportail_lu.rb +69 -0
  84. data/lib/geocoder/results/google.rb +139 -0
  85. data/lib/geocoder/results/google_places_details.rb +35 -0
  86. data/lib/geocoder/results/google_premier.rb +6 -0
  87. data/lib/geocoder/results/here.rb +71 -0
  88. data/lib/geocoder/results/ipapi_com.rb +45 -0
  89. data/lib/geocoder/results/ipinfo_io.rb +48 -0
  90. data/lib/geocoder/results/latlon.rb +71 -0
  91. data/lib/geocoder/results/mapbox.rb +47 -0
  92. data/lib/geocoder/results/mapquest.rb +48 -0
  93. data/lib/geocoder/results/mapzen.rb +5 -0
  94. data/lib/geocoder/results/maxmind.rb +135 -0
  95. data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
  96. data/lib/geocoder/results/maxmind_local.rb +49 -0
  97. data/lib/geocoder/results/nominatim.rb +99 -0
  98. data/lib/geocoder/results/okf.rb +106 -0
  99. data/lib/geocoder/results/opencagedata.rb +90 -0
  100. data/lib/geocoder/results/ovi.rb +71 -0
  101. data/lib/geocoder/results/pelias.rb +58 -0
  102. data/lib/geocoder/results/pointpin.rb +40 -0
  103. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  104. data/lib/geocoder/results/smarty_streets.rb +106 -0
  105. data/lib/geocoder/results/telize.rb +45 -0
  106. data/lib/geocoder/results/test.rb +33 -0
  107. data/lib/geocoder/results/yandex.rb +92 -0
  108. data/lib/geocoder/sql.rb +107 -0
  109. data/lib/geocoder/stores/active_record.rb +305 -0
  110. data/lib/geocoder/stores/base.rb +116 -0
  111. data/lib/geocoder/stores/mongo_base.rb +58 -0
  112. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  113. data/lib/geocoder/stores/mongoid.rb +13 -0
  114. data/lib/geocoder/version.rb +3 -0
  115. data/lib/hash_recursive_merge.rb +74 -0
  116. data/lib/maxmind_database.rb +109 -0
  117. data/lib/tasks/geocoder.rake +38 -0
  118. data/lib/tasks/maxmind.rake +73 -0
  119. 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
@@ -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