geokit 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +6 -6
- data/Rakefile +2 -0
- data/geokit.gemspec +15 -9
- data/lib/geokit.rb +1 -1
- data/lib/geokit/geocoders.rb +252 -1
- data/lib/geokit/mappable.rb +22 -11
- data/test/test_geoloc.rb +1 -1
- data/test/test_geoplugin_geocoder.rb +2 -2
- data/test/test_google_geocoder.rb +1 -1
- data/test/test_google_geocoder3.rb +536 -0
- data/test/test_ipgeocoder.rb +32 -10
- metadata +47 -10
data/README.markdown
CHANGED
@@ -54,7 +54,7 @@ If you're using this gem by itself, here are the configuration options:
|
|
54
54
|
# This is the timeout value in seconds to be used for calls to the geocoder web
|
55
55
|
# services. For no timeout at all, comment out the setting. The timeout unit
|
56
56
|
# is in seconds.
|
57
|
-
Geokit::Geocoders::
|
57
|
+
Geokit::Geocoders::request_timeout = 3
|
58
58
|
|
59
59
|
# These settings are used if web service calls must be routed through a proxy.
|
60
60
|
# These setting can be nil if not needed, otherwise, addr and port must be
|
@@ -142,26 +142,26 @@ The Google Geocoder sports a number of useful tricks that elevate it a little bi
|
|
142
142
|
|
143
143
|
In addition, you can use viewport or country code biasing to make sure the geocoders prefers results within a specific area. Say we wanted to geocode the city of Syracuse in Italy. A normal geocoding query would look like this:
|
144
144
|
|
145
|
-
irb> res = Geokit::
|
145
|
+
irb> res = Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse')
|
146
146
|
irb> res.full_address
|
147
147
|
=> "Syracuse, NY, USA"
|
148
148
|
|
149
149
|
Not exactly what we were looking for. We know that Syracuse is in Italy, so we can tell the Google Geocoder to prefer results from Italy first, and then wander the Syracuses of the world. To do that, we have to pass Italy's ccTLD (country code top-level domain) to the `:bias` option of the `geocode` method. You can find a comprehensive list of all ccTLDs here: http://en.wikipedia.org/wiki/CcTLD.
|
150
150
|
|
151
|
-
irb> res = Geokit::
|
151
|
+
irb> res = Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => 'it')
|
152
152
|
irb> res.full_address
|
153
153
|
=> "Syracuse, Italy"
|
154
154
|
|
155
155
|
Alternatively, we can speficy the geocoding bias as a bounding box object. Say we wanted to geocode the Winnetka district in Los Angeles.
|
156
156
|
|
157
|
-
irb> res = Geokit::
|
157
|
+
irb> res = Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka')
|
158
158
|
irb> res.full_address
|
159
159
|
=> "Winnetka, IL, USA"
|
160
160
|
|
161
161
|
Not it. What we can do is tell the geocoder to return results only from in and around LA.
|
162
162
|
|
163
|
-
irb> la_bounds = Geokit::
|
164
|
-
irb> res = Geokit::
|
163
|
+
irb> la_bounds = Geokit::Geocoders::GoogleGeocoder.geocode('Los Angeles').suggested_bounds
|
164
|
+
irb> res = Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => la_bounds)
|
165
165
|
irb> res.full_address
|
166
166
|
=> "Winnetka, California, USA"
|
167
167
|
|
data/Rakefile
CHANGED
data/geokit.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{geokit}
|
5
|
-
s.version = "1.
|
5
|
+
s.version = "1.6.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Andre Lewis and Bill Eisenhauer"]
|
@@ -10,7 +10,12 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.description = %q{Geokit Gem}
|
11
11
|
s.email = ["andre@earthcode.com / bill_eisenhauer@yahoo.com"]
|
12
12
|
s.extra_rdoc_files = ["Manifest.txt", "README.markdown"]
|
13
|
-
s.files = ["Manifest.txt", "README.markdown", "Rakefile", "lib/geokit/geocoders.rb", "lib/geokit.rb",
|
13
|
+
s.files = ["Manifest.txt", "README.markdown", "Rakefile", "lib/geokit/geocoders.rb", "lib/geokit.rb",
|
14
|
+
"lib/geokit/mappable.rb", "test/test_base_geocoder.rb", "test/test_bounds.rb",
|
15
|
+
"test/test_ca_geocoder.rb", "test/test_geoloc.rb", "test/test_google_geocoder3.rb",
|
16
|
+
"test/test_google_geocoder.rb", "test/test_latlng.rb", "test/test_multi_geocoder.rb",
|
17
|
+
"test/test_us_geocoder.rb", "test/test_yahoo_geocoder.rb"
|
18
|
+
]
|
14
19
|
s.has_rdoc = true
|
15
20
|
s.homepage = %q{http://geokit.rubyforge.org}
|
16
21
|
s.rdoc_options = ["--main", "README.markdown"]
|
@@ -18,15 +23,16 @@ Gem::Specification.new do |s|
|
|
18
23
|
s.rubyforge_project = %q{geokit}
|
19
24
|
s.rubygems_version = %q{1.3.5}
|
20
25
|
s.summary = %q{none}
|
21
|
-
s.test_files = [
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
s.test_files = [
|
27
|
+
"test/test_base_geocoder.rb", "test/test_bounds.rb", "test/test_ca_geocoder.rb",
|
28
|
+
"test/test_geoloc.rb", "test/test_geoplugin_geocoder.rb", "test/test_google_geocoder3.rb",
|
29
|
+
"test/test_google_geocoder.rb", "test/test_google_reverse_geocoder.rb", "test/test_inflector.rb",
|
30
|
+
"test/test_ipgeocoder.rb", "test/test_latlng.rb", "test/test_multi_geocoder.rb",
|
31
|
+
"test/test_multi_ip_geocoder.rb", "test/test_us_geocoder.rb", "test/test_yahoo_geocoder.rb"
|
32
|
+
]
|
26
33
|
if s.respond_to? :specification_version then
|
27
34
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
28
35
|
s.specification_version = 2
|
29
36
|
end
|
37
|
+
s.add_dependency(%q{json_pure})
|
30
38
|
end
|
31
|
-
|
32
|
-
|
data/lib/geokit.rb
CHANGED
data/lib/geokit/geocoders.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
require 'net/http'
|
2
|
+
require 'ipaddr'
|
2
3
|
require 'rexml/document'
|
3
4
|
require 'yaml'
|
4
5
|
require 'timeout'
|
5
6
|
require 'logger'
|
6
7
|
|
8
|
+
# do this just in case
|
9
|
+
begin
|
10
|
+
ActiveSupport.nil?
|
11
|
+
rescue NameError
|
12
|
+
require 'json/pure'
|
13
|
+
end
|
14
|
+
|
7
15
|
module Geokit
|
8
16
|
|
9
17
|
class TooManyQueriesError < StandardError; end
|
@@ -519,7 +527,220 @@ module Geokit
|
|
519
527
|
end
|
520
528
|
end
|
521
529
|
|
530
|
+
class GoogleGeocoder3 < Geocoder
|
531
|
+
|
532
|
+
private
|
533
|
+
# Template method which does the reverse-geocode lookup.
|
534
|
+
def self.do_reverse_geocode(latlng)
|
535
|
+
latlng=LatLng.normalize(latlng)
|
536
|
+
res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(latlng.ll)}")
|
537
|
+
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
538
|
+
json = res.body
|
539
|
+
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{json}"
|
540
|
+
return self.json2GeoLoc(json)
|
541
|
+
end
|
522
542
|
|
543
|
+
# Template method which does the geocode lookup.
|
544
|
+
#
|
545
|
+
# Supports viewport/country code biasing
|
546
|
+
#
|
547
|
+
# ==== OPTIONS
|
548
|
+
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
549
|
+
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
550
|
+
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
551
|
+
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
552
|
+
# will be biased to results within the US (ccTLD .com).
|
553
|
+
#
|
554
|
+
# If you'd like the Google Geocoder to prefer results within a given viewport,
|
555
|
+
# you can pass a Geokit::Bounds object as the :bias value.
|
556
|
+
#
|
557
|
+
# ==== EXAMPLES
|
558
|
+
# # By default, the geocoder will return Syracuse, NY
|
559
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
|
560
|
+
# # With country code biasing, it returns Syracuse in Sicily, Italy
|
561
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
|
562
|
+
#
|
563
|
+
# # By default, the geocoder will return Winnetka, IL
|
564
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
|
565
|
+
# # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
|
566
|
+
# bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
|
567
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
|
568
|
+
def self.do_geocode(address, options = {})
|
569
|
+
bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
|
570
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
571
|
+
res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(address_str)}#{bias_str}")
|
572
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
573
|
+
json = res.body
|
574
|
+
logger.debug "Google geocoding. Address: #{address}. Result: #{json}"
|
575
|
+
return self.json2GeoLoc(json, address)
|
576
|
+
end
|
577
|
+
|
578
|
+
def self.construct_bias_string_from_options(bias)
|
579
|
+
if bias.is_a?(String) or bias.is_a?(Symbol)
|
580
|
+
# country code biasing
|
581
|
+
"®ion=#{bias.to_s.downcase}"
|
582
|
+
elsif bias.is_a?(Bounds)
|
583
|
+
# viewport biasing
|
584
|
+
Geokit::Inflector::url_escape("&bounds=#{bias.sw.to_s}|#{bias.ne.to_s}")
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
def self.json2GeoLoc(json, address="")
|
589
|
+
ret=nil
|
590
|
+
begin
|
591
|
+
results=::ActiveSupport::JSON.decode(json)
|
592
|
+
rescue NameError => e
|
593
|
+
results=JSON.parse(json)
|
594
|
+
end
|
595
|
+
|
596
|
+
|
597
|
+
if results['status'] == 'OVER_QUERY_LIMIT'
|
598
|
+
raise Geokit::TooManyQueriesError
|
599
|
+
end
|
600
|
+
if results['status'] == 'ZERO_RESULTS'
|
601
|
+
return GeoLoc.new
|
602
|
+
end
|
603
|
+
# this should probably be smarter.
|
604
|
+
if !results['status'] == 'OK'
|
605
|
+
raise Geokit::Geocoders::GeocodeError
|
606
|
+
end
|
607
|
+
# location_type stores additional data about the specified location.
|
608
|
+
# The following values are currently supported:
|
609
|
+
# "ROOFTOP" indicates that the returned result is a precise geocode
|
610
|
+
# for which we have location information accurate down to street
|
611
|
+
# address precision.
|
612
|
+
# "RANGE_INTERPOLATED" indicates that the returned result reflects an
|
613
|
+
# approximation (usually on a road) interpolated between two precise
|
614
|
+
# points (such as intersections). Interpolated results are generally
|
615
|
+
# returned when rooftop geocodes are unavailable for a street address.
|
616
|
+
# "GEOMETRIC_CENTER" indicates that the returned result is the
|
617
|
+
# geometric center of a result such as a polyline (for example, a
|
618
|
+
# street) or polygon (region).
|
619
|
+
# "APPROXIMATE" indicates that the returned result is approximate
|
620
|
+
|
621
|
+
# these do not map well. Perhaps we should guess better based on size
|
622
|
+
# of bounding box where it exists? Does it really matter?
|
623
|
+
accuracy = {
|
624
|
+
"ROOFTOP" => 9,
|
625
|
+
"RANGE_INTERPOLATED" => 8,
|
626
|
+
"GEOMETRIC_CENTER" => 5,
|
627
|
+
"APPROXIMATE" => 4
|
628
|
+
}
|
629
|
+
results['results'].sort_by{|a|accuracy[a['geometry']['location_type']]}.reverse.each do |addr|
|
630
|
+
res=GeoLoc.new
|
631
|
+
res.provider = 'google3'
|
632
|
+
res.success = true
|
633
|
+
res.full_address = addr['formatted_address']
|
634
|
+
addr['address_components'].each do |comp|
|
635
|
+
case
|
636
|
+
when comp['types'].include?("street_number")
|
637
|
+
res.street_number = comp['short_name']
|
638
|
+
when comp['types'].include?("route")
|
639
|
+
res.street_name = comp['long_name']
|
640
|
+
when comp['types'].include?("locality")
|
641
|
+
res.city = comp['long_name']
|
642
|
+
when comp['types'].include?("administrative_area_level_1")
|
643
|
+
res.state = comp['short_name']
|
644
|
+
res.province = comp['short_name']
|
645
|
+
when comp['types'].include?("postal_code")
|
646
|
+
res.zip = comp['long_name']
|
647
|
+
when comp['types'].include?("country")
|
648
|
+
res.country_code = comp['short_name']
|
649
|
+
res.country = comp['long_name']
|
650
|
+
when comp['types'].include?("administrative_area_level_2")
|
651
|
+
res.district = comp['long_name']
|
652
|
+
end
|
653
|
+
end
|
654
|
+
if res.street_name
|
655
|
+
res.street_address=[res.street_number,res.street_name].join(' ').strip
|
656
|
+
end
|
657
|
+
res.accuracy = accuracy[addr['geometry']['location_type']]
|
658
|
+
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
659
|
+
# try a few overrides where we can
|
660
|
+
if res.street_name && res.precision=='city'
|
661
|
+
res.precision = 'street'
|
662
|
+
res.accuracy = 7
|
663
|
+
end
|
664
|
+
|
665
|
+
res.lat=addr['geometry']['location']['lat'].to_f
|
666
|
+
res.lng=addr['geometry']['location']['lng'].to_f
|
667
|
+
|
668
|
+
ne=Geokit::LatLng.new(
|
669
|
+
addr['geometry']['viewport']['northeast']['lat'].to_f,
|
670
|
+
addr['geometry']['viewport']['northeast']['lng'].to_f
|
671
|
+
)
|
672
|
+
sw=Geokit::LatLng.new(
|
673
|
+
addr['geometry']['viewport']['southwest']['lat'].to_f,
|
674
|
+
addr['geometry']['viewport']['southwest']['lng'].to_f
|
675
|
+
)
|
676
|
+
res.suggested_bounds = Geokit::Bounds.new(sw,ne)
|
677
|
+
|
678
|
+
if ret
|
679
|
+
ret.all.push(res)
|
680
|
+
else
|
681
|
+
ret=res
|
682
|
+
end
|
683
|
+
end
|
684
|
+
return ret
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
class FCCGeocoder < Geocoder
|
689
|
+
|
690
|
+
private
|
691
|
+
# Template method which does the reverse-geocode lookup.
|
692
|
+
def self.do_reverse_geocode(latlng)
|
693
|
+
latlng=LatLng.normalize(latlng)
|
694
|
+
res = self.call_geocoder_service("http://data.fcc.gov/api/block/find?format=json&latitude=#{Geokit::Inflector::url_escape(latlng.lat.to_s)}&longitude=#{Geokit::Inflector::url_escape(latlng.lng.to_s)}")
|
695
|
+
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
696
|
+
json = res.body
|
697
|
+
logger.debug "FCC reverse-geocoding. LL: #{latlng}. Result: #{json}"
|
698
|
+
return self.json2GeoLoc(json)
|
699
|
+
end
|
700
|
+
|
701
|
+
# Template method which does the geocode lookup.
|
702
|
+
#
|
703
|
+
# ==== EXAMPLES
|
704
|
+
# ll=GeoKit::LatLng.new(40, -85)
|
705
|
+
# Geokit::Geocoders::FCCGeocoder.geocode(ll) #
|
706
|
+
|
707
|
+
# JSON result looks like this
|
708
|
+
# => {"County"=>{"name"=>"Wayne", "FIPS"=>"18177"},
|
709
|
+
# "Block"=>{"FIPS"=>"181770103002004"},
|
710
|
+
# "executionTime"=>"0.099",
|
711
|
+
# "State"=>{"name"=>"Indiana", "code"=>"IN", "FIPS"=>"18"},
|
712
|
+
# "status"=>"OK"}
|
713
|
+
|
714
|
+
def self.json2GeoLoc(json, address="")
|
715
|
+
ret=nil
|
716
|
+
begin
|
717
|
+
results=::ActiveSupport::JSON.decode(json)
|
718
|
+
rescue NameError => e
|
719
|
+
results=JSON.parse(json)
|
720
|
+
end
|
721
|
+
|
722
|
+
if results.has_key?('Err') and results['Err']["msg"] == 'There are no results for this location'
|
723
|
+
return GeoLoc.new
|
724
|
+
end
|
725
|
+
# this should probably be smarter.
|
726
|
+
if !results['status'] == 'OK'
|
727
|
+
raise Geokit::Geocoders::GeocodeError
|
728
|
+
end
|
729
|
+
|
730
|
+
res = GeoLoc.new
|
731
|
+
res.provider = 'fcc'
|
732
|
+
res.success = true
|
733
|
+
res.precision = 'block'
|
734
|
+
res.country_code = 'US'
|
735
|
+
res.district = results['County']['name']
|
736
|
+
res.district_fips = results['County']['FIPS']
|
737
|
+
res.state = results['State']['code']
|
738
|
+
res.state_fips = results['State']['FIPS']
|
739
|
+
res.block_fips = results['Block']['FIPS']
|
740
|
+
|
741
|
+
res
|
742
|
+
end
|
743
|
+
end
|
523
744
|
# -------------------------------------------------------------------------------------------
|
524
745
|
# IP Geocoders
|
525
746
|
# -------------------------------------------------------------------------------------------
|
@@ -556,14 +777,35 @@ module Geokit
|
|
556
777
|
# as community contributions.
|
557
778
|
class IpGeocoder < Geocoder
|
558
779
|
|
780
|
+
# A number of non-routable IP ranges.
|
781
|
+
#
|
782
|
+
# --
|
783
|
+
# Sources for these:
|
784
|
+
# RFC 3330: Special-Use IPv4 Addresses
|
785
|
+
# The bogon list: http://www.cymru.com/Documents/bogon-list.html
|
786
|
+
|
787
|
+
NON_ROUTABLE_IP_RANGES = [
|
788
|
+
IPAddr.new('0.0.0.0/8'), # "This" Network
|
789
|
+
IPAddr.new('10.0.0.0/8'), # Private-Use Networks
|
790
|
+
IPAddr.new('14.0.0.0/8'), # Public-Data Networks
|
791
|
+
IPAddr.new('127.0.0.0/8'), # Loopback
|
792
|
+
IPAddr.new('169.254.0.0/16'), # Link local
|
793
|
+
IPAddr.new('172.16.0.0/12'), # Private-Use Networks
|
794
|
+
IPAddr.new('192.0.2.0/24'), # Test-Net
|
795
|
+
IPAddr.new('192.168.0.0/16'), # Private-Use Networks
|
796
|
+
IPAddr.new('198.18.0.0/15'), # Network Interconnect Device Benchmark Testing
|
797
|
+
IPAddr.new('224.0.0.0/4'), # Multicast
|
798
|
+
IPAddr.new('240.0.0.0/4') # Reserved for future use
|
799
|
+
].freeze
|
800
|
+
|
559
801
|
private
|
560
802
|
|
561
803
|
# Given an IP address, returns a GeoLoc instance which contains latitude,
|
562
804
|
# longitude, city, and country code. Sets the success attribute to false if the ip
|
563
805
|
# parameter does not match an ip address.
|
564
806
|
def self.do_geocode(ip, options = {})
|
565
|
-
return GeoLoc.new if '0.0.0.0' == ip
|
566
807
|
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
808
|
+
return GeoLoc.new if self.private_ip_address?(ip)
|
567
809
|
url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
|
568
810
|
response = self.call_geocoder_service(url)
|
569
811
|
response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
|
@@ -592,6 +834,15 @@ module Geokit
|
|
592
834
|
res.success = !(res.city =~ /\(.+\)/)
|
593
835
|
res
|
594
836
|
end
|
837
|
+
|
838
|
+
# Checks whether the IP address belongs to a private address range.
|
839
|
+
#
|
840
|
+
# This function is used to reduce the number of useless queries made to
|
841
|
+
# the geocoding service. Such queries can occur frequently during
|
842
|
+
# integration tests.
|
843
|
+
def self.private_ip_address?(ip)
|
844
|
+
return NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
|
845
|
+
end
|
595
846
|
end
|
596
847
|
|
597
848
|
# -------------------------------------------------------------------------------------------
|
data/lib/geokit/mappable.rb
CHANGED
@@ -287,6 +287,8 @@ module Geokit
|
|
287
287
|
return thing
|
288
288
|
elsif thing.class.respond_to?(:acts_as_mappable) && thing.class.respond_to?(:distance_column_name)
|
289
289
|
return thing.to_lat_lng
|
290
|
+
elsif thing.respond_to? :to_lat_lng
|
291
|
+
return thing.to_lat_lng
|
290
292
|
end
|
291
293
|
|
292
294
|
raise ArgumentError.new("#{thing} (#{thing.class}) cannot be normalized to a LatLng. We tried interpreting it as an array, string, Mappable, etc., but no dice.")
|
@@ -342,22 +344,26 @@ module Geokit
|
|
342
344
|
|
343
345
|
# Location attributes. Full address is a concatenation of all values. For example:
|
344
346
|
# 100 Spear St, San Francisco, CA, 94101, US
|
345
|
-
|
347
|
+
# Street number and street name are extracted from the street address attribute if they don't exist
|
348
|
+
attr_accessor :street_number,:street_name,:street_address, :city, :state, :zip, :country_code, :country, :full_address, :all, :district, :province
|
346
349
|
# Attributes set upon return from geocoding. Success will be true for successful
|
347
350
|
# geocode lookups. The provider will be set to the name of the providing geocoder.
|
348
351
|
# Finally, precision is an indicator of the accuracy of the geocoding.
|
349
352
|
attr_accessor :success, :provider, :precision, :suggested_bounds
|
350
|
-
# Street number and street name are extracted from the street address attribute.
|
351
|
-
attr_reader :street_number, :street_name
|
352
353
|
# accuracy is set for Yahoo and Google geocoders, it is a numeric value of the
|
353
354
|
# precision. see http://code.google.com/apis/maps/documentation/geocoding/#GeocodingAccuracy
|
354
355
|
attr_accessor :accuracy
|
356
|
+
# FCC Attributes
|
357
|
+
attr_accessor :district_fips, :state_fips, :block_fips
|
358
|
+
|
355
359
|
|
356
360
|
# Constructor expects a hash of symbols to correspond with attributes.
|
357
361
|
def initialize(h={})
|
358
362
|
@all = [self]
|
359
363
|
|
360
364
|
@street_address=h[:street_address]
|
365
|
+
@street_number=nil
|
366
|
+
@street_name=nil
|
361
367
|
@city=h[:city]
|
362
368
|
@state=h[:state]
|
363
369
|
@zip=h[:zip]
|
@@ -385,21 +391,22 @@ module Geokit
|
|
385
391
|
@full_address ? @full_address : to_geocodeable_s
|
386
392
|
end
|
387
393
|
|
388
|
-
# Extracts the street number from the street address
|
389
|
-
# has a value.
|
394
|
+
# Extracts the street number from the street address where possible.
|
390
395
|
def street_number
|
391
|
-
street_address[/(\d*)/] if street_address
|
396
|
+
@street_number ||= street_address[/(\d*)/] if street_address
|
397
|
+
@street_number
|
392
398
|
end
|
393
399
|
|
394
|
-
# Returns the street name portion of the street address
|
400
|
+
# Returns the street name portion of the street address where possible
|
395
401
|
def street_name
|
396
|
-
|
402
|
+
@street_name||=street_address[street_number.length, street_address.length].strip if street_address
|
403
|
+
@street_name
|
397
404
|
end
|
398
405
|
|
399
406
|
# gives you all the important fields as key-value pairs
|
400
407
|
def hash
|
401
408
|
res={}
|
402
|
-
[:success,:lat,:lng,:country_code,:city,:state,:zip,:street_address,:province,:district,:provider,:full_address,:is_us?,:ll,:precision].each { |s| res[s] = self.send(s.to_s) }
|
409
|
+
[:success,:lat,:lng,:country_code,:city,:state,:zip,:street_address,:province,:district,:provider,:full_address,:is_us?,:ll,:precision,:district_fips,:state_fips,:block_fips].each { |s| res[s] = self.send(s.to_s) }
|
403
410
|
res
|
404
411
|
end
|
405
412
|
alias to_hash hash
|
@@ -411,8 +418,12 @@ module Geokit
|
|
411
418
|
|
412
419
|
# Sets the street address after capitalizing each word within the street address.
|
413
420
|
def street_address=(address)
|
414
|
-
|
415
|
-
|
421
|
+
if address and not ['google','google3'].include?(self.provider)
|
422
|
+
@street_address = Geokit::Inflector::titleize(address)
|
423
|
+
else
|
424
|
+
@street_address = address
|
425
|
+
end
|
426
|
+
end
|
416
427
|
|
417
428
|
# Returns a comma-delimited string consisting of the street address, city, state,
|
418
429
|
# zip, and country code. Only includes those attributes that are non-blank.
|
data/test/test_geoloc.rb
CHANGED
@@ -65,7 +65,7 @@ class GeoLocTest < Test::Unit::TestCase #:nodoc: all
|
|
65
65
|
@loc.zip = '94105'
|
66
66
|
@loc.country_code = 'US'
|
67
67
|
assert_equal(
|
68
|
-
"--- !ruby/object:Geokit::GeoLoc \ncity: San Francisco\ncountry_code: US\nfull_address: \nlat: \nlng: \nprecision: unknown\nprovince: \nstate: CA\nstreet_address: \nsuccess: false\nzip: \"94105\"\n",
|
68
|
+
"--- !ruby/object:Geokit::GeoLoc \ncity: San Francisco\ncountry_code: US\nfull_address: \nlat: \nlng: \nprecision: unknown\nprovince: \nstate: CA\nstreet_address: \nstreet_name: \nstreet_number: \nsuccess: false\nzip: \"94105\"\n",
|
69
69
|
@loc.to_yaml)
|
70
70
|
end
|
71
71
|
|
@@ -33,8 +33,8 @@ class IpGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
33
33
|
GeoKit::Geocoders::GeoPluginGeocoder.expects(:call_geocoder_service).with(url).returns(success)
|
34
34
|
location = GeoKit::Geocoders::GeoPluginGeocoder.geocode('200.150.38.66')
|
35
35
|
assert_not_nil location
|
36
|
-
assert_equal
|
37
|
-
assert_equal
|
36
|
+
assert_equal(-19.916700, location.lat)
|
37
|
+
assert_equal(-43.933300, location.lng)
|
38
38
|
assert_equal "Belo Horizonte", location.city
|
39
39
|
assert_equal "Minas Gerais", location.state
|
40
40
|
assert_equal "BR", location.country_code
|
@@ -186,7 +186,7 @@ class GoogleGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
186
186
|
|
187
187
|
assert_equal "Plaza de la Puerta del Sol, 28013, Madrid, Spain", res.full_address
|
188
188
|
assert_equal "28013", res.zip
|
189
|
-
assert_equal "Plaza
|
189
|
+
assert_equal "Plaza de la Puerta del Sol", res.street_address
|
190
190
|
end
|
191
191
|
|
192
192
|
def test_country_code_biasing
|
@@ -0,0 +1,536 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_base_geocoder')
|
2
|
+
|
3
|
+
Geokit::Geocoders::google = 'Google'
|
4
|
+
|
5
|
+
class GoogleGeocoder3Test < BaseGeocoderTest #:nodoc: all
|
6
|
+
|
7
|
+
GOOGLE3_FULL=%q/
|
8
|
+
{
|
9
|
+
"status": "OK",
|
10
|
+
"results": [ {
|
11
|
+
"types": [ "street_address" ],
|
12
|
+
"formatted_address": "100 Spear St, San Francisco, CA 94105, USA",
|
13
|
+
"address_components": [ {
|
14
|
+
"long_name": "100",
|
15
|
+
"short_name": "100",
|
16
|
+
"types": [ "street_number" ]
|
17
|
+
}, {
|
18
|
+
"long_name": "Spear St",
|
19
|
+
"short_name": "Spear St",
|
20
|
+
"types": [ "route" ]
|
21
|
+
}, {
|
22
|
+
"long_name": "San Francisco",
|
23
|
+
"short_name": "San Francisco",
|
24
|
+
"types": [ "locality", "political" ]
|
25
|
+
}, {
|
26
|
+
"long_name": "San Francisco",
|
27
|
+
"short_name": "San Francisco",
|
28
|
+
"types": [ "administrative_area_level_3", "political" ]
|
29
|
+
}, {
|
30
|
+
"long_name": "San Francisco",
|
31
|
+
"short_name": "San Francisco",
|
32
|
+
"types": [ "administrative_area_level_2", "political" ]
|
33
|
+
}, {
|
34
|
+
"long_name": "California",
|
35
|
+
"short_name": "CA",
|
36
|
+
"types": [ "administrative_area_level_1", "political" ]
|
37
|
+
}, {
|
38
|
+
"long_name": "United States",
|
39
|
+
"short_name": "US",
|
40
|
+
"types": [ "country", "political" ]
|
41
|
+
}, {
|
42
|
+
"long_name": "94105",
|
43
|
+
"short_name": "94105",
|
44
|
+
"types": [ "postal_code" ]
|
45
|
+
} ],
|
46
|
+
"geometry": {
|
47
|
+
"location": {
|
48
|
+
"lat": 37.7921509,
|
49
|
+
"lng": -122.3940000
|
50
|
+
},
|
51
|
+
"location_type": "ROOFTOP",
|
52
|
+
"viewport": {
|
53
|
+
"southwest": {
|
54
|
+
"lat": 37.7890033,
|
55
|
+
"lng": -122.3971476
|
56
|
+
},
|
57
|
+
"northeast": {
|
58
|
+
"lat": 37.7952985,
|
59
|
+
"lng": -122.3908524
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
} ]
|
64
|
+
}
|
65
|
+
/.strip
|
66
|
+
|
67
|
+
GOOGLE3_CITY=%q/
|
68
|
+
{
|
69
|
+
"status": "OK",
|
70
|
+
"results": [ {
|
71
|
+
"types": [ "locality", "political" ],
|
72
|
+
"formatted_address": "San Francisco, CA, USA",
|
73
|
+
"address_components": [ {
|
74
|
+
"long_name": "San Francisco",
|
75
|
+
"short_name": "San Francisco",
|
76
|
+
"types": [ "locality", "political" ]
|
77
|
+
}, {
|
78
|
+
"long_name": "San Francisco",
|
79
|
+
"short_name": "San Francisco",
|
80
|
+
"types": [ "administrative_area_level_2", "political" ]
|
81
|
+
}, {
|
82
|
+
"long_name": "California",
|
83
|
+
"short_name": "CA",
|
84
|
+
"types": [ "administrative_area_level_1", "political" ]
|
85
|
+
}, {
|
86
|
+
"long_name": "United States",
|
87
|
+
"short_name": "US",
|
88
|
+
"types": [ "country", "political" ]
|
89
|
+
} ],
|
90
|
+
"geometry": {
|
91
|
+
"location": {
|
92
|
+
"lat": 37.7749295,
|
93
|
+
"lng": -122.4194155
|
94
|
+
},
|
95
|
+
"location_type": "APPROXIMATE",
|
96
|
+
"viewport": {
|
97
|
+
"southwest": {
|
98
|
+
"lat": 37.7043396,
|
99
|
+
"lng": -122.5474749
|
100
|
+
},
|
101
|
+
"northeast": {
|
102
|
+
"lat": 37.8454521,
|
103
|
+
"lng": -122.2913561
|
104
|
+
}
|
105
|
+
},
|
106
|
+
"bounds": {
|
107
|
+
"southwest": {
|
108
|
+
"lat": 37.7034000,
|
109
|
+
"lng": -122.5270000
|
110
|
+
},
|
111
|
+
"northeast": {
|
112
|
+
"lat": 37.8120000,
|
113
|
+
"lng": -122.3482000
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}
|
117
|
+
} ]
|
118
|
+
}
|
119
|
+
/.strip
|
120
|
+
GOOGLE3_MULTI=%q/
|
121
|
+
{
|
122
|
+
"status": "OK",
|
123
|
+
"results": [ {
|
124
|
+
"types": [ "street_address" ],
|
125
|
+
"formatted_address": "Via Sandro Pertini, 8, 20010 Mesero MI, Italy",
|
126
|
+
"address_components": [ {
|
127
|
+
"long_name": "8",
|
128
|
+
"short_name": "8",
|
129
|
+
"types": [ "street_number" ]
|
130
|
+
}, {
|
131
|
+
"long_name": "Via Sandro Pertini",
|
132
|
+
"short_name": "Via Sandro Pertini",
|
133
|
+
"types": [ "route" ]
|
134
|
+
}, {
|
135
|
+
"long_name": "Mesero",
|
136
|
+
"short_name": "Mesero",
|
137
|
+
"types": [ "locality", "political" ]
|
138
|
+
}, {
|
139
|
+
"long_name": "Milan",
|
140
|
+
"short_name": "MI",
|
141
|
+
"types": [ "administrative_area_level_2", "political" ]
|
142
|
+
}, {
|
143
|
+
"long_name": "Lombardy",
|
144
|
+
"short_name": "Lombardy",
|
145
|
+
"types": [ "administrative_area_level_1", "political" ]
|
146
|
+
}, {
|
147
|
+
"long_name": "Italy",
|
148
|
+
"short_name": "IT",
|
149
|
+
"types": [ "country", "political" ]
|
150
|
+
}, {
|
151
|
+
"long_name": "20010",
|
152
|
+
"short_name": "20010",
|
153
|
+
"types": [ "postal_code" ]
|
154
|
+
} ],
|
155
|
+
"geometry": {
|
156
|
+
"location": {
|
157
|
+
"lat": 45.4966218,
|
158
|
+
"lng": 8.8526940
|
159
|
+
},
|
160
|
+
"location_type": "RANGE_INTERPOLATED",
|
161
|
+
"viewport": {
|
162
|
+
"southwest": {
|
163
|
+
"lat": 45.4934754,
|
164
|
+
"lng": 8.8495559
|
165
|
+
},
|
166
|
+
"northeast": {
|
167
|
+
"lat": 45.4997707,
|
168
|
+
"lng": 8.8558512
|
169
|
+
}
|
170
|
+
},
|
171
|
+
"bounds": {
|
172
|
+
"southwest": {
|
173
|
+
"lat": 45.4966218,
|
174
|
+
"lng": 8.8526940
|
175
|
+
},
|
176
|
+
"northeast": {
|
177
|
+
"lat": 45.4966243,
|
178
|
+
"lng": 8.8527131
|
179
|
+
}
|
180
|
+
}
|
181
|
+
},
|
182
|
+
"partial_match": true
|
183
|
+
},
|
184
|
+
{
|
185
|
+
"types": [ "route" ],
|
186
|
+
"formatted_address": "Via Sandro Pertini, 20010 Ossona MI, Italy",
|
187
|
+
"address_components": [ {
|
188
|
+
"long_name": "Via Sandro Pertini",
|
189
|
+
"short_name": "Via Sandro Pertini",
|
190
|
+
"types": [ "route" ]
|
191
|
+
}, {
|
192
|
+
"long_name": "Ossona",
|
193
|
+
"short_name": "Ossona",
|
194
|
+
"types": [ "locality", "political" ]
|
195
|
+
}, {
|
196
|
+
"long_name": "Milan",
|
197
|
+
"short_name": "MI",
|
198
|
+
"types": [ "administrative_area_level_2", "political" ]
|
199
|
+
}, {
|
200
|
+
"long_name": "Lombardy",
|
201
|
+
"short_name": "Lombardy",
|
202
|
+
"types": [ "administrative_area_level_1", "political" ]
|
203
|
+
}, {
|
204
|
+
"long_name": "Italy",
|
205
|
+
"short_name": "IT",
|
206
|
+
"types": [ "country", "political" ]
|
207
|
+
}, {
|
208
|
+
"long_name": "20010",
|
209
|
+
"short_name": "20010",
|
210
|
+
"types": [ "postal_code" ]
|
211
|
+
} ],
|
212
|
+
"geometry": {
|
213
|
+
"location": {
|
214
|
+
"lat": 45.5074444,
|
215
|
+
"lng": 8.9023200
|
216
|
+
},
|
217
|
+
"location_type": "GEOMETRIC_CENTER",
|
218
|
+
"viewport": {
|
219
|
+
"southwest": {
|
220
|
+
"lat": 45.5043320,
|
221
|
+
"lng": 8.8990670
|
222
|
+
},
|
223
|
+
"northeast": {
|
224
|
+
"lat": 45.5106273,
|
225
|
+
"lng": 8.9053622
|
226
|
+
}
|
227
|
+
},
|
228
|
+
"bounds": {
|
229
|
+
"southwest": {
|
230
|
+
"lat": 45.5064427,
|
231
|
+
"lng": 8.9020024
|
232
|
+
},
|
233
|
+
"northeast": {
|
234
|
+
"lat": 45.5085166,
|
235
|
+
"lng": 8.9024268
|
236
|
+
}
|
237
|
+
}
|
238
|
+
}
|
239
|
+
}
|
240
|
+
]
|
241
|
+
}
|
242
|
+
/.strip
|
243
|
+
GOOGLE3_REVERSE_MADRID=%q/
|
244
|
+
{
|
245
|
+
"status": "OK",
|
246
|
+
"results": [ {
|
247
|
+
"types": [ ],
|
248
|
+
"formatted_address": "Calle de las Carretas, 28013 Madrid, Spain",
|
249
|
+
"address_components": [ {
|
250
|
+
"long_name": "Calle de las Carretas",
|
251
|
+
"short_name": "Calle de las Carretas",
|
252
|
+
"types": [ "route" ]
|
253
|
+
}, {
|
254
|
+
"long_name": "Madrid",
|
255
|
+
"short_name": "Madrid",
|
256
|
+
"types": [ "locality", "political" ]
|
257
|
+
}, {
|
258
|
+
"long_name": "Madrid",
|
259
|
+
"short_name": "M",
|
260
|
+
"types": [ "administrative_area_level_2", "political" ]
|
261
|
+
}, {
|
262
|
+
"long_name": "Madrid",
|
263
|
+
"short_name": "Madrid",
|
264
|
+
"types": [ "administrative_area_level_1", "political" ]
|
265
|
+
}, {
|
266
|
+
"long_name": "Spain",
|
267
|
+
"short_name": "ES",
|
268
|
+
"types": [ "country", "political" ]
|
269
|
+
}, {
|
270
|
+
"long_name": "28013",
|
271
|
+
"short_name": "28013",
|
272
|
+
"types": [ "postal_code" ]
|
273
|
+
} ],
|
274
|
+
"geometry": {
|
275
|
+
"location": {
|
276
|
+
"lat": 40.4166824,
|
277
|
+
"lng": -3.7033411
|
278
|
+
},
|
279
|
+
"location_type": "APPROXIMATE",
|
280
|
+
"viewport": {
|
281
|
+
"southwest": {
|
282
|
+
"lat": 40.4135351,
|
283
|
+
"lng": -3.7064880
|
284
|
+
},
|
285
|
+
"northeast": {
|
286
|
+
"lat": 40.4198303,
|
287
|
+
"lng": -3.7001927
|
288
|
+
}
|
289
|
+
},
|
290
|
+
"bounds": {
|
291
|
+
"southwest": {
|
292
|
+
"lat": 40.4166419,
|
293
|
+
"lng": -3.7033685
|
294
|
+
},
|
295
|
+
"northeast": {
|
296
|
+
"lat": 40.4167235,
|
297
|
+
"lng": -3.7033122
|
298
|
+
}
|
299
|
+
}
|
300
|
+
}
|
301
|
+
} ]
|
302
|
+
}
|
303
|
+
/
|
304
|
+
GOOGLE3_COUNTRY_CODE_BIASED_RESULT=%q/
|
305
|
+
{
|
306
|
+
"status": "OK",
|
307
|
+
"results": [ {
|
308
|
+
"types": [ "administrative_area_level_2", "political" ],
|
309
|
+
"formatted_address": "Syracuse, Italy",
|
310
|
+
"address_components": [ {
|
311
|
+
"long_name": "Syracuse",
|
312
|
+
"short_name": "SR",
|
313
|
+
"types": [ "administrative_area_level_2", "political" ]
|
314
|
+
}, {
|
315
|
+
"long_name": "Sicily",
|
316
|
+
"short_name": "Sicily",
|
317
|
+
"types": [ "administrative_area_level_1", "political" ]
|
318
|
+
}, {
|
319
|
+
"long_name": "Italy",
|
320
|
+
"short_name": "IT",
|
321
|
+
"types": [ "country", "political" ]
|
322
|
+
} ],
|
323
|
+
"geometry": {
|
324
|
+
"location": {
|
325
|
+
"lat": 37.0630218,
|
326
|
+
"lng": 14.9856176
|
327
|
+
},
|
328
|
+
"location_type": "APPROXIMATE",
|
329
|
+
"viewport": {
|
330
|
+
"southwest": {
|
331
|
+
"lat": 36.7775664,
|
332
|
+
"lng": 14.4733800
|
333
|
+
},
|
334
|
+
"northeast": {
|
335
|
+
"lat": 37.3474070,
|
336
|
+
"lng": 15.4978552
|
337
|
+
}
|
338
|
+
},
|
339
|
+
"bounds": {
|
340
|
+
"southwest": {
|
341
|
+
"lat": 36.6441736,
|
342
|
+
"lng": 14.7724913
|
343
|
+
},
|
344
|
+
"northeast": {
|
345
|
+
"lat": 37.4125978,
|
346
|
+
"lng": 15.3367367
|
347
|
+
}
|
348
|
+
}
|
349
|
+
}
|
350
|
+
} ]
|
351
|
+
}
|
352
|
+
/
|
353
|
+
GOOGLE3_TOO_MANY=%q/
|
354
|
+
{
|
355
|
+
"status": "OVER_QUERY_LIMIT"
|
356
|
+
}
|
357
|
+
/
|
358
|
+
def setup
|
359
|
+
super
|
360
|
+
@google_full_hash = {:street_address=>"100 Spear St", :city=>"San Francisco", :state=>"CA", :zip=>"94105", :country_code=>"US"}
|
361
|
+
@google_city_hash = {:city=>"San Francisco", :state=>"CA"}
|
362
|
+
|
363
|
+
@google_full_loc = Geokit::GeoLoc.new(@google_full_hash)
|
364
|
+
@google_city_loc = Geokit::GeoLoc.new(@google_city_hash)
|
365
|
+
end
|
366
|
+
|
367
|
+
def test_google3_full_address
|
368
|
+
response = MockSuccess.new
|
369
|
+
response.expects(:body).returns(GOOGLE3_FULL)
|
370
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
|
371
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
372
|
+
res=Geokit::Geocoders::GoogleGeocoder3.geocode(@address)
|
373
|
+
assert_equal "CA", res.state
|
374
|
+
assert_equal "San Francisco", res.city
|
375
|
+
assert_equal "37.7921509,-122.394", res.ll # slightly dif from yahoo
|
376
|
+
assert res.is_us?
|
377
|
+
assert_equal "100 Spear St, San Francisco, CA 94105, USA", res.full_address #slightly different from yahoo
|
378
|
+
assert_equal "google3", res.provider
|
379
|
+
end
|
380
|
+
|
381
|
+
def test_google3_full_address_with_geo_loc
|
382
|
+
response = MockSuccess.new
|
383
|
+
response.expects(:body).returns(GOOGLE3_FULL)
|
384
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@full_address_short_zip)}"
|
385
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
386
|
+
res=Geokit::Geocoders::GoogleGeocoder3.geocode(@google_full_loc)
|
387
|
+
assert_equal "CA", res.state
|
388
|
+
assert_equal "San Francisco", res.city
|
389
|
+
assert_equal "37.7921509,-122.394", res.ll # slightly dif from yahoo
|
390
|
+
assert res.is_us?
|
391
|
+
assert_equal "100 Spear St, San Francisco, CA 94105, USA", res.full_address #slightly different from yahoo
|
392
|
+
assert_equal "google3", res.provider
|
393
|
+
end
|
394
|
+
|
395
|
+
def test_google3_full_address_accuracy
|
396
|
+
response = MockSuccess.new
|
397
|
+
response.expects(:body).returns(GOOGLE3_FULL)
|
398
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@full_address_short_zip)}"
|
399
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
400
|
+
res=Geokit::Geocoders::GoogleGeocoder3.geocode(@google_full_loc)
|
401
|
+
assert_equal 9, res.accuracy
|
402
|
+
end
|
403
|
+
|
404
|
+
def test_google3_city
|
405
|
+
response = MockSuccess.new
|
406
|
+
response.expects(:body).returns(GOOGLE3_CITY)
|
407
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
|
408
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
409
|
+
res=Geokit::Geocoders::GoogleGeocoder3.do_geocode(@address)
|
410
|
+
assert_nil res.street_address
|
411
|
+
assert_equal "CA", res.state
|
412
|
+
assert_equal "San Francisco", res.city
|
413
|
+
assert_equal "37.7749295,-122.4194155", res.ll
|
414
|
+
assert res.is_us?
|
415
|
+
assert_equal "San Francisco, CA, USA", res.full_address
|
416
|
+
assert_equal "google3", res.provider
|
417
|
+
end
|
418
|
+
|
419
|
+
def test_google3_city_accuracy
|
420
|
+
response = MockSuccess.new
|
421
|
+
response.expects(:body).returns(GOOGLE3_CITY)
|
422
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
|
423
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
424
|
+
res=Geokit::Geocoders::GoogleGeocoder3.geocode(@address)
|
425
|
+
assert_equal 4, res.accuracy
|
426
|
+
end
|
427
|
+
|
428
|
+
def test_google3_city_with_geo_loc
|
429
|
+
response = MockSuccess.new
|
430
|
+
response.expects(:body).returns(GOOGLE3_CITY)
|
431
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
|
432
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
433
|
+
res=Geokit::Geocoders::GoogleGeocoder3.geocode(@google_city_loc)
|
434
|
+
assert_equal "CA", res.state
|
435
|
+
assert_equal "San Francisco", res.city
|
436
|
+
assert_equal "37.7749295,-122.4194155", res.ll
|
437
|
+
assert res.is_us?
|
438
|
+
assert_equal "San Francisco, CA, USA", res.full_address
|
439
|
+
assert_nil res.street_address
|
440
|
+
assert_equal "google3", res.provider
|
441
|
+
end
|
442
|
+
|
443
|
+
def test_google3_suggested_bounds
|
444
|
+
response = MockSuccess.new
|
445
|
+
response.expects(:body).returns(GOOGLE3_FULL)
|
446
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@full_address_short_zip)}"
|
447
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
448
|
+
res = Geokit::Geocoders::GoogleGeocoder3.geocode(@google_full_loc)
|
449
|
+
|
450
|
+
assert_instance_of Geokit::Bounds, res.suggested_bounds
|
451
|
+
assert_equal Geokit::Bounds.new(Geokit::LatLng.new(37.7890033, -122.3971476), Geokit::LatLng.new(37.7952985, -122.3908524)), res.suggested_bounds
|
452
|
+
end
|
453
|
+
|
454
|
+
def test_service_unavailable
|
455
|
+
response = MockFailure.new
|
456
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(@address)}"
|
457
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
458
|
+
assert !Geokit::Geocoders::GoogleGeocoder3.geocode(@google_city_loc).success
|
459
|
+
end
|
460
|
+
|
461
|
+
def test_multiple_results
|
462
|
+
#Geokit::Geocoders::GoogleGeocoder3.do_geocode('via Sandro Pertini 8, Ossona, MI')
|
463
|
+
response = MockSuccess.new
|
464
|
+
response.expects(:body).returns(GOOGLE3_MULTI)
|
465
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector.url_escape('via Sandro Pertini 8, Ossona, MI')}"
|
466
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
467
|
+
res=Geokit::Geocoders::GoogleGeocoder3.geocode('via Sandro Pertini 8, Ossona, MI')
|
468
|
+
assert_equal "Lombardy", res.state
|
469
|
+
assert_equal "Mesero", res.city
|
470
|
+
assert_equal "45.4966218,8.852694", res.ll
|
471
|
+
assert !res.is_us?
|
472
|
+
assert_equal "Via Sandro Pertini, 8, 20010 Mesero MI, Italy", res.full_address
|
473
|
+
assert_equal "8 Via Sandro Pertini", res.street_address
|
474
|
+
assert_equal "google3", res.provider
|
475
|
+
|
476
|
+
assert_equal 2, res.all.size
|
477
|
+
res = res.all[1]
|
478
|
+
assert_equal "Lombardy", res.state
|
479
|
+
assert_equal "Ossona", res.city
|
480
|
+
assert_equal "45.5074444,8.90232", res.ll
|
481
|
+
assert !res.is_us?
|
482
|
+
assert_equal "Via Sandro Pertini, 20010 Ossona MI, Italy", res.full_address
|
483
|
+
assert_equal "Via Sandro Pertini", res.street_address
|
484
|
+
assert_equal "google3", res.provider
|
485
|
+
end
|
486
|
+
#
|
487
|
+
def test_reverse_geocode
|
488
|
+
#Geokit::Geocoders::GoogleGeocoder3.do_reverse_geocode("40.4167413, -3.7032498")
|
489
|
+
madrid = Geokit::GeoLoc.new
|
490
|
+
madrid.lat, madrid.lng = "40.4167413", "-3.7032498"
|
491
|
+
response = MockSuccess.new
|
492
|
+
response.expects(:body).returns(GOOGLE3_REVERSE_MADRID)
|
493
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(madrid.ll)}"
|
494
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).
|
495
|
+
returns(response)
|
496
|
+
res=Geokit::Geocoders::GoogleGeocoder3.do_reverse_geocode(madrid.ll)
|
497
|
+
|
498
|
+
assert_equal madrid.lat.to_s.slice(1..5), res.lat.to_s.slice(1..5)
|
499
|
+
assert_equal madrid.lng.to_s.slice(1..5), res.lng.to_s.slice(1..5)
|
500
|
+
assert_equal "ES", res.country_code
|
501
|
+
assert_equal "google3", res.provider
|
502
|
+
|
503
|
+
assert_equal "Madrid", res.city
|
504
|
+
assert_equal "Madrid", res.state
|
505
|
+
|
506
|
+
assert_equal "Spain", res.country
|
507
|
+
assert_equal "street", res.precision
|
508
|
+
assert_equal true, res.success
|
509
|
+
|
510
|
+
assert_equal "Calle de las Carretas, 28013 Madrid, Spain", res.full_address
|
511
|
+
assert_equal "28013", res.zip
|
512
|
+
assert_equal "Calle de las Carretas", res.street_address
|
513
|
+
end
|
514
|
+
|
515
|
+
def test_country_code_biasing
|
516
|
+
response = MockSuccess.new
|
517
|
+
response.expects(:body).returns(GOOGLE3_COUNTRY_CODE_BIASED_RESULT)
|
518
|
+
|
519
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=Syracuse®ion=it"
|
520
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
521
|
+
biased_result = Geokit::Geocoders::GoogleGeocoder3.geocode('Syracuse', :bias => 'it')
|
522
|
+
|
523
|
+
assert_equal 'IT', biased_result.country_code
|
524
|
+
assert_equal 'Sicily', biased_result.state
|
525
|
+
end
|
526
|
+
|
527
|
+
def test_too_many_queries
|
528
|
+
response = MockSuccess.new
|
529
|
+
response.expects(:body).returns(GOOGLE3_TOO_MANY)
|
530
|
+
url = "http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector.url_escape(@address)}"
|
531
|
+
Geokit::Geocoders::GoogleGeocoder3.expects(:call_geocoder_service).with(url).returns(response)
|
532
|
+
assert_raise Geokit::TooManyQueriesError do
|
533
|
+
res=Geokit::Geocoders::GoogleGeocoder3.geocode(@address)
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
data/test/test_ipgeocoder.rb
CHANGED
@@ -4,8 +4,8 @@ require File.join(File.dirname(__FILE__), 'test_base_geocoder')
|
|
4
4
|
class IpGeocoderTest < BaseGeocoderTest #:nodoc: all
|
5
5
|
|
6
6
|
IP_FAILURE=<<-EOF
|
7
|
-
Country:
|
8
|
-
City: (
|
7
|
+
Country: SWITZERLAND (CH)
|
8
|
+
City: (Unknown City)
|
9
9
|
Latitude:
|
10
10
|
Longitude:
|
11
11
|
EOF
|
@@ -24,10 +24,22 @@ class IpGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
24
24
|
Longitude: 12.9167
|
25
25
|
EOF
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
PRIVATE_IPS_TO_TEST = [
|
28
|
+
'10.10.10.10',
|
29
|
+
'172.16.1.3',
|
30
|
+
'172.22.3.42',
|
31
|
+
'172.30.254.164',
|
32
|
+
'192.168.1.1',
|
33
|
+
'0.0.0.0',
|
34
|
+
'127.0.0.1',
|
35
|
+
'240.3.4.5',
|
36
|
+
'225.1.6.55'
|
37
|
+
].freeze
|
38
|
+
|
39
|
+
def setup
|
40
|
+
super
|
41
|
+
@success.provider = "hostip"
|
42
|
+
end
|
31
43
|
|
32
44
|
def test_successful_lookup
|
33
45
|
success = MockSuccess.new
|
@@ -64,14 +76,24 @@ class IpGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
64
76
|
def test_failed_lookup
|
65
77
|
failure = MockSuccess.new
|
66
78
|
failure.expects(:body).returns(IP_FAILURE)
|
67
|
-
url = 'http://api.hostip.info/get_html.php?ip=
|
79
|
+
url = 'http://api.hostip.info/get_html.php?ip=128.178.0.0&position=true'
|
68
80
|
GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).with(url).returns(failure)
|
69
|
-
location = GeoKit::Geocoders::IpGeocoder.geocode("
|
81
|
+
location = GeoKit::Geocoders::IpGeocoder.geocode("128.178.0.0")
|
70
82
|
assert_not_nil location
|
71
83
|
assert !location.success?
|
72
84
|
end
|
73
85
|
|
86
|
+
def test_private_ips
|
87
|
+
GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).never
|
88
|
+
PRIVATE_IPS_TO_TEST.each do |ip|
|
89
|
+
location = GeoKit::Geocoders::IpGeocoder.geocode(ip)
|
90
|
+
assert_not_nil location
|
91
|
+
assert !location.success?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
74
95
|
def test_invalid_ip
|
96
|
+
GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).never
|
75
97
|
location = GeoKit::Geocoders::IpGeocoder.geocode("blah")
|
76
98
|
assert_not_nil location
|
77
99
|
assert !location.success?
|
@@ -79,9 +101,9 @@ class IpGeocoderTest < BaseGeocoderTest #:nodoc: all
|
|
79
101
|
|
80
102
|
def test_service_unavailable
|
81
103
|
failure = MockFailure.new
|
82
|
-
url = 'http://api.hostip.info/get_html.php?ip=
|
104
|
+
url = 'http://api.hostip.info/get_html.php?ip=12.215.42.19&position=true'
|
83
105
|
GeoKit::Geocoders::IpGeocoder.expects(:call_geocoder_service).with(url).returns(failure)
|
84
|
-
location = GeoKit::Geocoders::IpGeocoder.geocode("
|
106
|
+
location = GeoKit::Geocoders::IpGeocoder.geocode("12.215.42.19")
|
85
107
|
assert_not_nil location
|
86
108
|
assert !location.success?
|
87
109
|
end
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geokit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 6
|
9
|
+
- 0
|
10
|
+
version: 1.6.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Andre Lewis
|
@@ -9,19 +15,41 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2011-05-27 00:00:00 -07:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
22
|
+
name: rubyforge
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
- 4
|
34
|
+
version: 2.0.4
|
17
35
|
type: :development
|
18
|
-
|
19
|
-
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: hoe
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
20
42
|
requirements:
|
21
43
|
- - ">="
|
22
44
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
45
|
+
hash: 21
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 6
|
49
|
+
- 1
|
50
|
+
version: 2.6.1
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
25
53
|
description: ""
|
26
54
|
email:
|
27
55
|
- andre@earthcode.com
|
@@ -54,6 +82,8 @@ files:
|
|
54
82
|
- test/test_multi_geocoder.rb
|
55
83
|
- test/test_us_geocoder.rb
|
56
84
|
- test/test_yahoo_geocoder.rb
|
85
|
+
- test/test_google_geocoder3.rb
|
86
|
+
- test/test_multi_ip_geocoder.rb
|
57
87
|
has_rdoc: true
|
58
88
|
homepage:
|
59
89
|
licenses: []
|
@@ -65,21 +95,27 @@ rdoc_options:
|
|
65
95
|
require_paths:
|
66
96
|
- lib
|
67
97
|
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
68
99
|
requirements:
|
69
100
|
- - ">="
|
70
101
|
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
103
|
+
segments:
|
104
|
+
- 0
|
71
105
|
version: "0"
|
72
|
-
version:
|
73
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
74
108
|
requirements:
|
75
109
|
- - ">="
|
76
110
|
- !ruby/object:Gem::Version
|
111
|
+
hash: 3
|
112
|
+
segments:
|
113
|
+
- 0
|
77
114
|
version: "0"
|
78
|
-
version:
|
79
115
|
requirements: []
|
80
116
|
|
81
117
|
rubyforge_project: geokit
|
82
|
-
rubygems_version: 1.3.
|
118
|
+
rubygems_version: 1.3.7
|
83
119
|
signing_key:
|
84
120
|
specification_version: 3
|
85
121
|
summary: Geokit provides geocoding and distance calculation in an easy-to-use API
|
@@ -90,6 +126,7 @@ test_files:
|
|
90
126
|
- test/test_geoloc.rb
|
91
127
|
- test/test_geoplugin_geocoder.rb
|
92
128
|
- test/test_google_geocoder.rb
|
129
|
+
- test/test_google_geocoder3.rb
|
93
130
|
- test/test_google_reverse_geocoder.rb
|
94
131
|
- test/test_inflector.rb
|
95
132
|
- test/test_ipgeocoder.rb
|