nws 0.2.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/LICENSE +24 -0
- data/README.md +55 -0
- data/bin/nws +429 -0
- data/lib/nws/alert.rb +267 -0
- data/lib/nws/client.rb +113 -0
- data/lib/nws/errors.rb +36 -0
- data/lib/nws/forecast.rb +207 -0
- data/lib/nws/geocoder.rb +276 -0
- data/lib/nws/hourly_forecast.rb +209 -0
- data/lib/nws/observation.rb +204 -0
- data/lib/nws/point.rb +166 -0
- data/lib/nws/station.rb +84 -0
- data/lib/nws/version.rb +3 -0
- data/lib/nws.rb +109 -0
- metadata +98 -0
data/lib/nws/point.rb
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NWS
|
|
4
|
+
# Represents a geographic point and provides access to weather data for that location
|
|
5
|
+
class Point
|
|
6
|
+
# @return [Float] Latitude of the point
|
|
7
|
+
attr_reader :latitude
|
|
8
|
+
|
|
9
|
+
# @return [Float] Longitude of the point
|
|
10
|
+
attr_reader :longitude
|
|
11
|
+
|
|
12
|
+
# @return [String] NWS grid identifier (e.g., "LWX")
|
|
13
|
+
attr_reader :grid_id
|
|
14
|
+
|
|
15
|
+
# @return [Integer] X coordinate on the NWS grid
|
|
16
|
+
attr_reader :grid_x
|
|
17
|
+
|
|
18
|
+
# @return [Integer] Y coordinate on the NWS grid
|
|
19
|
+
attr_reader :grid_y
|
|
20
|
+
|
|
21
|
+
# @return [String] URL for the forecast endpoint
|
|
22
|
+
attr_reader :forecast_url
|
|
23
|
+
|
|
24
|
+
# @return [String] URL for the hourly forecast endpoint
|
|
25
|
+
attr_reader :forecast_hourly_url
|
|
26
|
+
|
|
27
|
+
# @return [String] URL for the forecast grid data endpoint
|
|
28
|
+
attr_reader :forecast_grid_data_url
|
|
29
|
+
|
|
30
|
+
# @return [String] URL for the observation stations endpoint
|
|
31
|
+
attr_reader :observation_stations_url
|
|
32
|
+
|
|
33
|
+
# @return [String] City name for the point
|
|
34
|
+
attr_reader :city
|
|
35
|
+
|
|
36
|
+
# @return [String] State abbreviation for the point
|
|
37
|
+
attr_reader :state
|
|
38
|
+
|
|
39
|
+
# @return [String] Time zone identifier (e.g., "America/New_York")
|
|
40
|
+
attr_reader :time_zone
|
|
41
|
+
|
|
42
|
+
# @return [Hash] Raw API response data
|
|
43
|
+
attr_reader :raw_data
|
|
44
|
+
|
|
45
|
+
# Initialize a Point from API response data
|
|
46
|
+
#
|
|
47
|
+
# @param data [Hash] Parsed JSON response from the points API
|
|
48
|
+
# @param client [Client] NWS API client instance
|
|
49
|
+
def initialize(data, client:)
|
|
50
|
+
@client = client
|
|
51
|
+
@raw_data = data
|
|
52
|
+
parse_data(data)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Fetch point data for given coordinates
|
|
56
|
+
#
|
|
57
|
+
# @param lat [Float] Latitude
|
|
58
|
+
# @param lon [Float] Longitude
|
|
59
|
+
# @param client [Client] NWS API client instance
|
|
60
|
+
# @return [Point] Point instance with location data
|
|
61
|
+
# @raise [InvalidCoordinatesError] if coordinates are invalid or outside US coverage
|
|
62
|
+
def self.fetch(lat, lon, client: NWS.client)
|
|
63
|
+
validate_coordinates(lat, lon)
|
|
64
|
+
lat_rounded = lat.round(4)
|
|
65
|
+
lon_rounded = lon.round(4)
|
|
66
|
+
|
|
67
|
+
data = client.get("/points/#{lat_rounded},#{lon_rounded}")
|
|
68
|
+
new(data, client: client)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get the 7-day forecast for this point
|
|
72
|
+
#
|
|
73
|
+
# @return [Forecast] Forecast data for this location
|
|
74
|
+
def forecast
|
|
75
|
+
data = @client.get_url(@forecast_url)
|
|
76
|
+
Forecast.new(data, point: self)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get the hourly forecast for this point
|
|
80
|
+
#
|
|
81
|
+
# @return [HourlyForecast] Hourly forecast data for this location
|
|
82
|
+
def hourly_forecast
|
|
83
|
+
data = @client.get_url(@forecast_hourly_url)
|
|
84
|
+
HourlyForecast.new(data, point: self)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Get observation stations near this point
|
|
88
|
+
#
|
|
89
|
+
# @return [Array<Station>] Array of nearby weather stations, ordered by proximity
|
|
90
|
+
def observation_stations
|
|
91
|
+
data = @client.get_url(@observation_stations_url)
|
|
92
|
+
features = data["features"] || []
|
|
93
|
+
features.map { |f| Station.new(f, client: @client) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get the nearest observation station to this point
|
|
97
|
+
#
|
|
98
|
+
# @return [Station, nil] Nearest station or nil if none found
|
|
99
|
+
def nearest_station
|
|
100
|
+
observation_stations.first
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get current weather conditions for this point
|
|
104
|
+
#
|
|
105
|
+
# @return [Observation] Current weather observation
|
|
106
|
+
# @raise [NotFoundError] if no observation stations found
|
|
107
|
+
def current_conditions
|
|
108
|
+
station = nearest_station
|
|
109
|
+
raise NotFoundError.new("No observation stations found for this location") unless station
|
|
110
|
+
station.latest_observation
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Get a human-readable location string
|
|
114
|
+
#
|
|
115
|
+
# @return [String] Location in "City, State" format
|
|
116
|
+
def location_string
|
|
117
|
+
[city, state].compact.join(", ")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# Validate that coordinates are valid and within US coverage area
|
|
123
|
+
#
|
|
124
|
+
# @param lat [Float] Latitude
|
|
125
|
+
# @param lon [Float] Longitude
|
|
126
|
+
# @raise [InvalidCoordinatesError] if coordinates are invalid
|
|
127
|
+
def self.validate_coordinates(lat, lon)
|
|
128
|
+
lat = lat.to_f
|
|
129
|
+
lon = lon.to_f
|
|
130
|
+
|
|
131
|
+
unless lat.between?(-90, 90)
|
|
132
|
+
raise InvalidCoordinatesError, "Latitude must be between -90 and 90"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
unless lon.between?(-180, 180)
|
|
136
|
+
raise InvalidCoordinatesError, "Longitude must be between -180 and 180"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# NWS API only covers US territories
|
|
140
|
+
unless lat.between?(18, 72) && lon.between?(-180, -65)
|
|
141
|
+
raise InvalidCoordinatesError, "Coordinates must be within US coverage area"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Parse API response data into instance variables
|
|
146
|
+
#
|
|
147
|
+
# @param data [Hash] Parsed JSON response
|
|
148
|
+
# @return [void]
|
|
149
|
+
def parse_data(data)
|
|
150
|
+
props = data["properties"] || {}
|
|
151
|
+
|
|
152
|
+
@latitude = props.dig("relativeLocation", "geometry", "coordinates", 1)
|
|
153
|
+
@longitude = props.dig("relativeLocation", "geometry", "coordinates", 0)
|
|
154
|
+
@grid_id = props["gridId"]
|
|
155
|
+
@grid_x = props["gridX"]
|
|
156
|
+
@grid_y = props["gridY"]
|
|
157
|
+
@forecast_url = props["forecast"]
|
|
158
|
+
@forecast_hourly_url = props["forecastHourly"]
|
|
159
|
+
@forecast_grid_data_url = props["forecastGridData"]
|
|
160
|
+
@observation_stations_url = props["observationStations"]
|
|
161
|
+
@city = props.dig("relativeLocation", "properties", "city")
|
|
162
|
+
@state = props.dig("relativeLocation", "properties", "state")
|
|
163
|
+
@time_zone = props["timeZone"]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
data/lib/nws/station.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NWS
|
|
4
|
+
# Represents a weather observation station
|
|
5
|
+
class Station
|
|
6
|
+
# @return [String] Station identifier (e.g., "KDCA")
|
|
7
|
+
attr_reader :station_id
|
|
8
|
+
|
|
9
|
+
# @return [String] Station name
|
|
10
|
+
attr_reader :name
|
|
11
|
+
|
|
12
|
+
# @return [Float] Latitude of the station
|
|
13
|
+
attr_reader :latitude
|
|
14
|
+
|
|
15
|
+
# @return [Float] Longitude of the station
|
|
16
|
+
attr_reader :longitude
|
|
17
|
+
|
|
18
|
+
# @return [Float] Elevation in meters
|
|
19
|
+
attr_reader :elevation
|
|
20
|
+
|
|
21
|
+
# @return [String] Time zone identifier
|
|
22
|
+
attr_reader :time_zone
|
|
23
|
+
|
|
24
|
+
# @return [Hash] Raw API response data
|
|
25
|
+
attr_reader :raw_data
|
|
26
|
+
|
|
27
|
+
# Initialize a Station from API response data
|
|
28
|
+
#
|
|
29
|
+
# @param data [Hash] Parsed JSON response from the stations API
|
|
30
|
+
# @param client [Client] NWS API client instance
|
|
31
|
+
def initialize(data, client:)
|
|
32
|
+
@client = client
|
|
33
|
+
@raw_data = data
|
|
34
|
+
parse_data(data)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Fetch a station by its identifier
|
|
38
|
+
#
|
|
39
|
+
# @param station_id [String] Station identifier (e.g., "KDCA")
|
|
40
|
+
# @param client [Client] NWS API client instance
|
|
41
|
+
# @return [Station] Station instance
|
|
42
|
+
def self.fetch(station_id, client: NWS.client)
|
|
43
|
+
data = client.get("/stations/#{station_id}")
|
|
44
|
+
new(data, client: client)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Get the latest weather observation from this station
|
|
48
|
+
#
|
|
49
|
+
# @return [Observation] Most recent observation
|
|
50
|
+
def latest_observation
|
|
51
|
+
data = @client.get("/stations/#{@station_id}/observations/latest")
|
|
52
|
+
Observation.new(data, station: self)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get recent weather observations from this station
|
|
56
|
+
#
|
|
57
|
+
# @param limit [Integer] Maximum number of observations to return
|
|
58
|
+
# @return [Array<Observation>] Array of recent observations
|
|
59
|
+
def observations(limit: 10)
|
|
60
|
+
data = @client.get("/stations/#{@station_id}/observations", params: { limit: limit })
|
|
61
|
+
features = data["features"] || []
|
|
62
|
+
features.map { |f| Observation.new(f, station: self) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Parse API response data into instance variables
|
|
68
|
+
#
|
|
69
|
+
# @param data [Hash] Parsed JSON response
|
|
70
|
+
# @return [void]
|
|
71
|
+
def parse_data(data)
|
|
72
|
+
props = data["properties"] || {}
|
|
73
|
+
geometry = data["geometry"] || {}
|
|
74
|
+
coords = geometry["coordinates"] || []
|
|
75
|
+
|
|
76
|
+
@station_id = props["stationIdentifier"]
|
|
77
|
+
@name = props["name"]
|
|
78
|
+
@longitude = coords[0]
|
|
79
|
+
@latitude = coords[1]
|
|
80
|
+
@elevation = props.dig("elevation", "value")
|
|
81
|
+
@time_zone = props["timeZone"]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/nws/version.rb
ADDED
data/lib/nws.rb
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
require_relative "nws/version"
|
|
6
|
+
require_relative "nws/errors"
|
|
7
|
+
require_relative "nws/client"
|
|
8
|
+
require_relative "nws/point"
|
|
9
|
+
require_relative "nws/station"
|
|
10
|
+
require_relative "nws/observation"
|
|
11
|
+
require_relative "nws/forecast"
|
|
12
|
+
require_relative "nws/hourly_forecast"
|
|
13
|
+
require_relative "nws/alert"
|
|
14
|
+
require_relative "nws/geocoder"
|
|
15
|
+
|
|
16
|
+
# Ruby client library for the National Weather Service API
|
|
17
|
+
#
|
|
18
|
+
# Provides access to weather forecasts, current conditions, and alerts
|
|
19
|
+
# for locations within the United States.
|
|
20
|
+
#
|
|
21
|
+
# @example Basic usage
|
|
22
|
+
# # Get forecast for a location
|
|
23
|
+
# forecast = NWS.forecast(39.7456, -97.0892)
|
|
24
|
+
# puts forecast.today
|
|
25
|
+
#
|
|
26
|
+
# # Get current conditions
|
|
27
|
+
# conditions = NWS.current_conditions(39.7456, -97.0892)
|
|
28
|
+
# puts conditions.summary
|
|
29
|
+
#
|
|
30
|
+
# # Get active alerts for a state
|
|
31
|
+
# alerts = NWS.alerts(state: "KS")
|
|
32
|
+
# alerts.each { |alert| puts alert.headline }
|
|
33
|
+
module NWS
|
|
34
|
+
class << self
|
|
35
|
+
# @return [Client, nil] The default client instance
|
|
36
|
+
attr_accessor :default_client
|
|
37
|
+
|
|
38
|
+
# Get the default client, creating one if necessary
|
|
39
|
+
#
|
|
40
|
+
# @return [Client] The default NWS API client
|
|
41
|
+
def client
|
|
42
|
+
@default_client ||= Client.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Configure the NWS module
|
|
46
|
+
#
|
|
47
|
+
# @yield [self] Yields the module for configuration
|
|
48
|
+
# @return [void]
|
|
49
|
+
# @example
|
|
50
|
+
# NWS.configure do |config|
|
|
51
|
+
# config.default_client = NWS::Client.new(user_agent: "MyApp/1.0")
|
|
52
|
+
# end
|
|
53
|
+
def configure
|
|
54
|
+
yield self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Fetch point data for given coordinates
|
|
58
|
+
#
|
|
59
|
+
# @param lat [Float] Latitude
|
|
60
|
+
# @param lon [Float] Longitude
|
|
61
|
+
# @return [Point] Point instance with location data
|
|
62
|
+
# @raise [InvalidCoordinatesError] if coordinates are invalid or outside US coverage
|
|
63
|
+
def point(lat, lon)
|
|
64
|
+
Point.fetch(lat, lon, client: client)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Get the 7-day forecast for a location
|
|
68
|
+
#
|
|
69
|
+
# @param lat [Float] Latitude
|
|
70
|
+
# @param lon [Float] Longitude
|
|
71
|
+
# @return [Forecast] Forecast data for the location
|
|
72
|
+
# @raise [InvalidCoordinatesError] if coordinates are invalid or outside US coverage
|
|
73
|
+
def forecast(lat, lon)
|
|
74
|
+
point(lat, lon).forecast
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get the hourly forecast for a location
|
|
78
|
+
#
|
|
79
|
+
# @param lat [Float] Latitude
|
|
80
|
+
# @param lon [Float] Longitude
|
|
81
|
+
# @return [HourlyForecast] Hourly forecast data for the location
|
|
82
|
+
# @raise [InvalidCoordinatesError] if coordinates are invalid or outside US coverage
|
|
83
|
+
def hourly_forecast(lat, lon)
|
|
84
|
+
point(lat, lon).hourly_forecast
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Get current weather conditions for a location
|
|
88
|
+
#
|
|
89
|
+
# @param lat [Float] Latitude
|
|
90
|
+
# @param lon [Float] Longitude
|
|
91
|
+
# @return [Observation] Current weather observation
|
|
92
|
+
# @raise [InvalidCoordinatesError] if coordinates are invalid or outside US coverage
|
|
93
|
+
# @raise [NotFoundError] if no observation stations found nearby
|
|
94
|
+
def current_conditions(lat, lon)
|
|
95
|
+
point(lat, lon).current_conditions
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Fetch weather alerts
|
|
99
|
+
#
|
|
100
|
+
# @param state [String, nil] Two-letter state code to filter alerts
|
|
101
|
+
# @param point [Array<Float>, nil] Latitude and longitude pair [lat, lon]
|
|
102
|
+
# @param zone [String, nil] NWS zone identifier
|
|
103
|
+
# @param active [Boolean] Whether to fetch only active alerts
|
|
104
|
+
# @return [Array<Alert>] Array of Alert objects
|
|
105
|
+
def alerts(state: nil, point: nil, zone: nil, active: true)
|
|
106
|
+
Alert.fetch(state: state, point: point, zone: zone, active: active, client: client)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: nws
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jeff McFadden
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: minitest
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '5.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '5.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: webmock
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
description: A Ruby gem to access the publicly available National Weather Service
|
|
55
|
+
(USA) APIs for returning condition and forecast data.
|
|
56
|
+
email:
|
|
57
|
+
- ''
|
|
58
|
+
executables:
|
|
59
|
+
- nws
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- LICENSE
|
|
64
|
+
- README.md
|
|
65
|
+
- bin/nws
|
|
66
|
+
- lib/nws.rb
|
|
67
|
+
- lib/nws/alert.rb
|
|
68
|
+
- lib/nws/client.rb
|
|
69
|
+
- lib/nws/errors.rb
|
|
70
|
+
- lib/nws/forecast.rb
|
|
71
|
+
- lib/nws/geocoder.rb
|
|
72
|
+
- lib/nws/hourly_forecast.rb
|
|
73
|
+
- lib/nws/observation.rb
|
|
74
|
+
- lib/nws/point.rb
|
|
75
|
+
- lib/nws/station.rb
|
|
76
|
+
- lib/nws/version.rb
|
|
77
|
+
homepage: https://github.com/jeffmcfadden/nws
|
|
78
|
+
licenses:
|
|
79
|
+
- Unlicense
|
|
80
|
+
metadata: {}
|
|
81
|
+
rdoc_options: []
|
|
82
|
+
require_paths:
|
|
83
|
+
- lib
|
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: 2.7.0
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
requirements: []
|
|
95
|
+
rubygems_version: 3.7.2
|
|
96
|
+
specification_version: 4
|
|
97
|
+
summary: Ruby client for the National Weather Service API
|
|
98
|
+
test_files: []
|