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.
- data/README.markdown +37 -19
- data/lib/geokit/geocoders.rb +172 -113
- metadata +1 -1
data/README.markdown
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
-
|
1
|
+
## GEOKIT GEM DESCRIPTION
|
2
2
|
|
3
|
-
|
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.
|
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.
|
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
|
-
|
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
|
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
|
-
|
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),
|
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
|
|
data/lib/geokit/geocoders.rb
CHANGED
@@ -38,17 +38,26 @@ module Geokit
|
|
38
38
|
end.tr(' ', '+')
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
|
+
# Contains a range of geocoders:
|
42
43
|
#
|
43
|
-
#
|
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
|
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
|
-
|
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.
|