knmi 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,125 @@
1
+ require 'httparty'
2
+ module KNMI
3
+ class HttpService
4
+ include HTTParty
5
+
6
+ class << self
7
+
8
+ def get_daily(station_object, parameter_object, start_at = nil, end_at = nil, seasonal = false)
9
+ # select YYYYMMDD (drops hour term)
10
+ query = [station(station_object), parameters(parameter_object),
11
+ start_date(start_at)[0..13], end_date(end_at)[0..11],
12
+ seasonal(seasonal)].compact
13
+ result = get('http://www.knmi.nl/climatology/daily_data/getdata_day.cgi', { :query => "#{query * "&"}" } )
14
+
15
+ data = parse(station_object, parameter_object, result)
16
+
17
+ return new({"query" => query, "data" => data})
18
+ end
19
+
20
+ def get_hourly(station_object, parameter_object, start_at = nil, end_at = nil, seasonal = false)
21
+ query = [station(station_object), parameters(parameter_object),
22
+ start_date(start_at), end_date(end_at),
23
+ seasonal(seasonal)].compact
24
+ result = get('http://www.knmi.nl/klimatologie/uurgegevens/getdata_uur.cgi', { :query => "#{query * "&"}" } )
25
+
26
+ data = parse(station_object, parameter_object, result)
27
+
28
+ return new({"query" => query, "data" => data})
29
+ end
30
+
31
+ private
32
+
33
+ # Generates string for station
34
+ def station(station_object)
35
+ "stns=#{station_object.id}"
36
+ end
37
+
38
+ # pulls parameter names from parameter object defaults to All
39
+ def parameters(parameter_object)
40
+ vars = []
41
+ parameter_object.each { |p| vars << p.parameter }
42
+ "vars=#{vars * ":"}"
43
+ end
44
+
45
+ # Select Data seasonally? between selected dates for each year.
46
+ def seasonal(bool)
47
+ if bool == false
48
+ nil
49
+ else
50
+ "inseason=true"
51
+ end
52
+ end
53
+
54
+ # YYYYMMDDHH Default is previous day
55
+ # if starts is nil returns string for one day prior to current
56
+ def start_date(starts)
57
+ if starts.nil?
58
+ t = Time.now
59
+ t = Time.utc(t.year, t.month, t.day)
60
+ t = t - 24 * 60 * 60
61
+ "start" + time_str(t)
62
+ elsif starts.kind_of?(Time)
63
+ "start" + time_str(starts)
64
+ end
65
+ end
66
+
67
+ # YYYYMMDDHH Default is current day
68
+ # if ends is nil returns string for current time
69
+ def end_date(ends)
70
+ if ends.nil?
71
+ "end" + time_str(Time.now)
72
+ elsif ends.kind_of?(Time)
73
+ "end" + time_str(ends)
74
+ end
75
+ end
76
+
77
+ # Hours requires 01-24
78
+ def time_str(t)
79
+ hour = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"]
80
+ "=#{t.year}#{t.strftime("%m")}#{t.day}#{hour[t.hour]}"
81
+ end
82
+
83
+ # Grab the Http response data and convert to array of hashes
84
+ def parse(station_object, parameter_object, data)
85
+ # Number stations
86
+ nstn = 1
87
+ # Number parameters
88
+ nparams = parameter_object.length
89
+
90
+ # Split lines into array
91
+ data = data.split(/\n/)
92
+
93
+ # Get and clean data
94
+ data = data[(8 + nstn + nparams)..data.length]
95
+ data = data.join.tr("\s+", "")
96
+ data = data.tr("#", "")
97
+
98
+ # Parse into array and then hash with var name
99
+ data = CSV.parse(data, {:skip_blanks => true})
100
+ header = data.shift.map {|i| i.to_s.intern }
101
+ string_data = data.map {|row| row.map {|cell| cell.to_s } }
102
+ data = string_data.map {|row| Hash[*header.zip(row).flatten] }
103
+
104
+ return data
105
+ end
106
+
107
+ end
108
+
109
+ #
110
+ # Returns query string used in HTTP get request
111
+ # KNMI::HttpService.get_daily(210) #=> @ query =>[ "stns=210", "vars=ALL", "start=2011051500", "end=2011051613"]
112
+ attr_reader :query
113
+
114
+ #
115
+ # Parsed HTTP request
116
+ # Array of Hashes
117
+ attr_reader :data
118
+
119
+ def initialize(properties)
120
+ @query, @data = %w(query data).map do |p|
121
+ properties[p]
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,98 @@
1
+ module KNMI
2
+ class Parameters
3
+ class << self
4
+ attr_writer :keys_file
5
+
6
+ #
7
+ # Retrieve information about Parameter can be string or array
8
+ #
9
+ # KNMI::Parameter.find(period = "daily", "TA") #=> KNMI::Parameter object for TG (Daily Max temperature)
10
+ # KNMI::Parameter.find(period = "daily", ["TG", "TX"]) #=> KNMI::Parameter array of objects TG (Daily Mean temperature) and TX (Daily Max Temperature)
11
+ def find(period, parameter)
12
+ parameter = [parameter].flatten
13
+
14
+ list = []
15
+ parameter.uniq.each do |p|
16
+ list << keys.find { |k| k.parameter == p and k.period == period }
17
+ end
18
+
19
+ return list
20
+ end
21
+
22
+ #
23
+ # Retrieve each parameter within a category
24
+ # an Array of structs
25
+ #
26
+ # KNMI.Parameter.category(period = "daily, ""WIND") #=> [#<Parameter:0x00000100b433f8 @parameter="SQ", @category="RADT", @description="Sunshine Duration", @validate="(-1..6).include?(n)", @conversion="n == -1 ? 0.05 : (n / 10) * 60", @units="minutes">, #<Daily:0x00000100b43290 @parameter="SP", @category="RADT", @description="Percent of Maximum Sunshine Duration", @validate="(0..100).include?(n)", @conversion=nil, @units="%">, #<Daily:0x00000100b43128 @parameter="Q", @category="RADT", @description="Global Radiation", @validate="n.integer?", @conversion=nil, @units="J/cm^2">]
27
+ # KNMI.Parameter.category(period = "daily, ["WIND", "TEMP"])
28
+ def category(period, category)
29
+ category = [category].flatten
30
+
31
+ list = []
32
+ category.uniq.each do |c|
33
+ list << keys.select { |k| k.category == c and k.period == period }
34
+ end
35
+
36
+ return list.flatten!
37
+ end
38
+
39
+ #
40
+ # Retrieve all Parameters
41
+ # an Array of structs
42
+ def all(period)
43
+ list = []
44
+ list << keys.select { |k| k.period == period }
45
+ list.flatten!
46
+ end
47
+
48
+ private
49
+
50
+ # Parameters
51
+ def keys
52
+ File.open(keys_file) do |file|
53
+ yaml = YAML.load(file) || raise("Can't parse #{file.path}")
54
+ yaml.map { |key_hash| new(key_hash) }
55
+ end
56
+ end
57
+
58
+ def keys_file
59
+ @keys_file ||= File.join(File.dirname(__FILE__), '..', '..', 'data', 'data_key.yml')
60
+ end
61
+
62
+ end
63
+
64
+ # Paramter shortname
65
+ attr_reader :parameter
66
+
67
+ # Categories grouping parameters
68
+ attr_reader :category
69
+
70
+ # Description of parameter
71
+ attr_reader :description
72
+
73
+ # Code to validate data as accurate
74
+ attr_reader :validate
75
+
76
+ # Code to convert data into appropriate format/units
77
+ attr_reader :conversion
78
+
79
+ # Unit of converted format
80
+ attr_reader :units
81
+
82
+ # Time Period
83
+ attr_reader :period
84
+
85
+ def initialize(properties)
86
+ @parameter, @category, @description, @validate, @conversion, @units, @period = %w(parameter category description validate conversion units period).map do |p|
87
+ properties[p]
88
+ end
89
+ end
90
+
91
+ def detail
92
+ {:parameter => @parameter, :category => @category,
93
+ :description => @description, :validate => @validate,
94
+ :conversion => @conversion, :units => @units, :period => @period}
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,113 @@
1
+ module KNMI
2
+ class Station
3
+ class <<self
4
+ attr_writer :stations_file #:nodoc:
5
+
6
+ #
7
+ # Retrieve information about a station given a station ID
8
+ #
9
+ # KNMI::Station.find(210) #=> KNMI::Station object for Valkenburg
10
+ def find(id)
11
+ stations.find { |station| station.id == id }
12
+ end
13
+
14
+ #
15
+ # Find the station closest to a given location. Can accept arguments in any of the following
16
+ # three forms (all are equivalent):
17
+ #
18
+ # KNMI::Station.closest_to(52.165, 4.419)
19
+ # KNMI::Station.closest_to([52.165, 4.419])
20
+ # KNMI::Station.closest_to(GeoKit::LatLng.new(52.165, 4.419))
21
+ def closest_to(*args)
22
+ if args.length == 1
23
+ if args.first.respond_to?(:distance_to)
24
+ closest_to_coordinates(args.first)
25
+ elsif %w(first last).all? { |m| args.first.respond_to?(m) }
26
+ closest_to_lat_lng(args.first)
27
+ else
28
+ raise ArgumentError, "expected two-element Array or GeoKit::LatLng"
29
+ end
30
+ elsif args.length == 2
31
+ closest_to_lat_lng(args)
32
+ else
33
+ raise ArgumentError, "closest_to() will accept one Array argument, one GeoKit::LatLng argument, or two FixNum arguments"
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def closest_to_lat_lng(pair)
40
+ closest_to_coordinates(GeoKit::LatLng.new(pair.first, pair.last))
41
+ end
42
+
43
+ def closest_to_coordinates(coordinates)
44
+ stations.map do |station|
45
+ [coordinates.distance_to(station.coordinates), station]
46
+ end.min do |p1, p2|
47
+ p1.first <=> p2.first # compare distance
48
+ end[1]
49
+ end
50
+
51
+ def stations
52
+ File.open(stations_file) do |file|
53
+ yaml = YAML.load(file) || raise("Can't parse #{file.path} - be sure to run noaa-update-stations")
54
+ yaml.map { |station_hash| new(station_hash) }
55
+ end
56
+ end
57
+
58
+ def stations_file
59
+ @stations_file ||= File.join(File.dirname(__FILE__), '..', '..', 'data', 'current_stations.yml')
60
+ end
61
+ end
62
+
63
+ # GeoKit::LatLng containing the station's coordinates
64
+ attr_reader :coordinates
65
+
66
+ # Station ID (e.g., 210)
67
+ attr_reader :id
68
+
69
+ # Station name (e.g., "Valkenburg")
70
+ attr_reader :name
71
+
72
+ # Station Elevation
73
+ attr_reader :elevation
74
+ alias_method :elev, :elevation
75
+ alias_method :altitude, :elevation
76
+ alias_method :alt, :elevation
77
+
78
+ # Link to Station Photo
79
+ attr_reader :photo
80
+
81
+ # Link to map of station location
82
+ attr_reader :map
83
+
84
+ # Link to Metadata page listing
85
+ attr_reader :web
86
+
87
+ attr_reader :instrumentation
88
+
89
+ def initialize(properties)
90
+ @id, @name, @elevation, @photo, @map, @web, @instrumentation = %w(id name elevation photo map web instrumentation).map do |p|
91
+ properties[p]
92
+ end
93
+ @coordinates = GeoKit::LatLng.new(properties['lat'], properties['lng'])
94
+ end
95
+
96
+ # Latitude of station
97
+ def latitude
98
+ @coordinates.lat
99
+ end
100
+ alias_method :lat, :latitude
101
+
102
+ # Longitude of station
103
+ def longitude
104
+ @coordinates.lng
105
+ end
106
+ alias_method :lng, :longitude
107
+ alias_method :lon, :longitude
108
+
109
+ def detail
110
+ {:id => @id, :name => @name, :elev => @elevation, :lat => latitude, :lng => longitude}
111
+ end
112
+ end
113
+ end
@@ -8,11 +8,14 @@ rescue Bundler::BundlerError => e
8
8
  exit e.status_code
9
9
  end
10
10
  require 'test/unit'
11
- require 'shoulda'
11
+ require 'shoulda-context'
12
+ require 'pry'
12
13
 
13
14
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
15
  $LOAD_PATH.unshift(File.dirname(__FILE__))
15
16
  require 'knmi'
16
17
 
17
- class Test::Unit::TestCase
18
+ module KNMI
19
+ class TestCase < Test::Unit::TestCase
20
+ end
18
21
  end
@@ -0,0 +1,53 @@
1
+ require 'helper'
2
+
3
+ class TestHttpService < KNMI::TestCase
4
+ context "get daily data from station" do
5
+ setup do
6
+ station = KNMI.station_by_id(235)
7
+ parameter = KNMI.parameters("daily", "TX")
8
+ start_at = Time.utc(2010, 6, 28)
9
+ end_at = Time.utc(2010, 6, 29)
10
+
11
+ @response = KNMI::HttpService.get_daily(station, parameter, start_at, end_at)
12
+ end
13
+
14
+ should "have HTTParty::Response class" do
15
+ assert_equal @response.class, KNMI::HttpService
16
+ end
17
+
18
+ should "have query" do
19
+ assert_equal @response.query, ["stns=235", "vars=TX", "start=20100628", "end=20100629"]
20
+ end
21
+
22
+ should "have result" do
23
+ assert_equal @response.data, [{:STN=>"235", :YYYYMMDD=>"20100628", :TX=>"263"}, {:STN=>"235", :YYYYMMDD=>"20100629", :TX=>"225"}]
24
+ end
25
+ end
26
+
27
+ context "get hourly data from station" do
28
+ setup do
29
+ station = KNMI.station_by_id(235)
30
+ parameter = KNMI.parameters("hourly", "T")
31
+ start_at = Time.utc(2010, 4, 27, 0)
32
+ end_at = Time.utc(2010, 4, 27, 2)
33
+
34
+ @response = KNMI::HttpService.get_hourly(station, parameter, start_at, end_at)
35
+ end
36
+
37
+ should "have HTTParty::Response class" do
38
+ assert_equal @response.class, KNMI::HttpService
39
+ end
40
+
41
+ should "have query" do
42
+ assert_equal @response.query, ["stns=235", "vars=T", "start=2010042701", "end=2010042703"]
43
+ end
44
+
45
+ should "have result" do
46
+ assert_equal @response.data, [
47
+ {:STN=>"235", :YYYYMMDD=>"20100427", :HH=>"1", :T=>"88"},
48
+ {:STN=>"235", :YYYYMMDD=>"20100427", :HH=>"2", :T=>"79"},
49
+ {:STN=>"235", :YYYYMMDD=>"20100427", :HH=>"3", :T=>"73"} ]
50
+ end
51
+ end
52
+
53
+ end
@@ -1,7 +1,147 @@
1
1
  require 'helper'
2
2
 
3
- class TestKnmi < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
3
+ class TestKNMI < KNMI::TestCase
4
+ context "a station by location" do
5
+ setup do
6
+ @station = KNMI.station_by_location(52.165, 4.419)
7
+ end
8
+
9
+ should "be a struct" do
10
+ assert_equal @station.class, KNMI::Station
11
+ end
12
+
13
+ should 'find id 210' do
14
+ assert_equal @station.id, 210
15
+ end
6
16
  end
7
- end
17
+
18
+ context "a station by id" do
19
+ setup do
20
+ @station = KNMI.station_by_id(210)
21
+ end
22
+
23
+ should "be a struct" do
24
+ assert_equal @station.class, KNMI::Station
25
+ end
26
+
27
+ should 'find id 210' do
28
+ assert_equal @station.id, 210
29
+ end
30
+
31
+ end
32
+
33
+
34
+ # Test KNMI.Parameters Daily
35
+ context "fetch daily - a single parameter" do
36
+ setup do
37
+ @params = KNMI.parameters(period = "daily", "TX")
38
+ end
39
+
40
+ should "be kind of Array" do
41
+ assert_equal @params.class, Array
42
+ end
43
+
44
+ should "contain KNMI::Parameters object" do
45
+ assert_equal @params[0].class, KNMI::Parameters
46
+ end
47
+
48
+ should "be length 1" do
49
+ assert_equal @params.length, 1
50
+ end
51
+ end
52
+
53
+ context "fetch daily - more than one parameter" do
54
+ setup do
55
+ @params = KNMI.parameters(period = "daily", ["TX", "TG"])
56
+ end
57
+
58
+ should "be length 2" do
59
+ assert_equal @params.length, 2
60
+ end
61
+ end
62
+
63
+ context "fetch daily - one doubly named parameter" do
64
+ setup do
65
+ @params = KNMI.parameters(period = "daily", ["TX", "TX"])
66
+ end
67
+
68
+ should "be length 1" do
69
+ assert_equal @params.length, 1
70
+ end
71
+ end
72
+
73
+ context "fetch daily - a single parameter category" do
74
+ setup do
75
+ @params = KNMI.parameters(period = "daily", params = "", categories = "TEMP")
76
+ end
77
+
78
+ should "be length 7" do
79
+ assert_equal @params.length, 7
80
+ end
81
+ end
82
+
83
+ context "fetch daily - more than one parameter category" do
84
+ setup do
85
+ @params = KNMI.parameters(period = "daily", params = "", categories = ["TEMP", "WIND"])
86
+ end
87
+
88
+ should "be length 16" do
89
+ assert_equal @params.length, 16
90
+ end
91
+ end
92
+
93
+ context "fetch daily - one doubly named parameter category" do
94
+ setup do
95
+ @params = KNMI.parameters(period = "daily", params = "", categories = ["WIND", "WIND"])
96
+ end
97
+
98
+ should "be length 9" do
99
+ assert_equal @params.length, 9
100
+ end
101
+ end
102
+
103
+ context "fetch daily - a single parameter and a single parameter category of same grouping" do
104
+ setup do
105
+ @params = KNMI.parameters(period = "daily", params = "TX", categories = "TEMP")
106
+ end
107
+
108
+ should "be length 7" do
109
+ assert_equal @params.length, 7
110
+ end
111
+ end
112
+
113
+ context "fetch daily - an param or category which isn't available" do
114
+ setup do
115
+ @params = KNMI.parameters(period = "daily", "Tmax")
116
+ end
117
+
118
+ should "be length 0" do
119
+ assert_equal @params.length, 0
120
+ end
121
+ end
122
+
123
+ context "get data over daily timestep" do
124
+ setup do
125
+ station = KNMI.station_by_location(52.165, 4.419)
126
+ params = KNMI.parameters(period = "daily", "TX")
127
+ start_at = Time.utc(2010, 6, 28)
128
+ end_at = Time.utc(2010, 6, 29)
129
+ @response = KNMI.get_data(station, params, start_at, end_at)
130
+ end
131
+
132
+ should "have HTTParty::Response class" do
133
+ assert_equal @response.class, KNMI::HttpService
134
+ end
135
+
136
+ should "have query" do
137
+ assert_equal @response.query, ["stns=210", "vars=TX", "start=20100628", "end=20100629"]
138
+ end
139
+
140
+ should "have result" do
141
+ assert_equal @response.data, [
142
+ {:STN=>"210", :YYYYMMDD=>"20100628", :TX=>"268"},
143
+ {:STN=>"210", :YYYYMMDD=>"20100629", :TX=>"239"}]
144
+ end
145
+ end
146
+
147
+ end