geocoder 1.1.9 → 1.8.0

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 (238) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +309 -0
  3. data/LICENSE +1 -1
  4. data/README.md +544 -540
  5. data/bin/console +13 -0
  6. data/examples/app_defined_lookup_services.rb +22 -0
  7. data/examples/reverse_geocode_job.rb +40 -0
  8. data/lib/easting_northing.rb +171 -0
  9. data/lib/generators/geocoder/config/templates/initializer.rb +22 -16
  10. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +30 -0
  11. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +30 -0
  12. data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
  13. data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
  14. data/lib/generators/geocoder/migration_version.rb +15 -0
  15. data/lib/geocoder/cache.rb +20 -32
  16. data/lib/geocoder/cache_stores/base.rb +40 -0
  17. data/lib/geocoder/cache_stores/generic.rb +35 -0
  18. data/lib/geocoder/cache_stores/redis.rb +34 -0
  19. data/lib/geocoder/calculations.rb +67 -36
  20. data/lib/geocoder/cli.rb +2 -2
  21. data/lib/geocoder/configuration.rb +33 -16
  22. data/lib/geocoder/configuration_hash.rb +4 -4
  23. data/lib/geocoder/esri_token.rb +38 -0
  24. data/lib/geocoder/exceptions.rb +19 -0
  25. data/lib/geocoder/ip_address.rb +33 -0
  26. data/lib/geocoder/kernel_logger.rb +25 -0
  27. data/lib/geocoder/logger.rb +47 -0
  28. data/lib/geocoder/lookup.rb +74 -11
  29. data/lib/geocoder/lookups/abstract_api.rb +46 -0
  30. data/lib/geocoder/lookups/amap.rb +63 -0
  31. data/lib/geocoder/lookups/amazon_location_service.rb +54 -0
  32. data/lib/geocoder/lookups/baidu.rb +24 -15
  33. data/lib/geocoder/lookups/baidu_ip.rb +30 -0
  34. data/lib/geocoder/lookups/ban_data_gouv_fr.rb +143 -0
  35. data/lib/geocoder/lookups/base.rb +109 -23
  36. data/lib/geocoder/lookups/bing.rb +45 -10
  37. data/lib/geocoder/lookups/db_ip_com.rb +52 -0
  38. data/lib/geocoder/lookups/dstk.rb +4 -2
  39. data/lib/geocoder/lookups/esri.rb +61 -8
  40. data/lib/geocoder/lookups/freegeoip.rb +25 -6
  41. data/lib/geocoder/lookups/geoapify.rb +72 -0
  42. data/lib/geocoder/lookups/geocoder_ca.rb +5 -6
  43. data/lib/geocoder/lookups/geocodio.rb +42 -0
  44. data/lib/geocoder/lookups/geoip2.rb +49 -0
  45. data/lib/geocoder/lookups/geoportail_lu.rb +65 -0
  46. data/lib/geocoder/lookups/google.rb +45 -12
  47. data/lib/geocoder/lookups/google_places_details.rb +64 -0
  48. data/lib/geocoder/lookups/google_places_search.rb +76 -0
  49. data/lib/geocoder/lookups/google_premier.rb +16 -2
  50. data/lib/geocoder/lookups/here.rb +73 -0
  51. data/lib/geocoder/lookups/ip2location.rb +71 -0
  52. data/lib/geocoder/lookups/ipapi_com.rb +82 -0
  53. data/lib/geocoder/lookups/ipdata_co.rb +62 -0
  54. data/lib/geocoder/lookups/ipgeolocation.rb +51 -0
  55. data/lib/geocoder/lookups/ipinfo_io.rb +44 -0
  56. data/lib/geocoder/lookups/ipqualityscore.rb +50 -0
  57. data/lib/geocoder/lookups/ipregistry.rb +68 -0
  58. data/lib/geocoder/lookups/ipstack.rb +63 -0
  59. data/lib/geocoder/lookups/latlon.rb +58 -0
  60. data/lib/geocoder/lookups/location_iq.rb +54 -0
  61. data/lib/geocoder/lookups/mapbox.rb +59 -0
  62. data/lib/geocoder/lookups/mapquest.rb +9 -10
  63. data/lib/geocoder/lookups/maxmind.rb +10 -8
  64. data/lib/geocoder/lookups/maxmind_geoip2.rb +70 -0
  65. data/lib/geocoder/lookups/maxmind_local.rb +71 -0
  66. data/lib/geocoder/lookups/melissa_street.rb +41 -0
  67. data/lib/geocoder/lookups/nationaal_georegister_nl.rb +38 -0
  68. data/lib/geocoder/lookups/nominatim.rb +26 -6
  69. data/lib/geocoder/lookups/opencagedata.rb +65 -0
  70. data/lib/geocoder/lookups/osmnames.rb +57 -0
  71. data/lib/geocoder/lookups/pelias.rb +63 -0
  72. data/lib/geocoder/lookups/photon.rb +89 -0
  73. data/lib/geocoder/lookups/pickpoint.rb +41 -0
  74. data/lib/geocoder/lookups/pointpin.rb +69 -0
  75. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +50 -0
  76. data/lib/geocoder/lookups/postcodes_io.rb +31 -0
  77. data/lib/geocoder/lookups/smarty_streets.rb +68 -0
  78. data/lib/geocoder/lookups/telize.rb +75 -0
  79. data/lib/geocoder/lookups/tencent.rb +59 -0
  80. data/lib/geocoder/lookups/test.rb +4 -0
  81. data/lib/geocoder/lookups/twogis.rb +58 -0
  82. data/lib/geocoder/lookups/uk_ordnance_survey_names.rb +59 -0
  83. data/lib/geocoder/lookups/yandex.rb +18 -11
  84. data/lib/geocoder/models/active_record.rb +9 -4
  85. data/lib/geocoder/models/base.rb +1 -4
  86. data/lib/geocoder/models/mongo_base.rb +6 -4
  87. data/lib/geocoder/query.rb +23 -5
  88. data/lib/geocoder/railtie.rb +2 -2
  89. data/lib/geocoder/request.rb +102 -11
  90. data/lib/geocoder/results/abstract_api.rb +146 -0
  91. data/lib/geocoder/results/amap.rb +87 -0
  92. data/lib/geocoder/results/amazon_location_service.rb +57 -0
  93. data/lib/geocoder/results/baidu.rb +10 -14
  94. data/lib/geocoder/results/baidu_ip.rb +62 -0
  95. data/lib/geocoder/results/ban_data_gouv_fr.rb +282 -0
  96. data/lib/geocoder/results/base.rb +13 -1
  97. data/lib/geocoder/results/bing.rb +5 -1
  98. data/lib/geocoder/results/db_ip_com.rb +58 -0
  99. data/lib/geocoder/results/esri.rb +35 -8
  100. data/lib/geocoder/results/freegeoip.rb +2 -7
  101. data/lib/geocoder/results/geoapify.rb +179 -0
  102. data/lib/geocoder/results/geocoder_ca.rb +3 -3
  103. data/lib/geocoder/results/geocodio.rb +78 -0
  104. data/lib/geocoder/results/geoip2.rb +76 -0
  105. data/lib/geocoder/results/geoportail_lu.rb +71 -0
  106. data/lib/geocoder/results/google.rb +26 -0
  107. data/lib/geocoder/results/google_places_details.rb +39 -0
  108. data/lib/geocoder/results/google_places_search.rb +52 -0
  109. data/lib/geocoder/results/here.rb +77 -0
  110. data/lib/geocoder/results/ip2location.rb +22 -0
  111. data/lib/geocoder/results/ipapi_com.rb +45 -0
  112. data/lib/geocoder/results/ipdata_co.rb +40 -0
  113. data/lib/geocoder/results/ipgeolocation.rb +59 -0
  114. data/lib/geocoder/results/ipinfo_io.rb +48 -0
  115. data/lib/geocoder/results/ipqualityscore.rb +54 -0
  116. data/lib/geocoder/results/ipregistry.rb +304 -0
  117. data/lib/geocoder/results/ipstack.rb +60 -0
  118. data/lib/geocoder/results/latlon.rb +71 -0
  119. data/lib/geocoder/results/location_iq.rb +6 -0
  120. data/lib/geocoder/results/mapbox.rb +63 -0
  121. data/lib/geocoder/results/mapquest.rb +5 -8
  122. data/lib/geocoder/results/maxmind.rb +0 -5
  123. data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
  124. data/lib/geocoder/results/maxmind_local.rb +44 -0
  125. data/lib/geocoder/results/melissa_street.rb +46 -0
  126. data/lib/geocoder/results/nationaal_georegister_nl.rb +62 -0
  127. data/lib/geocoder/results/nominatim.rb +41 -14
  128. data/lib/geocoder/results/opencagedata.rb +100 -0
  129. data/lib/geocoder/results/osmnames.rb +56 -0
  130. data/lib/geocoder/results/pelias.rb +58 -0
  131. data/lib/geocoder/results/photon.rb +119 -0
  132. data/lib/geocoder/results/pickpoint.rb +6 -0
  133. data/lib/geocoder/results/pointpin.rb +40 -0
  134. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  135. data/lib/geocoder/results/postcodes_io.rb +40 -0
  136. data/lib/geocoder/results/smarty_streets.rb +142 -0
  137. data/lib/geocoder/results/telize.rb +40 -0
  138. data/lib/geocoder/results/tencent.rb +72 -0
  139. data/lib/geocoder/results/test.rb +20 -3
  140. data/lib/geocoder/results/twogis.rb +76 -0
  141. data/lib/geocoder/results/uk_ordnance_survey_names.rb +59 -0
  142. data/lib/geocoder/results/yandex.rb +244 -32
  143. data/lib/geocoder/sql.rb +25 -21
  144. data/lib/geocoder/stores/active_record.rb +82 -26
  145. data/lib/geocoder/stores/base.rb +9 -14
  146. data/lib/geocoder/stores/mongo_base.rb +0 -31
  147. data/lib/geocoder/util.rb +29 -0
  148. data/lib/geocoder/version.rb +1 -1
  149. data/lib/geocoder.rb +6 -13
  150. data/lib/maxmind_database.rb +109 -0
  151. data/lib/tasks/geocoder.rake +30 -3
  152. data/lib/tasks/maxmind.rake +73 -0
  153. metadata +115 -98
  154. data/.gitignore +0 -5
  155. data/.travis.yml +0 -27
  156. data/Rakefile +0 -25
  157. data/examples/autoexpire_cache_dalli.rb +0 -62
  158. data/examples/autoexpire_cache_redis.rb +0 -28
  159. data/gemfiles/Gemfile.mongoid-2.4.x +0 -15
  160. data/lib/geocoder/lookups/geocoder_us.rb +0 -39
  161. data/lib/geocoder/lookups/ovi.rb +0 -62
  162. data/lib/geocoder/lookups/yahoo.rb +0 -86
  163. data/lib/geocoder/results/geocoder_us.rb +0 -39
  164. data/lib/geocoder/results/ovi.rb +0 -62
  165. data/lib/geocoder/results/yahoo.rb +0 -55
  166. data/lib/hash_recursive_merge.rb +0 -74
  167. data/lib/oauth_util.rb +0 -112
  168. data/test/active_record_test.rb +0 -15
  169. data/test/cache_test.rb +0 -35
  170. data/test/calculations_test.rb +0 -211
  171. data/test/configuration_test.rb +0 -78
  172. data/test/custom_block_test.rb +0 -32
  173. data/test/error_handling_test.rb +0 -43
  174. data/test/fixtures/baidu_invalid_key +0 -1
  175. data/test/fixtures/baidu_no_results +0 -1
  176. data/test/fixtures/baidu_reverse +0 -1
  177. data/test/fixtures/baidu_shanghai_pearl_tower +0 -12
  178. data/test/fixtures/bing_invalid_key +0 -1
  179. data/test/fixtures/bing_madison_square_garden +0 -40
  180. data/test/fixtures/bing_no_results +0 -16
  181. data/test/fixtures/bing_reverse +0 -42
  182. data/test/fixtures/esri_madison_square_garden +0 -59
  183. data/test/fixtures/esri_no_results +0 -8
  184. data/test/fixtures/esri_reverse +0 -21
  185. data/test/fixtures/freegeoip_74_200_247_59 +0 -12
  186. data/test/fixtures/freegeoip_no_results +0 -1
  187. data/test/fixtures/geocoder_ca_madison_square_garden +0 -1
  188. data/test/fixtures/geocoder_ca_no_results +0 -1
  189. data/test/fixtures/geocoder_ca_reverse +0 -34
  190. data/test/fixtures/geocoder_us_madison_square_garden +0 -1
  191. data/test/fixtures/geocoder_us_no_results +0 -1
  192. data/test/fixtures/google_garbage +0 -456
  193. data/test/fixtures/google_madison_square_garden +0 -57
  194. data/test/fixtures/google_no_city_data +0 -44
  195. data/test/fixtures/google_no_locality +0 -51
  196. data/test/fixtures/google_no_results +0 -4
  197. data/test/fixtures/google_over_limit +0 -4
  198. data/test/fixtures/mapquest_error +0 -16
  199. data/test/fixtures/mapquest_invalid_api_key +0 -16
  200. data/test/fixtures/mapquest_invalid_request +0 -16
  201. data/test/fixtures/mapquest_madison_square_garden +0 -52
  202. data/test/fixtures/mapquest_no_results +0 -16
  203. data/test/fixtures/maxmind_24_24_24_21 +0 -1
  204. data/test/fixtures/maxmind_24_24_24_22 +0 -1
  205. data/test/fixtures/maxmind_24_24_24_23 +0 -1
  206. data/test/fixtures/maxmind_24_24_24_24 +0 -1
  207. data/test/fixtures/maxmind_74_200_247_59 +0 -1
  208. data/test/fixtures/maxmind_invalid_key +0 -1
  209. data/test/fixtures/maxmind_no_results +0 -1
  210. data/test/fixtures/nominatim_madison_square_garden +0 -150
  211. data/test/fixtures/nominatim_no_results +0 -1
  212. data/test/fixtures/ovi_madison_square_garden +0 -72
  213. data/test/fixtures/ovi_no_results +0 -8
  214. data/test/fixtures/yahoo_error +0 -1
  215. data/test/fixtures/yahoo_invalid_key +0 -2
  216. data/test/fixtures/yahoo_madison_square_garden +0 -52
  217. data/test/fixtures/yahoo_no_results +0 -10
  218. data/test/fixtures/yahoo_over_limit +0 -2
  219. data/test/fixtures/yandex_invalid_key +0 -1
  220. data/test/fixtures/yandex_kremlin +0 -48
  221. data/test/fixtures/yandex_no_city_and_town +0 -112
  222. data/test/fixtures/yandex_no_results +0 -16
  223. data/test/geocoder_test.rb +0 -59
  224. data/test/https_test.rb +0 -16
  225. data/test/integration/smoke_test.rb +0 -26
  226. data/test/lookup_test.rb +0 -117
  227. data/test/method_aliases_test.rb +0 -25
  228. data/test/mongoid_test.rb +0 -46
  229. data/test/mongoid_test_helper.rb +0 -43
  230. data/test/near_test.rb +0 -61
  231. data/test/oauth_util_test.rb +0 -30
  232. data/test/proxy_test.rb +0 -36
  233. data/test/query_test.rb +0 -52
  234. data/test/request_test.rb +0 -29
  235. data/test/result_test.rb +0 -42
  236. data/test/services_test.rb +0 -393
  237. data/test/test_helper.rb +0 -289
  238. data/test/test_mode_test.rb +0 -59
@@ -2,79 +2,291 @@ require 'geocoder/results/base'
2
2
 
3
3
  module Geocoder::Result
4
4
  class Yandex < Base
5
+ # Yandex result has difficult tree structure,
6
+ # and presence of some nodes depends on exact search case.
7
+
8
+ # Also Yandex lacks documentation about it.
9
+ # See https://tech.yandex.com/maps/doc/geocoder/desc/concepts/response_structure-docpage/
10
+
11
+ # Ultimatly, we need to find Locality and/or Thoroughfare data.
12
+
13
+ # It may resides on the top (ADDRESS_DETAILS) level.
14
+ # example: 'Baltic Sea'
15
+ # "AddressDetails": {
16
+ # "Locality": {
17
+ # "Premise": {
18
+ # "PremiseName": "Baltic Sea"
19
+ # }
20
+ # }
21
+ # }
22
+
23
+ ADDRESS_DETAILS = %w[
24
+ GeoObject metaDataProperty GeocoderMetaData
25
+ AddressDetails
26
+ ].freeze
27
+
28
+ # On COUNTRY_LEVEL.
29
+ # example: 'Potomak'
30
+ # "AddressDetails": {
31
+ # "Country": {
32
+ # "AddressLine": "reka Potomak",
33
+ # "CountryNameCode": "US",
34
+ # "CountryName": "United States of America",
35
+ # "Locality": {
36
+ # "Premise": {
37
+ # "PremiseName": "reka Potomak"
38
+ # }
39
+ # }
40
+ # }
41
+ # }
42
+
43
+ COUNTRY_LEVEL = %w[
44
+ GeoObject metaDataProperty GeocoderMetaData
45
+ AddressDetails Country
46
+ ].freeze
47
+
48
+ # On ADMIN_LEVEL (usually state or city)
49
+ # example: 'Moscow, Tverskaya'
50
+ # "AddressDetails": {
51
+ # "Country": {
52
+ # "AddressLine": "Moscow, Tverskaya Street",
53
+ # "CountryNameCode": "RU",
54
+ # "CountryName": "Russia",
55
+ # "AdministrativeArea": {
56
+ # "AdministrativeAreaName": "Moscow",
57
+ # "Locality": {
58
+ # "LocalityName": "Moscow",
59
+ # "Thoroughfare": {
60
+ # "ThoroughfareName": "Tverskaya Street"
61
+ # }
62
+ # }
63
+ # }
64
+ # }
65
+ # }
66
+
67
+ ADMIN_LEVEL = %w[
68
+ GeoObject metaDataProperty GeocoderMetaData
69
+ AddressDetails Country
70
+ AdministrativeArea
71
+ ].freeze
72
+
73
+ # On SUBADMIN_LEVEL (may refer to urban district)
74
+ # example: 'Moscow Region, Krasnogorsk'
75
+ # "AddressDetails": {
76
+ # "Country": {
77
+ # "AddressLine": "Moscow Region, Krasnogorsk",
78
+ # "CountryNameCode": "RU",
79
+ # "CountryName": "Russia",
80
+ # "AdministrativeArea": {
81
+ # "AdministrativeAreaName": "Moscow Region",
82
+ # "SubAdministrativeArea": {
83
+ # "SubAdministrativeAreaName": "gorodskoy okrug Krasnogorsk",
84
+ # "Locality": {
85
+ # "LocalityName": "Krasnogorsk"
86
+ # }
87
+ # }
88
+ # }
89
+ # }
90
+ # }
91
+
92
+ SUBADMIN_LEVEL = %w[
93
+ GeoObject metaDataProperty GeocoderMetaData
94
+ AddressDetails Country
95
+ AdministrativeArea
96
+ SubAdministrativeArea
97
+ ].freeze
98
+
99
+ # On DEPENDENT_LOCALITY_1 (may refer to district of city)
100
+ # example: 'Paris, Etienne Marcel'
101
+ # "AddressDetails": {
102
+ # "Country": {
103
+ # "AddressLine": "Île-de-France, Paris, 1er Arrondissement, Rue Étienne Marcel",
104
+ # "CountryNameCode": "FR",
105
+ # "CountryName": "France",
106
+ # "AdministrativeArea": {
107
+ # "AdministrativeAreaName": "Île-de-France",
108
+ # "Locality": {
109
+ # "LocalityName": "Paris",
110
+ # "DependentLocality": {
111
+ # "DependentLocalityName": "1er Arrondissement",
112
+ # "Thoroughfare": {
113
+ # "ThoroughfareName": "Rue Étienne Marcel"
114
+ # }
115
+ # }
116
+ # }
117
+ # }
118
+ # }
119
+ # }
120
+
121
+ DEPENDENT_LOCALITY_1 = %w[
122
+ GeoObject metaDataProperty GeocoderMetaData
123
+ AddressDetails Country
124
+ AdministrativeArea Locality
125
+ DependentLocality
126
+ ].freeze
127
+
128
+ # On DEPENDENT_LOCALITY_2 (for special cases like turkish "mahalle")
129
+ # https://en.wikipedia.org/wiki/Mahalle
130
+ # example: 'Istanbul Mabeyinci Yokuşu 17'
131
+
132
+ # "AddressDetails": {
133
+ # "Country": {
134
+ # "AddressLine": "İstanbul, Fatih, Saraç İshak Mah., Mabeyinci Yokuşu, 17",
135
+ # "CountryNameCode": "TR",
136
+ # "CountryName": "Turkey",
137
+ # "AdministrativeArea": {
138
+ # "AdministrativeAreaName": "İstanbul",
139
+ # "SubAdministrativeArea": {
140
+ # "SubAdministrativeAreaName": "Fatih",
141
+ # "Locality": {
142
+ # "DependentLocality": {
143
+ # "DependentLocalityName": "Saraç İshak Mah.",
144
+ # "Thoroughfare": {
145
+ # "ThoroughfareName": "Mabeyinci Yokuşu",
146
+ # "Premise": {
147
+ # "PremiseNumber": "17"
148
+ # }
149
+ # }
150
+ # }
151
+ # }
152
+ # }
153
+ # }
154
+ # }
155
+ # }
156
+
157
+ DEPENDENT_LOCALITY_2 = %w[
158
+ GeoObject metaDataProperty GeocoderMetaData
159
+ AddressDetails Country
160
+ AdministrativeArea
161
+ SubAdministrativeArea Locality
162
+ DependentLocality
163
+ ].freeze
5
164
 
6
165
  def coordinates
7
166
  @data['GeoObject']['Point']['pos'].split(' ').reverse.map(&:to_f)
8
167
  end
9
168
 
10
- def address(format = :full)
169
+ def address(_format = :full)
11
170
  @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['text']
12
171
  end
13
172
 
14
173
  def city
15
- if state.empty? and address_details.has_key? 'Locality'
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
174
+ result =
175
+ if state.empty?
176
+ find_in_hash(@data, *COUNTRY_LEVEL, 'Locality', 'LocalityName')
177
+ elsif sub_state.empty?
178
+ find_in_hash(@data, *ADMIN_LEVEL, 'Locality', 'LocalityName')
179
+ else
180
+ find_in_hash(@data, *SUBADMIN_LEVEL, 'Locality', 'LocalityName')
181
+ end
182
+
183
+ result || ""
24
184
  end
25
185
 
26
186
  def country
27
- address_details['CountryName']
187
+ find_in_hash(@data, *COUNTRY_LEVEL, 'CountryName') || ""
28
188
  end
29
189
 
30
190
  def country_code
31
- address_details['CountryNameCode']
191
+ find_in_hash(@data, *COUNTRY_LEVEL, 'CountryNameCode') || ""
32
192
  end
33
193
 
34
194
  def state
35
- if address_details['AdministrativeArea']
36
- address_details['AdministrativeArea']['AdministrativeAreaName']
37
- else
38
- ""
39
- end
195
+ find_in_hash(@data, *ADMIN_LEVEL, 'AdministrativeAreaName') || ""
40
196
  end
41
197
 
42
198
  def sub_state
43
- if !state.empty? and address_details['AdministrativeArea']['SubAdministrativeArea']
44
- address_details['AdministrativeArea']['SubAdministrativeArea']['SubAdministrativeAreaName']
45
- else
46
- ""
47
- end
199
+ return "" if state.empty?
200
+ find_in_hash(@data, *SUBADMIN_LEVEL, 'SubAdministrativeAreaName') || ""
48
201
  end
49
202
 
50
203
  def state_code
51
204
  ""
52
205
  end
53
206
 
54
- def postal_code
55
- ""
207
+ def street
208
+ thoroughfare_data.is_a?(Hash) ? thoroughfare_data['ThoroughfareName'] : ""
209
+ end
210
+
211
+ def street_number
212
+ premise.is_a?(Hash) ? premise.fetch('PremiseNumber', "") : ""
56
213
  end
57
214
 
58
215
  def premise_name
59
- address_details['Locality']['Premise']['PremiseName']
216
+ premise.is_a?(Hash) ? premise.fetch('PremiseName', "") : ""
217
+ end
218
+
219
+ def postal_code
220
+ return "" unless premise.is_a?(Hash)
221
+ find_in_hash(premise, 'PostalCode', 'PostalCodeNumber') || ""
222
+ end
223
+
224
+ def kind
225
+ @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['kind']
60
226
  end
61
227
 
62
228
  def precision
63
229
  @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['precision']
64
230
  end
65
231
 
232
+ def viewport
233
+ envelope = @data['GeoObject']['boundedBy']['Envelope'] || fail
234
+ east, north = envelope['upperCorner'].split(' ').map(&:to_f)
235
+ west, south = envelope['lowerCorner'].split(' ').map(&:to_f)
236
+ [south, west, north, east]
237
+ end
238
+
66
239
  private # ----------------------------------------------------------------
67
240
 
68
- def address_details
69
- @data['GeoObject']['metaDataProperty']['GeocoderMetaData']['AddressDetails']['Country']
241
+ def top_level_locality
242
+ find_in_hash(@data, *ADDRESS_DETAILS, 'Locality')
243
+ end
244
+
245
+ def country_level_locality
246
+ find_in_hash(@data, *COUNTRY_LEVEL, 'Locality')
247
+ end
248
+
249
+ def admin_locality
250
+ find_in_hash(@data, *ADMIN_LEVEL, 'Locality')
251
+ end
252
+
253
+ def subadmin_locality
254
+ find_in_hash(@data, *SUBADMIN_LEVEL, 'Locality')
70
255
  end
71
256
 
72
- def sub_state_city
73
- if sub_state && sub_state["Locality"]
74
- sub_state['Locality']['LocalityName']
75
- else
76
- ""
257
+ def dependent_locality
258
+ find_in_hash(@data, *DEPENDENT_LOCALITY_1) ||
259
+ find_in_hash(@data, *DEPENDENT_LOCALITY_2)
260
+ end
261
+
262
+ def locality_data
263
+ dependent_locality || subadmin_locality || admin_locality ||
264
+ country_level_locality || top_level_locality
265
+ end
266
+
267
+ def thoroughfare_data
268
+ locality_data['Thoroughfare'] if locality_data.is_a?(Hash)
269
+ end
270
+
271
+ def premise
272
+ if thoroughfare_data.is_a?(Hash)
273
+ thoroughfare_data['Premise']
274
+ elsif locality_data.is_a?(Hash)
275
+ locality_data['Premise']
77
276
  end
78
277
  end
278
+
279
+ def find_in_hash(source, *keys)
280
+ key = keys.shift
281
+ result = source[key]
282
+
283
+ if keys.empty?
284
+ return result
285
+ elsif !result.is_a?(Hash)
286
+ return nil
287
+ end
288
+
289
+ find_in_hash(result, *keys)
290
+ end
79
291
  end
80
292
  end
data/lib/geocoder/sql.rb CHANGED
@@ -4,8 +4,8 @@ module Geocoder
4
4
 
5
5
  ##
6
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().
7
+ # SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
8
+ # ATAN2().
9
9
  #
10
10
  # Based on the excellent tutorial at:
11
11
  # http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
@@ -44,13 +44,13 @@ module Geocoder
44
44
  end
45
45
 
46
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 "
47
+ spans = "#{lat_attr} BETWEEN #{sw_lat.to_f} AND #{ne_lat.to_f} AND "
48
48
  # handle box that spans 180 longitude
49
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}"
50
+ spans + "(#{lon_attr} BETWEEN #{sw_lng.to_f} AND 180 OR " +
51
+ "#{lon_attr} BETWEEN -180 AND #{ne_lng.to_f})"
52
52
  else
53
- spans + "#{lon_attr} BETWEEN #{sw_lng} AND #{ne_lng}"
53
+ spans + "#{lon_attr} BETWEEN #{sw_lng.to_f} AND #{ne_lng.to_f}"
54
54
  end
55
55
  end
56
56
 
@@ -59,30 +59,34 @@ module Geocoder
59
59
  # and an options hash which must include a :bearing value
60
60
  # (:linear or :spherical).
61
61
  #
62
+ # For use with a database that supports MOD() and trigonometric functions
63
+ # SIN(), COS(), ASIN(), ATAN2().
64
+ #
62
65
  # Based on:
63
66
  # http://www.beginningspatial.com/calculating_bearing_one_point_another
64
67
  #
65
68
  def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
69
+ degrees_per_radian = Geocoder::Calculations::DEGREES_PER_RADIAN
66
70
  case options[:bearing] || Geocoder.config.distances
67
71
  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"
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)"
74
78
  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}))" +
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})" +
80
84
  ") - (" +
81
- "SIN(RADIANS(#{latitude.to_f})) * COS(RADIANS(#{lat_attr})) * " +
82
- "COS(RADIANS(#{lon_attr} - #{longitude.to_f}))" +
85
+ "SIN( (#{latitude.to_f}) / #{degrees_per_radian}) * COS((#{lat_attr}) / #{degrees_per_radian}) * " +
86
+ "COS( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian})" +
83
87
  ")" +
84
- ")) + 360 " +
85
- "AS decimal) % 360"
88
+ ") * #{degrees_per_radian}) + 360 " +
89
+ "AS decimal), 360)"
86
90
  end
87
91
  end
88
92
 
@@ -18,14 +18,19 @@ module Geocoder::Store
18
18
 
19
19
  # scope: geocoded objects
20
20
  scope :geocoded, lambda {
21
- where("#{geocoder_options[:latitude]} IS NOT NULL " +
22
- "AND #{geocoder_options[:longitude]} IS NOT NULL")
21
+ where("#{table_name}.#{geocoder_options[:latitude]} IS NOT NULL " +
22
+ "AND #{table_name}.#{geocoder_options[:longitude]} IS NOT NULL")
23
23
  }
24
24
 
25
25
  # scope: not-geocoded objects
26
26
  scope :not_geocoded, lambda {
27
- where("#{geocoder_options[:latitude]} IS NULL " +
28
- "OR #{geocoder_options[:longitude]} IS NULL")
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")
29
34
  }
30
35
 
31
36
  ##
@@ -45,7 +50,7 @@ module Geocoder::Store
45
50
  # If no lat/lon given we don't want any results, but we still
46
51
  # need distance and bearing columns so you can add, for example:
47
52
  # .order("distance")
48
- select(select_clause(nil, "NULL", "NULL")).where(false_condition)
53
+ select(select_clause(nil, null_value, null_value)).where(false_condition)
49
54
  end
50
55
  }
51
56
 
@@ -55,7 +60,7 @@ module Geocoder::Store
55
60
  # corner followed by the northeast corner of the box
56
61
  # (<tt>[[sw_lat, sw_lon], [ne_lat, ne_lon]]</tt>).
57
62
  #
58
- scope :within_bounding_box, lambda{ |bounds|
63
+ scope :within_bounding_box, lambda{ |*bounds|
59
64
  sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
60
65
  if sw_lat && sw_lng && ne_lat && ne_lng
61
66
  where(Geocoder::Sql.within_bounding_box(
@@ -64,7 +69,7 @@ module Geocoder::Store
64
69
  full_column_name(geocoder_options[:longitude])
65
70
  ))
66
71
  else
67
- select(select_clause(nil, "NULL", "NULL")).where(false_condition)
72
+ select(select_clause(nil, null_value, null_value)).where(false_condition)
68
73
  end
69
74
  }
70
75
  end
@@ -82,8 +87,6 @@ module Geocoder::Store
82
87
  end
83
88
  end
84
89
 
85
- private # ----------------------------------------------------------------
86
-
87
90
  ##
88
91
  # Get options hash suitable for passing to ActiveRecord.find to get
89
92
  # records within a radius (in kilometers) of the given point.
@@ -107,31 +110,50 @@ module Geocoder::Store
107
110
  # * +:exclude+ - an object to exclude (used by the +nearbys+ method)
108
111
  # * +:distance_column+ - used to set the column name of the calculated distance.
109
112
  # * +:bearing_column+ - used to set the column name of the calculated bearing.
113
+ # * +:min_radius+ - the value to use as the minimum radius.
114
+ # ignored if database is sqlite.
115
+ # default is 0.0
110
116
  #
111
117
  def near_scope_options(latitude, longitude, radius = 20, options = {})
112
118
  if options[:units]
113
119
  options[:units] = options[:units].to_sym
114
120
  end
121
+ latitude_attribute = options[:latitude] || geocoder_options[:latitude]
122
+ longitude_attribute = options[:longitude] || geocoder_options[:longitude]
115
123
  options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
116
- select_distance = options.fetch(:select_distance, true)
124
+ select_distance = options.fetch(:select_distance) { true }
117
125
  options[:order] = "" if !select_distance && !options.include?(:order)
118
- select_bearing = options.fetch(:select_bearing, true)
126
+ select_bearing = options.fetch(:select_bearing) { true }
119
127
  bearing = bearing_sql(latitude, longitude, options)
120
128
  distance = distance_sql(latitude, longitude, options)
121
- distance_column = options.fetch(:distance_column, 'distance')
122
- bearing_column = options.fetch(:bearing_column, 'bearing')
129
+ distance_column = options.fetch(:distance_column) { 'distance' }
130
+ bearing_column = options.fetch(:bearing_column) { 'bearing' }
123
131
 
124
- b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
132
+ # If radius is a DB column name, bounding box should include
133
+ # all rows within the maximum radius appearing in that column.
134
+ # Note: performance is dependent on variability of radii.
135
+ bb_radius = radius.is_a?(Symbol) ? maximum(radius) : radius
136
+ b = Geocoder::Calculations.bounding_box([latitude, longitude], bb_radius, options)
125
137
  args = b + [
126
- full_column_name(geocoder_options[:latitude]),
127
- full_column_name(geocoder_options[:longitude])
138
+ full_column_name(latitude_attribute),
139
+ full_column_name(longitude_attribute)
128
140
  ]
129
141
  bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
130
142
 
131
- if using_sqlite?
143
+ if using_unextended_sqlite?
132
144
  conditions = bounding_box_conditions
133
145
  else
134
- conditions = [bounding_box_conditions + " AND #{distance} <= ?", radius]
146
+ min_radius = options.fetch(:min_radius, 0).to_f
147
+ # if radius is a DB column name,
148
+ # find rows between min_radius and value in column
149
+ if radius.is_a?(Symbol)
150
+ c = "BETWEEN ? AND #{radius}"
151
+ a = [min_radius]
152
+ else
153
+ c = "BETWEEN ? AND ?"
154
+ a = [min_radius, radius]
155
+ end
156
+ conditions = [bounding_box_conditions + " AND (#{distance}) " + c] + a
135
157
  end
136
158
  {
137
159
  :select => select_clause(options[:select],
@@ -149,12 +171,12 @@ module Geocoder::Store
149
171
  # capabilities (trig functions?).
150
172
  #
151
173
  def distance_sql(latitude, longitude, options = {})
152
- method_prefix = using_sqlite? ? "approx" : "full"
174
+ method_prefix = using_unextended_sqlite? ? "approx" : "full"
153
175
  Geocoder::Sql.send(
154
176
  method_prefix + "_distance",
155
177
  latitude, longitude,
156
- full_column_name(geocoder_options[:latitude]),
157
- full_column_name(geocoder_options[:longitude]),
178
+ full_column_name(options[:latitude] || geocoder_options[:latitude]),
179
+ full_column_name(options[:longitude]|| geocoder_options[:longitude]),
158
180
  options
159
181
  )
160
182
  end
@@ -168,12 +190,12 @@ module Geocoder::Store
168
190
  options[:bearing] = Geocoder.config.distances
169
191
  end
170
192
  if options[:bearing]
171
- method_prefix = using_sqlite? ? "approx" : "full"
193
+ method_prefix = using_unextended_sqlite? ? "approx" : "full"
172
194
  Geocoder::Sql.send(
173
195
  method_prefix + "_bearing",
174
196
  latitude, longitude,
175
- full_column_name(geocoder_options[:latitude]),
176
- full_column_name(geocoder_options[:longitude]),
197
+ full_column_name(options[:latitude] || geocoder_options[:latitude]),
198
+ full_column_name(options[:longitude]|| geocoder_options[:longitude]),
177
199
  options
178
200
  )
179
201
  end
@@ -214,15 +236,38 @@ module Geocoder::Store
214
236
  conditions
215
237
  end
216
238
 
239
+ def using_unextended_sqlite?
240
+ using_sqlite? && !using_sqlite_with_extensions?
241
+ end
242
+
217
243
  def using_sqlite?
218
- connection.adapter_name.match /sqlite/i
244
+ !!connection.adapter_name.match(/sqlite/i)
245
+ end
246
+
247
+ def using_sqlite_with_extensions?
248
+ connection.adapter_name.match(/sqlite/i) &&
249
+ defined?(::SqliteExt) &&
250
+ %W(MOD POWER SQRT PI SIN COS ASIN ATAN2).all?{ |fn_name|
251
+ connection.raw_connection.function_created?(fn_name)
252
+ }
253
+ end
254
+
255
+ def using_postgres?
256
+ connection.adapter_name.match(/postgres/i)
257
+ end
258
+
259
+ ##
260
+ # Use OID type when running in PosgreSQL
261
+ #
262
+ def null_value
263
+ using_postgres? ? 'NULL::text' : 'NULL'
219
264
  end
220
265
 
221
266
  ##
222
267
  # Value which can be passed to where() to produce no results.
223
268
  #
224
269
  def false_condition
225
- using_sqlite? ? 0 : "false"
270
+ using_unextended_sqlite? ? 0 : "false"
226
271
  end
227
272
 
228
273
  ##
@@ -234,6 +279,17 @@ module Geocoder::Store
234
279
  end
235
280
  end
236
281
 
282
+ ##
283
+ # Get nearby geocoded objects.
284
+ # Takes the same options hash as the near class method (scope).
285
+ # Returns nil if the object is not geocoded.
286
+ #
287
+ def nearbys(radius = 20, options = {})
288
+ return nil unless geocoded?
289
+ options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
290
+ self.class.near(self, radius, options)
291
+ end
292
+
237
293
  ##
238
294
  # Look up coordinates and assign to +latitude+ and +longitude+ attributes
239
295
  # (or other as specified in +geocoded_by+). Returns coordinates (array).
@@ -6,7 +6,7 @@ module Geocoder
6
6
  # Is this object geocoded? (Does it have latitude and longitude?)
7
7
  #
8
8
  def geocoded?
9
- to_coordinates.compact.size > 0
9
+ to_coordinates.compact.size == 2
10
10
  end
11
11
 
12
12
  ##
@@ -55,17 +55,6 @@ module Geocoder
55
55
  point, to_coordinates, options)
56
56
  end
57
57
 
58
- ##
59
- # Get nearby geocoded objects.
60
- # Takes the same options hash as the near class method (scope).
61
- # Returns nil if the object is not geocoded.
62
- #
63
- def nearbys(radius = 20, options = {})
64
- return nil unless geocoded?
65
- options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
66
- self.class.near(self, radius, options)
67
- end
68
-
69
58
  ##
70
59
  # Look up coordinates and assign to +latitude+ and +longitude+ attributes
71
60
  # (or other as specified in +geocoded_by+). Returns coordinates (array).
@@ -101,7 +90,14 @@ module Geocoder
101
90
  return
102
91
  end
103
92
 
104
- results = Geocoder.search(query)
93
+ query_options = [:lookup, :ip_lookup, :language, :params].inject({}) do |hash, key|
94
+ if options.has_key?(key)
95
+ val = options[key]
96
+ hash[key] = val.respond_to?(:call) ? val.call(self) : val
97
+ end
98
+ hash
99
+ end
100
+ results = Geocoder.search(query, query_options)
105
101
 
106
102
  # execute custom block, if specified in configuration
107
103
  block_key = reverse ? :reverse_block : :geocode_block
@@ -117,4 +113,3 @@ module Geocoder
117
113
  end
118
114
  end
119
115
  end
120
-