geokit-premier 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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