geocoder2 0.1.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 (146) hide show
  1. data/.gitignore +5 -0
  2. data/.travis.yml +27 -0
  3. data/CHANGELOG.md +329 -0
  4. data/LICENSE +20 -0
  5. data/README.md +796 -0
  6. data/Rakefile +25 -0
  7. data/bin/geocode2 +5 -0
  8. data/examples/autoexpire_cache_dalli.rb +62 -0
  9. data/examples/autoexpire_cache_redis.rb +28 -0
  10. data/examples/cache_bypass.rb +48 -0
  11. data/gemfiles/Gemfile.mongoid-2.4.x +15 -0
  12. data/json?address=26+leonard+street%2C+Belmont&key=AIzaSyDoltU6YL8XeIQrSLFGk6ZfpKaWkPukwYQ&language=en +68 -0
  13. data/lib/generators/geocoder2/config/config_generator.rb +14 -0
  14. data/lib/generators/geocoder2/config/templates/initializer.rb +21 -0
  15. data/lib/geocoder2/cache.rb +89 -0
  16. data/lib/geocoder2/calculations.rb +389 -0
  17. data/lib/geocoder2/cli.rb +121 -0
  18. data/lib/geocoder2/configuration.rb +130 -0
  19. data/lib/geocoder2/configuration_hash.rb +11 -0
  20. data/lib/geocoder2/exceptions.rb +21 -0
  21. data/lib/geocoder2/lookup.rb +86 -0
  22. data/lib/geocoder2/lookups/baidu.rb +54 -0
  23. data/lib/geocoder2/lookups/base.rb +266 -0
  24. data/lib/geocoder2/lookups/bing.rb +47 -0
  25. data/lib/geocoder2/lookups/dstk.rb +20 -0
  26. data/lib/geocoder2/lookups/esri.rb +48 -0
  27. data/lib/geocoder2/lookups/freegeoip.rb +43 -0
  28. data/lib/geocoder2/lookups/geocoder_ca.rb +54 -0
  29. data/lib/geocoder2/lookups/geocoder_us.rb +39 -0
  30. data/lib/geocoder2/lookups/google.rb +69 -0
  31. data/lib/geocoder2/lookups/google_premier.rb +47 -0
  32. data/lib/geocoder2/lookups/mapquest.rb +59 -0
  33. data/lib/geocoder2/lookups/maxmind.rb +88 -0
  34. data/lib/geocoder2/lookups/nominatim.rb +44 -0
  35. data/lib/geocoder2/lookups/ovi.rb +62 -0
  36. data/lib/geocoder2/lookups/test.rb +44 -0
  37. data/lib/geocoder2/lookups/yahoo.rb +86 -0
  38. data/lib/geocoder2/lookups/yandex.rb +54 -0
  39. data/lib/geocoder2/models/active_record.rb +46 -0
  40. data/lib/geocoder2/models/base.rb +42 -0
  41. data/lib/geocoder2/models/mongo_base.rb +60 -0
  42. data/lib/geocoder2/models/mongo_mapper.rb +26 -0
  43. data/lib/geocoder2/models/mongoid.rb +32 -0
  44. data/lib/geocoder2/query.rb +107 -0
  45. data/lib/geocoder2/railtie.rb +26 -0
  46. data/lib/geocoder2/request.rb +23 -0
  47. data/lib/geocoder2/results/baidu.rb +79 -0
  48. data/lib/geocoder2/results/base.rb +67 -0
  49. data/lib/geocoder2/results/bing.rb +48 -0
  50. data/lib/geocoder2/results/dstk.rb +6 -0
  51. data/lib/geocoder2/results/esri.rb +51 -0
  52. data/lib/geocoder2/results/freegeoip.rb +45 -0
  53. data/lib/geocoder2/results/geocoder_ca.rb +60 -0
  54. data/lib/geocoder2/results/geocoder_us.rb +39 -0
  55. data/lib/geocoder2/results/google.rb +124 -0
  56. data/lib/geocoder2/results/google_premier.rb +6 -0
  57. data/lib/geocoder2/results/mapquest.rb +51 -0
  58. data/lib/geocoder2/results/maxmind.rb +135 -0
  59. data/lib/geocoder2/results/nominatim.rb +94 -0
  60. data/lib/geocoder2/results/ovi.rb +62 -0
  61. data/lib/geocoder2/results/test.rb +16 -0
  62. data/lib/geocoder2/results/yahoo.rb +55 -0
  63. data/lib/geocoder2/results/yandex.rb +80 -0
  64. data/lib/geocoder2/sql.rb +106 -0
  65. data/lib/geocoder2/stores/active_record.rb +272 -0
  66. data/lib/geocoder2/stores/base.rb +120 -0
  67. data/lib/geocoder2/stores/mongo_base.rb +89 -0
  68. data/lib/geocoder2/stores/mongo_mapper.rb +13 -0
  69. data/lib/geocoder2/stores/mongoid.rb +13 -0
  70. data/lib/geocoder2/version.rb +3 -0
  71. data/lib/geocoder2.rb +55 -0
  72. data/lib/hash_recursive_merge.rb +74 -0
  73. data/lib/oauth_util.rb +112 -0
  74. data/lib/tasks/geocoder2.rake +27 -0
  75. data/test/active_record_test.rb +15 -0
  76. data/test/cache_test.rb +35 -0
  77. data/test/calculations_test.rb +211 -0
  78. data/test/configuration_test.rb +78 -0
  79. data/test/custom_block_test.rb +32 -0
  80. data/test/error_handling_test.rb +43 -0
  81. data/test/fixtures/baidu_invalid_key +1 -0
  82. data/test/fixtures/baidu_no_results +1 -0
  83. data/test/fixtures/baidu_reverse +1 -0
  84. data/test/fixtures/baidu_shanghai_pearl_tower +12 -0
  85. data/test/fixtures/bing_invalid_key +1 -0
  86. data/test/fixtures/bing_madison_square_garden +40 -0
  87. data/test/fixtures/bing_no_results +16 -0
  88. data/test/fixtures/bing_reverse +42 -0
  89. data/test/fixtures/esri_madison_square_garden +59 -0
  90. data/test/fixtures/esri_no_results +8 -0
  91. data/test/fixtures/esri_reverse +21 -0
  92. data/test/fixtures/freegeoip_74_200_247_59 +12 -0
  93. data/test/fixtures/freegeoip_no_results +1 -0
  94. data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
  95. data/test/fixtures/geocoder_ca_no_results +1 -0
  96. data/test/fixtures/geocoder_ca_reverse +34 -0
  97. data/test/fixtures/geocoder_us_madison_square_garden +1 -0
  98. data/test/fixtures/geocoder_us_no_results +1 -0
  99. data/test/fixtures/google_garbage +456 -0
  100. data/test/fixtures/google_madison_square_garden +57 -0
  101. data/test/fixtures/google_no_city_data +44 -0
  102. data/test/fixtures/google_no_locality +51 -0
  103. data/test/fixtures/google_no_results +4 -0
  104. data/test/fixtures/google_over_limit +4 -0
  105. data/test/fixtures/mapquest_error +16 -0
  106. data/test/fixtures/mapquest_invalid_api_key +16 -0
  107. data/test/fixtures/mapquest_invalid_request +16 -0
  108. data/test/fixtures/mapquest_madison_square_garden +52 -0
  109. data/test/fixtures/mapquest_no_results +16 -0
  110. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  111. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  112. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  113. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  114. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  115. data/test/fixtures/maxmind_invalid_key +1 -0
  116. data/test/fixtures/maxmind_no_results +1 -0
  117. data/test/fixtures/nominatim_madison_square_garden +150 -0
  118. data/test/fixtures/nominatim_no_results +1 -0
  119. data/test/fixtures/ovi_madison_square_garden +72 -0
  120. data/test/fixtures/ovi_no_results +8 -0
  121. data/test/fixtures/yahoo_error +1 -0
  122. data/test/fixtures/yahoo_invalid_key +2 -0
  123. data/test/fixtures/yahoo_madison_square_garden +52 -0
  124. data/test/fixtures/yahoo_no_results +10 -0
  125. data/test/fixtures/yahoo_over_limit +2 -0
  126. data/test/fixtures/yandex_invalid_key +1 -0
  127. data/test/fixtures/yandex_kremlin +48 -0
  128. data/test/fixtures/yandex_no_city_and_town +112 -0
  129. data/test/fixtures/yandex_no_results +16 -0
  130. data/test/geocoder_test.rb +59 -0
  131. data/test/https_test.rb +16 -0
  132. data/test/integration/smoke_test.rb +26 -0
  133. data/test/lookup_test.rb +117 -0
  134. data/test/method_aliases_test.rb +25 -0
  135. data/test/mongoid_test.rb +46 -0
  136. data/test/mongoid_test_helper.rb +43 -0
  137. data/test/near_test.rb +61 -0
  138. data/test/oauth_util_test.rb +30 -0
  139. data/test/proxy_test.rb +36 -0
  140. data/test/query_test.rb +52 -0
  141. data/test/request_test.rb +29 -0
  142. data/test/result_test.rb +42 -0
  143. data/test/services_test.rb +393 -0
  144. data/test/test_helper.rb +289 -0
  145. data/test/test_mode_test.rb +59 -0
  146. metadata +213 -0
@@ -0,0 +1,389 @@
1
+ module Geocoder2
2
+ module Calculations
3
+ extend self
4
+
5
+ ##
6
+ # Compass point names, listed clockwise starting at North.
7
+ #
8
+ # If you want bearings named using more, fewer, or different points
9
+ # override Geocoder2::Calculations.COMPASS_POINTS with your own array.
10
+ #
11
+ COMPASS_POINTS = %w[N NE E SE S SW W NW]
12
+
13
+ ##
14
+ # Radius of the Earth, in kilometers.
15
+ # Value taken from: http://en.wikipedia.org/wiki/Earth_radius
16
+ #
17
+ EARTH_RADIUS = 6371.0
18
+
19
+ ##
20
+ # Conversion factor: multiply by kilometers to get miles.
21
+ #
22
+ KM_IN_MI = 0.621371192
23
+
24
+ ##
25
+ # Conversion factor: multiply by nautical miles to get miles.
26
+ #
27
+ KM_IN_NM = 0.539957
28
+
29
+ # Not a number constant
30
+ NAN = defined?(::Float::NAN) ? ::Float::NAN : 0 / 0.0
31
+
32
+ ##
33
+ # Returns true if all given arguments are valid latitude/longitude values.
34
+ #
35
+ def coordinates_present?(*args)
36
+ args.each do |a|
37
+ # note that Float::NAN != Float::NAN
38
+ # still, this could probably be improved:
39
+ return false if (!a.is_a?(Numeric) or a.to_s == "NaN")
40
+ end
41
+ true
42
+ end
43
+
44
+ ##
45
+ # Distance spanned by one degree of latitude in the given units.
46
+ #
47
+ def latitude_degree_distance(units = nil)
48
+ units ||= Geocoder2.config.units
49
+ 2 * Math::PI * earth_radius(units) / 360
50
+ end
51
+
52
+ ##
53
+ # Distance spanned by one degree of longitude at the given latitude.
54
+ # This ranges from around 69 miles at the equator to zero at the poles.
55
+ #
56
+ def longitude_degree_distance(latitude, units = nil)
57
+ units ||= Geocoder2.config.units
58
+ latitude_degree_distance(units) * Math.cos(to_radians(latitude))
59
+ end
60
+
61
+ ##
62
+ # Distance between two points on Earth (Haversine formula).
63
+ # Takes two points and an options hash.
64
+ # The points are given in the same way that points are given to all
65
+ # Geocoder2 methods that accept points as arguments. They can be:
66
+ #
67
+ # * an array of coordinates ([lat,lon])
68
+ # * a geocodable address (string)
69
+ # * a geocoded object (one which implements a +to_coordinates+ method
70
+ # which returns a [lat,lon] array
71
+ #
72
+ # The options hash supports:
73
+ #
74
+ # * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>
75
+ # Use Geocoder2.configure(:units => ...) to configure default units.
76
+ #
77
+ def distance_between(point1, point2, options = {})
78
+
79
+ # set default options
80
+ options[:units] ||= Geocoder2.config.units
81
+
82
+ # convert to coordinate arrays
83
+ point1 = extract_coordinates(point1)
84
+ point2 = extract_coordinates(point2)
85
+
86
+ # convert degrees to radians
87
+ point1 = to_radians(point1)
88
+ point2 = to_radians(point2)
89
+
90
+ # compute deltas
91
+ dlat = point2[0] - point1[0]
92
+ dlon = point2[1] - point1[1]
93
+
94
+ a = (Math.sin(dlat / 2))**2 + Math.cos(point1[0]) *
95
+ (Math.sin(dlon / 2))**2 * Math.cos(point2[0])
96
+ c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
97
+ c * earth_radius(options[:units])
98
+ end
99
+
100
+ ##
101
+ # Bearing between two points on Earth.
102
+ # Returns a number of degrees from due north (clockwise).
103
+ #
104
+ # See Geocoder2::Calculations.distance_between for
105
+ # ways of specifying the points. Also accepts an options hash:
106
+ #
107
+ # * <tt>:method</tt> - <tt>:linear</tt> or <tt>:spherical</tt>;
108
+ # the spherical method is "correct" in that it returns the shortest path
109
+ # (one along a great circle) but the linear method is less confusing
110
+ # (returns due east or west when given two points with the same latitude).
111
+ # Use Geocoder2.configure(:distances => ...) to configure calculation method.
112
+ #
113
+ # Based on: http://www.movable-type.co.uk/scripts/latlong.html
114
+ #
115
+ def bearing_between(point1, point2, options = {})
116
+
117
+ # set default options
118
+ options[:method] ||= Geocoder2.config.distances
119
+ options[:method] = :linear unless options[:method] == :spherical
120
+
121
+ # convert to coordinate arrays
122
+ point1 = extract_coordinates(point1)
123
+ point2 = extract_coordinates(point2)
124
+
125
+ # convert degrees to radians
126
+ point1 = to_radians(point1)
127
+ point2 = to_radians(point2)
128
+
129
+ # compute deltas
130
+ dlat = point2[0] - point1[0]
131
+ dlon = point2[1] - point1[1]
132
+
133
+ case options[:method]
134
+ when :linear
135
+ y = dlon
136
+ x = dlat
137
+
138
+ when :spherical
139
+ y = Math.sin(dlon) * Math.cos(point2[0])
140
+ x = Math.cos(point1[0]) * Math.sin(point2[0]) -
141
+ Math.sin(point1[0]) * Math.cos(point2[0]) * Math.cos(dlon)
142
+ end
143
+
144
+ bearing = Math.atan2(x,y)
145
+ # Answer is in radians counterclockwise from due east.
146
+ # Convert to degrees clockwise from due north:
147
+ (90 - to_degrees(bearing) + 360) % 360
148
+ end
149
+
150
+ ##
151
+ # Translate a bearing (float) into a compass direction (string, eg "North").
152
+ #
153
+ def compass_point(bearing, points = COMPASS_POINTS)
154
+ seg_size = 360 / points.size
155
+ points[((bearing + (seg_size / 2)) % 360) / seg_size]
156
+ end
157
+
158
+ ##
159
+ # Compute the geographic center (aka geographic midpoint, center of
160
+ # gravity) for an array of geocoded objects and/or [lat,lon] arrays
161
+ # (can be mixed). Any objects missing coordinates are ignored. Follows
162
+ # the procedure documented at http://www.geomidpoint.com/calculation.html.
163
+ #
164
+ def geographic_center(points)
165
+
166
+ # convert objects to [lat,lon] arrays and convert degrees to radians
167
+ coords = points.map{ |p| to_radians(extract_coordinates(p)) }
168
+
169
+ # convert to Cartesian coordinates
170
+ x = []; y = []; z = []
171
+ coords.each do |p|
172
+ x << Math.cos(p[0]) * Math.cos(p[1])
173
+ y << Math.cos(p[0]) * Math.sin(p[1])
174
+ z << Math.sin(p[0])
175
+ end
176
+
177
+ # compute average coordinate values
178
+ xa, ya, za = [x,y,z].map do |c|
179
+ c.inject(0){ |tot,i| tot += i } / c.size.to_f
180
+ end
181
+
182
+ # convert back to latitude/longitude
183
+ lon = Math.atan2(ya, xa)
184
+ hyp = Math.sqrt(xa**2 + ya**2)
185
+ lat = Math.atan2(za, hyp)
186
+
187
+ # return answer in degrees
188
+ to_degrees [lat, lon]
189
+ end
190
+
191
+ ##
192
+ # Returns coordinates of the southwest and northeast corners of a box
193
+ # with the given point at its center. The radius is the shortest distance
194
+ # from the center point to any side of the box (the length of each side
195
+ # is twice the radius).
196
+ #
197
+ # This is useful for finding corner points of a map viewport, or for
198
+ # roughly limiting the possible solutions in a geo-spatial search
199
+ # (ActiveRecord queries use it thusly).
200
+ #
201
+ # See Geocoder2::Calculations.distance_between for
202
+ # ways of specifying the point. Also accepts an options hash:
203
+ #
204
+ # * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>.
205
+ # Use Geocoder2.configure(:units => ...) to configure default units.
206
+ #
207
+ def bounding_box(point, radius, options = {})
208
+ lat,lon = extract_coordinates(point)
209
+ radius = radius.to_f
210
+ units = options[:units] || Geocoder2.config.units
211
+ [
212
+ lat - (radius / latitude_degree_distance(units)),
213
+ lon - (radius / longitude_degree_distance(lat, units)),
214
+ lat + (radius / latitude_degree_distance(units)),
215
+ lon + (radius / longitude_degree_distance(lat, units))
216
+ ]
217
+ end
218
+
219
+ ##
220
+ # Random point within a circle of provided radius centered
221
+ # around the provided point
222
+ # Takes one point, one radius, and an options hash.
223
+ # The points are given in the same way that points are given to all
224
+ # Geocoder2 methods that accept points as arguments. They can be:
225
+ #
226
+ # * an array of coordinates ([lat,lon])
227
+ # * a geocodable address (string)
228
+ # * a geocoded object (one which implements a +to_coordinates+ method
229
+ # which returns a [lat,lon] array
230
+ #
231
+ # The options hash supports:
232
+ #
233
+ # * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>
234
+ # Use Geocoder2.configure(:units => ...) to configure default units.
235
+ def random_point_near(center, radius, options = {})
236
+
237
+ # set default options
238
+ options[:units] ||= Geocoder2.config.units
239
+
240
+ # convert to coordinate arrays
241
+ center = extract_coordinates(center)
242
+
243
+ earth_circumference = 2 * Math::PI * earth_radius(options[:units])
244
+ max_degree_delta = 360.0 * (radius / earth_circumference)
245
+
246
+ # random bearing in radians
247
+ theta = 2 * Math::PI * rand
248
+
249
+ # random radius, use the square root to ensure a uniform
250
+ # distribution of points over the circle
251
+ r = Math.sqrt(rand) * max_degree_delta
252
+
253
+ delta_lat, delta_long = [r * Math.cos(theta), r * Math.sin(theta)]
254
+ [center[0] + delta_lat, center[1] + delta_long]
255
+ end
256
+
257
+ ##
258
+ # Convert degrees to radians.
259
+ # If an array (or multiple arguments) is passed,
260
+ # converts each value and returns array.
261
+ #
262
+ def to_radians(*args)
263
+ args = args.first if args.first.is_a?(Array)
264
+ if args.size == 1
265
+ args.first * (Math::PI / 180)
266
+ else
267
+ args.map{ |i| to_radians(i) }
268
+ end
269
+ end
270
+
271
+ ##
272
+ # Convert radians to degrees.
273
+ # If an array (or multiple arguments) is passed,
274
+ # converts each value and returns array.
275
+ #
276
+ def to_degrees(*args)
277
+ args = args.first if args.first.is_a?(Array)
278
+ if args.size == 1
279
+ (args.first * 180.0) / Math::PI
280
+ else
281
+ args.map{ |i| to_degrees(i) }
282
+ end
283
+ end
284
+
285
+ def distance_to_radians(distance, units = nil)
286
+ units ||= Geocoder2.config.units
287
+ distance.to_f / earth_radius(units)
288
+ end
289
+
290
+ def radians_to_distance(radians, units = nil)
291
+ units ||= Geocoder2.config.units
292
+ radians * earth_radius(units)
293
+ end
294
+
295
+ ##
296
+ # Convert miles to kilometers.
297
+ #
298
+ def to_kilometers(mi)
299
+ mi * mi_in_km
300
+ end
301
+
302
+ ##
303
+ # Convert kilometers to miles.
304
+ #
305
+ def to_miles(km)
306
+ km * km_in_mi
307
+ end
308
+
309
+ ##
310
+ # Convert kilometers to nautical miles.
311
+ #
312
+ def to_nautical_miles(km)
313
+ km * km_in_nm
314
+ end
315
+
316
+ ##
317
+ # Radius of the Earth in the given units (:mi or :km).
318
+ # Use Geocoder2.configure(:units => ...) to configure default units.
319
+ #
320
+ def earth_radius(units = nil)
321
+ units ||= Geocoder2.config.units
322
+ case units
323
+ when :km; EARTH_RADIUS
324
+ when :mi; to_miles(EARTH_RADIUS)
325
+ when :nm; to_nautical_miles(EARTH_RADIUS)
326
+ end
327
+ end
328
+
329
+ ##
330
+ # Conversion factor: km to mi.
331
+ #
332
+ def km_in_mi
333
+ KM_IN_MI
334
+ end
335
+
336
+ ##
337
+ # Conversion factor: km to nm.
338
+ #
339
+ def km_in_nm
340
+ KM_IN_NM
341
+ end
342
+
343
+
344
+
345
+ ##
346
+ # Conversion factor: mi to km.
347
+ #
348
+ def mi_in_km
349
+ 1.0 / KM_IN_MI
350
+ end
351
+
352
+ ##
353
+ # Conversion factor: nm to km.
354
+ #
355
+ def nm_in_km
356
+ 1.0 / KM_IN_NM
357
+ end
358
+
359
+ ##
360
+ # Takes an object which is a [lat,lon] array, a geocodable string,
361
+ # or an object that implements +to_coordinates+ and returns a
362
+ # [lat,lon] array. Note that if a string is passed this may be a slow-
363
+ # running method and may return nil.
364
+ #
365
+ def extract_coordinates(point)
366
+ case point
367
+ when Array
368
+ if point.size == 2
369
+ lat, lon = point
370
+ if !lat.nil? && lat.respond_to?(:to_f) and
371
+ !lon.nil? && lon.respond_to?(:to_f)
372
+ then
373
+ return [ lat.to_f, lon.to_f ]
374
+ end
375
+ end
376
+ when String
377
+ point = Geocoder2.coordinates(point) and return point
378
+ else
379
+ if point.respond_to?(:to_coordinates)
380
+ if Array === array = point.to_coordinates
381
+ return extract_coordinates(array)
382
+ end
383
+ end
384
+ end
385
+ [ NAN, NAN ]
386
+ end
387
+ end
388
+ end
389
+
@@ -0,0 +1,121 @@
1
+ require 'geocoder2'
2
+ require 'optparse'
3
+
4
+ module Geocoder2
5
+ class Cli
6
+
7
+ def self.run(args, out = STDOUT)
8
+ show_url = false
9
+ show_json = false
10
+
11
+ # remove arguments that are probably coordinates so they are not
12
+ # processed as arguments (eg: -31.96047031,115.84274631)
13
+ coords = args.select{ |i| i.match(/^-\d/) }
14
+ args -= coords
15
+
16
+ OptionParser.new{ |opts|
17
+ opts.banner = "Usage:\n geocode [options] <location>"
18
+ opts.separator "\nOptions: "
19
+
20
+ opts.on("-k <key>", "--key <key>",
21
+ "Key for geocoding API (usually optional). Enclose multi-part keys in quotes and separate parts by spaces") do |key|
22
+ if (key_parts = key.split(/\s+/)).size > 1
23
+ Geocoder2.configure(:api_key => key_parts)
24
+ else
25
+ Geocoder2.configure(:api_key => key)
26
+ end
27
+ end
28
+
29
+ opts.on("-l <language>", "--language <language>",
30
+ "Language of output (see API docs for valid choices)") do |language|
31
+ Geocoder2.configure(:language => language)
32
+ end
33
+
34
+ opts.on("-p <proxy>", "--proxy <proxy>",
35
+ "HTTP proxy server to use (user:pass@host:port)") do |proxy|
36
+ Geocoder2.configure(:http_proxy => proxy)
37
+ end
38
+
39
+ opts.on("-s <service>", Geocoder2::Lookup.all_services_except_test, "--service <service>",
40
+ "Geocoding service: #{Geocoder2::Lookup.all_services_except_test * ', '}") do |service|
41
+ Geocoder2.configure(:lookup => service.to_sym)
42
+ Geocoder2.configure(:ip_lookup => service.to_sym)
43
+ end
44
+
45
+ opts.on("-t <seconds>", "--timeout <seconds>",
46
+ "Maximum number of seconds to wait for API response") do |timeout|
47
+ Geocoder2.configure(:timeout => timeout.to_i)
48
+ end
49
+
50
+ opts.on("-j", "--json", "Print API's raw JSON response") do
51
+ show_json = true
52
+ end
53
+
54
+ opts.on("-u", "--url", "Print URL for API query instead of result") do
55
+ show_url = true
56
+ end
57
+
58
+ opts.on_tail("-v", "--version", "Print version number") do
59
+ require "geocoder2/version"
60
+ out << "Geocoder2 #{Geocoder2::VERSION}\n"
61
+ exit
62
+ end
63
+
64
+ opts.on_tail("-h", "--help", "Print this help") do
65
+ out << "Look up geographic information about a location.\n\n"
66
+ out << opts
67
+ out << "\nCreated and maintained by Alex Reisner, available under the MIT License.\n"
68
+ out << "Report bugs and contribute at http://github.com/alexreisner/geocoder2\n"
69
+ exit
70
+ end
71
+ }.parse!(args)
72
+
73
+ # concatenate args with coords that might have been removed
74
+ # before option processing
75
+ query = (args + coords).join(" ")
76
+
77
+ if query == ""
78
+ out << "Please specify a location (run `geocode -h` for more info).\n"
79
+ exit 1
80
+ end
81
+
82
+ if show_url and show_json
83
+ out << "You can only specify one of -j and -u.\n"
84
+ exit 2
85
+ end
86
+
87
+ if show_url
88
+ q = Geocoder2::Query.new(query)
89
+ out << q.url + "\n"
90
+ exit 0
91
+ end
92
+
93
+ if show_json
94
+ q = Geocoder2::Query.new(query)
95
+ out << q.lookup.send(:fetch_raw_data, q) + "\n"
96
+ exit 0
97
+ end
98
+
99
+ if (result = Geocoder2.search(query).first)
100
+ google = Geocoder2::Lookup.get(:google)
101
+ lines = [
102
+ ["Latitude", result.latitude],
103
+ ["Longitude", result.longitude],
104
+ ["Full address", result.address],
105
+ ["City", result.city],
106
+ ["State/province", result.state],
107
+ ["Postal code", result.postal_code],
108
+ ["Country", result.country],
109
+ ["Google map", google.map_link_url(result.coordinates)],
110
+ ]
111
+ lines.each do |line|
112
+ out << (line[0] + ": ").ljust(18) + line[1].to_s + "\n"
113
+ end
114
+ exit 0
115
+ else
116
+ out << "Location '#{query}' not found.\n"
117
+ exit 1
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,130 @@
1
+ require 'singleton'
2
+ require 'geocoder2/configuration_hash'
3
+
4
+ module Geocoder2
5
+
6
+ ##
7
+ # Configuration options should be set by passing a hash:
8
+ #
9
+ # Geocoder2.configure(
10
+ # :timeout => 5,
11
+ # :lookup => :yandex,
12
+ # :api_key => "2a9fsa983jaslfj982fjasd",
13
+ # :units => :km
14
+ # )
15
+ #
16
+ def self.configure(options = nil, &block)
17
+ if block_given?
18
+ warn "WARNING: Passing a block to Geocoder2.configure is DEPRECATED. Please pass a hash instead (eg: Geocoder2.configure(:units => ..., :api_key => ...))."
19
+ block.call(Configuration.instance)
20
+ elsif !options.nil?
21
+ Configuration.instance.configure(options)
22
+ else
23
+ warn "WARNING: Use of Geocoder2.configure to read or write single config options is DEPRECATED. To write to the config please pass a hash (eg: Geocoder2.configure(:units => ...)). To read config options please use the Geocoder2.config object (eg: Geocoder2.config.units)."
24
+ Configuration.instance
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Read-only access to the singleton's config data.
30
+ #
31
+ def self.config
32
+ Configuration.instance.data
33
+ end
34
+
35
+ ##
36
+ # Read-only access to lookup-specific config data.
37
+ #
38
+ def self.config_for_lookup(lookup_name)
39
+ data = config.clone
40
+ data.reject!{ |key,value| !Configuration::OPTIONS.include?(key) }
41
+ if config.has_key?(lookup_name)
42
+ data.merge!(config[lookup_name])
43
+ end
44
+ data
45
+ end
46
+
47
+ class Configuration
48
+ include Singleton
49
+
50
+ OPTIONS = [
51
+ :timeout,
52
+ :lookup,
53
+ :ip_lookup,
54
+ :language,
55
+ :http_headers,
56
+ :use_https,
57
+ :http_proxy,
58
+ :https_proxy,
59
+ :api_key,
60
+ :cache,
61
+ :cache_prefix,
62
+ :always_raise,
63
+ :units,
64
+ :distances
65
+ ]
66
+
67
+ attr_accessor :data
68
+
69
+ def self.set_defaults
70
+ instance.set_defaults
71
+ end
72
+
73
+ OPTIONS.each do |o|
74
+ define_method o do
75
+ @data[o]
76
+ end
77
+ define_method "#{o}=" do |value|
78
+ @data[o] = value
79
+ end
80
+ end
81
+
82
+ def configure(options)
83
+ @data.rmerge!(options)
84
+ end
85
+
86
+ def initialize # :nodoc
87
+ @data = Geocoder2::ConfigurationHash.new
88
+ set_defaults
89
+ end
90
+
91
+ def set_defaults
92
+
93
+ # geocoding options
94
+ @data[:timeout] = 3 # geocoding service timeout (secs)
95
+ @data[:lookup] = :google # name of street address geocoding service (symbol)
96
+ @data[:ip_lookup] = :freegeoip # name of IP address geocoding service (symbol)
97
+ @data[:language] = :en # ISO-639 language code
98
+ @data[:http_headers] = {} # HTTP headers for lookup
99
+ @data[:use_https] = false # use HTTPS for lookup requests? (if supported)
100
+ @data[:http_proxy] = nil # HTTP proxy server (user:pass@host:port)
101
+ @data[:https_proxy] = nil # HTTPS proxy server (user:pass@host:port)
102
+ @data[:api_key] = nil # API key for geocoding service
103
+ @data[:cache] = nil # cache object (must respond to #[], #[]=, and #keys)
104
+ @data[:cache_prefix] = "geocoder2:" # prefix (string) to use for all cache keys
105
+
106
+ # exceptions that should not be rescued by default
107
+ # (if you want to implement custom error handling);
108
+ # supports SocketError and TimeoutError
109
+ @data[:always_raise] = []
110
+
111
+ # calculation options
112
+ @data[:units] = :mi # :mi or :km
113
+ @data[:distances] = :linear # :linear or :spherical
114
+ end
115
+
116
+ instance_eval(OPTIONS.map do |option|
117
+ o = option.to_s
118
+ <<-EOS
119
+ def #{o}
120
+ instance.data[:#{o}]
121
+ end
122
+
123
+ def #{o}=(value)
124
+ instance.data[:#{o}] = value
125
+ end
126
+ EOS
127
+ end.join("\n\n"))
128
+
129
+ end
130
+ end
@@ -0,0 +1,11 @@
1
+ require 'hash_recursive_merge'
2
+
3
+ module Geocoder2
4
+ class ConfigurationHash < Hash
5
+ include HashRecursiveMerge
6
+
7
+ def method_missing(meth, *args, &block)
8
+ has_key?(meth) ? self[meth] : super
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module Geocoder2
2
+
3
+ class Error < StandardError
4
+ end
5
+
6
+ class ConfigurationError < Error
7
+ end
8
+
9
+ class OverQueryLimitError < Error
10
+ end
11
+
12
+ class RequestDenied < Error
13
+ end
14
+
15
+ class InvalidRequest < Error
16
+ end
17
+
18
+ class InvalidApiKey < Error
19
+ end
20
+
21
+ end