├── README ├── ggeocode.gemspec ├── lib └── ggeocode.rb └── Rakefile /README: -------------------------------------------------------------------------------- 1 | NAME 2 | ggeocode 3 | 4 | SYNOPSIS 5 | require 'ggeocode' 6 | 7 | hash = GGeocode.geocode('boulder, co') 8 | hash = GGeocode.rgeocode('40.0149856,-105.2705456') 9 | 10 | INSTALL 11 | gem install ggeocode 12 | 13 | DESCRIPTION 14 | a simple interface to google's new geocoding api 15 | 16 | see http://code.google.com/apis/maps/documentation/geocoding/ 17 | -------------------------------------------------------------------------------- /ggeocode.gemspec: -------------------------------------------------------------------------------- 1 | ## ggeocode.gemspec 2 | # 3 | 4 | Gem::Specification::new do |spec| 5 | spec.name = "ggeocode" 6 | spec.version = "0.0.2" 7 | spec.platform = Gem::Platform::RUBY 8 | spec.summary = "ggeocode" 9 | spec.description = "description: ggeocode kicks the ass" 10 | 11 | spec.files = ["ggeocode.gemspec", "lib", "lib/ggeocode.rb", "Rakefile", "README"] 12 | spec.executables = [] 13 | 14 | spec.require_path = "lib" 15 | 16 | spec.has_rdoc = true 17 | spec.test_files = nil 18 | 19 | # spec.add_dependency 'lib', '>= version' 20 | spec.add_dependency 'map', '>= 2.3.0' 21 | spec.add_dependency 'yajl-ruby', '>= 0.7.9' 22 | 23 | spec.extensions.push(*[]) 24 | 25 | spec.rubyforge_project = "codeforpeople" 26 | spec.author = "Ara T. Howard" 27 | spec.email = "ara.t.howard@gmail.com" 28 | spec.homepage = "http://github.com/ahoward/ggeocode" 29 | end 30 | -------------------------------------------------------------------------------- /lib/ggeocode.rb: -------------------------------------------------------------------------------- 1 | module GGeocode 2 | ### ref: http://code.google.com/apis/maps/documentation/geocoding/ 3 | 4 | GGeocode::Version = '0.0.3' 5 | 6 | def GGeocode.version 7 | GGeocode::Version 8 | end 9 | 10 | require 'net/http' 11 | require 'uri' 12 | require 'cgi' 13 | 14 | begin 15 | require 'rubygems' 16 | gem 'yajl-ruby' 17 | gem 'map' 18 | rescue LoadError 19 | nil 20 | end 21 | 22 | require 'yajl/json_gem' 23 | require 'map' 24 | 25 | AddressPattern = /\w/iox 26 | 27 | def geocode(*args, &block) 28 | options = Map.options_for!(args) 29 | if options[:reverse] 30 | args.push(options) 31 | return reverse_geocode(*args, &block) 32 | end 33 | string = args.join(' ') 34 | address = address_for(string) 35 | response = get(url_for(:address => address)) 36 | result_for(response) 37 | end 38 | 39 | def reverse_geocode(*args, &block) 40 | options = Map.options_for!(args) 41 | string = args.join(' ') 42 | latlng = latlng_for(string) 43 | response = get(url_for(:latlng => latlng)) 44 | result_for(response) 45 | end 46 | alias_method('rgeocode', 'reverse_geocode') 47 | 48 | def GGeocode.call(*args, &block) 49 | options = Map.options_for!(args) 50 | string = args.join(' ') 51 | reverse = string !~ AddressPattern || options[:reverse] 52 | reverse ? GGeocode.reverse_geocode(string) : GGeocode.geocode(string) 53 | end 54 | 55 | def latlng_for(string) 56 | lat, lng = string.scan(/[^\s,]+/) 57 | latlng = [lat, lng].join(',') 58 | end 59 | 60 | def address_for(string) 61 | string.to_s.strip 62 | end 63 | 64 | def result_for(response) 65 | return nil unless response 66 | hash = JSON.parse(response.body) 67 | return nil unless hash['status']=='OK' 68 | map = Map.new 69 | map.extend(Response) 70 | map.response = response 71 | map['status'] = hash['status'] 72 | map['results'] = hash['results'] 73 | map 74 | end 75 | 76 | module Response 77 | attr_accessor :response 78 | def body 79 | response.body 80 | end 81 | alias_method('json', 'body') 82 | end 83 | 84 | Url = URI.parse("http://maps.google.com/maps/api/geocode/json?") 85 | 86 | def url_for(options = {}) 87 | options[:sensor] = false unless options.has_key?(:sensor) 88 | url = Url.dup 89 | url.query = query_for(options) 90 | url 91 | end 92 | 93 | def query_for(options = {}) 94 | pairs = [] 95 | options.each do |key, values| 96 | key = key.to_s 97 | values = [values].flatten 98 | values.each do |value| 99 | value = value.to_s 100 | if value.empty? 101 | pairs << [ CGI.escape(key) ] 102 | else 103 | pairs << [ CGI.escape(key), CGI.escape(value) ].join('=') 104 | end 105 | end 106 | end 107 | pairs.replace pairs.sort_by{|pair| pair.size} 108 | pairs.join('&') 109 | end 110 | 111 | def get(url) 112 | url = URI.parse(url.to_s) unless url.is_a?(URI) 113 | begin 114 | Net::HTTP.get_response(url) 115 | rescue SocketError, TimeoutError 116 | return nil 117 | end 118 | end 119 | 120 | extend(GGeocode) 121 | end 122 | 123 | module Kernel 124 | private 125 | def GGeocode(*args, &block) 126 | GGeocode.call(*args, &block) 127 | end 128 | end 129 | 130 | Ggeocode = GGeocode 131 | 132 | BEGIN { 133 | if defined?(GGeocode) 134 | Object.send(:remove_const, :GGeocode) 135 | Object.send(:remove_const, :Ggeocode) 136 | end 137 | } 138 | 139 | 140 | if $0 == __FILE__ 141 | require 'pp' 142 | 143 | pp(GGeocode.geocode('boulder, co')) 144 | pp(GGeocode.rgeocode('40.0149856,-105.2705456')) 145 | end 146 | 147 | 148 | 149 | 150 | 151 | __END__ 152 | 153 | { 154 | "status": "OK", 155 | 156 | "results": [ { 157 | "types": [ "locality", "political" ], 158 | 159 | "formatted_address": "Boulder, CO, USA", 160 | 161 | "address_components": [ { 162 | "long_name": "Boulder", 163 | "short_name": "Boulder", 164 | "types": [ "locality", "political" ] 165 | }, { 166 | "long_name": "Boulder", 167 | "short_name": "Boulder", 168 | "types": [ "administrative_area_level_2", "political" ] 169 | }, { 170 | "long_name": "Colorado", 171 | "short_name": "CO", 172 | "types": [ "administrative_area_level_1", "political" ] 173 | }, { 174 | "long_name": "United States", 175 | "short_name": "US", 176 | "types": [ "country", "political" ] 177 | } ], 178 | 179 | "geometry": { 180 | "location": { 181 | "lat": 40.0149856, 182 | "lng": -105.2705456 183 | }, 184 | "location_type": "APPROXIMATE", 185 | "viewport": { 186 | "southwest": { 187 | "lat": 39.9465862, 188 | "lng": -105.3986050 189 | }, 190 | "northeast": { 191 | "lat": 40.0833165, 192 | "lng": -105.1424862 193 | } 194 | }, 195 | "bounds": { 196 | "southwest": { 197 | "lat": 39.9640689, 198 | "lng": -105.3017580 199 | }, 200 | "northeast": { 201 | "lat": 40.0945509, 202 | "lng": -105.1781970 203 | } 204 | } 205 | } 206 | } ] 207 | } 208 | 209 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | This.rubyforge_project = 'codeforpeople' 2 | This.author = "Ara T. Howard" 3 | This.email = "ara.t.howard@gmail.com" 4 | This.homepage = "http://github.com/ahoward/#{ This.lib }" 5 | 6 | 7 | task :default do 8 | puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort) 9 | end 10 | 11 | task :test do 12 | run_tests! 13 | end 14 | 15 | namespace :test do 16 | task(:unit){ run_tests!(:unit) } 17 | task(:functional){ run_tests!(:functional) } 18 | task(:integration){ run_tests!(:integration) } 19 | end 20 | 21 | def run_tests!(which = nil) 22 | which ||= '**' 23 | test_dir = File.join(This.dir, "test") 24 | test_glob ||= File.join(test_dir, "#{ which }/**_test.rb") 25 | test_rbs = Dir.glob(test_glob).sort 26 | 27 | div = ('=' * 119) 28 | line = ('-' * 119) 29 | helper = "-r ./test/helper.rb" if test(?e, "./test/helper.rb") 30 | 31 | test_rbs.each_with_index do |test_rb, index| 32 | testno = index + 1 33 | command = "#{ This.ruby } -I ./lib -I ./test/lib #{ helper } #{ test_rb }" 34 | 35 | puts 36 | say(div, :color => :cyan, :bold => true) 37 | say("@#{ testno } => ", :bold => true, :method => :print) 38 | say(command, :color => :cyan, :bold => true) 39 | say(line, :color => :cyan, :bold => true) 40 | 41 | system(command) 42 | 43 | say(line, :color => :cyan, :bold => true) 44 | 45 | status = $?.exitstatus 46 | 47 | if status.zero? 48 | say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print) 49 | say("SUCCESS", :color => :green, :bold => true) 50 | else 51 | say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print) 52 | say("FAILURE", :color => :red, :bold => true) 53 | end 54 | say(line, :color => :cyan, :bold => true) 55 | 56 | exit(status) unless status.zero? 57 | end 58 | end 59 | 60 | 61 | task :gemspec do 62 | ignore_extensions = 'git', 'svn', 'tmp', /sw./, 'bak', 'gem' 63 | ignore_directories = %w[ pkg ] 64 | ignore_files = %w[ test/log ] 65 | 66 | shiteless = 67 | lambda do |list| 68 | list.delete_if do |entry| 69 | next unless test(?e, entry) 70 | extension = File.basename(entry).split(%r/[.]/).last 71 | ignore_extensions.any?{|ext| ext === extension} 72 | end 73 | list.delete_if do |entry| 74 | next unless test(?d, entry) 75 | dirname = File.expand_path(entry) 76 | ignore_directories.any?{|dir| File.expand_path(dir) == dirname} 77 | end 78 | list.delete_if do |entry| 79 | next unless test(?f, entry) 80 | filename = File.expand_path(entry) 81 | ignore_files.any?{|file| File.expand_path(file) == filename} 82 | end 83 | end 84 | 85 | lib = This.lib 86 | object = This.object 87 | version = This.version 88 | files = shiteless[Dir::glob("**/**")] 89 | executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)} 90 | has_rdoc = true #File.exist?('doc') 91 | test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb") 92 | summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass" 93 | description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass" 94 | 95 | if This.extensions.nil? 96 | This.extensions = [] 97 | extensions = This.extensions 98 | %w( Makefile configure extconf.rb ).each do |ext| 99 | extensions << ext if File.exists?(ext) 100 | end 101 | end 102 | extensions = [extensions].flatten.compact 103 | 104 | template = 105 | if test(?e, 'gemspec.erb') 106 | Template{ IO.read('gemspec.erb') } 107 | else 108 | Template { 109 | <<-__ 110 | ## #{ lib }.gemspec 111 | # 112 | 113 | Gem::Specification::new do |spec| 114 | spec.name = #{ lib.inspect } 115 | spec.version = #{ version.inspect } 116 | spec.platform = Gem::Platform::RUBY 117 | spec.summary = #{ lib.inspect } 118 | spec.description = #{ description.inspect } 119 | 120 | spec.files = #{ files.inspect } 121 | spec.executables = #{ executables.inspect } 122 | 123 | spec.require_path = "lib" 124 | 125 | spec.has_rdoc = #{ has_rdoc.inspect } 126 | spec.test_files = #{ test_files.inspect } 127 | 128 | # spec.add_dependency 'lib', '>= version' 129 | spec.add_dependency 'map', '>= 2.3.0' 130 | spec.add_dependency 'yajl-ruby', '>= 0.7.9' 131 | 132 | spec.extensions.push(*#{ extensions.inspect }) 133 | 134 | spec.rubyforge_project = #{ This.rubyforge_project.inspect } 135 | spec.author = #{ This.author.inspect } 136 | spec.email = #{ This.email.inspect } 137 | spec.homepage = #{ This.homepage.inspect } 138 | end 139 | __ 140 | } 141 | end 142 | 143 | Fu.mkdir_p(This.pkgdir) 144 | This.gemspec = File.join(This.dir, "#{ This.lib }.gemspec") #File.join(This.pkgdir, "gemspec.rb") 145 | open("#{ This.gemspec }", "w"){|fd| fd.puts(template)} 146 | end 147 | 148 | task :gem => [:clean, :gemspec] do 149 | Fu.mkdir_p(This.pkgdir) 150 | before = Dir['*.gem'] 151 | cmd = "gem build #{ This.gemspec }" 152 | `#{ cmd }` 153 | after = Dir['*.gem'] 154 | gem = ((after - before).first || after.first) or abort('no gem!') 155 | Fu.mv gem, This.pkgdir 156 | This.gem = File.basename(gem) 157 | end 158 | 159 | task :readme do 160 | samples = '' 161 | prompt = '~ > ' 162 | lib = This.lib 163 | version = This.version 164 | 165 | Dir['sample*/*'].sort.each do |sample| 166 | samples << "\n" << " <========< #{ sample } >========>" << "\n\n" 167 | 168 | cmd = "cat #{ sample }" 169 | samples << Util.indent(prompt + cmd, 2) << "\n\n" 170 | samples << Util.indent(`#{ cmd }`, 4) << "\n" 171 | 172 | cmd = "ruby #{ sample }" 173 | samples << Util.indent(prompt + cmd, 2) << "\n\n" 174 | 175 | cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'" 176 | samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n" 177 | end 178 | 179 | template = 180 | if test(?e, 'readme.erb') 181 | Template{ IO.read('readme.erb') } 182 | else 183 | Template { 184 | <<-__ 185 | NAME 186 | #{ lib } 187 | 188 | DESCRIPTION 189 | 190 | INSTALL 191 | gem install #{ lib } 192 | 193 | SAMPLES 194 | #{ samples } 195 | __ 196 | } 197 | end 198 | 199 | open("README", "w"){|fd| fd.puts template} 200 | end 201 | 202 | 203 | task :clean do 204 | Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)} 205 | end 206 | 207 | 208 | task :release => [:clean, :gemspec, :gem] do 209 | gems = Dir[File.join(This.pkgdir, '*.gem')].flatten 210 | raise "which one? : #{ gems.inspect }" if gems.size > 1 211 | raise "no gems?" if gems.size < 1 212 | cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.pkgdir }/#{ This.gem }" 213 | puts cmd 214 | system cmd 215 | cmd = "gem push #{ This.pkgdir }/#{ This.gem }" 216 | puts cmd 217 | system cmd 218 | end 219 | 220 | 221 | 222 | 223 | 224 | BEGIN { 225 | # support for this rakefile 226 | # 227 | $VERBOSE = nil 228 | 229 | require 'ostruct' 230 | require 'erb' 231 | require 'fileutils' 232 | require 'rbconfig' 233 | 234 | # fu shortcut 235 | # 236 | Fu = FileUtils 237 | 238 | # cache a bunch of stuff about this rakefile/environment 239 | # 240 | This = OpenStruct.new 241 | 242 | This.file = File.expand_path(__FILE__) 243 | This.dir = File.dirname(This.file) 244 | This.pkgdir = File.join(This.dir, 'pkg') 245 | 246 | # grok lib 247 | # 248 | lib = ENV['LIB'] 249 | unless lib 250 | lib = File.basename(Dir.pwd).sub(/[-].*$/, '') 251 | end 252 | This.lib = lib 253 | 254 | # grok version 255 | # 256 | version = ENV['VERSION'] 257 | unless version 258 | require "./lib/#{ This.lib }" 259 | This.name = lib.capitalize 260 | This.object = eval(This.name) 261 | version = This.object.send(:version) 262 | end 263 | This.version = version 264 | 265 | # we need to know the name of the lib an it's version 266 | # 267 | abort('no lib') unless This.lib 268 | abort('no version') unless This.version 269 | 270 | # discover full path to this ruby executable 271 | # 272 | c = Config::CONFIG 273 | bindir = c["bindir"] || c['BINDIR'] 274 | ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby' 275 | ruby_ext = c['EXEEXT'] || '' 276 | ruby = File.join(bindir, (ruby_install_name + ruby_ext)) 277 | This.ruby = ruby 278 | 279 | # some utils 280 | # 281 | module Util 282 | def indent(s, n = 2) 283 | s = unindent(s) 284 | ws = ' ' * n 285 | s.gsub(%r/^/, ws) 286 | end 287 | 288 | def unindent(s) 289 | indent = nil 290 | s.each_line do |line| 291 | next if line =~ %r/^\s*$/ 292 | indent = line[%r/^\s*/] and break 293 | end 294 | indent ? s.gsub(%r/^#{ indent }/, "") : s 295 | end 296 | extend self 297 | end 298 | 299 | # template support 300 | # 301 | class Template 302 | def initialize(&block) 303 | @block = block.binding 304 | @template = block.call.to_s 305 | end 306 | def expand(b=nil) 307 | ERB.new(Util.unindent(@template)).result(b||@block) 308 | end 309 | alias_method 'to_s', 'expand' 310 | end 311 | def Template(*args, &block) Template.new(*args, &block) end 312 | 313 | # colored console output support 314 | # 315 | This.ansi = { 316 | :clear => "\e[0m", 317 | :reset => "\e[0m", 318 | :erase_line => "\e[K", 319 | :erase_char => "\e[P", 320 | :bold => "\e[1m", 321 | :dark => "\e[2m", 322 | :underline => "\e[4m", 323 | :underscore => "\e[4m", 324 | :blink => "\e[5m", 325 | :reverse => "\e[7m", 326 | :concealed => "\e[8m", 327 | :black => "\e[30m", 328 | :red => "\e[31m", 329 | :green => "\e[32m", 330 | :yellow => "\e[33m", 331 | :blue => "\e[34m", 332 | :magenta => "\e[35m", 333 | :cyan => "\e[36m", 334 | :white => "\e[37m", 335 | :on_black => "\e[40m", 336 | :on_red => "\e[41m", 337 | :on_green => "\e[42m", 338 | :on_yellow => "\e[43m", 339 | :on_blue => "\e[44m", 340 | :on_magenta => "\e[45m", 341 | :on_cyan => "\e[46m", 342 | :on_white => "\e[47m" 343 | } 344 | def say(phrase, *args) 345 | options = args.last.is_a?(Hash) ? args.pop : {} 346 | options[:color] = args.shift.to_s.to_sym unless args.empty? 347 | keys = options.keys 348 | keys.each{|key| options[key.to_s.to_sym] = options.delete(key)} 349 | 350 | color = options[:color] 351 | bold = options.has_key?(:bold) 352 | 353 | parts = [phrase] 354 | parts.unshift(This.ansi[color]) if color 355 | parts.unshift(This.ansi[:bold]) if bold 356 | parts.push(This.ansi[:clear]) if parts.size > 1 357 | 358 | method = options[:method] || :puts 359 | 360 | Kernel.send(method, parts.join) 361 | end 362 | 363 | # always run out of the project dir 364 | # 365 | Dir.chdir(This.dir) 366 | } 367 | --------------------------------------------------------------------------------