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 +1 -1
- data/buoy_data.gemspec +7 -2
- data/lib/buoy_data/buoy_list.rb +11 -6
- data/lib/buoy_data/field_map.rb +3 -0
- data/lib/buoy_data/noaa.rb +5 -0
- data/lib/buoy_data/noaa_buoy_list.rb +5 -6
- data/lib/buoy_data/noaa_field_map.rb +15 -0
- data/lib/buoy_data/noaa_station.rb +111 -14
- data/lib/buoy_data.rb +3 -0
- data/spec/noaa_buoy_list_spec.rb +0 -5
- data/spec/noaa_station_spec.rb +22 -0
- metadata +8 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.0.beta.
|
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.
|
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-
|
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
|
|
data/lib/buoy_data/buoy_list.rb
CHANGED
@@ -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
|
-
|
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 =>
|
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
|
@@ -5,7 +5,11 @@ module BuoyData
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def base_url
|
8
|
-
|
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.
|
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
|
-
|
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
|
-
|
44
|
-
|
96
|
+
r = scrape_condition_from_element(element)
|
97
|
+
reading.merge! r unless r.blank?
|
45
98
|
end
|
46
99
|
end
|
47
100
|
|
48
|
-
|
49
|
-
|
50
|
-
h.merge! current_reading
|
101
|
+
reading
|
102
|
+
end
|
51
103
|
|
52
|
-
|
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
|
-
|
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'
|
data/spec/noaa_buoy_list_spec.rb
CHANGED
@@ -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
|
-
-
|
11
|
-
version: 1.0.0.beta.
|
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-
|
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
|