Last active
July 13, 2016 15:54
-
-
Save glennfu/9f9755960c7f94bdc2dbfca40a8515a4 to your computer and use it in GitHub Desktop.
I used this to help me parse some locations where the default Geokit wasn't enough
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'geokit' | |
module Geokit | |
module IpGeocodeLookup | |
# Overriding this to only store for 2 hours instead of 30 days | |
def store_ip_location | |
return if params[:lat] && params[:lng] | |
unless session[:geo_location] | |
loc = retrieve_location_from_cookie_or_service | |
if loc | |
loc.provider = "IP" | |
session[:geo_location] = loc | |
end | |
end | |
cookies[:geo_location] = { :value => session[:geo_location].to_yaml, :expires => 2.hours.from_now } if session[:geo_location] | |
end | |
# Uses the stored location value from the cookie if it exists. If | |
# no cookie exists, calls out to the web service to get the location. | |
def retrieve_location_from_cookie_or_service | |
exception = nil | |
if cookies[:geo_location] | |
begin | |
return YAML.load(cookies[:geo_location]) | |
rescue Exception => e | |
# I hate this but I'm getting 'uninitialized constant Syck::Syck' and can't make it stop otherwise | |
exception = e | |
end | |
end | |
location = Geocoders::MultiGeocoder.geocode(get_ip_address) | |
if exception | |
notify_airbrake(exception) | |
end | |
return location.success ? location : nil | |
end | |
end | |
module Geocoders | |
class GoogleGeocoder3 < Geocoder | |
# Patch this to fill .sublocality | |
def self.json2GeoLoc(json, address="") | |
ret=nil | |
results = MultiJson.decode(json) | |
if results['status'] == 'OVER_QUERY_LIMIT' | |
raise Geokit::Geocoders::TooManyQueriesError | |
end | |
if results['status'] == 'ZERO_RESULTS' | |
return GeoLoc.new | |
end | |
# this should probably be smarter. | |
if !results['status'] == 'OK' | |
raise Geokit::Geocoders::GeocodeError | |
end | |
# location_type stores additional data about the specified location. | |
# The following values are currently supported: | |
# "ROOFTOP" indicates that the returned result is a precise geocode | |
# for which we have location information accurate down to street | |
# address precision. | |
# "RANGE_INTERPOLATED" indicates that the returned result reflects an | |
# approximation (usually on a road) interpolated between two precise | |
# points (such as intersections). Interpolated results are generally | |
# returned when rooftop geocodes are unavailable for a street address. | |
# "GEOMETRIC_CENTER" indicates that the returned result is the | |
# geometric center of a result such as a polyline (for example, a | |
# street) or polygon (region). | |
# "APPROXIMATE" indicates that the returned result is approximate | |
# these do not map well. Perhaps we should guess better based on size | |
# of bounding box where it exists? Does it really matter? | |
accuracy = { | |
"ROOFTOP" => 9, | |
"RANGE_INTERPOLATED" => 8, | |
"GEOMETRIC_CENTER" => 5, | |
"APPROXIMATE" => 4 | |
} | |
@unsorted = [] | |
results['results'].each do |addr| | |
res = GeoLoc.new | |
res.provider = 'google3' | |
res.success = true | |
res.full_address = addr['formatted_address'] | |
addr['address_components'].each do |comp| | |
case | |
when comp['types'].include?("subpremise") | |
res.sub_premise = comp['short_name'] | |
when comp['types'].include?("street_number") | |
res.street_number = comp['short_name'] | |
when comp['types'].include?("route") | |
res.street_name = comp['long_name'] | |
when comp['types'].include?("locality") | |
res.city = comp['long_name'] | |
when comp['types'].include?("sublocality") | |
res.sublocality = comp['long_name'] | |
when comp['types'].include?("administrative_area_level_1") | |
res.state = comp['short_name'] | |
res.province = comp['short_name'] | |
when comp['types'].include?("postal_code") | |
res.zip = comp['long_name'] | |
when comp['types'].include?("country") | |
res.country_code = comp['short_name'] | |
res.country = comp['long_name'] | |
when comp['types'].include?("administrative_area_level_2") | |
res.district = comp['long_name'] | |
end | |
end | |
if res.street_name | |
res.street_address=[res.street_number,res.street_name].join(' ').strip | |
end | |
res.accuracy = accuracy[addr['geometry']['location_type']] | |
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy] | |
# try a few overrides where we can | |
if res.sub_premise | |
res.accuracy = 9 | |
res.precision = 'building' | |
end | |
if res.street_name && res.precision=='city' | |
res.precision = 'street' | |
res.accuracy = 7 | |
end | |
res.lat=addr['geometry']['location']['lat'].to_f | |
res.lng=addr['geometry']['location']['lng'].to_f | |
begin | |
ne=Geokit::LatLng.new( | |
addr['geometry']['viewport']['northeast']['lat'].to_f, | |
addr['geometry']['viewport']['northeast']['lng'].to_f | |
) | |
sw=Geokit::LatLng.new( | |
addr['geometry']['viewport']['southwest']['lat'].to_f, | |
addr['geometry']['viewport']['southwest']['lng'].to_f | |
) | |
res.suggested_bounds = Geokit::Bounds.new(sw,ne) | |
rescue => e | |
end | |
@unsorted << res | |
end | |
all = @unsorted.sort_by { |a| a.accuracy }.reverse | |
encoded = all.first | |
encoded.all = all | |
return encoded | |
end | |
def self.do_reverse_geocode(latlng) | |
latlng=LatLng.normalize(latlng) | |
res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(latlng.ll)}") | |
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK)) | |
json = res.body | |
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{json.to_s.force_encoding("UTF-8")}" | |
return self.json2GeoLoc(json) | |
end | |
end | |
class IpInfoDbGeocoder < Geocoder | |
private | |
def self.do_geocode(ip, options = {}) | |
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip) | |
response = self.call_geocoder_service("http://api.ipinfodb.com/v3/ip-city/?key=22648309e884fe7cfd411de23a4802445fc301cdbcc474d8444cf5a3bfc9eb14&ip=#{ip}") | |
return response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new | |
rescue Exception => e | |
logger.error "Caught an error during GeoPluginGeocoder geocoding call: " | |
logger.error e.message | |
logger.error e.backtrace.join("\n") | |
return GeoLoc.new | |
end | |
def self.parse_body(body) | |
body = body.split(";") | |
geo = GeoLoc.new | |
geo.provider='ipInfoDb' | |
geo.city = body[6] && body[6].titlecase | |
geo.state = body[5] && body[5].titlecase | |
geo.country_code = body[3] | |
geo.lat = body[8] | |
geo.lng = body[9] | |
geo.success = body[0] == "OK" | |
return geo | |
end | |
end | |
# https://github.com/geokit/geokit/issues/75 | |
class IpGeocoder | |
# require 'iconv' | |
def self.parse_body(body) | |
# yaml = YAML.load(Iconv.conv('ASCII//IGNORE', 'UTF8', body)) | |
yaml = YAML.load(body.encode("UTF-8")) # For Ruby 2.0 | |
res = GeoLoc.new | |
res.provider = 'hostip' | |
res.city, res.state = yaml['City'].split(', ') | |
country, res.country_code = yaml['Country'].split(' (') | |
res.lat = yaml['Latitude'] | |
res.lng = yaml['Longitude'] | |
res.country_code.chop! | |
res.success = !(res.city =~ /\(.+\)/) | |
res | |
end | |
end | |
end | |
# Patch this to record .sublocality | |
class GeoLoc < LatLng | |
attr_accessor :sublocality | |
def to_hash_with_sublocality | |
h = to_hash_without_sublocality | |
h[:sublocality] = self.sublocality.to_s | |
h | |
end | |
alias_method_chain :to_hash, :sublocality | |
end | |
end | |
Geokit::Geocoders.ip_provider_order << :ip_info_db |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment