├── .github └── workflows │ ├── release.yml │ └── test.yaml ├── .gitignore ├── Makefile ├── README.md ├── __init__.py ├── admin.py ├── common ├── __init__.py └── exceptions.py ├── config.json ├── docker-compose.yml ├── docs ├── assets │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── img │ │ └── examples │ │ │ ├── active_user_folder.png │ │ │ ├── add_custom_repository.png │ │ │ ├── enable_experimental_plugins.png │ │ │ ├── install_from_zip.png │ │ │ ├── isochrone.png │ │ │ ├── isochrones_design.png │ │ │ ├── isochrones_map_.png │ │ │ ├── isochrones_map_up.png │ │ │ ├── isochrones_plugin.png │ │ │ └── isochrones_plugin_result.png │ └── logo.png ├── index.md └── plugin │ └── changelog.txt ├── gui ├── __init__.py └── qgis_isochrone_dialog.py ├── help ├── Makefile ├── make.bat └── source │ ├── conf.py │ └── index.rst ├── i18n └── af.ts ├── iso ├── __init__.py ├── base.py ├── db_functions.py └── utils.py ├── main.py ├── metadata.txt ├── mkdocs.yml ├── pb_tool.cfg ├── plugin_upload.py ├── pylintrc ├── requirements-dev.txt ├── resources.py ├── resources ├── img │ └── icons │ │ ├── icon.png │ │ ├── icon2.png │ │ └── icon2.svg └── styles │ └── qgis │ ├── catchment.qml │ ├── drivetimes.qml │ ├── iso.qml │ ├── map.qml │ └── network.qml ├── run-docker-test.sh ├── scripts ├── compile-strings.sh ├── docker │ ├── qgis-isochrones-test-pre-scripts.sh │ └── qgis-testing-entrypoint.sh ├── run-env-linux.sh └── update-strings.sh ├── test ├── __init__.py ├── data │ ├── __init__.py │ ├── catchment │ │ ├── drivetime_layer.cpg │ │ ├── drivetime_layer.dbf │ │ ├── drivetime_layer.prj │ │ ├── drivetime_layer.qpj │ │ ├── drivetime_layer.shp │ │ ├── drivetime_layer.shx │ │ ├── hospitals.cpg │ │ ├── hospitals.dbf │ │ ├── hospitals.licence │ │ ├── hospitals.prj │ │ ├── hospitals.qpj │ │ ├── hospitals.shp │ │ ├── hospitals.shx │ │ ├── isochrones.cpg │ │ ├── isochrones.dbf │ │ ├── isochrones.prj │ │ ├── isochrones.qpj │ │ ├── isochrones.shp │ │ ├── isochrones.shx │ │ └── raster_iso.tif │ ├── hospitals.cpg │ ├── hospitals.dbf │ ├── hospitals.licence │ ├── hospitals.prj │ ├── hospitals.qpj │ ├── hospitals.shp │ ├── hospitals.shx │ └── network │ │ ├── roads.cpg │ │ ├── roads.dbf │ │ ├── roads.license │ │ ├── roads.prj │ │ ├── roads.qpj │ │ ├── roads.shp │ │ └── roads.shx ├── qgis_interface.py ├── test_init.py ├── test_isochrone_dialog.py ├── test_qgis_environment.py ├── test_resources.py ├── test_utilities.py └── utilities.py ├── test_suite.py └── ui ├── __init__.py └── isochrone_dialog_base.ui /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create a release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | jobs: 8 | create-release: 9 | runs-on: ubuntu-20.04 10 | container: 11 | image: qgis/qgis:release-3_22 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Fix Python command 17 | run: apt-get install python-is-python3 18 | 19 | - name: Install plugin dependencies 20 | run: pip3 install -r requirements-dev.txt 21 | 22 | - name: Get experimental info 23 | id: get-experimental 24 | run: | 25 | echo "::set-output name=IS_EXPERIMENTAL::$(python -c "import json; f = open('config.json'); data=json.load(f); print(str(data['general']['experimental']).lower())")" 26 | - name: Create release from tag 27 | id: create-release 28 | uses: actions/create-release@v1 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | with: 32 | tag_name: ${{ github.ref }} 33 | release_name: Release ${{ github.ref }} 34 | prerelease: ${{ steps.get-experimental.outputs.IS_EXPERIMENTAL }} 35 | draft: false 36 | 37 | - name: Generate zip 38 | run: python admin.py generate-zip 39 | 40 | - name: get zip details 41 | id: get-zip-details 42 | run: | 43 | echo "::set-output name=ZIP_PATH::dist/$(ls dist)\n" 44 | echo "::set-output name=ZIP_NAME::$(ls dist)" 45 | - name: Upload release asset 46 | id: upload-release-asset 47 | uses: actions/upload-release-asset@v1 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | with: 51 | upload_url: ${{ steps.create-release.outputs.upload_url}} 52 | asset_path: ${{ steps.get-zip-details.outputs.ZIP_PATH}} 53 | asset_name: ${{ steps.get-zip-details.outputs.ZIP_NAME}} 54 | asset_content_type: application/zip 55 | 56 | - name: Generate plugin repo XML 57 | run: python admin.py --verbose generate-plugin-repo-xml 58 | 59 | - name: Mark plugin directory as safe 60 | run: | 61 | git config --global --add safe.directory /__w/isochrones/isochrones 62 | 63 | - name: Update release repository 64 | run: mkdocs gh-deploy --force -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - edited 10 | - opened 11 | - reopened 12 | - synchronize 13 | branches: 14 | - master 15 | 16 | env: 17 | # Global environment variable 18 | IMAGE: qgis/qgis 19 | WITH_PYTHON_PEP: "true" 20 | MUTE_LOGS: "true" 21 | 22 | jobs: 23 | test: 24 | runs-on: ubuntu-20.04 25 | name: Running tests on QGIS ${{ matrix.qgis_version_tag }} 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | qgis_version_tag: 31 | - release-3_10 32 | - release-3_24 33 | - release-3_26 34 | - release-3_28 35 | 36 | steps: 37 | 38 | - name: Checkout 39 | uses: actions/checkout@v2 40 | with: 41 | submodules: recursive 42 | 43 | - name: Preparing docker-compose environment 44 | env: 45 | QGIS_VERSION_TAG: ${{ matrix.qgis_version_tag }} 46 | run: | 47 | cat << EOF > .env 48 | QGIS_VERSION_TAG=${QGIS_VERSION_TAG} 49 | IMAGE=${IMAGE} 50 | ON_TRAVIS=true 51 | MUTE_LOGS=${MUTE_LOGS} 52 | WITH_PYTHON_PEP=${WITH_PYTHON_PEP} 53 | EOF 54 | 55 | - name: Preparing test environment 56 | run: | 57 | cat .env 58 | docker pull "${IMAGE}":${{ matrix.qgis_version_tag }} 59 | docker-compose up -d 60 | sleep 10 61 | - name: Run test suite 62 | run: | 63 | docker-compose exec -T qgis-testing-environment qgis_testrunner.sh test_suite.test_package 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pycharm-debug.egg 2 | pycharm-debug.egg 3 | pydevd-pycharm.egg 4 | pydevd/ 5 | *~ 6 | *.*~ 7 | *.aux.xml 8 | *.bak 9 | build 10 | .coverage 11 | coverage.xml 12 | build 13 | dist 14 | docs/build 15 | docs/_build 16 | docs/output 17 | docs/release-approval-docs 18 | *.DS_Store 19 | .idea 20 | libpeerconnection.log 21 | nohup.out 22 | .noseids 23 | nosetests.xml 24 | *.orig 25 | *.*.orig 26 | pep8.log 27 | .project 28 | *.pyc 29 | pydevpath.txt 30 | .pydevproject 31 | pyflakes.log 32 | pylint.log 33 | qgispath.txt 34 | result.txt 35 | .settings 36 | setup.cfg 37 | .svn 38 | .vagrant 39 | venv 40 | /utilities/test/[temporary file].* 41 | /utilities/test/[temporary_file]/ 42 | [temporary file].* 43 | [temporary_file] 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #/*************************************************************************** 2 | # isochrones 3 | # 4 | # This plugin create isochrones maps. 5 | # ------------------- 6 | # begin : 2016-07-02 7 | # git sha : $Format:%H$ 8 | # copyright : (C) 2016 by Samweli Mwakisambwe 9 | # email : smwakisambwe@worldbank.org 10 | # ***************************************************************************/ 11 | # 12 | #/*************************************************************************** 13 | # * * 14 | # * This program is free software; you can redistribute it and/or modify * 15 | # * it under the terms of the GNU General Public License as published by * 16 | # * the Free Software Foundation; either version 2 of the License, or * 17 | # * (at your option) any later version. * 18 | # * * 19 | # ***************************************************************************/ 20 | 21 | ################################################# 22 | # Edit the following to match your sources lists 23 | ################################################# 24 | 25 | 26 | #Add iso code for any locales you want to support here (space separated) 27 | # default is no locales 28 | # LOCALES = af 29 | LOCALES = 30 | 31 | # If locales are enabled, set the name of the lrelease binary on your system. If 32 | # you have trouble compiling the translations, you may have to specify the full path to 33 | # lrelease 34 | #LRELEASE = lrelease 35 | #LRELEASE = lrelease-qt4 36 | 37 | 38 | # translation 39 | SOURCES = \ 40 | __init__.py \ 41 | iso/main/isochrone.py iso/gui/tools/isochrone_dialog.py 42 | 43 | PLUGINNAME = isochrones 44 | 45 | PY_FILES = \ 46 | __init__.py \ 47 | iso/main/isochrone.py iso/gui/tools/isochrone_dialog.py 48 | 49 | UI_FILES = iso/gui/ui/isochrone_dialog_base.ui 50 | 51 | EXTRAS = metadata.txt icon.png 52 | 53 | COMPILED_RESOURCE_FILES = resources.py 54 | 55 | PEP8EXCLUDE=pydev,resources.py,conf.py,third_party,ui 56 | 57 | 58 | ################################################# 59 | # Normally you would not need to edit below here 60 | ################################################# 61 | 62 | HELP = help/build/html 63 | 64 | PLUGIN_UPLOAD = $(c)/plugin_upload.py 65 | 66 | RESOURCE_SRC=$(shell grep '^ *@@g;s/.*>//g' | tr '\n' ' ') 67 | 68 | QGISDIR=.qgis2 69 | 70 | default: compile 71 | 72 | compile: $(COMPILED_RESOURCE_FILES) 73 | 74 | %.py : %.qrc $(RESOURCES_SRC) 75 | pyrcc4 -o $*.py $< 76 | 77 | %.qm : %.ts 78 | $(LRELEASE) $< 79 | 80 | test: compile transcompile 81 | @echo 82 | @echo "----------------------" 83 | @echo "Regression Test Suite" 84 | @echo "----------------------" 85 | 86 | @# Preceding dash means that make will continue in case of errors 87 | @-export PYTHONPATH=`pwd`:$(PYTHONPATH); \ 88 | export QGIS_DEBUG=0; \ 89 | export QGIS_LOG_FILE=/dev/null; \ 90 | nosetests -v --with-id --with-coverage --cover-package=. \ 91 | 3>&1 1>&2 2>&3 3>&- || true 92 | @echo "----------------------" 93 | @echo "If you get a 'no module named qgis.core error, try sourcing" 94 | @echo "the helper script we have provided first then run make test." 95 | @echo "e.g. source run-env-linux.sh ; make test" 96 | @echo "----------------------" 97 | 98 | deploy: compile doc transcompile 99 | @echo 100 | @echo "------------------------------------------" 101 | @echo "Deploying plugin to your .qgis2 directory." 102 | @echo "------------------------------------------" 103 | # The deploy target only works on unix like operating system where 104 | # the Python plugin directory is located at: 105 | # $HOME/$(QGISDIR)/python/plugins 106 | mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 107 | cp -vf $(PY_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 108 | cp -vf $(UI_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 109 | cp -vf $(COMPILED_RESOURCE_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 110 | cp -vf $(EXTRAS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 111 | cp -vfr i18n $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 112 | cp -vfr $(HELP) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/help 113 | 114 | # The dclean target removes compiled python files from plugin directory 115 | # also deletes any .git entry 116 | dclean: 117 | @echo 118 | @echo "-----------------------------------" 119 | @echo "Removing any compiled python files." 120 | @echo "-----------------------------------" 121 | find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete 122 | find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname ".git" -prune -exec rm -Rf {} \; 123 | 124 | 125 | derase: 126 | @echo 127 | @echo "-------------------------" 128 | @echo "Removing deployed plugin." 129 | @echo "-------------------------" 130 | rm -Rf $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) 131 | 132 | zip: deploy dclean 133 | @echo 134 | @echo "---------------------------" 135 | @echo "Creating plugin zip bundle." 136 | @echo "---------------------------" 137 | # The zip target deploys the plugin and creates a zip file with the deployed 138 | # content. You can then upload the zip file on http://plugins.qgis.org 139 | rm -f $(PLUGINNAME).zip 140 | cd $(HOME)/$(QGISDIR)/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME) 141 | 142 | package: compile 143 | # Create a zip package of the plugin named $(PLUGINNAME).zip. 144 | # This requires use of git (your plugin development directory must be a 145 | # git repository). 146 | # To use, pass a valid commit or tag as follows: 147 | # make package VERSION=Version_0.3.2 148 | @echo 149 | @echo "------------------------------------" 150 | @echo "Exporting plugin to zip package. " 151 | @echo "------------------------------------" 152 | rm -f $(PLUGINNAME).zip 153 | git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION) 154 | echo "Created package: $(PLUGINNAME).zip" 155 | 156 | upload: zip 157 | @echo 158 | @echo "-------------------------------------" 159 | @echo "Uploading plugin to QGIS Plugin repo." 160 | @echo "-------------------------------------" 161 | $(PLUGIN_UPLOAD) $(PLUGINNAME).zip 162 | 163 | transup: 164 | @echo 165 | @echo "------------------------------------------------" 166 | @echo "Updating translation files with any new strings." 167 | @echo "------------------------------------------------" 168 | @chmod +x scripts/update-strings.sh 169 | @scripts/update-strings.sh $(LOCALES) 170 | 171 | transcompile: 172 | @echo 173 | @echo "----------------------------------------" 174 | @echo "Compiled translation files to .qm files." 175 | @echo "----------------------------------------" 176 | @chmod +x scripts/compile-strings.sh 177 | @scripts/compile-strings.sh $(LRELEASE) $(LOCALES) 178 | 179 | transclean: 180 | @echo 181 | @echo "------------------------------------" 182 | @echo "Removing compiled translation files." 183 | @echo "------------------------------------" 184 | rm -f i18n/*.qm 185 | 186 | clean: 187 | @echo 188 | @echo "------------------------------------" 189 | @echo "Removing uic and rcc generated files" 190 | @echo "------------------------------------" 191 | rm $(COMPILED_UI_FILES) $(COMPILED_RESOURCE_FILES) 192 | 193 | doc: 194 | @echo 195 | @echo "------------------------------------" 196 | @echo "Building documentation using sphinx." 197 | @echo "------------------------------------" 198 | cd help; make html 199 | 200 | pylint: 201 | @echo 202 | @echo "-----------------" 203 | @echo "Pylint violations" 204 | @echo "-----------------" 205 | @pylint --reports=n --rcfile=pylintrc . || true 206 | @echo 207 | @echo "----------------------" 208 | @echo "If you get a 'no module named qgis.core' error, try sourcing" 209 | @echo "the helper script we have provided first then run make pylint." 210 | @echo "e.g. source run-env-linux.sh ; make pylint" 211 | @echo "----------------------" 212 | 213 | 214 | # Run pep8 style checking 215 | #http://pypi.python.org/pypi/pep8 216 | pep8: 217 | @echo 218 | @echo "-----------" 219 | @echo "PEP8 issues" 220 | @echo "-----------" 221 | @pep8 --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128 --exclude $(PEP8EXCLUDE) . || true 222 | @echo "-----------" 223 | @echo "Ignored in PEP8 check:" 224 | @echo $(PEP8EXCLUDE) 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Isochrones Qgis Plugin 2 | ======= 3 | 4 | ![Tests](https://github.com/samweli/isochrones/actions/workflows/test.yaml/badge.svg) 5 | ![Latest release](https://img.shields.io/github/v/release/samweli/isochrones) 6 | 7 | 8 | QGIS plugin that creates isochrones map from network data. 9 | 10 | Isochrone example 11 | 12 | Site - https://samweli.github.io/isochrones (still on development) 13 | 14 | ## Installation 15 | 16 | Install QGIS. Download it from http://download.qgis.org/. Instructions on installing QGIS for 17 | different Operating Sytstems is available here 18 | [https://www.qgis.org/en/site/forusers/download.html#tab1](https://www.qgis.org/en/site/forusers/download.html#tab1) 19 | 20 | ### Install from QGIS plugin repository 21 | 22 | - Launch QGIS application and open plugin manager. 23 | - Search for `Isochrones` in the `All` page of the plugin manager. 24 | - From the found results, click on the `Isochrones` result item and a page with plugin information will show up. 25 | ![Isochrones result item](docs/assets/img/examples/isochrones_plugin_result.png) 26 | - Click the `Install Plugin` button at the bottom of the dialog to install the plugin. 27 | 28 | After a successful install the plugin will be added to the QGIS plugins menu and database menu. 29 | 30 | ### Install from a ZIP file 31 | - Get the plugin ZIP file from [https://github.com/samweli/isochrones/releases](https://github.com/samweli/isochrones/releases), 32 | select the required release ZIP file and download it. 33 | - From the `Install from ZIP` page in the QGIS plugin manager, 34 | select the downloaded ZIP file and click the `Install Plugin` button to install it. 35 | 36 | ![Install from zip option](docs/assets/img/examples/install_from_zip.png) 37 | 38 | ### Install from a plugin custom repository 39 | 40 | The plugin is available on a custom QGIS plugin repository that host the plugin most recent versions. 41 | 42 | The plugin from the custom repository can be used to get the latest features that haven't landed on the official released plugin version that 43 | are published in the QGIS official plugin repository. 44 | 45 | The plugin versions available through the custom repository will be flagged experimental. 46 | This is because the custom repository might contain plugin versions that have not been approved yet for official use. 47 | 48 | When updating the plugin manager users should, in order to make sure the plugin manager fetches the experimental plugins 49 | from the custom repository. 50 | 51 | Following the below steps to add the custom repository and install the plugin from it. 52 | 53 | - From the plugin manager enable download of experimental plugins. 54 | 55 | ![Enable experimental plugins](docs/assets/img/examples/enable_experimental_plugins.png) 56 | - Select the `Settings` page from the QGIS plugin manager. 57 | - Click `Add` button on the Plugin Repositories group box and use the plugin custom repository found 58 | here [https://samweli.github.io/isochrones/repository/plugins.xml](https://samweli.github.io/isochrones/repository/plugins.xml) 59 | to create a new plugin repository entry. 60 | 61 | ![Add custom plugin repository](docs/assets/img/examples/add_custom_repository.png) 62 | - Disable the QGIS official plugin repository and go back to the `All` page, search for `Isochrones` and install it from there. 63 | 64 | ### Install from source code 65 | 66 | - Download or clone this repository and add the plugin folder into the QGIS plugin directory. 67 | The plugin directory is found on the QGIS profile folder under `profile_name/plugins`, 68 | eg. `default/plugins`. 69 | 70 | QGIS active profile folder can be found by going to **User Profiles > Open Active Profile Folder** 71 | ![Active Profile folder](docs/assets/img/examples/active_user_folder.png) 72 | 73 | - Restart your Qgis, go to **Plugins -> Manage and Install Plugins** search for `Isochrones` 74 | - Search results will show a result item with `Isochrones` title, toggle the checkbox beside the title to 75 | activate the plugin. 76 | 77 | ### Requirements 78 | 79 | Postgres Database with version 9.5 or above, 80 | with [Postgis](https://postgis.net)(`tested with version 3.1`) and 81 | [pgRouting](https://pgrouting.org) (`tested with version 3.1.3`) 82 | extensions installed. 83 | 84 | 85 | ### How to 86 | 87 | #### Running 88 | 89 | ##### Step 1 90 | - Setup two postgresql tables one for the network and other for the catchments areas. 91 | 92 | - Use test data is found here 93 | [https://github.com/Samweli/isochrones/tree/master/test/data](https://github.com/Samweli/isochrones/tree/master/test/data) 94 | these are shapefiles that you will need to import in your postgresql database as tables. 95 | 96 | 97 | ##### Step 2 98 | 99 | - Make note of the important columns that will be needed when using the plugin, 100 | this includes the unique identifier and geometric column for both tables. 101 | - The network data is required to have a column with a name `cost` and a number type, this will be used 102 | for the path calculations during isochrones creation. 103 | - Open the plugin (it will be on the Database Menu and the QGIS toolbar) and fill the connection details followed by tables details. 104 | - Checking the "Create isochrones map style" will generate tin and contour with a default plugin style. 105 | 106 | 107 | You can watch the demo video here [https://www.youtube.com/watch?v=thBKETlQbqY](https://www.youtube.com/watch?v=thBKETlQbqY) 108 | 109 | #### Data 110 | 111 | The network data before imported in the database should be prepared for routing, information on doing this can be found 112 | here [http://www.bostongis.com/PrinterFriendly.aspx?content_name=pgrouting_osm2po_1](http://www.bostongis.com/PrinterFriendly.aspx?content_name=pgrouting_osm2po_1) 113 | 114 | ## Licence 115 | 116 | Isochrones is a free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 (GPLv2) as published by the Free Software Foundation. 117 | 118 | The full GNU General Public License is available in LICENSE.txt or http://www.gnu.org/licenses/gpl.html 119 | 120 | 121 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | isochrones 5 | A QGIS plugin 6 | This plugin create isochrones maps. 7 | ------------------- 8 | begin : 2016-07-02 9 | copyright : (C) 2016 by Samweli Mwakisambwe 10 | email : smwakisambwe@worldbank.org 11 | git sha : $Format:%H$ 12 | ***************************************************************************/ 13 | /*************************************************************************** 14 | * * 15 | * This program is free software; you can redistribute it and/or modify * 16 | * it under the terms of the GNU General Public License as published by * 17 | * the Free Software Foundation; either version 2 of the License, or * 18 | * (at your option) any later version. * 19 | * * 20 | ***************************************************************************/ 21 | This script initializes the plugin, making it known to QGIS. 22 | """ 23 | from __future__ import absolute_import 24 | import os 25 | import sys 26 | 27 | sys.path.append(os.path.dirname(__file__)) 28 | 29 | sys.path.extend([os.path.abspath(os.path.join( 30 | os.path.dirname(__file__), os.path.pardir))]) 31 | 32 | 33 | # noinspection PyPep8Naming 34 | def classFactory(iface): # pylint: disable=invalid-name 35 | """Load isochrones class from file isochrones. 36 | :param iface: A QGIS interface instance. 37 | :type iface: QgsInterface 38 | """ 39 | from .main import QgisIsochrones 40 | return QgisIsochrones(iface) 41 | -------------------------------------------------------------------------------- /admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ QGIS Templates and Symbology plugin admin operations 3 | """ 4 | 5 | import os 6 | 7 | import configparser 8 | import datetime as dt 9 | import re 10 | import shlex 11 | import shutil 12 | import subprocess 13 | import typing 14 | import zipfile 15 | from dataclasses import dataclass 16 | from functools import lru_cache 17 | from pathlib import Path 18 | 19 | import httpx 20 | import json 21 | import typer 22 | 23 | LOCAL_ROOT_DIR = Path(__file__).parent.resolve() 24 | SRC_NAME = "isochrones" 25 | PACKAGE_NAME = SRC_NAME.replace("_", "") 26 | TEST_FILES = [ 27 | "test", 28 | "test_suite.py", 29 | "docker-compose.yml", 30 | "scripts" 31 | ] 32 | 33 | BUILD_SKIP_FILES = [ 34 | 'test', 35 | 'test_suite.py', 36 | 'docker', 37 | 'admin.py', 38 | 'run-docker-test.sh', 39 | 'config.json', 40 | 'docker-compose.yml', 41 | 'requirements-dev.txt', 42 | 'build', 43 | 'dist', 44 | 'docs', 45 | '.git', 46 | '.github', 47 | '.idea', 48 | '.gitignore', 49 | '__pycache__' 50 | ] 51 | 52 | app = typer.Typer() 53 | 54 | 55 | @dataclass 56 | class GithubRelease: 57 | """ 58 | Class for defining plugin releases details. 59 | """ 60 | pre_release: bool 61 | tag_name: str 62 | url: str 63 | published_at: dt.datetime 64 | 65 | 66 | @app.callback() 67 | def main( 68 | context: typer.Context, 69 | verbose: bool = False, 70 | qgis_profile: str = "default"): 71 | """Performs various development-oriented tasks for this plugin 72 | :param context: Application context 73 | :type context: typer.Context 74 | :param verbose: Boolean value to whether more details should be displayed 75 | :type verbose: bool 76 | :param qgis_profile: QGIS user profile to be used when operating in 77 | QGIS application 78 | :type qgis_profile: str 79 | """ 80 | context.obj = { 81 | "verbose": verbose, 82 | "qgis_profile": qgis_profile, 83 | } 84 | 85 | 86 | @app.command() 87 | def install( 88 | context: typer.Context, 89 | build_src: bool = True 90 | ): 91 | """Deploys plugin to QGIS plugins directory 92 | :param context: Application context 93 | :type context: typer.Context 94 | :param build_src: Whether to build plugin files from source 95 | :type build_src: bool 96 | """ 97 | _log("Uninstalling...", context=context) 98 | uninstall(context) 99 | _log("Building...", context=context) 100 | 101 | built_directory = build(context, clean=True) \ 102 | if build_src else LOCAL_ROOT_DIR 103 | 104 | root_directory = Path.home() / \ 105 | f".local/share/QGIS/QGIS3/profiles/" \ 106 | f"{context.obj['qgis_profile']}" 107 | 108 | base_target_directory = root_directory / "python/plugins" / SRC_NAME 109 | _log(f"Copying built plugin to {base_target_directory}...", context=context) 110 | shutil.copytree(built_directory, base_target_directory) 111 | _log( 112 | f"Installed {str(built_directory)!r}" 113 | f" into {str(base_target_directory)!r}", 114 | context=context) 115 | 116 | 117 | @app.command() 118 | def symlink( 119 | context: typer.Context 120 | ): 121 | """Create a plugin symlink to QGIS plugins directory 122 | :param context: Application context 123 | :type context: typer.Context 124 | """ 125 | 126 | build_path = LOCAL_ROOT_DIR / "build" / SRC_NAME 127 | 128 | root_directory = Path.home() / \ 129 | f".local/share/QGIS/QGIS3/profiles/" \ 130 | f"{context.obj['qgis_profile']}" 131 | 132 | destination_path = root_directory / "python/plugins" / SRC_NAME 133 | 134 | if not os.path.islink(destination_path): 135 | os.symlink(build_path, destination_path) 136 | else: 137 | _log(f"Symlink already exists, skipping creation.", context=context) 138 | 139 | 140 | @app.command() 141 | def uninstall(context: typer.Context): 142 | """Removes the plugin from QGIS plugins directory 143 | :param context: Application context 144 | :type context: typer.Context 145 | """ 146 | root_directory = Path.home() / \ 147 | f".local/share/QGIS/QGIS3/profiles/" \ 148 | f"{context.obj['qgis_profile']}" 149 | base_target_directory = root_directory / "python/plugins" / SRC_NAME 150 | shutil.rmtree(str(base_target_directory), ignore_errors=True) 151 | _log(f"Removed {str(base_target_directory)!r}", context=context) 152 | 153 | 154 | @app.command() 155 | def generate_zip( 156 | context: typer.Context, 157 | output_directory: typing.Optional[Path] = LOCAL_ROOT_DIR / "dist"): 158 | """ Generates plugin zip folder, that can be used to installed the 159 | plugin in QGIS 160 | :param context: Application context 161 | :type context: typer.Context 162 | :param output_directory: Directory where the zip folder will be saved. 163 | :type context: Path 164 | """ 165 | build_dir = build(context) 166 | metadata = _get_metadata() 167 | output_directory.mkdir(parents=True, exist_ok=True) 168 | zip_path = output_directory / f'{SRC_NAME}.{metadata["version"]}.zip' 169 | with zipfile.ZipFile(zip_path, "w") as fh: 170 | _add_to_zip(build_dir, fh, arc_path_base=build_dir.parent) 171 | typer.echo(f"zip generated at {str(zip_path)!r}") 172 | return zip_path 173 | 174 | 175 | @app.command() 176 | def build( 177 | context: typer.Context, 178 | output_directory: typing.Optional[Path] = LOCAL_ROOT_DIR / "build" / SRC_NAME, 179 | clean: bool = True, 180 | tests: bool = False 181 | ) -> Path: 182 | """ Builds plugin directory for use in QGIS application. 183 | :param context: Application context 184 | :type context: typer.Context 185 | :param output_directory: Build output directory plugin where 186 | files will be saved. 187 | :type output_directory: Path 188 | :param clean: Whether current build directory files should be removed, 189 | before writing new files. 190 | :type clean: bool 191 | :param tests: Flag to indicate whether to include test related files. 192 | :type tests: bool 193 | :returns: Build directory path. 194 | :rtype: Path 195 | """ 196 | if clean: 197 | shutil.rmtree(str(output_directory), ignore_errors=True) 198 | output_directory.mkdir(parents=True, exist_ok=True) 199 | copy_source_files(output_directory, tests=tests) 200 | icon_path = copy_icon(output_directory) 201 | if icon_path is None: 202 | _log("Could not copy icon", context=context) 203 | compile_resources(context, output_directory) 204 | generate_metadata(context, output_directory) 205 | return output_directory 206 | 207 | 208 | @app.command() 209 | def copy_source_files( 210 | output_directory: typing.Optional[Path] = LOCAL_ROOT_DIR / "temp", 211 | tests: bool = False 212 | ): 213 | """ Copies the plugin source files to the specified output 214 | directory. 215 | :param output_directory: Output directory where the icon will be saved. 216 | :type output_directory: Path 217 | :param tests: Flag to indicate whether to include test related files. 218 | :type tests: bool 219 | """ 220 | output_directory.mkdir(parents=True, exist_ok=True) 221 | for child in LOCAL_ROOT_DIR.iterdir(): 222 | if child.name not in BUILD_SKIP_FILES: 223 | target_path = output_directory / child.name 224 | handler = shutil.copytree if child.is_dir() else shutil.copy 225 | handler(str(child.resolve()), str(target_path)) 226 | if tests: 227 | for child in LOCAL_ROOT_DIR.iterdir(): 228 | if child.name in TEST_FILES: 229 | target_path = output_directory / child.name 230 | handler = shutil.copytree if child.is_dir() else shutil.copy 231 | handler(str(child.resolve()), str(target_path)) 232 | 233 | 234 | @app.command() 235 | def copy_icon( 236 | output_directory: typing.Optional[Path] = LOCAL_ROOT_DIR / "temp", 237 | ) -> Path: 238 | """ Copies the plugin intended icon to the specified output 239 | directory. 240 | :param output_directory: Output directory where the icon will be saved. 241 | :type output_directory: Path 242 | :returns: Icon output directory path. 243 | :rtype: Path 244 | """ 245 | 246 | metadata = _get_metadata() 247 | icon_path = LOCAL_ROOT_DIR / "resources" / metadata["icon"] 248 | if icon_path.is_file(): 249 | target_path = output_directory / icon_path.name 250 | target_path.parent.mkdir(parents=True, exist_ok=True) 251 | shutil.copy(icon_path, target_path) 252 | result = target_path 253 | else: 254 | result = None 255 | return result 256 | 257 | 258 | @app.command() 259 | def compile_resources( 260 | context: typer.Context, 261 | output_directory: typing.Optional[Path] = LOCAL_ROOT_DIR / "temp", 262 | ): 263 | """ Compiles plugin resources using the pyrcc package 264 | :param context: Application context 265 | :type context: typer.Context 266 | :param output_directory: Output directory where the resources will be saved. 267 | :type output_directory: Path 268 | """ 269 | resources_path = LOCAL_ROOT_DIR / "resources" / "resources.qrc" 270 | target_path = output_directory / "resources.py" 271 | target_path.parent.mkdir(parents=True, exist_ok=True) 272 | _log(f"compile_resources target_path: {target_path}", context=context) 273 | subprocess.run(shlex.split(f"pyrcc5 -o {target_path} {resources_path}")) 274 | 275 | 276 | @app.command() 277 | def generate_metadata( 278 | context: typer.Context, 279 | output_directory: typing.Optional[Path] = LOCAL_ROOT_DIR / "temp", 280 | ): 281 | """ Generates plugin metadata file using settings defined in the 282 | project configuration file 'pyproject.toml' 283 | :param context: Application context 284 | :type context: typer.Context 285 | :param output_directory: Output directory where the metadata.txt file will be saved. 286 | :type output_directory: Path 287 | """ 288 | metadata = _get_metadata() 289 | target_path = output_directory / "metadata.txt" 290 | target_path.parent.mkdir(parents=True, exist_ok=True) 291 | _log(f"generate_metadata target_path: {target_path}", context=context) 292 | config = configparser.ConfigParser() 293 | # do not modify case of parameters, as per 294 | # https://docs.python.org/3/library/configparser.html#customizing-parser-behaviour 295 | config.optionxform = lambda option: option 296 | config["general"] = metadata 297 | with target_path.open(mode="w") as fh: 298 | config.write(fh) 299 | 300 | 301 | @app.command() 302 | def generate_plugin_repo_xml( 303 | context: typer.Context, 304 | ): 305 | """ Generates the plugin repository xml file, from which users 306 | can use to install the plugin in QGIS. 307 | :param context: Application context 308 | :type context: typer.Context 309 | """ 310 | repo_base_dir = LOCAL_ROOT_DIR / "docs" / "repository" 311 | repo_base_dir.mkdir(parents=True, exist_ok=True) 312 | metadata = _get_metadata() 313 | fragment_template = """ 314 | 315 | 316 | 317 | {version} 318 | {qgis_minimum_version} 319 | {qgis_maximum_version} 320 | 321 | {filename} 322 | {icon} 323 | 324 | {download_url} 325 | {update_date} 326 | {experimental} 327 | {deprecated} 328 | 329 | 330 | 331 | False 332 | 333 | """.strip() 334 | contents = "\n" 335 | all_releases = _get_existing_releases(context=context) 336 | _log(f"Found {len(all_releases)} release(s)...", context=context) 337 | for release in [r for r in _get_latest_releases(all_releases) if r is not None]: 338 | tag_name = release.tag_name 339 | _log(f"Processing release {tag_name}...", context=context) 340 | fragment = fragment_template.format( 341 | name=metadata.get("name"), 342 | version=tag_name.replace("v", ""), 343 | description=metadata.get("description"), 344 | about=metadata.get("about"), 345 | qgis_minimum_version=metadata.get("qgisMinimumVersion"), 346 | qgis_maximum_version=metadata.get("qgisMaximumVersion"), 347 | homepage=metadata.get("homepage"), 348 | filename=release.url.rpartition("/")[-1], 349 | icon=metadata.get("icon", ""), 350 | author=metadata.get("author"), 351 | download_url=release.url, 352 | update_date=release.published_at, 353 | experimental=release.pre_release, 354 | deprecated=metadata.get("deprecated"), 355 | tracker=metadata.get("tracker"), 356 | repository=metadata.get("repository"), 357 | tags=metadata.get("tags"), 358 | ) 359 | contents = "\n".join((contents, fragment)) 360 | contents = "\n".join((contents, "")) 361 | repo_index = repo_base_dir / "plugins.xml" 362 | repo_index.write_text(contents, encoding="utf-8") 363 | _log(f"Plugin repo XML file saved at {repo_index}", context=context) 364 | 365 | 366 | @lru_cache() 367 | def _get_metadata() -> typing.Dict: 368 | """ Reads the metadata properties from the 369 | project configuration file 'config.json' 370 | :return: plugin metadata 371 | :type: Dict 372 | """ 373 | config_path = LOCAL_ROOT_DIR / "config.json" 374 | with config_path.open("r") as fh: 375 | conf = json.load(fh) 376 | general_plugin_config = conf["general"] 377 | metadata = general_plugin_config 378 | 379 | metadata.update( 380 | { 381 | "tags": ", ".join(general_plugin_config.get("tags", [])), 382 | "changelog": _changelog(), 383 | } 384 | ) 385 | return metadata 386 | 387 | 388 | def _changelog() -> str: 389 | """ Reads the changelog content from a config file. 390 | :returns: Plugin changelog 391 | :type: str 392 | """ 393 | path = LOCAL_ROOT_DIR / "docs/plugin/changelog.txt" 394 | 395 | with path.open() as fh: 396 | changelog_file = fh.read() 397 | 398 | return changelog_file 399 | 400 | 401 | def _add_to_zip( 402 | directory: Path, 403 | zip_handler: zipfile.ZipFile, 404 | arc_path_base: Path): 405 | """ Adds to files inside the passed directory to the zip file. 406 | :param directory: Directory with files that are to be zipped. 407 | :type directory: Path 408 | :param zip_handler: Plugin zip file 409 | :type zip_handler: ZipFile 410 | :param arc_path_base: Parent directory of the input files directory. 411 | :type arc_path_base: Path 412 | """ 413 | for item in directory.iterdir(): 414 | if item.is_file(): 415 | zip_handler.write(item, arcname=str( 416 | item.relative_to(arc_path_base))) 417 | else: 418 | _add_to_zip(item, zip_handler, arc_path_base) 419 | 420 | 421 | def _log( 422 | msg, 423 | *args, 424 | context: typing.Optional[typer.Context] = None, 425 | **kwargs): 426 | """ Logs the message into the terminal. 427 | :param msg: Directory with files that are to be zipped. 428 | :type msg: str 429 | :param context: Application context 430 | :type context: typer.Context 431 | """ 432 | if context is not None: 433 | context_user_data = context.obj or {} 434 | verbose = context_user_data.get("verbose", True) 435 | else: 436 | verbose = True 437 | if verbose: 438 | typer.echo(msg, *args, **kwargs) 439 | 440 | 441 | def _get_existing_releases( 442 | context: typing.Optional = None, 443 | ) -> typing.List[GithubRelease]: 444 | """ Gets the existing plugin releases in available in the Github repository. 445 | :param context: Application context 446 | :type context: typer.Context 447 | :returns: List of github releases 448 | :rtype: List[GithubRelease] 449 | """ 450 | base_url = "https://api.github.com/repos/" \ 451 | "samweli/isochrones/releases" 452 | response = httpx.get(base_url) 453 | result = [] 454 | if response.status_code == 200: 455 | payload = response.json() 456 | for release in payload: 457 | for asset in release["assets"]: 458 | if asset.get("content_type") == "application/zip": 459 | zip_download_url = asset.get("browser_download_url") 460 | break 461 | else: 462 | zip_download_url = None 463 | _log(f"zip_download_url: {zip_download_url}", context=context) 464 | if zip_download_url is not None: 465 | result.append( 466 | GithubRelease( 467 | pre_release=release.get("prerelease", True), 468 | tag_name=release.get("tag_name"), 469 | url=zip_download_url, 470 | published_at=dt.datetime.strptime( 471 | release["published_at"], "%Y-%m-%dT%H:%M:%SZ" 472 | ), 473 | ) 474 | ) 475 | return result 476 | 477 | 478 | def _get_latest_releases( 479 | current_releases: typing.List[GithubRelease], 480 | ) -> typing.Tuple[ 481 | typing.Optional[GithubRelease], 482 | typing.Optional[GithubRelease]]: 483 | """ Searches for the latest plugin releases from the Github plugin releases. 484 | :param current_releases: Existing plugin releases 485 | available in the Github repository. 486 | :type current_releases: list 487 | :returns: Tuple containing the latest stable and experimental releases 488 | :rtype: tuple 489 | """ 490 | latest_experimental = None 491 | latest_stable = None 492 | for release in current_releases: 493 | if release.pre_release: 494 | if latest_experimental is not None: 495 | if release.published_at > latest_experimental.published_at: 496 | latest_experimental = release 497 | else: 498 | latest_experimental = release 499 | else: 500 | if latest_stable is not None: 501 | if release.published_at > latest_stable.published_at: 502 | latest_stable = release 503 | else: 504 | latest_stable = release 505 | return latest_stable, latest_experimental 506 | 507 | 508 | if __name__ == "__main__": 509 | app() 510 | -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/common/__init__.py -------------------------------------------------------------------------------- /common/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | exception classes 5 | A QGIS plugin 6 | This plugin create isochrones maps. 7 | ------------------- 8 | begin : 2016-07-02 9 | git sha : $Format:%H$ 10 | copyright : (C) 2016 by Samweli Mwakisambwe 11 | email : smwakisambwe@worldbank.org 12 | ***************************************************************************/ 13 | 14 | /*************************************************************************** 15 | * * 16 | * This program is free software; you can redistribute it and/or modify * 17 | * it under the terms of the GNU General Public License as published by * 18 | * the Free Software Foundation; either version 2 of the License, or * 19 | * (at your option) any later version. * 20 | * * 21 | ***************************************************************************/ 22 | """ 23 | 24 | 25 | class IsochroneError(RuntimeError): 26 | """Base class for all user defined exceptions""" 27 | suggestion = 'An unspecified error occurred.' 28 | 29 | 30 | class IsochroneDBError(IsochroneError): 31 | """For Database errors""" 32 | suggestion = 'Check that the input tables and their attributes are right.' 33 | 34 | 35 | class IsochroneMapStyleError(IsochroneError): 36 | """For Map styling exceptions""" 37 | suggestion = 'Check that the processing plugin works right.' 38 | 39 | 40 | class ReadLayerError(IsochroneError): 41 | """When a layer can't be read""" 42 | suggestion = ( 43 | 'Check that the file exists and you have permissions to read it') 44 | 45 | 46 | class WriteLayerError(IsochroneError): 47 | """When a layer can't be written""" 48 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 49 | 50 | 51 | class BoundingBoxError(IsochroneError): 52 | """For errors relating to bboxes""" 53 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 54 | 55 | 56 | class VerificationError(IsochroneError): 57 | """Exception thrown by verify() 58 | """ 59 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 60 | 61 | 62 | class PolygonInputError(IsochroneError): 63 | """For invalid inputs to numeric polygon functions""" 64 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 65 | 66 | 67 | class PointsInputError(IsochroneError): 68 | """For invalid inputs to numeric point functions""" 69 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 70 | 71 | 72 | class BoundsError(IsochroneError): 73 | """For points falling outside interpolation grid""" 74 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 75 | 76 | 77 | class GetDataError(IsochroneError): 78 | """When layer data cannot be obtained""" 79 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 80 | 81 | 82 | class PostProcessorError(IsochroneError): 83 | """Raised when requested import cannot be performed if QGIS is too old.""" 84 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 85 | 86 | 87 | class WindowsError(IsochroneError): 88 | """For windows specific errors.""" 89 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 90 | 91 | 92 | class GridXmlFileNotFoundError(IsochroneError): 93 | """An exception for when an grid.xml could not be found""" 94 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 95 | 96 | 97 | class GridXmlParseError(IsochroneError): 98 | """An exception for when something went wrong parsing the grid.xml """ 99 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 100 | 101 | 102 | class ContourCreationError(IsochroneError): 103 | """An exception for when creating contours from shakemaps goes wrong""" 104 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 105 | 106 | 107 | class InvalidLayerError(IsochroneError): 108 | """Raised when a gis layer is invalid""" 109 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 110 | 111 | 112 | class ShapefileCreationError(IsochroneError): 113 | """Raised if an error occurs creating the cities file""" 114 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 115 | 116 | 117 | class ZeroImpactException(IsochroneError): 118 | """Raised if an impact function return zero impact""" 119 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 120 | 121 | 122 | class WrongDataTypeException(IsochroneError): 123 | """Raised if expected and received data types are different""" 124 | suggestion = 'Please ask the developers of Isochrones to add a suggestion.' 125 | 126 | 127 | class InvalidClipGeometryError(IsochroneError): 128 | """Custom exception for when clip geometry is invalid.""" 129 | pass 130 | 131 | 132 | class FileNotFoundError(IsochroneError): 133 | """Custom exception for when a file could not be found.""" 134 | pass 135 | 136 | 137 | class TestNotImplementedError(IsochroneError): 138 | """Custom exception for when a test exists only as a stub.""" 139 | pass 140 | 141 | 142 | class NoFunctionsFoundError(IsochroneError): 143 | """Custom exception for when a no impact calculation 144 | functions can be found.""" 145 | pass 146 | 147 | 148 | class KeywordDbError(IsochroneError): 149 | """Custom exception for when an error is encountered with keyword cache db. 150 | """ 151 | pass 152 | 153 | 154 | class HashNotFoundError(IsochroneError): 155 | """Custom exception for when a no keyword hash can be found.""" 156 | pass 157 | 158 | 159 | class StyleInfoNotFoundError(IsochroneError): 160 | """Custom exception for when a no styleInfo can be found.""" 161 | pass 162 | 163 | 164 | class InvalidParameterError(IsochroneError): 165 | """Custom exception for when an invalid parameter is passed to a function. 166 | """ 167 | pass 168 | 169 | 170 | class TranslationLoadError(IsochroneError): 171 | """Custom exception handler for whe translation file fails 172 | to load.""" 173 | pass 174 | 175 | 176 | class LegendLayerError(IsochroneError): 177 | """An exception raised when trying to create a legend from 178 | a QgsMapLayer that does not have suitable characteristics to 179 | allow a legend to be created from it.""" 180 | pass 181 | 182 | 183 | class NoFeaturesInExtentError(IsochroneError): 184 | """An exception that gets thrown when no features are within 185 | the extent being clipped.""" 186 | pass 187 | 188 | 189 | class InvalidProjectionError(IsochroneError): 190 | """An exception raised if a layer needs to be reprojected.""" 191 | pass 192 | 193 | 194 | class InsufficientOverlapError(IsochroneError): 195 | """An exception raised if an error occurs during extent calculation 196 | because the bounding boxes do not overlap.""" 197 | pass 198 | 199 | 200 | class StyleError(IsochroneError): 201 | """An exception relating to reading / generating GIS styles""" 202 | pass 203 | 204 | 205 | class MemoryLayerCreationError(IsochroneError): 206 | """Raised if an error occurs creating the cities file""" 207 | pass 208 | 209 | 210 | class MethodUnavailableError(IsochroneError): 211 | """Raised if the requested import cannot be performed dur to qgis being 212 | to old""" 213 | pass 214 | 215 | 216 | class CallGDALError(IsochroneError): 217 | """Raised if failed to call gdal command. Indicate by error message that is 218 | not empty""" 219 | pass 220 | 221 | 222 | class ImportDialogError(IsochroneError): 223 | """Raised if import process failed.""" 224 | pass 225 | 226 | 227 | class FileMissingError(IsochroneError): 228 | """Raised if a file cannot be found.""" 229 | pass 230 | 231 | 232 | class CanceledImportDialogError(IsochroneError): 233 | """Raised if import process canceled""" 234 | pass 235 | 236 | 237 | class HelpFileMissingError(IsochroneError): 238 | """Raised if a help file cannot be found.""" 239 | pass 240 | 241 | 242 | class InvalidGeometryError(IsochroneError): 243 | """Custom exception for when a feature geometry is invalid or none.""" 244 | pass 245 | 246 | 247 | class UnsupportedProviderError(IsochroneError): 248 | """For unsupported provider (e.g. openlayers plugin) encountered.""" 249 | pass 250 | 251 | 252 | class ReportCreationError(IsochroneError): 253 | """Raised when error occurs during report generation.""" 254 | pass 255 | 256 | 257 | class EmptyDirectoryError(IsochroneError): 258 | """Raised when output directory is empty string path.""" 259 | pass 260 | 261 | 262 | class NoValidLayerError(IsochroneError): 263 | """Raised when there no valid layer in Isochrone.""" 264 | pass 265 | 266 | 267 | class InsufficientMemoryWarning(IsochroneError): 268 | """Raised when there is a possible insufficient memory.""" 269 | pass 270 | 271 | 272 | class InvalidExtentError(IsochroneError): 273 | """Raised if an extent is not valid.""" 274 | pass 275 | 276 | 277 | class NoAttributeInLayerError(IsochroneError): 278 | """Raised if the attribute not exists in the vector layer""" 279 | pass 280 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "name" : "QGIS Isochrones", 4 | "qgisMinimumVersion": 3.0, 5 | "qgisMaximumVersion": 3.99, 6 | "icon": "resources/img/icons/icon.png", 7 | "experimental": false, 8 | "deprecated": false, 9 | "version": "0.1.2", 10 | "changelog": "", 11 | "homepage": "https://github.com/Samweli/isochrones", 12 | "tracker": "https://github.com/Samweli/isochrones/issues", 13 | "repository": "https://github.com/Samweli/isochrones", 14 | "tags": [ "map", "database", "routing", "accessibility", "time", "urban planning" ], 15 | "category": [ "plugins" ], 16 | "hasProcessingProvider": "no", 17 | "about": "Enables users to easily create isochrone maps from network and catchment data", 18 | "author": "Samweli Mwakisambwe", 19 | "email": "smwltwesa6@gmail.com", 20 | "description": "Automates isochrone map creation in Qgis" 21 | } 22 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | services: 3 | qgis-testing-environment: 4 | image: ${IMAGE}:${QGIS_VERSION_TAG} 5 | volumes: 6 | - ./:/tests_directory:ro 7 | environment: 8 | QGIS_VERSION_TAG: "${QGIS_VERSION_TAG}" 9 | WITH_PYTHON_PEP: "${WITH_PYTHON_PEP}" 10 | ON_TRAVIS: "${ON_TRAVIS}" 11 | MUTE_LOGS: "${MUTE_LOGS}" 12 | DISPLAY: ":99" 13 | working_dir: /tests_directory 14 | entrypoint: /tests_directory/scripts/docker/qgis-testing-entrypoint.sh 15 | # Enable "command:" line below to immediately run unittests upon docker-compose up 16 | # command: qgis_testrunner.sh test_suite.test_package 17 | # Default behaviour of the container is to standby 18 | command: tail -f /dev/null 19 | # qgis_testrunner.sh needs tty for tee 20 | tty: true 21 | -------------------------------------------------------------------------------- /docs/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/favicon-16x16.png -------------------------------------------------------------------------------- /docs/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/favicon-32x32.png -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/img/examples/active_user_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/active_user_folder.png -------------------------------------------------------------------------------- /docs/assets/img/examples/add_custom_repository.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/add_custom_repository.png -------------------------------------------------------------------------------- /docs/assets/img/examples/enable_experimental_plugins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/enable_experimental_plugins.png -------------------------------------------------------------------------------- /docs/assets/img/examples/install_from_zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/install_from_zip.png -------------------------------------------------------------------------------- /docs/assets/img/examples/isochrone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/isochrone.png -------------------------------------------------------------------------------- /docs/assets/img/examples/isochrones_design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/isochrones_design.png -------------------------------------------------------------------------------- /docs/assets/img/examples/isochrones_map_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/isochrones_map_.png -------------------------------------------------------------------------------- /docs/assets/img/examples/isochrones_map_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/isochrones_map_up.png -------------------------------------------------------------------------------- /docs/assets/img/examples/isochrones_plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/isochrones_plugin.png -------------------------------------------------------------------------------- /docs/assets/img/examples/isochrones_plugin_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/img/examples/isochrones_plugin_result.png -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # QGIS Isochrones plugin 2 | 3 | QGIS plugin that enables creation of isochrones maps using PostGIS database. -------------------------------------------------------------------------------- /docs/plugin/changelog.txt: -------------------------------------------------------------------------------- 1 | Version 0.1.2 2 | - Support for Postgresql version 15, PostGIS 3.1 and pgRouting 3.1.3 3 | - Main UI components update 4 | - Fix for bugs in the plugin database queries 5 | - Updates to plugin documentation 6 | - Added support for QGIS 3.2x versions 7 | - New plugin modules structure 8 | 9 | Version 0.1.1 10 | - Updated plugin from QGIS API version 2 to 3 and from PyQt4 to PyQt5 11 | 12 | Version 0.1 13 | - Generation of isochrones data 14 | - Add default style for isochrone map 15 | - Fix unit tests bugs -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/gui/__init__.py -------------------------------------------------------------------------------- /gui/qgis_isochrone_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | isochronesDialog 5 | A QGIS plugin 6 | This plugin create isochrones maps. 7 | ------------------- 8 | begin : 2016-07-02 9 | git sha : $Format:%H$ 10 | copyright : (C) 2016 by Samweli Mwakisambwe 11 | email : smwakisambwe@worldbank.org 12 | ***************************************************************************/ 13 | 14 | /*************************************************************************** 15 | * * 16 | * This program is free software; you can redistribute it and/or modify * 17 | * it under the terms of the GNU General Public License as published by * 18 | * the Free Software Foundation; either version 2 of the License, or * 19 | * (at your option) any later version. * 20 | * * 21 | ***************************************************************************/ 22 | """ 23 | 24 | import os 25 | 26 | from qgis.core import Qgis 27 | 28 | # noinspection PyPackageRequirements 29 | from qgis.PyQt import QtWidgets 30 | # noinspection PyPackageRequirements 31 | from qgis.PyQt.QtCore import QSettings, QFileInfo 32 | # noinspection PyPackageRequirements 33 | from qgis.PyQt.QtWidgets import QDialog 34 | from qgis.PyQt.uic import loadUiType 35 | 36 | from common.exceptions import ( 37 | ImportDialogError, 38 | FileMissingError) 39 | 40 | from iso.utils import display_warning_message_box 41 | from iso.base import isochrone 42 | 43 | FORM_CLASS, _ = loadUiType( 44 | os.path.join(os.path.dirname(__file__), '../ui/isochrone_dialog_base.ui') 45 | ) 46 | 47 | 48 | class QgisIsochronesDialog(QtWidgets.QDialog, FORM_CLASS): 49 | 50 | def __init__(self, parent=None, iface=None): 51 | """Constructor.""" 52 | QDialog.__init__(self, parent) 53 | # Set up the user interface from Designer. 54 | # After setupUI you can access any designer object by doing 55 | # self., and you can use autoconnect slots - see 56 | # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html 57 | # #widgets-and-dialogs-with-auto-connect 58 | self.parent = parent 59 | self.iface = iface 60 | 61 | self.setupUi(self) 62 | 63 | self.restore_state() 64 | 65 | self.canvas = iface.mapCanvas() if iface else None 66 | 67 | def accept(self): 68 | """Create an isochrone map and display it in QGIS.""" 69 | 70 | try: 71 | self.save_state() 72 | self.require_input() 73 | 74 | database_name = self.database.text() 75 | host_name = self.host.text() 76 | port_number = self.port.text() 77 | user_name = self.user_name.text() 78 | password = self.password.text() 79 | network_table = self.network_table.text() 80 | network_geom = self.network_geom_column.text() 81 | network_id_column = self.network_id_column.text() 82 | catchment_geom = self.catchment_geom_column.text() 83 | catchment_table = self.catchment_table.text() 84 | catchment_id_column = self.catchment_id_column.text() 85 | contour_interval = self.contour_interval.text() 86 | 87 | if self.style.isChecked(): 88 | style_checked = True 89 | else: 90 | style_checked = False 91 | 92 | isochrone( 93 | database_name, 94 | host_name, 95 | port_number, 96 | user_name, 97 | password, 98 | network_table, 99 | network_geom, 100 | network_id_column, 101 | catchment_table, 102 | catchment_geom, 103 | catchment_id_column, 104 | style_checked, 105 | contour_interval, 106 | self, 107 | None) 108 | 109 | self.done(QDialog.Accepted) 110 | 111 | except ImportDialogError as exception: 112 | error_dialog_title = self.tr("Error") 113 | 114 | display_warning_message_box( 115 | self, error_dialog_title, str(exception)) 116 | pass 117 | except Exception as exception: # pylint: disable=broad-except 118 | # noinspection PyCallByClass,PyTypeChecker,PyArgumentList 119 | error_dialog_title = self.tr("Error") 120 | display_warning_message_box( 121 | self, error_dialog_title, str(exception)) 122 | pass 123 | finally: 124 | dialog_title = self.tr("Success") 125 | 126 | def require_input(self): 127 | """Ensure input files are entered in dialog exist. 128 | 129 | :raises: ImportDialogError - when one or all 130 | of the input files are empty 131 | """ 132 | database_name = self.database.text() 133 | host_name = self.host.text() 134 | port_number = self.port.text() 135 | user_name = self.user_name.text() 136 | password = self.password.text() 137 | network_table = self.network_table.text() 138 | catchment_table = self.catchment_table.text() 139 | contour_interval = self.contour_interval.text() 140 | 141 | if database_name and host_name and port_number and \ 142 | user_name and password and network_table and \ 143 | catchment_table: 144 | return 145 | 146 | display_warning_message_box( 147 | self, 148 | self.tr('Error'), 149 | self.tr('Input cannot be empty.')) 150 | 151 | raise ImportDialogError() 152 | 153 | def restore_state(self): 154 | """ Read last state of GUI from configuration file.""" 155 | settings = QSettings() 156 | try: 157 | database_name = settings.value('database', type=str) 158 | host_name = settings.value('host', type=str) 159 | port_number = settings.value('port', type=str) 160 | user_name = settings.value('user_name', type=str) 161 | network_table = settings.value('network_table', type=str) 162 | network_geom_column = settings.value( 163 | 'network_geom_column', 164 | type=str) 165 | network_id_column = settings.value('network_id_column', type=str) 166 | catchment_table = settings.value('catchment_table', type=str) 167 | catchment_geom_column = settings.value( 168 | 'catchment_geom_column', 169 | type=str) 170 | catchment_id_column = settings.value( 171 | 'catchment_id_column', 172 | type=str) 173 | contour_interval = settings.value( 174 | 'contour_interval', 175 | type=str) 176 | 177 | except TypeError: 178 | database_name = '' 179 | host_name = '' 180 | port_number = '' 181 | user_name = '' 182 | network_table = '' 183 | network_geom_column = '' 184 | network_id_column = '' 185 | catchment_table = '' 186 | catchment_geom_column = '' 187 | catchment_id_column = '' 188 | contour_interval = '' 189 | 190 | self.database.setText(database_name) 191 | self.host.setText(host_name) 192 | self.port.setText(port_number) 193 | self.user_name.setText(user_name) 194 | self.network_table.setText(network_table) 195 | self.network_geom_column.setText(network_geom_column) 196 | self.network_id_column.setText(network_id_column) 197 | self.catchment_table.setText(catchment_table) 198 | self.catchment_geom_column.setText(catchment_geom_column) 199 | self.catchment_id_column.setText(catchment_id_column) 200 | self.contour_interval.setText(contour_interval) 201 | 202 | def save_state(self): 203 | """ Store current state of GUI to configuration file """ 204 | settings = QSettings() 205 | 206 | settings.setValue('database', self.database.text()) 207 | settings.setValue('host', self.host.text()) 208 | settings.setValue('port', self.port.text()) 209 | settings.setValue('user_name', self.user_name.text()) 210 | settings.setValue('network_table', self.network_table.text()) 211 | settings.setValue( 212 | 'network_geom_column', 213 | self.network_geom_column.text()) 214 | settings.setValue( 215 | 'network_id_column', 216 | self.network_id_column.text()) 217 | settings.setValue('catchment_table', self.catchment_table.text()) 218 | settings.setValue( 219 | 'catchment_geom_column', 220 | self.catchment_geom_column.text()) 221 | settings.setValue( 222 | 'catchment_id_column', 223 | self.catchment_id_column.text()) 224 | settings.setValue( 225 | 'contour_interval', 226 | self.contour_interval.text()) 227 | 228 | def reject(self): 229 | """Redefinition of the reject() method 230 | """ 231 | super(QgisIsochronesDialog, self).reject() 232 | 233 | def load_isochrone_map(self, base_path): 234 | """Load the isochrone map in the qgis 235 | 236 | :param base_path: Output path where layers are 237 | :type base_path:str 238 | """ 239 | 240 | if not os.path.exists(base_path): 241 | message = self.tr("Error, failed to load the isochrone map") 242 | raise FileMissingError(message) 243 | else: 244 | for layer in os.listdir(base_path): 245 | layer_name = QFileInfo(layer).baseName 246 | 247 | if layer.endswith(".asc"): 248 | self.iface.addRasterLayer(layer, layer_name) 249 | continue 250 | elif layer.endswith(".shp"): 251 | self.iface.addVectorLayer(layer, layer_name, 'ogr') 252 | continue 253 | else: 254 | continue 255 | canvas_srid = self.canvas.mapRenderer().destinationCrs().srsid() 256 | on_the_fly_projection = self.canvas.hasCrsTransformEnabled() 257 | if canvas_srid != 4326 and not on_the_fly_projection: 258 | if Qgis.QGIS_VERSION_INT >= 20400: 259 | self.canvas.setCrsTransformEnabled(True) 260 | else: 261 | display_warning_message_box( 262 | self.iface, 263 | self.tr('Enable \'on the fly\''), 264 | self.tr( 265 | 'Your current projection is different than EPSG:4326.' 266 | 'You should enable \'on the fly\' to display ' 267 | 'correctly the isochrone map') 268 | ) 269 | -------------------------------------------------------------------------------- /help/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) source 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/template_class.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/template_class.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/template_class" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/template_class" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /help/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | echo. 46 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 47 | goto end 48 | ) 49 | 50 | if "%1" == "dirhtml" ( 51 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 52 | echo. 53 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 54 | goto end 55 | ) 56 | 57 | if "%1" == "singlehtml" ( 58 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 59 | echo. 60 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 61 | goto end 62 | ) 63 | 64 | if "%1" == "pickle" ( 65 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 66 | echo. 67 | echo.Build finished; now you can process the pickle files. 68 | goto end 69 | ) 70 | 71 | if "%1" == "json" ( 72 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 73 | echo. 74 | echo.Build finished; now you can process the JSON files. 75 | goto end 76 | ) 77 | 78 | if "%1" == "htmlhelp" ( 79 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 80 | echo. 81 | echo.Build finished; now you can run HTML Help Workshop with the ^ 82 | .hhp project file in %BUILDDIR%/htmlhelp. 83 | goto end 84 | ) 85 | 86 | if "%1" == "qthelp" ( 87 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 88 | echo. 89 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 90 | .qhcp project file in %BUILDDIR%/qthelp, like this: 91 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\template_class.qhcp 92 | echo.To view the help file: 93 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\template_class.ghc 94 | goto end 95 | ) 96 | 97 | if "%1" == "devhelp" ( 98 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 99 | echo. 100 | echo.Build finished. 101 | goto end 102 | ) 103 | 104 | if "%1" == "epub" ( 105 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 106 | echo. 107 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 108 | goto end 109 | ) 110 | 111 | if "%1" == "latex" ( 112 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 113 | echo. 114 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 115 | goto end 116 | ) 117 | 118 | if "%1" == "text" ( 119 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 120 | echo. 121 | echo.Build finished. The text files are in %BUILDDIR%/text. 122 | goto end 123 | ) 124 | 125 | if "%1" == "man" ( 126 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 127 | echo. 128 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 129 | goto end 130 | ) 131 | 132 | if "%1" == "changes" ( 133 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 134 | echo. 135 | echo.The overview file is in %BUILDDIR%/changes. 136 | goto end 137 | ) 138 | 139 | if "%1" == "linkcheck" ( 140 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 141 | echo. 142 | echo.Link check complete; look for any errors in the above output ^ 143 | or in %BUILDDIR%/linkcheck/output.txt. 144 | goto end 145 | ) 146 | 147 | if "%1" == "doctest" ( 148 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 149 | echo. 150 | echo.Testing of doctests in the sources finished, look at the ^ 151 | results in %BUILDDIR%/doctest/output.txt. 152 | goto end 153 | ) 154 | 155 | :end 156 | -------------------------------------------------------------------------------- /help/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # isochrones documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Feb 12 17:11:03 2012. 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.todo', 'sphinx.ext.pngmath', 'sphinx.ext.viewcode'] 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'isochrones' 44 | copyright = u'2013, Samweli Mwakisambwe' 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.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.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 = [] 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_TemplateModuleNames = 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 = 'TemplateClassdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | # The paper size ('letter' or 'a4'). 173 | #latex_paper_size = 'letter' 174 | 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #latex_font_size = '10pt' 177 | 178 | # Grouping the document tree into LaTeX files. List of tuples 179 | # (source start file, target name, title, author, documentclass [howto/manual]). 180 | latex_documents = [ 181 | ('index', 'isochrones.tex', u'isochrones Documentation', 182 | u'Samweli Mwakisambwe', 'manual'), 183 | ] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | #latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | #latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | #latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | #latex_show_urls = False 198 | 199 | # Additional stuff for the LaTeX preamble. 200 | #latex_preamble = '' 201 | 202 | # Documents to append as an appendix to all manuals. 203 | #latex_appendices = [] 204 | 205 | # If false, no module index is generated. 206 | #latex_domain_indices = True 207 | 208 | 209 | # -- Options for manual page output -------------------------------------------- 210 | 211 | # One entry per manual page. List of tuples 212 | # (source start file, name, description, authors, manual section). 213 | man_pages = [ 214 | ('index', 'TemplateClass', u'isochrones Documentation', 215 | [u'Samweli Mwakisambwe'], 1) 216 | ] 217 | -------------------------------------------------------------------------------- /help/source/index.rst: -------------------------------------------------------------------------------- 1 | .. isochrones documentation master file, created by 2 | sphinx-quickstart on Sun Feb 12 17:11:03 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to isochrones's documentation! 7 | ============================================ 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | Indices and tables 15 | ================== 16 | 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | 21 | -------------------------------------------------------------------------------- /i18n/af.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /iso/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/iso/__init__.py -------------------------------------------------------------------------------- /iso/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # Utility functions that are specific to the plugin logic. 3 | import os 4 | 5 | from qgis.core import Qgis, QgsMessageLog 6 | import qgis # pylint: disable=unused-import 7 | from qgis.PyQt import QtCore, uic 8 | 9 | from builtins import str 10 | from qgis.PyQt.QtCore import QCoreApplication 11 | 12 | from qgis.PyQt.QtWidgets import QMessageBox 13 | 14 | import sys 15 | 16 | QGIS_APP = None 17 | CANVAS = None 18 | PARENT = None 19 | IFACE = None 20 | 21 | 22 | def tr(text): 23 | """ 24 | :param text: String to be translated 25 | :type text: str, unicode 26 | 27 | :returns: Translated version of the given string if available, otherwise 28 | the original string. 29 | :rtype: str, unicode 30 | """ 31 | # Ensure it's in unicode 32 | text = get_unicode(text) 33 | # noinspection PyCallByClass,PyTypeChecker,PyArgumentList 34 | return QCoreApplication.translate('@default', text) 35 | 36 | 37 | def __if_not_basestring(text_object): 38 | converted_str = text_object 39 | if not isinstance(text_object, str): 40 | converted_str = str(text_object) 41 | return converted_str 42 | 43 | 44 | def get_unicode(input_text, encoding='utf-8'): 45 | """Get the unicode representation of an object. 46 | 47 | :param input_text: The input text. 48 | :type input_text: unicode, str, float, int 49 | 50 | :param encoding: The encoding used to do the conversion, default to utf-8. 51 | :type encoding: str 52 | 53 | :returns: Unicode representation of the input. 54 | :rtype: unicode 55 | """ 56 | input_text = __if_not_basestring(input_text) 57 | if isinstance(input_text, str): 58 | return input_text 59 | return str(input_text, encoding, errors='ignore') 60 | 61 | 62 | def resources_path(*args): 63 | """Get the path to our resources folder. 64 | 65 | :param args List of path elements e.g. ['img', 66 | 'examples', 'isochrone.png'] 67 | :type args: list 68 | 69 | :return: Absolute path to the resources folder. 70 | :rtype: str 71 | """ 72 | path = os.path.dirname(__file__) 73 | path = os.path.abspath( 74 | os.path.join(path, os.path.pardir, 'resources')) 75 | for item in args: 76 | path = os.path.abspath(os.path.join(path, item)) 77 | 78 | return path 79 | 80 | def log( 81 | message: str, 82 | name: str = "qgis_isochrones", 83 | info: bool = True, 84 | notify: bool = True, 85 | ): 86 | """ Logs the message into QGIS logs using qgis_stac as the default 87 | log instance. 88 | If notify_user is True, user will be notified about the log. 89 | :param message: The log message 90 | :type message: str 91 | :param name: Name of te log instance, qgis_stac is the default 92 | :type message: str 93 | :param info: Whether the message is about info or a 94 | warning 95 | :type info: bool 96 | :param notify: Whether to notify user about the log 97 | :type notify: bool 98 | """ 99 | level = Qgis.Info if info else Qgis.Warning 100 | QgsMessageLog.logMessage( 101 | message, 102 | name, 103 | level=level, 104 | notifyUser=notify, 105 | ) 106 | 107 | 108 | def display_information_message_box( 109 | parent=None, title=None, message=None): 110 | """ 111 | Display an information message box. 112 | 113 | :param title: The title of the message box. 114 | :type title: str 115 | 116 | :param message: The message inside the message box. 117 | :type message: str 118 | """ 119 | QMessageBox.information(parent, title, message) 120 | 121 | 122 | def display_warning_message_box(parent=None, title=None, message=None): 123 | """ 124 | Display a warning message box. 125 | 126 | :param title: The title of the message box. 127 | :type title: str 128 | 129 | :param message: The message inside the message box. 130 | :type message: str 131 | """ 132 | QMessageBox.warning(parent, title, message) 133 | 134 | 135 | def display_critical_message_box(parent=None, title=None, message=None): 136 | """ 137 | Display a critical message box. 138 | 139 | :param title: The title of the message box. 140 | :type title: str 141 | 142 | :param message: The message inside the message box. 143 | :type message: str 144 | """ 145 | QMessageBox.critical(parent, title, message) 146 | 147 | 148 | def get_qgis_app(): 149 | """ Start one QGIS application to test against. 150 | 151 | :returns: Handle to QGIS app, canvas, iface and parent. If there are any 152 | errors the tuple members will be returned as None. 153 | :rtype: (QgsApplication, CANVAS, IFload_standard_layersACE, PARENT) 154 | 155 | If QGIS is already running the handle to that app will be returned. 156 | """ 157 | 158 | try: 159 | from qgis.core import QgsApplication 160 | from qgis.gui import QgsMapCanvas # pylint: disable=no-name-in-module 161 | # noinspection PyPackageRequirements 162 | from qgis.PyQt import QtGui, QtCore # pylint: disable=W0621 163 | # noinspection PyPackageRequirements 164 | from qgis.PyQt.QtCore import QCoreApplication, QSettings 165 | from qgis.gui import QgisInterface 166 | except ImportError: 167 | return None, None, None, None 168 | 169 | global QGIS_APP # pylint: disable=W0603 170 | 171 | if QGIS_APP is None: 172 | gui_flag = True # All test will run qgis in gui mode 173 | 174 | # AG: For testing purposes, we use our own configuration file instead 175 | # of using the QGIS apps conf of the host 176 | # noinspection PyCallByClass,PyArgumentList 177 | QCoreApplication.setOrganizationName('QGIS') 178 | # noinspection PyCallByClass,PyArgumentList 179 | QCoreApplication.setOrganizationDomain('qgis.org') 180 | # noinspection PyCallByClass,PyArgumentList 181 | QCoreApplication.setApplicationName('IsochronesTesting') 182 | 183 | # noinspection PyPep8Naming 184 | QGIS_APP = QgsApplication(sys.argv, gui_flag) 185 | 186 | # Make sure QGIS_PREFIX_PATH is set in your env if needed! 187 | QGIS_APP.initQgis() 188 | s = QGIS_APP.showSettings() 189 | 190 | # Save some settings 191 | settings = QSettings() 192 | settings.setValue('locale/overrideFlag', True) 193 | settings.setValue('locale/userLocale', 'en_US') 194 | # We disabled message bars for now for extent selector as 195 | # we don't have a main window to show them in TS - version 3.2 196 | 197 | global PARENT # pylint: disable=W0603 198 | if PARENT is None: 199 | # noinspection PyPep8Naming 200 | PARENT = QtGui.QWidget() 201 | 202 | global CANVAS # pylint: disable=W0603 203 | if CANVAS is None: 204 | # noinspection PyPep8Naming 205 | CANVAS = QgsMapCanvas(PARENT) 206 | CANVAS.resize(QtCore.QSize(400, 400)) 207 | 208 | global IFACE # pylint: disable=W0603 209 | if IFACE is None: 210 | # QgisInterface is a stub implementation of the QGIS plugin interface 211 | # noinspection PyPep8Naming 212 | IFACE = QgisInterface(CANVAS) 213 | 214 | return QGIS_APP, CANVAS, IFACE, PARENT 215 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | /*************************************************************************** 4 | isochrones 5 | A QGIS plugin 6 | This plugin create isochrones maps. 7 | ------------------- 8 | begin : 2016-07-02 9 | git sha : $Format:%H$ 10 | copyright : (C) 2016 by Samweli Mwakisambwe 11 | email : smwakisambwe@worldbank.org 12 | ***************************************************************************/ 13 | 14 | /*************************************************************************** 15 | * * 16 | * This program is free software; you can redistribute it and/or modify * 17 | * it under the terms of the GNU General Public License as published by * 18 | * the Free Software Foundation; either version 2 of the License, or * 19 | * (at your option) any later version. * 20 | * * 21 | ***************************************************************************/ 22 | """ 23 | 24 | from builtins import object 25 | from qgis.PyQt.QtCore import QSettings, QTranslator, qVersion, QCoreApplication 26 | from qgis.PyQt.QtWidgets import QAction 27 | from qgis.PyQt.QtGui import QIcon 28 | # Initialize Qt resources from file resources.py 29 | # Import the code for the dialog 30 | from gui.qgis_isochrone_dialog import QgisIsochronesDialog 31 | from .iso.utils import resources_path 32 | import os.path 33 | 34 | 35 | class QgisIsochrones(object): 36 | """QGIS Plugin Implementation.""" 37 | 38 | def __init__(self, iface): 39 | """Constructor. 40 | 41 | :param iface: An interface instance that will be passed to this class 42 | which provides the hook by which you can manipulate the QGIS 43 | application at run time. 44 | :type iface: QgsInterface 45 | """ 46 | # Save reference to the QGIS interface 47 | self.iface = iface 48 | # initialize plugin directory 49 | self.plugin_dir = os.path.dirname(__file__) 50 | # initialize locale 51 | locale = QSettings().value('locale/userLocale')[0:2] 52 | locale_path = os.path.join( 53 | self.plugin_dir, 54 | 'i18n', 55 | 'isochrones_{}.qm'.format(locale)) 56 | 57 | if os.path.exists(locale_path): 58 | self.translator = QTranslator() 59 | self.translator.load(locale_path) 60 | 61 | if qVersion() > '4.3.3': 62 | QCoreApplication.installTranslator(self.translator) 63 | 64 | # Create the dialog (after translation) and keep reference 65 | self.dlg = QgisIsochronesDialog(self.iface.mainWindow(), self.iface) 66 | 67 | # Declare instance attributes 68 | self.actions = [] 69 | self.menu = self.tr(u'&Isochrones') 70 | # TODO: We are going to let the user set this up in a future iteration 71 | self.toolbar = self.iface.addToolBar(u'isochrones') 72 | self.toolbar.setObjectName(u'isochrones') 73 | 74 | # noinspection PyMethodMayBeStatic 75 | def tr(self, message): 76 | """Get the translation for a string using Qt translation API. 77 | 78 | We implement this ourselves since we do not inherit QObject. 79 | 80 | :param message: String for translation. 81 | :type message: str, QString 82 | 83 | :returns: Translated version of message. 84 | :rtype: QString 85 | """ 86 | # noinspection PyTypeChecker,PyArgumentList,PyCallByClass 87 | return QCoreApplication.translate('isochrones', message) 88 | 89 | def add_action( 90 | self, 91 | icon_path, 92 | text, 93 | callback, 94 | enabled_flag=True, 95 | add_to_menu=True, 96 | add_to_database_menu=True, 97 | add_to_toolbar=True, 98 | status_tip=None, 99 | whats_this=None, 100 | parent=None): 101 | 102 | """Add a toolbar icon to the toolbar. 103 | 104 | :param icon_path: Path to the icon for this action. Can be a resource 105 | path (e.g. ':/plugins/foo/bar.png') or a normal file system path. 106 | :type icon_path: str 107 | 108 | :param text: Text that should be shown in menu items for this action. 109 | :type text: str 110 | 111 | :param callback: Function to be called when the action is triggered. 112 | :type callback: function 113 | 114 | :param enabled_flag: A flag indicating if the action should be enabled 115 | by default. Defaults to True. 116 | :type enabled_flag: bool 117 | 118 | :param add_to_menu: Flag indicating whether the action should also 119 | be added to the menu. Defaults to True. 120 | :type add_to_menu: bool 121 | 122 | :param add_to_database_menu: Flag indicating whether the action 123 | should also be added to the database menu. Defaults to True. 124 | :type add_to_database_menu: bool 125 | 126 | :param add_to_toolbar: Flag indicating whether the action should also 127 | be added to the toolbar. Defaults to True. 128 | :type add_to_toolbar: bool 129 | 130 | :param status_tip: Optional text to show in a popup when mouse pointer 131 | hovers over the action. 132 | :type status_tip: str 133 | 134 | :param parent: Parent widget for the new action. Defaults None. 135 | :type parent: QWidget 136 | 137 | :param whats_this: Optional text to show in the status bar when the 138 | mouse pointer hovers over the action. 139 | 140 | :returns: The action that was created. Note that the action is also 141 | added to self.actions list. 142 | :rtype: QAction 143 | """ 144 | 145 | icon = QIcon(icon_path) 146 | action = QAction(icon, text, parent) 147 | action.triggered.connect(callback) 148 | action.setEnabled(enabled_flag) 149 | 150 | if status_tip is not None: 151 | action.setStatusTip(status_tip) 152 | 153 | if whats_this is not None: 154 | action.setWhatsThis(whats_this) 155 | 156 | if add_to_toolbar: 157 | self.toolbar.addAction(action) 158 | 159 | if add_to_menu: 160 | self.iface.addPluginToMenu( 161 | self.menu, 162 | action) 163 | if add_to_menu: 164 | self.iface.addPluginToMenu( 165 | self.menu, 166 | action) 167 | if add_to_database_menu: 168 | self.iface.addPluginToDatabaseMenu( 169 | self.menu, 170 | action) 171 | 172 | self.actions.append(action) 173 | 174 | return action 175 | 176 | def initGui(self): 177 | """Create the menu entries and toolbar icons inside the QGIS GUI.""" 178 | 179 | # icon_path = ':/plugins/isochrones/icon.png' 180 | icon_path = resources_path('img', 'icons', 'icon.png') 181 | self.add_action( 182 | icon_path, 183 | text=self.tr(u'Create isochrone map'), 184 | callback=self.run, 185 | parent=self.iface.mainWindow(), 186 | add_to_menu=False, 187 | add_to_database_menu=True, 188 | status_tip="Create isochrone map", 189 | whats_this="Create isochrone map") 190 | 191 | def unload(self): 192 | """Removes the plugin menu item and icon from QGIS GUI.""" 193 | for action in self.actions: 194 | self.iface.removePluginMenu( 195 | self.tr(u'&Isochrones'), 196 | action) 197 | self.iface.removeToolBarIcon(action) 198 | # remove the toolbar 199 | del self.toolbar 200 | 201 | def run(self): 202 | """Run method that performs all the real work""" 203 | # show the dialog 204 | self.dlg.show() 205 | # Run the dialog event loop 206 | result = self.dlg.exec_() 207 | # See if OK was pressed 208 | if result: 209 | # Do something useful here - delete the line containing pass and 210 | # substitute with your code. 211 | pass 212 | -------------------------------------------------------------------------------- /metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=Isochrones 3 | qgisMinimumVersion=3.0 4 | 5 | description=Automates isochrone map creation in Qgis. 6 | about=Enables users to easily create isochrone maps from network and catchment data. 7 | 8 | version=0.1.1 9 | 10 | author=Samweli Mwakisambwe 11 | email=smwltwesa6@gmail.com 12 | 13 | # End of mandatory metadata 14 | 15 | # Recommended items: 16 | 17 | # Uncomment the following line and add your changelog: 18 | # changelog= 0.1 Improvement - Updated how to documentation 19 | 20 | changelog= 21 | Version 0.1.2 22 | - Support for Postgresql version 15, PostGIS 3.1 and pgRouting 3.1.3 23 | - Main UI components update 24 | - Fix for bugs in the plugin database queries 25 | - Updates to plugin documentation 26 | - Added support for QGIS 3.2x versions 27 | - New plugin modules structure 28 | Version 0.1.1 29 | - Updated plugin from QGIS API version 2 to 3 and from PyQt4 to PyQt5 30 | 31 | Version 0.1 32 | - Generation of isochrone data 33 | - Add default style for isochrone map 34 | - Fix unit tests bugs 35 | 36 | # Tags are comma separated with spaces allowed 37 | tags=map, database, routing, accessibility, time, urban planning 38 | 39 | homepage=https://github.com/Samweli/isochrones 40 | tracker=https://github.com/Samweli/isochrones/issues 41 | repository=https://github.com/Samweli/isochrones 42 | icon=resources/img/icons/icon.png 43 | 44 | # experimental flag 45 | experimental=False 46 | 47 | # deprecated flag (applies to the whole plugin, not just a single version) 48 | deprecated=False 49 | 50 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: QGIS Isochrones Plugin 2 | 3 | repo_url: https://github.com/samweli/isochrones 4 | repo_name: GitHub 5 | 6 | site_description: QGIS Isochrones Plugin 7 | 8 | site_author: Samweli Mwakisambwe 9 | 10 | nav: 11 | - Home: 'index.md' 12 | 13 | plugins: 14 | - search 15 | 16 | markdown_extensions: 17 | - pymdownx.snippets: 18 | base_path: . 19 | - meta: 20 | 21 | extra_css: 22 | - stylesheets/extra.css 23 | 24 | theme: 25 | name: material 26 | custom_dir: docs 27 | palette: 28 | - scheme: stac 29 | toggle: 30 | icon: material/toggle-switch-off-outline 31 | name: Switch to dark mode 32 | - scheme: slate 33 | toggle: 34 | icon: material/toggle-switch 35 | name: Switch to light mode 36 | accent: amber 37 | logo: assets/logo.png 38 | favicon: assets/favicon.ico 39 | features: 40 | - navigation.instant 41 | - navigation.tabs 42 | - navigation.tabs.sticky -------------------------------------------------------------------------------- /pb_tool.cfg: -------------------------------------------------------------------------------- 1 | #/*************************************************************************** 2 | # isochrones 3 | # 4 | # Configuration file for plugin builder tool (pb_tool) 5 | # ------------------- 6 | # begin : 2016-07-02 7 | # copyright : (C) 2016 by Samweli Mwakisambwe 8 | # email : smwakisambwe@worldbank.org 9 | # ***************************************************************************/ 10 | # 11 | #/*************************************************************************** 12 | # * * 13 | # * This program is free software; you can redistribute it and/or modify * 14 | # * it under the terms of the GNU General Public License as published by * 15 | # * the Free Software Foundation; either version 2 of the License, or * 16 | # * (at your option) any later version. * 17 | # * * 18 | # ***************************************************************************/ 19 | # 20 | # 21 | # You can install pb_tool using: 22 | # pip install http://geoapt.net/files/pb_tool.zip 23 | # 24 | # Consider doing your development (and install of pb_tool) in a virtualenv. 25 | # 26 | # For details on setting up and using pb_tool, see: 27 | # http://spatialgalaxy.net/qgis-plugin-development-with-pb_tool 28 | # 29 | # Issues and pull requests here: 30 | # https://github.com/g-sherman/plugin_build_tool: 31 | # 32 | # Sane defaults for your plugin generated by the Plugin Builder are 33 | # already set below. 34 | # 35 | # As you add Python source files and UI files to your plugin, add 36 | # them to the appropriate [files] section below. 37 | 38 | [plugin] 39 | # Name of the plugin. This is the name of the directory that will 40 | # be created in .qgis2/python/plugins 41 | name: isochrones 42 | 43 | [files] 44 | # Python files that should be deployed with the plugin 45 | python_files: __init__.py isochrone.py isochrone_dialog.py 46 | 47 | # The main dialog file that is loaded (not compiled) 48 | main_dialog: isochrone_dialog_base.ui 49 | 50 | # Other ui files for dialogs you create (these will be compiled) 51 | compiled_ui_files: 52 | 53 | # Resource file(s) that will be compiled 54 | resource_files: resources.qrc 55 | 56 | # Other files required for the plugin 57 | extras: metadata.txt icon.png 58 | 59 | # Other directories to be deployed with the plugin. 60 | # These must be subdirectories under the plugin directory 61 | extra_dirs: 62 | 63 | # ISO code(s) for any locales (translations), separated by spaces. 64 | # Corresponding .ts files must exist in the i18n directory 65 | locales: 66 | 67 | [help] 68 | # the built help directory that should be deployed with the plugin 69 | dir: help/build/html 70 | # the name of the directory to target in the deployed plugin 71 | target: help 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /plugin_upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | """This script uploads a plugin package on the server. 4 | Authors: A. Pasotti, V. Picavet 5 | git sha : $TemplateVCSFormat 6 | """ 7 | from __future__ import print_function 8 | 9 | from future import standard_library 10 | standard_library.install_aliases() 11 | from builtins import input 12 | import sys 13 | import getpass 14 | import xmlrpc.client 15 | from optparse import OptionParser 16 | 17 | # Configuration 18 | PROTOCOL = 'http' 19 | SERVER = 'plugins.qgis.org' 20 | PORT = '80' 21 | ENDPOINT = '/plugins/RPC2/' 22 | VERBOSE = False 23 | 24 | 25 | def main(parameters, arguments): 26 | """Main entry point. 27 | 28 | :param parameters: Command line parameters. 29 | :param arguments: Command line arguments. 30 | """ 31 | address = "%s://%s:%s@%s:%s%s" % ( 32 | PROTOCOL, 33 | parameters.username, 34 | parameters.password, 35 | parameters.server, 36 | parameters.port, 37 | ENDPOINT) 38 | # fix_print_with_import 39 | print("Connecting to: %s" % hide_password(address)) 40 | 41 | server = xmlrpc.client.ServerProxy(address, verbose=VERBOSE) 42 | 43 | try: 44 | plugin_id, version_id = server.plugin.upload( 45 | xmlrpc.client.Binary(open(arguments[0]).read())) 46 | # fix_print_with_import 47 | print("Plugin ID: %s" % plugin_id) 48 | # fix_print_with_import 49 | print("Version ID: %s" % version_id) 50 | except xmlrpc.client.ProtocolError as err: 51 | # fix_print_with_import 52 | print("A protocol error occurred") 53 | # fix_print_with_import 54 | print("URL: %s" % hide_password(err.url, 0)) 55 | # fix_print_with_import 56 | print("HTTP/HTTPS headers: %s" % err.headers) 57 | # fix_print_with_import 58 | print("Error code: %d" % err.errcode) 59 | # fix_print_with_import 60 | print("Error message: %s" % err.errmsg) 61 | except xmlrpc.client.Fault as err: 62 | # fix_print_with_import 63 | print("A fault occurred") 64 | # fix_print_with_import 65 | print("Fault code: %d" % err.faultCode) 66 | # fix_print_with_import 67 | print("Fault string: %s" % err.faultString) 68 | 69 | 70 | def hide_password(url, start=6): 71 | """Returns the http url with password part replaced with '*'. 72 | 73 | :param url: URL to upload the plugin to. 74 | :type url: str 75 | 76 | :param start: Position of start of password. 77 | :type start: int 78 | """ 79 | start_position = url.find(':', start) + 1 80 | end_position = url.find('@') 81 | return "%s%s%s" % ( 82 | url[:start_position], 83 | '*' * (end_position - start_position), 84 | url[end_position:]) 85 | 86 | 87 | if __name__ == "__main__": 88 | parser = OptionParser(usage="%prog [options] plugin.zip") 89 | parser.add_option( 90 | "-w", "--password", dest="password", 91 | help="Password for plugin site", metavar="******") 92 | parser.add_option( 93 | "-u", "--username", dest="username", 94 | help="Username of plugin site", metavar="user") 95 | parser.add_option( 96 | "-p", "--port", dest="port", 97 | help="Server port to connect to", metavar="80") 98 | parser.add_option( 99 | "-s", "--server", dest="server", 100 | help="Specify server name", metavar="plugins.qgis.org") 101 | options, args = parser.parse_args() 102 | if len(args) != 1: 103 | # fix_print_with_import 104 | print("Please specify zip file.\n") 105 | parser.print_help() 106 | sys.exit(1) 107 | if not options.server: 108 | options.server = SERVER 109 | if not options.port: 110 | options.port = PORT 111 | if not options.username: 112 | # interactive mode 113 | username = getpass.getuser() 114 | # fix_print_with_import 115 | print("Please enter user name [%s] :" % username, end=' ') 116 | res = input() 117 | if res != "": 118 | options.username = res 119 | else: 120 | options.username = username 121 | if not options.password: 122 | # interactive mode 123 | options.password = getpass.getpass() 124 | main(options, args) 125 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | 25 | [MESSAGES CONTROL] 26 | 27 | # Enable the message, report, category or checker with the given id(s). You can 28 | # either give multiple identifier separated by comma (,) or put this option 29 | # multiple time. See also the "--disable" option for examples. 30 | #enable= 31 | 32 | # Disable the message, report, category or checker with the given id(s). You 33 | # can either give multiple identifiers separated by comma (,) or put this 34 | # option multiple times (only on the command line, not in the configuration 35 | # file where it should appear only once).You can also use "--disable=all" to 36 | # disable everything first and then reenable specific checks. For example, if 37 | # you want to run only the similarities checker, you can use "--disable=all 38 | # --enable=similarities". If you want to run only the classes checker, but have 39 | # no Warning level messages displayed, use"--disable=all --enable=classes 40 | # --disable=W" 41 | # see http://stackoverflow.com/questions/21487025/pylint-locally-defined-disables-still-give-warnings-how-to-suppress-them 42 | disable=locally-disabled,C0103 43 | 44 | 45 | [REPORTS] 46 | 47 | # Set the output format. Available formats are text, parseable, colorized, msvs 48 | # (visual studio) and html. You can also give a reporter class, eg 49 | # mypackage.mymodule.MyReporterClass. 50 | output-format=text 51 | 52 | # Put messages in a separate file for each module / package specified on the 53 | # command line instead of printing them on stdout. Reports (if any) will be 54 | # written in a file name "pylint_global.[txt|html]". 55 | files-output=no 56 | 57 | # Tells whether to display a full report or only the messages 58 | reports=yes 59 | 60 | # Python expression which should return a note less than 10 (10 is the highest 61 | # note). You have access to the variables errors warning, statement which 62 | # respectively contain the number of errors / warnings messages and the total 63 | # number of statements analyzed. This is used by the global evaluation report 64 | # (RP0004). 65 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 66 | 67 | # Add a comment according to your evaluation note. This is used by the global 68 | # evaluation report (RP0004). 69 | comment=no 70 | 71 | # Template used to display messages. This is a python new-style format string 72 | # used to format the message information. See doc for all details 73 | #msg-template= 74 | 75 | 76 | [BASIC] 77 | 78 | # Required attributes for module, separated by a comma 79 | required-attributes= 80 | 81 | # List of builtins function names that should not be used, separated by a comma 82 | bad-functions=map,filter,apply,input 83 | 84 | # Regular expression which should only match correct module names 85 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 86 | 87 | # Regular expression which should only match correct module level names 88 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 89 | 90 | # Regular expression which should only match correct class names 91 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 92 | 93 | # Regular expression which should only match correct function names 94 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 95 | 96 | # Regular expression which should only match correct method names 97 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 98 | 99 | # Regular expression which should only match correct instance attribute names 100 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 101 | 102 | # Regular expression which should only match correct argument names 103 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 104 | 105 | # Regular expression which should only match correct variable names 106 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 107 | 108 | # Regular expression which should only match correct attribute names in class 109 | # bodies 110 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 111 | 112 | # Regular expression which should only match correct list comprehension / 113 | # generator expression variable names 114 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 115 | 116 | # Good variable names which should always be accepted, separated by a comma 117 | good-names=i,j,k,ex,Run,_ 118 | 119 | # Bad variable names which should always be refused, separated by a comma 120 | bad-names=foo,bar,baz,toto,tutu,tata 121 | 122 | # Regular expression which should only match function or class names that do 123 | # not require a docstring. 124 | no-docstring-rgx=__.*__ 125 | 126 | # Minimum line length for functions/classes that require docstrings, shorter 127 | # ones are exempt. 128 | docstring-min-length=-1 129 | 130 | 131 | [MISCELLANEOUS] 132 | 133 | # List of note tags to take in consideration, separated by a comma. 134 | notes=FIXME,XXX,TODO 135 | 136 | 137 | [TYPECHECK] 138 | 139 | # Tells whether missing members accessed in mixin class should be ignored. A 140 | # mixin class is detected if its name ends with "mixin" (case insensitive). 141 | ignore-mixin-members=yes 142 | 143 | # List of classes names for which member attributes should not be checked 144 | # (useful for classes with attributes dynamically set). 145 | ignored-classes=SQLObject 146 | 147 | # When zope mode is activated, add a predefined set of Zope acquired attributes 148 | # to generated-members. 149 | zope=no 150 | 151 | # List of members which are set dynamically and missed by pylint inference 152 | # system, and so shouldn't trigger E0201 when accessed. Python regular 153 | # expressions are accepted. 154 | generated-members=REQUEST,acl_users,aq_parent 155 | 156 | 157 | [VARIABLES] 158 | 159 | # Tells whether we should check for unused import in __init__ files. 160 | init-import=no 161 | 162 | # A regular expression matching the beginning of the name of dummy variables 163 | # (i.e. not used). 164 | dummy-variables-rgx=_$|dummy 165 | 166 | # List of additional names supposed to be defined in builtins. Remember that 167 | # you should avoid to define new builtins when possible. 168 | additional-builtins= 169 | 170 | 171 | [FORMAT] 172 | 173 | # Maximum number of characters on a single line. 174 | max-line-length=80 175 | 176 | # Regexp for a line that is allowed to be longer than the limit. 177 | ignore-long-lines=^\s*(# )??$ 178 | 179 | # Allow the body of an if to be on the same line as the test if there is no 180 | # else. 181 | single-line-if-stmt=no 182 | 183 | # List of optional constructs for which whitespace checking is disabled 184 | no-space-check=trailing-comma,dict-separator 185 | 186 | # Maximum number of lines in a module 187 | max-module-lines=1000 188 | 189 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 190 | # tab). 191 | indent-string=' ' 192 | 193 | 194 | [SIMILARITIES] 195 | 196 | # Minimum lines number of a similarity. 197 | min-similarity-lines=4 198 | 199 | # Ignore comments when computing similarities. 200 | ignore-comments=yes 201 | 202 | # Ignore docstrings when computing similarities. 203 | ignore-docstrings=yes 204 | 205 | # Ignore imports when computing similarities. 206 | ignore-imports=no 207 | 208 | 209 | [IMPORTS] 210 | 211 | # Deprecated modules which should not be used, separated by a comma 212 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 213 | 214 | # Create a graph of every (i.e. internal and external) dependencies in the 215 | # given file (report RP0402 must not be disabled) 216 | import-graph= 217 | 218 | # Create a graph of external dependencies in the given file (report RP0402 must 219 | # not be disabled) 220 | ext-import-graph= 221 | 222 | # Create a graph of internal dependencies in the given file (report RP0402 must 223 | # not be disabled) 224 | int-import-graph= 225 | 226 | 227 | [DESIGN] 228 | 229 | # Maximum number of arguments for function / method 230 | max-args=5 231 | 232 | # Argument names that match this expression will be ignored. Default to name 233 | # with leading underscore 234 | ignored-argument-names=_.* 235 | 236 | # Maximum number of locals for function / method body 237 | max-locals=15 238 | 239 | # Maximum number of return / yield for function / method body 240 | max-returns=6 241 | 242 | # Maximum number of branch for function / method body 243 | max-branches=12 244 | 245 | # Maximum number of statements in function / method body 246 | max-statements=50 247 | 248 | # Maximum number of parents for a class (see R0901). 249 | max-parents=7 250 | 251 | # Maximum number of attributes for a class (see R0902). 252 | max-attributes=7 253 | 254 | # Minimum number of public methods for a class (see R0903). 255 | min-public-methods=2 256 | 257 | # Maximum number of public methods for a class (see R0904). 258 | max-public-methods=20 259 | 260 | [CLASSES] 261 | 262 | # List of interface methods to ignore, separated by a comma. This is used for 263 | # instance to not check methods defines in Zope's Interface base class. 264 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 265 | 266 | # List of method names used to declare (i.e. assign) instance attributes. 267 | defining-attr-methods=__init__,__new__,setUp 268 | 269 | # List of valid names for the first argument in a class method. 270 | valid-classmethod-first-arg=cls 271 | 272 | # List of valid names for the first argument in a metaclass class method. 273 | valid-metaclass-classmethod-first-arg=mcs 274 | 275 | 276 | [EXCEPTIONS] 277 | 278 | # Exceptions that will emit a warning when being caught. Defaults to 279 | # "Exception" 280 | overgeneral-exceptions=Exception 281 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | # list of required packages 4 | 5 | anyio == 3.5.0 6 | babel == 2.9.1 7 | certifi == 2021.10.8 8 | charset-normalizer == 2.0.10 9 | click == 8.0.3 10 | colorama == 0.4.4 11 | flask == 2.0.2 12 | ghp-import == 2.0.2 13 | gitdb == 4.0.9 14 | gitpython == 3.1.26 15 | httpx == 0.23.0 16 | idna == 3.3 17 | importlib-metadata == 4.10.1 18 | itsdangerous == 2.0.1 19 | jinja2 == 3.0.3 20 | markdown == 3.3.6 21 | markupsafe == 2.0.1 22 | mergedeep == 1.3.4 23 | mkdocs-git-revision-date-localized-plugin == 0.11.1 24 | mkdocs-material-extensions == 1.0.3 25 | mkdocs-material == 8.1.7 26 | mkdocs-video == 1.1.0 27 | mkdocs == 1.2.3 28 | packaging == 21.3 29 | pygments == 2.11.2 30 | pymdown-extensions == 9.1 31 | pyparsing == 3.0.6 32 | pyqt5-qt5 == 5.15.2 33 | pyqt5-sip == 12.9.0 34 | pyqt5 == 5.15.6 35 | python-dateutil == 2.8.2 36 | pytz == 2021.3 37 | pyyaml-env-tag == 0.1 38 | pyyaml == 6.0 39 | rfc3986 == 1.5.0 40 | six == 1.16.0 41 | smmap == 5.0.0 42 | sniffio == 1.2.0 43 | toml == 0.10.2 44 | typer == 0.4.0 45 | watchdog == 2.1.6 46 | werkzeug == 2.0.2 47 | zipp == 3.7.0 -------------------------------------------------------------------------------- /resources/img/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/resources/img/icons/icon.png -------------------------------------------------------------------------------- /resources/img/icons/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/resources/img/icons/icon2.png -------------------------------------------------------------------------------- /resources/img/icons/icon2.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.13, written by Peter Selinger 2001-2015 9 | 10 | 12 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /resources/styles/qgis/catchment.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 0 183 | 0 184 | 0 185 | ID 186 | 187 | 188 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | . 214 | 215 | 216 | 217 | . 218 | 219 | 0 220 | 221 | 238 | 0 239 | generatedlayout 240 | 241 | 242 | 243 | 244 | 245 | 0 246 | 247 | -------------------------------------------------------------------------------- /resources/styles/qgis/drivetimes.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 0 201 | 0 202 | 0 203 | ID 204 | 205 | 206 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | . 232 | 233 | 234 | 235 | . 236 | 237 | 0 238 | 239 | 256 | 0 257 | generatedlayout 258 | 259 | 260 | 261 | 262 | 263 | 1 264 | 265 | -------------------------------------------------------------------------------- /resources/styles/qgis/iso.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | -------------------------------------------------------------------------------- /resources/styles/qgis/map.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 1 6 | 1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | MinMax 19 | WholeRaster 20 | Estimated 21 | 0.02 22 | 0.98 23 | 2 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 48 | -------------------------------------------------------------------------------- /resources/styles/qgis/network.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 0 249 | 0 250 | 19 251 | osm_name 252 | 253 | 254 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | . 280 | 281 | 282 | 283 | . 284 | 285 | 0 286 | 287 | 304 | 0 305 | generatedlayout 306 | 307 | 308 | 309 | 310 | 311 | 1 312 | 313 | -------------------------------------------------------------------------------- /run-docker-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | QGIS_IMAGE=qgis/qgis 4 | 5 | QGIS_VERSION_TAG=latest 6 | 7 | export ON_TRAVIS=false 8 | export MUTE_LOGS=true 9 | export WITH_PYTHON_PEP=true 10 | export QGIS_VERSION_TAG=latest 11 | export IMAGE=qgis/qgis 12 | 13 | DISPLAY=${DISPLAY:-:99} 14 | 15 | if [ "${DISPLAY}" != ":99" ]; then 16 | xhost + 17 | fi 18 | 19 | IMAGES=($QGIS_IMAGE) 20 | 21 | 22 | for IMAGE in "${IMAGES[@]}" 23 | do 24 | echo "Running tests for $IMAGE" 25 | docker-compose up -d 26 | 27 | sleep 10 28 | 29 | # Run the real test 30 | time docker-compose exec -T qgis-testing-environment sh -c "qgis_testrunner.sh test_suite.test_package" 31 | 32 | done 33 | 34 | 35 | -------------------------------------------------------------------------------- /scripts/compile-strings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | LRELEASE=$1 3 | LOCALES=$2 4 | 5 | 6 | for LOCALE in ${LOCALES} 7 | do 8 | echo "Processing: ${LOCALE}.ts" 9 | # Note we don't use pylupdate with qt .pro file approach as it is flakey 10 | # about what is made available. 11 | $LRELEASE i18n/${LOCALE}.ts 12 | done 13 | -------------------------------------------------------------------------------- /scripts/docker/qgis-isochrones-test-pre-scripts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | qgis_setup.sh 4 | 5 | # FIX default installation because the sources must be in "isochrones" parent folder 6 | rm -rf /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/isochrones 7 | ln -sf /tests_directory /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/isochrones 8 | ln -sf /tests_directory /usr/share/qgis/python/plugins/isochrones 9 | -------------------------------------------------------------------------------- /scripts/docker/qgis-testing-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Docker entrypoint file intended for docker-compose recipe for running unittests 4 | 5 | set -e 6 | 7 | source /tests_directory/scripts/docker/qgis-isochrones-test-pre-scripts.sh 8 | 9 | # Run supervisor 10 | # This is the default command of qgis/qgis but we will run it in background 11 | supervisord -c /etc/supervisor/supervisord.conf & 12 | 13 | # Wait for XVFB 14 | sleep 10 15 | 16 | exec "$@" 17 | -------------------------------------------------------------------------------- /scripts/run-env-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | QGIS_PREFIX_PATH=/usr/local/qgis-2.0 4 | if [ -n "$1" ]; then 5 | QGIS_PREFIX_PATH=$1 6 | fi 7 | 8 | echo ${QGIS_PREFIX_PATH} 9 | 10 | 11 | export QGIS_PREFIX_PATH=${QGIS_PREFIX_PATH} 12 | export QGIS_PATH=${QGIS_PREFIX_PATH} 13 | export LD_LIBRARY_PATH=${QGIS_PREFIX_PATH}/lib 14 | export PYTHONPATH=${QGIS_PREFIX_PATH}/share/qgis/python:${QGIS_PREFIX_PATH}/share/qgis/python/plugins:${PYTHONPATH} 15 | 16 | echo "QGIS PATH: $QGIS_PREFIX_PATH" 17 | export QGIS_DEBUG=0 18 | export QGIS_LOG_FILE=/tmp/isochrones/realtime/logs/qgis.log 19 | 20 | export PATH=${QGIS_PREFIX_PATH}/bin:$PATH 21 | 22 | echo "This script is intended to be sourced to set up your shell to" 23 | echo "use a QGIS 2.0 built in $QGIS_PREFIX_PATH" 24 | echo 25 | echo "To use it do:" 26 | echo "source $BASH_SOURCE /your/optional/install/path" 27 | echo 28 | echo "Then use the make file supplied here e.g. make guitest" 29 | -------------------------------------------------------------------------------- /scripts/update-strings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | LOCALES=$* 3 | 4 | # Get newest .py files so we don't update strings unnecessarily 5 | 6 | CHANGED_FILES=0 7 | PYTHON_FILES=`find . -regex ".*\(ui\|py\)$" -type f` 8 | for PYTHON_FILE in $PYTHON_FILES 9 | do 10 | CHANGED=$(stat -c %Y $PYTHON_FILE) 11 | if [ ${CHANGED} -gt ${CHANGED_FILES} ] 12 | then 13 | CHANGED_FILES=${CHANGED} 14 | fi 15 | done 16 | 17 | # Qt translation stuff 18 | # for .ts file 19 | UPDATE=false 20 | for LOCALE in ${LOCALES} 21 | do 22 | TRANSLATION_FILE="i18n/$LOCALE.ts" 23 | if [ ! -f ${TRANSLATION_FILE} ] 24 | then 25 | # Force translation string collection as we have a new language file 26 | touch ${TRANSLATION_FILE} 27 | UPDATE=true 28 | break 29 | fi 30 | 31 | MODIFICATION_TIME=$(stat -c %Y ${TRANSLATION_FILE}) 32 | if [ ${CHANGED_FILES} -gt ${MODIFICATION_TIME} ] 33 | then 34 | # Force translation string collection as a .py file has been updated 35 | UPDATE=true 36 | break 37 | fi 38 | done 39 | 40 | if [ ${UPDATE} == true ] 41 | # retrieve all python files 42 | then 43 | print ${PYTHON_FILES} 44 | # update .ts 45 | echo "Please provide translations by editing the translation files below:" 46 | for LOCALE in ${LOCALES} 47 | do 48 | echo "i18n/"${LOCALE}".ts" 49 | # Note we don't use pylupdate with qt .pro file approach as it is flakey 50 | # about what is made available. 51 | pylupdate4 -noobsolete ${PYTHON_FILES} -ts i18n/${LOCALE}.ts 52 | done 53 | else 54 | echo "No need to edit any translation files (.ts) because no python files" 55 | echo "has been updated since the last update translation. " 56 | fi 57 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | # import qgis libs so that ve set the correct sip api version 2 | import qgis # pylint: disable=W0611 # NOQA 3 | -------------------------------------------------------------------------------- /test/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/__init__.py -------------------------------------------------------------------------------- /test/data/catchment/drivetime_layer.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /test/data/catchment/drivetime_layer.dbf: -------------------------------------------------------------------------------- 1 | _aIDNtimesN  0 3.000 1 2.000 2 1.000 3 4.000 4 1.000 5 2.000 6 2.000 7 2.000 8 1.000 9 5.000 10 1.000 11 1.000 12 1.000 13 1.000 14 2.000 15 5.000 16 3.000 17 4.000 -------------------------------------------------------------------------------- /test/data/catchment/drivetime_layer.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/data/catchment/drivetime_layer.qpj: -------------------------------------------------------------------------------- 1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 2 | -------------------------------------------------------------------------------- /test/data/catchment/drivetime_layer.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/drivetime_layer.shp -------------------------------------------------------------------------------- /test/data/catchment/drivetime_layer.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/drivetime_layer.shx -------------------------------------------------------------------------------- /test/data/catchment/hospitals.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /test/data/catchment/hospitals.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/hospitals.dbf -------------------------------------------------------------------------------- /test/data/catchment/hospitals.licence: -------------------------------------------------------------------------------- 1 | OpenStreetMap is open data, licensed under the Open Data Commons Open 2 | Database License (ODbL). 3 | 4 | You are free to copy, distribute, transmit and adapt our data, as long as you 5 | credit OpenStreetMap and its contributors. If you alter or build upon our data, 6 | you may distribute the result only under the same licence. The full legal code 7 | explains your rights and responsibilities. 8 | 9 | The cartography in our map tiles, and our documentation, are licensed under the 10 | Creative Commons Attribution-ShareAlike 2.0 license (CC-BY-SA). 11 | 12 | Please visit http://www.openstreetmap.org/copyright for more information. -------------------------------------------------------------------------------- /test/data/catchment/hospitals.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/data/catchment/hospitals.qpj: -------------------------------------------------------------------------------- 1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 2 | -------------------------------------------------------------------------------- /test/data/catchment/hospitals.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/hospitals.shp -------------------------------------------------------------------------------- /test/data/catchment/hospitals.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/hospitals.shx -------------------------------------------------------------------------------- /test/data/catchment/isochrones.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /test/data/catchment/isochrones.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/isochrones.dbf -------------------------------------------------------------------------------- /test/data/catchment/isochrones.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/data/catchment/isochrones.qpj: -------------------------------------------------------------------------------- 1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 2 | -------------------------------------------------------------------------------- /test/data/catchment/isochrones.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/isochrones.shp -------------------------------------------------------------------------------- /test/data/catchment/isochrones.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/isochrones.shx -------------------------------------------------------------------------------- /test/data/catchment/raster_iso.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/catchment/raster_iso.tif -------------------------------------------------------------------------------- /test/data/hospitals.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /test/data/hospitals.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/hospitals.dbf -------------------------------------------------------------------------------- /test/data/hospitals.licence: -------------------------------------------------------------------------------- 1 | OpenStreetMap is open data, licensed under the Open Data Commons Open 2 | Database License (ODbL). 3 | 4 | You are free to copy, distribute, transmit and adapt our data, as long as you 5 | credit OpenStreetMap and its contributors. If you alter or build upon our data, 6 | you may distribute the result only under the same licence. The full legal code 7 | explains your rights and responsibilities. 8 | 9 | The cartography in our map tiles, and our documentation, are licensed under the 10 | Creative Commons Attribution-ShareAlike 2.0 license (CC-BY-SA). 11 | 12 | Please visit http://www.openstreetmap.org/copyright for more information. -------------------------------------------------------------------------------- /test/data/hospitals.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/data/hospitals.qpj: -------------------------------------------------------------------------------- 1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 2 | -------------------------------------------------------------------------------- /test/data/hospitals.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/hospitals.shp -------------------------------------------------------------------------------- /test/data/hospitals.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/hospitals.shx -------------------------------------------------------------------------------- /test/data/network/roads.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /test/data/network/roads.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/network/roads.dbf -------------------------------------------------------------------------------- /test/data/network/roads.license: -------------------------------------------------------------------------------- 1 | OpenStreetMap is open data, licensed under the Open Data Commons Open 2 | Database License (ODbL). 3 | 4 | You are free to copy, distribute, transmit and adapt our data, as long as you 5 | credit OpenStreetMap and its contributors. If you alter or build upon our data, 6 | you may distribute the result only under the same licence. The full legal code 7 | explains your rights and responsibilities. 8 | 9 | The cartography in our map tiles, and our documentation, are licensed under the 10 | Creative Commons Attribution-ShareAlike 2.0 license (CC-BY-SA). 11 | 12 | Please visit http://www.openstreetmap.org/copyright for more information. 13 | -------------------------------------------------------------------------------- /test/data/network/roads.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/data/network/roads.qpj: -------------------------------------------------------------------------------- 1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 2 | -------------------------------------------------------------------------------- /test/data/network/roads.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/network/roads.shp -------------------------------------------------------------------------------- /test/data/network/roads.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/data/network/roads.shx -------------------------------------------------------------------------------- /test/qgis_interface.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/qgis_interface.py -------------------------------------------------------------------------------- /test/test_init.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/test_init.py -------------------------------------------------------------------------------- /test/test_isochrone_dialog.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Dialog test. 3 | 4 | .. note:: This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | """ 10 | from __future__ import absolute_import 11 | 12 | __author__ = 'smwakisambwe@worldbank.org' 13 | __date__ = '2016-07-02' 14 | __copyright__ = 'Copyright 2016, Samweli Mwakisambwe' 15 | 16 | import unittest 17 | 18 | from isochrones.gui.qgis_isochrone_dialog import QgisIsochronesDialog 19 | from isochrones.test.utilities import get_qgis_app 20 | QGIS_APP = get_qgis_app() 21 | 22 | from qgis.PyQt.QtWidgets import QDialogButtonBox, QDialog 23 | 24 | 25 | class IsochronesDialogTest(unittest.TestCase): 26 | """Test dialog works.""" 27 | 28 | def setUp(self): 29 | """Runs before each test.""" 30 | self.dialog = QgisIsochronesDialog(None) 31 | 32 | def tearDown(self): 33 | """Runs after each test.""" 34 | self.dialog = None 35 | 36 | # TODO find out why the test below hangs and doesn't exit. 37 | # def test_dialog_ok(self): 38 | # """Test we can click OK.""" 39 | # 40 | # button = self.dialog.button_box.button(QDialogButtonBox.Ok) 41 | # button.click() 42 | # result = self.dialog.result() 43 | # self.assertEqual(result, QDialog.Accepted) 44 | 45 | def test_dialog_cancel(self): 46 | """Test we can click cancel.""" 47 | button = self.dialog.button_box.button(QDialogButtonBox.Cancel) 48 | button.click() 49 | result = self.dialog.result() 50 | self.assertEqual(result, QDialog.Rejected) 51 | 52 | 53 | if __name__ == "__main__": 54 | suite = unittest.makeSuite(IsochronesDialogTest) 55 | runner = unittest.TextTestRunner(verbosity=2) 56 | runner.run(suite) 57 | -------------------------------------------------------------------------------- /test/test_qgis_environment.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/test/test_qgis_environment.py -------------------------------------------------------------------------------- /test/test_resources.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Resources test. 3 | 4 | .. note:: This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | """ 10 | 11 | __author__ = 'smwakisambwe@worldbank.org' 12 | __date__ = '2016-07-02' 13 | __copyright__ = 'Copyright 2016, Samweli Mwakisambwe' 14 | 15 | import unittest 16 | 17 | from qgis.PyQt.QtGui import QIcon 18 | 19 | 20 | class IsochronesResourcesTest(unittest.TestCase): 21 | """Test resources work.""" 22 | 23 | def setUp(self): 24 | """Runs before each test.""" 25 | pass 26 | 27 | def tearDown(self): 28 | """Runs after each test.""" 29 | pass 30 | 31 | def test_icon_png(self): 32 | """Test we can click OK.""" 33 | path = ':/plugins/isochrones/icon.png' 34 | icon = QIcon(path) 35 | self.assertFalse(icon.isNull()) 36 | 37 | if __name__ == "__main__": 38 | suite = unittest.makeSuite(isochronesResourcesTest) 39 | runner = unittest.TextTestRunner(verbosity=2) 40 | runner.run(suite) 41 | -------------------------------------------------------------------------------- /test/test_utilities.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Tests for utilities.""" 3 | 4 | import unittest 5 | import os 6 | 7 | from isochrones.test.utilities import get_qgis_app 8 | 9 | from qgis.core import * 10 | 11 | QGIS_APP, CANVAS, IFACE, PARENT = get_qgis_app() 12 | 13 | from isochrones.iso.base import \ 14 | idw_interpolation, \ 15 | generate_drivetimes_contour 16 | 17 | 18 | class UtilitiesTest(unittest.TestCase): 19 | """Tests for writing and reading isochrone map 20 | """ 21 | 22 | def test_idw_interpolation(self): 23 | """ Test layer interpolation. """ 24 | 25 | testdir = os.path.abspath(os.path.join( 26 | os.path.realpath(os.path.dirname(__file__)))) 27 | 28 | testdata = os.path.join(testdir, 'data') 29 | catchment_path = os.path.join(testdata, 'catchment') 30 | 31 | layer_path = os.path.join(catchment_path, 'isochrones.shp') 32 | vector_layer = QgsVectorLayer(layer_path, 'isochrones', 'ogr') 33 | 34 | file = idw_interpolation(vector_layer, None) 35 | 36 | # Assert if file is a raster layer 37 | self.assertEqual( 38 | file.type(), QgsMapLayer.RasterLayer) 39 | 40 | self.assertEqual( 41 | file.isValid(), True) 42 | 43 | self.assertEqual( 44 | file.bandCount(), 1) 45 | 46 | def test_generate_drivetimes_contour(self): 47 | """ Test drivetimes generation. """ 48 | 49 | testdir = os.path.abspath(os.path.join( 50 | os.path.realpath(os.path.dirname(__file__)))) 51 | 52 | testdata = os.path.join(testdir, 'data') 53 | catchment_path = os.path.join(testdata, 'catchment') 54 | 55 | layer_path = os.path.join(catchment_path, 'raster_iso.tif') 56 | raster_layer = QgsRasterLayer(layer_path, 'raster_iso') 57 | contour_interval = 1 58 | parent_dialog = None 59 | 60 | vector_layer = generate_drivetimes_contour( 61 | raster_layer, 62 | contour_interval, 63 | parent_dialog) 64 | 65 | # Assert if file is a vector layer 66 | self.assertEqual( 67 | vector_layer.type(), 68 | QgsMapLayer.VectorLayer) 69 | self.assertEqual( 70 | vector_layer.isValid(), 71 | True) 72 | self.assertNotEqual( 73 | vector_layer, 74 | None) 75 | 76 | # def test_load_map_layers(self): 77 | # """ Test loading map layers. """ 78 | # 79 | # database_name = 'isochrones_test' 80 | # host_name = 'localhost' 81 | # port_number = '5432' 82 | # user_name = 'postgres' 83 | # password = '' 84 | # network_table = 'public.network' 85 | # network_id = 'id' 86 | # network_geom = 'geom' 87 | # catchment_table = 'public.catchment' 88 | # catchment_id = 'id' 89 | # catchment_geom = 'geom' 90 | # style_checked = False 91 | # contour_interval = 2 92 | # parent_dialog = None 93 | # progress_dialog = None 94 | # 95 | # network_array = network_table.split('.') 96 | # network_table = str(network_array[1]) 97 | # network_schema = network_array[0] 98 | # catchment = catchment_table.split('.') 99 | # catchment_table = catchment[1] 100 | # catchment_schema = catchment[0] 101 | # 102 | # args = {} 103 | # args['network_schema'] = network_schema 104 | # args['network_table'] = network_table 105 | # args['network_geom'] = network_geom 106 | # args['catchment_schema'] = catchment_schema 107 | # args['catchment_table'] = catchment_table 108 | # args['catchment_geom'] = catchment_geom 109 | # 110 | # uri = QgsDataSourceURI() 111 | # # set host name, port, database name, username and password 112 | # uri.setConnection( 113 | # host_name, 114 | # port_number, 115 | # database_name, 116 | # user_name, 117 | # password) 118 | # # set database schema, table name, geometry column and optionally 119 | # # subset (WHERE clause) 120 | # uri.setDataSource( 121 | # network_schema, 122 | # "catchment_final_no_null", 123 | # "the_geom") 124 | # 125 | # testdir = os.path.abspath(os.path.join( 126 | # os.path.realpath(os.path.dirname(__file__)))) 127 | # 128 | # testdata = os.path.join(testdir, 'data') 129 | # 130 | # catchment_path = os.path.join(testdata, 'catchment') 131 | # 132 | # layer_path = os.path.join( 133 | # catchment_path, 134 | # 'drivetime_layer.shp') 135 | # 136 | # drivetime_layer = QgsVectorLayer( 137 | # layer_path, 138 | # 'drivetime_layer', 139 | # 'ogr') 140 | # 141 | # load_map_layers(uri, parent_dialog , drivetime_layer, args) 142 | # 143 | # self.assertEquals(QgsMapLayerRegistry.instance().count(), 5) 144 | 145 | # def test_isochrone_utilities(self): 146 | # """ Tests for the main isochrone utilities""" 147 | # 148 | # database_name = 'isochrones_test' 149 | # host_name = 'localhost' 150 | # port_number = '5432' 151 | # user_name = 'postgres' 152 | # password = '' 153 | # network_table = 'public.network' 154 | # network_id = 'id' 155 | # network_geom = 'geom' 156 | # catchment_table = 'public.catchment' 157 | # catchment_id = 'id' 158 | # catchment_geom = 'geom' 159 | # style_checked = False 160 | # contour_interval = 2 161 | # parent_dialog = None 162 | # progress_dialog = None 163 | # 164 | # output_base_file_path = isochrone( 165 | # database_name, 166 | # host_name, 167 | # port_number, 168 | # user_name, 169 | # password, 170 | # network_table, 171 | # network_geom, 172 | # network_id, 173 | # catchment_table, 174 | # catchment_geom, 175 | # catchment_id, 176 | # style_checked, 177 | # contour_interval, 178 | # parent_dialog, 179 | # progress_dialog) 180 | # 181 | # self.assertEqual(output_base_file_path, "dbname='isochrones_test'" 182 | # " host=localhost" 183 | # " port=5432 user='postgres' " 184 | # "password='' key='tid' " 185 | # "table=\"public\".\"" 186 | # "catchment_final_no_null\"" 187 | # " (the_geom) sql=") 188 | 189 | 190 | if __name__ == '__main__': 191 | unittest.main() 192 | -------------------------------------------------------------------------------- /test/utilities.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Common functionality used by regression tests.""" 3 | 4 | import sys 5 | import logging 6 | 7 | from qgis.utils import iface 8 | 9 | 10 | LOGGER = logging.getLogger('QGIS') 11 | QGIS_APP = None # Static variable used to hold hand to running QGIS app 12 | CANVAS = None 13 | PARENT = None 14 | IFACE = None 15 | 16 | 17 | def get_qgis_app(): 18 | """ Start one QGIS application to test against. 19 | 20 | :returns: Handle to QGIS app, canvas, iface and parent. If there are any 21 | errors the tuple members will be returned as None. 22 | :rtype: (QgsApplication, CANVAS, IFACE, PARENT) 23 | 24 | If QGIS is already running the handle to that app will be returned. 25 | """ 26 | global QGIS_APP, PARENT, IFACE, CANVAS # pylint: disable=W0603 27 | 28 | if iface: 29 | from qgis.core import QgsApplication 30 | QGIS_APP = QgsApplication 31 | CANVAS = iface.mapCanvas() 32 | PARENT = iface.mainWindow() 33 | IFACE = iface 34 | return QGIS_APP, CANVAS, IFACE, PARENT 35 | 36 | try: 37 | from qgis.PyQt import QtGui, QtCore, QtWidgets 38 | from qgis.core import QgsApplication 39 | from qgis.gui import QgsMapCanvas 40 | 41 | except ImportError: 42 | return None, None, None, None 43 | 44 | if QGIS_APP is None: 45 | gui_flag = True # All test will run qgis in gui mode 46 | # noinspection PyPep8Naming 47 | QgsApplication.setPrefixPath('/usr', True) 48 | 49 | QGIS_APP = QgsApplication([], gui_flag) 50 | 51 | # Make sure QGIS_PREFIX_PATH is set in your env if needed! 52 | QGIS_APP.initQgis() 53 | s = QGIS_APP.showSettings() 54 | 55 | LOGGER.debug(s) 56 | 57 | if PARENT is None: 58 | # noinspection PyPep8Naming 59 | PARENT = QtWidgets.QWidget 60 | 61 | if CANVAS is None: 62 | # noinspection PyPep8Naming 63 | CANVAS = QgsMapCanvas() 64 | CANVAS.resize(QtCore.QSize(400, 400)) 65 | if IFACE is None: 66 | # QgisInterface is a stub implementation of the QGIS plugin interface 67 | # noinspection PyPep8Naming 68 | # IFACE = QgisInterface(CANVAS) 69 | IFACE = None 70 | 71 | return QGIS_APP, CANVAS, IFACE, PARENT 72 | -------------------------------------------------------------------------------- /test_suite.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | import qgis # NOQA For SIP API to V2 if run outside of QGIS 5 | 6 | try: 7 | from pip import main as pipmain 8 | except: 9 | from pip._internal import main as pipmain 10 | 11 | try: 12 | import coverage 13 | except ImportError: 14 | pipmain(['install', 'coverage']) 15 | import coverage 16 | import tempfile 17 | from osgeo import gdal 18 | from qgis.PyQt import Qt 19 | 20 | from qgis.core import Qgis 21 | 22 | 23 | def _run_tests(test_suite, package_name, with_coverage=False): 24 | """Core function to test a test suite.""" 25 | count = test_suite.countTestCases() 26 | 27 | version = str(Qgis.QGIS_VERSION_INT) 28 | version = int(version) 29 | 30 | print('########') 31 | print('%s tests has been discovered in %s' % (count, package_name)) 32 | print('QGIS : %s' % version) 33 | print('Python GDAL : %s' % gdal.VersionInfo('VERSION_NUM')) 34 | print('QT : %s' % Qt.QT_VERSION_STR) 35 | print('Run slow tests : %s' % (not os.environ.get('ON_TRAVIS', False))) 36 | print('########') 37 | if with_coverage: 38 | cov = coverage.Coverage( 39 | source=['./'], 40 | omit=['*/test/*', './definitions/*'], 41 | ) 42 | cov.start() 43 | 44 | unittest.TextTestRunner(verbosity=3, stream=sys.stdout).run(test_suite) 45 | 46 | if with_coverage: 47 | cov.stop() 48 | cov.save() 49 | report = tempfile.NamedTemporaryFile(delete=False) 50 | cov.report(file=report) 51 | # Produce HTML reports in the `htmlcov` folder and open index.html 52 | # cov.html_report() 53 | report.close() 54 | with open(report.name, 'r') as fin: 55 | print(fin.read()) 56 | 57 | 58 | def test_package(package='test'): 59 | """Test package. 60 | This function is called by travis without arguments. 61 | :param package: The package to test. 62 | :type package: str 63 | """ 64 | test_loader = unittest.defaultTestLoader 65 | try: 66 | test_suite = test_loader.discover(package) 67 | except ImportError: 68 | test_suite = unittest.TestSuite() 69 | _run_tests(test_suite, package) -------------------------------------------------------------------------------- /ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Samweli/isochrones/069c7944ac4fbcedee40c5b14fd07ec522ba10cb/ui/__init__.py -------------------------------------------------------------------------------- /ui/isochrone_dialog_base.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | IsochronesDialogBase 4 | 5 | 6 | 7 | 0 8 | 0 9 | 620 10 | 643 11 | 12 | 13 | 14 | Isochrones 15 | 16 | 17 | false 18 | 19 | 20 | 21 | 22 | 23 | 24 | 0 25 | 0 26 | 27 | 28 | 29 | 30 | 31 | 32 | Qt::AlignCenter 33 | 34 | 35 | false 36 | 37 | 38 | 39 | 40 | 41 | Host 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 0 50 | 0 51 | 52 | 53 | 54 | false 55 | 56 | 57 | Qt::ImhNone 58 | 59 | 60 | 61 | 62 | 63 | eg. 5432 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 0 72 | 0 73 | 74 | 75 | 76 | 77 | 78 | 79 | user name for database 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 0 88 | 0 89 | 90 | 91 | 92 | false 93 | 94 | 95 | Qt::ImhNone 96 | 97 | 98 | 99 | 100 | 101 | eg. roads 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 0 110 | 0 111 | 112 | 113 | 114 | Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData 115 | 116 | 117 | 118 | 119 | 120 | QLineEdit::Password 121 | 122 | 123 | 124 | 125 | 126 | 127 | Username 128 | 129 | 130 | 131 | 132 | 133 | 134 | Password 135 | 136 | 137 | 138 | 139 | 140 | 141 | Port 142 | 143 | 144 | 145 | 146 | 147 | 148 | Database 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 0 157 | 0 158 | 159 | 160 | 161 | false 162 | 163 | 164 | Qt::ImhNone 165 | 166 | 167 | 168 | 169 | 170 | eg. localhost 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 0 182 | 0 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | Qt::AlignCenter 193 | 194 | 195 | false 196 | 197 | 198 | 199 | 200 | 201 | 202 | 0 203 | 0 204 | 205 | 206 | 207 | 208 | 209 | 210 | eg. schema.table 211 | 212 | 213 | 214 | 215 | 216 | 217 | Id column 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 0 226 | 0 227 | 228 | 229 | 230 | 231 | 232 | 233 | eg. id 234 | 235 | 236 | 237 | 238 | 239 | 240 | Network Table 241 | 242 | 243 | 244 | 245 | 246 | 247 | Geometric column 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 258 | 259 | 260 | 261 | 262 | 263 | eg. geom 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 0 275 | 0 276 | 277 | 278 | 279 | 280 | 281 | 282 | Qt::AlignCenter 283 | 284 | 285 | false 286 | 287 | 288 | 289 | 290 | 291 | Geometric column 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 0 300 | 0 301 | 302 | 303 | 304 | 305 | 306 | 307 | eg. geom 308 | 309 | 310 | 311 | 312 | 313 | 314 | Catchment Table 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 0 323 | 0 324 | 325 | 326 | 327 | 328 | 329 | 330 | eg. schema.table 331 | 332 | 333 | 334 | 335 | 336 | 337 | Id column 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 0 346 | 0 347 | 348 | 349 | 350 | 351 | 352 | 353 | eg. id 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | Drivetime Interval (minutes) 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 0 372 | 0 373 | 374 | 375 | 376 | Qt::ImhDigitsOnly 377 | 378 | 379 | 380 | 381 | 382 | 1 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | Create isochrone map style 393 | 394 | 395 | 396 | 397 | 398 | 399 | Qt::Horizontal 400 | 401 | 402 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | button_box 412 | accepted() 413 | IsochronesDialogBase 414 | accept() 415 | 416 | 417 | 20 418 | 20 419 | 420 | 421 | 20 422 | 20 423 | 424 | 425 | 426 | 427 | button_box 428 | rejected() 429 | IsochronesDialogBase 430 | reject() 431 | 432 | 433 | 20 434 | 20 435 | 436 | 437 | 20 438 | 20 439 | 440 | 441 | 442 | 443 | 444 | --------------------------------------------------------------------------------