gull 0.3.4 → 1.0.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 +5 -5
- data/.github/workflows/ci.yml +23 -0
- data/.gitignore +5 -21
- data/.rubocop.yml +34 -1
- data/CHANGELOG.md +45 -26
- data/Gemfile +2 -5
- data/Guardfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +10 -41
- data/Rakefile +2 -0
- data/gull.gemspec +19 -9
- data/lib/gull/alert.rb +55 -46
- data/lib/gull/client.rb +44 -36
- data/lib/gull/error.rb +2 -0
- data/lib/gull/geocode.rb +2 -0
- data/lib/gull/polygon.rb +8 -57
- data/lib/gull/version.rb +3 -1
- data/lib/gull.rb +10 -3
- data/spec/alert_spec.rb +129 -83
- data/spec/client_spec.rb +34 -63
- data/spec/error_spec.rb +4 -2
- data/spec/fixtures/alerts.json +4881 -0
- data/spec/fixtures/empty.json +4 -0
- data/spec/fixtures/features/blizzard_warning.json +93 -0
- data/spec/fixtures/features/empty_geocode.json +34 -0
- data/spec/fixtures/features/flood_advisory.json +156 -0
- data/spec/fixtures/features/flood_warning.json +108 -0
- data/spec/fixtures/features/multipolygon.json +69 -0
- data/spec/fixtures/features/null_geometry.json +145 -0
- data/spec/fixtures/features/polygon_no_vtec.json +165 -0
- data/spec/fixtures/features/polygon_with_vtec.json +128 -0
- data/spec/fixtures/missing_event.json +21 -0
- data/spec/polygon_spec.rb +24 -42
- data/spec/spec_helper.rb +5 -88
- metadata +67 -40
- data/.hound.yml +0 -3
- data/.ruby-version +0 -1
- data/.travis.yml +0 -8
- data/spec/fixtures/alerts.xml +0 -118
- data/spec/fixtures/bad.xml +0 -1
- data/spec/fixtures/empty.xml +0 -30
- data/spec/fixtures/missing_cap.xml +0 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: eb20ff9550e66bb6897227d930069551cc9882c1eef9f285d7a1ba74e0b2133e
|
|
4
|
+
data.tar.gz: b50b4e87e416dce952046c357e2c06d1d826b84e451141e237570418569d4b1d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b06376a56d00f2fa3ee684f4897b02439d24cd910722779a1f696e4d2a596550fa82cd7efb2e83c94934550fdaf406a266cddcab78a97b41d9fbdf6a13f65373
|
|
7
|
+
data.tar.gz: ca1f9f07f27e928571b1d0ed54bbe2275bccd8389995073f0892b4c5e469d176ea5084fa09bf0bdbca923b225105627da4f45e986fd5cd06e245a82a4515a577
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [master]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [master]
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
ruby-version: ['3.1', '3.2', '3.3', '3.4']
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
16
|
+
uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
- name: Run tests
|
|
21
|
+
run: bundle exec rspec
|
|
22
|
+
- name: Run linter
|
|
23
|
+
run: bundle exec rubocop
|
data/.gitignore
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
.yardoc
|
|
1
|
+
/.bundle
|
|
2
|
+
/coverage
|
|
3
|
+
/pkg
|
|
4
|
+
/tmp
|
|
6
5
|
Gemfile.lock
|
|
7
|
-
|
|
8
|
-
_yardoc
|
|
9
|
-
coverage
|
|
10
|
-
doc/
|
|
11
|
-
lib/bundler/man
|
|
12
|
-
pkg
|
|
13
|
-
rdoc
|
|
14
|
-
spec/reports
|
|
15
|
-
test/tmp
|
|
16
|
-
test/version_tmp
|
|
17
|
-
tmp
|
|
18
|
-
*.bundle
|
|
19
|
-
*.so
|
|
20
|
-
*.o
|
|
21
|
-
*.a
|
|
22
|
-
mkmf.log
|
|
6
|
+
.ruby-version
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.1
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
2
5
|
Exclude:
|
|
3
6
|
- 'spec/spec_helper.rb'
|
|
7
|
+
- 'gull.gemspec'
|
|
8
|
+
- 'vendor/**/*'
|
|
9
|
+
Metrics/BlockLength:
|
|
10
|
+
Exclude:
|
|
11
|
+
- 'spec/**/*'
|
|
12
|
+
Metrics/MethodLength:
|
|
13
|
+
Max: 20
|
|
14
|
+
Exclude:
|
|
15
|
+
- 'spec/**/*'
|
|
16
|
+
Metrics/AbcSize:
|
|
17
|
+
Max: 35
|
|
18
|
+
Exclude:
|
|
19
|
+
- 'spec/**/*'
|
|
20
|
+
Metrics/ClassLength:
|
|
21
|
+
Enabled: false
|
|
22
|
+
Metrics/CyclomaticComplexity:
|
|
23
|
+
Enabled: false
|
|
24
|
+
Metrics/PerceivedComplexity:
|
|
25
|
+
Enabled: false
|
|
26
|
+
Layout/LineLength:
|
|
27
|
+
Max: 80
|
|
28
|
+
Exclude:
|
|
29
|
+
- 'spec/**/*'
|
|
4
30
|
Style/StringLiterals:
|
|
5
|
-
EnforcedStyle: single_quotes
|
|
31
|
+
EnforcedStyle: single_quotes
|
|
32
|
+
Style/StringConcatenation:
|
|
33
|
+
Exclude:
|
|
34
|
+
- 'spec/**/*'
|
|
35
|
+
Style/Documentation:
|
|
36
|
+
Enabled: false
|
|
37
|
+
Naming/PredicateMethod:
|
|
38
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,38 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
Fixed parser to handle empty geocode.
|
|
1
|
+
1.0.0 (03/07/2026) - Major rewrite: migrate from defunct `alerts.weather.gov` XML feed to `api.weather.gov/alerts` JSON API. Drop all runtime dependencies (`httpclient`, `nokogiri`) in favor of Ruby stdlib (`net/http`, `json`). Require Ruby >= 3.1. Add `area` option for state-based filtering. Replace Travis CI with GitHub Actions. Add RuboCop. **Breaking:** remove `url` option (use `area` instead), remove `Polygon#image_url`, remove `strict` option.
|
|
3
2
|
|
|
4
|
-
#### 0.3.3 (11/25/2015)
|
|
5
|
-
Handle additional service failure scenarios.
|
|
6
3
|
***
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
|
|
5
|
+
0.4.0 (07/26/2016) - Merged pull request #2 from [schrockwell](https://github.com/schrockwell), which adds `to_wkt` to `Polygon` which formats the polygon points as Well-Known Text (WKT). Removed `centroid` method, use new `to_wkt` method with an external geospatial library instead.
|
|
6
|
+
|
|
7
|
+
***
|
|
8
|
+
|
|
9
|
+
0.3.4 (12/16/2015) - Fixed parser to handle empty geocode.
|
|
10
|
+
|
|
9
11
|
***
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
|
|
13
|
+
0.3.3 (11/25/2015) - Handle additional service failure scenarios.
|
|
14
|
+
|
|
12
15
|
***
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
0.3.2 (11/10/2015) - Added BadResponseError to client error handling.
|
|
18
|
+
|
|
19
|
+
***
|
|
20
|
+
|
|
21
|
+
0.3.1 (6/25/2015) - Added nested error handling for other HTTPClient errors. HttpError has original attribute exposing the root error.
|
|
22
|
+
|
|
23
|
+
***
|
|
24
|
+
|
|
25
|
+
0.3.0 (6/23/2015) - Added Client class for error handling (bad entries accessed via errors array). Exposed option to turn on strict XML parsing for debugging. Alert.fetch uses new Client but is backwards compatible, however it doesn't give you access to errors array.
|
|
26
|
+
|
|
15
27
|
***
|
|
16
|
-
|
|
17
|
-
Handle missing cap section
|
|
28
|
+
|
|
29
|
+
0.2.13 (6/20/2015) - Handle missing cap section
|
|
30
|
+
|
|
18
31
|
***
|
|
19
|
-
|
|
20
|
-
Fix issue with missing link elements
|
|
32
|
+
|
|
33
|
+
0.2.12 (6/12/2015) - Fix issue with missing link elements
|
|
34
|
+
|
|
21
35
|
***
|
|
22
|
-
|
|
23
|
-
Added method to get original polygon string
|
|
36
|
+
|
|
37
|
+
0.2.11 (5/29/2015) -Added method to get original polygon string
|
|
38
|
+
|
|
24
39
|
***
|
|
25
|
-
|
|
26
|
-
Added option to override default alert service URI.
|
|
40
|
+
|
|
41
|
+
0.2.10 (3/23/2015) - Added option to override default alert service URI.
|
|
42
|
+
|
|
27
43
|
***
|
|
28
|
-
|
|
29
|
-
Added static map image to polygons.
|
|
44
|
+
|
|
45
|
+
0.2.1 (10/02/2014) - Added static map image to polygons.
|
|
46
|
+
|
|
30
47
|
***
|
|
31
|
-
|
|
32
|
-
Introduced Polygon type.
|
|
48
|
+
|
|
49
|
+
0.2.0 (10/02/2014) - Introduced Polygon type.
|
|
50
|
+
|
|
33
51
|
***
|
|
34
|
-
|
|
35
|
-
Refactored and simplified alert processing.
|
|
52
|
+
|
|
53
|
+
0.1.1 (10/01/2014) - Refactored and simplified alert processing.
|
|
54
|
+
|
|
36
55
|
***
|
|
37
|
-
|
|
38
|
-
Initial release, basic functionality of fetching and parsing alerts.
|
|
56
|
+
|
|
57
|
+
0.1.0 (10/01/2014) - Initial release, basic functionality of fetching and parsing alerts.
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
[](http://badge.fury.io/rb/gull)
|
|
2
|
-
[](https://coveralls.io/r/sethdeckard/gull)
|
|
4
|
-
[](https://codeclimate.com/github/sethdeckard/gull)
|
|
5
|
-
[](https://gemnasium.com/sethdeckard/gull)
|
|
6
|
-
[](https://hakiri.io/github/sethdeckard/gull/master)
|
|
2
|
+
[](https://github.com/sethdeckard/gull/actions/workflows/ci.yml)
|
|
7
3
|
# Gull
|
|
8
4
|
|
|
9
|
-
Ruby client for parsing NOAA/NWS alerts, warnings, and watches. The name comes from the type of bird featured on the NOAA logo.
|
|
5
|
+
Ruby client for parsing NOAA/NWS alerts, warnings, and watches. The name comes from the type of bird featured on the NOAA logo. Zero runtime dependencies -- uses only Ruby stdlib (`net/http`, `json`).
|
|
10
6
|
|
|
11
7
|
## Installation
|
|
12
8
|
|
|
@@ -49,49 +45,22 @@ alert.certainty
|
|
|
49
45
|
alert.vtec
|
|
50
46
|
```
|
|
51
47
|
|
|
52
|
-
To get alerts for a single state
|
|
48
|
+
To get alerts for a single state or territory, pass the area option:
|
|
53
49
|
|
|
54
50
|
```ruby
|
|
55
|
-
|
|
56
|
-
alerts = Gull::Alert.fetch(url: oklahoma_url)
|
|
51
|
+
alerts = Gull::Alert.fetch(area: 'OK')
|
|
57
52
|
```
|
|
58
53
|
|
|
59
|
-
You can also generate a map (a really long URL pointing to a map) of the polygon if alert has one (requires Google Static Maps API Key)
|
|
60
|
-
|
|
61
|
-
```ruby
|
|
62
|
-
alert.polygon.image_url 'YOUR_GOOGLE_API_KEY'
|
|
63
|
-
|
|
64
|
-
=> "http://maps.googleapis.com/maps/api/staticmap?size=640x640&maptype=roadmap&path=color:0xff0000|weight:3|fillcolor:0xff000060|38.73,-94.22|38.75,-94.16|38.57,-93.94|38.4,-93.84|38.4,-93.91|38.73,-94.22&key=YOUR_GOOGLE_API_KEY"
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Options can be passed for map to override defaults
|
|
68
|
-
|
|
69
|
-
```ruby
|
|
70
|
-
options = { width: 600, height: 300, color: '0xfbf000', weight: 4,
|
|
71
|
-
fillcolor: '0xfbf00070', maptype: 'hybrid' }
|
|
72
|
-
alert.polygon.image_url 'YOUR_GOOGLE_API_KEY', options
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Get the centroid of the polygon (to display a map pin, etc.)
|
|
76
|
-
|
|
77
|
-
```ruby
|
|
78
|
-
alert.polygon.centroid
|
|
79
|
-
|
|
80
|
-
=> [34.835, -91.205]
|
|
81
|
-
```
|
|
82
|
-
|
|
83
54
|
##Notes, Caveats
|
|
84
|
-
This library
|
|
85
|
-
|
|
86
|
-
The NWS will often cancel or update alerts before their expiration time. The public alert feed only provides current active alerts and does not include these separate update and cancellation CAP messages. If you're persisting these alerts you need to design for this scenario.
|
|
55
|
+
This library fetches active alerts from the [NWS API](https://api.weather.gov) (`api.weather.gov/alerts/active`), which returns GeoJSON. No authentication is required but the API does require a `User-Agent` header (set automatically by the gem).
|
|
87
56
|
|
|
88
|
-
The
|
|
57
|
+
The NWS will often cancel or update alerts before their expiration time. The API only returns currently active alerts.
|
|
89
58
|
|
|
90
59
|
### Urgency
|
|
91
60
|
|
|
92
|
-
| Symbol | Definition
|
|
61
|
+
| Symbol | Definition
|
|
93
62
|
| :------------- |:-------------
|
|
94
|
-
| :immediate | Responsive action should
|
|
63
|
+
| :immediate | Responsive action should be taken immediately
|
|
95
64
|
| :expected | Responsive action should be taken soon (within next hour)
|
|
96
65
|
| :future | Responsive action should be taken in the near future
|
|
97
66
|
| :past | Responsive action is no longer required
|
|
@@ -99,7 +68,7 @@ The public RSS feeds are not always reliable, if you are using this for critical
|
|
|
99
68
|
|
|
100
69
|
### Severity
|
|
101
70
|
|
|
102
|
-
| Symbol | Definition
|
|
71
|
+
| Symbol | Definition
|
|
103
72
|
| :------------- |:-------------
|
|
104
73
|
| :extreme | Extraordinary threat to life or property
|
|
105
74
|
| :severe | Significant threat to life or property
|
|
@@ -109,7 +78,7 @@ The public RSS feeds are not always reliable, if you are using this for critical
|
|
|
109
78
|
|
|
110
79
|
### Certainty
|
|
111
80
|
|
|
112
|
-
| Symbol | Definition
|
|
81
|
+
| Symbol | Definition
|
|
113
82
|
| :------------- |:-------------
|
|
114
83
|
| :very_likely | Highly likely (p > ~ 85%) or certain
|
|
115
84
|
| :likely | Likely (p > ~50%)
|
data/Rakefile
CHANGED
data/gull.gemspec
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require 'gull/version'
|
|
5
6
|
|
|
@@ -9,20 +10,29 @@ Gem::Specification.new do |spec|
|
|
|
9
10
|
spec.authors = ['Seth Deckard']
|
|
10
11
|
spec.email = ['seth@deckard.me']
|
|
11
12
|
spec.summary = 'Client for parsing NOAA/NWS alerts, warnings, and watches.'
|
|
12
|
-
spec.description = '
|
|
13
|
+
spec.description = 'Fetches and parses NOAA/NWS alerts, warnings, and watches from api.weather.gov. Zero runtime dependencies.'
|
|
13
14
|
spec.homepage = 'https://github.com/sethdeckard/gull'
|
|
14
15
|
spec.license = 'MIT'
|
|
16
|
+
spec.required_ruby_version = '>= 3.1'
|
|
17
|
+
|
|
18
|
+
spec.metadata = {
|
|
19
|
+
'source_code_uri' => 'https://github.com/sethdeckard/gull',
|
|
20
|
+
'changelog_uri' => 'https://github.com/sethdeckard/gull/blob/master/CHANGELOG.md',
|
|
21
|
+
'bug_tracker_uri' => 'https://github.com/sethdeckard/gull/issues'
|
|
22
|
+
}
|
|
15
23
|
|
|
16
|
-
spec.files
|
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
+
f.match(%r{^(AGENTS|CLAUDE)\.md$})
|
|
26
|
+
end
|
|
17
27
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
28
|
spec.require_paths = ['lib']
|
|
20
29
|
|
|
21
|
-
spec.
|
|
22
|
-
spec.
|
|
23
|
-
|
|
24
|
-
spec.add_development_dependency 'bundler', '>= 1.6'
|
|
30
|
+
spec.add_development_dependency 'bundler'
|
|
31
|
+
spec.add_development_dependency 'guard-rspec'
|
|
25
32
|
spec.add_development_dependency 'rake'
|
|
26
33
|
spec.add_development_dependency 'rspec', '>= 3.0'
|
|
34
|
+
spec.add_development_dependency 'rubocop'
|
|
35
|
+
spec.add_development_dependency 'rubocop-rake'
|
|
36
|
+
spec.add_development_dependency 'simplecov'
|
|
27
37
|
spec.add_development_dependency 'webmock'
|
|
28
38
|
end
|
data/lib/gull/alert.rb
CHANGED
|
@@ -1,80 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
require 'nokogiri'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module Gull
|
|
5
|
-
# Gull represents an NWS/NOAA alert and provides the ability to fetch
|
|
6
|
-
# them from the public web service
|
|
7
4
|
class Alert
|
|
8
|
-
attr_accessor :id, :title, :summary, :link, :alert_type, :polygon,
|
|
9
|
-
:effective_at, :expires_at, :updated_at,
|
|
10
|
-
:urgency, :severity, :certainty,
|
|
5
|
+
attr_accessor :id, :title, :summary, :link, :alert_type, :polygon,
|
|
6
|
+
:area, :effective_at, :expires_at, :updated_at,
|
|
7
|
+
:published_at, :urgency, :severity, :certainty,
|
|
8
|
+
:geocode, :vtec
|
|
11
9
|
|
|
12
10
|
def initialize
|
|
13
11
|
self.geocode = Geocode.new
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
def self.fetch(options = {})
|
|
17
|
-
client = Client.new
|
|
15
|
+
client = Client.new(options)
|
|
18
16
|
client.fetch
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
def parse(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
parse_polygon
|
|
27
|
-
parse_geocode
|
|
28
|
-
parse_vtec
|
|
19
|
+
def parse(feature)
|
|
20
|
+
props = feature['properties']
|
|
21
|
+
parse_core_attributes(feature, props)
|
|
22
|
+
parse_times(props)
|
|
23
|
+
parse_categories(props)
|
|
24
|
+
parse_polygon(feature)
|
|
25
|
+
parse_geocode(props)
|
|
26
|
+
parse_vtec(props)
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
private
|
|
32
30
|
|
|
33
|
-
def parse_core_attributes(
|
|
34
|
-
self.id =
|
|
35
|
-
self.title =
|
|
36
|
-
self.summary =
|
|
37
|
-
self.link =
|
|
38
|
-
self.alert_type =
|
|
39
|
-
self.area =
|
|
31
|
+
def parse_core_attributes(feature, props)
|
|
32
|
+
self.id = props['id']
|
|
33
|
+
self.title = props['headline']
|
|
34
|
+
self.summary = props['description']
|
|
35
|
+
self.link = props['@id'] || feature['id']
|
|
36
|
+
self.alert_type = props['event']
|
|
37
|
+
self.area = props['areaDesc']
|
|
40
38
|
end
|
|
41
39
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
def parse_times(props)
|
|
41
|
+
self.effective_at = Time.parse(props['effective'])
|
|
42
|
+
self.expires_at = Time.parse(props['expires'])
|
|
43
|
+
self.published_at = Time.parse(props['sent'])
|
|
44
|
+
self.updated_at = Time.parse(props['onset'] || props['sent'])
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
def
|
|
48
|
-
self.
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
51
|
-
self.expires_at = Time.parse(element.xpath('cap:expires').inner_text)
|
|
47
|
+
def parse_categories(props)
|
|
48
|
+
self.urgency = code_to_symbol(props['urgency'])
|
|
49
|
+
self.severity = code_to_symbol(props['severity'])
|
|
50
|
+
self.certainty = code_to_symbol(props['certainty'])
|
|
52
51
|
end
|
|
53
52
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
def parse_polygon(feature)
|
|
54
|
+
geometry = feature['geometry']
|
|
55
|
+
return if geometry.nil?
|
|
56
|
+
|
|
57
|
+
coords = geometry['coordinates']
|
|
58
|
+
return if coords.nil? || coords.empty?
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
self.polygon = Polygon.new
|
|
60
|
+
ring = coords.first
|
|
61
|
+
ring = ring.first if geometry['type'] == 'MultiPolygon'
|
|
62
|
+
self.polygon = Polygon.new(ring)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def parse_geocode(
|
|
66
|
-
|
|
65
|
+
def parse_geocode(props)
|
|
66
|
+
geocode_data = props['geocode']
|
|
67
|
+
return if geocode_data.nil?
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
geocode.ugc =
|
|
69
|
+
ugc = geocode_data['UGC']
|
|
70
|
+
geocode.ugc = ugc&.join(' ')
|
|
71
|
+
|
|
72
|
+
fips = geocode_data['SAME']
|
|
73
|
+
geocode.fips6 = fips&.join(' ')
|
|
70
74
|
end
|
|
71
75
|
|
|
72
|
-
def parse_vtec(
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
def parse_vtec(props)
|
|
77
|
+
params = props['parameters']
|
|
78
|
+
return if params.nil?
|
|
79
|
+
|
|
80
|
+
vtec_values = params['VTEC']
|
|
81
|
+
self.vtec = vtec_values&.first
|
|
75
82
|
end
|
|
76
83
|
|
|
77
84
|
def code_to_symbol(code)
|
|
85
|
+
return :unknown if code.nil?
|
|
86
|
+
|
|
78
87
|
code.tr(' ', '_').downcase.to_sym
|
|
79
88
|
end
|
|
80
89
|
end
|
data/lib/gull/client.rb
CHANGED
|
@@ -1,65 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
require 'nokogiri'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module Gull
|
|
5
|
-
# Client exposes methods and options for fetching alerts from the NWS/NOAA
|
|
6
|
-
# web service
|
|
7
4
|
class Client
|
|
5
|
+
URL = 'https://api.weather.gov/alerts/active'
|
|
6
|
+
USER_AGENT = "gull/#{VERSION} (Ruby #{RUBY_VERSION})".freeze
|
|
7
|
+
|
|
8
8
|
attr_accessor :errors
|
|
9
9
|
|
|
10
10
|
def initialize(options = {})
|
|
11
|
-
@options =
|
|
12
|
-
url: 'http://alerts.weather.gov/cap/us.php?x=1',
|
|
13
|
-
strict: false
|
|
14
|
-
}.merge options
|
|
11
|
+
@options = options
|
|
15
12
|
end
|
|
16
13
|
|
|
17
14
|
def fetch
|
|
18
15
|
self.errors = []
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
end
|
|
23
|
-
process document.xpath('//xmlns:feed/xmlns:entry', namespaces)
|
|
16
|
+
json = response
|
|
17
|
+
data = JSON.parse(json)
|
|
18
|
+
process(data['features'] || [])
|
|
24
19
|
end
|
|
25
20
|
|
|
26
21
|
private
|
|
27
22
|
|
|
28
23
|
def response
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
uri = build_uri
|
|
25
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
26
|
+
http.use_ssl = uri.scheme == 'https'
|
|
27
|
+
request = Net::HTTP::Get.new(uri)
|
|
28
|
+
request['User-Agent'] = USER_AGENT
|
|
29
|
+
request['Accept'] = 'application/geo+json'
|
|
30
|
+
result = http.request(request)
|
|
31
|
+
unless result.is_a?(Net::HTTPSuccess)
|
|
32
|
+
raise HttpError, "NWS API returned #{result.code}"
|
|
37
33
|
end
|
|
34
|
+
|
|
35
|
+
result.body
|
|
36
|
+
rescue Net::OpenTimeout, Net::ReadTimeout
|
|
37
|
+
raise TimeoutError,
|
|
38
|
+
'Timeout while connecting to NWS web service'
|
|
39
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET
|
|
40
|
+
raise HttpError,
|
|
41
|
+
'Could not connect to NWS web service'
|
|
38
42
|
end
|
|
39
43
|
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
def build_uri
|
|
45
|
+
uri = URI(URL)
|
|
46
|
+
if @options[:area]
|
|
47
|
+
params = URI.decode_www_form(uri.query || '')
|
|
48
|
+
params << ['area', @options[:area]]
|
|
49
|
+
uri.query = URI.encode_www_form(params)
|
|
46
50
|
end
|
|
51
|
+
uri
|
|
52
|
+
end
|
|
47
53
|
|
|
54
|
+
def process(features)
|
|
55
|
+
alerts = []
|
|
56
|
+
features.each do |feature|
|
|
57
|
+
alert = create_instance(feature)
|
|
58
|
+
alerts.push(alert) unless alert.nil?
|
|
59
|
+
errors.push(feature) if alert.nil?
|
|
60
|
+
end
|
|
48
61
|
alerts
|
|
49
62
|
end
|
|
50
63
|
|
|
51
|
-
def create_instance(
|
|
52
|
-
|
|
64
|
+
def create_instance(feature)
|
|
65
|
+
properties = feature['properties']
|
|
66
|
+
return if properties.nil? || properties['event'].nil?
|
|
53
67
|
|
|
54
68
|
alert = Alert.new
|
|
55
|
-
alert.parse
|
|
69
|
+
alert.parse(feature)
|
|
56
70
|
alert
|
|
57
71
|
end
|
|
58
|
-
|
|
59
|
-
def namespaces
|
|
60
|
-
{ 'xmlns' => 'http://www.w3.org/2005/Atom',
|
|
61
|
-
'cap' => 'urn:oasis:names:tc:emergency:cap:1.1',
|
|
62
|
-
'ha' => 'http://www.alerting.net/namespace/index_1.0' }
|
|
63
|
-
end
|
|
64
72
|
end
|
|
65
73
|
end
|
data/lib/gull/error.rb
CHANGED