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