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.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +557 -0
  3. data/LICENSE +20 -0
  4. data/README.md +3 -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 +22 -0
  12. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +30 -0
  13. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +30 -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/generators/geocoder/migration_version.rb +15 -0
  17. data/lib/geocoder.rb +48 -0
  18. data/lib/geocoder/cache.rb +94 -0
  19. data/lib/geocoder/calculations.rb +420 -0
  20. data/lib/geocoder/cli.rb +121 -0
  21. data/lib/geocoder/configuration.rb +137 -0
  22. data/lib/geocoder/configuration_hash.rb +11 -0
  23. data/lib/geocoder/esri_token.rb +38 -0
  24. data/lib/geocoder/exceptions.rb +40 -0
  25. data/lib/geocoder/ip_address.rb +26 -0
  26. data/lib/geocoder/kernel_logger.rb +25 -0
  27. data/lib/geocoder/logger.rb +47 -0
  28. data/lib/geocoder/lookup.rb +118 -0
  29. data/lib/geocoder/lookups/amap.rb +63 -0
  30. data/lib/geocoder/lookups/baidu.rb +63 -0
  31. data/lib/geocoder/lookups/baidu_ip.rb +30 -0
  32. data/lib/geocoder/lookups/ban_data_gouv_fr.rb +130 -0
  33. data/lib/geocoder/lookups/base.rb +348 -0
  34. data/lib/geocoder/lookups/bing.rb +82 -0
  35. data/lib/geocoder/lookups/db_ip_com.rb +52 -0
  36. data/lib/geocoder/lookups/dstk.rb +22 -0
  37. data/lib/geocoder/lookups/esri.rb +95 -0
  38. data/lib/geocoder/lookups/freegeoip.rb +60 -0
  39. data/lib/geocoder/lookups/geocoder_ca.rb +53 -0
  40. data/lib/geocoder/lookups/geocoder_us.rb +51 -0
  41. data/lib/geocoder/lookups/geocodio.rb +42 -0
  42. data/lib/geocoder/lookups/geoip2.rb +45 -0
  43. data/lib/geocoder/lookups/geoportail_lu.rb +65 -0
  44. data/lib/geocoder/lookups/google.rb +95 -0
  45. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  46. data/lib/geocoder/lookups/google_places_search.rb +33 -0
  47. data/lib/geocoder/lookups/google_premier.rb +57 -0
  48. data/lib/geocoder/lookups/here.rb +77 -0
  49. data/lib/geocoder/lookups/ip2location.rb +75 -0
  50. data/lib/geocoder/lookups/ipapi_com.rb +82 -0
  51. data/lib/geocoder/lookups/ipdata_co.rb +62 -0
  52. data/lib/geocoder/lookups/ipinfo_io.rb +44 -0
  53. data/lib/geocoder/lookups/ipstack.rb +63 -0
  54. data/lib/geocoder/lookups/latlon.rb +59 -0
  55. data/lib/geocoder/lookups/location_iq.rb +50 -0
  56. data/lib/geocoder/lookups/mapbox.rb +59 -0
  57. data/lib/geocoder/lookups/mapquest.rb +58 -0
  58. data/lib/geocoder/lookups/maxmind.rb +90 -0
  59. data/lib/geocoder/lookups/maxmind_geoip2.rb +70 -0
  60. data/lib/geocoder/lookups/maxmind_local.rb +65 -0
  61. data/lib/geocoder/lookups/nominatim.rb +64 -0
  62. data/lib/geocoder/lookups/opencagedata.rb +65 -0
  63. data/lib/geocoder/lookups/pelias.rb +63 -0
  64. data/lib/geocoder/lookups/pickpoint.rb +41 -0
  65. data/lib/geocoder/lookups/pointpin.rb +69 -0
  66. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +50 -0
  67. data/lib/geocoder/lookups/postcodes_io.rb +31 -0
  68. data/lib/geocoder/lookups/smarty_streets.rb +63 -0
  69. data/lib/geocoder/lookups/telize.rb +75 -0
  70. data/lib/geocoder/lookups/tencent.rb +59 -0
  71. data/lib/geocoder/lookups/test.rb +44 -0
  72. data/lib/geocoder/lookups/yandex.rb +62 -0
  73. data/lib/geocoder/models/active_record.rb +51 -0
  74. data/lib/geocoder/models/base.rb +39 -0
  75. data/lib/geocoder/models/mongo_base.rb +62 -0
  76. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  77. data/lib/geocoder/models/mongoid.rb +32 -0
  78. data/lib/geocoder/query.rb +125 -0
  79. data/lib/geocoder/railtie.rb +26 -0
  80. data/lib/geocoder/request.rb +114 -0
  81. data/lib/geocoder/results/amap.rb +87 -0
  82. data/lib/geocoder/results/baidu.rb +79 -0
  83. data/lib/geocoder/results/baidu_ip.rb +62 -0
  84. data/lib/geocoder/results/ban_data_gouv_fr.rb +257 -0
  85. data/lib/geocoder/results/base.rb +79 -0
  86. data/lib/geocoder/results/bing.rb +52 -0
  87. data/lib/geocoder/results/db_ip_com.rb +58 -0
  88. data/lib/geocoder/results/dstk.rb +6 -0
  89. data/lib/geocoder/results/esri.rb +75 -0
  90. data/lib/geocoder/results/freegeoip.rb +40 -0
  91. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  92. data/lib/geocoder/results/geocoder_us.rb +39 -0
  93. data/lib/geocoder/results/geocodio.rb +78 -0
  94. data/lib/geocoder/results/geoip2.rb +76 -0
  95. data/lib/geocoder/results/geoportail_lu.rb +71 -0
  96. data/lib/geocoder/results/google.rb +150 -0
  97. data/lib/geocoder/results/google_places_details.rb +39 -0
  98. data/lib/geocoder/results/google_places_search.rb +52 -0
  99. data/lib/geocoder/results/google_premier.rb +6 -0
  100. data/lib/geocoder/results/here.rb +79 -0
  101. data/lib/geocoder/results/ip2location.rb +22 -0
  102. data/lib/geocoder/results/ipapi_com.rb +45 -0
  103. data/lib/geocoder/results/ipdata_co.rb +40 -0
  104. data/lib/geocoder/results/ipinfo_io.rb +48 -0
  105. data/lib/geocoder/results/ipstack.rb +60 -0
  106. data/lib/geocoder/results/latlon.rb +71 -0
  107. data/lib/geocoder/results/location_iq.rb +6 -0
  108. data/lib/geocoder/results/mapbox.rb +57 -0
  109. data/lib/geocoder/results/mapquest.rb +48 -0
  110. data/lib/geocoder/results/maxmind.rb +130 -0
  111. data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
  112. data/lib/geocoder/results/maxmind_local.rb +44 -0
  113. data/lib/geocoder/results/nominatim.rb +109 -0
  114. data/lib/geocoder/results/opencagedata.rb +100 -0
  115. data/lib/geocoder/results/pelias.rb +58 -0
  116. data/lib/geocoder/results/pickpoint.rb +6 -0
  117. data/lib/geocoder/results/pointpin.rb +40 -0
  118. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  119. data/lib/geocoder/results/postcodes_io.rb +40 -0
  120. data/lib/geocoder/results/smarty_streets.rb +142 -0
  121. data/lib/geocoder/results/telize.rb +40 -0
  122. data/lib/geocoder/results/tencent.rb +72 -0
  123. data/lib/geocoder/results/test.rb +33 -0
  124. data/lib/geocoder/results/yandex.rb +134 -0
  125. data/lib/geocoder/sql.rb +110 -0
  126. data/lib/geocoder/stores/active_record.rb +328 -0
  127. data/lib/geocoder/stores/base.rb +115 -0
  128. data/lib/geocoder/stores/mongo_base.rb +58 -0
  129. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  130. data/lib/geocoder/stores/mongoid.rb +13 -0
  131. data/lib/geocoder/version.rb +3 -0
  132. data/lib/hash_recursive_merge.rb +74 -0
  133. data/lib/maxmind_database.rb +109 -0
  134. data/lib/tasks/geocoder.rake +54 -0
  135. data/lib/tasks/maxmind.rake +73 -0
  136. metadata +186 -0
@@ -0,0 +1,142 @@
1
+ require 'geocoder/lookups/base'
2
+
3
+ module Geocoder::Result
4
+ class SmartyStreets < Base
5
+ def coordinates
6
+ result = %w(latitude longitude).map do |i|
7
+ zipcode_endpoint? ? zipcodes.first[i] : metadata[i]
8
+ end
9
+
10
+ if result.compact.empty?
11
+ nil
12
+ else
13
+ result
14
+ end
15
+ end
16
+
17
+ def address
18
+ parts =
19
+ if international_endpoint?
20
+ (1..12).map { |i| @data["address#{i}"] }
21
+ else
22
+ [
23
+ delivery_line_1,
24
+ delivery_line_2,
25
+ last_line
26
+ ]
27
+ end
28
+ parts.select{ |i| i.to_s != "" }.join(" ")
29
+ end
30
+
31
+ def state
32
+ if international_endpoint?
33
+ components['administrative_area']
34
+ elsif zipcode_endpoint?
35
+ city_states.first['state']
36
+ else
37
+ components['state_abbreviation']
38
+ end
39
+ end
40
+
41
+ def state_code
42
+ if international_endpoint?
43
+ components['administrative_area']
44
+ elsif zipcode_endpoint?
45
+ city_states.first['state_abbreviation']
46
+ else
47
+ components['state_abbreviation']
48
+ end
49
+ end
50
+
51
+ def country
52
+ international_endpoint? ?
53
+ components['country_iso_3'] :
54
+ "United States"
55
+ end
56
+
57
+ def country_code
58
+ international_endpoint? ?
59
+ components['country_iso_3'] :
60
+ "US"
61
+ end
62
+
63
+ ## Extra methods not in base.rb ------------------------
64
+
65
+ def street
66
+ international_endpoint? ?
67
+ components['thoroughfare_name'] :
68
+ components['street_name']
69
+ end
70
+
71
+ def city
72
+ if international_endpoint?
73
+ components['locality']
74
+ elsif zipcode_endpoint?
75
+ city_states.first['city']
76
+ else
77
+ components['city_name']
78
+ end
79
+ end
80
+
81
+ def zipcode
82
+ if international_endpoint?
83
+ components['postal_code']
84
+ elsif zipcode_endpoint?
85
+ zipcodes.first['zipcode']
86
+ else
87
+ components['zipcode']
88
+ end
89
+ end
90
+ alias_method :postal_code, :zipcode
91
+
92
+ def zip4
93
+ components['plus4_code']
94
+ end
95
+ alias_method :postal_code_extended, :zip4
96
+
97
+ def fips
98
+ zipcode_endpoint? ?
99
+ zipcodes.first['county_fips'] :
100
+ metadata['county_fips']
101
+ end
102
+
103
+ def zipcode_endpoint?
104
+ zipcodes.any?
105
+ end
106
+
107
+ def international_endpoint?
108
+ !@data['address1'].nil?
109
+ end
110
+
111
+ [
112
+ :delivery_line_1,
113
+ :delivery_line_2,
114
+ :last_line,
115
+ :delivery_point_barcode,
116
+ :addressee
117
+ ].each do |m|
118
+ define_method(m) do
119
+ @data[m.to_s] || ''
120
+ end
121
+ end
122
+
123
+ [
124
+ :components,
125
+ :metadata,
126
+ :analysis
127
+ ].each do |m|
128
+ define_method(m) do
129
+ @data[m.to_s] || {}
130
+ end
131
+ end
132
+
133
+ [
134
+ :city_states,
135
+ :zipcodes
136
+ ].each do |m|
137
+ define_method(m) do
138
+ @data[m.to_s] || []
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,40 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class Telize < Base
5
+
6
+ def city
7
+ @data['city']
8
+ end
9
+
10
+ def state
11
+ @data['region']
12
+ end
13
+
14
+ def state_code
15
+ @data['region_code']
16
+ end
17
+
18
+ def country
19
+ @data['country']
20
+ end
21
+
22
+ def country_code
23
+ @data['country_code']
24
+ end
25
+
26
+ def postal_code
27
+ @data['postal_code']
28
+ end
29
+
30
+ def self.response_attributes
31
+ %w[timezone isp dma_code area_code ip asn continent_code country_code3]
32
+ end
33
+
34
+ response_attributes.each do |a|
35
+ define_method a do
36
+ @data[a]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class Tencent < Base
5
+
6
+ def coordinates
7
+ ['lat', 'lng'].map{ |i| @data['location'][i] }
8
+ end
9
+
10
+ def address
11
+ "#{province}#{city}#{district}#{street}#{street_number}"
12
+
13
+ #@data['title'] or @data['address']
14
+ end
15
+
16
+ # NOTE: The Tencent reverse geocoding API has the field named
17
+ # 'address_component' compared to 'address_components' in the
18
+ # regular geocoding API.
19
+ def province
20
+ @data['address_components'] and (@data['address_components']['province']) or
21
+ (@data['address_component'] and @data['address_component']['province']) or
22
+ ""
23
+ end
24
+
25
+ alias_method :state, :province
26
+
27
+ def city
28
+ @data['address_components'] and (@data['address_components']['city']) or
29
+ (@data['address_component'] and @data['address_component']['city']) or
30
+ ""
31
+ end
32
+
33
+ def district
34
+ @data['address_components'] and (@data['address_components']['district']) or
35
+ (@data['address_component'] and @data['address_component']['district']) or
36
+ ""
37
+ end
38
+
39
+ def street
40
+ @data['address_components'] and (@data['address_components']['street']) or
41
+ (@data['address_component'] and @data['address_component']['street']) or
42
+ ""
43
+ end
44
+
45
+ def street_number
46
+ @data['address_components'] and (@data['address_components']['street_number']) or
47
+ (@data['address_component'] and @data['address_component']['street_number']) or
48
+ ""
49
+ end
50
+
51
+ def address_components
52
+ @data['address_components'] or @data['address_component']
53
+ end
54
+
55
+ def state_code
56
+ ""
57
+ end
58
+
59
+ def postal_code
60
+ ""
61
+ end
62
+
63
+ def country
64
+ "China"
65
+ end
66
+
67
+ def country_code
68
+ "CN"
69
+ end
70
+
71
+ end
72
+ 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[coordinates 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,134 @@
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
+ if address_details
29
+ address_details['CountryName']
30
+ else
31
+ ""
32
+ end
33
+ end
34
+
35
+ def country_code
36
+ if address_details
37
+ address_details['CountryNameCode']
38
+ else
39
+ ""
40
+ end
41
+ end
42
+
43
+ def state
44
+ if address_details and address_details['AdministrativeArea']
45
+ address_details['AdministrativeArea']['AdministrativeAreaName']
46
+ else
47
+ ""
48
+ end
49
+ end
50
+
51
+ def sub_state
52
+ if !state.empty? and address_details and address_details['AdministrativeArea']['SubAdministrativeArea']
53
+ address_details['AdministrativeArea']['SubAdministrativeArea']['SubAdministrativeAreaName']
54
+ else
55
+ ""
56
+ end
57
+ end
58
+
59
+ def state_code
60
+ ""
61
+ end
62
+
63
+ def postal_code
64
+ ""
65
+ end
66
+
67
+ def premise_name
68
+ address_details['Locality']['Premise']['PremiseName']
69
+ end
70
+
71
+ def street
72
+ thoroughfare_data && thoroughfare_data['ThoroughfareName']
73
+ end
74
+
75
+ def street_number
76
+ thoroughfare_data && thoroughfare_data['Premise'] && thoroughfare_data['Premise']['PremiseNumber']
77
+ end
78
+
79
+ def kind
80
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['kind']
81
+ end
82
+
83
+ def precision
84
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['precision']
85
+ end
86
+
87
+ def viewport
88
+ envelope = @data['GeoObject']['boundedBy']['Envelope'] || fail
89
+ east, north = envelope['upperCorner'].split(' ').map(&:to_f)
90
+ west, south = envelope['lowerCorner'].split(' ').map(&:to_f)
91
+ [south, west, north, east]
92
+ end
93
+
94
+ private # ----------------------------------------------------------------
95
+
96
+ def thoroughfare_data
97
+ locality_data && locality_data['Thoroughfare']
98
+ end
99
+
100
+ def locality_data
101
+ dependent_locality && subadmin_locality && admin_locality
102
+ end
103
+
104
+ def admin_locality
105
+ address_details && address_details['AdministrativeArea'] &&
106
+ address_details['AdministrativeArea']['Locality']
107
+ end
108
+
109
+ def subadmin_locality
110
+ address_details && address_details['AdministrativeArea'] &&
111
+ address_details['AdministrativeArea']['SubAdministrativeArea'] &&
112
+ address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']
113
+ end
114
+
115
+ def dependent_locality
116
+ address_details && address_details['AdministrativeArea'] &&
117
+ address_details['AdministrativeArea']['SubAdministrativeArea'] &&
118
+ address_details['AdministrativeArea']['SubAdministrativeArea']['Locality'] &&
119
+ address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']['DependentLocality']
120
+ end
121
+
122
+ def address_details
123
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['AddressDetails']['Country']
124
+ end
125
+
126
+ def sub_state_city
127
+ if !sub_state.empty? and address_details and address_details['AdministrativeArea']['SubAdministrativeArea'].has_key? 'Locality'
128
+ address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']['LocalityName'] || ""
129
+ else
130
+ ""
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,110 @@
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().
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
+ # For use with a database that supports MOD() and trigonometric functions
63
+ # SIN(), COS(), ASIN(), ATAN2().
64
+ #
65
+ # Based on:
66
+ # http://www.beginningspatial.com/calculating_bearing_one_point_another
67
+ #
68
+ def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
69
+ degrees_per_radian = Geocoder::Calculations::DEGREES_PER_RADIAN
70
+ case options[:bearing] || Geocoder.config.distances
71
+ when :linear
72
+ "MOD(CAST(" +
73
+ "(ATAN2( " +
74
+ "((#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian}), " +
75
+ "((#{lat_attr} - #{latitude.to_f}) / #{degrees_per_radian})" +
76
+ ") * #{degrees_per_radian}) + 360 " +
77
+ "AS decimal), 360)"
78
+ when :spherical
79
+ "MOD(CAST(" +
80
+ "(ATAN2( " +
81
+ "SIN( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian} ) * " +
82
+ "COS( (#{lat_attr}) / #{degrees_per_radian} ), (" +
83
+ "COS( (#{latitude.to_f}) / #{degrees_per_radian} ) * SIN( (#{lat_attr}) / #{degrees_per_radian})" +
84
+ ") - (" +
85
+ "SIN( (#{latitude.to_f}) / #{degrees_per_radian}) * COS((#{lat_attr}) / #{degrees_per_radian}) * " +
86
+ "COS( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian})" +
87
+ ")" +
88
+ ") * #{degrees_per_radian}) + 360 " +
89
+ "AS decimal), 360)"
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Totally lame bearing calculation. Basically useless except that it
95
+ # returns *something* in databases without trig functions.
96
+ #
97
+ def approx_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
98
+ "CASE " +
99
+ "WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
100
+ "#{lon_attr} >= #{longitude.to_f}) THEN 45.0 " +
101
+ "WHEN (#{lat_attr} < #{latitude.to_f} AND " +
102
+ "#{lon_attr} >= #{longitude.to_f}) THEN 135.0 " +
103
+ "WHEN (#{lat_attr} < #{latitude.to_f} AND " +
104
+ "#{lon_attr} < #{longitude.to_f}) THEN 225.0 " +
105
+ "WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
106
+ "#{lon_attr} < #{longitude.to_f}) THEN 315.0 " +
107
+ "END"
108
+ end
109
+ end
110
+ end