gull 0.4.0 → 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 +5 -1
- data/Gemfile +2 -0
- data/Guardfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +10 -33
- data/Rakefile +2 -0
- data/gull.gemspec +19 -11
- 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 +4 -27
- 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 +22 -34
- data/spec/spec_helper.rb +5 -88
- metadata +41 -42
- data/.hound.yml +0 -3
- data/.ruby-version +0 -1
- data/.travis.yml +0 -7
- 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,3 +1,7 @@
|
|
|
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.
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
1
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.
|
|
2
6
|
|
|
3
7
|
***
|
|
@@ -43,7 +47,7 @@
|
|
|
43
47
|
***
|
|
44
48
|
|
|
45
49
|
0.2.0 (10/02/2014) - Introduced Polygon type.
|
|
46
|
-
|
|
50
|
+
|
|
47
51
|
***
|
|
48
52
|
|
|
49
53
|
0.1.1 (10/01/2014) - Refactored and simplified alert processing.
|
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,41 +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)
|
|
57
|
-
```
|
|
58
|
-
|
|
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
|
|
51
|
+
alerts = Gull::Alert.fetch(area: 'OK')
|
|
73
52
|
```
|
|
74
53
|
|
|
75
54
|
##Notes, Caveats
|
|
76
|
-
This library
|
|
77
|
-
|
|
78
|
-
The NWS will often cancel or update alerts before their expiration time. The public Atom feeds only provide current active alerts and do not include these separate update and cancellation CAP messages.
|
|
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).
|
|
79
56
|
|
|
80
|
-
The
|
|
57
|
+
The NWS will often cancel or update alerts before their expiration time. The API only returns currently active alerts.
|
|
81
58
|
|
|
82
59
|
### Urgency
|
|
83
60
|
|
|
84
|
-
| Symbol | Definition
|
|
61
|
+
| Symbol | Definition
|
|
85
62
|
| :------------- |:-------------
|
|
86
|
-
| :immediate | Responsive action should
|
|
63
|
+
| :immediate | Responsive action should be taken immediately
|
|
87
64
|
| :expected | Responsive action should be taken soon (within next hour)
|
|
88
65
|
| :future | Responsive action should be taken in the near future
|
|
89
66
|
| :past | Responsive action is no longer required
|
|
@@ -91,7 +68,7 @@ The public Atom feeds have not always been reliable in terms of uptime and are o
|
|
|
91
68
|
|
|
92
69
|
### Severity
|
|
93
70
|
|
|
94
|
-
| Symbol | Definition
|
|
71
|
+
| Symbol | Definition
|
|
95
72
|
| :------------- |:-------------
|
|
96
73
|
| :extreme | Extraordinary threat to life or property
|
|
97
74
|
| :severe | Significant threat to life or property
|
|
@@ -101,7 +78,7 @@ The public Atom feeds have not always been reliable in terms of uptime and are o
|
|
|
101
78
|
|
|
102
79
|
### Certainty
|
|
103
80
|
|
|
104
|
-
| Symbol | Definition
|
|
81
|
+
| Symbol | Definition
|
|
105
82
|
| :------------- |:-------------
|
|
106
83
|
| :very_likely | Highly likely (p > ~ 85%) or certain
|
|
107
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,22 +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.add_runtime_dependency 'httpclient'
|
|
22
|
-
spec.add_runtime_dependency 'nokogiri', '>= 1.6.8'
|
|
23
|
-
|
|
24
30
|
spec.add_development_dependency 'bundler'
|
|
31
|
+
spec.add_development_dependency 'guard-rspec'
|
|
25
32
|
spec.add_development_dependency 'rake'
|
|
26
|
-
spec.add_development_dependency 'rspec', '>=3.0'
|
|
27
|
-
spec.add_development_dependency '
|
|
28
|
-
spec.add_development_dependency '
|
|
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'
|
|
29
37
|
spec.add_development_dependency 'webmock'
|
|
30
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
data/lib/gull/geocode.rb
CHANGED
data/lib/gull/polygon.rb
CHANGED
|
@@ -1,44 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Gull
|
|
2
4
|
class Polygon
|
|
3
5
|
attr_accessor :coordinates
|
|
4
6
|
|
|
5
|
-
def initialize(
|
|
6
|
-
self.coordinates =
|
|
7
|
-
.map { |point| point.split(',').map(&:to_f) }
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def image_url(api_key, options = {})
|
|
11
|
-
options = {
|
|
12
|
-
width: 640,
|
|
13
|
-
height: 640,
|
|
14
|
-
color: '0xff0000',
|
|
15
|
-
weight: 3,
|
|
16
|
-
fillcolor: '0xff000060',
|
|
17
|
-
maptype: 'roadmap'
|
|
18
|
-
}.merge(options)
|
|
19
|
-
|
|
20
|
-
url_base = 'http://maps.googleapis.com/maps/api/staticmap'
|
|
21
|
-
"#{url_base}?size=#{options[:width]}x#{options[:height]}" \
|
|
22
|
-
"&maptype=#{options[:maptype]}&path=color:#{options[:color]}" \
|
|
23
|
-
"|weight:#{options[:weight]}|fillcolor:#{options[:fillcolor]}" \
|
|
24
|
-
"|#{coordinates_piped}&key=#{api_key}"
|
|
7
|
+
def initialize(coords)
|
|
8
|
+
self.coordinates = coords.map { |point| [point[1], point[0]] }
|
|
25
9
|
end
|
|
26
10
|
|
|
27
11
|
def to_s
|
|
28
12
|
coordinates.map { |pair| pair.join(',') }.join(' ')
|
|
29
13
|
end
|
|
30
14
|
|
|
31
|
-
# Returns well-known text (WKT) formatted polygon
|
|
32
15
|
def to_wkt
|
|
33
16
|
pairs = coordinates.map { |pair| "#{pair.last} #{pair.first}" }
|
|
34
17
|
.join(', ')
|
|
35
18
|
"POLYGON((#{pairs}))"
|
|
36
19
|
end
|
|
37
|
-
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
def coordinates_piped
|
|
41
|
-
coordinates.map { |pair| pair.join ',' }.join '|'
|
|
42
|
-
end
|
|
43
20
|
end
|
|
44
21
|
end
|
data/lib/gull/version.rb
CHANGED
data/lib/gull.rb
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'time'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
1
8
|
require 'gull/version'
|
|
2
9
|
require 'gull/error'
|
|
3
|
-
require 'gull/client'
|
|
4
|
-
require 'gull/alert'
|
|
5
|
-
require 'gull/polygon'
|
|
6
10
|
require 'gull/geocode'
|
|
11
|
+
require 'gull/polygon'
|
|
12
|
+
require 'gull/alert'
|
|
13
|
+
require 'gull/client'
|
|
7
14
|
|
|
8
15
|
module Gull
|
|
9
16
|
end
|