knmi 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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