geocoder-kb 1.2.6

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 (248) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +31 -0
  4. data/CHANGELOG.md +384 -0
  5. data/LICENSE +20 -0
  6. data/README.md +1085 -0
  7. data/Rakefile +25 -0
  8. data/bin/geocode +5 -0
  9. data/examples/autoexpire_cache_dalli.rb +62 -0
  10. data/examples/autoexpire_cache_redis.rb +28 -0
  11. data/examples/cache_bypass.rb +48 -0
  12. data/examples/sidekiq_worker.rb +16 -0
  13. data/gemfiles/Gemfile.mongoid-2.4.x +16 -0
  14. data/lib/generators/geocoder/config/config_generator.rb +14 -0
  15. data/lib/generators/geocoder/config/templates/initializer.rb +21 -0
  16. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +28 -0
  17. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +28 -0
  18. data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
  19. data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
  20. data/lib/geocoder.rb +47 -0
  21. data/lib/geocoder/cache.rb +90 -0
  22. data/lib/geocoder/calculations.rb +428 -0
  23. data/lib/geocoder/cli.rb +121 -0
  24. data/lib/geocoder/configuration.rb +124 -0
  25. data/lib/geocoder/configuration_hash.rb +11 -0
  26. data/lib/geocoder/exceptions.rb +21 -0
  27. data/lib/geocoder/ip_address.rb +21 -0
  28. data/lib/geocoder/lookup.rb +102 -0
  29. data/lib/geocoder/lookups/amap.rb +55 -0
  30. data/lib/geocoder/lookups/baidu.rb +55 -0
  31. data/lib/geocoder/lookups/baidu_ip.rb +54 -0
  32. data/lib/geocoder/lookups/base.rb +302 -0
  33. data/lib/geocoder/lookups/bing.rb +59 -0
  34. data/lib/geocoder/lookups/dstk.rb +20 -0
  35. data/lib/geocoder/lookups/esri.rb +48 -0
  36. data/lib/geocoder/lookups/freegeoip.rb +47 -0
  37. data/lib/geocoder/lookups/geocoder_ca.rb +54 -0
  38. data/lib/geocoder/lookups/geocoder_us.rb +39 -0
  39. data/lib/geocoder/lookups/geocodio.rb +42 -0
  40. data/lib/geocoder/lookups/geoip2.rb +40 -0
  41. data/lib/geocoder/lookups/google.rb +67 -0
  42. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  43. data/lib/geocoder/lookups/google_premier.rb +47 -0
  44. data/lib/geocoder/lookups/here.rb +62 -0
  45. data/lib/geocoder/lookups/mapquest.rb +60 -0
  46. data/lib/geocoder/lookups/maxmind.rb +90 -0
  47. data/lib/geocoder/lookups/maxmind_local.rb +58 -0
  48. data/lib/geocoder/lookups/nominatim.rb +52 -0
  49. data/lib/geocoder/lookups/okf.rb +43 -0
  50. data/lib/geocoder/lookups/opencagedata.rb +58 -0
  51. data/lib/geocoder/lookups/ovi.rb +62 -0
  52. data/lib/geocoder/lookups/pointpin.rb +68 -0
  53. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +51 -0
  54. data/lib/geocoder/lookups/smarty_streets.rb +45 -0
  55. data/lib/geocoder/lookups/telize.rb +40 -0
  56. data/lib/geocoder/lookups/test.rb +44 -0
  57. data/lib/geocoder/lookups/yahoo.rb +88 -0
  58. data/lib/geocoder/lookups/yandex.rb +54 -0
  59. data/lib/geocoder/models/active_record.rb +50 -0
  60. data/lib/geocoder/models/base.rb +39 -0
  61. data/lib/geocoder/models/mongo_base.rb +64 -0
  62. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  63. data/lib/geocoder/models/mongoid.rb +32 -0
  64. data/lib/geocoder/query.rb +111 -0
  65. data/lib/geocoder/railtie.rb +26 -0
  66. data/lib/geocoder/request.rb +25 -0
  67. data/lib/geocoder/results/amap.rb +85 -0
  68. data/lib/geocoder/results/baidu.rb +79 -0
  69. data/lib/geocoder/results/baidu_ip.rb +62 -0
  70. data/lib/geocoder/results/base.rb +67 -0
  71. data/lib/geocoder/results/bing.rb +48 -0
  72. data/lib/geocoder/results/dstk.rb +6 -0
  73. data/lib/geocoder/results/esri.rb +51 -0
  74. data/lib/geocoder/results/freegeoip.rb +45 -0
  75. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  76. data/lib/geocoder/results/geocoder_us.rb +39 -0
  77. data/lib/geocoder/results/geocodio.rb +66 -0
  78. data/lib/geocoder/results/geoip2.rb +64 -0
  79. data/lib/geocoder/results/google.rb +124 -0
  80. data/lib/geocoder/results/google_places_details.rb +35 -0
  81. data/lib/geocoder/results/google_premier.rb +6 -0
  82. data/lib/geocoder/results/here.rb +62 -0
  83. data/lib/geocoder/results/mapquest.rb +51 -0
  84. data/lib/geocoder/results/maxmind.rb +135 -0
  85. data/lib/geocoder/results/maxmind_local.rb +49 -0
  86. data/lib/geocoder/results/nominatim.rb +94 -0
  87. data/lib/geocoder/results/okf.rb +106 -0
  88. data/lib/geocoder/results/opencagedata.rb +82 -0
  89. data/lib/geocoder/results/ovi.rb +62 -0
  90. data/lib/geocoder/results/pointpin.rb +44 -0
  91. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  92. data/lib/geocoder/results/smarty_streets.rb +106 -0
  93. data/lib/geocoder/results/telize.rb +45 -0
  94. data/lib/geocoder/results/test.rb +33 -0
  95. data/lib/geocoder/results/yahoo.rb +55 -0
  96. data/lib/geocoder/results/yandex.rb +84 -0
  97. data/lib/geocoder/sql.rb +107 -0
  98. data/lib/geocoder/stores/active_record.rb +289 -0
  99. data/lib/geocoder/stores/base.rb +127 -0
  100. data/lib/geocoder/stores/mongo_base.rb +89 -0
  101. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  102. data/lib/geocoder/stores/mongoid.rb +13 -0
  103. data/lib/geocoder/version.rb +3 -0
  104. data/lib/hash_recursive_merge.rb +74 -0
  105. data/lib/maxmind_database.rb +109 -0
  106. data/lib/oauth_util.rb +112 -0
  107. data/lib/tasks/geocoder.rake +29 -0
  108. data/lib/tasks/maxmind.rake +73 -0
  109. data/test/fixtures/baidu_invalid_key +1 -0
  110. data/test/fixtures/baidu_ip_202_198_16_3 +19 -0
  111. data/test/fixtures/baidu_ip_invalid_key +1 -0
  112. data/test/fixtures/baidu_ip_no_results +1 -0
  113. data/test/fixtures/baidu_no_results +1 -0
  114. data/test/fixtures/baidu_reverse +1 -0
  115. data/test/fixtures/baidu_shanghai_pearl_tower +12 -0
  116. data/test/fixtures/bing_invalid_key +1 -0
  117. data/test/fixtures/bing_madison_square_garden +40 -0
  118. data/test/fixtures/bing_no_results +16 -0
  119. data/test/fixtures/bing_reverse +42 -0
  120. data/test/fixtures/cloudmade_invalid_key +1 -0
  121. data/test/fixtures/cloudmade_madison_square_garden +1 -0
  122. data/test/fixtures/cloudmade_no_results +1 -0
  123. data/test/fixtures/esri_madison_square_garden +59 -0
  124. data/test/fixtures/esri_no_results +8 -0
  125. data/test/fixtures/esri_reverse +21 -0
  126. data/test/fixtures/freegeoip_74_200_247_59 +12 -0
  127. data/test/fixtures/freegeoip_no_results +1 -0
  128. data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
  129. data/test/fixtures/geocoder_ca_no_results +1 -0
  130. data/test/fixtures/geocoder_ca_reverse +34 -0
  131. data/test/fixtures/geocoder_us_madison_square_garden +1 -0
  132. data/test/fixtures/geocoder_us_no_results +1 -0
  133. data/test/fixtures/geocodio_1101_pennsylvania_ave +1 -0
  134. data/test/fixtures/geocodio_bad_api_key +3 -0
  135. data/test/fixtures/geocodio_invalid +4 -0
  136. data/test/fixtures/geocodio_no_results +1 -0
  137. data/test/fixtures/geocodio_over_query_limit +4 -0
  138. data/test/fixtures/google_garbage +456 -0
  139. data/test/fixtures/google_madison_square_garden +57 -0
  140. data/test/fixtures/google_no_city_data +44 -0
  141. data/test/fixtures/google_no_locality +51 -0
  142. data/test/fixtures/google_no_results +4 -0
  143. data/test/fixtures/google_over_limit +4 -0
  144. data/test/fixtures/google_places_details_invalid_request +4 -0
  145. data/test/fixtures/google_places_details_madison_square_garden +120 -0
  146. data/test/fixtures/google_places_details_no_results +4 -0
  147. data/test/fixtures/google_places_details_no_reviews +60 -0
  148. data/test/fixtures/google_places_details_no_types +66 -0
  149. data/test/fixtures/here_madison_square_garden +72 -0
  150. data/test/fixtures/here_no_results +8 -0
  151. data/test/fixtures/mapquest_error +16 -0
  152. data/test/fixtures/mapquest_invalid_api_key +16 -0
  153. data/test/fixtures/mapquest_invalid_request +16 -0
  154. data/test/fixtures/mapquest_madison_square_garden +52 -0
  155. data/test/fixtures/mapquest_no_results +16 -0
  156. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  157. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  158. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  159. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  160. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  161. data/test/fixtures/maxmind_invalid_key +1 -0
  162. data/test/fixtures/maxmind_no_results +1 -0
  163. data/test/fixtures/nominatim_madison_square_garden +150 -0
  164. data/test/fixtures/nominatim_no_results +1 -0
  165. data/test/fixtures/nominatim_over_limit +1 -0
  166. data/test/fixtures/okf_kirstinmaki +67 -0
  167. data/test/fixtures/okf_no_results +4 -0
  168. data/test/fixtures/opencagedata_invalid_api_key +25 -0
  169. data/test/fixtures/opencagedata_invalid_request +26 -0
  170. data/test/fixtures/opencagedata_madison_square_garden +73 -0
  171. data/test/fixtures/opencagedata_no_results +29 -0
  172. data/test/fixtures/opencagedata_over_limit +31 -0
  173. data/test/fixtures/ovi_madison_square_garden +72 -0
  174. data/test/fixtures/ovi_no_results +8 -0
  175. data/test/fixtures/pointpin_10_10_10_10 +1 -0
  176. data/test/fixtures/pointpin_555_555_555_555 +1 -0
  177. data/test/fixtures/pointpin_80_111_555_555 +1 -0
  178. data/test/fixtures/pointpin_no_results +1 -0
  179. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_WR26NJ +1 -0
  180. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_generic_error +1 -0
  181. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_hampshire +1 -0
  182. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_key_limit_exceeded +1 -0
  183. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_no_results +1 -0
  184. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_romsey +1 -0
  185. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_unknown_key +1 -0
  186. data/test/fixtures/smarty_streets_11211 +1 -0
  187. data/test/fixtures/smarty_streets_madison_square_garden +47 -0
  188. data/test/fixtures/smarty_streets_no_results +1 -0
  189. data/test/fixtures/telize_10_10_10_10 +1 -0
  190. data/test/fixtures/telize_555_555_555_555 +4 -0
  191. data/test/fixtures/telize_74_200_247_59 +1 -0
  192. data/test/fixtures/telize_no_results +1 -0
  193. data/test/fixtures/yahoo_error +1 -0
  194. data/test/fixtures/yahoo_invalid_key +2 -0
  195. data/test/fixtures/yahoo_madison_square_garden +52 -0
  196. data/test/fixtures/yahoo_no_results +10 -0
  197. data/test/fixtures/yahoo_over_limit +2 -0
  198. data/test/fixtures/yandex_canada_rue_dupuis_14 +446 -0
  199. data/test/fixtures/yandex_invalid_key +1 -0
  200. data/test/fixtures/yandex_kremlin +48 -0
  201. data/test/fixtures/yandex_new_york +1 -0
  202. data/test/fixtures/yandex_no_city_and_town +112 -0
  203. data/test/fixtures/yandex_no_results +16 -0
  204. data/test/integration/http_client_test.rb +31 -0
  205. data/test/mongoid_test_helper.rb +43 -0
  206. data/test/test_helper.rb +416 -0
  207. data/test/unit/active_record_test.rb +16 -0
  208. data/test/unit/cache_test.rb +37 -0
  209. data/test/unit/calculations_test.rb +220 -0
  210. data/test/unit/configuration_test.rb +55 -0
  211. data/test/unit/error_handling_test.rb +56 -0
  212. data/test/unit/geocoder_test.rb +78 -0
  213. data/test/unit/https_test.rb +17 -0
  214. data/test/unit/ip_address_test.rb +27 -0
  215. data/test/unit/lookup_test.rb +153 -0
  216. data/test/unit/lookups/bing_test.rb +68 -0
  217. data/test/unit/lookups/dstk_test.rb +26 -0
  218. data/test/unit/lookups/esri_test.rb +48 -0
  219. data/test/unit/lookups/freegeoip_test.rb +27 -0
  220. data/test/unit/lookups/geocoder_ca_test.rb +17 -0
  221. data/test/unit/lookups/geocodio_test.rb +55 -0
  222. data/test/unit/lookups/geoip2_test.rb +27 -0
  223. data/test/unit/lookups/google_places_details_test.rb +122 -0
  224. data/test/unit/lookups/google_premier_test.rb +22 -0
  225. data/test/unit/lookups/google_test.rb +84 -0
  226. data/test/unit/lookups/mapquest_test.rb +60 -0
  227. data/test/unit/lookups/maxmind_local_test.rb +28 -0
  228. data/test/unit/lookups/maxmind_test.rb +63 -0
  229. data/test/unit/lookups/nominatim_test.rb +31 -0
  230. data/test/unit/lookups/okf_test.rb +38 -0
  231. data/test/unit/lookups/opencagedata_test.rb +64 -0
  232. data/test/unit/lookups/pointpin_test.rb +30 -0
  233. data/test/unit/lookups/postcode_anywhere_uk_test.rb +70 -0
  234. data/test/unit/lookups/smarty_streets_test.rb +71 -0
  235. data/test/unit/lookups/telize_test.rb +36 -0
  236. data/test/unit/lookups/yahoo_test.rb +35 -0
  237. data/test/unit/method_aliases_test.rb +26 -0
  238. data/test/unit/model_test.rb +38 -0
  239. data/test/unit/mongoid_test.rb +47 -0
  240. data/test/unit/near_test.rb +87 -0
  241. data/test/unit/oauth_util_test.rb +31 -0
  242. data/test/unit/proxy_test.rb +37 -0
  243. data/test/unit/query_test.rb +52 -0
  244. data/test/unit/rake_task_test.rb +21 -0
  245. data/test/unit/request_test.rb +35 -0
  246. data/test/unit/result_test.rb +72 -0
  247. data/test/unit/test_mode_test.rb +70 -0
  248. metadata +294 -0
@@ -0,0 +1,84 @@
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.has_key? 'Locality'
16
+ address_details['Locality']['LocalityName']
17
+ elsif sub_state.empty? and address_details['AdministrativeArea'].has_key? 'Locality'
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 kind
63
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['kind']
64
+ end
65
+
66
+ def precision
67
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['precision']
68
+ end
69
+
70
+ private # ----------------------------------------------------------------
71
+
72
+ def address_details
73
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['AddressDetails']['Country']
74
+ end
75
+
76
+ def sub_state_city
77
+ if !sub_state.empty? and address_details['AdministrativeArea']['SubAdministrativeArea'].has_key? 'Locality'
78
+ address_details['AdministrativeArea']['SubAdministrativeArea']['Locality']['LocalityName'] || ""
79
+ else
80
+ ""
81
+ end
82
+ end
83
+ end
84
+ 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,289 @@
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
+ ##
32
+ # Find all objects within a radius of the given location.
33
+ # Location may be either a string to geocode or an array of
34
+ # coordinates (<tt>[lat,lon]</tt>). Also takes an options hash
35
+ # (see Geocoder::Store::ActiveRecord::ClassMethods.near_scope_options
36
+ # for details).
37
+ #
38
+ scope :near, lambda{ |location, *args|
39
+ latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
40
+ if Geocoder::Calculations.coordinates_present?(latitude, longitude)
41
+ options = near_scope_options(latitude, longitude, *args)
42
+ select(options[:select]).where(options[:conditions]).
43
+ order(options[:order])
44
+ else
45
+ # If no lat/lon given we don't want any results, but we still
46
+ # need distance and bearing columns so you can add, for example:
47
+ # .order("distance")
48
+ select(select_clause(nil, null_value, null_value)).where(false_condition)
49
+ end
50
+ }
51
+
52
+ ##
53
+ # Find all objects within the area of a given bounding box.
54
+ # Bounds must be an array of locations specifying the southwest
55
+ # corner followed by the northeast corner of the box
56
+ # (<tt>[[sw_lat, sw_lon], [ne_lat, ne_lon]]</tt>).
57
+ #
58
+ scope :within_bounding_box, lambda{ |bounds|
59
+ sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
60
+ if sw_lat && sw_lng && ne_lat && ne_lng
61
+ where(Geocoder::Sql.within_bounding_box(
62
+ sw_lat, sw_lng, ne_lat, ne_lng,
63
+ full_column_name(geocoder_options[:latitude]),
64
+ full_column_name(geocoder_options[:longitude])
65
+ ))
66
+ else
67
+ select(select_clause(nil, null_value, null_value)).where(false_condition)
68
+ end
69
+ }
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Methods which will be class methods of the including class.
75
+ #
76
+ module ClassMethods
77
+
78
+ def distance_from_sql(location, *args)
79
+ latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
80
+ if Geocoder::Calculations.coordinates_present?(latitude, longitude)
81
+ distance_sql(latitude, longitude, *args)
82
+ end
83
+ end
84
+
85
+ private # ----------------------------------------------------------------
86
+
87
+ ##
88
+ # Get options hash suitable for passing to ActiveRecord.find to get
89
+ # records within a radius (in kilometers) of the given point.
90
+ # Options hash may include:
91
+ #
92
+ # * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
93
+ # for interpreting radius as well as the +distance+ attribute which
94
+ # is added to each found nearby object.
95
+ # Use Geocoder.configure[:units] to configure default units.
96
+ # * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
97
+ # the method to be used for calculating the bearing (direction)
98
+ # between the given point and each found nearby point;
99
+ # set to false for no bearing calculation. Use
100
+ # Geocoder.configure[:distances] to configure default calculation method.
101
+ # * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
102
+ # * +:select_distance+ - whether to include the distance alias in the
103
+ # SELECT SQL fragment (e.g. <formula> AS distance)
104
+ # * +:select_bearing+ - like +:select_distance+ but for bearing.
105
+ # * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
106
+ # set to false or nil to omit the ORDER BY clause
107
+ # * +:exclude+ - an object to exclude (used by the +nearbys+ method)
108
+ # * +:distance_column+ - used to set the column name of the calculated distance.
109
+ # * +:bearing_column+ - used to set the column name of the calculated bearing.
110
+ # * +:min_radius+ - the value to use as the minimum radius.
111
+ # ignored if database is sqlite.
112
+ # default is 0.0
113
+ #
114
+ def near_scope_options(latitude, longitude, radius = 20, options = {})
115
+ if options[:units]
116
+ options[:units] = options[:units].to_sym
117
+ end
118
+ latitude_attribute = options[:latitude] || geocoder_options[:latitude]
119
+ longitude_attribute = options[:longitude] || geocoder_options[:longitude]
120
+ options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
121
+ select_distance = options.fetch(:select_distance) { true }
122
+ options[:order] = "" if !select_distance && !options.include?(:order)
123
+ select_bearing = options.fetch(:select_bearing) { true }
124
+ bearing = bearing_sql(latitude, longitude, options)
125
+ distance = distance_sql(latitude, longitude, options)
126
+ distance_column = options.fetch(:distance_column) { 'distance' }
127
+ bearing_column = options.fetch(:bearing_column) { 'bearing' }
128
+
129
+ b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
130
+ args = b + [
131
+ full_column_name(latitude_attribute),
132
+ full_column_name(longitude_attribute)
133
+ ]
134
+ bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
135
+
136
+ if using_sqlite?
137
+ conditions = bounding_box_conditions
138
+ else
139
+ min_radius = options.fetch(:min_radius, 0).to_f
140
+ conditions = [bounding_box_conditions + " AND (#{distance}) BETWEEN ? AND ?", min_radius, radius]
141
+ end
142
+ {
143
+ :select => select_clause(options[:select],
144
+ select_distance ? distance : nil,
145
+ select_bearing ? bearing : nil,
146
+ distance_column,
147
+ bearing_column),
148
+ :conditions => add_exclude_condition(conditions, options[:exclude]),
149
+ :order => options.include?(:order) ? options[:order] : "#{distance_column} ASC"
150
+ }
151
+ end
152
+
153
+ ##
154
+ # SQL for calculating distance based on the current database's
155
+ # capabilities (trig functions?).
156
+ #
157
+ def distance_sql(latitude, longitude, options = {})
158
+ method_prefix = using_sqlite? ? "approx" : "full"
159
+ Geocoder::Sql.send(
160
+ method_prefix + "_distance",
161
+ latitude, longitude,
162
+ full_column_name(options[:latitude] || geocoder_options[:latitude]),
163
+ full_column_name(options[:longitude]|| geocoder_options[:longitude]),
164
+ options
165
+ )
166
+ end
167
+
168
+ ##
169
+ # SQL for calculating bearing based on the current database's
170
+ # capabilities (trig functions?).
171
+ #
172
+ def bearing_sql(latitude, longitude, options = {})
173
+ if !options.include?(:bearing)
174
+ options[:bearing] = Geocoder.config.distances
175
+ end
176
+ if options[:bearing]
177
+ method_prefix = using_sqlite? ? "approx" : "full"
178
+ Geocoder::Sql.send(
179
+ method_prefix + "_bearing",
180
+ latitude, longitude,
181
+ full_column_name(options[:latitude] || geocoder_options[:latitude]),
182
+ full_column_name(options[:longitude]|| geocoder_options[:longitude]),
183
+ options
184
+ )
185
+ end
186
+ end
187
+
188
+ ##
189
+ # Generate the SELECT clause.
190
+ #
191
+ def select_clause(columns, distance = nil, bearing = nil, distance_column = 'distance', bearing_column = 'bearing')
192
+ if columns == :id_only
193
+ return full_column_name(primary_key)
194
+ elsif columns == :geo_only
195
+ clause = ""
196
+ else
197
+ clause = (columns || full_column_name("*"))
198
+ end
199
+ if distance
200
+ clause += ", " unless clause.empty?
201
+ clause += "#{distance} AS #{distance_column}"
202
+ end
203
+ if bearing
204
+ clause += ", " unless clause.empty?
205
+ clause += "#{bearing} AS #{bearing_column}"
206
+ end
207
+ clause
208
+ end
209
+
210
+ ##
211
+ # Adds a condition to exclude a given object by ID.
212
+ # Expects conditions as an array or string. Returns array.
213
+ #
214
+ def add_exclude_condition(conditions, exclude)
215
+ conditions = [conditions] if conditions.is_a?(String)
216
+ if exclude
217
+ conditions[0] << " AND #{full_column_name(primary_key)} != ?"
218
+ conditions << exclude.id
219
+ end
220
+ conditions
221
+ end
222
+
223
+ def using_sqlite?
224
+ connection.adapter_name.match(/sqlite/i)
225
+ end
226
+
227
+ def using_postgres?
228
+ connection.adapter_name.match(/postgres/i)
229
+ end
230
+
231
+ ##
232
+ # Use OID type when running in PosgreSQL
233
+ #
234
+ def null_value
235
+ using_postgres? ? 'NULL::text' : 'NULL'
236
+ end
237
+
238
+ ##
239
+ # Value which can be passed to where() to produce no results.
240
+ #
241
+ def false_condition
242
+ using_sqlite? ? 0 : "false"
243
+ end
244
+
245
+ ##
246
+ # Prepend table name if column name doesn't already contain one.
247
+ #
248
+ def full_column_name(column)
249
+ column = column.to_s
250
+ column.include?(".") ? column : [table_name, column].join(".")
251
+ end
252
+ end
253
+
254
+ ##
255
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
256
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
257
+ #
258
+ def geocode
259
+ do_lookup(false) do |o,rs|
260
+ if r = rs.first
261
+ unless r.latitude.nil? or r.longitude.nil?
262
+ o.__send__ "#{self.class.geocoder_options[:latitude]}=", r.latitude
263
+ o.__send__ "#{self.class.geocoder_options[:longitude]}=", r.longitude
264
+ end
265
+ r.coordinates
266
+ end
267
+ end
268
+ end
269
+
270
+ alias_method :fetch_coordinates, :geocode
271
+
272
+ ##
273
+ # Look up address and assign to +address+ attribute (or other as specified
274
+ # in +reverse_geocoded_by+). Returns address (string).
275
+ #
276
+ def reverse_geocode
277
+ do_lookup(true) do |o,rs|
278
+ if r = rs.first
279
+ unless r.address.nil?
280
+ o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
281
+ end
282
+ r.address
283
+ end
284
+ end
285
+ end
286
+
287
+ alias_method :fetch_address, :reverse_geocode
288
+ end
289
+ end