Implementations/Libraries/Ruby

From Geohashing

Ruby Geohasher v1.2

This implementation IS FULLY 30W-compliant.

ScottKuma did this as a first excursion into Ruby. Yes, it's long. Yes, it's kinda clunky. However, IT WORKS!

v1.2 adds the ability to use unix-style commandline options in any order.

Any changes, suggestions, etc. are happily accepted!


#!/usr/bin/ruby
# == Synopsis
# geohasher: prints geohash coordinates for a given date & graticule
#
# == Usage
#
# geohasher [OPTION] ... 
# -d, --date YYYY-MM-DD:
#   the date in YYYY-MM-DD or YYYY/MM/DD format), 
#
# -t, --lat x:
#   the latitude "base coordinate"
#
# -g, --long x:
#   the longitude "base coordinate"
#
# -m, --dow x


require 'net/http'
require 'date'
require 'digest'
require 'getoptlong'
require 'rdoc/usage'

def hexFracToDecFrac(hexFracPart)
	#I wish I had a neat little algorithm to do this in one line - but this works!
	#NOTE:  do not feed the preceding "0." to this function....only the fractional part (the part after the decimal point)
	fracLen = hexFracPart.length
	fracPortion = hexFracPart[0..(fracLen-1)]
	fracLen = fracPortion.length
	myLen = (fracLen - 1)
	sum = 0	
	for i in (0 .. myLen)
		numSixteenths = fracPortion[i..i].to_i(16)
		conversionFactor = (16.**(i+1)).to_f
		conversionFactor = 1./conversionFactor
		sum = sum + ((numSixteenths) * conversionFactor)
	end
	return sum.to_s[2..8]
end

# Parse the command line options

opts = GetoptLong.new(
	['--help','-h',GetoptLong::NO_ARGUMENT ],
	['--lat','-t',GetoptLong::REQUIRED_ARGUMENT ],
	['--long','-g',GetoptLong::REQUIRED_ARGUMENT ],
	['--dow','-m',GetoptLong::REQUIRED_ARGUMENT ],
	['--date','-d',GetoptLong::REQUIRED_ARGUMENT ]
)

# Set default items (still parsing...)

myLat = "39"	# Change me to set the desired default graticule's latitude
myLong = "-84"	# Change me to set the desired default graticule's longitude
t=Time.now
myDate = t.strftime("%Y-%m-%d")
dow = ""

begin
opts.each do |opt,arg|
	case opt
		when '--help'
			RDoc::usage
		when '--lat'
			myLat = arg
		when '--long'
			myLong = arg
		when '--dow'
			dow = arg
		when '--date'
			myDate = arg
	end
end
rescue
  RDoc::usage
end


dateSplit = ''
if myDate.split('/').length == 3
	dateSplit = myDate.split('/')
else
	dateSplit = myDate.split('-')
end

myDate = Date.civil(dateSplit[0].to_i, dateSplit[1].to_i, dateSplit[2].to_i)

#fix for the "-30 rule"
fixDate = Date.civil(2008,05,25)
timeDelta = 0
if myLong.to_i > -30 and (myDate > fixDate)
	puts "-30 rule in effect!"
	timeDelta = 1
end

algorithmEncodedDate = myDate.to_s
urlEncodedDate = (myDate - timeDelta).strftime('%Y/%m/%d')

baseURL = 'carabiner.peeron.com'
basePath = '/xkcd/map/data/'
fullPath = basePath + urlEncodedDate
if dow==""
  Net::HTTP.start(baseURL,80) do |http|
    dow = http.get(fullPath).body
  end
end

if !dow.include? "404 Not Found"
	ghash = algorithmEncodedDate+'-'+dow
	digest = Digest::MD5.hexdigest(ghash)
	digest1 = digest[0..15]
	digest2 = digest[16..31]
	puts "(" + myLat + "." + hexFracToDecFrac(digest1) + ", " + myLong + "." + hexFracToDecFrac(digest2) + ")"
else 
	puts "Dow information not available for "+urlEncodedDate
end

Bugs

Dan Q used this as inspiration for his own implementation (underway), but notes that return sum.to_s[2..8] is truncating the resulting coordinates, rather than rounding them, which can result in a small error. This should probably instead be sprintf('%.08f', sum)[2..], which rounds to 8 decimal places before cutting off the "0." from the front.