andre-geokit 1.2.2 → 1.2.3

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.
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