geokit 1.7.1 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/.travis.yml +1 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +2 -1
- data/MIT-LICENSE +20 -0
- data/README.markdown +44 -39
- data/Rakefile +15 -0
- data/fixtures/vcr_cassettes/bing_full.yml +102 -0
- data/fixtures/vcr_cassettes/bing_full_au.yml +91 -0
- data/fixtures/vcr_cassettes/bing_full_de.yml +91 -0
- data/fixtures/vcr_cassettes/fcc_reverse_geocode.yml +37 -0
- data/fixtures/vcr_cassettes/free_geo_ip_geocode.yml +36 -0
- data/fixtures/vcr_cassettes/geo_plugin_geocode.yml +38 -0
- data/fixtures/vcr_cassettes/geonames_geocode.yml +304 -0
- data/fixtures/vcr_cassettes/{google3_city.yml → google_city.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_country_code_biased_result.yml → google_country_code_biased_result.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_full.yml → google_full.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_full_short.yml → google_full_short.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_language_response_fr.yml → google_language_response_fr.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_multi.yml → google_multi.yml} +0 -0
- data/fixtures/vcr_cassettes/{google3_reverse_madrid.yml → google_reverse_madrid.yml} +0 -0
- data/fixtures/vcr_cassettes/ripe_geocode.yml +66 -0
- data/fixtures/vcr_cassettes/ripe_geocode_au.yml +66 -0
- data/geokit.gemspec +1 -1
- data/lib/geokit.rb +5 -0
- data/lib/geokit/bounds.rb +96 -0
- data/lib/geokit/core_ext.rb +17 -0
- data/lib/geokit/geo_loc.rb +134 -0
- data/lib/geokit/geocoders.rb +48 -35
- data/lib/geokit/geocoders/base_ip.rb +43 -0
- data/lib/geokit/geocoders/bing.rb +101 -0
- data/lib/geokit/geocoders/ca_geocoder.rb +50 -0
- data/lib/geokit/{services → geocoders}/fcc.rb +17 -20
- data/lib/geokit/geocoders/free_geo_ip.rb +34 -0
- data/lib/geokit/geocoders/geo_plugin.rb +33 -0
- data/lib/geokit/geocoders/geonames.rb +53 -0
- data/lib/geokit/{services/google3.rb → geocoders/google.rb} +59 -57
- data/lib/geokit/geocoders/ip.rb +69 -0
- data/lib/geokit/geocoders/mapquest.rb +72 -0
- data/lib/geokit/geocoders/maxmind.rb +29 -0
- data/lib/geokit/geocoders/openstreetmap.rb +119 -0
- data/lib/geokit/geocoders/ripe.rb +41 -0
- data/lib/geokit/{services → geocoders}/us_geocoder.rb +15 -20
- data/lib/geokit/{services → geocoders}/yahoo.rb +52 -55
- data/lib/geokit/geocoders/yandex.rb +61 -0
- data/lib/geokit/inflectors.rb +1 -2
- data/lib/geokit/lat_lng.rb +129 -0
- data/lib/geokit/mappable.rb +41 -424
- data/lib/geokit/multi_geocoder.rb +6 -2
- data/lib/geokit/polygon.rb +46 -0
- data/lib/geokit/version.rb +1 -1
- data/test/helper.rb +2 -12
- data/test/test_base_geocoder.rb +0 -10
- data/test/test_bing_geocoder.rb +60 -0
- data/test/test_fcc_geocoder.rb +23 -0
- data/test/test_free_geo_ip_geocoder.rb +23 -0
- data/test/test_geo_plugin_geocoder.rb +23 -0
- data/test/test_geonames_geocoder.rb +23 -0
- data/test/test_google_geocoder.rb +208 -235
- data/test/test_maxmind_geocoder.rb +35 -4
- data/test/test_multi_geocoder.rb +3 -1
- data/test/test_ripe_geocoder.rb +35 -0
- data/test/test_yahoo_geocoder.rb +0 -12
- metadata +78 -52
- data/LICENSE +0 -25
- data/Manifest.txt +0 -21
- data/data/GeoLiteCity.dat +0 -0
- data/lib/geokit/services/ca_geocoder.rb +0 -55
- data/lib/geokit/services/geo_plugin.rb +0 -31
- data/lib/geokit/services/geonames.rb +0 -53
- data/lib/geokit/services/google.rb +0 -158
- data/lib/geokit/services/ip.rb +0 -103
- data/lib/geokit/services/maxmind.rb +0 -39
- data/lib/geokit/services/openstreetmap.rb +0 -119
- data/lib/geokit/services/ripe.rb +0 -32
- data/lib/geokit/services/yandex.rb +0 -51
- data/test/test_google_geocoder3.rb +0 -238
- data/test/test_google_reverse_geocoder.rb +0 -49
@@ -0,0 +1,43 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
class BaseIpGeocoder < Geocoder
|
4
|
+
# A number of non-routable IP ranges.
|
5
|
+
#
|
6
|
+
# --
|
7
|
+
# Sources for these:
|
8
|
+
# RFC 3330: Special-Use IPv4 Addresses
|
9
|
+
# The bogon list: http://www.cymru.com/Documents/bogon-list.html
|
10
|
+
|
11
|
+
NON_ROUTABLE_IP_RANGES = [
|
12
|
+
IPAddr.new('0.0.0.0/8'), # "This" Network
|
13
|
+
IPAddr.new('10.0.0.0/8'), # Private-Use Networks
|
14
|
+
IPAddr.new('14.0.0.0/8'), # Public-Data Networks
|
15
|
+
IPAddr.new('127.0.0.0/8'), # Loopback
|
16
|
+
IPAddr.new('169.254.0.0/16'), # Link local
|
17
|
+
IPAddr.new('172.16.0.0/12'), # Private-Use Networks
|
18
|
+
IPAddr.new('192.0.2.0/24'), # Test-Net
|
19
|
+
IPAddr.new('192.168.0.0/16'), # Private-Use Networks
|
20
|
+
IPAddr.new('198.18.0.0/15'), # Network Interconnect Device Benchmark Testing
|
21
|
+
IPAddr.new('224.0.0.0/4'), # Multicast
|
22
|
+
IPAddr.new('240.0.0.0/4') # Reserved for future use
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
def self.valid_ip?(ip)
|
26
|
+
ip?(ip) && !private_ip_address?(ip)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.ip?(ip)
|
30
|
+
/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Checks whether the IP address belongs to a private address range.
|
34
|
+
#
|
35
|
+
# This function is used to reduce the number of useless queries made to
|
36
|
+
# the geocoding service. Such queries can occur frequently during
|
37
|
+
# integration tests.
|
38
|
+
def self.private_ip_address?(ip)
|
39
|
+
NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
# Bing geocoder implementation. Requires the Geokit::Geocoders::bing variable to
|
4
|
+
# contain a Bing Maps API key. Conforms to the interface set by the Geocoder class.
|
5
|
+
class BingGeocoder < Geocoder
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Template method which does the geocode lookup.
|
10
|
+
def self.do_geocode(address)
|
11
|
+
url = submit_url(address)
|
12
|
+
res = call_geocoder_service(url)
|
13
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
14
|
+
xml = transcode_to_utf8(res.body)
|
15
|
+
logger.debug "Bing geocoding. Address: #{address}. Result: #{xml}"
|
16
|
+
parse :xml, xml
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.submit_url(address)
|
20
|
+
options = Geokit::Geocoders::bing_options
|
21
|
+
culture = options && options[:culture]
|
22
|
+
culture_string = culture ? "&c=#{culture}" : ''
|
23
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
24
|
+
"http://dev.virtualearth.net/REST/v1/Locations/#{URI.escape(address_str)}?key=#{Geokit::Geocoders::bing}#{culture_string}&o=xml"
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parse_xml(xml)
|
28
|
+
return GeoLoc.new if xml.elements['//Response/StatusCode'].try(:text) != '200'
|
29
|
+
loc = nil
|
30
|
+
# Bing can return multiple results as //Location elements.
|
31
|
+
# iterate through each and extract each location as a geoloc
|
32
|
+
xml.each_element('//Location') do |l|
|
33
|
+
extracted_geoloc = extract_location(l)
|
34
|
+
loc.nil? ? loc = extracted_geoloc : loc.all.push(extracted_geoloc)
|
35
|
+
end
|
36
|
+
loc
|
37
|
+
end
|
38
|
+
|
39
|
+
# extracts a single geoloc from a //Location element in the bing results xml
|
40
|
+
def self.extract_location(xml)
|
41
|
+
loc = GeoLoc.new
|
42
|
+
loc.provider = 'bing'
|
43
|
+
set_address_components(loc, xml)
|
44
|
+
set_precision(loc, xml)
|
45
|
+
set_bounds(loc, xml)
|
46
|
+
loc.success = true
|
47
|
+
loc
|
48
|
+
end
|
49
|
+
|
50
|
+
XML_MAPPINGS = {
|
51
|
+
:street_address => 'Address/AddressLine',
|
52
|
+
:full_address => 'Address/FormattedAddress',
|
53
|
+
:city => 'Address/Locality',
|
54
|
+
:state => 'Address/AdminDistrict',
|
55
|
+
:province => 'Address/AdminDistrict2',
|
56
|
+
:zip => 'Address/PostalCode',
|
57
|
+
:country => 'Address/CountryRegion',
|
58
|
+
:lat => 'Point/Latitude',
|
59
|
+
:lng => 'Point/Longitude'
|
60
|
+
}
|
61
|
+
|
62
|
+
def self.set_address_components(loc, xml)
|
63
|
+
set_mappings(loc, xml, XML_MAPPINGS)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.set_precision(loc, xml)
|
67
|
+
if xml.elements['.//Confidence']
|
68
|
+
loc.accuracy = case xml.elements['.//Confidence'].text
|
69
|
+
when 'High' then 8
|
70
|
+
when 'Medium' then 5
|
71
|
+
when 'Low' then 2
|
72
|
+
else 0
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if xml.elements['.//EntityType']
|
77
|
+
loc.precision = case xml.elements['.//EntityType'].text
|
78
|
+
when 'Sovereign' then 'country'
|
79
|
+
when 'AdminDivision1' then 'state'
|
80
|
+
when 'AdminDivision2' then 'state'
|
81
|
+
when 'PopulatedPlace' then 'city'
|
82
|
+
when 'Postcode1' then 'zip'
|
83
|
+
when 'Postcode2' then 'zip'
|
84
|
+
when 'RoadBlock' then 'street'
|
85
|
+
when 'Address' then 'address'
|
86
|
+
else 'unkown'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.set_bounds(loc, xml)
|
92
|
+
if suggested_bounds = xml.elements['.//BoundingBox']
|
93
|
+
bounds = suggested_bounds.elements
|
94
|
+
loc.suggested_bounds = Bounds.normalize(
|
95
|
+
[bounds['.//SouthLatitude'].text, bounds['.//WestLongitude'].text],
|
96
|
+
[bounds['.//NorthLatitude'].text, bounds['.//EastLongitude'].text])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
# Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
|
3
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
4
|
+
# interface set by the Geocoder class.
|
5
|
+
#
|
6
|
+
# Returns a response like:
|
7
|
+
# <?xml version="1.0" encoding="UTF-8" ?>
|
8
|
+
# <geodata>
|
9
|
+
# <latt>49.243086</latt>
|
10
|
+
# <longt>-123.153684</longt>
|
11
|
+
# </geodata>
|
12
|
+
module Geokit
|
13
|
+
module Geocoders
|
14
|
+
class CaGeocoder < Geocoder
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Template method which does the geocode lookup.
|
19
|
+
def self.do_geocode(loc)
|
20
|
+
raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless loc.is_a?(GeoLoc)
|
21
|
+
url = submit_url(loc)
|
22
|
+
res = call_geocoder_service(url)
|
23
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
24
|
+
xml = res.body
|
25
|
+
logger.debug "Geocoder.ca geocoding. Address: #{loc}. Result: #{xml}"
|
26
|
+
parse :xml, xml, loc
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parse_xml(xml, loc)
|
30
|
+
loc.lat = xml.elements['//latt'].text
|
31
|
+
loc.lng = xml.elements['//longt'].text
|
32
|
+
loc.success = true
|
33
|
+
loc
|
34
|
+
end
|
35
|
+
|
36
|
+
# Formats the request in the format acceptable by the CA geocoder.
|
37
|
+
def self.submit_url(loc)
|
38
|
+
args = []
|
39
|
+
args << "stno=#{loc.street_number}" if loc.street_address
|
40
|
+
args << "addresst=#{Geokit::Inflector::url_escape(loc.street_name)}" if loc.street_address
|
41
|
+
args << "city=#{Geokit::Inflector::url_escape(loc.city)}" if loc.city
|
42
|
+
args << "prov=#{loc.state}" if loc.state
|
43
|
+
args << "postal=#{loc.zip}" if loc.zip
|
44
|
+
args << "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
|
45
|
+
args << "geoit=xml"
|
46
|
+
'http://geocoder.ca/?' + args.join('&')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -6,11 +6,12 @@ module Geokit
|
|
6
6
|
# Template method which does the reverse-geocode lookup.
|
7
7
|
def self.do_reverse_geocode(latlng)
|
8
8
|
latlng=LatLng.normalize(latlng)
|
9
|
-
|
10
|
-
|
9
|
+
url = "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)}"
|
10
|
+
res = call_geocoder_service(url)
|
11
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
11
12
|
json = res.body
|
12
13
|
logger.debug "FCC reverse-geocoding. LL: #{latlng}. Result: #{json}"
|
13
|
-
|
14
|
+
parse :json, json
|
14
15
|
end
|
15
16
|
|
16
17
|
# Template method which does the geocode lookup.
|
@@ -26,11 +27,8 @@ module Geokit
|
|
26
27
|
# "State"=>{"name"=>"Indiana", "code"=>"IN", "FIPS"=>"18"},
|
27
28
|
# "status"=>"OK"}
|
28
29
|
|
29
|
-
def self.
|
30
|
-
|
31
|
-
results = MultiJson.load(json)
|
32
|
-
|
33
|
-
if results.has_key?('Err') and results['Err']["msg"] == 'There are no results for this location'
|
30
|
+
def self.parse_json(results)
|
31
|
+
if results.has_key?('Err') && results['Err']["msg"] == 'There are no results for this location'
|
34
32
|
return GeoLoc.new
|
35
33
|
end
|
36
34
|
# this should probably be smarter.
|
@@ -38,18 +36,17 @@ module Geokit
|
|
38
36
|
raise Geokit::Geocoders::GeocodeError
|
39
37
|
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
res
|
39
|
+
loc = GeoLoc.new
|
40
|
+
loc.provider = 'fcc'
|
41
|
+
loc.success = true
|
42
|
+
loc.precision = 'block'
|
43
|
+
loc.country_code = 'US'
|
44
|
+
loc.district = results['County']['name']
|
45
|
+
loc.district_fips = results['County']['FIPS']
|
46
|
+
loc.state = results['State']['code']
|
47
|
+
loc.state_fips = results['State']['FIPS']
|
48
|
+
loc.block_fips = results['Block']['FIPS']
|
49
|
+
loc
|
53
50
|
end
|
54
51
|
end
|
55
52
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
# Provides geocoding based upon an IP address. The underlying web service is freegeoip.net
|
4
|
+
class FreeGeoIpGeocoder < BaseIpGeocoder
|
5
|
+
private
|
6
|
+
|
7
|
+
def self.do_geocode(ip)
|
8
|
+
return GeoLoc.new unless valid_ip?(ip)
|
9
|
+
url = "http://freegeoip.net/xml/#{ip}"
|
10
|
+
res = call_geocoder_service(url)
|
11
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
12
|
+
parse :xml, res.body
|
13
|
+
end
|
14
|
+
|
15
|
+
XML_MAPPINGS = {
|
16
|
+
:city => 'City',
|
17
|
+
:state => 'RegionCode',
|
18
|
+
:zip => 'ZipCode',
|
19
|
+
:country_code => 'CountryCode',
|
20
|
+
:lat => 'Latitude',
|
21
|
+
:lng => 'Longitude'
|
22
|
+
}
|
23
|
+
|
24
|
+
def self.parse_xml(xml)
|
25
|
+
loc = GeoLoc.new
|
26
|
+
loc.provider = 'freegeoip'
|
27
|
+
set_mappings(loc, xml.elements['Response'], XML_MAPPINGS)
|
28
|
+
loc.success = !!loc.city && !loc.city.empty?
|
29
|
+
loc
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
# Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
|
4
|
+
class GeoPluginGeocoder < BaseIpGeocoder
|
5
|
+
private
|
6
|
+
|
7
|
+
def self.do_geocode(ip)
|
8
|
+
return GeoLoc.new unless valid_ip?(ip)
|
9
|
+
url = "http://www.geoplugin.net/xml.gp?ip=#{ip}"
|
10
|
+
res = call_geocoder_service(url)
|
11
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
12
|
+
parse :xml, res.body
|
13
|
+
end
|
14
|
+
|
15
|
+
XML_MAPPINGS = {
|
16
|
+
:city => 'geoplugin_city',
|
17
|
+
:state => 'geoplugin_region',
|
18
|
+
:country_code => 'geoplugin_countryCode',
|
19
|
+
:lat => 'geoplugin_latitude',
|
20
|
+
:lng => 'geoplugin_longitude'
|
21
|
+
}
|
22
|
+
|
23
|
+
def self.parse_xml(xml)
|
24
|
+
loc = GeoLoc.new
|
25
|
+
loc.provider = 'geoPlugin'
|
26
|
+
set_mappings(loc, xml.elements['geoPlugin'], XML_MAPPINGS)
|
27
|
+
loc.success = !!loc.city && !loc.city.empty?
|
28
|
+
loc
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
# Another geocoding web service
|
4
|
+
# http://www.geonames.org
|
5
|
+
class GeonamesGeocoder < Geocoder
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Template method which does the geocode lookup.
|
10
|
+
def self.do_geocode(address)
|
11
|
+
url = submit_url(address)
|
12
|
+
res = call_geocoder_service(url)
|
13
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
14
|
+
|
15
|
+
xml = res.body
|
16
|
+
logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
|
17
|
+
parse :xml, xml
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.submit_url(address)
|
21
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
22
|
+
# geonames need a space seperated search string
|
23
|
+
address_str.gsub!(/,/, " ")
|
24
|
+
params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
|
25
|
+
|
26
|
+
if Geokit::Geocoders::geonames
|
27
|
+
"http://ws.geonames.net#{params}&username=#{Geokit::Geocoders::geonames}"
|
28
|
+
else
|
29
|
+
"http://ws.geonames.org#{params}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
XML_MAPPINGS = {
|
34
|
+
:city => 'name',
|
35
|
+
:state => 'adminName1',
|
36
|
+
:zip => 'postalcode',
|
37
|
+
:country_code => 'countryCode',
|
38
|
+
:lat => 'lat',
|
39
|
+
:lng => 'lng'
|
40
|
+
}
|
41
|
+
|
42
|
+
def self.parse_xml(xml)
|
43
|
+
return GeoLoc.new unless xml.elements['geonames/totalResultsCount'].text.to_i > 0
|
44
|
+
loc = GeoLoc.new
|
45
|
+
loc.provider = 'genomes'
|
46
|
+
# only take the first result
|
47
|
+
set_mappings(loc, xml.elements['geonames/code'], XML_MAPPINGS)
|
48
|
+
loc.success = true
|
49
|
+
loc
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
module Geokit
|
2
2
|
module Geocoders
|
3
|
-
class
|
3
|
+
class GoogleGeocoder < Geocoder
|
4
4
|
|
5
5
|
private
|
6
6
|
# Template method which does the reverse-geocode lookup.
|
7
7
|
def self.do_reverse_geocode(latlng)
|
8
8
|
latlng=LatLng.normalize(latlng)
|
9
|
-
|
10
|
-
res =
|
11
|
-
return GeoLoc.new
|
9
|
+
url = submit_url("/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(latlng.ll)}")
|
10
|
+
res = call_geocoder_service(url)
|
11
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
12
12
|
json = res.body
|
13
13
|
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{CGI.escape(json)}"
|
14
|
-
|
14
|
+
parse :json, json
|
15
15
|
end
|
16
16
|
|
17
17
|
# Template method which does the geocode lookup.
|
@@ -43,15 +43,15 @@ module Geokit
|
|
43
43
|
bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
|
44
44
|
language_str = options[:language] ? "&language=#{options[:language]}" : ''
|
45
45
|
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
46
|
-
|
46
|
+
url = submit_url("/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(address_str)}#{bias_str}#{language_str}")
|
47
47
|
|
48
|
-
res =
|
48
|
+
res = call_geocoder_service(url)
|
49
49
|
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
50
50
|
|
51
51
|
json = res.body
|
52
52
|
logger.debug "Google geocoding. Address: #{address}. Result: #{CGI.escape(json)}"
|
53
53
|
|
54
|
-
|
54
|
+
parse :json, json
|
55
55
|
end
|
56
56
|
|
57
57
|
# This code comes from Googles Examples
|
@@ -64,13 +64,14 @@ module Geokit
|
|
64
64
|
# create a signature using the private key and the URL
|
65
65
|
rawSignature = OpenSSL::HMAC.digest('sha1', rawKey, urlToSign)
|
66
66
|
# encode the signature into base64 for url use form.
|
67
|
-
|
67
|
+
Base64.encode64(rawSignature).tr('+/','-_').gsub(/\n/, '')
|
68
68
|
end
|
69
69
|
|
70
70
|
|
71
71
|
def self.submit_url(query_string)
|
72
|
-
if
|
73
|
-
|
72
|
+
if Geokit::Geocoders::google_client_id && Geokit::Geocoders::google_cryptographic_key
|
73
|
+
channel = Geokit::Geocoders::google_channel ? "&channel=#{Geokit::Geocoders::google_channel}" : ''
|
74
|
+
urlToSign = query_string + "&client=#{Geokit::Geocoders::google_client_id}" + channel
|
74
75
|
signature = sign_gmap_bus_api_url(urlToSign, Geokit::Geocoders::google_cryptographic_key)
|
75
76
|
"http://maps.googleapis.com" + urlToSign + "&signature=#{signature}"
|
76
77
|
else
|
@@ -91,11 +92,9 @@ module Geokit
|
|
91
92
|
end
|
92
93
|
end
|
93
94
|
|
94
|
-
def self.
|
95
|
-
results = MultiJson.load(json)
|
96
|
-
|
95
|
+
def self.parse_json(results)
|
97
96
|
case results['status']
|
98
|
-
when 'OVER_QUERY_LIMIT' then raise Geokit::TooManyQueriesError
|
97
|
+
when 'OVER_QUERY_LIMIT' then raise Geokit::Geocoders::TooManyQueriesError
|
99
98
|
when 'ZERO_RESULTS' then return GeoLoc.new
|
100
99
|
end
|
101
100
|
# this should probably be smarter.
|
@@ -138,66 +137,69 @@ module Geokit
|
|
138
137
|
}
|
139
138
|
|
140
139
|
def self.single_json_to_geoloc(addr)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
140
|
+
loc = GeoLoc.new
|
141
|
+
loc.provider = 'google'
|
142
|
+
loc.success = true
|
143
|
+
loc.full_address = addr['formatted_address']
|
144
|
+
|
145
|
+
set_address_components(loc, addr)
|
146
|
+
set_precision(loc, addr)
|
147
|
+
if loc.street_name
|
148
|
+
loc.street_address=[loc.street_number, loc.street_name].join(' ').strip
|
149
|
+
end
|
150
|
+
|
151
|
+
ll = addr['geometry']['location']
|
152
|
+
loc.lat = ll['lat'].to_f
|
153
|
+
loc.lng = ll['lng'].to_f
|
154
|
+
|
155
|
+
viewport = addr['geometry']['viewport']
|
156
|
+
ne = Geokit::LatLng.from_json(viewport['northeast'])
|
157
|
+
sw = Geokit::LatLng.from_json(viewport['southwest'])
|
158
|
+
loc.suggested_bounds = Geokit::Bounds.new(sw, ne)
|
159
|
+
|
160
|
+
loc
|
161
|
+
end
|
145
162
|
|
163
|
+
def self.set_address_components(loc, addr)
|
146
164
|
addr['address_components'].each do |comp|
|
147
165
|
case
|
148
166
|
when comp['types'].include?("subpremise")
|
149
|
-
|
167
|
+
loc.sub_premise = comp['short_name']
|
150
168
|
when comp['types'].include?("street_number")
|
151
|
-
|
169
|
+
loc.street_number = comp['short_name']
|
152
170
|
when comp['types'].include?("route")
|
153
|
-
|
171
|
+
loc.street_name = comp['long_name']
|
154
172
|
when comp['types'].include?("locality")
|
155
|
-
|
173
|
+
loc.city = comp['long_name']
|
156
174
|
when comp['types'].include?("administrative_area_level_1")
|
157
|
-
|
158
|
-
|
175
|
+
loc.state = comp['short_name']
|
176
|
+
loc.province = comp['short_name']
|
159
177
|
when comp['types'].include?("postal_code")
|
160
|
-
|
178
|
+
loc.zip = comp['long_name']
|
161
179
|
when comp['types'].include?("country")
|
162
|
-
|
163
|
-
|
180
|
+
loc.country_code = comp['short_name']
|
181
|
+
loc.country = comp['long_name']
|
164
182
|
when comp['types'].include?("administrative_area_level_2")
|
165
|
-
|
183
|
+
loc.district = comp['long_name']
|
166
184
|
when comp['types'].include?('neighborhood')
|
167
|
-
|
185
|
+
loc.neighborhood = comp['short_name']
|
168
186
|
end
|
169
187
|
end
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.set_precision(loc, addr)
|
191
|
+
loc.accuracy = ACCURACY[addr['geometry']['location_type']]
|
192
|
+
loc.precision=%w{unknown country state state city zip zip+4 street address building}[loc.accuracy]
|
175
193
|
# try a few overrides where we can
|
176
|
-
if
|
177
|
-
|
178
|
-
|
194
|
+
if loc.sub_premise
|
195
|
+
loc.accuracy = 9
|
196
|
+
loc.precision = 'building'
|
179
197
|
end
|
180
|
-
if
|
181
|
-
|
182
|
-
|
198
|
+
if loc.street_name && loc.precision=='city'
|
199
|
+
loc.precision = 'street'
|
200
|
+
loc.accuracy = 7
|
183
201
|
end
|
184
|
-
|
185
|
-
res.lat=addr['geometry']['location']['lat'].to_f
|
186
|
-
res.lng=addr['geometry']['location']['lng'].to_f
|
187
|
-
|
188
|
-
ne=Geokit::LatLng.new(
|
189
|
-
addr['geometry']['viewport']['northeast']['lat'].to_f,
|
190
|
-
addr['geometry']['viewport']['northeast']['lng'].to_f
|
191
|
-
)
|
192
|
-
sw=Geokit::LatLng.new(
|
193
|
-
addr['geometry']['viewport']['southwest']['lat'].to_f,
|
194
|
-
addr['geometry']['viewport']['southwest']['lng'].to_f
|
195
|
-
)
|
196
|
-
res.suggested_bounds = Geokit::Bounds.new(sw,ne)
|
197
|
-
|
198
|
-
res
|
199
202
|
end
|
200
203
|
end
|
201
|
-
Google3Geocoder = GoogleGeocoder3
|
202
204
|
end
|
203
205
|
end
|