lib/geokit/geocoders/bing.rb
module Geokit
module Geocoders
# Bing geocoder implementation. Requires the Geokit::Geocoders::bing variable to
# contain a Bing Maps API key. Conforms to the interface set by the Geocoder class.
class BingGeocoder < Geocoder
config :key, :options
self.secure = true
private
# Template method which does the geocode lookup.
def self.do_geocode(address, _=nil)
url = submit_url(address)
res = call_geocoder_service(url)
return GeoLoc.new unless net_adapter.success?(res)
xml = res.body.encode!('UTF-8', 'UTF-8', invalid: :replace)
parse :xml, xml
end
def self.submit_url(address)
culture = options && options[:culture]
culture_string = culture ? "&c=#{culture}" : ''
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
"#{protocol}://dev.virtualearth.net/REST/v1/Locations/#{CGI.escape(address_str)}?key=#{key}#{culture_string}&o=xml"
end
def self.parse_xml(xml)
return GeoLoc.new if xml.elements['//Response/StatusCode'].try(:text) != '200'
loc = nil
# Bing can return multiple results as //Location elements.
# iterate through each and extract each location as a geoloc
xml.each_element('//Location') do |l|
extracted_geoloc = extract_location(l)
loc.nil? ? loc = extracted_geoloc : loc.all.push(extracted_geoloc)
end
loc
end
# extracts a single geoloc from a //Location element in the bing results xml
def self.extract_location(xml)
loc = new_loc
set_address_components(loc, xml)
set_precision(loc, xml)
set_bounds(loc, xml)
loc.success = true
loc
end
XML_MAPPINGS = {
street_address: 'Address/AddressLine',
full_address: 'Address/FormattedAddress',
city: 'Address/Locality',
state: 'Address/AdminDistrict',
district: 'Address/AdminDistrict2',
zip: 'Address/PostalCode',
country: 'Address/CountryRegion',
lat: 'Point/Latitude',
lng: 'Point/Longitude',
}
def self.set_address_components(loc, xml)
set_mappings(loc, xml, XML_MAPPINGS)
end
ACCURACY_MAP = {
'High' => 8,
'Medium' => 5,
'Low' => 2,
}
PRECISION_MAP = {
'Sovereign' => 'country',
'CountryRegion' => 'country',
'AdminDivision1' => 'state',
'AdminDivision2' => 'state',
'PopulatedPlace' => 'city',
'Postcode1' => 'zip',
'Postcode2' => 'zip',
'RoadBlock' => 'street',
'Address' => 'address',
}
def self.set_precision(loc, xml)
if xml.elements['.//Confidence']
loc.accuracy = ACCURACY_MAP[xml.elements['.//Confidence'].text] || 0
end
if xml.elements['.//EntityType']
loc.precision = PRECISION_MAP[xml.elements['.//EntityType'].text] || 'unknown'
end
end
def self.set_bounds(loc, xml)
suggested_bounds = xml.elements['.//BoundingBox']
if suggested_bounds
bounds = suggested_bounds.elements
loc.suggested_bounds = Bounds.normalize(
[bounds['.//SouthLatitude'].text, bounds['.//WestLongitude'].text],
[bounds['.//NorthLatitude'].text, bounds['.//EastLongitude'].text])
end
end
end
end
end