noaa_ncei_weather 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 53642b4bebf5f150c3022a688787ec632b5f9b1c
4
+ data.tar.gz: 6f04afc3ab6c0a17fcb8b75db46985ff16d93000
5
+ SHA512:
6
+ metadata.gz: a58cd2a4d133bbfd8fb9079c808a52a53ce8fe7c77af8392244600a4ce993c0d5abf4d9852bb50e515a9f9e3d920a9815e742c2163624781361cfba36c99d2ff
7
+ data.tar.gz: fc8a428f3fc37523eb2d8ed2b9929f86c9bd0a332c23899e9888d6ebd1eea0fc6cc10a6e710aea6a69cc95fcb1aaeb915b9b73acf0c14300ffef5ff0e1729761
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ env.rb
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.0.0"
4
+ - "2.1" # latest 2.1.x
5
+ - "2.2" # latest 2.2.x
6
+ - "jruby-9.0.5.0"
7
+ script: rake test
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in noaa_ncei_weather.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Jason B Thelen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ [![Build Status](https://travis-ci.org/JasonBThelen/noaa_ncei_weather.svg?branch=master)](https://travis-ci.org/JasonBThelen/noaa_ncei_weather)
2
+
3
+ # NOAA NCEI Weather
4
+
5
+ Ruby wrapper to the NOAA NCEI Weather API at [ncdc.noaa.gov](http://www.ncdc.noaa.gov/cdo-web/webservices/v2)
6
+
7
+ Uses [Rest-Client](https://github.com/rest-client/rest-client) to query information from the National Oceanic and Atmospheric Administration Climate Data Web Services API. This provides free access to global historical weather information from around the globe. Many types of weather measurements can be pulled in time frames from the first records to near current (a few days ago).
8
+
9
+ Use of the API requires the use of a free token. Register for your token on the [request page](http://www.ncdc.noaa.gov/cdo-web/token). You'll need a token to use this gem.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'noaa_ncei_weather'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install noaa_ncei_weather
26
+
27
+ ## Usage
28
+
29
+ Start by setting the token
30
+
31
+ ```ruby
32
+ NoaaNceiWeather::Connection.token = 'TOKEN'
33
+ ```
34
+
35
+ Use the [NOAA](http://www.ncdc.noaa.gov/cdo-web/webservices/v2#gettingStarted) page as a reference for the data available and the paramaters that can be used. This gem provides a class for the data returned by each of the available endpoints.
36
+
37
+ All of the endpoints (except `Data`) are what I'll call 'organizational' information used to filter down a query to the `Data` class. All of these have class methods for querying against the database.
38
+
39
+ ```ruby
40
+ NoaaNceiWeather::LocationCategory.all # Returns a collection of LocationCategory objects
41
+ NoaaNceiWeather::LocationCategory.where({limit: 42}) # Returns a collection of LocationCategory objects filtered by the parameters given
42
+ NoaaNceiWeather::LocationCategory.first # Returns a single LocationCategory object
43
+ NoaaNceiWeather::LocationCategory.find("CITY") # Returns a single LocationCategory with the given ID
44
+ ```
45
+
46
+ Many of these endpoints also have relationships with one another. For example, each location category has many locations. This is shown as an optional parameter on the NOAA page for the [location endpoint](http://www.ncdc.noaa.gov/cdo-web/webservices/v2#locations). To reflect this relationship, `LocationCategory` has an instance method `.locations` to retrieve the related records. The same is true for the other classes where available via parameters shown on the NOAA documentation
47
+
48
+ ```ruby
49
+ lc = NoaaNceiWeather::LocationCategory.first # returns a single instance of LocationCategory
50
+ lc.locations # returns a collection of Location objects related to lc
51
+ ```
52
+
53
+ Parameters passed to the class methods are parsed before being sent to the NOAA API for convenience.
54
+ 1. Relationship parameters to another endpoint can be passed an object instance. For example:
55
+
56
+ ```ruby
57
+ lc = NoaaNceiWeather::LocationCategory.find('CITY') # an instance of LocationCategory
58
+ NoaaNceiWeather::Location.where(locationcategoryid: lc.id) # Parameter shown on the NOAA API Docs
59
+ NoaaNceiWeather::Location.where(locationcategory: lc) # alternative
60
+ ```
61
+
62
+ 2. Date parameters shown in the NOAA parameters can be given as Ruby `Date`, `DateTime`, or `Time` instances rather than an ISO formatted string
63
+
64
+ Limit is a parameter available to all the NOAA endpoints. Limit is being handled in the query so you can exceed the documented NOAA limit of a maximum 1000 per query. The `.all` method will return all of the available records, even if there are more than 1000. You may also set a limit of something over 1000 and that number will be returned to you. For example:
65
+
66
+ ```ruby
67
+ data = NoaaNceiWeather::DataType.all
68
+ data.count # => 1461 - the current count as of this writing
69
+ data = NoaaNceiWeather::DataType.where(limit: 1200)
70
+ data.count # => 1200 - passing a 1200 limit to the noaa api directly would raise a bad request error
71
+ ```
72
+
73
+
74
+ The /data endpoint and corresponding `Data` class is where most of the real data is stored. The NOAA API required parameters are required in the class method, while the rest are passed with a hash as with the other classes. This returns a collection of Data objects.
75
+
76
+ ```ruby
77
+ ds = NoaaNceiWeather::Dataset.first
78
+ date = Date.today - 14
79
+ data = NoaaNceiWeather::Data.where(ds, date, date, {offset: 0})
80
+ ```
81
+
82
+
83
+ ## Development
84
+
85
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
86
+
87
+ Create an `env.rb` file and add a line `ENV['NOAA_TOKEN'] = 'TOKEN'`. The token will be loaded in when using `bin/console` so you don't have to set the token each time. It is also used in the test setup. The file is already included in `.gitignore`.
88
+
89
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
90
+
91
+ ## Contributing
92
+
93
+ Bug reports and pull requests are welcome on GitHub at https://github.com//JasonBThelen/noaa_ncei_weather.
94
+
95
+
96
+ ## License
97
+
98
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
3
+
4
+
5
+ require 'rake/testtask'
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "tests"
9
+ t.test_files = FileList['tests/test*.rb']
10
+ t.verbose = true
11
+ end
data/bin/console ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "noaa_ncei_weather"
5
+ load 'env.rb' if File.exist?('./env.rb')
6
+ NoaaNceiWeather::Connection.token = ENV['NOAA_TOKEN']
7
+
8
+ # You can add fixtures and/or initialization code here to make experimenting
9
+ # with your gem easier. You can also use a different console, if you like.
10
+
11
+ # (If you use this, don't forget to add pry to your Gemfile!)
12
+ require "pry"
13
+ Pry.start
14
+
15
+
16
+
17
+
18
+ # require "irb"
19
+ # IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,13 @@
1
+ require "rest-client"
2
+ require "noaa_ncei_weather/version"
3
+
4
+ require 'noaa_ncei_weather/connection'
5
+ require 'noaa_ncei_weather/weather'
6
+ require 'noaa_ncei_weather/data_category'
7
+
8
+ require 'noaa_ncei_weather/location_category'
9
+ require 'noaa_ncei_weather/data'
10
+ require 'noaa_ncei_weather/station'
11
+ require 'noaa_ncei_weather/data_type'
12
+ require 'noaa_ncei_weather/dataset'
13
+ require 'noaa_ncei_weather/location'
@@ -0,0 +1,92 @@
1
+ module NoaaNceiWeather
2
+
3
+ # @abstract Contains common connection components shared between {Weather} and {Data}
4
+ # used to make requests to the NOAA API.
5
+ module Connection
6
+ # Base URL for the NOAA API. Endppoints are appended in concrete classes.
7
+ @@url = 'http://www.ncdc.noaa.gov/cdo-web/api/v2/'
8
+
9
+ # Connection token required to make requests. Must be set before calling
10
+ # class methods from any NoaaNceiWeather module classes.
11
+ # @note Each Token is restricted to five request per second and 1,000 requests
12
+ # per day per the NOAA documentation
13
+ @@token = ''
14
+
15
+ # Set the request token to be used in requests to NOAA. Token must be obtained
16
+ # from {http://www.ncdc.noaa.gov/cdo-web/token NOAA}. Use this before trying
17
+ # to make any requests:
18
+ # NoaaNceiWeather::Connection.token = 'token'
19
+ #
20
+ # @!attribute [w] token
21
+ # @return [String] Token required to be sent with any requests. This can be
22
+ # obtained for free from {http://www.ncdc.noaa.gov/cdo-web/token NOAA}.
23
+ # The token is good for 5 requests per second, 1,000 requests per day.
24
+ def self.token=(token)
25
+ @@token = token
26
+ end
27
+
28
+ # Parses params to the format expected by the API. Allows more flexibility in what can
29
+ # be sent in as a parameter. Objects, Dates, and Limits are converted
30
+ # into strings as expected by the API.
31
+ #
32
+ # @param params [Hash] Hash of params to be parsed into an API compatible version
33
+ # @return [Hash] Hash of params. Objects are converted into IDs, Dates are
34
+ # converted into iso formatted strings, and max limit is set at 1000
35
+ def parse_params(params)
36
+ # Handle passing of weather objects as parameters for other api queries
37
+ objects = [:dataset, :datatype, :location, :station, :datacategory, :locationcategory]
38
+ objects.each do |object|
39
+ params[(object.to_s + "id").to_sym] = params.delete(object).id if params.has_key?(object)
40
+ end
41
+
42
+ # Handle Date, DateTime, or Time parameters
43
+ # Convert to formatted string the api is expecting
44
+ dates = [:startdate, :enddate]
45
+ dates.each do |date|
46
+ params[date] = params[date].iso8601 if params[date].respond_to?(:iso8601)
47
+ end
48
+
49
+ # Prep for handling requests for over the 1k NOAA limit
50
+ params[:limit] = 1000 unless params[:limit] && params[:limit] < 1000
51
+
52
+ #return modified params
53
+ params
54
+ end
55
+
56
+ # Raw request sent via RestClient to the API. Used by all other requests
57
+ #
58
+ # @param endpoint [String] Endpoint of the API to be used. This is set as a
59
+ # class variable by each of the concerete classes in this gem.
60
+ # @param params [Hash] Hash of params to be passed through to the API
61
+ # @return [Hash] Hashified version of the response body received, includes
62
+ # both actual data and metadata about the resultset
63
+ def request(endpoint, params = {})
64
+ url = @@url + endpoint
65
+ response = RestClient::Request.execute(method: 'get', url: url, headers: {token: @@token, params: params})
66
+ JSON.parse(response.body)
67
+ end
68
+
69
+ # Request with parameters used by child classes. Handles NOAA max limit of 1000
70
+ # records by looping through the request.
71
+ #
72
+ # @param endpoint [String] Endpoint of the API to be used. This is set as a
73
+ # class variable by each of the concerete classes in this gem.
74
+ # @param params [Hash] Hash of params to be passed through to the API
75
+ # @return [Array<Hash>] Array of Hashes containing data only of the result (no metadata)
76
+ def where(endpoint, params = {})
77
+ limit = params[:limit] || Float::INFINITY
78
+ params = self.parse_params(params)
79
+ output = []
80
+ begin
81
+ response = self.request(endpoint, params)
82
+ break unless response.any?
83
+ meta = response['metadata']['resultset']
84
+ output.concat response['results']
85
+ count = meta['offset'] + meta['limit'] - 1
86
+ params[:offset] = count + 1
87
+ params[:limit] = limit - count if (limit - count) < 1000
88
+ end while count < meta['count'] && count < limit && limit > 1000
89
+ output
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,87 @@
1
+ # Namespace for classes and modules that handle requesting data
2
+ # from the NOAA NCEI Historical Weather Database
3
+ module NoaaNceiWeather
4
+
5
+ # Class for querying against the /data endpoint of the
6
+ # {http://www.ncdc.noaa.gov/cdo-web/webservices/v2 NOAA API}. This endpoint
7
+ # gives access to the actual measurements taken.
8
+ class Data
9
+ extend Connection
10
+
11
+ # Endpoint portion of the API URL, appended to the Connection URL for requests
12
+ @@endpoint = 'data'
13
+
14
+ # @!attribute [r] date
15
+ # @return [DateTime] The date/time this data object was measured
16
+ # @!attribute [r] datatype
17
+ # @return [String] The type of data measured, ID of {DataType}
18
+ # @!attribute [r] station
19
+ # @return [String] The station at which the measurement was taken, ID of {Station}
20
+ # @!attribute [r] value
21
+ # @return [Numeric] Numeric value of the measurement
22
+ attr_reader :date, :datatype, :station, :attributes, :value
23
+
24
+ # Creates a new Data object
25
+ def initialize(date, datatype, station, attributes, value)
26
+ @date = date
27
+ @datatype = datatype
28
+ @station = station
29
+ @attributes = attributes
30
+ @value = value
31
+ end
32
+
33
+ # Retrieves a collection of {Data} objects based on the params given.
34
+ #
35
+ # @param datasetid [String, Dataset] A String ID for a Dataset or a DataSet
36
+ # object from the NOAA DB.
37
+ # @param startdate [String, Date] A Date or ISO8601 formatted date string for
38
+ # the earliest data that should be retrieved
39
+ # @param enddate [String, Date] A Date or ISO8601 formatted date string for
40
+ # the latest data that should be retrieved
41
+ # @param params [Hash] A hash including other params to set filters on the NOAA request
42
+ # @option params [String] :datatypeid String ID of a {DataType}
43
+ # within the selected dataset that should be retrieved
44
+ # @option params [DataType] :datatype {DataType} object
45
+ # @option params [String] :locationid String ID of a {Location}
46
+ # @option params [Location] :location {Location} object
47
+ # @option params [String] :stationid String ID of a {Station}
48
+ # @option params [Station] :station {Station} object
49
+ # @option params [String] :units ('metric') Accepts string 'standard' or 'metric'
50
+ # to set the unit of measurement returned in the value
51
+ # @option params [String] :sortfield ('id') Accepts string values 'id', 'name,
52
+ # 'mindate', 'maxdate', and 'datacoverage' to sort data before being returned
53
+ # @option params [String] :sortorder ('asc') Accepts 'asc' or 'desc' for sort order
54
+ # @option params [Integer] :limit Set a limit to the amount of records returned
55
+ # @option params [Integer] :offset (0) Used to offset the result list
56
+ # @return [Array<Data>] An array of Data objects
57
+ def self.where(datasetid, startdate, enddate, params = {})
58
+ datasetid = datasetid.id if datasetid.respond_to?(:id)
59
+ params[:datasetid] = datasetid
60
+ params[:includemetadata] = true
61
+
62
+ startdate = Date.parse startdate if startdate.kind_of? String
63
+ enddate = Date.parse enddate if enddate.kind_of? String
64
+ to_date = enddate
65
+ enddate = startdate + 365 if enddate - startdate > 365
66
+ limit = params[:limit] if params[:limit]
67
+
68
+ output = []
69
+ begin
70
+ params.merge!({startdate: startdate, enddate: enddate})
71
+
72
+ data = super(@@endpoint, params)
73
+ output.concat data
74
+ if limit && output.count >= limit
75
+ output = output[0...limit]
76
+ break
77
+ end
78
+ startdate = enddate + 1
79
+ enddate = startdate + 365
80
+ enddate = to_date if enddate > to_date
81
+ params[:limit] = limit
82
+ params[:offset] = nil
83
+ end while to_date > startdate
84
+ output.collect {|item| self.new DateTime.parse(item['date']), item['datatype'], item['station'], item['attributes'], item['value']}
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,85 @@
1
+ module NoaaNceiWeather
2
+
3
+ # Class for querying against the /datacategory endpoint of the NOAA API
4
+ class DataCategory < Weather
5
+
6
+ # Endpoint portion of the API URL, appended to the Connection URL for requests
7
+ @@endpoint = 'datacategories'
8
+
9
+ # @!attribute [r] id
10
+ # @return [String] The unique Identifier of the {DataCategory}
11
+ # @!attribute [r] name
12
+ # @return [String] The descriptive name of the {DataCategory}
13
+
14
+ # Retrieves the {DataType DataTypes} associated with a {DataCategory} object
15
+ # {DataCategory} has a one to many relationship with {DataType} (in rare
16
+ # cases a DataType may belong to more than one DataCategory)
17
+ #
18
+ # @param params [Hash] See {DataType.where} for valid param key/values
19
+ # @return [Array<DataType>] Array of the data types associated with this
20
+ # {DataCategory} instance
21
+ def data_types(params = {})
22
+ params.merge!({datacategoryid: @id})
23
+ DataType.where(params)
24
+ end
25
+
26
+ # Retrieves the {Location Locations} associated with a {DataCategory} object.
27
+ # {Location} and {DataCategory} have a many to many relationship.
28
+ #
29
+ # @param params [Hash] See {Location.where} for valid param key/values
30
+ # @return [Array<DataType>] Array of the data types associated with this
31
+ # DataCategory instance
32
+ def locations(params = {})
33
+ params.merge!({datacategoryid: @id})
34
+ Location.where(params)
35
+ end
36
+
37
+ # Retrieves the {Station Stations} associated with a {DataCategory} object
38
+ # {Station} and {DataCategory} have a many to many relationship.
39
+ #
40
+ # @param params [Hash] See {Station.where} for valid param key/values
41
+ # @return [Array<DataType>] Array of the data types associated with this
42
+ # DataCategory instance
43
+ def stations(params = {})
44
+ params.merge!({datacategoryid: @id})
45
+ Station.where(params)
46
+ end
47
+
48
+ # Finds a specific instance of {DataCategory} by its ID
49
+ #
50
+ # @param id [String] String ID of the resource.
51
+ # @return [DataCategory, nil] Instance of {DataCategory}, or nil if none found.
52
+ def self.find(id)
53
+ data = super(@@endpoint + "/#{id}")
54
+ if data && data.any?
55
+ self.new data['id'], data['name']
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
61
+ # Retrieves a set of {DataCategory DataCategories} based on the parameters given
62
+ #
63
+ # @param params [Hash] Hash to set filters on the request sent to the NOAA API
64
+ # @option params [String] :datasetid String ID of a {Dataset}
65
+ # @option params [Dataset] :dataset {Dataset} object
66
+ # @option params [String] :locationid String ID of a {Location}
67
+ # @option params [Location] :location {Location} object
68
+ # @option params [String] :stationid String ID of a {Station}
69
+ # @option params [Station] :station {Station} object
70
+ # @option params [String] :sortfield ('id') Accepts string values 'id', 'name,
71
+ # 'mindate', 'maxdate', and 'datacoverage' to sort data before being returned
72
+ # @option params [String] :sortorder ('asc') Accepts 'asc' or 'desc' for sort order
73
+ # @option params [Integer] :limit Set a limit to the amount of records returned
74
+ # @option params [Integer] :offset (0) Used to offset the result list
75
+ # @return [Array<DataCategory>] Array of {DataCategory} objects that match the filter.
76
+ def self.where(params = {})
77
+ data = super(@@endpoint, params)
78
+ if data && data.any?
79
+ data.collect { |item| self.new(item['id'], item['name']) }
80
+ else
81
+ []
82
+ end
83
+ end
84
+ end
85
+ end