geocoder-sgonyea 1.1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/.travis.yml +23 -0
- data/CHANGELOG.md +298 -0
- data/LICENSE +20 -0
- data/README.md +656 -0
- data/Rakefile +25 -0
- data/bin/geocode +5 -0
- data/examples/autoexpire_cache.rb +28 -0
- data/gemfiles/Gemfile.mongoid-2.4.x +15 -0
- data/lib/generators/geocoder/config/config_generator.rb +14 -0
- data/lib/generators/geocoder/config/templates/initializer.rb +21 -0
- data/lib/geocoder.rb +55 -0
- data/lib/geocoder/cache.rb +85 -0
- data/lib/geocoder/calculations.rb +319 -0
- data/lib/geocoder/cli.rb +114 -0
- data/lib/geocoder/configuration.rb +130 -0
- data/lib/geocoder/configuration_hash.rb +11 -0
- data/lib/geocoder/exceptions.rb +21 -0
- data/lib/geocoder/lookup.rb +82 -0
- data/lib/geocoder/lookups/base.rb +250 -0
- data/lib/geocoder/lookups/bing.rb +47 -0
- data/lib/geocoder/lookups/freegeoip.rb +47 -0
- data/lib/geocoder/lookups/geocoder_ca.rb +54 -0
- data/lib/geocoder/lookups/google.rb +62 -0
- data/lib/geocoder/lookups/google_premier.rb +47 -0
- data/lib/geocoder/lookups/mapquest.rb +43 -0
- data/lib/geocoder/lookups/maxmind.rb +88 -0
- data/lib/geocoder/lookups/nominatim.rb +45 -0
- data/lib/geocoder/lookups/ovi.rb +52 -0
- data/lib/geocoder/lookups/test.rb +38 -0
- data/lib/geocoder/lookups/yahoo.rb +84 -0
- data/lib/geocoder/lookups/yandex.rb +54 -0
- data/lib/geocoder/models/active_record.rb +46 -0
- data/lib/geocoder/models/base.rb +42 -0
- data/lib/geocoder/models/mongo_base.rb +60 -0
- data/lib/geocoder/models/mongo_mapper.rb +26 -0
- data/lib/geocoder/models/mongoid.rb +32 -0
- data/lib/geocoder/query.rb +103 -0
- data/lib/geocoder/railtie.rb +26 -0
- data/lib/geocoder/request.rb +23 -0
- data/lib/geocoder/results/base.rb +67 -0
- data/lib/geocoder/results/bing.rb +48 -0
- data/lib/geocoder/results/freegeoip.rb +45 -0
- data/lib/geocoder/results/geocoder_ca.rb +60 -0
- data/lib/geocoder/results/google.rb +106 -0
- data/lib/geocoder/results/google_premier.rb +6 -0
- data/lib/geocoder/results/mapquest.rb +51 -0
- data/lib/geocoder/results/maxmind.rb +136 -0
- data/lib/geocoder/results/nominatim.rb +94 -0
- data/lib/geocoder/results/ovi.rb +62 -0
- data/lib/geocoder/results/test.rb +16 -0
- data/lib/geocoder/results/yahoo.rb +55 -0
- data/lib/geocoder/results/yandex.rb +80 -0
- data/lib/geocoder/sql.rb +106 -0
- data/lib/geocoder/stores/active_record.rb +259 -0
- data/lib/geocoder/stores/base.rb +120 -0
- data/lib/geocoder/stores/mongo_base.rb +85 -0
- data/lib/geocoder/stores/mongo_mapper.rb +13 -0
- data/lib/geocoder/stores/mongoid.rb +13 -0
- data/lib/geocoder/version.rb +3 -0
- data/lib/hash_recursive_merge.rb +74 -0
- data/lib/oauth_util.rb +112 -0
- data/lib/tasks/geocoder.rake +25 -0
- data/test/active_record_test.rb +15 -0
- data/test/cache_test.rb +19 -0
- data/test/calculations_test.rb +195 -0
- data/test/configuration_test.rb +78 -0
- data/test/custom_block_test.rb +32 -0
- data/test/error_handling_test.rb +43 -0
- data/test/fixtures/bing_invalid_key +1 -0
- data/test/fixtures/bing_madison_square_garden +40 -0
- data/test/fixtures/bing_no_results +16 -0
- data/test/fixtures/bing_reverse +42 -0
- data/test/fixtures/freegeoip_74_200_247_59 +12 -0
- data/test/fixtures/freegeoip_no_results +1 -0
- data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
- data/test/fixtures/geocoder_ca_no_results +1 -0
- data/test/fixtures/geocoder_ca_reverse +34 -0
- data/test/fixtures/google_garbage +456 -0
- data/test/fixtures/google_madison_square_garden +57 -0
- data/test/fixtures/google_no_city_data +44 -0
- data/test/fixtures/google_no_locality +51 -0
- data/test/fixtures/google_no_results +4 -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/nominatim_madison_square_garden +150 -0
- data/test/fixtures/nominatim_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_invalid_key +1 -0
- data/test/fixtures/yandex_kremlin +48 -0
- data/test/fixtures/yandex_no_city_and_town +112 -0
- data/test/fixtures/yandex_no_results +16 -0
- data/test/geocoder_test.rb +59 -0
- data/test/https_test.rb +16 -0
- data/test/integration/smoke_test.rb +26 -0
- data/test/lookup_test.rb +116 -0
- data/test/method_aliases_test.rb +25 -0
- data/test/mongoid_test.rb +39 -0
- data/test/mongoid_test_helper.rb +43 -0
- data/test/near_test.rb +43 -0
- data/test/oauth_util_test.rb +30 -0
- data/test/proxy_test.rb +23 -0
- data/test/query_test.rb +51 -0
- data/test/request_test.rb +29 -0
- data/test/result_test.rb +42 -0
- data/test/services_test.rb +277 -0
- data/test/test_helper.rb +279 -0
- data/test/test_mode_test.rb +50 -0
- metadata +170 -0
data/lib/geocoder/cli.rb
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require 'geocoder'
|
|
2
|
+
require 'optparse'
|
|
3
|
+
|
|
4
|
+
module Geocoder
|
|
5
|
+
class Cli
|
|
6
|
+
|
|
7
|
+
def self.run(args, out = STDOUT)
|
|
8
|
+
show_url = false
|
|
9
|
+
show_json = false
|
|
10
|
+
|
|
11
|
+
OptionParser.new{ |opts|
|
|
12
|
+
opts.banner = "Usage:\n geocode [options] <location>"
|
|
13
|
+
opts.separator "\nOptions: "
|
|
14
|
+
|
|
15
|
+
opts.on("-k <key>", "--key <key>",
|
|
16
|
+
"Key for geocoding API (usually optional). Enclose multi-part keys in quotes and separate parts by spaces") do |key|
|
|
17
|
+
if (key_parts = key.split(/\s+/)).size > 1
|
|
18
|
+
Geocoder.configure(:api_key => key_parts)
|
|
19
|
+
else
|
|
20
|
+
Geocoder.configure(:api_key => key)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
opts.on("-l <language>", "--language <language>",
|
|
25
|
+
"Language of output (see API docs for valid choices)") do |language|
|
|
26
|
+
Geocoder.configure(:language => language)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
opts.on("-p <proxy>", "--proxy <proxy>",
|
|
30
|
+
"HTTP proxy server to use (user:pass@host:port)") do |proxy|
|
|
31
|
+
Geocoder.configure(:http_proxy => proxy)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
opts.on("-s <service>", Geocoder::Lookup.all_services_except_test, "--service <service>",
|
|
35
|
+
"Geocoding service: #{Geocoder::Lookup.all_services_except_test * ', '}") do |service|
|
|
36
|
+
Geocoder.configure(:lookup => service.to_sym)
|
|
37
|
+
Geocoder.configure(:ip_lookup => service.to_sym)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
opts.on("-t <seconds>", "--timeout <seconds>",
|
|
41
|
+
"Maximum number of seconds to wait for API response") do |timeout|
|
|
42
|
+
Geocoder.configure(:timeout => timeout.to_i)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
opts.on("-j", "--json", "Print API's raw JSON response") do
|
|
46
|
+
show_json = true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
opts.on("-u", "--url", "Print URL for API query instead of result") do
|
|
50
|
+
show_url = true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
opts.on_tail("-v", "--version", "Print version number") do
|
|
54
|
+
require "geocoder/version"
|
|
55
|
+
out << "Geocoder #{Geocoder::VERSION}\n"
|
|
56
|
+
exit
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
opts.on_tail("-h", "--help", "Print this help") do
|
|
60
|
+
out << "Look up geographic information about a location.\n\n"
|
|
61
|
+
out << opts
|
|
62
|
+
out << "\nCreated and maintained by Alex Reisner, available under the MIT License.\n"
|
|
63
|
+
out << "Report bugs and contribute at http://github.com/alexreisner/geocoder\n"
|
|
64
|
+
exit
|
|
65
|
+
end
|
|
66
|
+
}.parse!(args)
|
|
67
|
+
|
|
68
|
+
query = args.join(" ")
|
|
69
|
+
|
|
70
|
+
if query == ""
|
|
71
|
+
out << "Please specify a location (run `geocode -h` for more info).\n"
|
|
72
|
+
exit 1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if show_url and show_json
|
|
76
|
+
out << "You can only specify one of -j and -u.\n"
|
|
77
|
+
exit 2
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if show_url
|
|
81
|
+
q = Geocoder::Query.new(query)
|
|
82
|
+
out << q.url + "\n"
|
|
83
|
+
exit 0
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if show_json
|
|
87
|
+
q = Geocoder::Query.new(query)
|
|
88
|
+
out << q.lookup.send(:fetch_raw_data, q) + "\n"
|
|
89
|
+
exit 0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if (result = Geocoder.search(query).first)
|
|
93
|
+
google = Geocoder::Lookup.get(:google)
|
|
94
|
+
lines = [
|
|
95
|
+
["Latitude", result.latitude],
|
|
96
|
+
["Longitude", result.longitude],
|
|
97
|
+
["Full address", result.address],
|
|
98
|
+
["City", result.city],
|
|
99
|
+
["State/province", result.state],
|
|
100
|
+
["Postal code", result.postal_code],
|
|
101
|
+
["Country", result.country],
|
|
102
|
+
["Google map", google.map_link_url(result.coordinates)],
|
|
103
|
+
]
|
|
104
|
+
lines.each do |line|
|
|
105
|
+
out << (line[0] + ": ").ljust(18) + line[1].to_s + "\n"
|
|
106
|
+
end
|
|
107
|
+
exit 0
|
|
108
|
+
else
|
|
109
|
+
out << "Location '#{query}' not found.\n"
|
|
110
|
+
exit 1
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
require 'geocoder/configuration_hash'
|
|
3
|
+
|
|
4
|
+
module Geocoder
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# Configuration options should be set by passing a hash:
|
|
8
|
+
#
|
|
9
|
+
# Geocoder.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 Geocoder.configure is DEPRECATED. Please pass a hash instead (eg: Geocoder.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 Geocoder.configure to read or write single config options is DEPRECATED. To write to the config please pass a hash (eg: Geocoder.configure(:units => ...)). To read config options please use the Geocoder.config object (eg: Geocoder.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 = Geocoder::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] = "geocoder:" # 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,21 @@
|
|
|
1
|
+
module Geocoder
|
|
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
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Geocoder
|
|
2
|
+
module Lookup
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Array of valid Lookup service names.
|
|
7
|
+
#
|
|
8
|
+
def all_services
|
|
9
|
+
street_services + ip_services
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Array of valid Lookup service names, excluding :test.
|
|
14
|
+
#
|
|
15
|
+
def all_services_except_test
|
|
16
|
+
all_services - [:test]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# All street address lookup services, default first.
|
|
21
|
+
#
|
|
22
|
+
def street_services
|
|
23
|
+
[
|
|
24
|
+
:google,
|
|
25
|
+
:google_premier,
|
|
26
|
+
:yahoo,
|
|
27
|
+
:bing,
|
|
28
|
+
:geocoder_ca,
|
|
29
|
+
:yandex,
|
|
30
|
+
:nominatim,
|
|
31
|
+
:mapquest,
|
|
32
|
+
:ovi,
|
|
33
|
+
:test
|
|
34
|
+
]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# All IP address lookup services, default first.
|
|
39
|
+
#
|
|
40
|
+
def ip_services
|
|
41
|
+
[:freegeoip, :maxmind]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Retrieve a Lookup object from the store.
|
|
46
|
+
# Use this instead of Geocoder::Lookup::X.new to get an
|
|
47
|
+
# already-configured Lookup object.
|
|
48
|
+
#
|
|
49
|
+
def get(name)
|
|
50
|
+
@services = {} unless defined?(@services)
|
|
51
|
+
@services[name] = spawn(name) unless @services.include?(name)
|
|
52
|
+
@services[name]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
private # -----------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# Spawn a Lookup of the given name.
|
|
60
|
+
#
|
|
61
|
+
def spawn(name)
|
|
62
|
+
if all_services.include?(name)
|
|
63
|
+
Geocoder::Lookup.const_get(classify_name(name)).new
|
|
64
|
+
else
|
|
65
|
+
valids = all_services.map(&:inspect).join(", ")
|
|
66
|
+
raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
|
|
67
|
+
"(#{name.inspect} is not one of: #{valids})."
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Convert an "underscore" version of a name into a "class" version.
|
|
73
|
+
#
|
|
74
|
+
def classify_name(filename)
|
|
75
|
+
filename.to_s.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
Geocoder::Lookup.all_services.each do |name|
|
|
81
|
+
require "geocoder/lookups/#{name}"
|
|
82
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'net/https'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
unless defined?(ActiveSupport::JSON)
|
|
6
|
+
begin
|
|
7
|
+
require 'rubygems' # for Ruby 1.8
|
|
8
|
+
require 'json'
|
|
9
|
+
rescue LoadError
|
|
10
|
+
raise LoadError, "Please install the 'json' or 'json_pure' gem to parse geocoder results."
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module Geocoder
|
|
15
|
+
module Lookup
|
|
16
|
+
|
|
17
|
+
class Base
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Human-readable name of the geocoding API.
|
|
21
|
+
#
|
|
22
|
+
def name
|
|
23
|
+
fail
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Symbol which is used in configuration to refer to this Lookup.
|
|
28
|
+
#
|
|
29
|
+
def handle
|
|
30
|
+
str = self.class.to_s
|
|
31
|
+
str[str.rindex(':')+1..-1].gsub(/([a-z\d]+)([A-Z])/,'\1_\2').downcase.to_sym
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Query the geocoding API and return a Geocoder::Result object.
|
|
36
|
+
# Returns +nil+ on timeout or error.
|
|
37
|
+
#
|
|
38
|
+
# Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS",
|
|
39
|
+
# "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
|
|
40
|
+
# for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
|
|
41
|
+
#
|
|
42
|
+
def search(query, options = {})
|
|
43
|
+
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
|
|
44
|
+
results(query).map{ |r|
|
|
45
|
+
result = result_class.new(r)
|
|
46
|
+
result.cache_hit = @cache_hit if cache
|
|
47
|
+
result
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Return the URL for a map of the given coordinates.
|
|
53
|
+
#
|
|
54
|
+
# Not necessarily implemented by all subclasses as only some lookups
|
|
55
|
+
# also provide maps.
|
|
56
|
+
#
|
|
57
|
+
def map_link_url(coordinates)
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Array containing string descriptions of keys required by the API.
|
|
63
|
+
# Empty array if keys are optional or not required.
|
|
64
|
+
#
|
|
65
|
+
def required_api_key_parts
|
|
66
|
+
[]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# URL to use for querying the geocoding engine.
|
|
71
|
+
#
|
|
72
|
+
def query_url(query)
|
|
73
|
+
fail
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private # -------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# An object with configuration data for this particular lookup.
|
|
80
|
+
#
|
|
81
|
+
def configuration
|
|
82
|
+
Geocoder.config_for_lookup(handle)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# Object used to make HTTP requests.
|
|
87
|
+
#
|
|
88
|
+
def http_client
|
|
89
|
+
protocol = "http#{'s' if configuration.use_https}"
|
|
90
|
+
proxy_name = "#{protocol}_proxy"
|
|
91
|
+
if proxy = configuration.send(proxy_name)
|
|
92
|
+
proxy_url = protocol + '://' + proxy
|
|
93
|
+
begin
|
|
94
|
+
uri = URI.parse(proxy_url)
|
|
95
|
+
rescue URI::InvalidURIError
|
|
96
|
+
raise ConfigurationError,
|
|
97
|
+
"Error parsing #{protocol.upcase} proxy URL: '#{proxy_url}'"
|
|
98
|
+
end
|
|
99
|
+
Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
|
|
100
|
+
else
|
|
101
|
+
Net::HTTP
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# Geocoder::Result object or nil on timeout or other error.
|
|
107
|
+
#
|
|
108
|
+
def results(query)
|
|
109
|
+
fail
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def query_url_params(query)
|
|
113
|
+
query.options[:params] || {}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def url_query_string(query)
|
|
117
|
+
hash_to_query(
|
|
118
|
+
query_url_params(query).reject{ |key,value| value.nil? }
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# Key to use for caching a geocoding result. Usually this will be the
|
|
124
|
+
# request URL, but in cases where OAuth is used and the nonce,
|
|
125
|
+
# timestamp, etc varies from one request to another, we need to use
|
|
126
|
+
# something else (like the URL before OAuth encoding).
|
|
127
|
+
#
|
|
128
|
+
def cache_key(query)
|
|
129
|
+
query_url(query)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
##
|
|
133
|
+
# Class of the result objects
|
|
134
|
+
#
|
|
135
|
+
def result_class
|
|
136
|
+
Geocoder::Result.const_get(self.class.to_s.split(":").last)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
##
|
|
140
|
+
# Raise exception if configuration specifies it should be raised.
|
|
141
|
+
# Return false if exception not raised.
|
|
142
|
+
#
|
|
143
|
+
def raise_error(error, message = nil)
|
|
144
|
+
exceptions = configuration.always_raise
|
|
145
|
+
if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
|
|
146
|
+
raise error, message
|
|
147
|
+
else
|
|
148
|
+
false
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
##
|
|
153
|
+
# Returns a parsed search result (Ruby hash).
|
|
154
|
+
#
|
|
155
|
+
def fetch_data(query)
|
|
156
|
+
parse_raw_data fetch_raw_data(query)
|
|
157
|
+
rescue SocketError => err
|
|
158
|
+
raise_error(err) or warn "Geocoding API connection cannot be established."
|
|
159
|
+
rescue TimeoutError => err
|
|
160
|
+
raise_error(err) or warn "Geocoding API not responding fast enough " +
|
|
161
|
+
"(use Geocoder.configure(:timeout => ...) to set limit)."
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
##
|
|
165
|
+
# Parses a raw search result (returns hash or array).
|
|
166
|
+
#
|
|
167
|
+
def parse_raw_data(raw_data)
|
|
168
|
+
if defined?(ActiveSupport::JSON)
|
|
169
|
+
ActiveSupport::JSON.decode(raw_data)
|
|
170
|
+
else
|
|
171
|
+
JSON.parse(raw_data)
|
|
172
|
+
end
|
|
173
|
+
rescue
|
|
174
|
+
warn "Geocoding API's response was not valid JSON."
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# Protocol to use for communication with geocoding services.
|
|
179
|
+
# Set in configuration but not available for every service.
|
|
180
|
+
#
|
|
181
|
+
def protocol
|
|
182
|
+
"http" + (configuration.use_https ? "s" : "")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
##
|
|
186
|
+
# Fetch a raw geocoding result (JSON string).
|
|
187
|
+
# The result might or might not be cached.
|
|
188
|
+
#
|
|
189
|
+
def fetch_raw_data(query)
|
|
190
|
+
key = cache_key(query)
|
|
191
|
+
if cache and body = cache[key]
|
|
192
|
+
@cache_hit = true
|
|
193
|
+
else
|
|
194
|
+
check_api_key_configuration!(query)
|
|
195
|
+
response = make_api_request(query)
|
|
196
|
+
body = response.body
|
|
197
|
+
if cache and (200..399).include?(response.code.to_i)
|
|
198
|
+
cache[key] = body
|
|
199
|
+
end
|
|
200
|
+
@cache_hit = false
|
|
201
|
+
end
|
|
202
|
+
body
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
##
|
|
206
|
+
# Make an HTTP(S) request to a geocoding API and
|
|
207
|
+
# return the response object.
|
|
208
|
+
#
|
|
209
|
+
def make_api_request(query)
|
|
210
|
+
timeout(configuration.timeout) do
|
|
211
|
+
uri = URI.parse(query_url(query))
|
|
212
|
+
client = http_client.new(uri.host, uri.port)
|
|
213
|
+
client.use_ssl = true if configuration.use_https
|
|
214
|
+
client.get(uri.request_uri, configuration.http_headers)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def check_api_key_configuration!(query)
|
|
219
|
+
key_parts = query.lookup.required_api_key_parts
|
|
220
|
+
if key_parts.size > Array(configuration.api_key).size
|
|
221
|
+
parts_string = key_parts.size == 1 ? key_parts.first : key_parts
|
|
222
|
+
raise Geocoder::ConfigurationError,
|
|
223
|
+
"The #{query.lookup.name} API requires a key to be configured: " +
|
|
224
|
+
parts_string.inspect
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
##
|
|
229
|
+
# The working Cache object.
|
|
230
|
+
#
|
|
231
|
+
def cache
|
|
232
|
+
if @cache.nil? and store = configuration.cache
|
|
233
|
+
@cache = Cache.new(store, configuration.cache_prefix)
|
|
234
|
+
end
|
|
235
|
+
@cache
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
##
|
|
239
|
+
# Simulate ActiveSupport's Object#to_query.
|
|
240
|
+
# Removes any keys with nil value.
|
|
241
|
+
#
|
|
242
|
+
def hash_to_query(hash)
|
|
243
|
+
require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
|
|
244
|
+
hash.collect{ |p|
|
|
245
|
+
p[1].nil? ? nil : p.map{ |i| CGI.escape i.to_s } * '='
|
|
246
|
+
}.compact.sort * '&'
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|