geocoder-sgonyea 1.1.6.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.
Files changed (122) hide show
  1. data/.gitignore +5 -0
  2. data/.travis.yml +23 -0
  3. data/CHANGELOG.md +298 -0
  4. data/LICENSE +20 -0
  5. data/README.md +656 -0
  6. data/Rakefile +25 -0
  7. data/bin/geocode +5 -0
  8. data/examples/autoexpire_cache.rb +28 -0
  9. data/gemfiles/Gemfile.mongoid-2.4.x +15 -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/geocoder.rb +55 -0
  13. data/lib/geocoder/cache.rb +85 -0
  14. data/lib/geocoder/calculations.rb +319 -0
  15. data/lib/geocoder/cli.rb +114 -0
  16. data/lib/geocoder/configuration.rb +130 -0
  17. data/lib/geocoder/configuration_hash.rb +11 -0
  18. data/lib/geocoder/exceptions.rb +21 -0
  19. data/lib/geocoder/lookup.rb +82 -0
  20. data/lib/geocoder/lookups/base.rb +250 -0
  21. data/lib/geocoder/lookups/bing.rb +47 -0
  22. data/lib/geocoder/lookups/freegeoip.rb +47 -0
  23. data/lib/geocoder/lookups/geocoder_ca.rb +54 -0
  24. data/lib/geocoder/lookups/google.rb +62 -0
  25. data/lib/geocoder/lookups/google_premier.rb +47 -0
  26. data/lib/geocoder/lookups/mapquest.rb +43 -0
  27. data/lib/geocoder/lookups/maxmind.rb +88 -0
  28. data/lib/geocoder/lookups/nominatim.rb +45 -0
  29. data/lib/geocoder/lookups/ovi.rb +52 -0
  30. data/lib/geocoder/lookups/test.rb +38 -0
  31. data/lib/geocoder/lookups/yahoo.rb +84 -0
  32. data/lib/geocoder/lookups/yandex.rb +54 -0
  33. data/lib/geocoder/models/active_record.rb +46 -0
  34. data/lib/geocoder/models/base.rb +42 -0
  35. data/lib/geocoder/models/mongo_base.rb +60 -0
  36. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  37. data/lib/geocoder/models/mongoid.rb +32 -0
  38. data/lib/geocoder/query.rb +103 -0
  39. data/lib/geocoder/railtie.rb +26 -0
  40. data/lib/geocoder/request.rb +23 -0
  41. data/lib/geocoder/results/base.rb +67 -0
  42. data/lib/geocoder/results/bing.rb +48 -0
  43. data/lib/geocoder/results/freegeoip.rb +45 -0
  44. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  45. data/lib/geocoder/results/google.rb +106 -0
  46. data/lib/geocoder/results/google_premier.rb +6 -0
  47. data/lib/geocoder/results/mapquest.rb +51 -0
  48. data/lib/geocoder/results/maxmind.rb +136 -0
  49. data/lib/geocoder/results/nominatim.rb +94 -0
  50. data/lib/geocoder/results/ovi.rb +62 -0
  51. data/lib/geocoder/results/test.rb +16 -0
  52. data/lib/geocoder/results/yahoo.rb +55 -0
  53. data/lib/geocoder/results/yandex.rb +80 -0
  54. data/lib/geocoder/sql.rb +106 -0
  55. data/lib/geocoder/stores/active_record.rb +259 -0
  56. data/lib/geocoder/stores/base.rb +120 -0
  57. data/lib/geocoder/stores/mongo_base.rb +85 -0
  58. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  59. data/lib/geocoder/stores/mongoid.rb +13 -0
  60. data/lib/geocoder/version.rb +3 -0
  61. data/lib/hash_recursive_merge.rb +74 -0
  62. data/lib/oauth_util.rb +112 -0
  63. data/lib/tasks/geocoder.rake +25 -0
  64. data/test/active_record_test.rb +15 -0
  65. data/test/cache_test.rb +19 -0
  66. data/test/calculations_test.rb +195 -0
  67. data/test/configuration_test.rb +78 -0
  68. data/test/custom_block_test.rb +32 -0
  69. data/test/error_handling_test.rb +43 -0
  70. data/test/fixtures/bing_invalid_key +1 -0
  71. data/test/fixtures/bing_madison_square_garden +40 -0
  72. data/test/fixtures/bing_no_results +16 -0
  73. data/test/fixtures/bing_reverse +42 -0
  74. data/test/fixtures/freegeoip_74_200_247_59 +12 -0
  75. data/test/fixtures/freegeoip_no_results +1 -0
  76. data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
  77. data/test/fixtures/geocoder_ca_no_results +1 -0
  78. data/test/fixtures/geocoder_ca_reverse +34 -0
  79. data/test/fixtures/google_garbage +456 -0
  80. data/test/fixtures/google_madison_square_garden +57 -0
  81. data/test/fixtures/google_no_city_data +44 -0
  82. data/test/fixtures/google_no_locality +51 -0
  83. data/test/fixtures/google_no_results +4 -0
  84. data/test/fixtures/mapquest_madison_square_garden +52 -0
  85. data/test/fixtures/mapquest_no_results +7 -0
  86. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  87. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  88. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  89. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  90. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  91. data/test/fixtures/maxmind_invalid_key +1 -0
  92. data/test/fixtures/maxmind_no_results +1 -0
  93. data/test/fixtures/nominatim_madison_square_garden +150 -0
  94. data/test/fixtures/nominatim_no_results +1 -0
  95. data/test/fixtures/ovi_madison_square_garden +72 -0
  96. data/test/fixtures/ovi_no_results +8 -0
  97. data/test/fixtures/yahoo_error +1 -0
  98. data/test/fixtures/yahoo_invalid_key +2 -0
  99. data/test/fixtures/yahoo_madison_square_garden +52 -0
  100. data/test/fixtures/yahoo_no_results +10 -0
  101. data/test/fixtures/yahoo_over_limit +2 -0
  102. data/test/fixtures/yandex_invalid_key +1 -0
  103. data/test/fixtures/yandex_kremlin +48 -0
  104. data/test/fixtures/yandex_no_city_and_town +112 -0
  105. data/test/fixtures/yandex_no_results +16 -0
  106. data/test/geocoder_test.rb +59 -0
  107. data/test/https_test.rb +16 -0
  108. data/test/integration/smoke_test.rb +26 -0
  109. data/test/lookup_test.rb +116 -0
  110. data/test/method_aliases_test.rb +25 -0
  111. data/test/mongoid_test.rb +39 -0
  112. data/test/mongoid_test_helper.rb +43 -0
  113. data/test/near_test.rb +43 -0
  114. data/test/oauth_util_test.rb +30 -0
  115. data/test/proxy_test.rb +23 -0
  116. data/test/query_test.rb +51 -0
  117. data/test/request_test.rb +29 -0
  118. data/test/result_test.rb +42 -0
  119. data/test/services_test.rb +277 -0
  120. data/test/test_helper.rb +279 -0
  121. data/test/test_mode_test.rb +50 -0
  122. metadata +170 -0
@@ -0,0 +1,94 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class Nominatim < Base
5
+
6
+ def poi
7
+ %w[stadium bus_stop tram_stop].each do |key|
8
+ return @data['address'][key] if @data['address'].key?(key)
9
+ end
10
+ return nil
11
+ end
12
+
13
+ def house_number
14
+ @data['address']['house_number']
15
+ end
16
+
17
+ def address
18
+ @data['display_name']
19
+ end
20
+
21
+ def street
22
+ %w[road pedestrian highway].each do |key|
23
+ return @data['address'][key] if @data['address'].key?(key)
24
+ end
25
+ return nil
26
+ end
27
+
28
+ def city
29
+ %w[city town village hamlet].each do |key|
30
+ return @data['address'][key] if @data['address'].key?(key)
31
+ end
32
+ return nil
33
+ end
34
+
35
+ def village
36
+ @data['address']['village']
37
+ end
38
+
39
+ def town
40
+ @data['address']['town']
41
+ end
42
+
43
+ def state
44
+ @data['address']['state']
45
+ end
46
+
47
+ alias_method :state_code, :state
48
+
49
+ def postal_code
50
+ @data['address']['postcode']
51
+ end
52
+
53
+ def county
54
+ @data['address']['county']
55
+ end
56
+
57
+ def country
58
+ @data['address']['country']
59
+ end
60
+
61
+ def country_code
62
+ @data['address']['country_code']
63
+ end
64
+
65
+ def suburb
66
+ @data['address']['suburb']
67
+ end
68
+
69
+ def coordinates
70
+ [@data['lat'].to_f, @data['lon'].to_f]
71
+ end
72
+
73
+ def place_class
74
+ @data['class']
75
+ end
76
+
77
+ def place_type
78
+ @data['type']
79
+ end
80
+
81
+ def self.response_attributes
82
+ %w[place_id osm_type osm_id boundingbox license
83
+ polygonpoints display_name class type stadium]
84
+ end
85
+
86
+ response_attributes.each do |a|
87
+ unless method_defined?(a)
88
+ define_method a do
89
+ @data[a]
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,62 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class Ovi < Base
5
+
6
+ ##
7
+ # A string in the given format.
8
+ #
9
+ def address(format = :full)
10
+ address_data['Label']
11
+ end
12
+
13
+ ##
14
+ # A two-element array: [lat, lon].
15
+ #
16
+ def coordinates
17
+ fail unless d = @data['Location']['DisplayPosition']
18
+ [d['Latitude'].to_f, d['Longitude'].to_f]
19
+ end
20
+
21
+ def state
22
+ address_data['County']
23
+ end
24
+
25
+ def province
26
+ address_data['County']
27
+ end
28
+
29
+ def postal_code
30
+ address_data['PostalCode']
31
+ end
32
+
33
+ def city
34
+ address_data['City']
35
+ end
36
+
37
+ def state_code
38
+ address_data['State']
39
+ end
40
+
41
+ def province_code
42
+ address_data['State']
43
+ end
44
+
45
+ def country
46
+ fail unless d = address_data['AdditionalData']
47
+ if v = d.find{|ad| ad['key']=='CountryName'}
48
+ return v['value']
49
+ end
50
+ end
51
+
52
+ def country_code
53
+ address_data['Country']
54
+ end
55
+
56
+ private # ----------------------------------------------------------------
57
+
58
+ def address_data
59
+ @data['Location']['Address'] || fail
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,16 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder
4
+ module Result
5
+ class Test < Base
6
+
7
+ %w[latitude longitude city state state_code province
8
+ province_code postal_code country country_code address
9
+ street_address street_number route].each do |attr|
10
+ define_method(attr) do
11
+ @data[attr.to_s] || @data[attr.to_sym]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,55 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class Yahoo < Base
5
+
6
+ def address(format = :full)
7
+ (1..4).to_a.map{ |i| @data["line#{i}"] }.reject{ |i| i.nil? or i == "" }.join(", ")
8
+ end
9
+
10
+ def city
11
+ @data['city']
12
+ end
13
+
14
+ def state
15
+ @data['state']
16
+ end
17
+
18
+ def state_code
19
+ @data['statecode']
20
+ end
21
+
22
+ def country
23
+ @data['country']
24
+ end
25
+
26
+ def country_code
27
+ @data['countrycode']
28
+ end
29
+
30
+ def postal_code
31
+ @data['postal']
32
+ end
33
+
34
+ def address_hash
35
+ @data['hash']
36
+ end
37
+
38
+ def self.response_attributes
39
+ %w[quality offsetlat offsetlon radius boundingbox name
40
+ line1 line2 line3 line4 cross house street xstreet unittype unit
41
+ city state statecode country countrycode postal
42
+ neighborhood county countycode
43
+ level0 level1 level2 level3 level4 level0code level1code level2code
44
+ timezone areacode uzip hash woeid woetype]
45
+ end
46
+
47
+ response_attributes.each do |a|
48
+ unless method_defined?(a)
49
+ define_method a do
50
+ @data[a]
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,80 @@
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?
16
+ address_details['Locality']['LocalityName']
17
+ elsif sub_state.empty?
18
+ address_details['AdministrativeArea']['Locality']['LocalityName']
19
+ elsif not sub_state_city.empty?
20
+ sub_state_city
21
+ else
22
+ ""
23
+ end
24
+ end
25
+
26
+ def country
27
+ address_details['CountryName']
28
+ end
29
+
30
+ def country_code
31
+ address_details['CountryNameCode']
32
+ end
33
+
34
+ def state
35
+ if address_details['AdministrativeArea']
36
+ address_details['AdministrativeArea']['AdministrativeAreaName']
37
+ else
38
+ ""
39
+ end
40
+ end
41
+
42
+ def sub_state
43
+ if !state.empty? and address_details['AdministrativeArea']['SubAdministrativeArea']
44
+ address_details['AdministrativeArea']['SubAdministrativeArea']['SubAdministrativeAreaName']
45
+ else
46
+ ""
47
+ end
48
+ end
49
+
50
+ def state_code
51
+ ""
52
+ end
53
+
54
+ def postal_code
55
+ ""
56
+ end
57
+
58
+ def premise_name
59
+ address_details['Locality']['Premise']['PremiseName']
60
+ end
61
+
62
+ def precision
63
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['precision']
64
+ end
65
+
66
+ private # ----------------------------------------------------------------
67
+
68
+ def address_details
69
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['AddressDetails']['Country']
70
+ end
71
+
72
+ def sub_state_city
73
+ if sub_state && sub_state["Locality"]
74
+ sub_state['Locality']['LocalityName']
75
+ else
76
+ ""
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,106 @@
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
+ case options[:bearing] || Geocoder.config.distances
67
+ when :linear
68
+ "CAST(" +
69
+ "DEGREES(ATAN2( " +
70
+ "RADIANS(#{lon_attr} - #{longitude.to_f}), " +
71
+ "RADIANS(#{lat_attr} - #{latitude.to_f})" +
72
+ ")) + 360 " +
73
+ "AS decimal) % 360"
74
+ when :spherical
75
+ "CAST(" +
76
+ "DEGREES(ATAN2( " +
77
+ "SIN(RADIANS(#{lon_attr} - #{longitude.to_f})) * " +
78
+ "COS(RADIANS(#{lat_attr})), (" +
79
+ "COS(RADIANS(#{latitude.to_f})) * SIN(RADIANS(#{lat_attr}))" +
80
+ ") - (" +
81
+ "SIN(RADIANS(#{latitude.to_f})) * COS(RADIANS(#{lat_attr})) * " +
82
+ "COS(RADIANS(#{lon_attr} - #{longitude.to_f}))" +
83
+ ")" +
84
+ ")) + 360 " +
85
+ "AS decimal) % 360"
86
+ end
87
+ end
88
+
89
+ ##
90
+ # Totally lame bearing calculation. Basically useless except that it
91
+ # returns *something* in databases without trig functions.
92
+ #
93
+ def approx_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
94
+ "CASE " +
95
+ "WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
96
+ "#{lon_attr} >= #{longitude.to_f}) THEN 45.0 " +
97
+ "WHEN (#{lat_attr} < #{latitude.to_f} AND " +
98
+ "#{lon_attr} >= #{longitude.to_f}) THEN 135.0 " +
99
+ "WHEN (#{lat_attr} < #{latitude.to_f} AND " +
100
+ "#{lon_attr} < #{longitude.to_f}) THEN 225.0 " +
101
+ "WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
102
+ "#{lon_attr} < #{longitude.to_f}) THEN 315.0 " +
103
+ "END"
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,259 @@
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
+ {:conditions => "#{geocoder_options[:latitude]} IS NOT NULL " +
22
+ "AND #{geocoder_options[:longitude]} IS NOT NULL"}}
23
+
24
+ # scope: not-geocoded objects
25
+ scope :not_geocoded, lambda {
26
+ {:conditions => "#{geocoder_options[:latitude]} IS NULL " +
27
+ "OR #{geocoder_options[:longitude]} IS NULL"}}
28
+
29
+ ##
30
+ # Find all objects within a radius of the given location.
31
+ # Location may be either a string to geocode or an array of
32
+ # coordinates (<tt>[lat,lon]</tt>). Also takes an options hash
33
+ # (see Geocoder::Orm::ActiveRecord::ClassMethods.near_scope_options
34
+ # for details).
35
+ #
36
+ scope :near, lambda{ |location, *args|
37
+ latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
38
+ if Geocoder::Calculations.coordinates_present?(latitude, longitude)
39
+ near_scope_options(latitude, longitude, *args)
40
+ else
41
+ # If no lat/lon given we don't want any results, but we still
42
+ # need distance and bearing columns so you can add, for example:
43
+ # .order("distance")
44
+ select(select_clause(nil, "NULL", "NULL")).where(false_condition)
45
+ end
46
+ }
47
+
48
+ ##
49
+ # Find all objects within the area of a given bounding box.
50
+ # Bounds must be an array of locations specifying the southwest
51
+ # corner followed by the northeast corner of the box
52
+ # (<tt>[[sw_lat, sw_lon], [ne_lat, ne_lon]]</tt>).
53
+ #
54
+ scope :within_bounding_box, lambda{ |bounds|
55
+ sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
56
+ if sw_lat && sw_lng && ne_lat && ne_lng
57
+ {:conditions => Geocoder::Sql.within_bounding_box(
58
+ sw_lat, sw_lng, ne_lat, ne_lng,
59
+ full_column_name(geocoder_options[:latitude]),
60
+ full_column_name(geocoder_options[:longitude])
61
+ )}
62
+ else
63
+ select(select_clause(nil, "NULL", "NULL")).where(false_condition)
64
+ end
65
+ }
66
+ end
67
+ end
68
+
69
+ ##
70
+ # Methods which will be class methods of the including class.
71
+ #
72
+ module ClassMethods
73
+
74
+ def distance_from_sql(location, *args)
75
+ latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
76
+ if Geocoder::Calculations.coordinates_present?(latitude, longitude)
77
+ distance_sql(latitude, longitude, *args)
78
+ end
79
+ end
80
+
81
+ private # ----------------------------------------------------------------
82
+
83
+ ##
84
+ # Get options hash suitable for passing to ActiveRecord.find to get
85
+ # records within a radius (in kilometers) of the given point.
86
+ # Options hash may include:
87
+ #
88
+ # * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
89
+ # for interpreting radius as well as the +distance+ attribute which
90
+ # is added to each found nearby object.
91
+ # Use Geocoder.configure[:units] to configure default units.
92
+ # * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
93
+ # the method to be used for calculating the bearing (direction)
94
+ # between the given point and each found nearby point;
95
+ # set to false for no bearing calculation. Use
96
+ # Geocoder.configure[:distances] to configure default calculation method.
97
+ # * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
98
+ # * +:select_distance+ - whether to include the distance alias in the
99
+ # SELECT SQL fragment (e.g. <formula> AS distance)
100
+ # * +:select_bearing+ - like +:select_distance+ but for bearing.
101
+ # * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
102
+ # set to false or nil to omit the ORDER BY clause
103
+ # * +:exclude+ - an object to exclude (used by the +nearbys+ method)
104
+ #
105
+ def near_scope_options(latitude, longitude, radius = 20, options = {})
106
+ options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
107
+ select_distance = options.fetch(:select_distance, true)
108
+ options[:order] = "" if !select_distance && !options.include?(:order)
109
+ select_bearing = options.fetch(:select_bearing, true)
110
+ bearing = bearing_sql(latitude, longitude, options)
111
+ distance = distance_sql(latitude, longitude, options)
112
+
113
+ b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
114
+ args = b + [
115
+ full_column_name(geocoder_options[:latitude]),
116
+ full_column_name(geocoder_options[:longitude])
117
+ ]
118
+ bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
119
+
120
+ if using_sqlite?
121
+ conditions = bounding_box_conditions
122
+ else
123
+ conditions = [bounding_box_conditions + " AND #{distance} <= ?", radius]
124
+ end
125
+ {
126
+ :select => select_clause(options[:select],
127
+ select_distance ? distance : nil,
128
+ select_bearing ? bearing : nil),
129
+ :conditions => add_exclude_condition(conditions, options[:exclude]),
130
+ :order => options.include?(:order) ? options[:order] : "distance ASC"
131
+ }
132
+ end
133
+
134
+ ##
135
+ # SQL for calculating distance based on the current database's
136
+ # capabilities (trig functions?).
137
+ #
138
+ def distance_sql(latitude, longitude, options = {})
139
+ method_prefix = using_sqlite? ? "approx" : "full"
140
+ Geocoder::Sql.send(
141
+ method_prefix + "_distance",
142
+ latitude, longitude,
143
+ full_column_name(geocoder_options[:latitude]),
144
+ full_column_name(geocoder_options[:longitude]),
145
+ options
146
+ )
147
+ end
148
+
149
+ ##
150
+ # SQL for calculating bearing based on the current database's
151
+ # capabilities (trig functions?).
152
+ #
153
+ def bearing_sql(latitude, longitude, options = {})
154
+ if !options.include?(:bearing)
155
+ options[:bearing] = Geocoder.config.distances
156
+ end
157
+ if options[:bearing]
158
+ method_prefix = using_sqlite? ? "approx" : "full"
159
+ Geocoder::Sql.send(
160
+ method_prefix + "_bearing",
161
+ latitude, longitude,
162
+ full_column_name(geocoder_options[:latitude]),
163
+ full_column_name(geocoder_options[:longitude]),
164
+ options
165
+ )
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Generate the SELECT clause.
171
+ #
172
+ def select_clause(columns, distance = nil, bearing = nil)
173
+ if columns == :id_only
174
+ return full_column_name(primary_key)
175
+ elsif columns == :geo_only
176
+ clause = ""
177
+ else
178
+ clause = (columns || full_column_name("*"))
179
+ end
180
+ if distance
181
+ clause += ", " unless clause.empty?
182
+ clause += "#{distance} AS distance"
183
+ end
184
+ if bearing
185
+ clause += ", " unless clause.empty?
186
+ clause += "#{bearing} AS bearing"
187
+ end
188
+ clause
189
+ end
190
+
191
+ ##
192
+ # Adds a condition to exclude a given object by ID.
193
+ # Expects conditions as an array or string. Returns array.
194
+ #
195
+ def add_exclude_condition(conditions, exclude)
196
+ conditions = [conditions] if conditions.is_a?(String)
197
+ if exclude
198
+ conditions[0] << " AND #{full_column_name(primary_key)} != ?"
199
+ conditions << exclude.id
200
+ end
201
+ conditions
202
+ end
203
+
204
+ def using_sqlite?
205
+ connection.adapter_name.match /sqlite/i
206
+ end
207
+
208
+ ##
209
+ # Value which can be passed to where() to produce no results.
210
+ #
211
+ def false_condition
212
+ using_sqlite? ? 0 : "false"
213
+ end
214
+
215
+ ##
216
+ # Prepend table name if column name doesn't already contain one.
217
+ #
218
+ def full_column_name(column)
219
+ column = column.to_s
220
+ column.include?(".") ? column : [table_name, column].join(".")
221
+ end
222
+ end
223
+
224
+ ##
225
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
226
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
227
+ #
228
+ def geocode
229
+ do_lookup(false) do |o,rs|
230
+ if r = rs.first
231
+ unless r.latitude.nil? or r.longitude.nil?
232
+ o.__send__ "#{self.class.geocoder_options[:latitude]}=", r.latitude
233
+ o.__send__ "#{self.class.geocoder_options[:longitude]}=", r.longitude
234
+ end
235
+ r.coordinates
236
+ end
237
+ end
238
+ end
239
+
240
+ alias_method :fetch_coordinates, :geocode
241
+
242
+ ##
243
+ # Look up address and assign to +address+ attribute (or other as specified
244
+ # in +reverse_geocoded_by+). Returns address (string).
245
+ #
246
+ def reverse_geocode
247
+ do_lookup(true) do |o,rs|
248
+ if r = rs.first
249
+ unless r.address.nil?
250
+ o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
251
+ end
252
+ r.address
253
+ end
254
+ end
255
+ end
256
+
257
+ alias_method :fetch_address, :reverse_geocode
258
+ end
259
+ end