Skip to content

Instantly share code, notes, and snippets.

@glennfu
Last active July 13, 2016 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save glennfu/9f9755960c7f94bdc2dbfca40a8515a4 to your computer and use it in GitHub Desktop.
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
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