pm_25 0.0.1

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: 00dac5016f468d768d295752677967f3073c757e
4
+ data.tar.gz: c592db0adc0ac25c6e4732a5849fd3319014b06a
5
+ SHA512:
6
+ metadata.gz: debccc45f01e29b5b3104fed7452429c1c556dc674bf3015896657331f33da8e5821bf1304e254c2ee07ec8e85ba3b0069dd188a1b27a602b7cab165b4a1c413
7
+ data.tar.gz: 4e9f749a2b868045f9dc9c0bbd93f88eecc258677bf43f605a3b781f62171287395a182fdbbff4e049b3202cdf6bd2104d6cc9e8fa4705e552ca8a1a26849a38
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.idea/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ *.bundle
12
+ *.so
13
+ *.o
14
+ *.a
15
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pm25.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jakukyo Friel
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # Pm25
2
+
3
+ A Ruby wrapper for pm25.in API and other PM 2.5 related utility functions.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'pm_25'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install pm_25
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require 'pm_25'
25
+ PM25.Func(args)
26
+ ```
27
+
28
+ The following APIs of pm25.in are implemented:
29
+
30
+ - 1.1 `pm25` Get PM 2.5 info of all stations in the specified city.
31
+ - 1.11 `available_cities` Get a list of cites providing PM 2.5 data.
32
+ - 1.12 `all_cities` Get PM 2.5 data for all cities.
33
+ - 1.13 `aqi_ranking` Get average data for all cities. (Cities are sorted by
34
+ AQI.)
35
+
36
+ `1.1`, `1.11` etc are for reference of [pm25.in official api
37
+ documentation][api_doc].
38
+
39
+ [api_doc]: http://www.pm25.in/api_doc
40
+
41
+ I only implemented APIs I care, and I guess in most cases these is the APIs
42
+ you actually want to use.
43
+
44
+ However, if you do need other APIs, feel free to send a pull request.
45
+ You can use `PM25.access_api`. (Actually implementing other APIs is trivial
46
+ with this.)
47
+
48
+ pm25.in requires a token to access its api.
49
+ You can apply one at [here][api_doc].
50
+
51
+ `pm25` will look for a token in the following order:
52
+
53
+ - Token argument when invoking functions.
54
+ - Environment virable `PM25_IN_TOKEN`
55
+ - `"PM25_IN_TOKEN": 'your_token'` in `config.json` at the current directory.
56
+ - The default test token at [pm25.in official api documentation][api_doc].
57
+
58
+ Note that the test token is barely usable (too many people use it).
59
+
60
+ If you does not have a token, you can use `bing_pm25` to get the average
61
+ PM 2.5 value for the specified city.
62
+ This function uses data from bing.com, thus does not need a token.
63
+ For example:
64
+
65
+ ```ruby
66
+ PM25.bing_pm25 '北京'
67
+ ```
68
+
69
+ pm25.in uses CN standard for AQI category.
70
+ If you want to use US standard instead, use `pm25_level`.
71
+ For example:
72
+
73
+ ```ruby
74
+ PM25.pm25_level(123)
75
+ ```
76
+
77
+ It will return a hash containing:
78
+
79
+ - PM 2.5 value
80
+ - AQI category
81
+ - AQI category meaning
82
+ - suggested action
83
+
84
+ For other functions and usage details, check API documentation:
85
+
86
+ http://www.rubydoc.info/gems/pm25/
87
+
88
+ ### Command line usage
89
+
90
+ We also provide a command line utility to query PM 2.5 info for the
91
+ specified city:
92
+
93
+ ```sh
94
+ ; PM25_IN_TOKEN='your_token' pm25 北京
95
+ 北京: 106, Unhealthy
96
+ Everyone may begin to experience health effects; members of sensitive groups may
97
+ experience more serious health effects.
98
+ People with heart or lung disease, children and older adults should avoid
99
+ prolonged or heavy exertion. Everyone else should reduce prolonged or heavy
100
+ exertion.
101
+ ```
102
+
103
+ If you does not provide a city argument, it will use environment variable `PM25_IN_CITY`.
104
+
105
+ If something goes wrong with pm25.in, (say, you does not provide a token), it
106
+ will use bing.com instead.
107
+
108
+ ## Contributing
109
+
110
+ 1. Fork it ( https://github.com/[my-github-username]/pm25/fork )
111
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
112
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
113
+ 4. Push to the branch (`git push origin my-new-feature`)
114
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/pm25 ADDED
@@ -0,0 +1,13 @@
1
+ require 'pm25'
2
+
3
+ if ARGV.empty?
4
+ city = ENV['PM25_IN_CITY']
5
+ else
6
+ city = ARGV[0]
7
+ end
8
+
9
+ pm25_info = PM25.pm25_level(PM25.just_pm25(city, ENV['PM25_IN_TOKEN']))
10
+
11
+ puts "#{city}: #{pm25_info[:pm25]}, #{pm25_info[:category]}"
12
+ puts pm25_info[:meaning]
13
+ puts pm25_info[:action]
@@ -0,0 +1,3 @@
1
+ module PM25
2
+ VERSION = "0.0.1"
3
+ end
data/lib/pm_25.rb ADDED
@@ -0,0 +1,243 @@
1
+ require 'pm_25/version'
2
+
3
+ require 'nokogiri'
4
+ require 'json'
5
+ require 'open-uri'
6
+ require 'rest_client'
7
+ require 'time-lord'
8
+
9
+ module PM25
10
+ # Yes, pm25.in requires a token but uses http!
11
+ API_base = 'http://www.pm25.in/api/querys/'
12
+
13
+ module_function
14
+
15
+ # Use environment variable or the value
16
+ # from config at the current directory.
17
+ # If all failed, use the default value.
18
+ #
19
+ # @param [String] constant_name
20
+ # @param [String] default_value
21
+ # @return [String]
22
+ def get_config(constant_name, default_value=nil)
23
+ if ENV[constant_name]
24
+ ENV[constant_name]
25
+ elsif File.exist?('config.json')
26
+ open('config.json') do |f|
27
+ JSON.parse(f.read)[constant_name]
28
+ end
29
+ else
30
+ default_value
31
+ end
32
+ end
33
+
34
+ # Use environment variable PM25_IN_TOKEN
35
+ # or the value from config at the current directory.
36
+ # If all failed, use the test token.
37
+ # You can apply a token at pm25.in:
38
+ # http://www.pm25.in/api_doc
39
+ #
40
+ # @return [String] token
41
+ def get_token
42
+ test_token= '5j1znBVAsnSf5xQyNQyq'
43
+ get_config('PM25_IN_TOKEN', test_token)
44
+ end
45
+
46
+ # Use environment variable PM25_IN_CITY
47
+ # or the value from config at the current directory.
48
+ #
49
+ # @return [String] city
50
+ def get_default_city
51
+ get_config('PM25_IN_CITY')
52
+ end
53
+
54
+ # @param [String] interface of api
55
+ # @param [Hash] params (additional) options
56
+ # @return [Hash] result
57
+ # @return [Fixnum] error code
58
+ # TODO pm25.in tends to 502, need to email complaints and handle this.
59
+ def access_api(interface, params={})
60
+ params[:token] ||= get_token
61
+ res = RestClient.get(API_base + interface, {params: params})
62
+ if res.code == 200
63
+ JSON.parse res.body
64
+ else
65
+ res.code
66
+ end
67
+ end
68
+
69
+
70
+ # Get PM 2.5 info of all stations in the specified city.
71
+ # API frequency limit: 500 per hour.
72
+ #
73
+ # @param [String] city You can use
74
+ # - Chinese (e.g. 广州)
75
+ # - area code (e.g. 020)
76
+ # - Pinyin (e.g. guangzhou)
77
+ # If there is ambiguity in Pinyin, use `shi` as postfix, for example:
78
+ # 泰州 is taizhoushi, and 台州 is taizhou.
79
+ # For more information, see http://www.pm25.in/
80
+ # @return [Array<Hash>] an array of all stations
81
+ # Every station includes:
82
+ # * aqi (according to CN standard)
83
+ # * area
84
+ # * pm2_5
85
+ # * pm2_5_24h (moving average)
86
+ # * position_name (station name)
87
+ # * primary_pollutant
88
+ # * quality (优、良、轻度污染、中度污染、重度污染、严重污染 according to CN standard)
89
+ # * station_code
90
+ # * time_point (publish time of air condition)
91
+ #
92
+ # Example:
93
+ #
94
+ # pm25('zhuhai')
95
+ #
96
+ # [
97
+ # {
98
+ # "aqi"=> 82,
99
+ # "area"=> "珠海",
100
+ # "pm2_5"=> 31,
101
+ # "pm2_5_24h"=> 60,
102
+ # "position_name"=> "吉大",
103
+ # "primary_pollutant"=> "颗粒物(PM2.5)",
104
+ # "quality"=> "良",
105
+ # "station_code"=> "1367A",
106
+ # "time_point"=> "2013-03-07T19:00:00Z"
107
+ # },
108
+ # ...
109
+ # ...
110
+ # ...
111
+ # {
112
+ # "aqi"=> 108,
113
+ # "area"=> "珠海",
114
+ # "pm2_5"=> 0,
115
+ # "pm2_5_24h"=> 53,
116
+ # "position_name"=> "斗门",
117
+ # "primary_pollutant"=> "臭氧8小时",
118
+ # "quality"=> "轻度污染",
119
+ # "station_code"=> "1370A",
120
+ # "time_point"=> "2013-03-07T19:00:00Z"
121
+ # },
122
+ # {
123
+ # "aqi"=> 99,
124
+ # "area"=> "珠海",
125
+ # "pm2_5"=> 39,
126
+ # "pm2_5_24h"=> 67,
127
+ # "position_name"=> null,
128
+ # "primary_pollutant"=> null,
129
+ # "quality"=> "良",
130
+ # "station_code"=> null,
131
+ # "time_point"=> "2013-03-07T19:00:00Z"
132
+ # }
133
+ # ]
134
+ def pm25(city=get_default_city, token=nil)
135
+ access_api('pm2_5.json', city: city, token: token)
136
+ end
137
+
138
+ # Get a list of cites providing PM 2.5 data.
139
+ # API frequency limit: 10 per hour.
140
+ # @param [String] token
141
+ # @return [Array] city list
142
+ # @return [Fixnum] error code
143
+ def available_cities(token=get_token)
144
+ res = RestClient.get 'http://www.pm25.in/api/querys.json', {params: {token: token}}
145
+ if res.code == 200
146
+ JSON.parse(res.body)['cities']
147
+ else
148
+ res.code
149
+ end
150
+ end
151
+
152
+ # Get PM 2.5 data for all cities.
153
+ # API frequency limit: 5 per hour.
154
+ # @param [String] token
155
+ # @return [Hash]
156
+ # @return [Fixnum] error code
157
+ def all_cities(token=nil)
158
+ access_api('all_cities.json', token: token)
159
+ end
160
+
161
+ # Get average data for all cities. (Cities are sorted by AQI.)
162
+ # API frequency limit: 15 per hour.
163
+ # @param [String] token
164
+ # @return [Hash]
165
+ # @return [Fixnum] error code
166
+ def aqi_ranking(token=nil)
167
+ access_api('aqi_ranking.json', token: token)
168
+ end
169
+
170
+
171
+ # Get PM 2.5 from bing.com
172
+ # @param [String] city only accept Chinese
173
+ # @return [Fixnum] PM 25 value
174
+ def bing_pm25(city)
175
+ city = URI.encode(city)
176
+ bing_url = "http://cn.bing.com/search?q=#{city}+pm2.5"
177
+ html = Nokogiri.parse(open(bing_url).read)
178
+ html.at('#msn_pm25rt .b_xlText').content.to_i
179
+ end
180
+
181
+ # Get PM 2.5 value for city.
182
+ # Fallback to bing.com.
183
+ # Return PM 2.5 value only.
184
+ # @param [String] city only accept Chinese
185
+ # @param [String] token
186
+ # @return [Fixnum] average PM 2.5 value for stations
187
+ def just_pm25(city=get_default_city, token=nil)
188
+ result = pm25(city, token)
189
+ # error on pm25.in api
190
+ if result.is_a? Fixnum
191
+ bing_pm25(city)
192
+ elsif result.include? 'error'
193
+ bing_pm25(city)
194
+ else
195
+ publish_time = Time.parse(result[-1]['time_point'])
196
+ # Data on pm25.in is too old.
197
+ # According to GB 3095-2013, PM 2.5 should have hourly
198
+ # average values for at least 20 hours a day.
199
+ # http://hbj.shanghang.gov.cn/hjjc/gldt/201411/P020141110361645902554.pdf
200
+ if Time.now - publish_time > 2.hours
201
+ bing_pm25(city)
202
+ else
203
+ result[-1]['pm2_5']
204
+ end
205
+ end
206
+ end
207
+
208
+
209
+ # Get AQI category, meaning and action according to US standard.
210
+ # @param [Fixnum]
211
+ # @return [Hash{Symbol: Fixnum, Symbol: String, Symbol: String, Symbol: String}]
212
+ def pm25_level(pm25)
213
+ if pm25 <= 12
214
+ aqi_category = 'Good'
215
+ aqi_meaning = 'Air quality is considered satisfactory, and air pollution poses little or no risk.'
216
+ aqi_action = 'None'
217
+ elsif pm25 <= 35.4
218
+ aqi_category = 'Moderate'
219
+ aqi_meaning = 'Air quality is acceptable; however, for some pollutants there may be a moderate health concern for a very small number of people who are unusually sensitive to air pollution.'
220
+ aqi_action = 'Unusually sensitive people should consider reducing prolonged or heavy exertion.'
221
+ elsif pm25 <= 55.4
222
+ aqi_category = 'Unhealthy for sensitive Groups'
223
+ aqi_meaning = 'Members of sensitive groups may experience health effects. The general public is not likely to be affected.'
224
+ aqi_action = 'People with heart or lung disease, children and older adults should reduc e prolonged or heavy exertion'
225
+ elsif pm25 <= 150.4
226
+ aqi_category = 'Unhealthy'
227
+ aqi_meaning = 'Everyone may begin to experience health effects; members of sensitive groups may experience more serious health effects.'
228
+ aqi_action = 'People with heart or lung disease, children and older adults should avoid prolonged or heavy exertion. Everyone else should reduce prolonged or heavy exertion.'
229
+ elsif pm25 <= 250.4
230
+ aqi_category = 'Very Unhealthy'
231
+ aqi_meaning = 'Health warnings of emergency conditions. The entire population is more likely to be affected.'
232
+ aqi_action = 'People with heart or lung disease, children and older adults should avoid all physical activity outdoors. Everyone else should avoid prolonged or heavy exertion.'
233
+ else
234
+ aqi_category = 'Hazardous'
235
+ aqi_meaning = 'Health alert: everyone may experience more serious health effects'
236
+ aqi_action = 'Avoid all physical activity outdoors.'
237
+ end
238
+ {pm25: pm25,
239
+ category: aqi_category,
240
+ meaning: aqi_meaning,
241
+ action: aqi_action}
242
+ end
243
+ end
data/pm_25.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pm_25/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'pm_25'
8
+ spec.version = PM25::VERSION
9
+ spec.authors = ['Jakukyo Friel']
10
+ spec.email = ['weakish@gmail.com']
11
+ spec.summary = %q{Fetch PM 2.5 data in China.}
12
+ spec.description = %q{A Ruby wrapper for pm25.in API and other PM 2.5
13
+ related utility functions.}
14
+ spec.homepage = 'https://github.com/weakish/pm_25'
15
+ spec.license = 'MIT'
16
+ spec.metadata = {
17
+ 'repository' => 'https://github.com/weakish/pm_25.git',
18
+ 'documentation' => 'http://www.rubydoc.info/gems/pm_25/',
19
+ 'issues' => 'https://github.com/weakish/pm_25/issues',
20
+ 'wiki' => 'https://github.com/weakish/pm_25/wiki'
21
+ }
22
+
23
+ spec.files = `git ls-files -z`.split("\x0")
24
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
25
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26
+ spec.require_paths = ['lib']
27
+
28
+ spec.add_runtime_dependency 'nokogiri', '~> 1.6'
29
+ spec.add_runtime_dependency 'rest_client', '~> 1.8'
30
+ spec.add_runtime_dependency 'time-lord', '~> 1.0'
31
+ spec.add_development_dependency 'bundler', '~> 1.7'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pm_25
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jakukyo Friel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest_client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: time-lord
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ description: |-
84
+ A Ruby wrapper for pm25.in API and other PM 2.5
85
+ related utility functions.
86
+ email:
87
+ - weakish@gmail.com
88
+ executables:
89
+ - pm25
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".gitignore"
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - bin/pm25
99
+ - lib/pm_25.rb
100
+ - lib/pm_25/version.rb
101
+ - pm_25.gemspec
102
+ homepage: https://github.com/weakish/pm_25
103
+ licenses:
104
+ - MIT
105
+ metadata:
106
+ repository: https://github.com/weakish/pm_25.git
107
+ documentation: http://www.rubydoc.info/gems/pm_25/
108
+ issues: https://github.com/weakish/pm_25/issues
109
+ wiki: https://github.com/weakish/pm_25/wiki
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.2.2
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Fetch PM 2.5 data in China.
130
+ test_files: []
131
+ has_rdoc: