geokit-premier 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ pkg
4
4
  .idea
5
5
  .bundle
6
6
  .rvmrc
7
+ Gemfile.lock
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.add_dependency('json_pure')
16
16
  s.add_dependency('hoe')
17
17
 
18
- s.add_development_dependency('rspec')
18
+ s.add_development_dependency('rspec', '>= 2.14.1')
19
19
  s.add_development_dependency('autotest')
20
20
  s.add_development_dependency('ZenTest')
21
21
  s.add_development_dependency('standalone_migrations')
@@ -6,8 +6,8 @@ require 'timeout'
6
6
  require 'logger'
7
7
  require 'base64'
8
8
 
9
- # do this just in case
10
- begin
9
+ # do this just in case
10
+ begin
11
11
  ActiveSupport.nil?
12
12
  rescue NameError
13
13
  require 'json/pure'
@@ -18,13 +18,13 @@ module Geokit
18
18
  class TooManyQueriesError < StandardError; end
19
19
 
20
20
  module Inflector
21
-
21
+
22
22
  extend self
23
-
23
+
24
24
  def titleize(word)
25
25
  humanize(underscore(word)).gsub(/\b([a-z])/u) { $1.capitalize }
26
26
  end
27
-
27
+
28
28
  def underscore(camel_cased_word)
29
29
  camel_cased_word.to_s.gsub(/::/, '/').
30
30
  gsub(/([A-Z]+)([A-Z][a-z])/u,'\1_\2').
@@ -32,52 +32,52 @@ module Geokit
32
32
  tr("-", "_").
33
33
  downcase
34
34
  end
35
-
35
+
36
36
  def humanize(lower_case_and_underscored_word)
37
37
  lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
38
38
  end
39
-
39
+
40
40
  def snake_case(s)
41
41
  return s.downcase if s =~ /^[A-Z]+$/u
42
42
  s.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/u, '_\&') =~ /_*(.*)/
43
43
  return $+.downcase
44
-
44
+
45
45
  end
46
-
46
+
47
47
  def url_escape(s)
48
48
  URI.escape(s)
49
49
  end
50
-
50
+
51
51
  def camelize(str)
52
52
  str.split('_').map {|w| w.capitalize}.join
53
53
  end
54
- end
55
-
54
+ end
55
+
56
56
  # Contains a range of geocoders:
57
- #
58
- # ### "regular" address geocoders
57
+ #
58
+ # ### "regular" address geocoders
59
59
  # * Yahoo Geocoder - requires an API key.
60
60
  # * Geocoder.us - may require authentication if performing more than the free request limit.
61
61
  # * Geocoder.ca - for Canada; may require authentication as well.
62
62
  # * Geonames - a free geocoder
63
63
  #
64
- # ### address geocoders that also provide reverse geocoding
64
+ # ### address geocoders that also provide reverse geocoding
65
65
  # * Google Geocoder - requires an API key.
66
- #
67
- # ### IP address geocoders
66
+ #
67
+ # ### IP address geocoders
68
68
  # * IP Geocoder - geocodes an IP address using hostip.info's web service.
69
69
  # * Geoplugin.net -- another IP address geocoder
70
70
  #
71
71
  # ### The Multigeocoder
72
72
  # * Multi Geocoder - provides failover for the physical location geocoders.
73
- #
73
+ #
74
74
  # Some of these geocoders require configuration. You don't have to provide it here. See the README.
75
75
  module Geocoders
76
76
  @@proxy_addr = nil
77
77
  @@proxy_port = nil
78
78
  @@proxy_user = nil
79
79
  @@proxy_pass = nil
80
- @@request_timeout = nil
80
+ @@request_timeout = nil
81
81
  @@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
82
82
  @@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
83
83
  @@google_client_id = nil #only used for premier accounts
@@ -90,9 +90,9 @@ module Geokit
90
90
  @@logger=Logger.new(STDOUT)
91
91
  @@logger.level=Logger::INFO
92
92
  @@domain = nil
93
-
93
+
94
94
  def self.__define_accessors
95
- class_variables.each do |v|
95
+ class_variables.each do |v|
96
96
  sym = v.to_s.delete("@").to_sym
97
97
  unless self.respond_to? sym
98
98
  module_eval <<-EOS, __FILE__, __LINE__
@@ -107,7 +107,7 @@ module Geokit
107
107
  end
108
108
  value
109
109
  end
110
-
110
+
111
111
  def self.#{sym}=(obj)
112
112
  @@#{sym} = obj
113
113
  end
@@ -117,38 +117,38 @@ module Geokit
117
117
  end
118
118
 
119
119
  __define_accessors
120
-
120
+
121
121
  # Error which is thrown in the event a geocoding error occurs.
122
122
  class GeocodeError < StandardError; end
123
123
 
124
124
  # -------------------------------------------------------------------------------------------
125
125
  # Geocoder Base class -- every geocoder should inherit from this
126
- # -------------------------------------------------------------------------------------------
127
-
126
+ # -------------------------------------------------------------------------------------------
127
+
128
128
  # The Geocoder base class which defines the interface to be used by all
129
129
  # other geocoders.
130
- class Geocoder
130
+ class Geocoder
131
131
  # Main method which calls the do_geocode template method which subclasses
132
132
  # are responsible for implementing. Returns a populated GeoLoc or an
133
133
  # empty one with a failed success code.
134
- def self.geocode(address, options = {})
134
+ def self.geocode(address, options = {})
135
135
  res = do_geocode(address, options)
136
136
  return res.nil? ? GeoLoc.new : res
137
- end
137
+ end
138
138
  # Main method which calls the do_reverse_geocode template method which subclasses
139
139
  # are responsible for implementing. Returns a populated GeoLoc or an
140
140
  # empty one with a failed success code.
141
141
  def self.reverse_geocode(latlng)
142
142
  res = do_reverse_geocode(latlng)
143
- return res.success? ? res : GeoLoc.new
143
+ return res.success? ? res : GeoLoc.new
144
144
  end
145
-
145
+
146
146
  # Call the geocoder service using the timeout if configured.
147
147
  def self.call_geocoder_service(url)
148
- Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout
148
+ Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout
149
149
  return self.do_get(url)
150
150
  rescue TimeoutError
151
- return nil
151
+ return nil
152
152
  end
153
153
 
154
154
  # Not all geocoders can do reverse geocoding. So, unless the subclass explicitly overrides this method,
@@ -157,14 +157,14 @@ module Geokit
157
157
  def self.do_reverse_geocode(latlng)
158
158
  return GeoLoc.new
159
159
  end
160
-
160
+
161
161
  # This will sign a raw url with a private key
162
162
  def self.sign_url(raw_url,private_key)
163
163
  uri = URI.parse(raw_url)
164
164
  url_to_sign = uri.path + "?" + uri.query
165
165
  decoded_key = Geocoder.urlsafe_decode64(private_key)
166
166
 
167
- sha1_digest = OpenSSL::Digest::Digest.new('sha1')
167
+ sha1_digest = OpenSSL::Digest.new('sha1')
168
168
  signature = OpenSSL::HMAC.digest(sha1_digest,decoded_key,url_to_sign)
169
169
  encoded_signature = Geocoder.urlsafe_encode64(signature)
170
170
  signed_url = "#{uri.scheme}://#{uri.host}#{uri.path}?#{uri.query}&signature=#{encoded_signature}".strip!
@@ -183,18 +183,18 @@ module Geokit
183
183
  encoded_text = Base64.encode64(raw_text)
184
184
  encoded_text = encoded_text.gsub('+','-').gsub('/', '_')
185
185
  encoded_text
186
- end
186
+ end
187
187
 
188
188
  protected
189
189
 
190
- def self.logger()
190
+ def self.logger()
191
191
  Geokit::Geocoders::logger
192
192
  end
193
-
193
+
194
194
  private
195
-
195
+
196
196
  # Wraps the geocoder call around a proxy if necessary.
197
- def self.do_get(url)
197
+ def self.do_get(url)
198
198
  uri = URI.parse(url)
199
199
  req = Net::HTTP::Get.new(url)
200
200
  req.basic_auth(uri.user, uri.password) if uri.userinfo
@@ -204,8 +204,8 @@ module Geokit
204
204
  GeoKit::Geocoders::proxy_pass).start(uri.host, uri.port) { |http| http.get(uri.path + "?" + uri.query) }
205
205
  return res
206
206
  end
207
-
208
- # Adds subclass' geocode method making it conveniently available through
207
+
208
+ # Adds subclass' geocode method making it conveniently available through
209
209
  # the base class.
210
210
  def self.inherited(clazz)
211
211
  class_name = clazz.name.split('::').last
@@ -220,10 +220,10 @@ module Geokit
220
220
 
221
221
  # -------------------------------------------------------------------------------------------
222
222
  # "Regular" Address geocoders
223
- # -------------------------------------------------------------------------------------------
224
-
223
+ # -------------------------------------------------------------------------------------------
224
+
225
225
  # Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
226
- # contain true or false based upon whether authentication is to occur. Conforms to the
226
+ # contain true or false based upon whether authentication is to occur. Conforms to the
227
227
  # interface set by the Geocoder class.
228
228
  #
229
229
  # Returns a response like:
@@ -245,15 +245,15 @@ module Geokit
245
245
  xml = res.body
246
246
  logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
247
247
  # Parse the document.
248
- doc = REXML::Document.new(xml)
248
+ doc = REXML::Document.new(xml)
249
249
  address.lat = doc.elements['//latt'].text
250
250
  address.lng = doc.elements['//longt'].text
251
251
  address.success = true
252
252
  return address
253
253
  rescue
254
254
  logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
255
- return GeoLoc.new
256
- end
255
+ return GeoLoc.new
256
+ end
257
257
 
258
258
  # Formats the request in the format acceptable by the CA geocoder.
259
259
  def self.construct_request(location)
@@ -271,60 +271,60 @@ module Geokit
271
271
  def self.add_ampersand(url)
272
272
  url && url.length > 0 ? "&" : ""
273
273
  end
274
- end
275
-
274
+ end
275
+
276
276
  # Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
277
- # contain true or false based upon whether authentication is to occur. Conforms to the
277
+ # contain true or false based upon whether authentication is to occur. Conforms to the
278
278
  # interface set by the Geocoder class.
279
279
  class UsGeocoder < Geocoder
280
280
 
281
281
  private
282
282
  def self.do_geocode(address, options = {})
283
283
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
284
-
284
+
285
285
  query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
286
- url = if GeoKit::Geocoders::geocoder_us
286
+ url = if GeoKit::Geocoders::geocoder_us
287
287
  "http://#{GeoKit::Geocoders::geocoder_us}@geocoder.us/member/service/csv/geocode"
288
288
  else
289
289
  "http://geocoder.us/service/csv/geocode"
290
290
  end
291
-
292
- url = "#{url}?#{query}"
291
+
292
+ url = "#{url}?#{query}"
293
293
  res = self.call_geocoder_service(url)
294
-
294
+
295
295
  return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
296
296
  data = res.body
297
297
  logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
298
298
  array = data.chomp.split(',')
299
-
299
+
300
300
  if array.length == 5
301
301
  res=GeoLoc.new
302
302
  res.lat,res.lng,res.city,res.state,res.zip=array
303
303
  res.country_code='US'
304
304
  res.success=true
305
305
  return res
306
- elsif array.length == 6
307
- res=GeoLoc.new
306
+ elsif array.length == 6
307
+ res=GeoLoc.new
308
308
  res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
309
309
  res.country_code='US'
310
- res.success=true
310
+ res.success=true
311
311
  return res
312
- else
312
+ else
313
313
  logger.info "geocoder.us was unable to geocode address: "+address
314
- return GeoLoc.new
314
+ return GeoLoc.new
315
315
  end
316
- rescue
316
+ rescue
317
317
  logger.error "Caught an error during geocoder.us geocoding call: "+$!
318
318
  return GeoLoc.new
319
319
 
320
320
  end
321
321
  end
322
-
322
+
323
323
  # Yahoo geocoder implementation. Requires the Geokit::Geocoders::YAHOO variable to
324
324
  # contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
325
325
  class YahooGeocoder < Geocoder
326
326
 
327
- private
327
+ private
328
328
 
329
329
  # Template method which does the geocode lookup.
330
330
  def self.do_geocode(address, options = {})
@@ -339,11 +339,11 @@ module Geokit
339
339
  if doc.elements['//ResultSet']
340
340
  res=GeoLoc.new
341
341
 
342
- #basic
342
+ #basic
343
343
  res.lat=doc.elements['//Latitude'].text
344
344
  res.lng=doc.elements['//Longitude'].text
345
345
  res.country_code=doc.elements['//Country'].text
346
- res.provider='yahoo'
346
+ res.provider='yahoo'
347
347
 
348
348
  #extended - false if not available
349
349
  res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil
@@ -355,12 +355,12 @@ module Geokit
355
355
  res.accuracy=%w{unknown country state state city zip zip+4 street address building}.index(res.precision)
356
356
  res.success=true
357
357
  return res
358
- else
358
+ else
359
359
  logger.info "Yahoo was unable to geocode address: "+address
360
360
  return GeoLoc.new
361
- end
361
+ end
362
362
 
363
- rescue
363
+ rescue
364
364
  logger.info "Caught an error during Yahoo geocoding call: "+$!
365
365
  return GeoLoc.new
366
366
  end
@@ -370,47 +370,47 @@ module Geokit
370
370
  # http://www.geonames.org
371
371
  class GeonamesGeocoder < Geocoder
372
372
 
373
- private
374
-
373
+ private
374
+
375
375
  # Template method which does the geocode lookup.
376
376
  def self.do_geocode(address, options = {})
377
377
  address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
378
378
  # geonames need a space seperated search string
379
379
  address_str.gsub!(/,/, " ")
380
380
  params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
381
-
381
+
382
382
  if(GeoKit::Geocoders::geonames)
383
383
  url = "http://ws.geonames.net#{params}&username=#{GeoKit::Geocoders::geonames}"
384
384
  else
385
385
  url = "http://ws.geonames.org#{params}"
386
386
  end
387
-
387
+
388
388
  res = self.call_geocoder_service(url)
389
-
389
+
390
390
  return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
391
-
391
+
392
392
  xml=res.body
393
393
  logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
394
394
  doc=REXML::Document.new(xml)
395
-
395
+
396
396
  if(doc.elements['//geonames/totalResultsCount'].text.to_i > 0)
397
397
  res=GeoLoc.new
398
-
398
+
399
399
  # only take the first result
400
400
  res.lat=doc.elements['//code/lat'].text if doc.elements['//code/lat']
401
401
  res.lng=doc.elements['//code/lng'].text if doc.elements['//code/lng']
402
402
  res.country_code=doc.elements['//code/countryCode'].text if doc.elements['//code/countryCode']
403
- res.provider='genomes'
403
+ res.provider='genomes'
404
404
  res.city=doc.elements['//code/name'].text if doc.elements['//code/name']
405
405
  res.state=doc.elements['//code/adminName1'].text if doc.elements['//code/adminName1']
406
406
  res.zip=doc.elements['//code/postalcode'].text if doc.elements['//code/postalcode']
407
407
  res.success=true
408
408
  return res
409
- else
409
+ else
410
410
  logger.info "Geonames was unable to geocode address: "+address
411
411
  return GeoLoc.new
412
412
  end
413
-
413
+
414
414
  rescue
415
415
  logger.error "Caught an error during Geonames geocoding call: "+$!
416
416
  end
@@ -424,18 +424,18 @@ module Geokit
424
424
  # contain a Google API key. Conforms to the interface set by the Geocoder class.
425
425
  class GoogleGeocoder < Geocoder
426
426
 
427
- private
428
-
427
+ private
428
+
429
429
  # Template method which does the reverse-geocode lookup.
430
- def self.do_reverse_geocode(latlng)
430
+ def self.do_reverse_geocode(latlng)
431
431
  latlng=LatLng.normalize(latlng)
432
432
  res = self.call_geocoder_service("http://maps.google.com/maps/geo?ll=#{Geokit::Inflector::url_escape(latlng.ll)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8")
433
433
  # res = Net::HTTP.get_response(URI.parse("http://maps.google.com/maps/geo?ll=#{Geokit::Inflector::url_escape(address_str)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8"))
434
434
  return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
435
435
  xml = res.body
436
436
  logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
437
- return self.xml2GeoLoc(xml)
438
- end
437
+ return self.xml2GeoLoc(xml)
438
+ end
439
439
 
440
440
  # Template method which does the geocode lookup.
441
441
  #
@@ -444,7 +444,7 @@ module Geokit
444
444
  # ==== OPTIONS
445
445
  # * :bias - This option makes the Google Geocoder return results biased to a particular
446
446
  # country or viewport. Country code biasing is achieved by passing the ccTLD
447
- # ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
447
+ # ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
448
448
  # look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
449
449
  # will be biased to results within the US (ccTLD .com).
450
450
  #
@@ -469,9 +469,9 @@ module Geokit
469
469
  return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
470
470
  xml = res.body
471
471
  logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
472
- return self.xml2GeoLoc(xml, address)
472
+ return self.xml2GeoLoc(xml, address)
473
473
  end
474
-
474
+
475
475
  def self.construct_bias_string_from_options(bias)
476
476
  if bias.is_a?(String) or bias.is_a?(Symbol)
477
477
  # country code biasing
@@ -481,24 +481,24 @@ module Geokit
481
481
  "&ll=#{bias.center.ll}&spn=#{bias.to_span.ll}"
482
482
  end
483
483
  end
484
-
484
+
485
485
  def self.xml2GeoLoc(xml, address="")
486
486
  doc=REXML::Document.new(xml)
487
487
 
488
488
  if doc.elements['//kml/Response/Status/code'].text == '200'
489
489
  geoloc = nil
490
- # Google can return multiple results as //Placemark elements.
490
+ # Google can return multiple results as //Placemark elements.
491
491
  # iterate through each and extract each placemark as a geoloc
492
492
  doc.each_element('//Placemark') do |e|
493
493
  extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
494
- if geoloc.nil?
494
+ if geoloc.nil?
495
495
  # first time through, geoloc is still nil, so we make it the geoloc we just extracted
496
- geoloc = extracted_geoloc
496
+ geoloc = extracted_geoloc
497
497
  else
498
- # second (and subsequent) iterations, we push additional
499
- # geolocs onto "geoloc.all"
500
- geoloc.all.push(extracted_geoloc)
501
- end
498
+ # second (and subsequent) iterations, we push additional
499
+ # geolocs onto "geoloc.all"
500
+ geoloc.all.push(extracted_geoloc)
501
+ end
502
502
  end
503
503
  return geoloc
504
504
  elsif doc.elements['//kml/Response/Status/code'].text == '620'
@@ -514,7 +514,7 @@ module Geokit
514
514
  rescue
515
515
  logger.error "Caught an error during Google geocoding call: "+$!
516
516
  return GeoLoc.new
517
- end
517
+ end
518
518
 
519
519
  # extracts a single geoloc from a //placemark element in the google results xml
520
520
  def self.extract_placemark(doc)
@@ -541,14 +541,14 @@ module Geokit
541
541
  address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
542
542
  res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
543
543
  res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
544
-
544
+
545
545
  # google returns a set of suggested boundaries for the geocoded result
546
- if suggested_bounds = doc.elements['//LatLonBox']
546
+ if suggested_bounds = doc.elements['//LatLonBox']
547
547
  res.suggested_bounds = Bounds.normalize(
548
- [suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
548
+ [suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
549
549
  [suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
550
550
  end
551
-
551
+
552
552
  res.success=true
553
553
 
554
554
  return res
@@ -557,16 +557,16 @@ module Geokit
557
557
 
558
558
  class GoogleGeocoder3 < Geocoder
559
559
 
560
- private
560
+ private
561
561
  # Template method which does the reverse-geocode lookup.
562
- def self.do_reverse_geocode(latlng)
562
+ def self.do_reverse_geocode(latlng)
563
563
  latlng=LatLng.normalize(latlng)
564
564
  res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(latlng.ll)}")
565
565
  return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
566
566
  json = res.body
567
567
  logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{json}"
568
- return self.json2GeoLoc(json)
569
- end
568
+ return self.json2GeoLoc(json)
569
+ end
570
570
 
571
571
  # Template method which does the geocode lookup.
572
572
  #
@@ -575,7 +575,7 @@ module Geokit
575
575
  # ==== OPTIONS
576
576
  # * :bias - This option makes the Google Geocoder return results biased to a particular
577
577
  # country or viewport. Country code biasing is achieved by passing the ccTLD
578
- # ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
578
+ # ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
579
579
  # look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
580
580
  # will be biased to results within the US (ccTLD .com).
581
581
  #
@@ -598,9 +598,9 @@ module Geokit
598
598
  return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
599
599
  json = res.body
600
600
  # logger.debug "Google geocoding. Address: #{address}. Result: #{json}"
601
- return self.json2GeoLoc(json, address)
601
+ return self.json2GeoLoc(json, address)
602
602
  end
603
-
603
+
604
604
  # Determine the Google API url based on the google api key, or based on the client / private key for premier users
605
605
  def self.geocode_url(address,options = {})
606
606
  bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
@@ -613,8 +613,8 @@ module Geokit
613
613
  "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(address_str)}#{bias_str}"
614
614
  end
615
615
  end
616
-
617
-
616
+
617
+
618
618
  def self.construct_bias_string_from_options(bias)
619
619
  if bias.is_a?(String) or bias.is_a?(Symbol)
620
620
  # country code biasing
@@ -625,6 +625,40 @@ module Geokit
625
625
  end
626
626
  end
627
627
 
628
+ # location_type stores additional data about the specified location.
629
+ # The following values are currently supported:
630
+ # "ROOFTOP" indicates that the returned result is a precise geocode
631
+ # for which we have location information accurate down to street
632
+ # address precision.
633
+ # "RANGE_INTERPOLATED" indicates that the returned result reflects an
634
+ # approximation (usually on a road) interpolated between two precise
635
+ # points (such as intersections). Interpolated results are generally
636
+ # returned when rooftop geocodes are unavailable for a street address.
637
+ # "GEOMETRIC_CENTER" indicates that the returned result is the
638
+ # geometric center of a result such as a polyline (for example, a
639
+ # street) or polygon (region).
640
+ # "APPROXIMATE" indicates that the returned result is approximate
641
+
642
+ # these do not map well. Perhaps we should guess better based on size
643
+ # of bounding box where it exists? Does it really matter?
644
+ def self.accuracy
645
+ {
646
+ "ROOFTOP" => 9,
647
+ "RANGE_INTERPOLATED" => 8,
648
+ "GEOMETRIC_CENTER" => 5,
649
+ "APPROXIMATE" => 4
650
+ }
651
+ end
652
+
653
+ # Grouped by accuracy, then sorts the groups by accuracy, and
654
+ # joins the results. This means that if google returns 5 results,
655
+ # and of those 5, 3 of them are the same accuracy, we will maintain
656
+ # the order of those three as we've received them, within our sorted
657
+ # results that are returned.
658
+ def self.grouped_and_sorted_by_accuracy(results)
659
+ results.group_by{|a| accuracy[ a['geometry']['location_type'] ]}.sort_by {|accuracy,x| accuracy }.reverse.map {|x,locations| locations}.flatten
660
+ end
661
+
628
662
  def self.json2GeoLoc(json, address="")
629
663
  ret=nil
630
664
  begin
@@ -632,8 +666,8 @@ module Geokit
632
666
  rescue NameError => e
633
667
  results=JSON.parse(json)
634
668
  end
635
-
636
-
669
+
670
+
637
671
  if results['status'] == 'OVER_QUERY_LIMIT'
638
672
  raise Geokit::TooManyQueriesError
639
673
  end
@@ -644,29 +678,8 @@ module Geokit
644
678
  if !results['status'] == 'OK'
645
679
  raise Geokit::Geocoders::GeocodeError
646
680
  end
647
- # location_type stores additional data about the specified location.
648
- # The following values are currently supported:
649
- # "ROOFTOP" indicates that the returned result is a precise geocode
650
- # for which we have location information accurate down to street
651
- # address precision.
652
- # "RANGE_INTERPOLATED" indicates that the returned result reflects an
653
- # approximation (usually on a road) interpolated between two precise
654
- # points (such as intersections). Interpolated results are generally
655
- # returned when rooftop geocodes are unavailable for a street address.
656
- # "GEOMETRIC_CENTER" indicates that the returned result is the
657
- # geometric center of a result such as a polyline (for example, a
658
- # street) or polygon (region).
659
- # "APPROXIMATE" indicates that the returned result is approximate
660
-
661
- # these do not map well. Perhaps we should guess better based on size
662
- # of bounding box where it exists? Does it really matter?
663
- accuracy = {
664
- "ROOFTOP" => 9,
665
- "RANGE_INTERPOLATED" => 8,
666
- "GEOMETRIC_CENTER" => 5,
667
- "APPROXIMATE" => 4
668
- }
669
- results['results'].sort_by{|a|accuracy[a['geometry']['location_type']]}.reverse.each do |addr|
681
+
682
+ grouped_and_sorted_by_accuracy(results['results']).each do |addr|
670
683
  res=GeoLoc.new
671
684
  res.provider = 'google3'
672
685
  res.success = true
@@ -701,13 +714,13 @@ module Geokit
701
714
  res.precision = 'street'
702
715
  res.accuracy = 7
703
716
  end
704
-
717
+
705
718
  res.lat=addr['geometry']['location']['lat'].to_f
706
719
  res.lng=addr['geometry']['location']['lng'].to_f
707
720
 
708
721
  if addr['geometry'].include?('viewport')
709
722
  ne=Geokit::LatLng.new(
710
- addr['geometry']['viewport']['northeast']['lat'].to_f,
723
+ addr['geometry']['viewport']['northeast']['lat'].to_f,
711
724
  addr['geometry']['viewport']['northeast']['lng'].to_f
712
725
  )
713
726
  sw=Geokit::LatLng.new(
@@ -729,11 +742,11 @@ module Geokit
729
742
  # -------------------------------------------------------------------------------------------
730
743
  # IP Geocoders
731
744
  # -------------------------------------------------------------------------------------------
732
-
745
+
733
746
  # Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
734
747
  class GeoPluginGeocoder < Geocoder
735
748
  private
736
-
749
+
737
750
  def self.do_geocode(ip, options = {})
738
751
  return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
739
752
  response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
@@ -760,7 +773,7 @@ module Geokit
760
773
  # Provides geocoding based upon an IP address. The underlying web service is a hostip.info
761
774
  # which sources their data through a combination of publicly available information as well
762
775
  # as community contributions.
763
- class IpGeocoder < Geocoder
776
+ class IpGeocoder < Geocoder
764
777
 
765
778
  # A number of non-routable IP ranges.
766
779
  #
@@ -783,11 +796,11 @@ module Geokit
783
796
  IPAddr.new('240.0.0.0/4') # Reserved for future use
784
797
  ].freeze
785
798
 
786
- private
799
+ private
787
800
 
788
801
  # Given an IP address, returns a GeoLoc instance which contains latitude,
789
- # longitude, city, and country code. Sets the success attribute to false if the ip
790
- # parameter does not match an ip address.
802
+ # longitude, city, and country code. Sets the success attribute to false if the ip
803
+ # parameter does not match an ip address.
791
804
  def self.do_geocode(ip, options = {})
792
805
  return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
793
806
  return GeoLoc.new if self.private_ip_address?(ip)
@@ -813,7 +826,7 @@ module Geokit
813
826
  res.provider = 'hostip'
814
827
  res.city, res.state = yaml['City'].split(', ')
815
828
  country, res.country_code = yaml['Country'].split(' (')
816
- res.lat = yaml['Latitude']
829
+ res.lat = yaml['Latitude']
817
830
  res.lng = yaml['Longitude']
818
831
  res.country_code.chop!
819
832
  res.success = !(res.city =~ /\(.+\)/)
@@ -829,33 +842,33 @@ module Geokit
829
842
  return NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
830
843
  end
831
844
  end
832
-
845
+
833
846
  # -------------------------------------------------------------------------------------------
834
847
  # The Multi Geocoder
835
- # -------------------------------------------------------------------------------------------
836
-
848
+ # -------------------------------------------------------------------------------------------
849
+
837
850
  # Provides methods to geocode with a variety of geocoding service providers, plus failover
838
851
  # among providers in the order you configure. When 2nd parameter is set 'true', perform
839
852
  # ip location lookup with 'address' as the ip address.
840
- #
853
+ #
841
854
  # Goal:
842
855
  # - homogenize the results of multiple geocoders
843
- #
856
+ #
844
857
  # Limitations:
845
858
  # - currently only provides the first result. Sometimes geocoders will return multiple results.
846
859
  # - currently discards the "accuracy" component of the geocoding calls
847
- class MultiGeocoder < Geocoder
860
+ class MultiGeocoder < Geocoder
848
861
 
849
862
  private
850
- # This method will call one or more geocoders in the order specified in the
863
+ # This method will call one or more geocoders in the order specified in the
851
864
  # configuration until one of the geocoders work.
852
- #
865
+ #
853
866
  # The failover approach is crucial for production-grade apps, but is rarely used.
854
- # 98% of your geocoding calls will be successful with the first call
867
+ # 98% of your geocoding calls will be successful with the first call
855
868
  def self.do_geocode(address, options = {})
856
869
  geocode_ip = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
857
870
  provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
858
-
871
+
859
872
  provider_order.each do |provider|
860
873
  begin
861
874
  klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
@@ -868,8 +881,8 @@ module Geokit
868
881
  # If we get here, we failed completely.
869
882
  GeoLoc.new
870
883
  end
871
-
872
- # This method will call one or more geocoders in the order specified in the
884
+
885
+ # This method will call one or more geocoders in the order specified in the
873
886
  # configuration until one of the geocoders work, only this time it's going
874
887
  # to try to reverse geocode a geographical point.
875
888
  def self.do_reverse_geocode(latlng)
@@ -885,6 +898,6 @@ module Geokit
885
898
  # If we get here, we failed completely.
886
899
  GeoLoc.new
887
900
  end
888
- end
901
+ end
889
902
  end
890
903
  end