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