geocoder 1.1.4 → 1.1.8
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.
- data/.travis.yml +4 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +120 -39
- data/README.md +200 -54
- data/examples/autoexpire_cache_dalli.rb +62 -0
- data/examples/{autoexpire_cache.rb → autoexpire_cache_redis.rb} +4 -6
- data/lib/generators/geocoder/config/templates/initializer.rb +19 -23
- data/lib/geocoder/cache.rb +6 -1
- data/lib/geocoder/calculations.rb +45 -13
- data/lib/geocoder/cli.rb +10 -11
- data/lib/geocoder/configuration.rb +67 -43
- data/lib/geocoder/configuration_hash.rb +11 -0
- data/lib/geocoder/exceptions.rb +3 -0
- data/lib/geocoder/lookup.rb +3 -1
- data/lib/geocoder/lookups/base.rb +89 -27
- data/lib/geocoder/lookups/bing.rb +21 -11
- data/lib/geocoder/lookups/esri.rb +48 -0
- data/lib/geocoder/lookups/freegeoip.rb +8 -4
- data/lib/geocoder/lookups/geocoder_ca.rb +11 -7
- data/lib/geocoder/lookups/google.rb +18 -9
- data/lib/geocoder/lookups/google_premier.rb +16 -8
- data/lib/geocoder/lookups/mapquest.rb +33 -5
- data/lib/geocoder/lookups/maxmind.rb +88 -0
- data/lib/geocoder/lookups/nominatim.rb +13 -8
- data/lib/geocoder/lookups/ovi.rb +52 -0
- data/lib/geocoder/lookups/test.rb +10 -0
- data/lib/geocoder/lookups/yahoo.rb +51 -38
- data/lib/geocoder/lookups/yandex.rb +17 -9
- data/lib/geocoder/models/mongoid.rb +1 -1
- data/lib/geocoder/query.rb +20 -5
- data/lib/geocoder/request.rb +7 -1
- data/lib/geocoder/results/base.rb +8 -2
- data/lib/geocoder/results/esri.rb +51 -0
- data/lib/geocoder/results/google.rb +34 -0
- data/lib/geocoder/results/mapquest.rb +46 -2
- data/lib/geocoder/results/maxmind.rb +135 -0
- data/lib/geocoder/results/nominatim.rb +0 -10
- data/lib/geocoder/results/ovi.rb +62 -0
- data/lib/geocoder/results/test.rb +2 -1
- data/lib/geocoder/results/yahoo.rb +9 -2
- data/lib/geocoder/results/yandex.rb +12 -2
- data/lib/geocoder/sql.rb +6 -4
- data/lib/geocoder/stores/active_record.rb +57 -30
- data/lib/geocoder/stores/base.rb +3 -2
- data/lib/geocoder/stores/mongo_base.rb +2 -1
- data/lib/geocoder/version.rb +1 -1
- data/lib/geocoder.rb +2 -4
- data/lib/hash_recursive_merge.rb +74 -0
- data/lib/oauth_util.rb +112 -0
- data/test/cache_test.rb +19 -0
- data/test/calculations_test.rb +10 -4
- data/test/configuration_test.rb +26 -51
- data/test/error_handling_test.rb +8 -4
- data/test/fixtures/bing_invalid_key +1 -0
- data/test/fixtures/esri_madison_square_garden +59 -0
- data/test/fixtures/esri_no_results +8 -0
- data/test/fixtures/esri_reverse +21 -0
- data/test/fixtures/mapquest_madison_square_garden +52 -0
- data/test/fixtures/mapquest_no_results +7 -0
- data/test/fixtures/maxmind_24_24_24_21 +1 -0
- data/test/fixtures/maxmind_24_24_24_22 +1 -0
- data/test/fixtures/maxmind_24_24_24_23 +1 -0
- data/test/fixtures/maxmind_24_24_24_24 +1 -0
- data/test/fixtures/maxmind_74_200_247_59 +1 -0
- data/test/fixtures/maxmind_invalid_key +1 -0
- data/test/fixtures/maxmind_no_results +1 -0
- data/test/fixtures/ovi_madison_square_garden +72 -0
- data/test/fixtures/ovi_no_results +8 -0
- data/test/fixtures/yahoo_error +1 -0
- data/test/fixtures/yahoo_invalid_key +2 -0
- data/test/fixtures/yahoo_madison_square_garden +52 -0
- data/test/fixtures/yahoo_no_results +10 -0
- data/test/fixtures/yahoo_over_limit +2 -0
- data/test/fixtures/yandex_no_city_and_town +112 -0
- data/test/https_test.rb +3 -3
- data/test/integration/smoke_test.rb +2 -2
- data/test/lookup_test.rb +84 -11
- data/test/near_test.rb +43 -0
- data/test/oauth_util_test.rb +30 -0
- data/test/proxy_test.rb +2 -2
- data/test/query_test.rb +13 -0
- data/test/request_test.rb +29 -0
- data/test/result_test.rb +12 -2
- data/test/services_test.rb +208 -57
- data/test/test_helper.rb +66 -100
- data/test/test_mode_test.rb +35 -26
- metadata +58 -35
- data/test/fixtures/mapquest_madison_square_garden.json +0 -27
- data/test/fixtures/mapquest_no_results.json +0 -1
- data/test/fixtures/yahoo_madison_square_garden.json +0 -52
- data/test/fixtures/yahoo_no_results.json +0 -10
- data/test/fixtures/yahoo_v1_madison_square_garden.json +0 -46
- data/test/fixtures/yahoo_v1_no_results.json +0 -10
- /data/test/fixtures/{bing_madison_square_garden.json → bing_madison_square_garden} +0 -0
- /data/test/fixtures/{bing_no_results.json → bing_no_results} +0 -0
- /data/test/fixtures/{bing_reverse.json → bing_reverse} +0 -0
- /data/test/fixtures/{freegeoip_74_200_247_59.json → freegeoip_74_200_247_59} +0 -0
- /data/test/fixtures/{freegeoip_no_results.json → freegeoip_no_results} +0 -0
- /data/test/fixtures/{geocoder_ca_madison_square_garden.json → geocoder_ca_madison_square_garden} +0 -0
- /data/test/fixtures/{geocoder_ca_no_results.json → geocoder_ca_no_results} +0 -0
- /data/test/fixtures/{geocoder_ca_reverse.json → geocoder_ca_reverse} +0 -0
- /data/test/fixtures/{google_garbage.json → google_garbage} +0 -0
- /data/test/fixtures/{google_madison_square_garden.json → google_madison_square_garden} +0 -0
- /data/test/fixtures/{google_no_city_data.json → google_no_city_data} +0 -0
- /data/test/fixtures/{google_no_locality.json → google_no_locality} +0 -0
- /data/test/fixtures/{google_no_results.json → google_no_results} +0 -0
- /data/test/fixtures/{nominatim_madison_square_garden.json → nominatim_madison_square_garden} +0 -0
- /data/test/fixtures/{nominatim_no_results.json → nominatim_no_results} +0 -0
- /data/test/fixtures/{yandex_invalid_key.json → yandex_invalid_key} +0 -0
- /data/test/fixtures/{yandex_kremlin.json → yandex_kremlin} +0 -0
- /data/test/fixtures/{yandex_no_results.json → yandex_no_results} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
require 'geocoder/sql'
|
|
2
3
|
require 'geocoder/stores/base'
|
|
3
4
|
|
|
@@ -17,13 +18,15 @@ module Geocoder::Store
|
|
|
17
18
|
|
|
18
19
|
# scope: geocoded objects
|
|
19
20
|
scope :geocoded, lambda {
|
|
20
|
-
|
|
21
|
-
"AND #{geocoder_options[:longitude]} IS NOT NULL"
|
|
21
|
+
where("#{geocoder_options[:latitude]} IS NOT NULL " +
|
|
22
|
+
"AND #{geocoder_options[:longitude]} IS NOT NULL")
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
# scope: not-geocoded objects
|
|
24
26
|
scope :not_geocoded, lambda {
|
|
25
|
-
|
|
26
|
-
"OR #{geocoder_options[:longitude]} IS NULL"
|
|
27
|
+
where("#{geocoder_options[:latitude]} IS NULL " +
|
|
28
|
+
"OR #{geocoder_options[:longitude]} IS NULL")
|
|
29
|
+
}
|
|
27
30
|
|
|
28
31
|
##
|
|
29
32
|
# Find all objects within a radius of the given location.
|
|
@@ -35,7 +38,9 @@ module Geocoder::Store
|
|
|
35
38
|
scope :near, lambda{ |location, *args|
|
|
36
39
|
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
|
|
37
40
|
if Geocoder::Calculations.coordinates_present?(latitude, longitude)
|
|
38
|
-
near_scope_options(latitude, longitude, *args)
|
|
41
|
+
options = near_scope_options(latitude, longitude, *args)
|
|
42
|
+
select(options[:select]).where(options[:conditions]).
|
|
43
|
+
order(options[:order])
|
|
39
44
|
else
|
|
40
45
|
# If no lat/lon given we don't want any results, but we still
|
|
41
46
|
# need distance and bearing columns so you can add, for example:
|
|
@@ -53,11 +58,11 @@ module Geocoder::Store
|
|
|
53
58
|
scope :within_bounding_box, lambda{ |bounds|
|
|
54
59
|
sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
|
|
55
60
|
if sw_lat && sw_lng && ne_lat && ne_lng
|
|
56
|
-
|
|
61
|
+
where(Geocoder::Sql.within_bounding_box(
|
|
57
62
|
sw_lat, sw_lng, ne_lat, ne_lng,
|
|
58
63
|
full_column_name(geocoder_options[:latitude]),
|
|
59
64
|
full_column_name(geocoder_options[:longitude])
|
|
60
|
-
)
|
|
65
|
+
))
|
|
61
66
|
else
|
|
62
67
|
select(select_clause(nil, "NULL", "NULL")).where(false_condition)
|
|
63
68
|
end
|
|
@@ -87,33 +92,47 @@ module Geocoder::Store
|
|
|
87
92
|
# * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
|
|
88
93
|
# for interpreting radius as well as the +distance+ attribute which
|
|
89
94
|
# is added to each found nearby object.
|
|
90
|
-
#
|
|
95
|
+
# Use Geocoder.configure[:units] to configure default units.
|
|
91
96
|
# * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
|
|
92
97
|
# the method to be used for calculating the bearing (direction)
|
|
93
98
|
# between the given point and each found nearby point;
|
|
94
|
-
# set to false for no bearing calculation.
|
|
95
|
-
#
|
|
96
|
-
# * +:select+
|
|
97
|
-
# * +:
|
|
98
|
-
#
|
|
99
|
-
# * +:
|
|
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)
|
|
100
108
|
#
|
|
101
109
|
def near_scope_options(latitude, longitude, radius = 20, options = {})
|
|
102
|
-
options[:units]
|
|
110
|
+
if options[:units]
|
|
111
|
+
options[:units] = options[:units].to_sym
|
|
112
|
+
end
|
|
113
|
+
options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
|
|
114
|
+
select_distance = options.fetch(:select_distance, true)
|
|
115
|
+
options[:order] = "" if !select_distance && !options.include?(:order)
|
|
116
|
+
select_bearing = options.fetch(:select_bearing, true)
|
|
103
117
|
bearing = bearing_sql(latitude, longitude, options)
|
|
104
118
|
distance = distance_sql(latitude, longitude, options)
|
|
119
|
+
|
|
120
|
+
b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
|
|
121
|
+
args = b + [
|
|
122
|
+
full_column_name(geocoder_options[:latitude]),
|
|
123
|
+
full_column_name(geocoder_options[:longitude])
|
|
124
|
+
]
|
|
125
|
+
bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
|
|
126
|
+
|
|
105
127
|
if using_sqlite?
|
|
106
|
-
|
|
107
|
-
args = b + [
|
|
108
|
-
full_column_name(geocoder_options[:latitude]),
|
|
109
|
-
full_column_name(geocoder_options[:longitude])
|
|
110
|
-
]
|
|
111
|
-
conditions = Geocoder::Sql.within_bounding_box(*args)
|
|
128
|
+
conditions = bounding_box_conditions
|
|
112
129
|
else
|
|
113
|
-
conditions = ["#{distance} <= ?", radius]
|
|
130
|
+
conditions = [bounding_box_conditions + " AND #{distance} <= ?", radius]
|
|
114
131
|
end
|
|
115
132
|
{
|
|
116
|
-
:select => select_clause(options[:select],
|
|
133
|
+
:select => select_clause(options[:select],
|
|
134
|
+
select_distance ? distance : nil,
|
|
135
|
+
select_bearing ? bearing : nil),
|
|
117
136
|
:conditions => add_exclude_condition(conditions, options[:exclude]),
|
|
118
137
|
:order => options.include?(:order) ? options[:order] : "distance ASC"
|
|
119
138
|
}
|
|
@@ -140,7 +159,7 @@ module Geocoder::Store
|
|
|
140
159
|
#
|
|
141
160
|
def bearing_sql(latitude, longitude, options = {})
|
|
142
161
|
if !options.include?(:bearing)
|
|
143
|
-
options[:bearing] = Geocoder
|
|
162
|
+
options[:bearing] = Geocoder.config.distances
|
|
144
163
|
end
|
|
145
164
|
if options[:bearing]
|
|
146
165
|
method_prefix = using_sqlite? ? "approx" : "full"
|
|
@@ -157,14 +176,23 @@ module Geocoder::Store
|
|
|
157
176
|
##
|
|
158
177
|
# Generate the SELECT clause.
|
|
159
178
|
#
|
|
160
|
-
def select_clause(columns, distance, bearing = nil)
|
|
161
|
-
if columns == :
|
|
179
|
+
def select_clause(columns, distance = nil, bearing = nil)
|
|
180
|
+
if columns == :id_only
|
|
181
|
+
return full_column_name(primary_key)
|
|
182
|
+
elsif columns == :geo_only
|
|
162
183
|
clause = ""
|
|
163
184
|
else
|
|
164
|
-
clause = (columns || full_column_name("*"))
|
|
185
|
+
clause = (columns || full_column_name("*"))
|
|
186
|
+
end
|
|
187
|
+
if distance
|
|
188
|
+
clause += ", " unless clause.empty?
|
|
189
|
+
clause += "#{distance} AS distance"
|
|
165
190
|
end
|
|
166
|
-
|
|
167
|
-
|
|
191
|
+
if bearing
|
|
192
|
+
clause += ", " unless clause.empty?
|
|
193
|
+
clause += "#{bearing} AS bearing"
|
|
194
|
+
end
|
|
195
|
+
clause
|
|
168
196
|
end
|
|
169
197
|
|
|
170
198
|
##
|
|
@@ -236,4 +264,3 @@ module Geocoder::Store
|
|
|
236
264
|
alias_method :fetch_address, :reverse_geocode
|
|
237
265
|
end
|
|
238
266
|
end
|
|
239
|
-
|
data/lib/geocoder/stores/base.rb
CHANGED
|
@@ -58,10 +58,11 @@ module Geocoder
|
|
|
58
58
|
##
|
|
59
59
|
# Get nearby geocoded objects.
|
|
60
60
|
# Takes the same options hash as the near class method (scope).
|
|
61
|
+
# Returns nil if the object is not geocoded.
|
|
61
62
|
#
|
|
62
63
|
def nearbys(radius = 20, options = {})
|
|
63
|
-
return
|
|
64
|
-
options.merge!(:exclude => self)
|
|
64
|
+
return nil unless geocoded?
|
|
65
|
+
options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
|
|
65
66
|
self.class.near(self, radius, options)
|
|
66
67
|
end
|
|
67
68
|
|
|
@@ -24,7 +24,8 @@ module Geocoder::Store
|
|
|
24
24
|
|
|
25
25
|
# Use BSON::OrderedHash if Ruby's hashes are unordered.
|
|
26
26
|
# Conditions must be in order required by indexes (see mongo gem).
|
|
27
|
-
|
|
27
|
+
version = RUBY_VERSION.split('.').map { |i| i.to_i }
|
|
28
|
+
empty = version[0] < 2 && version[1] < 9 ? BSON::OrderedHash.new : {}
|
|
28
29
|
|
|
29
30
|
conds = empty.clone
|
|
30
31
|
field = geocoder_options[:coordinates]
|
data/lib/geocoder/version.rb
CHANGED
data/lib/geocoder.rb
CHANGED
|
@@ -43,10 +43,8 @@ module Geocoder
|
|
|
43
43
|
# The working Cache object, or +nil+ if none configured.
|
|
44
44
|
#
|
|
45
45
|
def cache
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
49
|
-
@cache
|
|
46
|
+
warn "WARNING: Calling Geocoder.cache is DEPRECATED. The #cache method now belongs to the Geocoder::Lookup object."
|
|
47
|
+
Geocoder::Lookup.get(Geocoder.config.lookup).cache
|
|
50
48
|
end
|
|
51
49
|
end
|
|
52
50
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#
|
|
2
|
+
# = Hash Recursive Merge
|
|
3
|
+
#
|
|
4
|
+
# Merges a Ruby Hash recursively, Also known as deep merge.
|
|
5
|
+
# Recursive version of Hash#merge and Hash#merge!.
|
|
6
|
+
#
|
|
7
|
+
# Category:: Ruby
|
|
8
|
+
# Package:: Hash
|
|
9
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
|
10
|
+
# Copyright:: 2007-2008 The Authors
|
|
11
|
+
# License:: MIT License
|
|
12
|
+
# Link:: http://www.simonecarletti.com/
|
|
13
|
+
# Source:: http://gist.github.com/gists/6391/
|
|
14
|
+
#
|
|
15
|
+
module HashRecursiveMerge
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Recursive version of Hash#merge!
|
|
19
|
+
#
|
|
20
|
+
# Adds the contents of +other_hash+ to +hsh+,
|
|
21
|
+
# merging entries in +hsh+ with duplicate keys with those from +other_hash+.
|
|
22
|
+
#
|
|
23
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
|
24
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
|
25
|
+
# it merges and returns the values from both arrays.
|
|
26
|
+
#
|
|
27
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
|
28
|
+
# h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
|
|
29
|
+
# h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
|
30
|
+
#
|
|
31
|
+
# Simply using Hash#merge! would return
|
|
32
|
+
#
|
|
33
|
+
# h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
|
34
|
+
#
|
|
35
|
+
def rmerge!(other_hash)
|
|
36
|
+
merge!(other_hash) do |key, oldval, newval|
|
|
37
|
+
oldval.class == self.class ? oldval.rmerge!(newval) : newval
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# Recursive version of Hash#merge
|
|
43
|
+
#
|
|
44
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
|
45
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
|
46
|
+
# it merges and returns the values from both arrays.
|
|
47
|
+
#
|
|
48
|
+
# Compared with Hash#merge, this method provides a different approch
|
|
49
|
+
# for merging nasted hashes.
|
|
50
|
+
# If the value of a given key is an Hash and both +other_hash+ abd +hsh
|
|
51
|
+
# includes the same key, the value is merged instead replaced with
|
|
52
|
+
# +other_hash+ value.
|
|
53
|
+
#
|
|
54
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
|
55
|
+
# h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
|
|
56
|
+
# h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
|
57
|
+
#
|
|
58
|
+
# Simply using Hash#merge would return
|
|
59
|
+
#
|
|
60
|
+
# h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
|
61
|
+
#
|
|
62
|
+
def rmerge(other_hash)
|
|
63
|
+
r = {}
|
|
64
|
+
merge(other_hash) do |key, oldval, newval|
|
|
65
|
+
r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Hash
|
|
73
|
+
include HashRecursiveMerge
|
|
74
|
+
end
|
data/lib/oauth_util.rb
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# A utility for signing an url using OAuth in a way that's convenient for debugging
|
|
2
|
+
# Note: the standard Ruby OAuth lib is here http://github.com/mojodna/oauth
|
|
3
|
+
# Source: http://gist.github.com/383159
|
|
4
|
+
# License: http://gist.github.com/375593
|
|
5
|
+
# Usage: see example.rb below
|
|
6
|
+
#
|
|
7
|
+
# NOTE: This file has been modified from the original Gist:
|
|
8
|
+
#
|
|
9
|
+
# 1. Fix to prevent param-array conversion, as mentioned in Gist comment.
|
|
10
|
+
# 2. Query string escaping has been changed. See:
|
|
11
|
+
# https://github.com/alexreisner/geocoder/pull/360
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
require 'uri'
|
|
15
|
+
require 'cgi'
|
|
16
|
+
require 'openssl'
|
|
17
|
+
require 'base64'
|
|
18
|
+
|
|
19
|
+
class OauthUtil
|
|
20
|
+
|
|
21
|
+
attr_accessor :consumer_key, :consumer_secret, :token, :token_secret, :req_method,
|
|
22
|
+
:sig_method, :oauth_version, :callback_url, :params, :req_url, :base_str
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@consumer_key = ''
|
|
26
|
+
@consumer_secret = ''
|
|
27
|
+
@token = ''
|
|
28
|
+
@token_secret = ''
|
|
29
|
+
@req_method = 'GET'
|
|
30
|
+
@sig_method = 'HMAC-SHA1'
|
|
31
|
+
@oauth_version = '1.0'
|
|
32
|
+
@callback_url = ''
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# openssl::random_bytes returns non-word chars, which need to be removed. using alt method to get length
|
|
36
|
+
# ref http://snippets.dzone.com/posts/show/491
|
|
37
|
+
def nonce
|
|
38
|
+
Array.new( 5 ) { rand(256) }.pack('C*').unpack('H*').first
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def percent_encode( string )
|
|
42
|
+
|
|
43
|
+
# ref http://snippets.dzone.com/posts/show/1260
|
|
44
|
+
return URI.escape( string, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]") ).gsub('*', '%2A')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @ref http://oauth.net/core/1.0/#rfc.section.9.2
|
|
48
|
+
def signature
|
|
49
|
+
key = percent_encode( @consumer_secret ) + '&' + percent_encode( @token_secret )
|
|
50
|
+
|
|
51
|
+
# ref: http://blog.nathanielbibler.com/post/63031273/openssl-hmac-vs-ruby-hmac-benchmarks
|
|
52
|
+
digest = OpenSSL::Digest::Digest.new( 'sha1' )
|
|
53
|
+
hmac = OpenSSL::HMAC.digest( digest, key, @base_str )
|
|
54
|
+
|
|
55
|
+
# ref http://groups.google.com/group/oauth-ruby/browse_thread/thread/9110ed8c8f3cae81
|
|
56
|
+
Base64.encode64( hmac ).chomp.gsub( /\n/, '' )
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# sort (very important as it affects the signature), concat, and percent encode
|
|
60
|
+
# @ref http://oauth.net/core/1.0/#rfc.section.9.1.1
|
|
61
|
+
# @ref http://oauth.net/core/1.0/#9.2.1
|
|
62
|
+
# @ref http://oauth.net/core/1.0/#rfc.section.A.5.1
|
|
63
|
+
def query_string
|
|
64
|
+
pairs = []
|
|
65
|
+
@params.sort.each { | key, val |
|
|
66
|
+
pairs.push( "#{ CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') } }=#{ CGI.escape(val.to_s) }" )
|
|
67
|
+
}
|
|
68
|
+
pairs.join '&'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# organize params & create signature
|
|
72
|
+
def sign( parsed_url )
|
|
73
|
+
|
|
74
|
+
@params = {
|
|
75
|
+
'oauth_consumer_key' => @consumer_key,
|
|
76
|
+
'oauth_nonce' => nonce,
|
|
77
|
+
'oauth_signature_method' => @sig_method,
|
|
78
|
+
'oauth_timestamp' => Time.now.to_i.to_s,
|
|
79
|
+
'oauth_version' => @oauth_version
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# if url has query, merge key/values into params obj overwriting defaults
|
|
83
|
+
if parsed_url.query
|
|
84
|
+
CGI.parse( parsed_url.query ).each do |k,v|
|
|
85
|
+
if v.is_a?(Array) && v.count == 1
|
|
86
|
+
@params[k] = v.first
|
|
87
|
+
else
|
|
88
|
+
@params[k] = v
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @ref http://oauth.net/core/1.0/#rfc.section.9.1.2
|
|
94
|
+
@req_url = parsed_url.scheme + '://' + parsed_url.host + parsed_url.path
|
|
95
|
+
|
|
96
|
+
# create base str. make it an object attr for ez debugging
|
|
97
|
+
# ref http://oauth.net/core/1.0/#anchor14
|
|
98
|
+
@base_str = [
|
|
99
|
+
@req_method,
|
|
100
|
+
percent_encode( req_url ),
|
|
101
|
+
|
|
102
|
+
# normalization is just x-www-form-urlencoded
|
|
103
|
+
percent_encode( query_string )
|
|
104
|
+
|
|
105
|
+
].join( '&' )
|
|
106
|
+
|
|
107
|
+
# add signature
|
|
108
|
+
@params[ 'oauth_signature' ] = signature
|
|
109
|
+
|
|
110
|
+
return self
|
|
111
|
+
end
|
|
112
|
+
end
|
data/test/cache_test.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'test_helper'
|
|
3
|
+
|
|
4
|
+
class CacheTest < Test::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
def test_second_occurrence_of_request_is_cache_hit
|
|
7
|
+
Geocoder.configure(:cache => {})
|
|
8
|
+
Geocoder::Lookup.all_services_except_test.each do |l|
|
|
9
|
+
Geocoder.configure(:lookup => l)
|
|
10
|
+
set_api_key!(l)
|
|
11
|
+
results = Geocoder.search("Madison Square Garden")
|
|
12
|
+
assert !results.first.cache_hit,
|
|
13
|
+
"Lookup #{l} returned erroneously cached result."
|
|
14
|
+
results = Geocoder.search("Madison Square Garden")
|
|
15
|
+
assert results.first.cache_hit,
|
|
16
|
+
"Lookup #{l} did not return cached result."
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/test/calculations_test.rb
CHANGED
|
@@ -3,10 +3,10 @@ require 'test_helper'
|
|
|
3
3
|
|
|
4
4
|
class CalculationsTest < Test::Unit::TestCase
|
|
5
5
|
def setup
|
|
6
|
-
Geocoder.configure
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
Geocoder.configure(
|
|
7
|
+
:units => :mi,
|
|
8
|
+
:distances => :linear
|
|
9
|
+
)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
# --- degree distance ---
|
|
@@ -38,6 +38,12 @@ class CalculationsTest < Test::Unit::TestCase
|
|
|
38
38
|
assert (la_to_ny - 3942).abs < 10
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
def test_distance_between_in_nautical_miles
|
|
42
|
+
assert_equal 60, Geocoder::Calculations.distance_between([0,0], [0,1], :units => :nm).round
|
|
43
|
+
la_to_ny = Geocoder::Calculations.distance_between([34.05,-118.25], [40.72,-74], :units => :nm).round
|
|
44
|
+
assert (la_to_ny - 2124).abs < 10
|
|
45
|
+
end
|
|
46
|
+
|
|
41
47
|
|
|
42
48
|
# --- geographic center ---
|
|
43
49
|
|
data/test/configuration_test.rb
CHANGED
|
@@ -7,69 +7,44 @@ class ConfigurationTest < Test::Unit::TestCase
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def test_exception_raised_on_bad_lookup_config
|
|
10
|
-
Geocoder
|
|
10
|
+
Geocoder.configure(:lookup => :stoopid)
|
|
11
11
|
assert_raises Geocoder::ConfigurationError do
|
|
12
12
|
Geocoder.search "something dumb"
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Geocoder
|
|
19
|
-
|
|
20
|
-
assert_not_equal 111, distance
|
|
21
|
-
assert_equal 69, distance
|
|
22
|
-
|
|
23
|
-
Geocoder::Configuration.units = :km
|
|
24
|
-
distance = Geocoder::Calculations.distance_between([0,0], [0,1]).round
|
|
25
|
-
assert_equal 111, distance
|
|
26
|
-
assert_not_equal 69, distance
|
|
27
|
-
|
|
28
|
-
Geocoder::Configuration.distances = :spherical
|
|
29
|
-
angle = Geocoder::Calculations.bearing_between([50,-85], [40.750354, -73.993371]).round
|
|
30
|
-
assert_equal 136, angle
|
|
31
|
-
assert_not_equal 130, angle
|
|
16
|
+
def test_setting_with_class_method
|
|
17
|
+
Geocoder::Configuration.units = :test
|
|
18
|
+
assert_equal :test, Geocoder.config.units
|
|
19
|
+
end
|
|
32
20
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
assert_equal 130, angle
|
|
21
|
+
def test_setting_with_configure_method
|
|
22
|
+
Geocoder.configure(:units => :test)
|
|
23
|
+
assert_equal :test, Geocoder.config.units
|
|
37
24
|
end
|
|
38
25
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# DSL
|
|
26
|
+
def test_setting_with_block_syntax
|
|
27
|
+
orig = $VERBOSE; $VERBOSE = nil
|
|
42
28
|
Geocoder.configure do |config|
|
|
43
|
-
config.units
|
|
44
|
-
config.distances = :linear
|
|
29
|
+
config.units = :test
|
|
45
30
|
end
|
|
31
|
+
assert_equal :test, Geocoder.config.units
|
|
32
|
+
ensure
|
|
33
|
+
$VERBOSE = orig
|
|
34
|
+
end
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
assert_equal
|
|
56
|
-
|
|
57
|
-
# Direct
|
|
58
|
-
Geocoder.configure.units = :km
|
|
59
|
-
Geocoder.configure.distances = :spherical
|
|
60
|
-
|
|
61
|
-
assert_equal Geocoder::Configuration.units, :km
|
|
62
|
-
distance = Geocoder::Calculations.distance_between([0,0], [0,1]).round
|
|
63
|
-
assert_equal 111, distance
|
|
64
|
-
assert_not_equal 69, distance
|
|
65
|
-
|
|
66
|
-
assert_equal Geocoder::Configuration.distances, :spherical
|
|
67
|
-
angle = Geocoder::Calculations.bearing_between([50,-85], [40.750354, -73.993371]).round
|
|
68
|
-
assert_equal 136, angle
|
|
69
|
-
assert_not_equal 130, angle
|
|
36
|
+
def test_config_for_lookup
|
|
37
|
+
Geocoder.configure(
|
|
38
|
+
:timeout => 5,
|
|
39
|
+
:api_key => "aaa",
|
|
40
|
+
:google => {
|
|
41
|
+
:timeout => 2
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
assert_equal 2, Geocoder.config_for_lookup(:google).timeout
|
|
45
|
+
assert_equal "aaa", Geocoder.config_for_lookup(:google).api_key
|
|
70
46
|
end
|
|
71
47
|
|
|
72
|
-
# Geocoder per-model configuration
|
|
73
48
|
def test_model_configuration
|
|
74
49
|
Landmark.reverse_geocoded_by :latitude, :longitude, :method => :spherical, :units => :km
|
|
75
50
|
assert_equal :km, Landmark.geocoder_options[:units]
|
|
@@ -90,7 +65,7 @@ class ConfigurationTest < Test::Unit::TestCase
|
|
|
90
65
|
v.longitude = 0
|
|
91
66
|
|
|
92
67
|
# method option > global configuration
|
|
93
|
-
Geocoder.configure
|
|
68
|
+
Geocoder.configure(:units => :km)
|
|
94
69
|
assert_equal 69, v.distance_to([0,1], :mi).round
|
|
95
70
|
|
|
96
71
|
# per-model configuration > global configuration
|
data/test/error_handling_test.rb
CHANGED
|
@@ -4,23 +4,26 @@ require 'test_helper'
|
|
|
4
4
|
class ErrorHandlingTest < Test::Unit::TestCase
|
|
5
5
|
|
|
6
6
|
def teardown
|
|
7
|
-
Geocoder
|
|
7
|
+
Geocoder.configure(:always_raise => [])
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def test_does_not_choke_on_timeout
|
|
11
11
|
# keep test output clean: suppress timeout warning
|
|
12
12
|
orig = $VERBOSE; $VERBOSE = nil
|
|
13
13
|
Geocoder::Lookup.all_services_except_test.each do |l|
|
|
14
|
-
Geocoder
|
|
14
|
+
Geocoder.configure(:lookup => l)
|
|
15
|
+
set_api_key!(l)
|
|
15
16
|
assert_nothing_raised { Geocoder.search("timeout") }
|
|
16
17
|
end
|
|
18
|
+
ensure
|
|
17
19
|
$VERBOSE = orig
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def test_always_raise_timeout_error
|
|
21
|
-
Geocoder
|
|
23
|
+
Geocoder.configure(:always_raise => [TimeoutError])
|
|
22
24
|
Geocoder::Lookup.all_services_except_test.each do |l|
|
|
23
25
|
lookup = Geocoder::Lookup.get(l)
|
|
26
|
+
set_api_key!(l)
|
|
24
27
|
assert_raises TimeoutError do
|
|
25
28
|
lookup.send(:results, Geocoder::Query.new("timeout"))
|
|
26
29
|
end
|
|
@@ -28,9 +31,10 @@ class ErrorHandlingTest < Test::Unit::TestCase
|
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
def test_always_raise_socket_error
|
|
31
|
-
Geocoder
|
|
34
|
+
Geocoder.configure(:always_raise => [SocketError])
|
|
32
35
|
Geocoder::Lookup.all_services_except_test.each do |l|
|
|
33
36
|
lookup = Geocoder::Lookup.get(l)
|
|
37
|
+
set_api_key!(l)
|
|
34
38
|
assert_raises SocketError do
|
|
35
39
|
lookup.send(:results, Geocoder::Query.new("socket_error"))
|
|
36
40
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"authenticationResultCode":"InvalidCredentials","brandLogoUri":"http:\\/\\/dev.virtualearth.net\\/Branding\\/logo_powered_by.png","copyright":"Copyright \xC2\xA9 2012 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.","errorDetails":["Access was denied. You may have entered your credentials incorrectly, or you might not have access to the requested resource or operation."],"resourceSets":[],"statusCode":401,"statusDescription":"Unauthorized","traceId":"5c539f6e70c44b2e858741b6c932318e|EWRM001670|02.00.83.1900|"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"spatialReference": {
|
|
3
|
+
"wkid": 4326,
|
|
4
|
+
"latestWkid": 4326
|
|
5
|
+
},
|
|
6
|
+
"locations": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Madison Square Garden",
|
|
9
|
+
"extent": {
|
|
10
|
+
"xmin": -74.000241000000003,
|
|
11
|
+
"ymin": 40.744050000000001,
|
|
12
|
+
"xmax": -73.988241000000002,
|
|
13
|
+
"ymax": 40.756050000000002
|
|
14
|
+
},
|
|
15
|
+
"feature": {
|
|
16
|
+
"geometry": {
|
|
17
|
+
"x": -73.994238897999651,
|
|
18
|
+
"y": 40.750049813000487
|
|
19
|
+
},
|
|
20
|
+
"attributes": {
|
|
21
|
+
"Loc_name": "Gaz.WorldGazetteer.POI2",
|
|
22
|
+
"Score": 100,
|
|
23
|
+
"Match_addr": "Madison Square Garden",
|
|
24
|
+
"Addr_type": "POI",
|
|
25
|
+
"Type": "Sports Complex",
|
|
26
|
+
"PlaceName": "Madison Square Garden",
|
|
27
|
+
"Rank": "18",
|
|
28
|
+
"AddBldg": "",
|
|
29
|
+
"AddNum": "",
|
|
30
|
+
"AddNumFrom": "",
|
|
31
|
+
"AddNumTo": "",
|
|
32
|
+
"Side": "",
|
|
33
|
+
"StPreDir": "",
|
|
34
|
+
"StPreType": "",
|
|
35
|
+
"StName": "",
|
|
36
|
+
"StType": "",
|
|
37
|
+
"StDir": "",
|
|
38
|
+
"Nbrhd": "",
|
|
39
|
+
"City": "New York",
|
|
40
|
+
"Subregion": "New York",
|
|
41
|
+
"Region": "New York",
|
|
42
|
+
"Postal": "10001",
|
|
43
|
+
"PostalExt": "",
|
|
44
|
+
"Country": "USA",
|
|
45
|
+
"LangCode": "",
|
|
46
|
+
"Distance": 0,
|
|
47
|
+
"X": -73.994240000000005,
|
|
48
|
+
"Y": 40.750050000000002,
|
|
49
|
+
"DisplayX": -73.994240000000005,
|
|
50
|
+
"DisplayY": 40.750050000000002,
|
|
51
|
+
"Xmin": -74.000241000000003,
|
|
52
|
+
"Xmax": -73.988241000000002,
|
|
53
|
+
"Ymin": 40.744050000000001,
|
|
54
|
+
"Ymax": 40.756050000000002
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|