geokit 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|