buoy_data 1.0.0.beta.1 → 1.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0.beta.1
1
+ 1.0.0.beta.2
data/buoy_data.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{buoy_data}
8
- s.version = "1.0.0.beta.1"
8
+ s.version = "1.0.0.beta.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Adam Weller"]
12
- s.date = %q{2011-04-17}
12
+ s.date = %q{2011-05-06}
13
13
  s.description = %q{The goal of this gem is to provide marine buoy data from a variety of sources}
14
14
  s.email = %q{minch@trazzler.com}
15
15
  s.extra_rdoc_files = [
@@ -27,14 +27,18 @@ Gem::Specification.new do |s|
27
27
  "lib/buoy_data.rb",
28
28
  "lib/buoy_data/buoy_list.rb",
29
29
  "lib/buoy_data/buoy_reading.rb",
30
+ "lib/buoy_data/field_map.rb",
31
+ "lib/buoy_data/noaa.rb",
30
32
  "lib/buoy_data/noaa_buoy_forecast.rb",
31
33
  "lib/buoy_data/noaa_buoy_list.rb",
32
34
  "lib/buoy_data/noaa_buoy_observation.rb",
33
35
  "lib/buoy_data/noaa_buoy_reading.rb",
36
+ "lib/buoy_data/noaa_field_map.rb",
34
37
  "lib/buoy_data/noaa_station.rb",
35
38
  "spec/noaa_buoy_forecast_spec.rb",
36
39
  "spec/noaa_buoy_list_spec.rb",
37
40
  "spec/noaa_buoy_observation_spec.rb",
41
+ "spec/noaa_station_spec.rb",
38
42
  "spec/spec_helper.rb"
39
43
  ]
40
44
  s.homepage = %q{http://github.com/minch/buoy_data}
@@ -46,6 +50,7 @@ Gem::Specification.new do |s|
46
50
  "spec/noaa_buoy_forecast_spec.rb",
47
51
  "spec/noaa_buoy_list_spec.rb",
48
52
  "spec/noaa_buoy_observation_spec.rb",
53
+ "spec/noaa_station_spec.rb",
49
54
  "spec/spec_helper.rb"
50
55
  ]
51
56
 
@@ -3,7 +3,7 @@ module BuoyData
3
3
  require 'nokogiri'
4
4
 
5
5
  class BuoyList
6
- def get
6
+ def get(limit = 0)
7
7
  stats = {
8
8
  :stations => [],
9
9
  :station_count => 0,
@@ -13,15 +13,20 @@ module BuoyData
13
13
  @doc = doc
14
14
  @station_list = stations doc
15
15
 
16
+ # Only relevant for testing
17
+ @station_list = @station_list[0..limit] if limit > 0
18
+
16
19
  @station_list.each do |station_url|
17
- begin
20
+ #p station_url
21
+
22
+ #begin
18
23
  h = scrape_station(station_url)
19
24
  stats[:stations].push h
20
25
  stats[:station_count] += 1
21
- rescue => e
22
- stats[:error_count] += 1
23
- stats[:errors].push({ :url => s, :error => e.backtrace })
24
- end
26
+ #rescue => e
27
+ #stats[:error_count] += 1
28
+ #stats[:errors].push({ :url => station_url, :error => e.backtrace })
29
+ #end
25
30
  end
26
31
 
27
32
  stats
@@ -0,0 +1,3 @@
1
+ class FieldMap
2
+ # TODO: these classes should be singletons
3
+ end
@@ -0,0 +1,5 @@
1
+ module BuoyData
2
+ module Noaa
3
+ BASE_URL = 'http://www.ndbc.noaa.gov'
4
+ end
5
+ end
@@ -5,7 +5,11 @@ module BuoyData
5
5
  end
6
6
 
7
7
  def base_url
8
- 'http://www.ndbc.noaa.gov'
8
+ Noaa::BASE_URL
9
+ end
10
+
11
+ def scrape_station(url)
12
+ NoaaStation.new.scrape(url)
9
13
  end
10
14
 
11
15
  private
@@ -15,10 +19,5 @@ module BuoyData
15
19
 
16
20
  doc.xpath(xpath).map{|element| element['href']}
17
21
  end
18
-
19
- def scrape_station(url)
20
- url = [ base_url, url ].join('/')
21
- NoaaStation.new.scrape(url)
22
- end
23
22
  end
24
23
  end
@@ -0,0 +1,15 @@
1
+ class NoaaFieldMap < FieldMap
2
+ def self.field_map
3
+ f = {
4
+ :wvht => ['Wave Height', 'ft'],
5
+ :dpd => ['Dominant Wave Period', 's'],
6
+ :apd => ['Average Period', 's'],
7
+ :gst => ['Wind Gust', 'kts'],
8
+ :wspd => ['Wind Speed', 'kts'],
9
+ :wdir => ['Wind Direction', 'deg'],
10
+ #:wtmp => ['Water Temperature',"#{String.new('U+00B0')} F"],
11
+ :wtmp => ['Water Temperature',"F"],
12
+ :atmp => ['Air Temperature', "F"]
13
+ }
14
+ end
15
+ end
@@ -2,8 +2,23 @@ module BuoyData
2
2
  require 'open-uri'
3
3
  require 'nokogiri'
4
4
 
5
+ # This class scrapes readings from noaa stations.
6
+ # A station in this parlance is an noaa resource that does
7
+ # not have wave data (in other words is not necessarily
8
+ # a floating buoy).
9
+ #
10
+ # There is no super class for this model as we don't yet
11
+ # know if this set up will apply for other data sources.
12
+ #
13
+ # NOTE: This class can currently be used to scrape the latest
14
+ # readings for buoys as well. The reason it exists however
15
+ # is that we can't fetch data for the stations from the .bull
16
+ # files (they don't exists for stations only buoys).
17
+
5
18
  class NoaaStation
6
19
  def scrape(url)
20
+ url = normalize_url url
21
+
7
22
  h = {}
8
23
  xpath = "//h1"
9
24
 
@@ -27,29 +42,83 @@ module BuoyData
27
42
  element = elements.find{|e| /\d{2,3}\.\d{2,3}/.match(e)}
28
43
  latlng = lat_lng_from element
29
44
 
30
- unless latlng.empty?
45
+ unless latlng.blank?
31
46
  h[:lat] = normal_lat latlng.first
32
47
  h[:lng] = normal_lng latlng.last
33
48
  end
34
49
 
35
50
  # Latest reading
36
- xpath = "//table/caption[@class='titleDataHeader'][text()[contains(.,'Conditions')]]"
51
+ current_reading = {}
52
+
53
+ current_reading = latest_reading doc
54
+
55
+ # If we couldn't get a water temp from this station then it's not of use
56
+ return {} unless valid_reading? current_reading
57
+ h.merge! current_reading
58
+
59
+ h
60
+ end
61
+
62
+ # Do the best we can but get something
63
+ def latest_reading(doc)
64
+ # Check the current reading
65
+ reading = current_reading doc
66
+
67
+ # If we don't have a wave height (:wvht) then let's check the previous reading
68
+ unless reading[:wvht]
69
+ p_reading = previous_reading doc
70
+ reading = p_reading if p_reading[:wvht]
71
+ end
72
+
73
+ reading
74
+ end
75
+
76
+ # Reding from the 'Conditions at..as of..' table
77
+ def current_reading(doc)
78
+ reading = {}
79
+
80
+ xpath = "//table/caption[@class='titleDataHeader']["
81
+ xpath += "contains(text(),'Conditions')"
82
+ xpath += " and "
83
+ xpath += "not(contains(text(),'Solar Radiation'))"
84
+ xpath += "]"
85
+
86
+ # Get the reading timestamp
87
+ source_updated_at = reading_timestamp(doc, xpath)
88
+ reading[:source_updated_at] = source_updated_at
89
+
90
+ # Get the reading data
37
91
  xpath += "/../tr"
38
92
  elements = doc.xpath xpath
39
93
 
40
- current_reading = {}
41
94
  unless elements.empty?
42
95
  elements.each do |element|
43
- reading = scrape_condition_from_element(element)
44
- current_reading.merge! reading unless reading.blank?
96
+ r = scrape_condition_from_element(element)
97
+ reading.merge! r unless r.blank?
45
98
  end
46
99
  end
47
100
 
48
- # If we couldn't get a water temp from this station then it's not of use
49
- return {} unless valid_reading? current_reading
50
- h.merge! current_reading
101
+ reading
102
+ end
51
103
 
52
- h
104
+ # Most recent observation from the 'Previous observations' table
105
+ # This is unfinished, will need to capture the markup when the
106
+ # current reading is not avaliable and stub it w/fakeweb...
107
+ def previous_reading(doc)
108
+ reading = {}
109
+ text = 'Previous observations'
110
+ xpath = "//table/caption[@class='dataHeader'][text()[contains(.,'#{text}')]]"
111
+
112
+ xpath += "/../tr/td"
113
+ elements = doc.xpath xpath
114
+
115
+ #unless elements.empty?
116
+ #elements.each do |element|
117
+ #p element.text
118
+ #end
119
+ #end
120
+
121
+ reading
53
122
  end
54
123
 
55
124
  private
@@ -60,10 +129,7 @@ module BuoyData
60
129
 
61
130
  def scrape_condition_from_element(element)
62
131
  reading = {}
63
- # (include units)
64
- #regexp = /\(([A-Z]+)\):\n\t ?(.*)\n$/
65
- # (no units)
66
- regexp = /\(([A-Z]+)\):\n\t ?([0-9.-]+).*\n$/
132
+ regexp = /\(([A-Z]+)\):\n\n?\t? ?(.*)\n$/m
67
133
  text = element.text
68
134
 
69
135
  if regexp.match(text)
@@ -72,6 +138,7 @@ module BuoyData
72
138
  key = key.downcase.to_sym
73
139
  value = $2
74
140
  return reading unless value
141
+ value = $1 if value =~ /([0-9.]+)/
75
142
  value = value.lstrip.rstrip.to_f
76
143
  reading = { key => value }
77
144
  end
@@ -80,7 +147,8 @@ module BuoyData
80
147
  end
81
148
 
82
149
  def lat_lng_from(text)
83
- text = text.sub(/ \(.*$/, '')
150
+ text = text.sub(/ \(.*$/, '') rescue nil
151
+ return unless text
84
152
  matches = /([0-9\.]+ ?[NESW]) ([0-9\.]+ ?[NESW])/.match(text)
85
153
 
86
154
  matches && matches.size == 3 ? [ $1, $2 ] : []
@@ -101,5 +169,34 @@ module BuoyData
101
169
 
102
170
  lng
103
171
  end
172
+
173
+ def normalize_url(url)
174
+ base_url = Noaa::BASE_URL
175
+ /^#{base_url}/.match(url) ? url : [Noaa::BASE_URL, url].join('/')
176
+ end
177
+
178
+ def logger
179
+ RAILS_DEFAULT_LOGGER if defined? RAILS_DEFAULT_LOGGER
180
+ end
181
+
182
+ def reading_timestamp(doc, xpath)
183
+ elements = doc.xpath(xpath).children rescue []
184
+ unless elements.empty?
185
+ begin
186
+ elements = elements.to_a
187
+ elements.reject!{|e|e.name == 'br'}
188
+ s = elements.last.text.sub(/ ?:$/, '')
189
+ time, date = s.split(/ on /)
190
+ month, day, year = date.split(/\//)
191
+ date = [ day, month, year ].join('-')
192
+ hour = time[0..1]
193
+ minutes = time[2..3]
194
+ time = [ hour, minutes ].join(':')
195
+ s = [ date, time ].join('T')
196
+ t = Time.parse s if s
197
+ rescue
198
+ end
199
+ end
200
+ end
104
201
  end
105
202
  end
data/lib/buoy_data.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  require 'httparty'
2
2
  require 'buoy_data/buoy_list'
3
3
  require 'buoy_data/buoy_reading'
4
+ require 'buoy_data/noaa'
4
5
  require 'buoy_data/noaa_buoy_reading'
5
6
  require 'buoy_data/noaa_station'
6
7
  require 'buoy_data/noaa_buoy_list'
7
8
  require 'buoy_data/noaa_buoy_observation'
8
9
  require 'buoy_data/noaa_buoy_forecast'
10
+ require 'buoy_data/field_map'
11
+ require 'buoy_data/noaa_field_map'
@@ -13,11 +13,6 @@ describe BuoyData::NoaaBuoyList do
13
13
  results[:errors].should be_empty
14
14
  stations = results[:stations]
15
15
  stations.compact.should_not be_empty
16
- station = stations.first
17
-
18
- # Verify we have water temp value
19
- wtmp = station[:wtmp]
20
- wtmp.should be_kind_of Float
21
16
  end
22
17
  end
23
18
 
@@ -0,0 +1,22 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe BuoyData::NoaaStation do
4
+ let (:station) { BuoyData::NoaaStation.new }
5
+
6
+ it "should scrape station" do
7
+ data = station.scrape station_url
8
+ data.should_not be_empty
9
+ data.should be_a(Hash)
10
+
11
+ data[:wtmp].should be_a(Float)
12
+ data[:source_updated_at].should be_a(Time)
13
+ end
14
+
15
+ def station_url
16
+ "station_page.php?station=sauf1"
17
+ end
18
+
19
+ def buoy_url
20
+ "station_page.php?station=41012"
21
+ end
22
+ end
metadata CHANGED
@@ -7,8 +7,8 @@ version: !ruby/object:Gem::Version
7
7
  - 0
8
8
  - 0
9
9
  - beta
10
- - 1
11
- version: 1.0.0.beta.1
10
+ - 2
11
+ version: 1.0.0.beta.2
12
12
  platform: ruby
13
13
  authors:
14
14
  - Adam Weller
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-04-17 00:00:00 -04:00
19
+ date: 2011-05-06 00:00:00 -04:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -91,14 +91,18 @@ files:
91
91
  - lib/buoy_data.rb
92
92
  - lib/buoy_data/buoy_list.rb
93
93
  - lib/buoy_data/buoy_reading.rb
94
+ - lib/buoy_data/field_map.rb
95
+ - lib/buoy_data/noaa.rb
94
96
  - lib/buoy_data/noaa_buoy_forecast.rb
95
97
  - lib/buoy_data/noaa_buoy_list.rb
96
98
  - lib/buoy_data/noaa_buoy_observation.rb
97
99
  - lib/buoy_data/noaa_buoy_reading.rb
100
+ - lib/buoy_data/noaa_field_map.rb
98
101
  - lib/buoy_data/noaa_station.rb
99
102
  - spec/noaa_buoy_forecast_spec.rb
100
103
  - spec/noaa_buoy_list_spec.rb
101
104
  - spec/noaa_buoy_observation_spec.rb
105
+ - spec/noaa_station_spec.rb
102
106
  - spec/spec_helper.rb
103
107
  has_rdoc: true
104
108
  homepage: http://github.com/minch/buoy_data
@@ -138,4 +142,5 @@ test_files:
138
142
  - spec/noaa_buoy_forecast_spec.rb
139
143
  - spec/noaa_buoy_list_spec.rb
140
144
  - spec/noaa_buoy_observation_spec.rb
145
+ - spec/noaa_station_spec.rb
141
146
  - spec/spec_helper.rb