weather_gov 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/dependabot.yml +8 -0
- data/.github/workflows/rspec.yml +23 -0
- data/.github/workflows/rubocop.yml +23 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +29 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +40 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/weather_gov.rb +13 -0
- data/lib/weather_gov/address.rb +27 -0
- data/lib/weather_gov/alert.rb +45 -0
- data/lib/weather_gov/alert_collection.rb +28 -0
- data/lib/weather_gov/api.rb +93 -0
- data/lib/weather_gov/client.rb +115 -0
- data/lib/weather_gov/feature.rb +40 -0
- data/lib/weather_gov/feature_collection.rb +20 -0
- data/lib/weather_gov/forecast.rb +27 -0
- data/lib/weather_gov/forecast_period.rb +63 -0
- data/lib/weather_gov/gridpoint.rb +75 -0
- data/lib/weather_gov/identifier.rb +47 -0
- data/lib/weather_gov/identifier/alert.rb +13 -0
- data/lib/weather_gov/identifier/county_zone.rb +13 -0
- data/lib/weather_gov/identifier/fire_zone.rb +13 -0
- data/lib/weather_gov/identifier/forecast_zone.rb +13 -0
- data/lib/weather_gov/identifier/gridpoint.rb +29 -0
- data/lib/weather_gov/identifier/office.rb +13 -0
- data/lib/weather_gov/identifier/point.rb +21 -0
- data/lib/weather_gov/identifier/problem.rb +13 -0
- data/lib/weather_gov/identifier/station.rb +13 -0
- data/lib/weather_gov/observation_station.rb +27 -0
- data/lib/weather_gov/observation_station_collection.rb +20 -0
- data/lib/weather_gov/office.rb +68 -0
- data/lib/weather_gov/point.rb +81 -0
- data/lib/weather_gov/product.rb +29 -0
- data/lib/weather_gov/product_list.rb +17 -0
- data/lib/weather_gov/relative_location.rb +23 -0
- data/lib/weather_gov/valid_duration_value.rb +26 -0
- data/lib/weather_gov/valid_time.rb +48 -0
- data/lib/weather_gov/version.rb +5 -0
- data/lib/weather_gov/zone.rb +33 -0
- data/weather_gov.gemspec +39 -0
- metadata +190 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "weather_gov/office"
|
4
|
+
require "weather_gov/observation_station"
|
5
|
+
require "weather_gov/observation_station_collection"
|
6
|
+
require "weather_gov/point"
|
7
|
+
require "weather_gov/gridpoint"
|
8
|
+
require "weather_gov/alert_collection"
|
9
|
+
require "weather_gov/forecast"
|
10
|
+
require "weather_gov/zone"
|
11
|
+
require "weather_gov/product"
|
12
|
+
require "weather_gov/product_list"
|
13
|
+
|
14
|
+
module WeatherGov
|
15
|
+
class Client
|
16
|
+
attr_reader :api
|
17
|
+
|
18
|
+
def initialize(user_agent:)
|
19
|
+
@api = API.new(user_agent: user_agent)
|
20
|
+
end
|
21
|
+
|
22
|
+
def json_for(response)
|
23
|
+
return response.parsed_response unless response.parsed_response.include?("status")
|
24
|
+
|
25
|
+
raise response.parsed_response.fetch("title", "Unknown Error")
|
26
|
+
end
|
27
|
+
|
28
|
+
def stations(data: nil, uri: nil)
|
29
|
+
return ObservationStationCollection.new(client: self, data: data) if data
|
30
|
+
return ObservationStationCollection.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
31
|
+
|
32
|
+
# In theory an "all stations" query is okay, but in practice it seems to time out.
|
33
|
+
|
34
|
+
raise ArgumentError, "data or uri parameter required"
|
35
|
+
end
|
36
|
+
|
37
|
+
def station(data: nil, uri: nil, id: nil)
|
38
|
+
return ObservationStation.new(client: self, data: data) if data
|
39
|
+
return ObservationStation.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
40
|
+
return ObservationStation.new(client: self, data: -> { json_for(api.station(id: id)) }) if id
|
41
|
+
|
42
|
+
raise ArgumentError, "data or id parameter required"
|
43
|
+
end
|
44
|
+
|
45
|
+
def office(data: nil, uri: nil, id: nil)
|
46
|
+
return Office.new(client: self, data: data) if data
|
47
|
+
return Office.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
48
|
+
return Office.new(client: self, data: -> { json_for(api.office(id: id)) }) if id
|
49
|
+
|
50
|
+
raise ArgumentError, "data, uri, or id parameter required"
|
51
|
+
end
|
52
|
+
|
53
|
+
def point(data: nil, uri: nil, lat: nil, lon: nil)
|
54
|
+
return Point.new(client: self, data: data) if data
|
55
|
+
return Point.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
56
|
+
return Point.new(client: self, data: -> { json_for(api.point(lat: lat, lon: lon)) }) if lat && lon
|
57
|
+
|
58
|
+
raise ArgumentError, "data, uri, or lat and lon parameters required"
|
59
|
+
end
|
60
|
+
|
61
|
+
def gridpoint(data: nil, uri: nil)
|
62
|
+
return Gridpoint.new(client: self, data: data) if data
|
63
|
+
return Gridpoint.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
64
|
+
|
65
|
+
raise ArgumentError, "data, uri, or lat and lon parameters required"
|
66
|
+
end
|
67
|
+
|
68
|
+
def alerts_active(data: nil, zone: nil, area: nil, region: nil)
|
69
|
+
return AlertCollection.new(client: self, data: data) if data
|
70
|
+
|
71
|
+
if zone || area || region
|
72
|
+
return AlertCollection.new(
|
73
|
+
client: self,
|
74
|
+
data: -> { json_for(api.alerts_active(zone: zone, area: area, region: region)) }
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
raise ArgumentError, "data, zone, area, or region parameter required"
|
79
|
+
end
|
80
|
+
|
81
|
+
def zone(data: nil, uri: nil, type: nil, id: nil)
|
82
|
+
return Zone.new(client: self, data: data) if data
|
83
|
+
return Zone.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
84
|
+
return Zone.new(client: self, data: -> { json_for(api.zone(type: type, id: id)) }) if type && id
|
85
|
+
|
86
|
+
raise ArgumentError, "data, uri, or type and id parameter required"
|
87
|
+
end
|
88
|
+
|
89
|
+
def forecast(data: nil, uri: nil)
|
90
|
+
return Forecast.new(client: self, data: data) if data
|
91
|
+
return Forecast.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
92
|
+
|
93
|
+
raise ArgumentError, "data or uri parameter required"
|
94
|
+
end
|
95
|
+
|
96
|
+
def products(data: nil, uri: nil, type: nil, location: nil)
|
97
|
+
return ProductList.new(client: self, data: data) if data
|
98
|
+
return ProductList.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
99
|
+
|
100
|
+
if type && location
|
101
|
+
return ProductList.new(client: self, data: -> { json_for(api.products(type: type, location: location)) })
|
102
|
+
end
|
103
|
+
|
104
|
+
raise ArgumentError, "data, uri, or type and location parameters required"
|
105
|
+
end
|
106
|
+
|
107
|
+
def product(data: nil, uri: nil, id: nil)
|
108
|
+
return Product.new(client: self, data: data) if data
|
109
|
+
return Product.new(client: self, data: -> { json_for(api.get(uri: uri)) }) if uri
|
110
|
+
return Product.new(client: self, data: -> { json_for(api.product(id: id)) }) if id
|
111
|
+
|
112
|
+
raise ArgumentError, "data, uri, or id parameter required"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeatherGov
|
4
|
+
class Feature
|
5
|
+
attr_reader :client
|
6
|
+
|
7
|
+
def initialize(client:, data:)
|
8
|
+
@client = client
|
9
|
+
if data.respond_to?(:call)
|
10
|
+
@data_proc = data
|
11
|
+
else
|
12
|
+
@data = data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def data
|
17
|
+
@data ||= @data_proc.call
|
18
|
+
end
|
19
|
+
|
20
|
+
def context
|
21
|
+
data.fetch("@context", nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
def id
|
25
|
+
data.fetch("id")
|
26
|
+
end
|
27
|
+
|
28
|
+
def type
|
29
|
+
data.fetch("type")
|
30
|
+
end
|
31
|
+
|
32
|
+
def geometry
|
33
|
+
data.fetch("geometry", nil)
|
34
|
+
end
|
35
|
+
|
36
|
+
def properties
|
37
|
+
data.fetch("properties", nil)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "weather_gov/feature"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module WeatherGov
|
7
|
+
class FeatureCollection < Feature
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def self.feature_class
|
11
|
+
Feature
|
12
|
+
end
|
13
|
+
|
14
|
+
def features
|
15
|
+
@features ||= data.fetch("features").map { |feature| self.class.feature_class.new(client: client, data: feature) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def_delegators :features, :[], :size, :each, :first, :last, :map
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "weather_gov/forecast_period"
|
4
|
+
|
5
|
+
module WeatherGov
|
6
|
+
class Forecast < Feature
|
7
|
+
def update_time
|
8
|
+
@update_time ||= Time.parse(properties.fetch("updateTime"))
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid_time
|
12
|
+
@valid_time ||= ValidTime.parse(properties.fetch("validTimes"))
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
valid_time.valid?
|
17
|
+
end
|
18
|
+
|
19
|
+
def periods
|
20
|
+
properties.fetch("periods").map { |period| ForecastPeriod.new(data: period) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def current
|
24
|
+
periods.find { |p| (p.start_time...p.end_time).include?(Time.now) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeatherGov
|
4
|
+
class ForecastPeriod
|
5
|
+
attr_reader :data
|
6
|
+
|
7
|
+
def initialize(data:)
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def number
|
12
|
+
data.fetch("number")
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
data.fetch("name")
|
17
|
+
end
|
18
|
+
|
19
|
+
def start_time
|
20
|
+
@start_time ||= Time.parse(data.fetch("startTime"))
|
21
|
+
end
|
22
|
+
|
23
|
+
def end_time
|
24
|
+
@end_time ||= Time.parse(data.fetch("endTime"))
|
25
|
+
end
|
26
|
+
|
27
|
+
def daytime?
|
28
|
+
data.fetch("isDaytime")
|
29
|
+
end
|
30
|
+
|
31
|
+
def temperature
|
32
|
+
data.fetch("temperature")
|
33
|
+
end
|
34
|
+
|
35
|
+
def temperature_unit
|
36
|
+
data.fetch("temperatureUnit")
|
37
|
+
end
|
38
|
+
|
39
|
+
def temperature_trend
|
40
|
+
data.fetch("temperatureTrend")
|
41
|
+
end
|
42
|
+
|
43
|
+
def wind_speed
|
44
|
+
data.fetch("windSpeed")
|
45
|
+
end
|
46
|
+
|
47
|
+
def wind_direction
|
48
|
+
data.fetch("windDirection")
|
49
|
+
end
|
50
|
+
|
51
|
+
def icon
|
52
|
+
data.fetch("icon")
|
53
|
+
end
|
54
|
+
|
55
|
+
def short_forecast
|
56
|
+
data.fetch("shortForecast")
|
57
|
+
end
|
58
|
+
|
59
|
+
def detailed_forecast
|
60
|
+
data.fetch("detailedForecast")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "weather_gov/valid_duration_value"
|
4
|
+
|
5
|
+
module WeatherGov
|
6
|
+
class Gridpoint < Feature
|
7
|
+
def update_time
|
8
|
+
@update_time ||= Time.parse(properties.fetch("updateTime"))
|
9
|
+
end
|
10
|
+
|
11
|
+
def forecast_office
|
12
|
+
@forecast_office ||= Identifier::Office.new(properties.fetch("forecastOffice"))
|
13
|
+
end
|
14
|
+
|
15
|
+
def temperature_unit
|
16
|
+
@temperature_unit ||= properties.dig("temperature", "uom").split(":").last
|
17
|
+
end
|
18
|
+
|
19
|
+
def temperature
|
20
|
+
@temperature ||= properties.dig("temperature", "values").map do |data|
|
21
|
+
ValidDurationValue.new(data: data, unit: temperature_unit)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def dewpoint_unit
|
26
|
+
@dewpoint_unit ||= properties.dig("dewpoint", "uom").split(":").last
|
27
|
+
end
|
28
|
+
|
29
|
+
def dewpoint
|
30
|
+
@dewpoint ||= properties.dig("dewpoint", "values").map do |data|
|
31
|
+
ValidDurationValue.new(data: data, unit: dewpoint_unit)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def min_temperature_unit
|
36
|
+
@min_temperature_unit ||= properties.dig("minTemperature", "uom").split(":").last
|
37
|
+
end
|
38
|
+
|
39
|
+
def min_temperature
|
40
|
+
@min_temperature ||= properties.dig("minTemperature", "values").map do |data|
|
41
|
+
ValidDurationValue.new(data: data, unit: min_temperature_unit)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def max_temperature_unit
|
46
|
+
@max_temperature_unit ||= properties.dig("maxTemperature", "uom").split(":").last
|
47
|
+
end
|
48
|
+
|
49
|
+
def max_temperature
|
50
|
+
@max_temperature ||= properties.dig("maxTemperature", "values").map do |data|
|
51
|
+
ValidDurationValue.new(data: data, unit: max_temperature_unit)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def apparent_temperature_unit
|
56
|
+
@apparent_temperature_unit ||= properties.dig("apparentTemperature", "uom").split(":").last
|
57
|
+
end
|
58
|
+
|
59
|
+
def apparent_temperature
|
60
|
+
@apparent_temperature ||= properties.dig("apparentTemperature", "values").map do |data|
|
61
|
+
ValidDurationValue.new(data: data, unit: apparent_temperature_unit)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def relative_humidity_unit
|
66
|
+
@relative_humidity_unit ||= properties.dig("relativeHumidity", "uom").split(":").last
|
67
|
+
end
|
68
|
+
|
69
|
+
def relative_humidity
|
70
|
+
@relative_humidity ||= properties.dig("relativeHumidity", "values").map do |data|
|
71
|
+
ValidDurationValue.new(data: data, unit: relative_humidity_unit)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeatherGov
|
4
|
+
class Identifier
|
5
|
+
attr_reader :uri
|
6
|
+
|
7
|
+
def self.parse(uri)
|
8
|
+
TYPES.each do |type|
|
9
|
+
return type.new(uri) if uri.start_with?(type.base_uri)
|
10
|
+
end
|
11
|
+
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.base_uri
|
16
|
+
API_BASE_URI
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(uri)
|
20
|
+
@uri = uri
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
id
|
25
|
+
end
|
26
|
+
|
27
|
+
def id
|
28
|
+
uri.sub(/^#{self.class.base_uri}/, "")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require "weather_gov/identifier/alert"
|
34
|
+
require "weather_gov/identifier/county_zone"
|
35
|
+
require "weather_gov/identifier/fire_zone"
|
36
|
+
require "weather_gov/identifier/forecast_zone"
|
37
|
+
require "weather_gov/identifier/gridpoint"
|
38
|
+
require "weather_gov/identifier/office"
|
39
|
+
require "weather_gov/identifier/point"
|
40
|
+
require "weather_gov/identifier/problem"
|
41
|
+
require "weather_gov/identifier/station"
|
42
|
+
|
43
|
+
module WeatherGov
|
44
|
+
class Identifier
|
45
|
+
TYPES = constants.map { |c| const_get(c) }.select { |c| c.superclass == self }
|
46
|
+
end
|
47
|
+
end
|