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

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.
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