Skip to content

Instantly share code, notes, and snippets.

@samleb
Created November 11, 2010 18:46
Show Gist options
  • Select an option

  • Save samleb/672966 to your computer and use it in GitHub Desktop.

Select an option

Save samleb/672966 to your computer and use it in GitHub Desktop.
require 'fiber'
require 'em-http'
require 'em-synchrony/em-http'
require 'active_support/cache'
require 'active_support/core_ext/numeric/time'
require 'static_gmaps'
class GeoIp < Struct.new(:country, :city, :latitude, :longitude)
CACHE = ActiveSupport::Cache::MemoryStore.new(expires_in: 2.days)
HOSTIP_API_URL_FORMAT = 'http://api.hostip.info/get_html.php?ip=%s&position=true'.freeze
PRIVATE = /private/i
UNKNOWN = /unknown/i
include StaticGmaps
autoload :CGI, 'cgi'
class << self
def query(ip, timeout = 0.3)
cache(ip) do
url = HOSTIP_API_URL_FORMAT % ip
request = EM::HttpRequest.new(url).get(timeout: timeout)
response = request.response
new(parse_response(response)) if response.present?
end
end
protected
def cache(key)
unless result = CACHE.read(key)
result = yield
CACHE.write(key, result) if result
end
result
end
# Country: FRANCE (FR)
# City: (Unknown city)
# IP: 85.68.190.0
def parse_response(response)
attrs = {}
response.scan(/(\w+): (.*)$/) do |key, value|
next if key == 'IP'
key = key.downcase.to_sym
attrs[key] = parse_attribute(key, value)
end
attrs
end
def parse_attribute(key, value)
case key
when :latitude, :longitude
Float(value) rescue nil
when :country, :city
parse_country_or_city(value)
else
value
end
end
def parse_country_or_city(value)
case value
when PRIVATE
:private
when UNKNOWN
nil
else
value
end
end
end
def initialize(attrs)
attrs.each { |key, value| send("#{key}=", value) }
end
def to_hash
{
city: city,
country: country,
geo: {
latitude: latitude,
longitude: longitude
}
}
end
def found?
country.present? && !private?
end
def private?
country == :private
end
def geolocated?
coordinates.all?
end
def accurate?
city.present? || geolocated?
end
def coordinates
[latitude, longitude]
end
def location
[ city, country_name ].compact.join(', ')
end
def country_name
if country
country.scan(/\w+/)[0..-2].collect(&:capitalize).join(' ')
end
end
def google_maps_url(label = nil)
url = "http://maps.google.com/maps?q=#{CGI.escape(map_query)}"
url << "+(#{CGI.escape(label)})" if label
url
end
def static_map_url(options = {})
Map.new(map_options.merge(options)).url
end
protected
def marker
if geolocated?
Marker.new(latitude: latitude, longitude: longitude)
else
Marker.new(address: location)
end
end
def map_options
options = { center: map_query, zoom: map_zoom }
options[:markers] = [ marker ] if accurate?
options
end
def map_query
if geolocated?
coordinates.join(',')
else
location
end
end
def map_zoom
if geolocated?
12
elsif city
7
else
3
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment