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