<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,4 +1,4 @@
-Copyright (c) 2009 Mark
+Copyright (c) 2009 Mark G
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the</diff>
      <filename>LICENSE</filename>
    </modified>
    <modified>
      <diff>@@ -8,28 +8,43 @@ information, or they can be used in a hierarchical configuration where lower
 preferred weather services are only used if previous services are
 unavailable.
 
+== version
+
+Version 0.1.0 is the current release of this gem.
+The gem is available from github (attack-barometer) or rubyforge (barometer).
+It is fully functional (for three weather service APIs).
+
 == status
 
 Currently this project is in development and will only work for a few weather
 services (wunderground, google, yahoo).
 
-Features to be added before first release:
-- gem setup/config, apply to rubyforge
-
-Features to be added in future releases:
+Features to be added next:
 - even more weather service drivers (noaa, weather.com, weatherbug)
-- ability to query multiple services and combine/average the results
-- support iaco as a query format
 
 = dependencies
 
+=== Google API key
+
+In most cases you will need to have a free google geocode api key.
+Get one here: http://code.google.com/apis/maps/signup.html
+Then put it in the file '~/.barometer' by adding the line:
+geocode_google: YOUR_KEY_HERE
+
+You will need this for:
+- using the Barometer gem (unless you use queries that are directly supported
+  by the weather source API, ie yahoo will take a zip code directly and doesn't
+  require any geocoding)
+- running the Barometer binary
+- running the Barometer Web Demo
+
 === HTTParty
 
 Why? HTTParty was created and designed specifically for consuming web services.
 I choose to use this over using the Net::HTTP library directly to allow for
 faster development of this project.
 
-HTTParty is also extended to include configurable Timoout support.
+HTTParty is also extended to include configurable Timeout support.
 
 === tzinfo
 
@@ -96,6 +111,18 @@ You can use barometer from the command line.
   # barometer berlin
   
 This will output the weather information for the given query.
+See the help for more command line information.
+
+  # barometer -h
+
+=== web demo
+
+There is a Sinatra application that demos the functionality of Barometer,
+and provides Barometer information.  Start this local demo with:
+
+  # barometer -w
+  
+NOTE: This requires the gems &quot;sinatra&quot; and &quot;vegas&quot;.
   
 === fail
 
@@ -188,6 +215,25 @@ the data as shown in the above examples.
   
   puts weather.source(:wunderground).for(time).low.f
   
+== averages
+
+If you consume more then one weather service, Barometer can provide averages
+for the values (currently only for the 'current' values and not the forecasted
+values).
+
+  require 'barometer'
+  
+  Barometer.google_geocode_key = &quot;THE_GOOGLE_API_KEY&quot;
+  # use yahoo and wunderground
+  Barometer.selection = { 1 =&gt; [:yahoo, :wunderground] }
+
+  barometer = Barometer.new(&quot;90210&quot;)
+  weather = barometer.measure
+
+  puts weather.temperture
+
+This will calculate the average temperature as given by :yahoo and :wunderground
+  
 == simple answers
 
 After you have measured the data, Barometer provides several &quot;simple answer&quot;</diff>
      <filename>README.rdoc</filename>
    </modified>
    <modified>
      <diff>@@ -7,5 +7,4 @@
 
 - Rdoc compatible documentation
 - Weather Service API key setting
-- support IACO as a query format
 - store links from weather response
\ No newline at end of file</diff>
      <filename>TODO</filename>
    </modified>
    <modified>
      <diff>@@ -1,63 +1,415 @@
 #!/usr/bin/env ruby
- 
-require File.dirname(__FILE__) + '/../lib/barometer'
-#require 'rubygems'
-#require 'attack-barometer'
-
-# TODO
-
-# set default Google Key ... maybe need a config file?
-Barometer.google_geocode_key = &quot;ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw&quot;
-
-# take command line paramters
-# service: --yahoo, --wunderground, --google
-# units: -m --metric, -i --imperial
-# geocode: -g --geocode (force geocode)
-# timeout: -t 15 --timeout 15
-# skip: --skip (skip graticule)
-# help: -h --help
-# wet threshold: -w --wet
-# windy threshold: -v --wind
-# pop threshold: -p --pop
-# time: -a --at
-
-# prettier output
-# show simple answers
-# more help
-# error display (out of sources, etc.)
- 
-if ARGV.size == 0
-  puts 'Barometer [Powered by wunderground]'
-  puts 'USAGE: barometer [query]'
-  puts 'EXAMPLES:'
-  puts ' barometer paris'
-  exit
+
+# == Barometer 
+#   This is the command line interface to the barometer gem.
+#
+# == Examples
+#   This command will measure the weather for the given query.
+#     barometer berlin
+#
+#   Other examples:
+#     barometer --yahoo 90210
+#     barometer --verbose 'new york'
+#
+# == Local Web Demo
+#   You can easily interact directly with barometer with the command:
+#     barometer -w
+#
+#   This demo has 2 gem requirements:
+#     - sinatra (tested with 0.9.1.1)
+#     - vegas (tested with 0.0.1)
+#
+# == Usage 
+#   barometer [options] query
+#
+#   For help use: barometer -h
+#
+#   Options:
+#   -v, --version       Display the version, then exit
+#   -V, --verbose       Verbose output
+#   -t, --timeout       seconds until service queries will timeout
+#   -g, --geocode       Force Geocoding of query
+#   -m, --metric        measure in metric
+#   -i, --imperial      measure in imperial
+#   --no-wunderground   DONT use default wunderground as source
+#   --yahoo             use yahoo as source
+#   --google            use google as source
+#   -p, --pop           pop threshold used to determine wet?
+#   -s, --wind          wind speed threshold used to determine windy?
+#   -a, --at            time/date used to determine when to calculate summary
+#
+#   Web Demo:
+#   -w, --web           run web-app with barometer demo
+#   -k, --kill          stop the web demo background process
+#   -S, --status        show the web demo status
+#
+# == Author
+#   Mark G
+#   http://github.com/attack/barometer
+#
+# == Copyright
+#   Copyright (c) 2009 Mark G. Licensed under the MIT License:
+#   http://www.opensource.org/licenses/mit-license.php
+
+require 'rubygems'
+#require 'barometer'
+require 'lib/barometer'
+
+require 'optparse' 
+require 'rdoc/usage'
+require 'ostruct'
+require 'time'
+require 'date'
+require 'yaml'
+
+# file where API keys are stored
+KEY_FILE = File.expand_path(File.join('~', '.barometer'))
+
+class App
+  VERSION = '0.0.1'
+  
+  attr_reader :options
+
+  def initialize(arguments, stdin)
+    @arguments = arguments.dup
+    
+    # Set defaults
+    @options = OpenStruct.new
+    @options.timeout = 15
+    @options.geocode = false
+    @options.skip_graticule = false
+    @options.metric = true
+    @options.sources = [:wunderground]
+    @options.verbode = false
+    @options.web = false
+    @options.at = Time.now.utc
+    
+    # thresholds
+    @options.windy_m = 10
+    @options.windy_i = 7
+    @options.pop = 50
+  end
+
+  # Parse options, check arguments, then process the command
+  def run
+    if parsed_options? &amp;&amp; arguments_valid? 
+      puts &quot;Start at #{DateTime.now}\n\n&quot; if @options.verbose
+      output_options if @options.verbose # [Optional]
+            
+      process_arguments            
+      process_command
+      
+      puts &quot;\nFinished at #{DateTime.now}&quot; if @options.verbose
+    else
+      output_usage
+    end
+  end
+  
+  protected
+  
+    # future options
+    #
+    # time: -a --at
+    #
+    def parsed_options?
+      # Specify options
+      opt = OptionParser.new 
+      opt.on('-v', '--version')    { output_version ; exit 0 }
+      opt.on('-h', '--help')       { output_help }
+      opt.on('-V', '--verbose')    { @options.verbose = true }
+      opt.on('-a n', '--at n')     {|n| @options.at = Time.parse(n.to_s) }
+      opt.on('-t n', '--timeout n') {|n| @options.timeout = n }
+      opt.on('-w', '--web')        { @options.web = true; ARGV.shift }
+      opt.on('-g', '--geocode')    { @options.geocode = true }
+      opt.on('--skip')             { @options.skip_graticule = true }
+      opt.on('-m', '--metric')     { @options.metric = true }
+      opt.on('-i', '--imperial')   { @options.metric = false }
+      opt.on('--no-wunderground')  { @options.sources = @options.sources.delete_if{|s| s == :wunderground} }
+      opt.on('--yahoo')            { @options.sources &lt;&lt; :yahoo }
+      opt.on('--google')           { @options.sources &lt;&lt; :google }
+      opt.on('-p n', '--pop n')    {|n| @options.pop = n.to_i || 50 }
+      opt.on('-s n', '--wind n')   {|n| @options.metric ? @options.windy_m = n.to_f || 10 : @options.windy_i = n.to_f || 7 }
+      
+      # pass these onto vegas
+      opt.on('-k', '--kill')       { @options.web = true }
+      opt.on('-S', '--status')     { @options.web = true }
+            
+      opt.parse!(@arguments) rescue return false
+      
+      process_options
+      true      
+    end
+
+    # Performs post-parse processing on options
+    def process_options
+      Barometer.force_geocode =  @options.geocode
+      Barometer.selection = { 1 =&gt; @options.sources.uniq }
+      Barometer.skip_graticule = @options.skip_graticule
+      Barometer.timeout = @options.timeout
+    end
+    
+    def output_options
+      puts &quot;Options:\n&quot;
+      
+      @options.marshal_dump.each do |name, val|        
+        puts &quot;  #{name} = #{val}&quot;
+      end
+      puts
+    end
+
+    # True if required arguments were provided
+    def arguments_valid?
+      true if (@arguments.length &gt;= 1 || @options.web)
+    end
+    
+    # Setup the arguments
+    def process_arguments
+      #puts @arguments.inspect
+    end
+    
+    def output_help
+      output_version
+      RDoc::usage() #exits app
+    end
+    
+    def output_usage
+      RDoc::usage('usage') # gets usage from comments above
+    end
+    
+    def output_version
+      puts &quot;#{File.basename(__FILE__)} version #{VERSION}&quot;
+    end
+    
+    def process_command
+      if @options.web
+        run_web_mode(@arguments.join(&quot; &quot;))
+      else
+        barometer = Barometer.new(@arguments.join(&quot; &quot;))
+        begin
+          barometer.measure(@options.metric) if barometer
+          pretty_output(barometer) if barometer.weather
+        rescue Barometer::OutOfSources
+          puts
+          puts &quot;   SORRY: your query did not provide any results&quot;
+          puts
+        end
+      end
+    end
 end
- 
-barometer = Barometer.new(ARGV[0])
-weather = barometer.measure(true)
+
+#
+# HELPERS
+#
+
+@level = 1
 
 def y(value)
   value ? &quot;yes&quot; : &quot;no&quot;
 end
 
-if weather
-  puts &quot;###################################################&quot;
-  puts &quot;#  #{weather.default.location.name}&quot;
-  puts &quot;#&quot;
-  puts &quot;#  (lat: #{weather.default.location.latitude}, long: #{weather.default.location.longitude})&quot;
-  puts &quot;###################################################&quot;
-  puts &quot; -- CURRENT --&quot;
-  puts &quot; temperature: #{weather.now.temperature}&quot;
+def div(char=&quot;#&quot;)
+  puts char*50
+end
+
+def title(title, level=1)
+  @level = level
+  puts &quot;#{&quot;  &quot; * @level}-- #{title} --&quot;
+end
+
+def value(title, value)
+  puts &quot;#{&quot;  &quot; * @level}#{title}: #{value}&quot; unless value.nil?
+end
+
+def blank
   puts
-  puts &quot; -- QUESTIONS --&quot;
-  puts &quot; day?  : #{y(weather.day?)}&quot;
-  puts &quot; sunny?: #{y(weather.sunny?)}&quot;
-  puts &quot; windy?: #{y(weather.windy?)}&quot;
-  puts &quot; wet?  : #{y(weather.wet?)}&quot;
+end
+
+def section(title, level=1, show_blank=true)
+  @level = level
+  title(title, level); yield; blank if show_blank
+end
+
+def pretty_hash(hash)
+  return unless hash.is_a?(Hash)
+  hash.each { |k,v| value(k,v) }
+end
+
+def pretty_summary(s)
+  return unless s
+  section(&quot;AVERAGES&quot;) do
+    pretty_hash({
+      &quot;humidity&quot; =&gt; s.humidity.to_i, &quot;temperature&quot; =&gt; s.temperature })
+  end
+  section(&quot;SUMMARY#{ &quot; (@ #{@options.at})&quot; if @options.at }&quot;) do
+    pretty_hash({
+      &quot;day?&quot; =&gt; s.day?(@options.at), &quot;sunny?&quot; =&gt; s.sunny?(@options.at),
+      &quot;windy?&quot; =&gt; s.windy?(@options.metric ? @options.windy_m : @options.windy_i, @options.at),
+      &quot;wet?&quot; =&gt; s.wet?(@options.pop,@options.at) })
+  end
+end
+
+def pretty_query(q)
+  return unless q
+  section(&quot;QUERY&quot;, 2) do
+    pretty_hash({&quot;Format&quot; =&gt; q.format})
+    pretty_hash({
+      &quot;Address&quot; =&gt; q.geo.address,
+      &quot;Locality&quot; =&gt; q.geo.locality, &quot;Region&quot; =&gt; q.geo.region,
+      &quot;Country&quot; =&gt; q.geo.country, &quot;Country Code&quot; =&gt; q.geo.country_code,
+      &quot;Latitude&quot; =&gt; q.geo.latitude, &quot;Longitude&quot; =&gt; q.geo.longitude }) if q.geo
+  end
+end
+
+def pretty_location(l)
+  return unless l
+  section(&quot;LOCATION&quot;, 2) do
+    pretty_hash({
+      &quot;ID&quot; =&gt; l.id, &quot;Name&quot; =&gt; l.name,
+      &quot;City&quot; =&gt; l.city, &quot;State Name&quot; =&gt; l.state_name,
+      &quot;State Code&quot; =&gt; l.state_code, &quot;Country&quot; =&gt; l.country,
+      &quot;Country Code&quot; =&gt; l.country_code, &quot;Zip Code&quot; =&gt; l.zip_code,
+      &quot;Latitude&quot; =&gt; l.latitude, &quot;Longitude&quot; =&gt; l.longitude })
+  end
+end
+
+def pretty_station(s)
+  return unless s
+  section(&quot;STATION&quot;, 2) do
+    pretty_hash({
+      &quot;ID&quot; =&gt; s.id, &quot;Name&quot; =&gt; s.name,
+      &quot;City&quot; =&gt; s.city, &quot;State Name&quot; =&gt; s.state_name,
+      &quot;State Code&quot; =&gt; s.state_code, &quot;Country&quot; =&gt; s.country,
+      &quot;Country Code&quot; =&gt; s.country_code, &quot;Zip Code&quot; =&gt; s.zip_code,
+      &quot;Latitude&quot; =&gt; s.latitude, &quot;Longitude&quot; =&gt; s.longitude })
+  end
+end
+
+def pretty_timezone(t)
+  return unless t
+  section(&quot;TIMEZONE&quot;, 2) do
+    pretty_hash({ &quot;Long&quot; =&gt; t.timezone, &quot;Code&quot; =&gt; t.code,&quot;DST?&quot; =&gt; t.dst? })
+  end
+end
+
+def pretty_current(c)
+  return unless c
+  section(&quot;CURRENT&quot;, 2) do
+    pretty_hash({
+      &quot;Time&quot; =&gt; c.time, &quot;Local Time&quot; =&gt; c.local_time,
+      &quot;Humidity&quot; =&gt; c.humidity, &quot;Icon&quot; =&gt; c.icon,
+      &quot;Condition&quot; =&gt; c.condition, &quot;Temperature&quot; =&gt; c.temperature,
+      &quot;Dew Point&quot; =&gt; c.dew_point, &quot;Heat Index&quot; =&gt; c.heat_index,
+      &quot;Pressure&quot; =&gt; c.pressure, &quot;Visibility&quot; =&gt; c.visibility })
+    pretty_hash({ &quot;Wind Chill&quot; =&gt; c.wind_chill, &quot;Wind&quot; =&gt; c.wind,
+      &quot;Wind Direction&quot; =&gt; c.wind.direction, &quot;Degrees&quot; =&gt; c.wind.degrees }) if c.wind
+    pretty_hash({ &quot;Sun Rise&quot; =&gt; c.sun.rise, &quot;Sun Set&quot; =&gt; c.sun.set }) if c.sun
+  end
+end
+
+def pretty_forecast(f)
+  return unless f
+  section(&quot;FOR: #{f.date}&quot;, 3) do
+    pretty_hash({
+      &quot;Date&quot; =&gt; f.date, &quot;Icon&quot; =&gt; f.icon,
+      &quot;Condition&quot; =&gt; f.condition, &quot;High&quot; =&gt; f.high,
+      &quot;Low&quot; =&gt; f.low, &quot;POP&quot; =&gt; f.pop })
+    pretty_hash({ &quot;Sun Rise&quot; =&gt; f.sun.rise, &quot;Sun Set&quot; =&gt; f.sun.set }) if f.sun
+  end
+end
+
+def pretty_forecasts(forecasts)
+  return unless forecasts
+  section(&quot;FORECAST&quot;, 3, false) do
+    blank
+    forecasts.each do |forecast|
+      pretty_forecast(forecast)
+    end
+  end
+end
+
+def pretty_measurement(m)
+  return unless m
+  section(m.source.to_s, 1) do
+    pretty_hash({
+      &quot;Source&quot; =&gt; m.source, &quot;Time&quot; =&gt; m.time,
+      &quot;Metric&quot; =&gt; m.metric?, &quot;Success&quot; =&gt; m.success? })
+  end
+  pretty_location(m.location)
+  pretty_station(m.station)
+  pretty_timezone(m.timezone)
+  pretty_current(m.current)
+  pretty_forecasts(m.forecast)
+end
+
+def pretty_measurements(w)
+  return unless w
+  section(&quot;MEASUREMENTS&quot;, 1) do
+    blank
+    w.measurements.each do |m|
+      pretty_measurement(m)
+    end
+  end
+end
+
+def pretty_info
+  title(&quot;INFO&quot;, 1)
+  value(&quot;GitHub&quot;, &quot;http://github.com/attack/barometer&quot;)
+end
+
+def pretty_output(barometer)
+  weather = barometer.weather
+  if weather
+    div
+    puts &quot;#&quot;
+    puts &quot;#  #{weather.default.location.name || barometer.query.q}&quot;
+    puts &quot;#&quot;
+    div
+    blank
+    pretty_summary(weather)
+    pretty_query(barometer.query)
+    pretty_measurements(weather)
+    pretty_info
+    div(&quot;-&quot;)
+  end
+end
+
+def run_web_mode(query=nil)
+  
+  raise &quot;This is currently disabled&quot;
+  
+  #require File.expand_path(File.dirname(__FILE__) + '/../lib/webometer/webometer.rb')
+  #require 'vegas'
+  
+  #Vegas::Runner.new(Webometer, 'webometer') do |opts, app|
+    # opts is an option parser object
+    # app is your app class
+  #end
+end
+
+def geocode_google_key_message
   puts
+  puts &quot;Please update the key_file '#{KEY_FILE}' with your google api key&quot;
+  puts &quot;Get it here: http://code.google.com/apis/maps/signup.html&quot;
+  puts &quot;The, add this line to the file:&quot;
+  puts &quot;geocode_google: YOUR_KEY_KERE&quot;
   puts
-  puts &quot; -- INFO --&quot;
-  puts &quot; http://github.com/attack/barometer&quot;
-  puts &quot;---------------------------------------------------&quot;
-end
\ No newline at end of file
+end
+
+# set API keys
+if File.exists?(KEY_FILE)
+	keys = YAML.load_file(KEY_FILE)
+	if keys[&quot;geocode_google&quot;]
+	  Barometer.google_geocode_key = keys[&quot;geocode_google&quot;]
+  else
+    geocode_google_key_message
+    exit
+  end
+else
+  File.open(KEY_FILE, 'w') {|f| f &lt;&lt; &quot;geocode_google: YOUR_KEY_KERE&quot; }
+  geocode_google_key_message
+  exit
+end
+
+# Create and run the application
+app = App.new(ARGV, STDIN)
+app.run</diff>
      <filename>bin/barometer</filename>
    </modified>
    <modified>
      <diff>@@ -8,7 +8,7 @@ module Barometer
   class Geo
     
     attr_accessor :latitude, :longitude
-    attr_accessor :locality, :region, :country, :country_code
+    attr_accessor :locality, :region, :country, :country_code, :address
     
     #
     # this will take a Location object (either generated by Graticule
@@ -60,6 +60,7 @@ module Barometer
       @region = location.region
       @country = location.country
       @country_code = location.country_code
+      @address = location.address_line
     end
     
     def build_from_httparty(location=nil)
@@ -87,6 +88,7 @@ module Barometer
         end
         @country = placemark[&quot;AddressDetails&quot;][&quot;Country&quot;][&quot;CountryName&quot;]
         @country_code = placemark[&quot;AddressDetails&quot;][&quot;Country&quot;][&quot;CountryNameCode&quot;]
+        @address = placemark[&quot;AddressDetails&quot;][&quot;Country&quot;][&quot;AddressLine&quot;]
       end
     end
     </diff>
      <filename>lib/barometer/data/geo.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,10 +1,10 @@
 module Graticule
   class Location
     
-    attr_accessor :country_code
+    attr_accessor :country_code, :address_line
     
     def attributes
-      [:latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision, :cuntry_code].inject({}) do |result,attr|
+      [:latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision, :cuntry_code, :address_line].inject({}) do |result,attr|
         result[attr] = self.send(attr) unless self.send(attr).blank?
         result
       end
@@ -39,6 +39,7 @@ module Graticule
             l.postal_code = value(address.elements['.//PostalCodeNumber/text()'])
             l.country = value(address.elements['.//CountryName/text()'])
             l.country_code = value(address.elements['.//CountryNameCode/text()'])
+            l.address_line = value(address.elements['.//AddressLine/text()'])
             l.precision = PRECISION[address.attribute('Accuracy').value.to_i] || :unknown
           end
         end</diff>
      <filename>lib/barometer/extensions/graticule.rb</filename>
    </modified>
    <modified>
      <diff>@@ -31,20 +31,23 @@ module Barometer
       self.analyze!
     end
     
-    # analyze the saved query to determine the format.  for the format of
-    # :zipcode and :postalcode the country_code can also be set
+    # analyze the saved query to determine the format.
     def analyze!
       return unless @q
       if Barometer::Query.is_us_zipcode?(@q)
         @format = :zipcode
+        @country_code = Barometer::Query.format_to_country_code(@format)
       elsif Barometer::Query.is_canadian_postcode?(@q)
         @format = :postalcode
+        @country_code = Barometer::Query.format_to_country_code(@format)
       elsif Barometer::Query.is_coordinates?(@q)
         @format = :coordinates
+      elsif Barometer::Query.is_icao?(@q)
+        @format = :icao
+#        @country_code = Barometer::Query.icao_to_country_code(@q)
       else
         @format = :geocode
       end
-      @country_code = Barometer::Query.format_to_country_code(@format)
     end
     
     # take a list of acceptable (and ordered by preference) formats and convert
@@ -93,6 +96,7 @@ module Barometer
     def postalcode?; @format == :postalcode; end
     def coordinates?; @format == :coordinates; end
     def geocode?; @format == :geocode; end
+    def icao?; @format == :icao; end
     
     def self.is_us_zipcode?(query)
       us_zipcode_regex = /(^[0-9]{5}$)|(^[0-9]{5}-[0-9]{4}$)/
@@ -112,12 +116,25 @@ module Barometer
       return !(query =~ coordinates_regex).nil?
     end
     
+    def self.is_icao?(query)
+      # allow any 3 or 4 letter word ... unfortunately this means some locations
+      # (ie Utah, Goa, Kiev, etc) will be detected as ICAO.  This won't matter for
+      # returning weather results ... it will just effect what happens to the query.
+      # For example, wunderground will accept :icao above :coordinates and :geocode,
+      # which means that a city like Kiev would normally get converted to :coordinates
+      # but in this case it will be detected as :icao so it will be passed as is.
+      # Currently, only wunderground accepts ICAO, and they process ICAO the same as a
+      # city name, so it doesn't matter.
+      icao_regex = /^[A-Za-z]{3,4}$/
+      return !(query =~ icao_regex).nil?
+    end
+    
     #
     # CONVERTERS
     #
     
     # this will take all query formats and convert them to coordinates
-    # accepts- :zipcode, :postalcode, :geocode
+    # accepts- :zipcode, :postalcode, :geocode, :icao
     # returns- :coordinates
     # if the conversion fails, return nil
     def self.to_coordinates(query, format)
@@ -129,7 +146,7 @@ module Barometer
     end
     
     # this will take all query formats and convert them to coorinates
-    # accepts- :zipcode, :postalcode, :coordinates
+    # accepts- :zipcode, :postalcode, :coordinates, :icao
     # returns- :geocode
     def self.to_geocode(query, format)
       perform_geocode = false
@@ -143,8 +160,17 @@ module Barometer
       if perform_geocode
         geo = self.geocode(query, country_code)
         country_code ||= geo.country_code if geo
-        return nil unless geo &amp;&amp; geo.locality &amp;&amp; geo.region &amp;&amp; geo.country
-        return [&quot;#{geo.locality}, #{geo.region}, #{geo.country}&quot;, country_code, geo]
+        # different formats have different acceptance criteria
+        q = nil
+        case format
+        when :icao
+          return nil unless geo &amp;&amp; geo.address &amp;&amp; geo.country
+          q = &quot;#{geo.address}, #{geo.country}&quot;
+        else
+          return nil unless geo &amp;&amp; geo.locality &amp;&amp; geo.region &amp;&amp; geo.country
+          q = &quot;#{geo.locality}, #{geo.region}, #{geo.country}&quot;
+        end
+        return [q, country_code, geo]
       else
         # without geocoding, the best we can do is just make use the given query as
         # the query for the &quot;geocode&quot; format
@@ -207,7 +233,6 @@ module Barometer
         },
         :format =&gt; :xml
       )['kml']['Response']
-      #puts location.inspect
       geo = Barometer::Geo.new(location)
     end
     
@@ -223,6 +248,30 @@ module Barometer
       end
       country_code
     end
+    
+    # todo, the fist letter in a 4-letter icao can designate country:
+    # c=canada
+    # k=usa
+    # etc...
+    # def self.icao_to_country_code(icao_code)
+    #   return unless icao_code.is_a?(String)
+    #   country_code = nil
+    #   if icao_code.size == 4
+    #     case icao_code.first_letter
+    #     when &quot;C&quot;
+    #       country_code = &quot;CA&quot;
+    #     when &quot;K&quot;
+    #       country_code = &quot;US&quot;
+    #     end
+    #     if coutry_code.nil?
+    #       case icao_code.first_two_letters
+    #       when &quot;ET&quot;
+    #         country_code = &quot;GERMANY&quot;
+    #       end
+    #     end
+    #   end  
+    #   country_code
+    # end
 
   end
 end  </diff>
      <filename>lib/barometer/query.rb</filename>
    </modified>
    <modified>
      <diff>@@ -39,7 +39,7 @@ module Barometer
   class Wunderground &lt; Service
     
     def self.accepted_formats
-      [:zipcode, :postalcode, :coordinates, :geocode]
+      [:zipcode, :postalcode, :icao, :coordinates, :geocode]
     end
     
     def self.source_name
@@ -48,8 +48,9 @@ module Barometer
     
     # these are the icon codes that indicate &quot;wet&quot;, used by wet? function
     def self.wet_icon_codes
-      %w(flurries rain sleet snow tstorms nt_flurries nt_rain nt_sleet nt_snow nt_tstorms)
+      %w(flurries rain sleet snow tstorms nt_flurries nt_rain nt_sleet nt_snow nt_tstorms chancerain)
     end
+    # these are the icon codes that indicate &quot;sun&quot;, used by sunny? function
     def self.sunny_icon_codes
       %w(clear mostlysunny partlysunny sunny partlycloudy)
     end</diff>
      <filename>lib/barometer/services/wunderground.rb</filename>
    </modified>
    <modified>
      <diff>@@ -34,17 +34,9 @@ module Barometer
     # Quick access methods
     #
     
-    def current
-      (default = self.default) ? default.current : nil
-    end
-    
-    def forecast
-      (default = self.default) ? default.forecast : nil
-    end
-
-    def now
-      self.current
-    end
+    def current; (default = self.default) ? default.current : nil; end
+    def forecast; (default = self.default) ? default.forecast : nil; end
+    def now; self.current; end
     
     def today
       default = self.default
@@ -72,37 +64,48 @@ module Barometer
     # averages
     #
     
-    # average of all humidity values
-    # def humidity
-    # end
-    # 
-    # # average of all temperature values
-    # def temperature
-    # end
-    # 
-    # # average of all wind speed values
-    # def wind
-    # end
-    # 
-    # # average of all pressure values
-    # def pressure
-    # end
-    # 
-    # # average of all dew_point values
-    # def dew_point
-    # end
-    # 
-    # # average of all heat_index values
-    # def heat_index
-    # end
-    # 
-    # # average of all wind_chill values
-    # def wind_chill
-    # end
-    # 
-    # # average of all visibility values
-    # def visibility
-    # end
+    # TODO: not tested (except via averages)
+    def metric?
+      self.default ? self.default.metric? : true
+    end
+    
+    # TODO: not tested (except via averages)
+    # this assumes calculating for current, and that &quot;to_f&quot; for a value
+    # will return the value needed
+    # value_name = the name of the value we are averaging
+    def current_average(value_name)
+      values = []
+      @measurements.each do |measurement|
+        values &lt;&lt; measurement.current.send(value_name).to_f if measurement.success?
+      end
+      return nil unless values &amp;&amp; values.size &gt; 0
+      values.inject(0.0) { |sum,v| sum += v } / values.size
+    end
+    
+    # TODO: not tested (except via averages)
+    def average(value_name, do_average=true, class_name=nil)
+      if class_name
+        if do_average
+          avg = Barometer.const_get(class_name).new(self.metric?)
+          avg &lt;&lt; self.current_average(value_name)
+        else
+          avg = self.now.send(value_name)
+        end
+      else
+        avg = (do_average ? self.current_average(value_name) : self.now.send(value_name))
+      end
+      avg
+    end
+      
+    # average of all values
+    def humidity(do_average=true); average(&quot;humidity&quot;,do_average); end
+    def temperature(do_average=true); average(&quot;temperature&quot;,do_average,&quot;Temperature&quot;); end
+    def wind(do_average=true); average(&quot;wind&quot;,do_average,&quot;Speed&quot;); end
+    def pressure(do_average=true); average(&quot;pressure&quot;,do_average,&quot;Pressure&quot;); end
+    def dew_point(do_average=true); average(&quot;dew_point&quot;,do_average,&quot;Temperature&quot;); end
+    def heat_index(do_average=true); average(&quot;heat_index&quot;,do_average,&quot;Temperature&quot;); end
+    def wind_chill(do_average=true); average(&quot;wind_chill&quot;,do_average,&quot;Temperature&quot;); end
+    def visibility(do_average=true); average(&quot;visibility&quot;,do_average,&quot;Distance&quot;); end
     
     #
     # quick access methods</diff>
      <filename>lib/barometer/weather.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,7 +4,7 @@ describe &quot;Barometer&quot; do
   
   before(:each) do
     @preference_hash = { 1 =&gt; [:wunderground] }
-    @key = &quot;ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw&quot;
+    @key = KEY
   end
   
   describe &quot;and class methods&quot; do</diff>
      <filename>spec/barometer_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -35,6 +35,10 @@ describe &quot;Geo&quot; do
       @geo.country.should be_nil
     end
     
+    it &quot;responds to address&quot; do
+      @geo.address.should be_nil
+    end
+    
     it &quot;responds to coordinates&quot; do
       @geo.longitude = &quot;99.99&quot;
       @geo.latitude = &quot;88.88&quot;</diff>
      <filename>spec/data_geo_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,6 +7,7 @@ describe &quot;Query&quot; do
     @postal_code = &quot;T5B 4M9&quot;
     @coordinates = &quot;40.756054,-73.986951&quot;
     @geocode = &quot;New York, NY&quot;
+    @icao = &quot;KSFO&quot;
     
     # actual conversions
     @zipcode_to_coordinates = &quot;34.1030032,-118.4104684&quot;
@@ -26,18 +27,28 @@ describe &quot;Query&quot; do
       Barometer::Query.is_us_zipcode?(@zipcode).should be_true
       Barometer::Query.is_us_zipcode?(@postal_code).should be_false
       Barometer::Query.is_us_zipcode?(@coordinates).should be_false
+      Barometer::Query.is_coordinates?(@icao).should be_false
     end
     
     it &quot;detects a postalcode&quot; do
       Barometer::Query.is_canadian_postcode?(@postal_code).should be_true
       Barometer::Query.is_canadian_postcode?(@zipcode).should be_false
       Barometer::Query.is_canadian_postcode?(@coordinates).should be_false
+      Barometer::Query.is_coordinates?(@icao).should be_false
     end
     
     it &quot;detects a coordinates&quot; do
       Barometer::Query.is_coordinates?(@coordinates).should be_true
       Barometer::Query.is_coordinates?(@zipcode).should be_false
       Barometer::Query.is_coordinates?(@postal_code).should be_false
+      Barometer::Query.is_coordinates?(@icao).should be_false
+    end
+    
+    it &quot;detects an ICAO&quot; do
+      Barometer::Query.is_icao?(@coordinates).should be_false
+      Barometer::Query.is_icao?(@zipcode).should be_false
+      Barometer::Query.is_icao?(@postal_code).should be_false
+      Barometer::Query.is_icao?(@icao).should be_true
     end
     
   end
@@ -58,6 +69,7 @@ describe &quot;Query&quot; do
       @query.country_code.should == &quot;US&quot;
       @query.zipcode?.should be_true
       @query.postalcode?.should be_false
+      @query.icao?.should be_false
       @query.coordinates?.should be_false
       @query.geocode?.should be_false
     end
@@ -71,6 +83,21 @@ describe &quot;Query&quot; do
       @query.country_code.should == &quot;CA&quot;
       @query.zipcode?.should be_false
       @query.postalcode?.should be_true
+      @query.icao?.should be_false
+      @query.coordinates?.should be_false
+      @query.geocode?.should be_false
+    end
+    
+    it &quot;recognizes icao&quot; do
+      @query.q = @icao
+      @query.format.should be_nil
+      @query.analyze!
+      @query.format.to_sym.should == :icao
+      
+      @query.country_code.should be_nil
+      @query.zipcode?.should be_false
+      @query.postalcode?.should be_false
+      @query.icao?.should be_true
       @query.coordinates?.should be_false
       @query.geocode?.should be_false
     end
@@ -84,6 +111,7 @@ describe &quot;Query&quot; do
       @query.country_code.should be_nil
       @query.zipcode?.should be_false
       @query.postalcode?.should be_false
+      @query.icao?.should be_false
       @query.coordinates?.should be_true
       @query.geocode?.should be_false
     end
@@ -97,6 +125,7 @@ describe &quot;Query&quot; do
       @query.country_code.should be_nil
       @query.zipcode?.should be_false
       @query.postalcode?.should be_false
+      @query.icao?.should be_false
       @query.coordinates?.should be_false
       @query.geocode?.should be_true
     end
@@ -168,7 +197,7 @@ describe &quot;Query&quot; do
   describe &quot;when converting queries&quot; do
     
     before(:each) do
-      @key = &quot;ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw&quot;
+      @key = KEY
       url_start = &quot;http://maps.google.com/maps/geo?&quot;
       #
       # for Graticule and/or HTTParty geocoding
@@ -221,6 +250,13 @@ describe &quot;Query&quot; do
           'geocode_40_73.xml')
         )
       )
+      FakeWeb.register_uri(:get, 
+        &quot;#{url_start}output=xml&amp;q=KSFO&amp;gl=&amp;key=#{@key}&quot;,
+        :string =&gt; File.read(File.join(File.dirname(__FILE__), 
+          'fixtures', 
+          'geocode_ksfo.xml')
+        )
+      )
     end
 
     describe &quot;to coordinates,&quot; do
@@ -253,6 +289,10 @@ describe &quot;Query&quot; do
         Barometer::Query.to_coordinates(@postal_code, :postalcode).first.should == &quot;53.570447,-113.456083&quot;
       end
       
+      it &quot;converts from icao&quot; do
+        Barometer::Query.to_coordinates(@icao, :icao).first.should == &quot;37.615223,-122.389979&quot;
+      end
+      
     end
     
     describe &quot;to geocode&quot; do
@@ -275,6 +315,10 @@ describe &quot;Query&quot; do
           Barometer::Query.to_geocode(@postal_code, :postalcode).first.should == @postal_code
         end
         
+        it &quot;converts from icao&quot; do
+          Barometer::Query.to_geocode(@icao, :icao).first.should == &quot;San Francisco Airport, USA&quot;
+        end
+
       end
       
       describe &quot;when Graticule disabled,&quot; do
@@ -357,7 +401,7 @@ describe &quot;Query&quot; do
         
         before(:each) do
           @query = Barometer::Query.new(@zipcode)
-          Barometer::Query.google_geocode_key = &quot;ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw&quot;
+          Barometer::Query.google_geocode_key = KEY
         end
         
         it &quot;converts to coordinates&quot; do
@@ -384,7 +428,7 @@ describe &quot;Query&quot; do
         
         before(:each) do
           @query = Barometer::Query.new(@postal_code)
-          Barometer::Query.google_geocode_key = &quot;ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw&quot;
+          Barometer::Query.google_geocode_key = KEY
         end
         
         it &quot;converts to coordinates&quot; do
@@ -411,7 +455,7 @@ describe &quot;Query&quot; do
         
         before(:each) do
           @query = Barometer::Query.new(@geocode)
-          Barometer::Query.google_geocode_key = &quot;ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw&quot;
+          Barometer::Query.google_geocode_key = KEY
         end
         
         it &quot;converts to coordinates&quot; do
@@ -438,7 +482,7 @@ describe &quot;Query&quot; do
         
         before(:each) do
           @query = Barometer::Query.new(@coordinates)
-          Barometer::Query.google_geocode_key = &quot;ABQIAAAAq8TH4offRcGrok8JVY_MyxRi_j0U6kJrkFvY4-OX2XYmEAa76BSFwMlSow1YgX8BOPUeve_shMG7xw&quot;
+          Barometer::Query.google_geocode_key = KEY
         end
         
         it &quot;converts to geocode&quot; do</diff>
      <filename>spec/query_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,7 +3,7 @@ require 'spec_helper'
 describe &quot;Wunderground&quot; do
   
   before(:each) do
-    @accepted_formats = [:zipcode, :postalcode, :coordinates, :geocode]
+    @accepted_formats = [:zipcode, :postalcode, :icao, :coordinates, :geocode]
     @base_uri = &quot;http://api.wunderground.com/auto/wui/geo&quot;
   end
   </diff>
      <filename>spec/service_wunderground_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -2,6 +2,7 @@ require 'rubygems'
 require 'spec'
 require 'fakeweb'
 require 'cgi'
+require 'yaml'
 
 $LOAD_PATH.unshift(File.dirname(__FILE__))
 $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
@@ -9,6 +10,30 @@ require 'barometer'
 
 FakeWeb.allow_net_connect = false
 
+KEY_FILE = File.expand_path(File.join('~', '.barometer'))
+
+def geocode_google_key_message
+  puts
+  puts &quot;Please update the key_file '#{KEY_FILE}' with your google api key&quot;
+  puts &quot;example:&quot;
+  puts &quot;geocode_google: YOUR_KEY_KERE&quot;
+  puts
+end
+
+if File.exists?(KEY_FILE)
+	keys = YAML.load_file(KEY_FILE)
+	if keys[&quot;geocode_google&quot;]
+	  KEY = keys[&quot;geocode_google&quot;]
+  else
+    geocode_google_key_message
+    exit
+  end
+else
+  File.open(KEY_FILE, 'w') {|f| f &lt;&lt; &quot;geocode_google: YOUR_KEY_KERE&quot; }
+  geocode_google_key_message
+  exit
+end
+
 Spec::Runner.configure do |config|
   
 end</diff>
      <filename>spec/spec_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -78,6 +78,174 @@ describe &quot;Weather&quot; do
     
   end
   
+  describe &quot;when calculating averages&quot; do
+    
+    before(:each) do
+      @weather = Barometer::Weather.new
+      @wunderground = Barometer::Measurement.new(:wunderground)
+      @wunderground.current = Barometer::CurrentMeasurement.new
+      @wunderground.success = true
+      @yahoo = Barometer::Measurement.new(:yahoo)
+      @yahoo.current = Barometer::CurrentMeasurement.new
+      @yahoo.success = true
+      @google = Barometer::Measurement.new(:google)
+      @weather.measurements &lt;&lt; @wunderground
+      @weather.measurements &lt;&lt; @yahoo
+      @weather.measurements &lt;&lt; @google
+    end
+    
+    describe &quot;for temperature&quot; do
+      
+      before(:each) do
+        @weather.source(:wunderground).current.temperature = Barometer::Temperature.new
+        @weather.source(:wunderground).current.temperature.c = 10
+        @weather.source(:yahoo).current.temperature = Barometer::Temperature.new
+        @weather.source(:yahoo).current.temperature.c = 6
+      end
+
+      it &quot;returns averages&quot; do
+        @weather.temperature.c.should == 8
+      end
+      
+      it &quot;returns default when disabled&quot; do
+        @weather.temperature(false).c.should == 10
+      end
+      
+    end
+    
+    describe &quot;for wind&quot; do
+      
+      before(:each) do
+        @weather.source(:wunderground).current.wind = Barometer::Speed.new
+        @weather.source(:wunderground).current.wind.kph = 10
+        @weather.source(:yahoo).current.wind = Barometer::Speed.new
+        @weather.source(:yahoo).current.wind.kph = 6
+      end
+
+      it &quot;returns averages&quot; do
+        @weather.wind.kph.should == 8
+      end
+      
+      it &quot;returns default when disabled&quot; do
+        @weather.wind(false).kph.should == 10
+      end
+      
+    end
+    
+    describe &quot;for humidity&quot; do
+      
+      before(:each) do
+        @weather.source(:wunderground).current.humidity = 10
+        @weather.source(:yahoo).current.humidity = 6
+      end
+
+      it &quot;returns averages&quot; do
+        @weather.humidity.should == 8
+      end
+      
+      it &quot;returns default when disabled&quot; do
+        @weather.humidity(false).should == 10
+      end
+      
+    end
+    
+    describe &quot;for pressure&quot; do
+      
+      before(:each) do
+        @weather.source(:wunderground).current.pressure = Barometer::Pressure.new
+        @weather.source(:wunderground).current.pressure.mb = 10
+        @weather.source(:yahoo).current.pressure = Barometer::Pressure.new
+        @weather.source(:yahoo).current.pressure.mb = 6
+      end
+
+      it &quot;returns averages&quot; do
+        @weather.pressure.mb.should == 8
+      end
+      
+      it &quot;returns default when disabled&quot; do
+        @weather.pressure(false).mb.should == 10
+      end
+      
+    end
+    
+    describe &quot;for dew_point&quot; do
+      
+      before(:each) do
+        @weather.source(:wunderground).current.dew_point = Barometer::Temperature.new
+        @weather.source(:wunderground).current.dew_point.c = 10
+        @weather.source(:yahoo).current.dew_point = Barometer::Temperature.new
+        @weather.source(:yahoo).current.dew_point.c = 6
+      end
+
+      it &quot;returns averages&quot; do
+        @weather.dew_point.c.should == 8
+      end
+      
+      it &quot;returns default when disabled&quot; do
+        @weather.dew_point(false).c.should == 10
+      end
+      
+    end
+    
+    describe &quot;for heat_index&quot; do
+      
+      before(:each) do
+        @weather.source(:wunderground).current.heat_index = Barometer::Temperature.new
+        @weather.source(:wunderground).current.heat_index.c = 10
+        @weather.source(:yahoo).current.heat_index = Barometer::Temperature.new
+        @weather.source(:yahoo).current.heat_index.c = 6
+      end
+
+      it &quot;returns averages&quot; do
+        @weather.heat_index.c.should == 8
+      end
+      
+      it &quot;returns default when disabled&quot; do
+        @weather.heat_index(false).c.should == 10
+      end
+      
+    end
+    
+    describe &quot;for wind_chill&quot; do
+      
+      before(:each) do
+        @weather.source(:wunderground).current.wind_chill = Barometer::Temperature.new
+        @weather.source(:wunderground).current.wind_chill.c = 10
+        @weather.source(:yahoo).current.wind_chill = Barometer::Temperature.new
+        @weather.source(:yahoo).current.wind_chill.c = 6
+      end
+
+      it &quot;returns averages&quot; do
+        @weather.wind_chill.c.should == 8
+      end
+      
+      it &quot;returns default when disabled&quot; do
+        @weather.wind_chill(false).c.should == 10
+      end
+      
+    end
+    
+    describe &quot;for visibility&quot; do
+      
+      before(:each) do
+        @weather.source(:wunderground).current.visibility = Barometer::Distance.new
+        @weather.source(:wunderground).current.visibility.km = 10
+        @weather.source(:yahoo).current.visibility = Barometer::Distance.new
+        @weather.source(:yahoo).current.visibility.km = 6
+      end
+
+      it &quot;returns averages&quot; do
+        @weather.visibility.km.should == 8
+      end
+      
+      it &quot;returns default when disabled&quot; do
+        @weather.visibility(false).km.should == 10
+      end
+      
+    end
+    
+  end
+  
   describe &quot;when answering the simple questions,&quot; do
     
     before(:each) do</diff>
      <filename>spec/weather_spec.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>4624cc51299cf5356698d65a7a29eeafbb4f927a</id>
    </parent>
  </parents>
  <author>
    <name>Mark</name>
    <email>rtec88@gmail.com</email>
  </author>
  <url>http://github.com/attack/barometer/commit/4ed4222ea098d7b5e6f58c2efa6a860c073556f5</url>
  <id>4ed4222ea098d7b5e6f58c2efa6a860c073556f5</id>
  <committed-date>2009-05-02T17:27:52-07:00</committed-date>
  <authored-date>2009-05-02T17:27:52-07:00</authored-date>
  <message>improve barometer binary, add config file support, update readme</message>
  <tree>5271fb4e8f173c620044becd392107868240359d</tree>
  <committer>
    <name>Mark</name>
    <email>rtec88@gmail.com</email>
  </committer>
</commit>
