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.
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
@@ -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
@@ -0,0 +1,3 @@
1
+ module NWS
2
+ VERSION = "0.2.0"
3
+ end
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: []