├── test ├── __init__.py ├── test_algos.py ├── test_base.py ├── test_aqi.py └── test_epa.py ├── MANIFEST.in ├── dev_requirements.txt ├── .gitignore ├── tox.ini ├── .travis.yml ├── Makefile ├── bumpr.rc ├── docs ├── index.rst ├── Makefile └── conf.py ├── aqi ├── constants.py ├── algos │ ├── __init__.py │ ├── epa.py │ ├── base.py │ └── mep.py └── __init__.py ├── setup.py ├── LICENSE └── README.rst /test/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # include basic files 2 | include LICENSE README.rst 3 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | #releasing 2 | bumpr 3 | tox 4 | twine 5 | wheel 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | python_aqi.egg-info 4 | dev_aqi.py 5 | 6 | docs/_build/ 7 | docs/_static/ 8 | 9 | .tox 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27 3 | 4 | [testenv] 5 | commands = 6 | python -m unittest discover 7 | install_command = 8 | pip install {opts} {packages} 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | - "3.3" 5 | - "3.2" 6 | - "2.7" 7 | install: 8 | - python setup.py develop 9 | script: 10 | - python -m unittest discover 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # vim: set noexpandtab: 2 | pypi: pydist pypipush 3 | 4 | pydist: pyclean test 5 | python setup.py sdist bdist_wheel 6 | 7 | pypipush: dist 8 | twine upload dist/* 9 | 10 | pyclean: 11 | python setup.py clean 12 | rm -rf *egg-info build dist 13 | 14 | test: tox 15 | 16 | unittest: 17 | python -m unittest discover 18 | 19 | tox: 20 | tox 21 | 22 | 23 | .PHONY: pypi pydist pypipush pyclean test unittest tox 24 | -------------------------------------------------------------------------------- /bumpr.rc: -------------------------------------------------------------------------------- 1 | [bumpr] 2 | file = aqi/__init__.py 3 | vcs = git 4 | clean = 5 | python setup.py clean 6 | rm -rf *egg-info build dist 7 | tests = python -m unittest discover 8 | publish = python setup.py sdist bdist_wheel register upload 9 | files = 10 | README.rst 11 | setup.py 12 | docs/conf.py 13 | 14 | [bump] 15 | unsuffix = True 16 | message = bump version to {version} 17 | 18 | [readthedoc] 19 | id = python-aqi 20 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. python-aqi documentation master file, created by 2 | sphinx-quickstart on Sun Sep 21 13:55:04 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. include:: ../README.rst 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | 12 | Indices and tables 13 | ================== 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | 19 | -------------------------------------------------------------------------------- /test/test_algos.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | import aqi.algos 6 | import aqi.algos.epa 7 | import aqi.constants 8 | 9 | 10 | class TestAlgos(unittest.TestCase): 11 | """ 12 | Test the aqi.algo module 13 | """ 14 | 15 | def test_get_algo(self): 16 | """Test algo loading""" 17 | self.assertIsNone( 18 | aqi.algos.get_algo('qwertyuiop')) 19 | self.assertIsInstance( 20 | aqi.algos.get_algo(aqi.constants.ALGO_EPA), 21 | aqi.algos.epa.AQI) 22 | -------------------------------------------------------------------------------- /aqi/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # constants for pollutants names 4 | POLLUTANT_O3_8H = 'o3_8h' 5 | POLLUTANT_O3_1H = 'o3_1h' 6 | POLLUTANT_PM10 = 'pm10' 7 | POLLUTANT_PM25 = 'pm25' 8 | POLLUTANT_CO_1H = 'co_1h' 9 | POLLUTANT_CO_8H = 'co_8h' 10 | POLLUTANT_CO_24H = 'co_24h' 11 | POLLUTANT_SO2_1H = 'so2_1h' 12 | POLLUTANT_SO2_24H = 'so2_24h' 13 | POLLUTANT_NO2_1H = 'no2_1h' 14 | POLLUTANT_NO2_24H = 'no2_24h' 15 | 16 | # constants for algorithms, canonical module name 17 | ALGO_EPA = 'aqi.algos.epa' 18 | ALGO_MEP = 'aqi.algos.mep' 19 | -------------------------------------------------------------------------------- /test/test_base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | import aqi.algos.base 6 | 7 | 8 | class TestBaseAQI(unittest.TestCase): 9 | """ 10 | Test the base module 11 | """ 12 | 13 | def test_aqi(self): 14 | """Test the BaseAQI class""" 15 | _aqi = aqi.algos.base.BaseAQI() 16 | # dummy iaqi function, return the concentration 17 | _aqi.iaqi = lambda x, y: int(y) 18 | self.assertEqual( 19 | _aqi.aqi([(0, '20'), (1,'30'), (2, '100')]), 20 | 100) 21 | self.assertEqual( 22 | _aqi.aqi([(0, '20'), (1,'30'), (2, '100')], iaqis=True), 23 | (100, {0: 20, 1: 30, 2: 100})) 24 | -------------------------------------------------------------------------------- /test/test_aqi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | import aqi 6 | 7 | 8 | class TestAqi(unittest.TestCase): 9 | """ 10 | Test the aqi module with EPA formula 11 | """ 12 | 13 | def test_to_iaqi(self): 14 | """Test IAQI conversion""" 15 | self.assertEqual( 16 | aqi.to_iaqi(aqi.POLLUTANT_O3_8H, '0.08753333', algo=aqi.ALGO_EPA), 17 | 129) 18 | 19 | def test_to_aqi(self): 20 | """Test AQI conversion""" 21 | self.assertEqual( 22 | aqi.to_aqi([ 23 | (aqi.POLLUTANT_O3_8H, '0.077'), 24 | (aqi.POLLUTANT_PM25, '35.9'), 25 | (aqi.POLLUTANT_CO_8H, '8.4') 26 | ], 27 | algo=aqi.ALGO_EPA), 28 | 104) 29 | -------------------------------------------------------------------------------- /aqi/algos/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pkgutil 3 | 4 | 5 | def get_algo(algo_mod): 6 | """Instanciate an AQI algorithm class. If there is a problem during 7 | the import or instanciation, return None. 8 | 9 | :param algo_mod: algorithm module canonical name 10 | :type algo_mod: str 11 | """ 12 | # try to import the algorithm module 13 | try: 14 | mod = __import__(algo_mod, fromlist=[algo_mod]) 15 | except ImportError: 16 | return None 17 | 18 | # try to instanciate an AQI class 19 | try: 20 | return mod.AQI() 21 | except AttributeError: 22 | return None 23 | 24 | def list_algos(): 25 | """Return a list of available algorithms with corresponding 26 | pollutant 27 | """ 28 | _algos = [] 29 | algos_pkg = 'aqi.algos' 30 | 31 | package = __import__(algos_pkg, fromlist=[algos_pkg]) 32 | for importer, modname, ispkg in pkgutil.iter_modules(package.__path__): 33 | if ispkg is False and modname != 'base': 34 | algo_mod = '.'.join([algos_pkg, modname]) 35 | mod = __import__(algo_mod, fromlist=[algo_mod]) 36 | _aqi = mod.AQI() 37 | _algos.append((mod.__name__, _aqi.list_pollutants())) 38 | return _algos 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from setuptools import setup, find_packages 4 | 5 | 6 | here = os.path.abspath(os.path.dirname(__file__)) 7 | 8 | long_description = open(os.path.join(here, 'README.rst')).read() 9 | 10 | # allow setup.py to be run from any path 11 | #os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 12 | 13 | setup( 14 | name='python-aqi', 15 | version="0.6.1", 16 | author="Stefan \"hr\" Berder", 17 | author_email="hr@bonz.org", 18 | license="BSD 3-Clause", 19 | packages=find_packages(exclude=['contrib', 'docs', 'test*']), 20 | url='https://github.com/hrbonz/python-aqi', 21 | description='A library to convert between AQI value and pollutant ' 22 | 'concentration (µg/m³ or ppm)', 23 | long_description=long_description, 24 | classifiers=[ 25 | 'Development Status :: 5 - Production/Stable', 26 | 'Environment :: Console', 27 | 'Intended Audience :: Developers', 28 | 'Intended Audience :: Healthcare Industry', 29 | 'License :: OSI Approved :: BSD License', 30 | 'Programming Language :: Python :: 2.7', 31 | 'Programming Language :: Python :: 3.3', 32 | ], 33 | keywords='air quality pm2.5 EPA MEP', 34 | entry_points = { 35 | 'console_scripts': [ 36 | 'aqi=aqi:console_aqi', 37 | ], 38 | }, 39 | ) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Stefan "hr" Berder 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /test/test_epa.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from decimal import Decimal 4 | import unittest 5 | 6 | from aqi.algos.epa import AQI 7 | from aqi.constants import (POLLUTANT_PM25, POLLUTANT_PM10, 8 | POLLUTANT_O3_8H, POLLUTANT_O3_1H, 9 | POLLUTANT_CO_8H, POLLUTANT_SO2_1H, 10 | POLLUTANT_NO2_1H) 11 | 12 | 13 | class TestEPA(unittest.TestCase): 14 | """ 15 | Test the EPA algorithm based on examples from the EPA official doc. 16 | """ 17 | 18 | def test_PM25(self): 19 | """Test PM2.5 AQI""" 20 | myaqi = AQI() 21 | self.assertEqual( 22 | myaqi.iaqi(POLLUTANT_PM25, '9.3'), 23 | 39) 24 | self.assertEqual( 25 | myaqi.iaqi(POLLUTANT_PM25, '15'), 26 | 57) 27 | self.assertEqual( 28 | myaqi.iaqi(POLLUTANT_PM25, '49.5'), 29 | 135) 30 | self.assertEqual( 31 | myaqi.iaqi(POLLUTANT_PM25, '235.4'), 32 | 285) 33 | 34 | def test_O3(self): 35 | """Test Ozone AQI""" 36 | myaqi = AQI() 37 | self.assertEqual( 38 | myaqi.iaqi(POLLUTANT_O3_8H, '0.08753333'), 39 | 129) 40 | self.assertEqual( 41 | myaqi.iaqi(POLLUTANT_O3_1H, '0.162'), 42 | 147) 43 | self.assertEqual( 44 | myaqi.iaqi(POLLUTANT_O3_8H, '0.141'), 45 | 211) 46 | 47 | def test_O3_PM25_CO(self): 48 | """Test O3, PM2.5 and CO AQI""" 49 | myaqi = AQI() 50 | self.assertEqual( 51 | myaqi.iaqi(POLLUTANT_O3_8H, '0.077') 52 | , 104) 53 | self.assertEqual( 54 | myaqi.iaqi(POLLUTANT_PM25, '35.9') 55 | , 102) 56 | self.assertEqual( 57 | myaqi.iaqi(POLLUTANT_CO_8H, '8.4'), 58 | 90) 59 | self.assertEqual( 60 | myaqi.aqi([ 61 | (POLLUTANT_O3_8H, '0.077'), 62 | (POLLUTANT_PM25, '35.9'), 63 | (POLLUTANT_CO_8H, '8.4') 64 | ]), 65 | 104) 66 | 67 | def test_to_cc(self): 68 | """Test conversion to concentration""" 69 | myaqi = AQI() 70 | self.assertEqual( 71 | myaqi.cc(POLLUTANT_PM25, '39'), 72 | Decimal('9.3')) 73 | self.assertEqual( 74 | myaqi.cc(POLLUTANT_PM25, '50'), 75 | Decimal('12')) 76 | self.assertEqual( 77 | myaqi.cc(POLLUTANT_PM25, '100'), 78 | Decimal('35.4')) 79 | self.assertEqual( 80 | myaqi.cc(POLLUTANT_PM25, '345'), 81 | Decimal('294.9')) 82 | 83 | def test_blank_bp(self): 84 | """Test blank breakpoints""" 85 | myaqi = AQI() 86 | self.assertEqual( 87 | myaqi.iaqi(POLLUTANT_O3_8H, '0.087'), 88 | 129) 89 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ########################### 2 | python-aqi - AQI conversion 3 | ########################### 4 | 5 | A library to convert between AQI value and pollutant concentration 6 | (µg/m³ or ppm) using the following algorithms: 7 | 8 | * United States Environmental Protection Agency (EPA) 9 | * China Ministry of Environmental Protection (MEP) 10 | 11 | .. image:: https://travis-ci.org/hrbonz/python-aqi.svg?branch=master 12 | :target: https://travis-ci.org/hrbonz/python-aqi 13 | :alt: Testing Status 14 | 15 | .. image:: https://readthedocs.org/projects/python-aqi/badge/?version=0.6.1 16 | :target: https://readthedocs.org/projects/python-aqi/?badge=latest 17 | :alt: Documentation Status 18 | 19 | .. image:: http://img.shields.io/badge/license-BSD%203--Clause-blue.svg 20 | :target: http://opensource.org/licenses/BSD-3-Clause 21 | :alt: license BSD 3-Clause 22 | 23 | 24 | Install 25 | ======= 26 | 27 | :: 28 | 29 | $ pip install python-aqi 30 | 31 | 32 | Usage 33 | ===== 34 | 35 | Library 36 | ------- 37 | 38 | Convert a pollutant to its IAQI (Intermediate Air Quality Index):: 39 | 40 | import aqi 41 | myaqi = aqi.to_iaqi(aqi.POLLUTANT_PM25, '12', algo=aqi.ALGO_EPA) 42 | 43 | Get an AQI out of several pollutant concentrations, default algorithm is EPA:: 44 | 45 | import aqi 46 | myaqi = aqi.to_aqi([ 47 | (aqi.POLLUTANT_PM25, '12'), 48 | (aqi.POLLUTANT_PM10, '24'), 49 | (aqi.POLLUTANT_O3_8H, '0.087') 50 | ]) 51 | 52 | Convert an IAQI to its pollutant concentration:: 53 | 54 | import aqi 55 | mycc = aqi.to_cc(aqi.POLLUTANT_PM25, '22', algo=aqi.ALGO_EPA) 56 | 57 | 58 | Command line 59 | ------------ 60 | 61 | List supported algorithms and pollutants:: 62 | 63 | $ aqi -l 64 | aqi.algos.epa: pm10 (µg/m³), o3_8h (ppm), co_8h (ppm), no2_1h (ppb), o3_1h (ppm), so2_1h (ppb), pm25 (µg/m³) 65 | aqi.algos.mep: no2_24h (µg/m³), so2_24h (µg/m³), no2_1h (µg/m³), pm10 (µg/m³), o3_1h (µg/m³), o3_8h (µg/m³), so2_1h (µg/m³), co_1h (mg/m³), pm25 (µg/m³), co_24h (mg/m³) 66 | 67 | Convert PM2.5 to IAQI using EPA algorithm:: 68 | 69 | $ aqi aqi.algos.epa pm25:12 70 | 50 71 | 72 | Convert PM2.5 to IAQI using EPA algorithm (full length):: 73 | 74 | $ aqi -c aqi aqi.algos.epa pm25:12 75 | 50 76 | 77 | Convert pollutants concentrations to AQI using EPA algorithm:: 78 | 79 | $ aqi aqi.algos.epa pm25:40.9 o3_8h:0.077 co_1h:8.4 80 | 114 81 | 82 | Convert pollutants concentrations to AQI using EPA algorithm, display IAQIs:: 83 | 84 | $ aqi -v aqi.algos.epa pm25:40.9 o3_8h:0.077 co_1h:8.4 85 | pm25:102 o3_8h:104 co_1h:90 86 | 114 87 | 88 | Convert PM2.5 IAQI to concentration using EPA algorithm:: 89 | 90 | $ aqi -c cc aqi.algos.epa pm25:39 91 | pm2.5:9.3 92 | 93 | 94 | Development 95 | =========== 96 | 97 | To install the development environment:: 98 | 99 | $ pip install -r dev_requirements.txt 100 | 101 | 102 | Test 103 | ==== 104 | 105 | Test the package:: 106 | 107 | $ python -m unittest discover 108 | 109 | Automatic testing in various environments:: 110 | 111 | $ tox 112 | 113 | 114 | Release 115 | ======= 116 | 117 | Use `bumpr` to release the package:: 118 | 119 | $ bumpr -b -m 120 | 121 | 122 | Project 123 | ======= 124 | 125 | * `Source code on github `_ 126 | * `Documentation on readthedocs `_ 127 | * `Package on pypi `_ 128 | 129 | 130 | Resources 131 | ========= 132 | 133 | * EPA AQI: Technical Assistance Document for the Reporting of Daily Air 134 | Quality – the Air Quality Index (AQI) December 2013) found at http://www.epa.gov/airnow/aqi-technical-assistance-document-dec2013.pdf 135 | * National Ambient Air Quality Standards for Particulate Matter found at http://www.gpo.gov/fdsys/pkg/FR-2013-01-15/pdf/2012-30946.pdf 136 | * MEP AQI: 137 | 138 | * GB3095—2012 (2012/02/29) found at http://www.mep.gov.cn/gkml/hbb/bwj/201203/t20120302_224147.htm 139 | * HJ633-2012 (2012/02/29) found at http://www.zzemc.cn/em_aw/Content/HJ633-2012.pdf 140 | 141 | 142 | License 143 | ======= 144 | 145 | python-aqi is published under a BSD 3-clause license, see the LICENSE file 146 | distributed with the project. 147 | -------------------------------------------------------------------------------- /aqi/algos/epa.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from decimal import * 4 | 5 | from aqi.constants import (POLLUTANT_PM25, POLLUTANT_PM10, 6 | POLLUTANT_O3_8H, POLLUTANT_O3_1H, 7 | POLLUTANT_CO_8H, POLLUTANT_SO2_1H, 8 | POLLUTANT_NO2_1H) 9 | from aqi.algos.base import PiecewiseAQI 10 | 11 | 12 | class AQI(PiecewiseAQI): 13 | """Implementation of the EPA AQI algorithm. 14 | """ 15 | 16 | piecewise = { 17 | 'aqi': [ 18 | (0, 50), 19 | (51, 100), 20 | (101, 150), 21 | (151, 200), 22 | (201, 300), 23 | (301, 400), 24 | (401, 500)], 25 | 'bp': { 26 | POLLUTANT_O3_8H: [ 27 | (Decimal('0.000'), Decimal('0.059')), 28 | (Decimal('0.060'), Decimal('0.075')), 29 | (Decimal('0.076'), Decimal('0.095')), 30 | (Decimal('0.096'), Decimal('0.115')), 31 | (Decimal('0.116'), Decimal('0.374')), 32 | ], 33 | POLLUTANT_O3_1H: [ 34 | (0, 0), 35 | (0, 0), 36 | (Decimal('0.125'), Decimal('0.164')), 37 | (Decimal('0.165'), Decimal('0.204')), 38 | (Decimal('0.205'), Decimal('0.404')), 39 | (Decimal('0.405'), Decimal('0.504')), 40 | (Decimal('0.505'), Decimal('0.604')), 41 | ], 42 | POLLUTANT_PM10: [ 43 | (Decimal('0'), Decimal('54')), 44 | (Decimal('55'), Decimal('154')), 45 | (Decimal('155'), Decimal('254')), 46 | (Decimal('255'), Decimal('354')), 47 | (Decimal('355'), Decimal('424')), 48 | (Decimal('425'), Decimal('504')), 49 | (Decimal('505'), Decimal('604')), 50 | ], 51 | POLLUTANT_PM25: [ 52 | (Decimal('0.0'), Decimal('12.0')), 53 | (Decimal('12.1'), Decimal('35.4')), 54 | (Decimal('35.5'), Decimal('55.4')), 55 | (Decimal('55.5'), Decimal('150.4')), 56 | (Decimal('150.5'), Decimal('250.4')), 57 | (Decimal('250.5'), Decimal('350.4')), 58 | (Decimal('350.5'), Decimal('500.4')), 59 | ], 60 | POLLUTANT_CO_8H: [ 61 | (Decimal('0.0'), Decimal('4.4')), 62 | (Decimal('4.5'), Decimal('9.4')), 63 | (Decimal('9.5'), Decimal('12.4')), 64 | (Decimal('12.5'), Decimal('15.4')), 65 | (Decimal('15.5'), Decimal('30.4')), 66 | (Decimal('30.5'), Decimal('40.4')), 67 | (Decimal('40.5'), Decimal('50.4')), 68 | ], 69 | POLLUTANT_SO2_1H: [ 70 | (Decimal('0'), Decimal('35')), 71 | (Decimal('36'), Decimal('75')), 72 | (Decimal('76'), Decimal('185')), 73 | (Decimal('186'), Decimal('304')), 74 | (Decimal('305'), Decimal('604')), 75 | (Decimal('605'), Decimal('804')), 76 | (Decimal('805'), Decimal('1004')), 77 | ], 78 | POLLUTANT_NO2_1H: [ 79 | (Decimal('0'), Decimal('53')), 80 | (Decimal('54'), Decimal('100')), 81 | (Decimal('101'), Decimal('360')), 82 | (Decimal('361'), Decimal('649')), 83 | (Decimal('650'), Decimal('1249')), 84 | (Decimal('1250'), Decimal('1649')), 85 | (Decimal('1650'), Decimal('2049')), 86 | ], 87 | }, 88 | 'prec': { 89 | POLLUTANT_O3_8H: Decimal('.001'), 90 | POLLUTANT_O3_1H: Decimal('.001'), 91 | POLLUTANT_PM10: Decimal('1.'), 92 | POLLUTANT_PM25: Decimal('.1'), 93 | POLLUTANT_CO_8H: Decimal('.1'), 94 | POLLUTANT_SO2_1H: Decimal('1.'), 95 | POLLUTANT_NO2_1H: Decimal('1.'), 96 | }, 97 | 'units': { 98 | POLLUTANT_O3_8H: 'ppm', 99 | POLLUTANT_O3_1H: 'ppm', 100 | POLLUTANT_PM10: 'µg/m³', 101 | POLLUTANT_PM25: 'µg/m³', 102 | POLLUTANT_CO_8H: 'ppm', 103 | POLLUTANT_SO2_1H: 'ppb', 104 | POLLUTANT_NO2_1H: 'ppb', 105 | }, 106 | } 107 | -------------------------------------------------------------------------------- /aqi/algos/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from decimal import * 4 | 5 | 6 | class BaseAQI(object): 7 | """A generic AQI class""" 8 | 9 | def iaqi(self, elem, cc): 10 | """Calculate an intermediate AQI for a given pollutant. This is 11 | the heart of the algo. Return the IAQI for the given pollutant. 12 | 13 | .. warning:: the concentration is passed as a string so 14 | :class:`decimal.Decimal` doesn't act up with binary floats. 15 | 16 | :param elem: pollutant constant 17 | :type elem: int 18 | :param cc: pollutant contentration (µg/m³ or ppm) 19 | :type cc: str 20 | """ 21 | raise NotImplementedError 22 | 23 | def aqi(self, ccs, iaqis=False): 24 | """Calculate the AQI based on a list of pollutants. Return an 25 | AQI value, if `iaqis` is set to True, send back a tuple 26 | containing the AQI and a dict of IAQIs. 27 | 28 | :param ccs: a list of tuples of pollutants concentrations with 29 | pollutant constant and concentration as values 30 | :type ccs: list 31 | :param iaqis: return IAQIs with result 32 | :type iaqis: bool 33 | """ 34 | _iaqis = {} 35 | for (elem, cc) in ccs: 36 | _iaqi = self.iaqi(elem, cc) 37 | if _iaqi is not None: 38 | _iaqis[elem] = _iaqi 39 | _aqi = max(_iaqis.values()) 40 | if iaqis: 41 | return (_aqi, _iaqis) 42 | else: 43 | return _aqi 44 | 45 | def cc(self, elem, iaqi): 46 | """Calculate a concentration for a given pollutant. Return the 47 | concentration for the given pollutant based on the intermediate AQI. 48 | 49 | .. warning:: the intermediate AQI is passed as a string 50 | 51 | :param elem: pollutant constant 52 | :type elem: int 53 | :param cc: intermediate AQI 54 | :type cc: str 55 | """ 56 | raise NotImplementedError 57 | 58 | def list_pollutants(self): 59 | """List pollutants covered by an algorithm, return a list of 60 | pollutant names. 61 | """ 62 | raise NotImplementedError 63 | 64 | 65 | class PiecewiseAQI(BaseAQI): 66 | """A piecewise function AQI class (like EPA or MEP)""" 67 | 68 | piecewise = None 69 | 70 | def iaqi(self, elem, cc): 71 | if self.piecewise is None: 72 | raise NameError("piecewise struct is not defined") 73 | if elem not in self.piecewise['bp'].keys(): 74 | return None 75 | 76 | _cc = Decimal(cc).quantize(self.piecewise['prec'][elem], 77 | rounding=ROUND_DOWN) 78 | 79 | # define breakpoints for this pollutant at this contentration 80 | bps = self.piecewise['bp'][elem] 81 | bplo = None 82 | bphi = None 83 | idx = 0 84 | for bp in bps: 85 | if _cc >= bp[0] and _cc <= bp[1]: 86 | bplo = bp[0] 87 | bphi = bp[1] 88 | break 89 | idx += 1 90 | # get corresponding AQI boundaries 91 | (aqilo, aqihi) = self.piecewise['aqi'][idx] 92 | 93 | # equation 94 | value = (aqihi - aqilo) / (bphi - bplo) * (_cc - bplo) + aqilo 95 | return value.quantize(Decimal('1.'), rounding=ROUND_HALF_EVEN) 96 | 97 | 98 | def cc(self, elem, iaqi): 99 | if self.piecewise is None: 100 | raise NameError("piecewise struct is not defined") 101 | if elem not in self.piecewise['bp'].keys(): 102 | return None 103 | 104 | _iaqi = int(iaqi) 105 | 106 | # define aqi breakpoints for this pollutant at this IAQI 107 | bps = self.piecewise['aqi'] 108 | bplo = None 109 | bphi = None 110 | idx = 0 111 | for bp in bps: 112 | if _iaqi >= bp[0] and _iaqi <= bp[1]: 113 | bplo = bp[0] 114 | bphi = bp[1] 115 | break 116 | idx += 1 117 | # get corresponding concentration boundaries 118 | (cclo, cchi) = self.piecewise['bp'][elem][idx] 119 | 120 | # equation 121 | value = (cchi - cclo) / (bphi - bplo) * (_iaqi - bplo) + cclo 122 | return Decimal(value).quantize(self.piecewise['prec'][elem], 123 | rounding=ROUND_DOWN) 124 | 125 | def list_pollutants(self): 126 | return self.piecewise['units'].items() 127 | -------------------------------------------------------------------------------- /aqi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import argparse 4 | 5 | from aqi.constants import (POLLUTANT_PM25, POLLUTANT_PM10, 6 | POLLUTANT_O3_8H, POLLUTANT_O3_1H, 7 | POLLUTANT_CO_8H, POLLUTANT_SO2_1H, 8 | POLLUTANT_NO2_1H, ALGO_EPA, ALGO_MEP) 9 | 10 | from aqi.algos import get_algo, list_algos 11 | 12 | __author__ = "Stefan \"hr\" Berder" 13 | __contact__ = "hr@bonz.org" 14 | __license__ = "BSD 3-Clause" 15 | __version__ = "0.6.1" 16 | 17 | 18 | def to_iaqi(elem, cc, algo=ALGO_EPA): 19 | """Calculate an intermediate AQI for a given pollutant. This is the 20 | heart of the algo. 21 | 22 | .. warning:: the concentration is passed as a string so 23 | :class:`decimal.Decimal` doesn't act up with binary floats. 24 | 25 | :param elem: pollutant constant 26 | :type elem: int 27 | :param cc: pollutant contentration (µg/m³ or ppm) 28 | :type cc: str 29 | :param algo: algorithm module canonical name 30 | :type algo: str 31 | """ 32 | _aqi = get_algo(algo) 33 | return _aqi.iaqi(elem, cc) 34 | 35 | def to_aqi(ccs, algo=ALGO_EPA): 36 | """Calculate the AQI based on a list of pollutants 37 | 38 | :param ccs: a list of tuples of pollutants concentrations with 39 | pollutant constant and concentration as values 40 | :type ccs: list 41 | :param algo: algorithm module name 42 | :type algo: str 43 | """ 44 | _aqi = get_algo(algo) 45 | return _aqi.aqi(ccs) 46 | 47 | def to_cc(elem, iaqi, algo=ALGO_EPA): 48 | """Calculate a concentration for a given pollutant. 49 | 50 | .. warning:: the intermediate AQI is passed as a string 51 | 52 | :param elem: pollutant constant 53 | :type elem: int 54 | :param cc: intermediate AQI 55 | :type iaqi: str 56 | :param algo: algorithm module canonical name 57 | :type algo: str 58 | """ 59 | _aqi = get_algo(algo) 60 | return _aqi.cc(elem, iaqi) 61 | 62 | def console_aqi(): 63 | """Console entry point, this function is used as an entry point to 64 | the 'aqi' command. 65 | """ 66 | import sys 67 | import argparse 68 | 69 | parser = argparse.ArgumentParser(description= 70 | """Convert between AQI value and pollutant concentration (µg/m³ or 71 | ppm).""") 72 | parser.add_argument('-c', dest='conv', choices=['aqi', 'cc'], default='aqi', 73 | help="Conversion to perform, defaults to 'aqi'") 74 | parser.add_argument('-l', action='store_true', dest='list', 75 | help='list the available algorithms and ' 76 | 'corresponding pollutants') 77 | parser.add_argument('-v', action='store_true', dest='verbose', 78 | help='add IAQIs to the result') 79 | parser.add_argument('algo', nargs='?', 80 | help='the formula to use for the AQI ' 81 | 'calculation, use the python module path') 82 | parser.add_argument('measures', nargs='*', metavar='measure', 83 | help='pollutant measure, format is ' 84 | 'element_name:concentration. Unknown ' 85 | 'pollutants are silently ignored.') 86 | args = parser.parse_args() 87 | 88 | # list available algorithms 89 | if args.list is True: 90 | for _algo in list_algos(): 91 | print("{algo}: {elem}".format( 92 | algo=_algo[0], elem=', '.join( 93 | ["{0} ({1})".format(elem, unit) for (elem, unit) \ 94 | in _algo[1]]))) 95 | else: 96 | # if not listing but missing other positional argument 97 | if args.algo is None or args.measures is None: 98 | sys.stderr.write("Missing algorithm or measure.\n") 99 | parser.print_help() 100 | sys.exit(1) 101 | _aqi = get_algo(args.algo) 102 | # couln't load the algo module or instanciate AQI class 103 | if _aqi is None: 104 | sys.stderr.write("Unknown algorithm or module is missing an " 105 | "AQI class\n") 106 | parser.print_help() 107 | sys.exit(1) 108 | if args.conv == 'aqi': 109 | ccs = [] 110 | for measure in args.measures: 111 | (elem, cc) = measure.split(':') 112 | ccs.append((elem, cc)) 113 | 114 | ret = _aqi.aqi(ccs, iaqis=args.verbose) 115 | if args.verbose is True: 116 | iaqis = [] 117 | for (constant, iaqi) in ret[1].items(): 118 | iaqis.append(constant + ':' + str(iaqi)) 119 | sys.stdout.write(' '.join(iaqis) + "\n") 120 | sys.stdout.write(str(ret[0]) + "\n") 121 | else: 122 | sys.stdout.write(str(ret) + "\n") 123 | else: 124 | iaqis = [] 125 | for measure in args.measures: 126 | (elem, iaqi) = measure.split(':') 127 | iaqis.append((elem, iaqi)) 128 | 129 | ret = [] 130 | for iaqi in iaqis: 131 | ret.append((iaqi[0], _aqi.cc(iaqi[0], iaqi[1]))) 132 | if len(ret) == 1: 133 | sys.stdout.write(str(ret[0][1]) + "\n") 134 | elif len(ret) > 1: 135 | ccs = [] 136 | for (elem, cc) in ret: 137 | if cc is None: 138 | ccs.append(elem + ':na') 139 | else: 140 | ccs.append(elem + ':' + str(cc)) 141 | sys.stdout.write('\n'.join(ccs) + "\n") 142 | 143 | # end the script without a problem 144 | sys.exit(0) 145 | -------------------------------------------------------------------------------- /aqi/algos/mep.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from decimal import * 4 | 5 | from aqi.constants import (POLLUTANT_PM25, POLLUTANT_PM10, 6 | POLLUTANT_O3_8H, POLLUTANT_O3_1H, 7 | POLLUTANT_CO_1H, POLLUTANT_CO_24H, 8 | POLLUTANT_SO2_1H, POLLUTANT_SO2_24H, 9 | POLLUTANT_NO2_1H, POLLUTANT_NO2_24H) 10 | from aqi.algos.base import PiecewiseAQI 11 | 12 | 13 | class AQI(PiecewiseAQI): 14 | """Implementation of the MEP AQI algorithm. 15 | """ 16 | 17 | piecewise = { 18 | 'aqi': [ 19 | (0, 50), 20 | (51, 100), 21 | (101, 150), 22 | (151, 200), 23 | (201, 300), 24 | (301, 400), 25 | (401, 500)], 26 | 'bp': { 27 | POLLUTANT_SO2_24H: [ 28 | (Decimal('0.0'), Decimal('50')), 29 | (Decimal('51'), Decimal('150')), 30 | (Decimal('151'), Decimal('475')), 31 | (Decimal('476'), Decimal('800')), 32 | (Decimal('801'), Decimal('1600')), 33 | (Decimal('1601'), Decimal('2100')), 34 | (Decimal('2101'), Decimal('2620')), 35 | ], 36 | POLLUTANT_SO2_1H: [ 37 | (Decimal('0.0'), Decimal('150')), 38 | (Decimal('151'), Decimal('500')), 39 | (Decimal('501'), Decimal('650')), 40 | (Decimal('651'), Decimal('800')), 41 | ], 42 | POLLUTANT_NO2_24H: [ 43 | (Decimal('0.0'), Decimal('40')), 44 | (Decimal('41'), Decimal('80')), 45 | (Decimal('81'), Decimal('180')), 46 | (Decimal('181'), Decimal('280')), 47 | (Decimal('281'), Decimal('565')), 48 | (Decimal('566'), Decimal('750')), 49 | (Decimal('751'), Decimal('940')), 50 | ], 51 | POLLUTANT_NO2_1H: [ 52 | (Decimal('0.0'), Decimal('100')), 53 | (Decimal('101'), Decimal('200')), 54 | (Decimal('201'), Decimal('700')), 55 | (Decimal('701'), Decimal('1200')), 56 | (Decimal('1201'), Decimal('2340')), 57 | (Decimal('2341'), Decimal('3090')), 58 | (Decimal('3091'), Decimal('3840')), 59 | ], 60 | POLLUTANT_PM10: [ 61 | (Decimal('00'), Decimal('50')), 62 | (Decimal('51'), Decimal('150')), 63 | (Decimal('151'), Decimal('250')), 64 | (Decimal('251'), Decimal('350')), 65 | (Decimal('351'), Decimal('420')), 66 | (Decimal('421'), Decimal('500')), 67 | (Decimal('501'), Decimal('600')), 68 | ], 69 | POLLUTANT_CO_24H: [ 70 | (Decimal('0.0'), Decimal('2')), 71 | (Decimal('3'), Decimal('4')), 72 | (Decimal('5'), Decimal('14')), 73 | (Decimal('15'), Decimal('24')), 74 | (Decimal('25'), Decimal('36')), 75 | (Decimal('37'), Decimal('48')), 76 | (Decimal('49'), Decimal('60')), 77 | ], 78 | POLLUTANT_CO_1H: [ 79 | (Decimal('0.0'), Decimal('5')), 80 | (Decimal('6'), Decimal('10')), 81 | (Decimal('11'), Decimal('35')), 82 | (Decimal('36'), Decimal('60')), 83 | (Decimal('61'), Decimal('90')), 84 | (Decimal('91'), Decimal('120')), 85 | (Decimal('121'), Decimal('150')), 86 | ], 87 | POLLUTANT_O3_1H: [ 88 | (Decimal('0.0'), Decimal('160')), 89 | (Decimal('161'), Decimal('200')), 90 | (Decimal('201'), Decimal('300')), 91 | (Decimal('301'), Decimal('400')), 92 | (Decimal('401'), Decimal('800')), 93 | (Decimal('801'), Decimal('1000')), 94 | (Decimal('1001'), Decimal('1200')), 95 | ], 96 | POLLUTANT_O3_8H: [ 97 | (Decimal('0.0'), Decimal('100')), 98 | (Decimal('101'), Decimal('160')), 99 | (Decimal('161'), Decimal('215')), 100 | (Decimal('216'), Decimal('265')), 101 | (Decimal('266'), Decimal('800')), 102 | ], 103 | POLLUTANT_PM25: [ 104 | (Decimal('0.0'), Decimal('35')), 105 | (Decimal('36'), Decimal('75')), 106 | (Decimal('76'), Decimal('115')), 107 | (Decimal('116'), Decimal('150')), 108 | (Decimal('151'), Decimal('250')), 109 | (Decimal('251'), Decimal('350')), 110 | (Decimal('351'), Decimal('500')), 111 | ], 112 | }, 113 | 'prec': { 114 | POLLUTANT_O3_8H: Decimal('1.'), 115 | POLLUTANT_O3_1H: Decimal('1.'), 116 | POLLUTANT_PM10: Decimal('1.'), 117 | POLLUTANT_PM25: Decimal('1.'), 118 | POLLUTANT_CO_1H: Decimal('1.'), 119 | POLLUTANT_CO_24H: Decimal('1.'), 120 | POLLUTANT_SO2_1H: Decimal('1.'), 121 | POLLUTANT_SO2_24H: Decimal('1.'), 122 | POLLUTANT_NO2_1H: Decimal('1.'), 123 | POLLUTANT_NO2_24H: Decimal('1.'), 124 | }, 125 | 'units': { 126 | POLLUTANT_SO2_24H: 'µg/m³', 127 | POLLUTANT_SO2_1H: 'µg/m³', 128 | POLLUTANT_NO2_24H: 'µg/m³', 129 | POLLUTANT_NO2_1H: 'µg/m³', 130 | POLLUTANT_PM10: 'µg/m³', 131 | POLLUTANT_CO_24H: 'mg/m³', 132 | POLLUTANT_CO_1H: 'mg/m³', 133 | POLLUTANT_O3_1H: 'µg/m³', 134 | POLLUTANT_O3_8H: 'µg/m³', 135 | POLLUTANT_PM25: 'µg/m³', 136 | }, 137 | } 138 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-aqi.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-aqi.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/python-aqi" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-aqi" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # python-aqi documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Sep 21 13:55:04 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'python-aqi' 44 | copyright = u'2014, Stefan "hr" Berder' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.4' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.6.1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'python-aqidoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'python-aqi.tex', u'python-aqi Documentation', 187 | u'Stefan "hr" Berder', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'python-aqi', u'python-aqi Documentation', 217 | [u'Stefan "hr" Berder'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'python-aqi', u'python-aqi Documentation', 231 | u'Stefan "hr" Berder', 'python-aqi', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | --------------------------------------------------------------------------------