darrell-geokit 1.2.4.1 → 1.4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +3 -1
- data/README.markdown +89 -5
- data/Rakefile +12 -10
- data/lib/geokit.rb +3 -3
- data/lib/geokit/geocoders.rb +127 -46
- data/lib/geokit/mappable.rb +57 -8
- data/test/test_base_geocoder.rb +9 -7
- data/test/test_bounds.rb +23 -0
- data/test/test_geoloc.rb +18 -0
- data/test/test_geoplugin_geocoder.rb +59 -0
- data/test/test_google_geocoder.rb +67 -0
- data/test/test_google_reverse_geocoder.rb +49 -0
- data/test/test_inflector.rb +24 -0
- data/test/test_ipgeocoder.rb +88 -0
- data/test/test_latlng.rb +77 -0
- data/test/test_multi_geocoder.rb +58 -9
- data/test/test_multi_ip_geocoder.rb +38 -0
- data/test/test_yahoo_geocoder.rb +18 -0
- metadata +9 -3
data/Manifest.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
.
|
1
|
+
History.txt
|
2
2
|
Manifest.txt
|
3
3
|
README.markdown
|
4
4
|
Rakefile
|
@@ -10,8 +10,10 @@ test/test_base_geocoder.rb
|
|
10
10
|
test/test_bounds.rb
|
11
11
|
test/test_ca_geocoder.rb
|
12
12
|
test/test_geoloc.rb
|
13
|
+
test/test_geoplugin_geocoder.rb
|
13
14
|
test/test_google_geocoder.rb
|
14
15
|
test/test_google_reverse_geocoder.rb
|
16
|
+
test/test_inflector.rb
|
15
17
|
test/test_ipgeocoder.rb
|
16
18
|
test/test_latlng.rb
|
17
19
|
test/test_multi_geocoder.rb
|
data/README.markdown
CHANGED
@@ -8,15 +8,15 @@ The Geokit gem provides:
|
|
8
8
|
* Rectangular bounds calculations: is a point within a given rectangular bounds?
|
9
9
|
* Heading and midpoint calculations
|
10
10
|
|
11
|
-
Combine this
|
11
|
+
Combine this gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master) to get location-based finders for your Rails app.
|
12
12
|
|
13
13
|
* Geokit Documentation at Rubyforge [http://geokit.rubyforge.org](http://geokit.rubyforge.org).
|
14
14
|
* Repository at Github: [http://github.com/andre/geokit-gem/tree/master](http://github.com/andre/geokit-gem/tree/master).
|
15
|
+
* Follow the Google Group for updates and discussion on Geokit: [http://groups.google.com/group/geokit](http://groups.google.com/group/geokit)
|
15
16
|
|
16
17
|
## INSTALL
|
17
18
|
|
18
|
-
gem
|
19
|
-
sudo gem install andre-geokit
|
19
|
+
sudo gem install geokit
|
20
20
|
|
21
21
|
## QUICK START
|
22
22
|
|
@@ -74,6 +74,12 @@ If you're using this gem by itself, here are the configuration options:
|
|
74
74
|
# See http://www.google.com/apis/maps/signup.html
|
75
75
|
# and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
|
76
76
|
Geokit::Geocoders::google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
|
77
|
+
|
78
|
+
# You can also set multiple API KEYS for different domains that may be directed to this same application.
|
79
|
+
# The domain from which the current user is being directed will automatically be updated for Geokit via
|
80
|
+
# the GeocoderControl class, which gets it's begin filter mixed into the ActionController.
|
81
|
+
# You define these keys with a Hash as follows:
|
82
|
+
#Geokit::Geocoders::google = { 'rubyonrails.org' => 'RUBY_ON_RAILS_API_KEY', 'ruby-docs.org' => 'RUBY_DOCS_API_KEY' }
|
77
83
|
|
78
84
|
# This is your username and password for geocoder.us.
|
79
85
|
# To use the free service, the value can be set to nil or false. For
|
@@ -90,6 +96,10 @@ If you're using this gem by itself, here are the configuration options:
|
|
90
96
|
# and http://geocoder.ca/?register=1
|
91
97
|
Geokit::Geocoders::geocoder_ca = false
|
92
98
|
|
99
|
+
# require "external_geocoder.rb"
|
100
|
+
# Please see the section "writing your own geocoders" for more information.
|
101
|
+
# Geokit::Geocoders::external_key = 'REPLACE_WITH_YOUR_API_KEY'
|
102
|
+
|
93
103
|
# This is the order in which the geocoders are called in a failover scenario
|
94
104
|
# If you only want to use a single geocoder, put a single symbol in the array.
|
95
105
|
# Valid symbols are :google, :yahoo, :us, and :ca.
|
@@ -97,6 +107,10 @@ If you're using this gem by itself, here are the configuration options:
|
|
97
107
|
# various geocoders. Make sure you read up on relevant Terms of Use for each
|
98
108
|
# geocoder you are going to use.
|
99
109
|
Geokit::Geocoders::provider_order = [:google,:us]
|
110
|
+
|
111
|
+
# The IP provider order. Valid symbols are :ip,:geo_plugin.
|
112
|
+
# As before, make sure you read up on relevant Terms of Use for each.
|
113
|
+
# Geokit::Geocoders::ip_provider_order = [:external,:geo_plugin,:ip]
|
100
114
|
|
101
115
|
If you're using this gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master), the plugin
|
102
116
|
creates a template with these settings and places it in `config/initializers/geokit_config.rb`.
|
@@ -110,14 +124,54 @@ creates a template with these settings and places it in `config/initializers/geo
|
|
110
124
|
* Geonames - a free geocoder
|
111
125
|
|
112
126
|
### address geocoders that also provide reverse geocoding
|
113
|
-
* Google Geocoder - requires an API key. Also supports multiple results.
|
127
|
+
* Google Geocoder - requires an API key. Also supports multiple results and bounding box/country code biasing.
|
114
128
|
|
115
129
|
### IP address geocoders
|
116
130
|
* IP Geocoder - geocodes an IP address using hostip.info's web service.
|
117
131
|
* Geoplugin.net -- another IP address geocoder
|
118
132
|
|
133
|
+
### Google Geocoder Tricks
|
134
|
+
|
135
|
+
The Google Geocoder sports a number of useful tricks that elevate it a little bit above the rest of the currently supported geocoders. For starters, it returns a `suggested_bounds` property for all your geocoded results, so you can more easily decide where and how to center a map on the places you geocode. Here's a quick example:
|
136
|
+
|
137
|
+
irb> res = Geokit::Geocoders::GoogleGeocoder.geocode('140 Market St, San Francisco, CA')
|
138
|
+
irb> pp res.suggested_bounds
|
139
|
+
#<Geokit::Bounds:0x53b36c
|
140
|
+
@ne=#<Geokit::LatLng:0x53b204 @lat=37.7968528, @lng=-122.3926933>,
|
141
|
+
@sw=#<Geokit::LatLng:0x53b2b8 @lat=37.7905576, @lng=-122.3989885>>
|
142
|
+
|
143
|
+
In addition, you can use viewport or country code biasing to make sure the geocoders prefers results within a specific area. Say we wanted to geocode the city of Syracuse in Italy. A normal geocoding query would look like this:
|
144
|
+
|
145
|
+
irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Syracuse')
|
146
|
+
irb> res.full_address
|
147
|
+
=> "Syracuse, NY, USA"
|
148
|
+
|
149
|
+
Not exactly what we were looking for. We know that Syracuse is in Italy, so we can tell the Google Geocoder to prefer results from Italy first, and then wander the Syracuses of the world. To do that, we have to pass Italy's ccTLD (country code top-level domain) to the `:bias` option of the `geocode` method. You can find a comprehensive list of all ccTLDs here: http://en.wikipedia.org/wiki/CcTLD.
|
150
|
+
|
151
|
+
irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Syracuse', :bias => 'it')
|
152
|
+
irb> res.full_address
|
153
|
+
=> "Syracuse, Italy"
|
154
|
+
|
155
|
+
Alternatively, we can speficy the geocoding bias as a bounding box object. Say we wanted to geocode the Winnetka district in Los Angeles.
|
156
|
+
|
157
|
+
irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Winnetka')
|
158
|
+
irb> res.full_address
|
159
|
+
=> "Winnetka, IL, USA"
|
160
|
+
|
161
|
+
Not it. What we can do is tell the geocoder to return results only from in and around LA.
|
162
|
+
|
163
|
+
irb> la_bounds = Geokit::Geocoder::GoogleGeocoder.geocode('Los Angeles').suggested_bounds
|
164
|
+
irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Winnetka', :bias => la_bounds)
|
165
|
+
irb> res.full_address
|
166
|
+
=> "Winnetka, California, USA"
|
167
|
+
|
168
|
+
|
119
169
|
### The Multigeocoder
|
120
|
-
|
170
|
+
Multi Geocoder - provides failover for the physical location geocoders, and also IP address geocoders. Its configured by setting Geokit::Geocoders::provider_order, and Geokit::Geocoders::ip_provider_order. You should call the Multi-Geocoder with its :geocode method, supplying one address parameter which is either a real street address, or an ip address. For example:
|
171
|
+
|
172
|
+
Geokit::Geocoders::MultiGeocoder.geocode("900 Sycamore Drive")
|
173
|
+
|
174
|
+
Geokit::Geocoders::MultiGeocoder.geocode("12.12.12.12")
|
121
175
|
|
122
176
|
## MULTIPLE RESULTS
|
123
177
|
Some geocoding services will return multple results if the there isn't one clear result.
|
@@ -159,6 +213,36 @@ geocoders.rb contains all the geocoder implemenations. All the gercoders
|
|
159
213
|
inherit from a common base (class Geocoder) and implement the private method
|
160
214
|
do_geocode.
|
161
215
|
|
216
|
+
## WRITING YOUR OWN GEOCODERS
|
217
|
+
|
218
|
+
If you would like to write your own geocoders, you can do so by requiring 'geokit' or 'geokit/geocoders.rb' in a new file and subclassing the base class (which is class "Geocoder").
|
219
|
+
You must then also require such extenal file back in your main geokit configuration.
|
220
|
+
|
221
|
+
require "geokit"
|
222
|
+
|
223
|
+
module Geokit
|
224
|
+
module Geocoders
|
225
|
+
|
226
|
+
# Should be overriden as Geokit::Geocoders::external_key in your configuration file
|
227
|
+
@@external_key = 'REPLACE_WITH_YOUR_API_KEY'
|
228
|
+
__define_accessors
|
229
|
+
|
230
|
+
# Replace name 'External' (below) with the name of your custom geocoder class
|
231
|
+
# and use :external to specify this geocoder in your list of geocoders.
|
232
|
+
class ExternalGeocoder < Geocoder
|
233
|
+
private
|
234
|
+
def self.do_geocode(address, options = {})
|
235
|
+
# Main geocoding method
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.parse_http_resp(body) # :nodoc:
|
239
|
+
# Helper method to parse http response. See geokit/geocoders.rb.
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
162
246
|
## GOOGLE GROUP
|
163
247
|
|
164
248
|
Follow the Google Group for updates and discussion on Geokit: http://groups.google.com/group/geokit
|
data/Rakefile
CHANGED
@@ -2,19 +2,21 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'hoe'
|
5
|
-
require './lib/geokit'
|
5
|
+
require './lib/geokit.rb'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
# undefined method `empty?' for nil:NilClass
|
8
|
+
# /Library/Ruby/Site/1.8/rubygems/specification.rb:886:in `validate'
|
9
|
+
class NilClass
|
10
|
+
def empty?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
11
14
|
|
12
|
-
|
13
|
-
|
15
|
+
project=Hoe.new('geokit', Geokit::VERSION) do |p|
|
16
|
+
#p.rubyforge_name = 'geokit' # if different than lowercase project name
|
17
|
+
p.developer('Andre Lewis', 'andre@earthcode.com')
|
18
|
+
p.summary="Geokit provides geocoding and distance calculation in an easy-to-use API"
|
14
19
|
end
|
15
20
|
|
16
|
-
task :update_manifest do
|
17
|
-
system "touch Manifest.txt; rake check_manifest | grep -v \"(in \" | patch"
|
18
|
-
end
|
19
21
|
|
20
22
|
# vim: syntax=Ruby
|
data/lib/geokit.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Geokit
|
2
|
-
VERSION = '1.
|
2
|
+
VERSION = '1.4.1'
|
3
3
|
# These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
|
4
4
|
@@default_units = :miles
|
5
5
|
@@default_formula = :sphere
|
@@ -22,9 +22,9 @@ module Geokit
|
|
22
22
|
end
|
23
23
|
|
24
24
|
path = File.expand_path(File.dirname(__FILE__))
|
25
|
-
|
25
|
+
$:.unshift path unless $:.include?(path)
|
26
26
|
require 'geokit/geocoders'
|
27
27
|
require 'geokit/mappable'
|
28
28
|
|
29
|
-
# make old-style module name "GeoKit"
|
29
|
+
# make old-style module name "GeoKit" equivalent to new-style "Geokit"
|
30
30
|
GeoKit=Geokit
|
data/lib/geokit/geocoders.rb
CHANGED
@@ -37,6 +37,10 @@ module Geokit
|
|
37
37
|
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
38
38
|
end.tr(' ', '+')
|
39
39
|
end
|
40
|
+
|
41
|
+
def camelize(str)
|
42
|
+
str.split('_').map {|w| w.capitalize}.join
|
43
|
+
end
|
40
44
|
end
|
41
45
|
|
42
46
|
# Contains a range of geocoders:
|
@@ -70,25 +74,37 @@ module Geokit
|
|
70
74
|
@@geocoder_ca = false
|
71
75
|
@@geonames = false
|
72
76
|
@@provider_order = [:google,:us]
|
77
|
+
@@ip_provider_order = [:geo_plugin,:ip]
|
73
78
|
@@logger=Logger.new(STDOUT)
|
74
79
|
@@logger.level=Logger::INFO
|
80
|
+
@@domain = nil
|
75
81
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
82
|
+
def self.__define_accessors
|
83
|
+
class_variables.each do |v|
|
84
|
+
sym = v.to_s.delete("@").to_sym
|
85
|
+
unless self.respond_to? sym
|
86
|
+
module_eval <<-EOS, __FILE__, __LINE__
|
87
|
+
def self.#{sym}
|
88
|
+
value = if defined?(#{sym.to_s.upcase})
|
89
|
+
#{sym.to_s.upcase}
|
90
|
+
else
|
91
|
+
@@#{sym}
|
92
|
+
end
|
93
|
+
if value.is_a?(Hash)
|
94
|
+
value = (self.domain.nil? ? nil : value[self.domain]) || value.values.first
|
95
|
+
end
|
96
|
+
value
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.#{sym}=(obj)
|
100
|
+
@@#{sym} = obj
|
101
|
+
end
|
102
|
+
EOS
|
89
103
|
end
|
90
|
-
|
104
|
+
end
|
91
105
|
end
|
106
|
+
|
107
|
+
__define_accessors
|
92
108
|
|
93
109
|
# Error which is thrown in the event a geocoding error occurs.
|
94
110
|
class GeocodeError < StandardError; end
|
@@ -103,17 +119,16 @@ module Geokit
|
|
103
119
|
# Main method which calls the do_geocode template method which subclasses
|
104
120
|
# are responsible for implementing. Returns a populated GeoLoc or an
|
105
121
|
# empty one with a failed success code.
|
106
|
-
def self.geocode(address)
|
107
|
-
res = do_geocode(address)
|
108
|
-
return res.
|
122
|
+
def self.geocode(address, options = {})
|
123
|
+
res = do_geocode(address, options)
|
124
|
+
return res.nil? ? GeoLoc.new : res
|
109
125
|
end
|
110
|
-
|
111
126
|
# Main method which calls the do_reverse_geocode template method which subclasses
|
112
127
|
# are responsible for implementing. Returns a populated GeoLoc or an
|
113
128
|
# empty one with a failed success code.
|
114
129
|
def self.reverse_geocode(latlng)
|
115
130
|
res = do_reverse_geocode(latlng)
|
116
|
-
return res.success ? res : GeoLoc.new
|
131
|
+
return res.success? ? res : GeoLoc.new
|
117
132
|
end
|
118
133
|
|
119
134
|
# Call the geocoder service using the timeout if configured.
|
@@ -157,8 +172,8 @@ module Geokit
|
|
157
172
|
def self.inherited(clazz)
|
158
173
|
class_name = clazz.name.split('::').last
|
159
174
|
src = <<-END_SRC
|
160
|
-
def self.#{Geokit::Inflector.underscore(class_name)}(address)
|
161
|
-
#{class_name}.geocode(address)
|
175
|
+
def self.#{Geokit::Inflector.underscore(class_name)}(address, options = {})
|
176
|
+
#{class_name}.geocode(address, options)
|
162
177
|
end
|
163
178
|
END_SRC
|
164
179
|
class_eval(src)
|
@@ -184,7 +199,7 @@ module Geokit
|
|
184
199
|
private
|
185
200
|
|
186
201
|
# Template method which does the geocode lookup.
|
187
|
-
def self.do_geocode(address)
|
202
|
+
def self.do_geocode(address, options = {})
|
188
203
|
raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
|
189
204
|
url = construct_request(address)
|
190
205
|
res = self.call_geocoder_service(url)
|
@@ -226,7 +241,7 @@ module Geokit
|
|
226
241
|
class UsGeocoder < Geocoder
|
227
242
|
|
228
243
|
private
|
229
|
-
def self.do_geocode(address)
|
244
|
+
def self.do_geocode(address, options = {})
|
230
245
|
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
231
246
|
|
232
247
|
query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
|
@@ -274,7 +289,7 @@ module Geokit
|
|
274
289
|
private
|
275
290
|
|
276
291
|
# Template method which does the geocode lookup.
|
277
|
-
def self.do_geocode(address)
|
292
|
+
def self.do_geocode(address, options = {})
|
278
293
|
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
279
294
|
url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{Geokit::Geocoders::yahoo}&location=#{Geokit::Inflector::url_escape(address_str)}"
|
280
295
|
res = self.call_geocoder_service(url)
|
@@ -298,6 +313,8 @@ module Geokit
|
|
298
313
|
res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil
|
299
314
|
res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil
|
300
315
|
res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result']
|
316
|
+
# set the accuracy as google does (added by Andruby)
|
317
|
+
res.accuracy=%w{unknown country state state city zip zip+4 street address building}.index(res.precision)
|
301
318
|
res.success=true
|
302
319
|
return res
|
303
320
|
else
|
@@ -318,7 +335,7 @@ module Geokit
|
|
318
335
|
private
|
319
336
|
|
320
337
|
# Template method which does the geocode lookup.
|
321
|
-
def self.do_geocode(address)
|
338
|
+
def self.do_geocode(address, options = {})
|
322
339
|
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
323
340
|
# geonames need a space seperated search string
|
324
341
|
address_str.gsub!(/,/, " ")
|
@@ -383,16 +400,51 @@ module Geokit
|
|
383
400
|
end
|
384
401
|
|
385
402
|
# Template method which does the geocode lookup.
|
386
|
-
|
403
|
+
#
|
404
|
+
# Supports viewport/country code biasing
|
405
|
+
#
|
406
|
+
# ==== OPTIONS
|
407
|
+
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
408
|
+
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
409
|
+
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
410
|
+
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
411
|
+
# will be biased to results within the US (ccTLD .com).
|
412
|
+
#
|
413
|
+
# If you'd like the Google Geocoder to prefer results within a given viewport,
|
414
|
+
# you can pass a Geokit::Bounds object as the :bias value.
|
415
|
+
#
|
416
|
+
# ==== EXAMPLES
|
417
|
+
# # By default, the geocoder will return Syracuse, NY
|
418
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
|
419
|
+
# # With country code biasing, it returns Syracuse in Sicily, Italy
|
420
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
|
421
|
+
#
|
422
|
+
# # By default, the geocoder will return Winnetka, IL
|
423
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
|
424
|
+
# # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
|
425
|
+
# bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
|
426
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
|
427
|
+
def self.do_geocode(address, options = {})
|
428
|
+
bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
|
387
429
|
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
388
|
-
res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8")
|
430
|
+
res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml#{bias_str}&key=#{Geokit::Geocoders::google}&oe=utf-8")
|
389
431
|
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
390
432
|
xml = res.body
|
391
433
|
logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
|
392
|
-
return self.xml2GeoLoc(xml)
|
434
|
+
return self.xml2GeoLoc(xml, address)
|
393
435
|
end
|
394
436
|
|
395
|
-
def self.
|
437
|
+
def self.construct_bias_string_from_options(bias)
|
438
|
+
if bias.is_a?(String) or bias.is_a?(Symbol)
|
439
|
+
# country code biasing
|
440
|
+
"&gl=#{bias.to_s.downcase}"
|
441
|
+
elsif bias.is_a?(Bounds)
|
442
|
+
# viewport biasing
|
443
|
+
"&ll=#{bias.center.ll}&spn=#{bias.to_span.ll}"
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
def self.xml2GeoLoc(xml, address="")
|
396
448
|
doc=REXML::Document.new(xml)
|
397
449
|
|
398
450
|
if doc.elements['//kml/Response/Status/code'].text == '200'
|
@@ -400,9 +452,9 @@ module Geokit
|
|
400
452
|
# Google can return multiple results as //Placemark elements.
|
401
453
|
# iterate through each and extract each placemark as a geoloc
|
402
454
|
doc.each_element('//Placemark') do |e|
|
403
|
-
extracted_geoloc = extract_placemark(e) # g is now an instance of
|
455
|
+
extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
|
404
456
|
if geoloc.nil?
|
405
|
-
# first time through, geoloc is still
|
457
|
+
# first time through, geoloc is still nil, so we make it the geoloc we just extracted
|
406
458
|
geoloc = extracted_geoloc
|
407
459
|
else
|
408
460
|
# second (and subsequent) iterations, we push additional
|
@@ -441,8 +493,16 @@ module Geokit
|
|
441
493
|
# Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
|
442
494
|
# For Google, 1=low accuracy, 8=high accuracy
|
443
495
|
address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
|
444
|
-
accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
445
|
-
res.precision=%w{unknown country state state city zip zip+4 street address building}[accuracy]
|
496
|
+
res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
497
|
+
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
498
|
+
|
499
|
+
# google returns a set of suggested boundaries for the geocoded result
|
500
|
+
if suggested_bounds = doc.elements['//LatLonBox']
|
501
|
+
res.suggested_bounds = Bounds.normalize(
|
502
|
+
[suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
|
503
|
+
[suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
|
504
|
+
end
|
505
|
+
|
446
506
|
res.success=true
|
447
507
|
|
448
508
|
return res
|
@@ -458,12 +518,12 @@ module Geokit
|
|
458
518
|
class GeoPluginGeocoder < Geocoder
|
459
519
|
private
|
460
520
|
|
461
|
-
def self.do_geocode(ip)
|
521
|
+
def self.do_geocode(ip, options = {})
|
462
522
|
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
463
523
|
response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
|
464
524
|
return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
|
465
525
|
rescue
|
466
|
-
logger.error "Caught an error during
|
526
|
+
logger.error "Caught an error during GeoPluginGeocoder geocoding call: "+$!
|
467
527
|
return GeoLoc.new
|
468
528
|
end
|
469
529
|
|
@@ -476,7 +536,7 @@ module Geokit
|
|
476
536
|
geo.country_code = xml.elements['//geoplugin_countryCode'].text
|
477
537
|
geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
|
478
538
|
geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
|
479
|
-
geo.success = !geo.city.empty?
|
539
|
+
geo.success = !!geo.city && !geo.city.empty?
|
480
540
|
return geo
|
481
541
|
end
|
482
542
|
end
|
@@ -491,8 +551,8 @@ module Geokit
|
|
491
551
|
# Given an IP address, returns a GeoLoc instance which contains latitude,
|
492
552
|
# longitude, city, and country code. Sets the success attribute to false if the ip
|
493
553
|
# parameter does not match an ip address.
|
494
|
-
def self.do_geocode(ip)
|
495
|
-
return
|
554
|
+
def self.do_geocode(ip, options = {})
|
555
|
+
return GeoLoc.new if '0.0.0.0' == ip
|
496
556
|
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
497
557
|
url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
|
498
558
|
response = self.call_geocoder_service(url)
|
@@ -529,7 +589,8 @@ module Geokit
|
|
529
589
|
# -------------------------------------------------------------------------------------------
|
530
590
|
|
531
591
|
# Provides methods to geocode with a variety of geocoding service providers, plus failover
|
532
|
-
# among providers in the order you configure.
|
592
|
+
# among providers in the order you configure. When 2nd parameter is set 'true', perform
|
593
|
+
# ip location lookup with 'address' as the ip address.
|
533
594
|
#
|
534
595
|
# Goal:
|
535
596
|
# - homogenize the results of multiple geocoders
|
@@ -537,20 +598,23 @@ module Geokit
|
|
537
598
|
# Limitations:
|
538
599
|
# - currently only provides the first result. Sometimes geocoders will return multiple results.
|
539
600
|
# - currently discards the "accuracy" component of the geocoding calls
|
540
|
-
class MultiGeocoder < Geocoder
|
541
|
-
private
|
601
|
+
class MultiGeocoder < Geocoder
|
542
602
|
|
603
|
+
private
|
543
604
|
# This method will call one or more geocoders in the order specified in the
|
544
605
|
# configuration until one of the geocoders work.
|
545
606
|
#
|
546
607
|
# The failover approach is crucial for production-grade apps, but is rarely used.
|
547
608
|
# 98% of your geocoding calls will be successful with the first call
|
548
|
-
def self.do_geocode(address)
|
549
|
-
|
609
|
+
def self.do_geocode(address, options = {})
|
610
|
+
geocode_ip = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
|
611
|
+
provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
|
612
|
+
|
613
|
+
provider_order.each do |provider|
|
550
614
|
begin
|
551
|
-
klass = Geokit::Geocoders.const_get "#{provider.to_s
|
552
|
-
res = klass.send :geocode, address
|
553
|
-
return res if res.success
|
615
|
+
klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
|
616
|
+
res = klass.send :geocode, address, options
|
617
|
+
return res if res.success?
|
554
618
|
rescue
|
555
619
|
logger.error("Something has gone very wrong during geocoding, OR you have configured an invalid class name in Geokit::Geocoders::provider_order. Address: #{address}. Provider: #{provider}")
|
556
620
|
end
|
@@ -558,6 +622,23 @@ module Geokit
|
|
558
622
|
# If we get here, we failed completely.
|
559
623
|
GeoLoc.new
|
560
624
|
end
|
625
|
+
|
626
|
+
# This method will call one or more geocoders in the order specified in the
|
627
|
+
# configuration until one of the geocoders work, only this time it's going
|
628
|
+
# to try to reverse geocode a geographical point.
|
629
|
+
def self.do_reverse_geocode(latlng)
|
630
|
+
Geokit::Geocoders::provider_order.each do |provider|
|
631
|
+
begin
|
632
|
+
klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
|
633
|
+
res = klass.send :reverse_geocode, latlng
|
634
|
+
return res if res.success?
|
635
|
+
rescue
|
636
|
+
logger.error("Something has gone very wrong during reverse geocoding, OR you have configured an invalid class name in Geokit::Geocoders::provider_order. LatLng: #{latlng}. Provider: #{provider}")
|
637
|
+
end
|
638
|
+
end
|
639
|
+
# If we get here, we failed completely.
|
640
|
+
GeoLoc.new
|
641
|
+
end
|
561
642
|
end
|
562
643
|
end
|
563
644
|
end
|