andre-geokit 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.markdown +37 -19
  2. data/lib/geokit/geocoders.rb +172 -113
  3. metadata +1 -1
data/README.markdown CHANGED
@@ -1,20 +1,24 @@
1
- # Geokit gem
1
+ ## GEOKIT GEM DESCRIPTION
2
2
 
3
- * Geokit Documentation at Rubyforge [http://geokit.rubyforge.org](http://geokit.rubyforge.org).
4
- * Repository at Github: [http://github.com/andre/geokit-gem/tree/master](http://github.com/andre/geokit-gem/tree/master).
5
-
6
- ## DESCRIPTION:
7
-
8
- The Geokit gem provides the following:
3
+ The Geokit gem provides:
9
4
 
10
5
  * Distance calculations between two points on the earth. Calculate the distance in miles, kilometers, or nautical miles, with all the trigonometry abstracted away by GeoKit.
11
- * Geocoding from multiple providers. It currently supports Google, Yahoo, Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response structure from all of them. It also provides a fail-over mechanism, in case your input fails to geocode in one service.
6
+ * Geocoding from multiple providers. It currently supports Google, Yahoo, Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response structure from all of them.
7
+ It also provides a fail-over mechanism, in case your input fails to geocode in one service.
12
8
  * Rectangular bounds calculations: is a point within a given rectangular bounds?
13
9
  * Heading and midpoint calculations
14
10
 
15
- Combine this with gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master) to get location-based finders for your Rails app. Plugins for other web frameworks and ORMs will provide similar functionality.
11
+ Combine this with gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master) to get location-based finders for your Rails app.
12
+
13
+ * Geokit Documentation at Rubyforge [http://geokit.rubyforge.org](http://geokit.rubyforge.org).
14
+ * Repository at Github: [http://github.com/andre/geokit-gem/tree/master](http://github.com/andre/geokit-gem/tree/master).
15
+
16
+ ## INSTALL
16
17
 
17
- ## SYNOPSIS:
18
+ gem sources -a http://gems.github.com
19
+ sudo gem install andre-geokit
20
+
21
+ ## QUICK START
18
22
 
19
23
  irb> require 'rubygems'
20
24
  irb> require 'geokit'
@@ -37,14 +41,9 @@ Combine this with gem with the [geokit-rails plugin](http://github.com/andre/geo
37
41
 
38
42
  FYI, that `.ll` method means "latitude longitude".
39
43
 
40
- See the RDOC more more ... there is also operations on rectangular bounds (e.g., determining if a point is within bounds, find the center, etc).
41
-
42
- ## INSTALL:
44
+ See the RDOC more more ... there are also operations on rectangular bounds (e.g., determining if a point is within bounds, find the center, etc).
43
45
 
44
- * gem sources -a http://gems.github.com
45
- * sudo gem install andre-geokit
46
-
47
- ## Configuration
46
+ ## CONFIGURATION
48
47
 
49
48
  If you're using this gem by itself, here's how to set configurations:
50
49
 
@@ -99,7 +98,26 @@ If you're using this gem by itself, here's how to set configurations:
99
98
  # geocoder you are going to use.
100
99
  Geokit::Geocoders::provider_order = [:google,:us]
101
100
 
102
- If you're using this gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master), a template with these settings gets placed in your app's config/initializers directory.
101
+ If you're using this gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master), the plugin
102
+ creates a template with these settings and places it in `config/initializers/geokit_config.rb`.
103
+
104
+ ## SUPPORTED GEOCODERS
105
+
106
+ ### "regular" address geocoders
107
+ * Yahoo Geocoder - requires an API key.
108
+ * Geocoder.us - may require authentication if performing more than the free request limit.
109
+ * Geocoder.ca - for Canada; may require authentication as well.
110
+ * Geonames - a free geocoder
111
+
112
+ ### address geocoders that also provide reverse geocoding
113
+ * Google Geocoder - requires an API key.
114
+
115
+ ### IP address geocoders
116
+ * IP Geocoder - geocodes an IP address using hostip.info's web service.
117
+ * Geoplugin.net -- another IP address geocoder
118
+
119
+ ### The Multigeocoder
120
+ * Multi Geocoder - provides failover for the physical location geocoders.
103
121
 
104
122
  ## NOTES ON WHAT'S WHERE
105
123
 
@@ -122,7 +140,7 @@ geocoders.rb contains all the geocoder implemenations. All the gercoders
122
140
  inherit from a common base (class Geocoder) and implement the private method
123
141
  do_geocode.
124
142
 
125
- ## LICENSE:
143
+ ## LICENSE
126
144
 
127
145
  (The MIT License)
128
146
 
@@ -38,17 +38,26 @@ module Geokit
38
38
  end.tr(' ', '+')
39
39
  end
40
40
  end
41
- # Contains a set of geocoders which can be used independently if desired. The list contains:
41
+
42
+ # Contains a range of geocoders:
42
43
  #
43
- # * Google Geocoder - requires an API key.
44
+ # ### "regular" address geocoders
44
45
  # * Yahoo Geocoder - requires an API key.
45
46
  # * Geocoder.us - may require authentication if performing more than the free request limit.
46
47
  # * Geocoder.ca - for Canada; may require authentication as well.
48
+ # * Geonames - a free geocoder
49
+ #
50
+ # ### address geocoders that also provide reverse geocoding
51
+ # * Google Geocoder - requires an API key.
52
+ #
53
+ # ### IP address geocoders
47
54
  # * IP Geocoder - geocodes an IP address using hostip.info's web service.
55
+ # * Geoplugin.net -- another IP address geocoder
56
+ #
57
+ # ### The Multigeocoder
48
58
  # * Multi Geocoder - provides failover for the physical location geocoders.
49
59
  #
50
- # Some configuration is required for these geocoders and can be located in the environment
51
- # configuration files.
60
+ # Some of these geocoders require configuration. You don't have to provide it here. See the README.
52
61
  module Geocoders
53
62
  @@proxy_addr = nil
54
63
  @@proxy_port = nil
@@ -83,6 +92,10 @@ module Geokit
83
92
 
84
93
  # Error which is thrown in the event a geocoding error occurs.
85
94
  class GeocodeError < StandardError; end
95
+
96
+ # -------------------------------------------------------------------------------------------
97
+ # Geocoder Base class -- every geocoder should inherit from this
98
+ # -------------------------------------------------------------------------------------------
86
99
 
87
100
  # The Geocoder base class which defines the interface to be used by all
88
101
  # other geocoders.
@@ -151,6 +164,10 @@ module Geokit
151
164
  class_eval(src)
152
165
  end
153
166
  end
167
+
168
+ # -------------------------------------------------------------------------------------------
169
+ # "Regular" Address geocoders
170
+ # -------------------------------------------------------------------------------------------
154
171
 
155
172
  # Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
156
173
  # contain true or false based upon whether authentication is to occur. Conforms to the
@@ -203,115 +220,7 @@ module Geokit
203
220
  end
204
221
  end
205
222
 
206
- # Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
207
- # contain a Google API key. Conforms to the interface set by the Geocoder class.
208
- class GoogleGeocoder < Geocoder
209
-
210
- private
211
-
212
- # Template method which does the reverse-geocode lookup.
213
- def self.do_reverse_geocode(latlng)
214
- latlng=LatLng.normalize(latlng)
215
- 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")
216
- # 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"))
217
- return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
218
- xml = res.body
219
- logger.debug "Google reverse-geocoding. LL: #{latlng.ll}. Result: #{xml}"
220
- return self.xml2GeoLoc(xml)
221
- end
222
-
223
- # Template method which does the geocode lookup.
224
- def self.do_geocode(address)
225
- address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
226
- 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")
227
- # res = Net::HTTP.get_response(URI.parse("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8"))
228
- return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
229
- xml = res.body
230
- logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
231
- return self.xml2GeoLoc(xml)
232
- end
233
-
234
- def self.xml2GeoLoc(xml)
235
- doc=REXML::Document.new(xml)
236
-
237
- if doc.elements['//kml/Response/Status/code'].text == '200'
238
- res = GeoLoc.new
239
- coordinates=doc.elements['//coordinates'].text.to_s.split(',')
240
-
241
- #basics
242
- res.lat=coordinates[1]
243
- res.lng=coordinates[0]
244
- res.country_code=doc.elements['//CountryNameCode'].text if doc.elements['//CountryNameCode']
245
- res.provider='google'
246
-
247
- #extended -- false if not not available
248
- res.city = doc.elements['//LocalityName'].text if doc.elements['//LocalityName']
249
- res.state = doc.elements['//AdministrativeAreaName'].text if doc.elements['//AdministrativeAreaName']
250
- res.full_address = doc.elements['//address'].text if doc.elements['//address'] # google provides it
251
- res.zip = doc.elements['//PostalCodeNumber'].text if doc.elements['//PostalCodeNumber']
252
- res.street_address = doc.elements['//ThoroughfareName'].text if doc.elements['//ThoroughfareName']
253
- # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
254
- # For Google, 1=low accuracy, 8=high accuracy
255
- # old way -- address_details=doc.elements['//AddressDetails','urn:oasis:names:tc:ciq:xsdschema:xAL:2.0']
256
- address_details=doc.elements['//*[local-name() = "AddressDetails"]']
257
- accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
258
- res.precision=%w{unknown country state state city zip zip+4 street address}[accuracy]
259
- res.success=true
260
-
261
- return res
262
- else
263
- logger.info "Google was unable to geocode address: "+address
264
- return GeoLoc.new
265
- end
266
-
267
- rescue
268
- logger.error "Caught an error during Google geocoding call: "+$!
269
- return GeoLoc.new
270
- end
271
- end
272
-
273
- # Provides geocoding based upon an IP address. The underlying web service is a hostip.info
274
- # which sources their data through a combination of publicly available information as well
275
- # as community contributions.
276
- class IpGeocoder < Geocoder
277
-
278
- private
279
-
280
- # Given an IP address, returns a GeoLoc instance which contains latitude,
281
- # longitude, city, and country code. Sets the success attribute to false if the ip
282
- # parameter does not match an ip address.
283
- def self.do_geocode(ip)
284
- return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
285
- url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
286
- response = self.call_geocoder_service(url)
287
- response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
288
- rescue
289
- logger.error "Caught an error during HostIp geocoding call: "+$!
290
- return GeoLoc.new
291
- end
292
-
293
- # Converts the body to YAML since its in the form of:
294
- #
295
- # Country: UNITED STATES (US)
296
- # City: Sugar Grove, IL
297
- # Latitude: 41.7696
298
- # Longitude: -88.4588
299
- #
300
- # then instantiates a GeoLoc instance to populate with location data.
301
- def self.parse_body(body) # :nodoc:
302
- yaml = YAML.load(body)
303
- res = GeoLoc.new
304
- res.provider = 'hostip'
305
- res.city, res.state = yaml['City'].split(', ')
306
- country, res.country_code = yaml['Country'].split(' (')
307
- res.lat = yaml['Latitude']
308
- res.lng = yaml['Longitude']
309
- res.country_code.chop!
310
- res.success = res.city != "(Private Address)"
311
- res
312
- end
313
- end
314
-
223
+
315
224
  # Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
316
225
  # contain true or false based upon whether authentication is to occur. Conforms to the
317
226
  # interface set by the Geocoder class.
@@ -403,6 +312,8 @@ module Geokit
403
312
  end
404
313
  end
405
314
 
315
+ # Another geocoding web service
316
+ # http://www.geonames.org
406
317
  class GeonamesGeocoder < Geocoder
407
318
 
408
319
  private
@@ -450,6 +361,154 @@ module Geokit
450
361
  logger.error "Caught an error during Geonames geocoding call: "+$!
451
362
  end
452
363
  end
364
+
365
+ # -------------------------------------------------------------------------------------------
366
+ # Address geocoders that also provide reverse geocoding
367
+ # -------------------------------------------------------------------------------------------
368
+
369
+ # Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
370
+ # contain a Google API key. Conforms to the interface set by the Geocoder class.
371
+ class GoogleGeocoder < Geocoder
372
+
373
+ private
374
+
375
+ # Template method which does the reverse-geocode lookup.
376
+ def self.do_reverse_geocode(latlng)
377
+ latlng=LatLng.normalize(latlng)
378
+ 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")
379
+ # 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"))
380
+ return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
381
+ xml = res.body
382
+ logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
383
+ return self.xml2GeoLoc(xml)
384
+ end
385
+
386
+ # Template method which does the geocode lookup.
387
+ def self.do_geocode(address)
388
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
389
+ 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")
390
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
391
+ xml = res.body
392
+ logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
393
+ return self.xml2GeoLoc(xml)
394
+ end
395
+
396
+ def self.xml2GeoLoc(xml)
397
+ doc=REXML::Document.new(xml)
398
+
399
+ if doc.elements['//kml/Response/Status/code'].text == '200'
400
+ res = GeoLoc.new
401
+ coordinates=doc.elements['//coordinates'].text.to_s.split(',')
402
+
403
+ #basics
404
+ res.lat=coordinates[1]
405
+ res.lng=coordinates[0]
406
+ res.country_code=doc.elements['//CountryNameCode'].text if doc.elements['//CountryNameCode']
407
+ res.provider='google'
408
+
409
+ #extended -- false if not not available
410
+ res.city = doc.elements['//LocalityName'].text if doc.elements['//LocalityName']
411
+ res.state = doc.elements['//AdministrativeAreaName'].text if doc.elements['//AdministrativeAreaName']
412
+ res.full_address = doc.elements['//address'].text if doc.elements['//address'] # google provides it
413
+ res.zip = doc.elements['//PostalCodeNumber'].text if doc.elements['//PostalCodeNumber']
414
+ res.street_address = doc.elements['//ThoroughfareName'].text if doc.elements['//ThoroughfareName']
415
+ # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
416
+ # For Google, 1=low accuracy, 8=high accuracy
417
+ # old way -- address_details=doc.elements['//AddressDetails','urn:oasis:names:tc:ciq:xsdschema:xAL:2.0']
418
+ address_details=doc.elements['//*[local-name() = "AddressDetails"]']
419
+ accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
420
+ res.precision=%w{unknown country state state city zip zip+4 street address}[accuracy]
421
+ res.success=true
422
+
423
+ return res
424
+ else
425
+ logger.info "Google was unable to geocode address: "+address
426
+ return GeoLoc.new
427
+ end
428
+
429
+ rescue
430
+ logger.error "Caught an error during Google geocoding call: "+$!
431
+ return GeoLoc.new
432
+ end
433
+ end
434
+
435
+
436
+ # -------------------------------------------------------------------------------------------
437
+ # IP Geocoders
438
+ # -------------------------------------------------------------------------------------------
439
+
440
+ # Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
441
+ class GeoPluginGeocoder < Geocoder
442
+ private
443
+
444
+ def self.do_geocode(ip)
445
+ return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
446
+ response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
447
+ return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
448
+ rescue
449
+ logger.error "Caught an error during GeloPluginGeocoder geocoding call: "+$!
450
+ return GeoLoc.new
451
+ end
452
+
453
+ def self.parse_xml(xml)
454
+ xml = REXML::Document.new(xml)
455
+ geo = GeoLoc.new
456
+ geo.provider='geoPlugin'
457
+ geo.city = xml.elements['//geoplugin_city'].text
458
+ geo.state = xml.elements['//geoplugin_region'].text
459
+ geo.country_code = xml.elements['//geoplugin_countryCode'].text
460
+ geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
461
+ geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
462
+ geo.success = !geo.city.empty?
463
+ return geo
464
+ end
465
+ end
466
+
467
+ # Provides geocoding based upon an IP address. The underlying web service is a hostip.info
468
+ # which sources their data through a combination of publicly available information as well
469
+ # as community contributions.
470
+ class IpGeocoder < Geocoder
471
+
472
+ private
473
+
474
+ # Given an IP address, returns a GeoLoc instance which contains latitude,
475
+ # longitude, city, and country code. Sets the success attribute to false if the ip
476
+ # parameter does not match an ip address.
477
+ def self.do_geocode(ip)
478
+ return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
479
+ url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
480
+ response = self.call_geocoder_service(url)
481
+ response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
482
+ rescue
483
+ logger.error "Caught an error during HostIp geocoding call: "+$!
484
+ return GeoLoc.new
485
+ end
486
+
487
+ # Converts the body to YAML since its in the form of:
488
+ #
489
+ # Country: UNITED STATES (US)
490
+ # City: Sugar Grove, IL
491
+ # Latitude: 41.7696
492
+ # Longitude: -88.4588
493
+ #
494
+ # then instantiates a GeoLoc instance to populate with location data.
495
+ def self.parse_body(body) # :nodoc:
496
+ yaml = YAML.load(body)
497
+ res = GeoLoc.new
498
+ res.provider = 'hostip'
499
+ res.city, res.state = yaml['City'].split(', ')
500
+ country, res.country_code = yaml['Country'].split(' (')
501
+ res.lat = yaml['Latitude']
502
+ res.lng = yaml['Longitude']
503
+ res.country_code.chop!
504
+ res.success = !(res.city =~ /\(.+\)/)
505
+ res
506
+ end
507
+ end
508
+
509
+ # -------------------------------------------------------------------------------------------
510
+ # The Multi Geocoder
511
+ # -------------------------------------------------------------------------------------------
453
512
 
454
513
  # Provides methods to geocode with a variety of geocoding service providers, plus failover
455
514
  # among providers in the order you configure.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: andre-geokit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andre Lewis and Bill Eisenhauer