├── test
├── tenbytenraster.keywords
├── __init__.py
├── tenbytenraster.prj
├── tenbytenraster.asc
├── tenbytenraster.asc.aux.xml
├── tenbytenraster.lic
├── test_resources.py
├── test_raster_tracer_dockwidget.py
├── tenbytenraster.qml
├── test_translations.py
├── utilities.py
├── test_init.py
├── test_qgis_environment.py
└── qgis_interface.py
├── exceptions.py
├── icon.png
├── screen.gif
├── resources.qrc
├── scripts
├── compile-strings.sh
├── run-env-linux.sh
└── update-strings.sh
├── i18n
└── af.ts
├── help
├── source
│ ├── index.rst
│ └── conf.py
├── make.bat
└── Makefile
├── LICENSE
├── line_simplification.py
├── __init__.py
├── .gitignore
├── raster_tracer_dockwidget.py
├── utils.py
├── metadata.txt
├── README.md
├── pb_tool.cfg
├── raster_tracer_dockwidget_base.ui
├── plugin_upload.py
├── autotrace.py
├── astar.py
├── pointtool_states.py
├── Makefile
├── pylintrc
├── raster_tracer.py
├── pointtool.py
└── resources.py
/test/tenbytenraster.keywords:
--------------------------------------------------------------------------------
1 | title: Tenbytenraster
2 |
--------------------------------------------------------------------------------
/exceptions.py:
--------------------------------------------------------------------------------
1 |
2 | class OutsideMapError(Exception):
3 | pass
4 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkondratyev85/raster_tracer/HEAD/icon.png
--------------------------------------------------------------------------------
/screen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkondratyev85/raster_tracer/HEAD/screen.gif
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
1 | # import qgis libs so that ve set the correct sip api version
2 | import qgis # pylint: disable=W0611 # NOQA
--------------------------------------------------------------------------------
/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | icon.png
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/tenbytenraster.prj:
--------------------------------------------------------------------------------
1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/i18n/af.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @default
5 |
6 |
7 | Good morning
8 | Goeie more
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/tenbytenraster.asc:
--------------------------------------------------------------------------------
1 | NCOLS 10
2 | NROWS 10
3 | XLLCENTER 1535380.000000
4 | YLLCENTER 5083260.000000
5 | DX 10
6 | DY 10
7 | NODATA_VALUE -9999
8 | 0 1 2 3 4 5 6 7 8 9
9 | 0 1 2 3 4 5 6 7 8 9
10 | 0 1 2 3 4 5 6 7 8 9
11 | 0 1 2 3 4 5 6 7 8 9
12 | 0 1 2 3 4 5 6 7 8 9
13 | 0 1 2 3 4 5 6 7 8 9
14 | 0 1 2 3 4 5 6 7 8 9
15 | 0 1 2 3 4 5 6 7 8 9
16 | 0 1 2 3 4 5 6 7 8 9
17 | 0 1 2 3 4 5 6 7 8 9
18 | CRS
19 | NOTES
20 |
--------------------------------------------------------------------------------
/test/tenbytenraster.asc.aux.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Point
4 |
5 |
6 |
7 | 9
8 | 4.5
9 | 0
10 | 2.872281323269
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/help/source/index.rst:
--------------------------------------------------------------------------------
1 | .. RasterTracer 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 RasterTracer'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 |
--------------------------------------------------------------------------------
/test/tenbytenraster.lic:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Tim Sutton, Linfiniti Consulting CC
5 |
6 |
7 |
8 | tenbytenraster.asc
9 | 2700044251
10 | Yes
11 | Tim Sutton
12 | Tim Sutton (QGIS Source Tree)
13 | Tim Sutton
14 | This data is publicly available from QGIS Source Tree. The original
15 | file was created and contributed to QGIS by Tim Sutton.
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/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/inasafe/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 mkondratyev85
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/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__ = 'mkondratyev85@gmail.com'
12 | __date__ = '2019-11-09'
13 | __copyright__ = 'Copyright 2019, Mikhail Kondratyev'
14 |
15 | import unittest
16 |
17 | from qgis.PyQt.QtGui import QIcon
18 |
19 |
20 |
21 | class RasterTracerDialogTest(unittest.TestCase):
22 | """Test rerources work."""
23 |
24 | def setUp(self):
25 | """Runs before each test."""
26 | pass
27 |
28 | def tearDown(self):
29 | """Runs after each test."""
30 | pass
31 |
32 | def test_icon_png(self):
33 | """Test we can click OK."""
34 | path = ':/plugins/RasterTracer/icon.png'
35 | icon = QIcon(path)
36 | self.assertFalse(icon.isNull())
37 |
38 | if __name__ == "__main__":
39 | suite = unittest.makeSuite(RasterTracerResourcesTest)
40 | runner = unittest.TextTestRunner(verbosity=2)
41 | runner.run(suite)
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/line_simplification.py:
--------------------------------------------------------------------------------
1 | from math import tan, radians, atan2
2 |
3 | def smooth(path, size=2):
4 | smoothed = []
5 | smoothed.append(path[0])
6 | for i in range(size, len(path)-size):
7 | xx = [x for (x,y) in path[i-size:i+size]]
8 | yy = [y for (x,y) in path[i-size:i+size]]
9 | x = sum(xx)/(len(xx)*1.0)
10 | y = sum(yy)/(len(yy)*1.0)
11 | smoothed.append((x,y))
12 | smoothed.append(path[-1])
13 | return smoothed
14 |
15 | def simplify(path, tolerance = 2):
16 | previous = None
17 | previousfactor = None
18 | nodes_to_delete = []
19 | tolerance = tan(radians(tolerance))
20 | for i, node in enumerate(path):
21 | if not previous:
22 | previous = node
23 | continue
24 | factor = atan2((node[0]-previous[0]),(node[1]-previous[1]))
25 | #factor = ((node[0]-previous[0]),(node[1]-previous[1]))
26 | if not previousfactor:
27 | previousfactor = factor
28 | continue
29 | if abs(factor-previousfactor) < tolerance: nodes_to_delete.append(i-1)
30 | #print factor, previousfactor, abs(factor-previousfactor), tolerance
31 | previous = node
32 | previousfactor = factor
33 |
34 | for i in nodes_to_delete[::-1]:
35 | path.pop(i)
36 | return path
37 |
--------------------------------------------------------------------------------
/test/test_raster_tracer_dockwidget.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | """DockWidget 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__ = 'mkondratyev85@gmail.com'
12 | __date__ = '2019-11-09'
13 | __copyright__ = 'Copyright 2019, Mikhail Kondratyev'
14 |
15 | import unittest
16 |
17 | from qgis.PyQt.QtGui import QDockWidget
18 |
19 | from raster_tracer_dockwidget import RasterTracerDockWidget
20 |
21 | from utilities import get_qgis_app
22 |
23 | QGIS_APP = get_qgis_app()
24 |
25 |
26 | class RasterTracerDockWidgetTest(unittest.TestCase):
27 | """Test dockwidget works."""
28 |
29 | def setUp(self):
30 | """Runs before each test."""
31 | self.dockwidget = RasterTracerDockWidget(None)
32 |
33 | def tearDown(self):
34 | """Runs after each test."""
35 | self.dockwidget = None
36 |
37 | def test_dockwidget_ok(self):
38 | """Test we can click OK."""
39 | pass
40 |
41 | if __name__ == "__main__":
42 | suite = unittest.makeSuite(RasterTracerDialogTest)
43 | runner = unittest.TextTestRunner(verbosity=2)
44 | runner.run(suite)
45 |
46 |
--------------------------------------------------------------------------------
/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 | echo ${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/tenbytenraster.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 | 0
26 |
27 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | /***************************************************************************
4 | RasterTracer
5 | A QGIS plugin
6 | This plugin traces the underlying raster map
7 | Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
8 | -------------------
9 | begin : 2019-11-09
10 | copyright : (C) 2019 by Mikhail Kondratyev
11 | email : mkondratyev85@gmail.com
12 | git sha : $Format:%H$
13 | ***************************************************************************/
14 |
15 | /***************************************************************************
16 | * *
17 | * This program is free software; you can redistribute it and/or modify *
18 | * it under the terms of the GNU General Public License as published by *
19 | * the Free Software Foundation; either version 2 of the License, or *
20 | * (at your option) any later version. *
21 | * *
22 | ***************************************************************************/
23 | This script initializes the plugin, making it known to QGIS.
24 | """
25 |
26 |
27 | # noinspection PyPep8Naming
28 | def classFactory(iface): # pylint: disable=invalid-name
29 | """Load RasterTracer class from file RasterTracer.
30 |
31 | :param iface: A QGIS interface instance.
32 | :type iface: QgsInterface
33 | """
34 | #
35 | from .raster_tracer import RasterTracer
36 | return RasterTracer(iface)
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
106 | .noseids
107 | .tags
108 |
--------------------------------------------------------------------------------
/test/test_translations.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | """Safe Translations 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 .utilities import get_qgis_app
11 |
12 | __author__ = 'ismailsunni@yahoo.co.id'
13 | __date__ = '12/10/2011'
14 | __copyright__ = ('Copyright 2012, Australia Indonesia Facility for '
15 | 'Disaster Reduction')
16 | import unittest
17 | import os
18 |
19 | from qgis.PyQt.QtCore import QCoreApplication, QTranslator
20 |
21 | QGIS_APP = get_qgis_app()
22 |
23 |
24 | class SafeTranslationsTest(unittest.TestCase):
25 | """Test translations work."""
26 |
27 | def setUp(self):
28 | """Runs before each test."""
29 | if 'LANG' in iter(os.environ.keys()):
30 | os.environ.__delitem__('LANG')
31 |
32 | def tearDown(self):
33 | """Runs after each test."""
34 | if 'LANG' in iter(os.environ.keys()):
35 | os.environ.__delitem__('LANG')
36 |
37 | def test_qgis_translations(self):
38 | """Test that translations work."""
39 | parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir)
40 | dir_path = os.path.abspath(parent_path)
41 | file_path = os.path.join(
42 | dir_path, 'i18n', 'af.qm')
43 | translator = QTranslator()
44 | translator.load(file_path)
45 | QCoreApplication.installTranslator(translator)
46 |
47 | expected_message = 'Goeie more'
48 | real_message = QCoreApplication.translate("@default", 'Good morning')
49 | self.assertEqual(real_message, expected_message)
50 |
51 |
52 | if __name__ == "__main__":
53 | suite = unittest.makeSuite(SafeTranslationsTest)
54 | runner = unittest.TextTestRunner(verbosity=2)
55 | runner.run(suite)
56 |
--------------------------------------------------------------------------------
/test/utilities.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | """Common functionality used by regression tests."""
3 |
4 | import sys
5 | import logging
6 |
7 |
8 | LOGGER = logging.getLogger('QGIS')
9 | QGIS_APP = None # Static variable used to hold hand to running QGIS app
10 | CANVAS = None
11 | PARENT = None
12 | IFACE = None
13 |
14 |
15 | def get_qgis_app():
16 | """ Start one QGIS application to test against.
17 |
18 | :returns: Handle to QGIS app, canvas, iface and parent. If there are any
19 | errors the tuple members will be returned as None.
20 | :rtype: (QgsApplication, CANVAS, IFACE, PARENT)
21 |
22 | If QGIS is already running the handle to that app will be returned.
23 | """
24 |
25 | try:
26 | from qgis.PyQt import QtGui, QtCore
27 | from qgis.core import QgsApplication
28 | from qgis.gui import QgsMapCanvas
29 | from .qgis_interface import QgisInterface
30 | except ImportError:
31 | return None, None, None, None
32 |
33 | global QGIS_APP # pylint: disable=W0603
34 |
35 | if QGIS_APP is None:
36 | gui_flag = True # All test will run qgis in gui mode
37 | #noinspection PyPep8Naming
38 | QGIS_APP = QgsApplication(sys.argv, gui_flag)
39 | # Make sure QGIS_PREFIX_PATH is set in your env if needed!
40 | QGIS_APP.initQgis()
41 | s = QGIS_APP.showSettings()
42 | LOGGER.debug(s)
43 |
44 | global PARENT # pylint: disable=W0603
45 | if PARENT is None:
46 | #noinspection PyPep8Naming
47 | PARENT = QtGui.QWidget()
48 |
49 | global CANVAS # pylint: disable=W0603
50 | if CANVAS is None:
51 | #noinspection PyPep8Naming
52 | CANVAS = QgsMapCanvas(PARENT)
53 | CANVAS.resize(QtCore.QSize(400, 400))
54 |
55 | global IFACE # pylint: disable=W0603
56 | if IFACE is None:
57 | # QgisInterface is a stub implementation of the QGIS plugin interface
58 | #noinspection PyPep8Naming
59 | IFACE = QgisInterface(CANVAS)
60 |
61 | return QGIS_APP, CANVAS, IFACE, PARENT
62 |
--------------------------------------------------------------------------------
/test/test_init.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | """Tests QGIS plugin init."""
3 |
4 | __author__ = 'Tim Sutton '
5 | __revision__ = '$Format:%H$'
6 | __date__ = '17/10/2010'
7 | __license__ = "GPL"
8 | __copyright__ = 'Copyright 2012, Australia Indonesia Facility for '
9 | __copyright__ += 'Disaster Reduction'
10 |
11 | import os
12 | import unittest
13 | import logging
14 | import configparser
15 |
16 | LOGGER = logging.getLogger('QGIS')
17 |
18 |
19 | class TestInit(unittest.TestCase):
20 | """Test that the plugin init is usable for QGIS.
21 |
22 | Based heavily on the validator class by Alessandro
23 | Passoti available here:
24 |
25 | http://github.com/qgis/qgis-django/blob/master/qgis-app/
26 | plugins/validator.py
27 |
28 | """
29 |
30 | def test_read_init(self):
31 | """Test that the plugin __init__ will validate on plugins.qgis.org."""
32 |
33 | # You should update this list according to the latest in
34 | # https://github.com/qgis/qgis-django/blob/master/qgis-app/
35 | # plugins/validator.py
36 |
37 | required_metadata = [
38 | 'name',
39 | 'description',
40 | 'version',
41 | 'qgisMinimumVersion',
42 | 'email',
43 | 'author']
44 |
45 | file_path = os.path.abspath(os.path.join(
46 | os.path.dirname(__file__), os.pardir,
47 | 'metadata.txt'))
48 | LOGGER.info(file_path)
49 | metadata = []
50 | parser = configparser.ConfigParser()
51 | parser.optionxform = str
52 | parser.read(file_path)
53 | message = 'Cannot find a section named "general" in %s' % file_path
54 | assert parser.has_section('general'), message
55 | metadata.extend(parser.items('general'))
56 |
57 | for expectation in required_metadata:
58 | message = ('Cannot find metadata "%s" in metadata source (%s).' % (
59 | expectation, file_path))
60 |
61 | self.assertIn(expectation, dict(metadata), message)
62 |
63 | if __name__ == '__main__':
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/test/test_qgis_environment.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | """Tests for QGIS functionality.
3 |
4 |
5 | .. note:: This program is free software; you can redistribute it and/or modify
6 | it under the terms of the GNU General Public License as published by
7 | the Free Software Foundation; either version 2 of the License, or
8 | (at your option) any later version.
9 |
10 | """
11 | __author__ = 'tim@linfiniti.com'
12 | __date__ = '20/01/2011'
13 | __copyright__ = ('Copyright 2012, Australia Indonesia Facility for '
14 | 'Disaster Reduction')
15 |
16 | import os
17 | import unittest
18 | from qgis.core import (
19 | QgsProviderRegistry,
20 | QgsCoordinateReferenceSystem,
21 | QgsRasterLayer)
22 |
23 | from .utilities import get_qgis_app
24 | QGIS_APP = get_qgis_app()
25 |
26 |
27 | class QGISTest(unittest.TestCase):
28 | """Test the QGIS Environment"""
29 |
30 | def test_qgis_environment(self):
31 | """QGIS environment has the expected providers"""
32 |
33 | r = QgsProviderRegistry.instance()
34 | self.assertIn('gdal', r.providerList())
35 | self.assertIn('ogr', r.providerList())
36 | self.assertIn('postgres', r.providerList())
37 |
38 | def test_projection(self):
39 | """Test that QGIS properly parses a wkt string.
40 | """
41 | crs = QgsCoordinateReferenceSystem()
42 | wkt = (
43 | 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",'
44 | 'SPHEROID["WGS_1984",6378137.0,298.257223563]],'
45 | 'PRIMEM["Greenwich",0.0],UNIT["Degree",'
46 | '0.0174532925199433]]')
47 | crs.createFromWkt(wkt)
48 | auth_id = crs.authid()
49 | expected_auth_id = 'EPSG:4326'
50 | self.assertEqual(auth_id, expected_auth_id)
51 |
52 | # now test for a loaded layer
53 | path = os.path.join(os.path.dirname(__file__), 'tenbytenraster.asc')
54 | title = 'TestRaster'
55 | layer = QgsRasterLayer(path, title)
56 | auth_id = layer.crs().authid()
57 | self.assertEqual(auth_id, expected_auth_id)
58 |
59 | if __name__ == '__main__':
60 | unittest.main()
61 |
--------------------------------------------------------------------------------
/raster_tracer_dockwidget.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | /***************************************************************************
4 | RasterTracerDockWidget
5 | A QGIS plugin
6 | This plugin traces the underlying raster map
7 | Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
8 | -------------------
9 | begin : 2019-11-09
10 | git sha : $Format:%H$
11 | copyright : (C) 2019 by Mikhail Kondratyev
12 | email : mkondratyev85@gmail.com
13 | ***************************************************************************/
14 |
15 | /***************************************************************************
16 | * *
17 | * This program is free software; you can redistribute it and/or modify *
18 | * it under the terms of the GNU General Public License as published by *
19 | * the Free Software Foundation; either version 2 of the License, or *
20 | * (at your option) any later version. *
21 | * *
22 | ***************************************************************************/
23 | """
24 |
25 | import os
26 |
27 | from qgis.PyQt import QtGui, QtWidgets, uic
28 | from qgis.PyQt.QtCore import pyqtSignal
29 |
30 | FORM_CLASS, _ = uic.loadUiType(os.path.join(
31 | os.path.dirname(__file__), 'raster_tracer_dockwidget_base.ui'))
32 |
33 |
34 | class RasterTracerDockWidget(QtWidgets.QDockWidget, FORM_CLASS):
35 |
36 | closingPlugin = pyqtSignal()
37 |
38 | def __init__(self, parent=None):
39 | """Constructor."""
40 | super(RasterTracerDockWidget, self).__init__(parent)
41 | # Set up the user interface from Designer.
42 | # After setupUI you can access any designer object by doing
43 | # self., and you can use autoconnect slots - see
44 | # http://doc.qt.io/qt-5/designer-using-a-ui-file.html
45 | # #widgets-and-dialogs-with-auto-connect
46 | self.setupUi(self)
47 |
48 | def closeEvent(self, event):
49 | self.closingPlugin.emit()
50 | event.accept()
51 |
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | from osgeo import gdal
2 | from qgis.core import QgsCoordinateTransform
3 | import numpy as np
4 |
5 |
6 | class PossiblyIndexedImageError(Exception):
7 | pass
8 |
9 |
10 | def get_indxs_from_raster_coords(geo_ref, xy):
11 | x, y = xy
12 | top_left_x, top_left_y, we_resolution, ns_resolution = geo_ref
13 | i = int((y - top_left_y) / ns_resolution) * -1
14 | j = int((x - top_left_x) / we_resolution)
15 | return i, j
16 |
17 |
18 | def get_coords_from_raster_indxs(geo_ref, ij):
19 | i, j = ij
20 | top_left_x, top_left_y, we_resolution, ns_resolution = geo_ref
21 | y = (top_left_y - (i + 0.5) * ns_resolution)
22 | x = top_left_x - (j + 0.5) * we_resolution * -1
23 | return x, y
24 |
25 |
26 | def get_whole_raster(layer, project_instance):
27 | provider = layer.dataProvider()
28 | extent = provider.extent()
29 |
30 | project_crs = project_instance.crs()
31 | trfm_from_src = QgsCoordinateTransform(provider.crs(),
32 | project_crs,
33 | project_instance)
34 | trfm_to_src = QgsCoordinateTransform(project_crs,
35 | provider.crs(),
36 | project_instance)
37 |
38 | dx = layer.rasterUnitsPerPixelX()
39 | dy = layer.rasterUnitsPerPixelY()
40 | top_left_x = extent.xMinimum()
41 | top_left_y = extent.yMaximum()
42 |
43 | geo_ref = (top_left_x, top_left_y, dx, dy)
44 |
45 | to_indexes = lambda x, y: get_indxs_from_raster_coords(
46 | geo_ref,
47 | trfm_to_src.transform(x, y))
48 | to_coords = lambda i, j: trfm_from_src.transform(
49 | *get_coords_from_raster_indxs(geo_ref, (i, j)))
50 | to_coords_provider = lambda i, j:\
51 | get_coords_from_raster_indxs(geo_ref,
52 | (i, j))
53 | to_coords_provider2 = lambda x, y: trfm_to_src.transform(x, y)
54 | raster_path = layer.source()
55 | ds = gdal.Open(raster_path)
56 | try:
57 | band1 = np.array(ds.GetRasterBand(1).ReadAsArray())
58 | band2 = np.array(ds.GetRasterBand(2).ReadAsArray())
59 | band3 = np.array(ds.GetRasterBand(3).ReadAsArray())
60 | except AttributeError:
61 | raise PossiblyIndexedImageError
62 |
63 | return ((band1, band2, band3), to_indexes, to_coords,
64 | to_coords_provider, to_coords_provider2)
65 |
--------------------------------------------------------------------------------
/metadata.txt:
--------------------------------------------------------------------------------
1 | # This file contains metadata for your plugin.
2 |
3 | # This file should be included when you package your plugin.# Mandatory items:
4 |
5 | [general]
6 | name=Raster Tracer
7 | qgisMinimumVersion=3.0
8 | description=This plugin allows user to automaticaly trace lineal features of the underlaying raster map, simply by clicking on knots of lines on map.
9 | version=0.3.3
10 | author=Mikhail Kondratyev
11 | email=mkondratyev85@gmail.com
12 |
13 | about=RasterTracer is a plugin for semi-automatic digitizing of underlying raster layer in QGis. It is useful, for example, when you need to digitize a scanned topographic map, with curved black lines representing lines of equal heights of the surface. Instead of creating this curved vector line by manually clicking at each segment of this curved line to create multi-line, with this plugin you can click at the beginning of the curved line and at the end of the curved line, and it will automatically trace over black pixels (or pixels that are almost black) from the beginning to the end. By using this plugin you reduce clicks while digitizing raster maps. See https://github.com/mkondratyev85/raster_tracer for more explanation.
14 |
15 | tracker=https://github.com/mkondratyev85/raster_tracer/issues
16 | repository=https://github.com/mkondratyev85/raster_tracer/
17 | # End of mandatory metadata
18 |
19 | # Recommended items:
20 |
21 | hasProcessingProvider=no
22 | # Uncomment the following line and add your changelog:
23 | changelog=0.3.3 -- Fixed bug in QGis 3.30.x (#39).
24 | 0.3.2
25 | -- Fixed important bug when raster and vector layers have different CS (#26).
26 | 0.3.1
27 | -- Snapping to vector layer while drawing new segments.
28 | -- Using the correct way of closing the docker.
29 | 0.3.0
30 | -- Tracing is on the background. No freezing of QGgis anymore (#22)
31 | 0.2.0
32 | -- Make smoothing optional (#15)
33 | -- Warn the user when geometry type is not MultiLineString (#7, #13)
34 | 0.1.1
35 | -- Update in details and homepage address
36 | 0.1
37 | -- Initial version
38 |
39 | # Tags are comma separated with spaces allowed
40 | tags=python, digitizing, raster, vector
41 |
42 | homepage=https://github.com/mkondratyev85/raster_tracer/
43 | category=Plugins
44 | icon=icon.png
45 | # experimental flag
46 | experimental=False
47 |
48 | # deprecated flag (applies to the whole plugin, not just a single version)
49 | deprecated=False
50 |
51 | # Since QGIS 3.8, a comma separated list of plugins to be installed
52 | # (or upgraded) can be specified.
53 | # Check the documentation for more information.
54 | # plugin_dependencies=
55 |
56 | Category of the plugin: Raster, Vector, Database or Web
57 | #category=Raster
58 |
59 | # If the plugin can run on QGIS Server.
60 | server=False
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RasterTracer
2 |
3 | RasterTracer is a plugin for semi-automatic digitizing of an underlying raster
4 | layer in QGis.
5 | It is useful, for example, when you need to digitize a scanned
6 | topographic map, with curved black lines representing lines of equal heights of
7 | the surface (contours).
8 | Instead of creating this curved vector line by manually clicking
9 | at each segment of this curved line, with this plugin you
10 | can click at the beginning of the curved line and at the end of the curved
11 | line, and it will automatically trace over black pixels (or pixels that are
12 | almost black) starting from the beginning to the end.
13 | By using this plugin you reduce
14 | clicks while digitizing raster maps.
15 |
16 | The process is show here:
17 |
18 |
19 |
20 | ## Usage
21 |
22 | Tracing is enabled only if the selected vector layer is in the editing mode.
23 |
24 | The geometry type of the vector layer has to be MultiLineString / MultiCurve.
25 |
26 | You can choose the color that will be traced over in the raster image.
27 | To do this, check the box `trace color` and select the desired color in
28 | the dialog window.
29 |
30 | If `trace color` is not checked, the plugin will try to trace the color that is
31 | similar to the color of the pixel on the map at the place where you clicked the
32 | last time.
33 | This means that each time you click on the map, it will trace a slightly
34 | different color.
35 | This slows down tracing a bit, but may be useful if the color of the line you are
36 | tracing varies over the map.
37 |
38 | ## What image can it trace?
39 |
40 | Right now the plugin can trace images that have a standard RGB color space.
41 | It has no support for any black and white, grey, or indexed images.
42 | This means that if your image has an unsupported colorspace,
43 | you have to convert the colorspace of your image to RGB first. This can be done in QGis with:
44 |
45 | `Processing >> Toolbox >> GDAL >> Raster conversion >> PCT to RGB`
46 |
47 | or directly in the CLI with:
48 |
49 | `pct2rgb.py -of GTiff -b 1`
50 |
51 | Also in the current version there are some issues when coordinate system
52 | of the raster layer differs from the coordinate system of the project.
53 | It might be useful to convert the image that will be processed to the same coordinate
54 | system used by the QGis project before importing. For example, the command bellow
55 | converts a geotiff image (already georeferenced) to an `EPSG:4326` coordinate system.
56 |
57 | `gdalwarp -t_srs EPSG:4326 -of GTiff infile.tif outfile.tif`
58 |
59 | __NOTE__: `pct2rgb.py` and `gdalwarp` are part of the GDAL package.
60 |
61 | ## Useful keys
62 |
63 |
64 | `b` - delete last segment
65 |
66 | `a` - switch between "trace" mode and "straight-line" mode.
67 |
68 | `Esc` - cancel tracing segment. Useful when raster_tracer struggles to find
69 | a good path between clicked points (Usually when points are far from each other).
70 |
--------------------------------------------------------------------------------
/pb_tool.cfg:
--------------------------------------------------------------------------------
1 | #/***************************************************************************
2 | # RasterTracer
3 | #
4 | # Configuration file for plugin builder tool (pb_tool)
5 | # Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
6 | # -------------------
7 | # begin : 2019-11-09
8 | # copyright : (C) 2019 by Mikhail Kondratyev
9 | # email : mkondratyev85@gmail.com
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 | # You can install pb_tool using:
23 | # pip install http://geoapt.net/files/pb_tool.zip
24 | #
25 | # Consider doing your development (and install of pb_tool) in a virtualenv.
26 | #
27 | # For details on setting up and using pb_tool, see:
28 | # http://g-sherman.github.io/plugin_build_tool/
29 | #
30 | # Issues and pull requests here:
31 | # https://github.com/g-sherman/plugin_build_tool:
32 | #
33 | # Sane defaults for your plugin generated by the Plugin Builder are
34 | # already set below.
35 | #
36 | # As you add Python source files and UI files to your plugin, add
37 | # them to the appropriate [files] section below.
38 |
39 | [plugin]
40 | # Name of the plugin. This is the name of the directory that will
41 | # be created in .qgis2/python/plugins
42 | name: raster_tracer
43 |
44 | # Full path to where you want your plugin directory copied. If empty,
45 | # the QGIS default path will be used. Don't include the plugin name in
46 | # the path.
47 | plugin_path:
48 |
49 | [files]
50 | # Python files that should be deployed with the plugin
51 | python_files: __init__.py raster_tracer.py raster_tracer_dockwidget.py
52 |
53 | # The main dialog file that is loaded (not compiled)
54 | main_dialog: raster_tracer_dockwidget_base.ui
55 |
56 | # Other ui files for dialogs you create (these will be compiled)
57 | compiled_ui_files:
58 |
59 | # Resource file(s) that will be compiled
60 | resource_files: resources.qrc
61 |
62 | # Other files required for the plugin
63 | extras: metadata.txt icon.png
64 |
65 | # Other directories to be deployed with the plugin.
66 | # These must be subdirectories under the plugin directory
67 | extra_dirs:
68 |
69 | # ISO code(s) for any locales (translations), separated by spaces.
70 | # Corresponding .ts files must exist in the i18n directory
71 | locales:
72 |
73 | [help]
74 | # the built help directory that should be deployed with the plugin
75 | dir: help/build/html
76 | # the name of the directory to target in the deployed plugin
77 | target: help
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/raster_tracer_dockwidget_base.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | RasterTracerDockWidgetBase
4 |
5 |
6 |
7 | 0
8 | 0
9 | 252
10 | 225
11 |
12 |
13 |
14 | Raster Tracer
15 |
16 |
17 |
18 |
19 | -
20 |
21 |
22 | true
23 |
24 |
25 | Snap to vector layer
26 |
27 |
28 |
29 | -
30 |
31 |
32 | false
33 |
34 |
35 | 1
36 |
37 |
38 | 1
39 |
40 |
41 |
42 |
43 | -
44 |
45 |
46 | false
47 |
48 |
49 | Snap to nearest
50 |
51 |
52 |
53 | -
54 |
55 |
56 | Trace color
57 |
58 |
59 |
60 | -
61 |
62 |
63 | false
64 |
65 |
66 |
67 | 0
68 | 0
69 | 0
70 |
71 |
72 |
73 |
74 | -
75 |
76 |
77 | Layer to trace
78 |
79 |
80 |
81 | -
82 |
83 |
84 | -
85 |
86 |
87 | false
88 |
89 |
90 | 1
91 |
92 |
93 | 1
94 |
95 |
96 |
97 | -
98 |
99 |
100 | Smooth lines
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | QgsColorButton
110 | QToolButton
111 |
112 |
113 |
114 | QgsMapLayerComboBox
115 | QComboBox
116 |
117 |
118 |
119 | QgsSpinBox
120 | QSpinBox
121 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/plugin_upload.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf-8
3 | """This script uploads a plugin package to the plugin repository.
4 | Authors: A. Pasotti, V. Picavet
5 | git sha : $TemplateVCSFormat
6 | """
7 |
8 | import sys
9 | import getpass
10 | import xmlrpc.client
11 | from optparse import OptionParser
12 |
13 | standard_library.install_aliases()
14 |
15 | # Configuration
16 | PROTOCOL = 'https'
17 | SERVER = 'plugins.qgis.org'
18 | PORT = '443'
19 | ENDPOINT = '/plugins/RPC2/'
20 | VERBOSE = False
21 |
22 |
23 | def main(parameters, arguments):
24 | """Main entry point.
25 |
26 | :param parameters: Command line parameters.
27 | :param arguments: Command line arguments.
28 | """
29 | address = "{protocol}://{username}:{password}@{server}:{port}{endpoint}".format(
30 | protocol=PROTOCOL,
31 | username=parameters.username,
32 | password=parameters.password,
33 | server=parameters.server,
34 | port=parameters.port,
35 | endpoint=ENDPOINT)
36 | print("Connecting to: %s" % hide_password(address))
37 |
38 | server = xmlrpc.client.ServerProxy(address, verbose=VERBOSE)
39 |
40 | try:
41 | with open(arguments[0], 'rb') as handle:
42 | plugin_id, version_id = server.plugin.upload(
43 | xmlrpc.client.Binary(handle.read()))
44 | print("Plugin ID: %s" % plugin_id)
45 | print("Version ID: %s" % version_id)
46 | except xmlrpc.client.ProtocolError as err:
47 | print("A protocol error occurred")
48 | print("URL: %s" % hide_password(err.url, 0))
49 | print("HTTP/HTTPS headers: %s" % err.headers)
50 | print("Error code: %d" % err.errcode)
51 | print("Error message: %s" % err.errmsg)
52 | except xmlrpc.client.Fault as err:
53 | print("A fault occurred")
54 | print("Fault code: %d" % err.faultCode)
55 | print("Fault string: %s" % err.faultString)
56 |
57 |
58 | def hide_password(url, start=6):
59 | """Returns the http url with password part replaced with '*'.
60 |
61 | :param url: URL to upload the plugin to.
62 | :type url: str
63 |
64 | :param start: Position of start of password.
65 | :type start: int
66 | """
67 | start_position = url.find(':', start) + 1
68 | end_position = url.find('@')
69 | return "%s%s%s" % (
70 | url[:start_position],
71 | '*' * (end_position - start_position),
72 | url[end_position:])
73 |
74 |
75 | if __name__ == "__main__":
76 | parser = OptionParser(usage="%prog [options] plugin.zip")
77 | parser.add_option(
78 | "-w", "--password", dest="password",
79 | help="Password for plugin site", metavar="******")
80 | parser.add_option(
81 | "-u", "--username", dest="username",
82 | help="Username of plugin site", metavar="user")
83 | parser.add_option(
84 | "-p", "--port", dest="port",
85 | help="Server port to connect to", metavar="80")
86 | parser.add_option(
87 | "-s", "--server", dest="server",
88 | help="Specify server name", metavar="plugins.qgis.org")
89 | options, args = parser.parse_args()
90 | if len(args) != 1:
91 | print("Please specify zip file.\n")
92 | parser.print_help()
93 | sys.exit(1)
94 | if not options.server:
95 | options.server = SERVER
96 | if not options.port:
97 | options.port = PORT
98 | if not options.username:
99 | # interactive mode
100 | username = getpass.getuser()
101 | print("Please enter user name [%s] :" % username, end=' ')
102 |
103 | res = input()
104 | if res != "":
105 | options.username = res
106 | else:
107 | options.username = username
108 | if not options.password:
109 | # interactive mode
110 | options.password = getpass.getpass()
111 | main(options, args)
112 |
--------------------------------------------------------------------------------
/autotrace.py:
--------------------------------------------------------------------------------
1 | from math import atan2, cos, sin, radians
2 |
3 | from qgis.core import QgsTask, QgsMessageLog
4 |
5 |
6 | class AutotraceSubTask(QgsTask):
7 |
8 | def __init__(self, pointtool, vlayer, clicked_point=None):
9 | super().__init__(
10 | 'Task for switching mode to autotrace',
11 | QgsTask.CanCancel
12 | )
13 | self.pointtool = pointtool
14 | self.vlayer = vlayer
15 | self.pseudo_anchors = []
16 | self.path = []
17 | self.clicked_point = clicked_point
18 |
19 | def run(self):
20 |
21 | if self.clicked_point:
22 | self.pseudo_anchors.append(self.pointtool.anchors[-1])
23 | self.pseudo_anchors.append(self.clicked_point)
24 | self.clicked_point = None
25 | else:
26 | self.pseudo_anchors.append(self.pointtool.anchors[-2])
27 | self.pseudo_anchors.append(self.pointtool.anchors[-1])
28 |
29 | result_path = self.follow_next_segment(initial=True)
30 | self.path += result_path
31 |
32 |
33 | for _ in range(5):
34 | # check isCanceled() to handle cancellation
35 | if self.isCanceled():
36 | return False
37 |
38 | result_path = self.follow_next_segment()
39 | self.path += result_path[1:]
40 |
41 | return True
42 |
43 |
44 | def follow_next_segment(self, initial=False):
45 | _, _, i0, j0 = self.pseudo_anchors[-2]
46 | _, _, i1, j1 = self.pseudo_anchors[-1]
47 |
48 | direction = atan2(j1 - j0, i1 - i0)
49 | distance = 5
50 |
51 | if initial:
52 | # self.pointtool.remove_last_anchor_point(undo_edit=False, redraw=False)
53 | self.pseudo_anchors.pop()
54 | i1, j1 = i0, j0
55 |
56 | points = self.search_near_points((i1, j1), direction, distance)
57 |
58 | costs = []
59 | paths = []
60 |
61 | for point in points:
62 | i2, j2 = point
63 | # x2, y2 = self.pointtool.to_coords(i2, j2)
64 |
65 | path, cost = self.pointtool.trace_over_image((i1, j1), (i2, j2))
66 | costs.append(cost)
67 | paths.append(path)
68 |
69 | min_cost = min(costs)
70 | min_cost_index = costs.index(min_cost)
71 |
72 | best_point = points[min_cost_index]
73 | best_path = paths[min_cost_index]
74 | i, j = best_point
75 | x, y = self.pointtool.to_coords(i, j)
76 | self.pseudo_anchors.append((x, y, i, j))
77 |
78 | return best_path
79 |
80 |
81 | def search_near_points(self, point, direction, distance):
82 | '''
83 | Returns list of points near last point in the given direction,
84 | at a given distance with given space between points.
85 | '''
86 |
87 | points = []
88 |
89 | i1, j1 = point
90 |
91 | angles = [direction + radians(i) for i in range(-60, 60, 10)]
92 |
93 | for angle in angles:
94 | i2 = i1 + distance * cos(angle)
95 | j2 = j1 + distance * sin(angle)
96 |
97 | points.append((int(i2), int(j2)))
98 |
99 | return points
100 |
101 | def finished(self, result):
102 | '''
103 | Call callback function if self.run was successful
104 | '''
105 |
106 | if result:
107 | vlayer = self.vlayer
108 | self.pointtool.draw_path(self.path, vlayer, was_tracing=True)
109 | x, y, i, j = self.pseudo_anchors[-1]
110 | self.pointtool.add_anchor_points(x, y, i, j)
111 | self.pointtool.pan(x, y)
112 | self.pointtool.redraw()
113 | self.pointtool.update_rubber_band()
114 |
115 |
116 | def cancel(self):
117 | '''
118 | Executed when run catches cancel signal.
119 | Terminates the QgsTask.
120 | '''
121 |
122 | super().cancel()
123 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/astar.py:
--------------------------------------------------------------------------------
1 | '''
2 | Module performs searching of the best path on 2D grid between
3 | two given points by using famous A* method.
4 | Code is based on example from
5 | https://www.redblobgames.com/pathfinding/a-star/implementation.html
6 | '''
7 |
8 | import heapq
9 |
10 | from qgis.core import QgsTask, QgsMessageLog
11 |
12 | class PriorityQueue:
13 | def __init__(self):
14 | self.elements = []
15 |
16 | def empty(self):
17 | return len(self.elements) == 0
18 |
19 | def put(self, item, priority):
20 | heapq.heappush(self.elements, (priority, item))
21 |
22 | def get(self):
23 | return heapq.heappop(self.elements)[1]
24 |
25 | def heuristic(a, b):
26 | (x1, y1) = a
27 | (x2, y2) = b
28 | return abs(x1 - x2) + abs(y1 - y2)
29 |
30 | def get_neighbors(size_i, size_j, ij):
31 | """ returns possible neighbors of a numpy cell """
32 | i,j = ij
33 | neighbors = set()
34 | if i>0:
35 | neighbors.add((i-1, j))
36 | if j>0:
37 | neighbors.add((i, j-1))
38 | if i list of map layers that were added
66 |
67 | .. note:: The QgsInterface api does not include this method,
68 | it is added here as a helper to facilitate testing.
69 | """
70 | #LOGGER.debug('addLayers called on qgis_interface')
71 | #LOGGER.debug('Number of layers being added: %s' % len(layers))
72 | #LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers()))
73 | current_layers = self.canvas.layers()
74 | final_layers = []
75 | for layer in current_layers:
76 | final_layers.append(QgsMapCanvasLayer(layer))
77 | for layer in layers:
78 | final_layers.append(QgsMapCanvasLayer(layer))
79 |
80 | self.canvas.setLayerSet(final_layers)
81 | #LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers()))
82 |
83 | @pyqtSlot('QgsMapLayer')
84 | def addLayer(self, layer):
85 | """Handle a layer being added to the registry so it shows up in canvas.
86 |
87 | :param layer: list list of map layers that were added
88 |
89 | .. note: The QgsInterface api does not include this method, it is added
90 | here as a helper to facilitate testing.
91 |
92 | .. note: The addLayer method was deprecated in QGIS 1.8 so you should
93 | not need this method much.
94 | """
95 | pass
96 |
97 | @pyqtSlot()
98 | def removeAllLayers(self):
99 | """Remove layers from the canvas before they get deleted."""
100 | self.canvas.setLayerSet([])
101 |
102 | def newProject(self):
103 | """Create new project."""
104 | # noinspection PyArgumentList
105 | QgsMapLayerRegistry.instance().removeAllMapLayers()
106 |
107 | # ---------------- API Mock for QgsInterface follows -------------------
108 |
109 | def zoomFull(self):
110 | """Zoom to the map full extent."""
111 | pass
112 |
113 | def zoomToPrevious(self):
114 | """Zoom to previous view extent."""
115 | pass
116 |
117 | def zoomToNext(self):
118 | """Zoom to next view extent."""
119 | pass
120 |
121 | def zoomToActiveLayer(self):
122 | """Zoom to extent of active layer."""
123 | pass
124 |
125 | def addVectorLayer(self, path, base_name, provider_key):
126 | """Add a vector layer.
127 |
128 | :param path: Path to layer.
129 | :type path: str
130 |
131 | :param base_name: Base name for layer.
132 | :type base_name: str
133 |
134 | :param provider_key: Provider key e.g. 'ogr'
135 | :type provider_key: str
136 | """
137 | pass
138 |
139 | def addRasterLayer(self, path, base_name):
140 | """Add a raster layer given a raster layer file name
141 |
142 | :param path: Path to layer.
143 | :type path: str
144 |
145 | :param base_name: Base name for layer.
146 | :type base_name: str
147 | """
148 | pass
149 |
150 | def activeLayer(self):
151 | """Get pointer to the active layer (layer selected in the legend)."""
152 | # noinspection PyArgumentList
153 | layers = QgsMapLayerRegistry.instance().mapLayers()
154 | for item in layers:
155 | return layers[item]
156 |
157 | def addToolBarIcon(self, action):
158 | """Add an icon to the plugins toolbar.
159 |
160 | :param action: Action to add to the toolbar.
161 | :type action: QAction
162 | """
163 | pass
164 |
165 | def removeToolBarIcon(self, action):
166 | """Remove an action (icon) from the plugin toolbar.
167 |
168 | :param action: Action to add to the toolbar.
169 | :type action: QAction
170 | """
171 | pass
172 |
173 | def addToolBar(self, name):
174 | """Add toolbar with specified name.
175 |
176 | :param name: Name for the toolbar.
177 | :type name: str
178 | """
179 | pass
180 |
181 | def mapCanvas(self):
182 | """Return a pointer to the map canvas."""
183 | return self.canvas
184 |
185 | def mainWindow(self):
186 | """Return a pointer to the main window.
187 |
188 | In case of QGIS it returns an instance of QgisApp.
189 | """
190 | pass
191 |
192 | def addDockWidget(self, area, dock_widget):
193 | """Add a dock widget to the main window.
194 |
195 | :param area: Where in the ui the dock should be placed.
196 | :type area:
197 |
198 | :param dock_widget: A dock widget to add to the UI.
199 | :type dock_widget: QDockWidget
200 | """
201 | pass
202 |
203 | def legendInterface(self):
204 | """Get the legend."""
205 | return self.canvas
206 |
--------------------------------------------------------------------------------
/pointtool_states.py:
--------------------------------------------------------------------------------
1 | '''
2 | Module contains States for pointtool.
3 | '''
4 |
5 | from math import atan2, cos, sin, radians
6 |
7 | from qgis.core import QgsApplication
8 |
9 | from .autotrace import AutotraceSubTask
10 |
11 |
12 | class State:
13 | '''
14 | Abstract class for the state
15 | '''
16 |
17 | def __init__(self, pointtool):
18 | self.pointtool = pointtool
19 |
20 | def click_rmb(self, mouseEvent, vlayer):
21 | '''
22 | Event when the user clicks on the map with the right button
23 | '''
24 |
25 | # finish point path if it was last point
26 | self.pointtool.anchors = []
27 |
28 | # hide all markers
29 | while self.pointtool.markers:
30 | marker = self.pointtool.markers.pop()
31 | self.pointtool.canvas().scene().removeItem(marker)
32 |
33 | # hide rubber_band
34 | self.pointtool.rubber_band.hide()
35 |
36 | # change state
37 | self.pointtool.change_state(WaitingFirstPointState)
38 |
39 | def click_lmb(self, mouseEvent, vlayer):
40 | '''
41 | Event when the user clicks on the map with the left button
42 | '''
43 |
44 | # self.pointtool.last_mouse_event_pos = mouseEvent.pos()
45 | # hide rubber_band
46 | self.pointtool.rubber_band.hide()
47 |
48 | # check if he haven't any new tasks yet
49 | if self.pointtool.tracking_is_active:
50 | self.pointtool.display_message(
51 | " ",
52 | "Please wait till the last segment is finished" +
53 | " or terminate tracing by hitting Esc",
54 | level='Critical',
55 | duration=1,
56 | )
57 | return False
58 |
59 | # acquire point coordinates from mouseEvent
60 | qgsPoint = self.pointtool.toMapCoordinates(mouseEvent.pos())
61 | x1, y1 = qgsPoint.x(), qgsPoint.y()
62 |
63 | if self.pointtool.to_indexes is None:
64 | self.pointtool.display_message(
65 | "Missing Layer",
66 | "Please select correct raster layer",
67 | level='Critical',
68 | duration=2,
69 | )
70 | return False
71 |
72 | if self.pointtool.snap2_tolerance:
73 | x1, y1 = self.pointtool.snap_to_itself(x1, y1, self.pointtool.snap2_tolerance)
74 | i1, j1 = self.pointtool.to_indexes(x1, y1)
75 | self.pointtool.add_anchor_points(x1, y1, i1, j1)
76 |
77 | return True
78 |
79 |
80 | class WaitingFirstPointState(State):
81 | '''
82 | State of waiting the user to click on the first point in the line.
83 | Is active when the user is about to begin tracing new line.
84 | After the user clicks on the left mouse button
85 | it changes the state to WaitingMiddlePointState.
86 | '''
87 |
88 | def click_lmb(self, mouseEvent, vlayer):
89 |
90 | if super().click_lmb(mouseEvent, vlayer) is False:
91 | return
92 |
93 | # change state
94 | self.pointtool.change_state(WaitingMiddlePointState)
95 | # self.pointtool.change_state(AutoFollowingLineState)
96 |
97 | def click_rmb(self, mouseEvent, vlayer):
98 | pass
99 |
100 |
101 | class WaitingMiddlePointState(State):
102 | '''
103 | State of waiting the user to click on the next point in the line.
104 | Is active when the user is already clicked on at least one point.
105 | After the user clicks on the left mouse button it keeps the state.
106 | After the user clicks on the right mouse button it finishes the line and
107 | switches the state to WaitingFirstPointState.
108 |
109 | '''
110 |
111 | def click_lmb(self, mouseEvent, vlayer):
112 | if super().click_lmb(mouseEvent, vlayer) is False:
113 | return
114 |
115 | x1, y1, i1, j1 = self.pointtool.anchors[-1]
116 |
117 | if self.pointtool.tracing_mode.is_auto():
118 |
119 | # perform autotrace
120 | self.autotrace_task = AutotraceSubTask(
121 | self.pointtool,
122 | vlayer,
123 | clicked_point=self.pointtool.anchors[-1],
124 | )
125 | # self.pointtool.remove_last_anchor_point(undo_edit=False, redraw=False)
126 |
127 | QgsApplication.taskManager().addTask(
128 | self.autotrace_task,
129 | )
130 |
131 | else:
132 | self.pointtool.trace(x1, y1, i1, j1, vlayer)
133 |
134 | def click_rmb(self, mouseEvent, vlayer):
135 |
136 | super().click_rmb(mouseEvent, vlayer)
137 |
138 | # # add last feature to spatial index to perform fast search of closet points
139 | # self.pointtool.add_last_feature_to_spindex(vlayer)
140 |
141 |
142 | class AutoFollowingLineState(State):
143 | '''
144 | This state is active when raster_tracer is trying to
145 | perform auto-following of the line.
146 | '''
147 |
148 | def click_lmb(self, mouseEvent, vlayer):
149 | if super().click_lmb(mouseEvent, vlayer) is False:
150 | return
151 |
152 | self.follow_next_segment(vlayer, initial=True)
153 |
154 | for _ in range(25):
155 | self.follow_next_segment(vlayer)
156 | # while True:
157 | # if self.pointtool.ready is True:
158 | # break
159 | self.pointtool.redraw()
160 | self.pointtool.update_rubber_band()
161 | # print('a')
162 |
163 |
164 | def click_rmb(self, mouseEvent, vlayer):
165 | super().click_rmb(mouseEvent, vlayer)
166 |
167 | def follow_next_segment(self, vlayer, initial=False):
168 | _, _, i0, j0 = self.pointtool.anchors[-2]
169 | _, _, i1, j1 = self.pointtool.anchors[-1]
170 |
171 | direction = atan2(j1 - j0, i1 - i0)
172 | distance = 5
173 |
174 | if initial:
175 | self.pointtool.remove_last_anchor_point(undo_edit=False)
176 | i1, j1 = i0, j0
177 |
178 | points = self.search_near_points((i1, j1), direction, distance)
179 |
180 | costs = []
181 | paths = []
182 |
183 | for point in points:
184 | i2, j2 = point
185 | x2, y2 = self.pointtool.to_coords(i2, j2)
186 |
187 | path, cost = self.pointtool.trace_over_image((i1, j1), (i2, j2))
188 | costs.append(cost)
189 | paths.append(path)
190 |
191 | min_cost = min(costs)
192 | min_cost_index = costs.index(min_cost)
193 |
194 | best_point = points[min_cost_index]
195 | best_path = paths[min_cost_index]
196 | i, j = best_point
197 | x, y = self.pointtool.to_coords(i, j)
198 |
199 | if len(self.pointtool.anchors)>1:
200 | self.pointtool.draw_path(best_path, vlayer, was_tracing=True)
201 | self.pointtool.add_anchor_points(x, y, i, j)
202 | else:
203 | self.pointtool.add_anchor_points(x, y, i, j)
204 | self.pointtool.draw_path(best_path, vlayer, was_tracing=True)
205 |
206 | self.pointtool.pan(x, y)
207 |
208 | def search_near_points(self, point, direction, distance):
209 | '''
210 | Returns list of points near last point in the given direction,
211 | at a given distance with given space between points.
212 | '''
213 |
214 | points = []
215 |
216 | i1, j1 = point
217 |
218 | angles = [direction + radians(i) for i in range(-60, 60, 10)]
219 |
220 | for angle in angles:
221 | i2 = i1 + distance * cos(angle)
222 | j2 = j1 + distance * sin(angle)
223 |
224 | points.append((int(i2), int(j2)))
225 |
226 | return points
227 |
--------------------------------------------------------------------------------
/help/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # RasterTracer 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.imgmath', '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'RasterTracer'
44 | copyright = u'2013, Mikhail Kondratyev'
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', 'RasterTracer.tex', u'RasterTracer Documentation',
182 | u'Mikhail Kondratyev', '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'RasterTracer Documentation',
215 | [u'Mikhail Kondratyev'], 1)
216 | ]
217 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | #/***************************************************************************
2 | # RasterTracer
3 | #
4 | # This plugin traces the underlying raster map
5 | # -------------------
6 | # begin : 2019-11-09
7 | # git sha : $Format:%H$
8 | # copyright : (C) 2019 by Mikhail Kondratyev
9 | # email : mkondratyev85@gmail.com
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 | raster_tracer.py raster_tracer_dockwidget.py
42 |
43 | PLUGINNAME = raster_tracer
44 |
45 | PY_FILES = \
46 | __init__.py \
47 | raster_tracer.py raster_tracer_dockwidget.py
48 |
49 | UI_FILES = raster_tracer_dockwidget_base.ui
50 |
51 | EXTRAS = metadata.txt icon.png
52 |
53 | EXTRA_DIRS =
54 |
55 | COMPILED_RESOURCE_FILES = resources.py
56 |
57 | PEP8EXCLUDE=pydev,resources.py,conf.py,third_party,ui
58 |
59 | # QGISDIR points to the location where your plugin should be installed.
60 | # This varies by platform, relative to your HOME directory:
61 | # * Linux:
62 | # .local/share/QGIS/QGIS3/profiles/default/python/plugins/
63 | # * Mac OS X:
64 | # Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins
65 | # * Windows:
66 | # AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins'
67 |
68 | QGISDIR=/home/fatune/.local/share/QGIS/QGIS3/profiles/default/python/plugins/
69 |
70 | #################################################
71 | # Normally you would not need to edit below here
72 | #################################################
73 |
74 | HELP = help/build/html
75 |
76 | PLUGIN_UPLOAD = $(c)/plugin_upload.py
77 |
78 | RESOURCE_SRC=$(shell grep '^ *@@g;s/.*>//g' | tr '\n' ' ')
79 |
80 | .PHONY: default
81 | default:
82 | @echo While you can use make to build and deploy your plugin, pb_tool
83 | @echo is a much better solution.
84 | @echo A Python script, pb_tool provides platform independent management of
85 | @echo your plugins and runs anywhere.
86 | @echo You can install pb_tool using: pip install pb_tool
87 | @echo See https://g-sherman.github.io/plugin_build_tool/ for info.
88 |
89 | compile: $(COMPILED_RESOURCE_FILES)
90 |
91 | %.py : %.qrc $(RESOURCES_SRC)
92 | pyrcc5 -o $*.py $<
93 |
94 | %.qm : %.ts
95 | $(LRELEASE) $<
96 |
97 | test: compile transcompile
98 | @echo
99 | @echo "----------------------"
100 | @echo "Regression Test Suite"
101 | @echo "----------------------"
102 |
103 | @# Preceding dash means that make will continue in case of errors
104 | @-export PYTHONPATH=`pwd`:$(PYTHONPATH); \
105 | export QGIS_DEBUG=0; \
106 | export QGIS_LOG_FILE=/dev/null; \
107 | nosetests -v --with-id --with-coverage --cover-package=. \
108 | 3>&1 1>&2 2>&3 3>&- || true
109 | @echo "----------------------"
110 | @echo "If you get a 'no module named qgis.core error, try sourcing"
111 | @echo "the helper script we have provided first then run make test."
112 | @echo "e.g. source run-env-linux.sh ; make test"
113 | @echo "----------------------"
114 |
115 | deploy: compile doc transcompile
116 | @echo
117 | @echo "------------------------------------------"
118 | @echo "Deploying plugin to your .qgis2 directory."
119 | @echo "------------------------------------------"
120 | # The deploy target only works on unix like operating system where
121 | # the Python plugin directory is located at:
122 | # $HOME/$(QGISDIR)/python/plugins
123 | mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
124 | cp -vf $(PY_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
125 | cp -vf $(UI_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
126 | cp -vf $(COMPILED_RESOURCE_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
127 | cp -vf $(EXTRAS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
128 | cp -vfr i18n $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
129 | cp -vfr $(HELP) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/help
130 | # Copy extra directories if any
131 | (foreach EXTRA_DIR,(EXTRA_DIRS), cp -R (EXTRA_DIR) (HOME)/(QGISDIR)/python/plugins/(PLUGINNAME)/;)
132 |
133 |
134 | # The dclean target removes compiled python files from plugin directory
135 | # also deletes any .git entry
136 | dclean:
137 | @echo
138 | @echo "-----------------------------------"
139 | @echo "Removing any compiled python files."
140 | @echo "-----------------------------------"
141 | find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete
142 | find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname ".git" -prune -exec rm -Rf {} \;
143 |
144 |
145 | derase:
146 | @echo
147 | @echo "-------------------------"
148 | @echo "Removing deployed plugin."
149 | @echo "-------------------------"
150 | rm -Rf $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)
151 |
152 | zip: deploy dclean
153 | @echo
154 | @echo "---------------------------"
155 | @echo "Creating plugin zip bundle."
156 | @echo "---------------------------"
157 | # The zip target deploys the plugin and creates a zip file with the deployed
158 | # content. You can then upload the zip file on http://plugins.qgis.org
159 | rm -f $(PLUGINNAME).zip
160 | cd $(HOME)/$(QGISDIR)/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME)
161 |
162 | package: compile
163 | # Create a zip package of the plugin named $(PLUGINNAME).zip.
164 | # This requires use of git (your plugin development directory must be a
165 | # git repository).
166 | # To use, pass a valid commit or tag as follows:
167 | # make package VERSION=Version_0.3.2
168 | @echo
169 | @echo "------------------------------------"
170 | @echo "Exporting plugin to zip package. "
171 | @echo "------------------------------------"
172 | rm -f $(PLUGINNAME).zip
173 | git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION)
174 | echo "Created package: $(PLUGINNAME).zip"
175 |
176 | upload: zip
177 | @echo
178 | @echo "-------------------------------------"
179 | @echo "Uploading plugin to QGIS Plugin repo."
180 | @echo "-------------------------------------"
181 | $(PLUGIN_UPLOAD) $(PLUGINNAME).zip
182 |
183 | transup:
184 | @echo
185 | @echo "------------------------------------------------"
186 | @echo "Updating translation files with any new strings."
187 | @echo "------------------------------------------------"
188 | @chmod +x scripts/update-strings.sh
189 | @scripts/update-strings.sh $(LOCALES)
190 |
191 | transcompile:
192 | @echo
193 | @echo "----------------------------------------"
194 | @echo "Compiled translation files to .qm files."
195 | @echo "----------------------------------------"
196 | @chmod +x scripts/compile-strings.sh
197 | @scripts/compile-strings.sh $(LRELEASE) $(LOCALES)
198 |
199 | transclean:
200 | @echo
201 | @echo "------------------------------------"
202 | @echo "Removing compiled translation files."
203 | @echo "------------------------------------"
204 | rm -f i18n/*.qm
205 |
206 | clean:
207 | @echo
208 | @echo "------------------------------------"
209 | @echo "Removing uic and rcc generated files"
210 | @echo "------------------------------------"
211 | rm $(COMPILED_UI_FILES) $(COMPILED_RESOURCE_FILES)
212 |
213 | doc:
214 | @echo
215 | @echo "------------------------------------"
216 | @echo "Building documentation using sphinx."
217 | @echo "------------------------------------"
218 | cd help; make html
219 |
220 | pylint:
221 | @echo
222 | @echo "-----------------"
223 | @echo "Pylint violations"
224 | @echo "-----------------"
225 | @pylint --reports=n --rcfile=pylintrc . || true
226 | @echo
227 | @echo "----------------------"
228 | @echo "If you get a 'no module named qgis.core' error, try sourcing"
229 | @echo "the helper script we have provided first then run make pylint."
230 | @echo "e.g. source run-env-linux.sh ; make pylint"
231 | @echo "----------------------"
232 |
233 |
234 | # Run pep8 style checking
235 | #http://pypi.python.org/pypi/pep8
236 | pep8:
237 | @echo
238 | @echo "-----------"
239 | @echo "PEP8 issues"
240 | @echo "-----------"
241 | @pep8 --repeat --ignore=E203,E121,E122,E123,E124,E125,E126,E127,E128 --exclude $(PEP8EXCLUDE) . || true
242 | @echo "-----------"
243 | @echo "Ignored in PEP8 check:"
244 | @echo $(PEP8EXCLUDE)
245 |
--------------------------------------------------------------------------------
/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 |
261 | [CLASSES]
262 |
263 | # List of interface methods to ignore, separated by a comma. This is used for
264 | # instance to not check methods defines in Zope's Interface base class.
265 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
266 |
267 | # List of method names used to declare (i.e. assign) instance attributes.
268 | defining-attr-methods=__init__,__new__,setUp
269 |
270 | # List of valid names for the first argument in a class method.
271 | valid-classmethod-first-arg=cls
272 |
273 | # List of valid names for the first argument in a metaclass class method.
274 | valid-metaclass-classmethod-first-arg=mcs
275 |
276 |
277 | [EXCEPTIONS]
278 |
279 | # Exceptions that will emit a warning when being caught. Defaults to
280 | # "Exception"
281 | overgeneral-exceptions=Exception
282 |
--------------------------------------------------------------------------------
/raster_tracer.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | /***************************************************************************
4 | RasterTracer
5 | A QGIS plugin
6 | This plugin traces the underlying raster map
7 | Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
8 | -------------------
9 | begin : 2019-11-09
10 | git sha : $Format:%H$
11 | copyright : (C) 2019 by Mikhail Kondratyev
12 | email : mkondratyev85@gmail.com
13 | ***************************************************************************/
14 |
15 | /***************************************************************************
16 | * *
17 | * This program is free software; you can redistribute it and/or modify *
18 | * it under the terms of the GNU General Public License as published by *
19 | * the Free Software Foundation; either version 2 of the License, or *
20 | * (at your option) any later version. *
21 | * *
22 | ***************************************************************************/
23 | """
24 | from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication, Qt
25 | from qgis.PyQt.QtGui import QIcon
26 | from qgis.PyQt.QtWidgets import QAction, QApplication
27 | # Initialize Qt resources from file resources.py
28 | from .resources import *
29 |
30 |
31 | # Import the code for the DockWidget
32 | from .raster_tracer_dockwidget import RasterTracerDockWidget
33 | import os.path
34 |
35 |
36 | from qgis.core import QgsProject, QgsVectorLayer
37 |
38 | from .pointtool import PointTool
39 |
40 |
41 | class RasterTracer:
42 | """QGIS Plugin Implementation."""
43 |
44 | def __init__(self, iface):
45 | """Constructor.
46 |
47 | :param iface: An interface instance that will be passed to this class
48 | which provides the hook by which you can manipulate the QGIS
49 | application at run time.
50 | :type iface: QgsInterface
51 | """
52 | # Save reference to the QGIS interface
53 | self.iface = iface
54 |
55 | # initialize plugin directory
56 | self.plugin_dir = os.path.dirname(__file__)
57 |
58 | # initialize locale
59 | locale = QSettings().value('locale/userLocale')[0:2]
60 | locale_path = os.path.join(
61 | self.plugin_dir,
62 | 'i18n',
63 | 'RasterTracer_{}.qm'.format(locale))
64 |
65 | if os.path.exists(locale_path):
66 | self.translator = QTranslator()
67 | self.translator.load(locale_path)
68 | QCoreApplication.installTranslator(self.translator)
69 |
70 | # Declare instance attributes
71 | self.actions = []
72 | self.menu = self.tr(u'&Raster Tracer')
73 | # TODO: We are going to let the user set this up in a future iteration
74 | self.toolbar = self.iface.addToolBar(u'RasterTracer')
75 | self.toolbar.setObjectName(u'RasterTracer')
76 |
77 | # print "** INITIALIZING RasterTracer"
78 |
79 | self.pluginIsActive = False
80 | self.dockwidget = None
81 |
82 | # noinspection PyMethodMayBeStatic
83 | def tr(self, message):
84 | """Get the translation for a string using Qt translation API.
85 |
86 | We implement this ourselves since we do not inherit QObject.
87 |
88 | :param message: String for translation.
89 | :type message: str, QString
90 |
91 | :returns: Translated version of message.
92 | :rtype: QString
93 | """
94 | # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
95 | return QCoreApplication.translate('RasterTracer', message)
96 |
97 | def add_action(self,
98 | icon_path,
99 | text,
100 | callback,
101 | enabled_flag=True,
102 | add_to_menu=True,
103 | add_to_toolbar=True,
104 | status_tip=None,
105 | whats_this=None,
106 | parent=None):
107 | """Add a toolbar icon to the toolbar.
108 |
109 | :param icon_path: Path to the icon for this action. Can be a resource
110 | path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
111 | :type icon_path: str
112 |
113 | :param text: Text that should be shown in menu items for this action.
114 | :type text: str
115 |
116 | :param callback: Function to be called when the action is triggered.
117 | :type callback: function
118 |
119 | :param enabled_flag: A flag indicating if the action should be enabled
120 | by default. Defaults to True.
121 | :type enabled_flag: bool
122 |
123 | :param add_to_menu: Flag indicating whether the action should also
124 | be added to the menu. Defaults to True.
125 | :type add_to_menu: bool
126 |
127 | :param add_to_toolbar: Flag indicating whether the action should also
128 | be added to the toolbar. Defaults to True.
129 | :type add_to_toolbar: bool
130 |
131 | :param status_tip: Optional text to show in a popup when mouse pointer
132 | hovers over the action.
133 | :type status_tip: str
134 |
135 | :param parent: Parent widget for the new action. Defaults None.
136 | :type parent: QWidget
137 |
138 | :param whats_this: Optional text to show in the status bar when the
139 | mouse pointer hovers over the action.
140 |
141 | :returns: The action that was created. Note that the action is also
142 | added to self.actions list.
143 | :rtype: QAction
144 | """
145 |
146 | icon = QIcon(icon_path)
147 | action = QAction(icon, text, parent)
148 | action.triggered.connect(callback)
149 | action.setEnabled(enabled_flag)
150 |
151 | if status_tip is not None:
152 | action.setStatusTip(status_tip)
153 |
154 | if whats_this is not None:
155 | action.setWhatsThis(whats_this)
156 |
157 | if add_to_toolbar:
158 | self.toolbar.addAction(action)
159 |
160 | if add_to_menu:
161 | self.iface.addPluginToMenu(
162 | self.menu,
163 | action)
164 |
165 | self.actions.append(action)
166 |
167 | return action
168 |
169 | def initGui(self):
170 | """Create the menu entries and toolbar icons inside the QGIS GUI."""
171 |
172 | icon_path = ':/plugins/raster_tracer/icon.png'
173 | self.add_action(
174 | icon_path,
175 | text=self.tr(u'Trace Raster'),
176 | callback=self.run,
177 | parent=self.iface.mainWindow())
178 |
179 | # -------------------------------------------------------------------------
180 |
181 | def onClosePlugin(self):
182 | """Cleanup necessary items here when plugin dockwidget is closed"""
183 |
184 | # disconnects
185 | self.dockwidget.closingPlugin.disconnect(self.onClosePlugin)
186 |
187 | # remove this statement if dockwidget is to remain
188 | # for reuse if plugin is reopened
189 | # Commented next statement since it causes QGIS crashe
190 | # when closing the docked window:
191 | self.dockwidget = None
192 |
193 | self.pluginIsActive = False
194 |
195 | self.tool_identify.deactivate()
196 | QApplication.restoreOverrideCursor()
197 | self.map_canvas.setMapTool(self.last_maptool)
198 |
199 | def unload(self):
200 | """Removes the plugin menu item and icon from QGIS GUI."""
201 |
202 | # print( "** UNLOAD RasterTracer")
203 |
204 | for action in self.actions:
205 | self.iface.removePluginMenu(
206 | self.tr(u'&Raster Tracer'),
207 | action)
208 | self.iface.removeToolBarIcon(action)
209 | # remove the toolbar
210 | del self.toolbar
211 |
212 | # -------------------------------------------------------------------------
213 |
214 | def activate_map_tool(self):
215 | ''' Activates map tool'''
216 | self.last_maptool = self.iface.mapCanvas().mapTool()
217 | self.map_canvas.setMapTool(self.tool_identify)
218 |
219 | def run(self):
220 | """Run method that loads and starts the plugin"""
221 |
222 | if self.pluginIsActive:
223 | self.activate_map_tool()
224 | return
225 |
226 | self.pluginIsActive = True
227 |
228 | # print "** STARTING RasterTracer"
229 |
230 | # dockwidget may not exist if:
231 | # first run of plugin
232 | # removed on close (see self.onClosePlugin method)
233 | if self.dockwidget is None:
234 | # Create the dockwidget (after translation) and keep reference
235 | self.dockwidget = RasterTracerDockWidget()
236 |
237 | # connect to provide cleanup on closing of dockwidget
238 | self.dockwidget.closingPlugin.connect(self.onClosePlugin)
239 |
240 | # show the dockwidget
241 | self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dockwidget)
242 | self.dockwidget.show()
243 |
244 | self.map_canvas = self.iface.mapCanvas()
245 | # vlayer = self.iface.layerTreeView().selectedLayers()[0]
246 | self.tool_identify = PointTool(self.map_canvas, self.iface, self.turn_off_snap)
247 | # self.map_canvas.setMapTool(self.tool_identify)
248 | self.activate_map_tool()
249 |
250 | excluded_layers = [l for l in QgsProject().instance().mapLayers().values()
251 | if isinstance(l, QgsVectorLayer)]
252 | self.dockwidget.mMapLayerComboBox.setExceptedLayerList(excluded_layers)
253 | self.dockwidget.mMapLayerComboBox.currentIndexChanged.connect(self.raster_layer_changed)
254 | self.tool_identify.raster_layer_has_changed(self.dockwidget.mMapLayerComboBox.currentLayer())
255 |
256 | self.dockwidget.checkBoxColor.stateChanged.connect(self.checkBoxColor_changed)
257 | self.dockwidget.mColorButton.colorChanged.connect(self.checkBoxColor_changed)
258 |
259 | self.dockwidget.checkBoxSnap.stateChanged.connect(self.checkBoxSnap_changed)
260 | self.dockwidget.mQgsSpinBox.valueChanged.connect(self.checkBoxSnap_changed)
261 |
262 | self.map_canvas.setMapTool(self.tool_identify)
263 | # self.last_maptool = self.iface.mapCanvas().mapTool()
264 |
265 | self.dockwidget.checkBoxSmooth.stateChanged.connect(self.checkBoxSmooth_changed)
266 | self.dockwidget.checkBoxSmooth.setChecked(True)
267 |
268 | self.dockwidget.checkBoxSnap2.stateChanged.connect(self.checkBoxSnap2_changed)
269 | self.dockwidget.SpinBoxSnap.valueChanged.connect(self.checkBoxSnap2_changed)
270 |
271 |
272 | def raster_layer_changed(self):
273 | self.tool_identify.raster_layer_has_changed(self.dockwidget.mMapLayerComboBox.currentLayer())
274 | self.checkBoxColor_changed()
275 |
276 | def checkBoxSmooth_changed(self):
277 | self.tool_identify.smooth_line = (self.dockwidget.checkBoxSmooth.isChecked() is True)
278 |
279 | def checkBoxSnap_changed(self):
280 | if self.dockwidget.checkBoxSnap.isChecked():
281 | self.dockwidget.mQgsSpinBox.setEnabled(True)
282 | snap_tolerance = self.dockwidget.mQgsSpinBox.value()
283 | self.tool_identify.snap_tolerance_changed(snap_tolerance)
284 | else:
285 | self.dockwidget.mQgsSpinBox.setEnabled(False)
286 | self.tool_identify.snap_tolerance_changed(None)
287 |
288 | def checkBoxSnap2_changed(self):
289 | if self.dockwidget.checkBoxSnap2.isChecked():
290 | self.dockwidget.SpinBoxSnap.setEnabled(True)
291 | snap_tolerance = self.dockwidget.SpinBoxSnap.value()
292 | self.tool_identify.snap2_tolerance_changed(snap_tolerance)
293 | else:
294 | self.dockwidget.SpinBoxSnap.setEnabled(False)
295 | self.tool_identify.snap2_tolerance_changed(None)
296 |
297 |
298 | def turn_off_snap(self):
299 | self.dockwidget.checkBoxSnap.nextCheckState()
300 |
301 | def checkBoxColor_changed(self):
302 | if self.dockwidget.checkBoxColor.isChecked():
303 | self.dockwidget.mColorButton.setEnabled(True)
304 | self.dockwidget.checkBoxSnap.setEnabled(True)
305 | color = self.dockwidget.mColorButton.color()
306 | self.tool_identify.trace_color_changed(color)
307 | else:
308 | self.dockwidget.mColorButton.setEnabled(False)
309 | self.dockwidget.checkBoxSnap.setEnabled(False)
310 | self.tool_identify.trace_color_changed(False)
311 |
--------------------------------------------------------------------------------
/pointtool.py:
--------------------------------------------------------------------------------
1 | '''
2 | Main functionality of raster tracer.
3 | '''
4 |
5 | from enum import Enum
6 | from collections import namedtuple
7 | import numpy as np
8 |
9 | from qgis.core import QgsPointXY, QgsPoint, QgsGeometry, QgsFeature, \
10 | QgsVectorLayer, QgsProject, QgsWkbTypes, QgsApplication, \
11 | QgsRectangle, QgsSpatialIndex
12 | from qgis.gui import QgsMapToolEmitPoint, QgsMapToolEdit, \
13 | QgsRubberBand, QgsVertexMarker, QgsMapTool
14 | from qgis.PyQt.QtCore import Qt
15 | from qgis.PyQt.QtGui import QColor
16 | from qgis.core import Qgis
17 | from qgis.core import QgsCoordinateTransform
18 |
19 |
20 | from .astar import FindPathTask, FindPathFunction
21 | from .line_simplification import smooth, simplify
22 | from .utils import get_whole_raster, PossiblyIndexedImageError
23 | from .pointtool_states import WaitingFirstPointState
24 | from .exceptions import OutsideMapError
25 |
26 | # An point on the map where the user clicked along the line
27 | Anchor = namedtuple('Anchor', ['x', 'y', 'i', 'j'])
28 |
29 | # Flag for experimental Autofollowing mode
30 | ALLOW_AUTO_FOLLOWING = False
31 |
32 |
33 | class TracingModes(Enum):
34 | '''
35 | Possible Tracing Modes for Pointtool.
36 | LINE - straight line from start to end.
37 | PATH - tracing along color from start to end.
38 | AUTO - auto tracing mode along color in the given direction.
39 | '''
40 |
41 | LINE = 1
42 | PATH = 2
43 | AUTO = 3
44 |
45 | def next(self):
46 | '''
47 | Switches between LINE and PATH
48 | '''
49 | cls = self.__class__
50 | members = list(cls)
51 |
52 | if not ALLOW_AUTO_FOLLOWING:
53 | return members[0] if self.value == 2 else members[1]
54 |
55 | index = members.index(self) + 1
56 | if index >= len(members):
57 | index = 0
58 | return members[index]
59 |
60 | def is_tracing(self):
61 | '''
62 | Returns True if mode is PATH
63 | '''
64 | return True if self.value == 2 else False
65 |
66 | def is_auto(self):
67 | '''
68 | Returns True if mode is PATH
69 | '''
70 | return True if self.value == 3 else False
71 |
72 |
73 | # Line styles for the rubber band
74 | RUBBERBAND_LINE_STYLES = {
75 | TracingModes.PATH: Qt.DotLine,
76 | TracingModes.LINE: Qt.SolidLine,
77 | TracingModes.AUTO: Qt.DashDotLine,
78 | }
79 |
80 |
81 | class PointTool(QgsMapToolEdit):
82 | '''
83 | Implementation of interactions of the user with the main map.
84 | Will called every time the user clicks on the map
85 | or hovers the mouse over the map.
86 | '''
87 |
88 | def deactivate(self):
89 | QgsMapTool.deactivate(self)
90 | self.deactivated.emit()
91 |
92 | def __init__(self, canvas, iface, turn_off_snap, smooth=False):
93 | '''
94 | canvas - link to the QgsCanvas of the application
95 | iface - link to the Qgis Interface
96 | turn_off_snap - flag sets snapping to the nearest color
97 | smooth - flag sets smoothing of the traced path
98 | '''
99 |
100 | self.iface = iface
101 |
102 | # list of Anchors for current line
103 | self.anchors = []
104 |
105 | # for keeping track of mouse event for rubber band updating
106 | self.last_mouse_event_pos = None
107 |
108 | self.tracing_mode = TracingModes.PATH
109 |
110 | self.turn_off_snap = turn_off_snap
111 | self.smooth_line = smooth
112 |
113 | # possible variants: gray_diff, as_is, color_diff (using v from hsv)
114 | self.grid_conversion = "gray_diff"
115 |
116 | # QApplication.restoreOverrideCursor()
117 | # QApplication.setOverrideCursor(Qt.CrossCursor)
118 | QgsMapToolEmitPoint.__init__(self, canvas)
119 |
120 | self.rlayer = None
121 | self.grid_changed = None
122 | self.snap_tolerance = None # snap to color
123 | self.snap2_tolerance = None # snap to itself
124 | self.vlayer = None
125 | self.grid = None
126 | self.sample = None
127 |
128 | self.tracking_is_active = False
129 |
130 | # False = not a polygon
131 | self.rubber_band = QgsRubberBand(self.canvas(), QgsWkbTypes.LineGeometry)
132 | self.markers = []
133 | self.marker_snap = QgsVertexMarker(self.canvas())
134 | self.marker_snap.setColor(QColor(255, 0, 255))
135 |
136 | self.find_path_task = None
137 |
138 | self.change_state(WaitingFirstPointState)
139 |
140 | self.last_vlayer = None
141 |
142 | def display_message(self,
143 | title,
144 | message,
145 | level='Info',
146 | duration=2,
147 | ):
148 | '''
149 | Shows message bar to the user.
150 | `level` receives one of four possible string values:
151 | Info, Warning, Critical, Success
152 | '''
153 |
154 | LEVELS = {
155 | 'Info': Qgis.Info,
156 | 'Warning': Qgis.Warning,
157 | 'Critical': Qgis.Critical,
158 | 'Success': Qgis.Success,
159 | }
160 |
161 | self.iface.messageBar().pushMessage(
162 | title,
163 | message,
164 | LEVELS[level],
165 | duration)
166 |
167 | def change_state(self, state):
168 | self.state = state(self)
169 |
170 | def snap_tolerance_changed(self, snap_tolerance):
171 | self.snap_tolerance = snap_tolerance
172 | if snap_tolerance is None:
173 | self.marker_snap.hide()
174 | else:
175 | self.marker_snap.show()
176 |
177 | def snap2_tolerance_changed(self, snap_tolerance):
178 | self.snap2_tolerance = snap_tolerance**2
179 | # if snap_tolerance is None:
180 | # self.marker_snap.hide()
181 | # else:
182 | # self.marker_snap.show()
183 |
184 | def trace_color_changed(self, color):
185 | r, g, b = self.sample
186 |
187 | if color is False:
188 | self.grid_changed = None
189 | else:
190 | r0, g0, b0, t = color.getRgb()
191 | self.grid_changed = np.abs((r0 - r) ** 2 + (g0 - g) ** 2 +
192 | (b0 - b) ** 2)
193 |
194 | def get_current_vector_layer(self):
195 | try:
196 | vlayer = self.iface.layerTreeView().selectedLayers()[0]
197 | if isinstance(vlayer, QgsVectorLayer):
198 | if vlayer.wkbType() == QgsWkbTypes.MultiLineString:
199 | # if self.last_vlayer:
200 | # if vlayer != self.last_vlayer:
201 | # self.create_spatial_index_for_vlayer(vlayer)
202 | # else:
203 | # self.create_spatial_index_for_vlayer(vlayer)
204 | # self.last_vlayer = vlayer
205 | return vlayer
206 | else:
207 | self.display_message(
208 | " ",
209 | "The active layer must be" +
210 | " a MultiLineString vector layer",
211 | level='Warning',
212 | duration=2,
213 | )
214 | return None
215 | else:
216 | self.display_message(
217 | "Missing Layer",
218 | "Please select vector layer to draw",
219 | level='Warning',
220 | duration=2,
221 | )
222 | return None
223 | except IndexError:
224 | self.display_message(
225 | "Missing Layer",
226 | "Please select vector layer to draw",
227 | level='Warning',
228 | duration=2,
229 | )
230 | return None
231 |
232 | def raster_layer_has_changed(self, raster_layer):
233 | self.rlayer = raster_layer
234 | if self.rlayer is None:
235 | self.display_message(
236 | "Missing Layer",
237 | "Please select raster layer to trace",
238 | level='Warning',
239 | duration=2,
240 | )
241 | return
242 |
243 | try:
244 | sample, to_indexes, to_coords, to_coords_provider, \
245 | to_coords_provider2 = \
246 | get_whole_raster(self.rlayer,
247 | QgsProject.instance(),
248 | )
249 | except PossiblyIndexedImageError:
250 | self.display_message(
251 | "Missing Layer",
252 | "Can't trace indexed or gray image",
253 | level='Critical',
254 | duration=2,
255 | )
256 | return
257 |
258 | r = sample[0].astype(float)
259 | g = sample[1].astype(float)
260 | b = sample[2].astype(float)
261 | where_are_NaNs = np.isnan(r)
262 | r[where_are_NaNs] = 0
263 | where_are_NaNs = np.isnan(g)
264 | g[where_are_NaNs] = 0
265 | where_are_NaNs = np.isnan(b)
266 | b[where_are_NaNs] = 0
267 |
268 | self.sample = (r, g, b)
269 | self.grid = r + g + b
270 | self.to_indexes = to_indexes
271 | self.to_coords = to_coords
272 | self.to_coords_provider = to_coords_provider
273 | self.to_coords_provider2 = to_coords_provider2
274 |
275 | def remove_last_anchor_point(self, undo_edit=True, redraw=True):
276 | '''
277 | Removes last anchor point and last marker point
278 | '''
279 |
280 | # check if we have at least one feature to delete
281 | vlayer = self.get_current_vector_layer()
282 | if vlayer is None:
283 | return
284 | if vlayer.featureCount() < 1:
285 | return
286 |
287 | # remove last marker
288 | if self.markers:
289 | last_marker = self.markers.pop()
290 | self.canvas().scene().removeItem(last_marker)
291 |
292 | # remove last anchor
293 | if self.anchors:
294 | self.anchors.pop()
295 |
296 | if undo_edit:
297 | # it's a very ugly way of triggering single undo event
298 | self.iface.editMenu().actions()[0].trigger()
299 |
300 | if redraw:
301 | self.update_rubber_band()
302 | self.redraw()
303 |
304 | def keyPressEvent(self, e):
305 | if e.key() == Qt.Key_Backspace or e.key() == Qt.Key_B:
306 | # delete last segment if backspace is pressed
307 | self.remove_last_anchor_point()
308 | elif e.key() == Qt.Key_A:
309 | # change tracing mode
310 | self.tracing_mode = self.tracing_mode.next()
311 | self.update_rubber_band()
312 | elif e.key() == Qt.Key_S:
313 | # toggle snap mode
314 | self.turn_off_snap()
315 | elif e.key() == Qt.Key_Escape:
316 | # Abort tracing process
317 | self.abort_tracing_process()
318 |
319 | def add_anchor_points(self, x1, y1, i1, j1):
320 | '''
321 | Adds anchor points and markers to self.
322 | '''
323 |
324 | anchor = Anchor(x1, y1, i1, j1)
325 | self.anchors.append(anchor)
326 |
327 | marker = QgsVertexMarker(self.canvas())
328 | marker.setCenter(QgsPointXY(x1, y1))
329 | self.markers.append(marker)
330 |
331 | def trace_over_image(self,
332 | start,
333 | goal,
334 | do_it_as_task=False,
335 | vlayer=None):
336 | '''
337 | performs tracing
338 | '''
339 |
340 | i0, j0 = start
341 | i1, j1 = goal
342 |
343 | r, g, b, = self.sample
344 |
345 | try:
346 | r0 = r[i1, j1]
347 | g0 = g[i1, j1]
348 | b0 = b[i1, j1]
349 | except IndexError:
350 | raise OutsideMapError
351 |
352 | if self.grid_changed is None:
353 | grid = np.abs((r0 - r) ** 2 + (g0 - g) ** 2 + (b0 - b) ** 2)
354 | else:
355 | grid = self.grid_changed
356 |
357 | if do_it_as_task:
358 | # dirty hack to avoid QGIS crashing
359 | self.find_path_task = FindPathTask(
360 | grid.astype(np.dtype('l')),
361 | start,
362 | goal,
363 | self.draw_path,
364 | vlayer,
365 | )
366 |
367 | QgsApplication.taskManager().addTask(
368 | self.find_path_task,
369 | )
370 | self.tracking_is_active = True
371 | else:
372 | path, cost = FindPathFunction(
373 | grid.astype(np.dtype('l')),
374 | (i0, j0),
375 | (i1, j1),
376 | )
377 | return path, cost
378 |
379 | def trace(self, x1, y1, i1, j1, vlayer):
380 | '''
381 | Traces path from last point to given point.
382 | In case tracing is inactive just creates
383 | straight line.
384 | '''
385 |
386 | if self.tracing_mode.is_tracing():
387 | if self.snap_tolerance is not None:
388 | try:
389 | i1, j1 = self.snap(i1, j1)
390 | except OutsideMapError:
391 | return
392 |
393 | _, _, i0, j0 = self.anchors[-2]
394 | start_point = i0, j0
395 | end_point = i1, j1
396 | try:
397 | self.trace_over_image(start_point,
398 | end_point,
399 | do_it_as_task=True,
400 | vlayer=vlayer)
401 | except OutsideMapError:
402 | pass
403 | else:
404 | self.draw_path(
405 | None,
406 | vlayer,
407 | was_tracing=False,
408 | x1=x1,
409 | y1=y1,
410 | )
411 |
412 | def snap_to_itself(self, x, y, sq_tolerance=1):
413 | '''
414 | finds a nearest segment line to the current vlayer
415 | '''
416 |
417 | pt = QgsPointXY(x, y)
418 | # nearest_feature_id = self.spIndex.nearestNeighbor(pt, 1, tolerance)[0]
419 | vlayer = self.get_current_vector_layer()
420 | # feature = vlayer.getFeature(nearest_feature_id)
421 | for feature in vlayer.getFeatures():
422 | closest_point, _, _, _, sq_distance = feature.geometry().closestVertex(pt)
423 | if sq_distance < sq_tolerance:
424 | return closest_point.x(), closest_point.y()
425 | return x, y
426 |
427 | def snap(self, i, j):
428 | if self.snap_tolerance is None:
429 | return i, j
430 | if not self.tracing_mode.is_tracing():
431 | return i, j
432 | if self.grid_changed is None:
433 | return i, j
434 |
435 | size_i, size_j = self.grid.shape
436 | size = self.snap_tolerance
437 |
438 | if i < size or j < size or i + size > size_i or j + size > size_j:
439 | raise OutsideMapError
440 |
441 | grid_small = self.grid_changed
442 | grid_small = grid_small[i - size: i + size, j - size: j + size]
443 |
444 | smallest_cells = np.where(grid_small == np.amin(grid_small))
445 | coordinates = list(zip(smallest_cells[0], smallest_cells[1]))
446 |
447 | if len(coordinates) == 1:
448 | delta_i, delta_j = coordinates[0]
449 | delta_i -= size
450 | delta_j -= size
451 | else:
452 | # find the closest to the center
453 | deltas = [(i - size, j - size) for i, j in coordinates]
454 | lengths = [(i ** 2 + j ** 2) for i, j in deltas]
455 | i = lengths.index(min(lengths))
456 | delta_i, delta_j = deltas[i]
457 |
458 | return i+delta_i, j+delta_j
459 |
460 | def canvasReleaseEvent(self, mouseEvent):
461 | '''
462 | Method where the actual tracing is performed
463 | after the user clicked on the map
464 | '''
465 |
466 | vlayer = self.get_current_vector_layer()
467 |
468 | if vlayer is None:
469 | return
470 |
471 | if not vlayer.isEditable():
472 | self.display_message(
473 | "Edit mode",
474 | "Please begin editing vector layer to trace",
475 | level='Warning',
476 | duration=2,
477 | )
478 | return
479 |
480 | if self.rlayer is None:
481 | self.display_message(
482 | "Missing Layer",
483 | "Please select raster layer to trace",
484 | level='Warning',
485 | duration=2,
486 | )
487 | return
488 |
489 | if mouseEvent.button() == Qt.RightButton:
490 | self.state.click_rmb(mouseEvent, vlayer)
491 | elif mouseEvent.button() == Qt.LeftButton:
492 | self.state.click_lmb(mouseEvent, vlayer)
493 |
494 | return
495 |
496 | def draw_path(self, path, vlayer, was_tracing=True,\
497 | x1=None, y1=None):
498 | '''
499 | Draws a path after tracer found it.
500 | '''
501 |
502 | transform = QgsCoordinateTransform(QgsProject.instance().crs(),
503 | vlayer.crs(),
504 | QgsProject.instance())
505 | if was_tracing:
506 | if self.smooth_line:
507 | path = smooth(path, size=5)
508 | path = simplify(path)
509 | vlayer = self.get_current_vector_layer()
510 | current_last_point = self.to_coords(*path[-1])
511 | path_ref = [transform.transform(*self.to_coords_provider(i, j)) for i, j in path]
512 | x0, y0, _, _ = self.anchors[-2]
513 | last_point = transform.transform(*self.to_coords_provider2(x0, y0))
514 | path_ref = [last_point] + path_ref[1:]
515 | else:
516 | x0, y0, _i, _j = self.anchors[-2]
517 | current_last_point = (x1, y1)
518 | path_ref = [transform.transform(*self.to_coords_provider2(x0, y0)),
519 | transform.transform(*self.to_coords_provider2(x1, y1))]
520 |
521 |
522 | self.ready = False
523 | if len(self.anchors) == 2:
524 | vlayer.beginEditCommand("Adding new line")
525 | add_feature_to_vlayer(vlayer, path_ref)
526 | vlayer.endEditCommand()
527 | else:
528 | vlayer.beginEditCommand("Adding new segment to the line")
529 | add_to_last_feature(vlayer, path_ref)
530 | vlayer.endEditCommand()
531 | _, _, current_last_point_i, current_last_point_j = self.anchors[-1]
532 | self.anchors[-1] = current_last_point[0], current_last_point[1], current_last_point_i, current_last_point_j
533 | self.redraw()
534 | self.tracking_is_active = False
535 |
536 |
537 | def update_rubber_band(self):
538 | # this is very ugly but I can't make another way
539 | if self.last_mouse_event_pos is None:
540 | return
541 |
542 | if not self.anchors:
543 | return
544 |
545 | x0, y0, _, _ = self.anchors[-1]
546 | qgsPoint = self.toMapCoordinates(self.last_mouse_event_pos)
547 | x1, y1 = qgsPoint.x(), qgsPoint.y()
548 | points = [QgsPoint(x0, y0), QgsPoint(x1, y1)]
549 |
550 | self.rubber_band.setColor(QColor(255, 0, 0))
551 | self.rubber_band.setWidth(3)
552 |
553 | self.rubber_band.setLineStyle(
554 | RUBBERBAND_LINE_STYLES[self.tracing_mode],
555 | )
556 |
557 | vlayer = self.get_current_vector_layer()
558 | if vlayer is None:
559 | return
560 |
561 | self.rubber_band.setToGeometry(
562 | QgsGeometry.fromPolyline(points),
563 | self.vlayer,
564 | )
565 |
566 | def canvasMoveEvent(self, mouseEvent):
567 | '''
568 | Store the mouse position for the correct
569 | updating of the rubber band
570 | '''
571 |
572 | # we need at least one point to draw
573 | if not self.anchors:
574 | return
575 |
576 | if self.snap_tolerance is not None and self.tracing_mode.is_tracing():
577 | qgsPoint = self.toMapCoordinates(mouseEvent.pos())
578 | x1, y1 = qgsPoint.x(), qgsPoint.y()
579 | # i, j = get_indxs_from_raster_coords(self.geo_ref, x1, y1)
580 | i, j = self.to_indexes(x1, y1)
581 | try:
582 | i1, j1 = self.snap(i, j)
583 | except OutsideMapError:
584 | return
585 | # x1, y1 = get_coords_from_raster_indxs(self.geo_ref, i1, j1)
586 | x1, y1 = self.to_coords(i1, j1)
587 | self.marker_snap.setCenter(QgsPointXY(x1, y1))
588 |
589 | self.last_mouse_event_pos = mouseEvent.pos()
590 | self.update_rubber_band()
591 | self.redraw()
592 |
593 | def abort_tracing_process(self):
594 | '''
595 | Terminate background process of tracing raster
596 | after the user hits Esc.
597 | '''
598 |
599 | # check if we have any tasks
600 | if self.find_path_task is None:
601 | return
602 |
603 | self.tracking_is_active = False
604 |
605 | try:
606 | # send terminate signal to the task
607 | self.find_path_task.cancel()
608 | self.find_path_task = None
609 | except RuntimeError:
610 | return
611 | else:
612 | self.remove_last_anchor_point(
613 | undo_edit=False,
614 | )
615 |
616 | def redraw(self):
617 | # If caching is enabled, a simple canvas refresh might not be
618 | # sufficient to trigger a redraw and you must clear the cached image
619 | # for the layer
620 | if self.iface.mapCanvas().isCachingEnabled():
621 | vlayer = self.get_current_vector_layer()
622 | if vlayer is None:
623 | return
624 | vlayer.triggerRepaint()
625 |
626 | self.iface.mapCanvas().refresh()
627 | QgsApplication.processEvents()
628 |
629 | def pan(self, x, y):
630 | '''
631 | Move the canvas to the x, y position
632 | '''
633 | currExt = self.iface.mapCanvas().extent()
634 | canvasCenter = currExt.center()
635 | dx = x - canvasCenter.x()
636 | dy = y - canvasCenter.y()
637 | xMin = currExt.xMinimum() + dx
638 | xMax = currExt.xMaximum() + dx
639 | yMin = currExt.yMinimum() + dy
640 | yMax = currExt.yMaximum() + dy
641 | newRect = QgsRectangle(xMin, yMin, xMax, yMax)
642 | self.iface.mapCanvas().setExtent(newRect)
643 |
644 | def add_last_feature_to_spindex(self, vlayer):
645 | '''
646 | Adds last feature to spatial index
647 | '''
648 | features = list(vlayer.getFeatures())
649 | last_feature = features[-1]
650 | self.spIndex.insertFeature(last_feature)
651 |
652 | def create_spatial_index_for_vlayer(self, vlayer):
653 | '''
654 | Creates spatial index for the vlayer
655 | '''
656 |
657 | self.spIndex = QgsSpatialIndex()
658 | # features = [f for f in vlayer]
659 | self.spIndex.addFeatures(vlayer.getFeatures())
660 |
661 |
662 |
663 | def add_to_last_feature(vlayer, points):
664 | '''
665 | Adds points to the last line feature in the vlayer
666 | vlayer - QgsLayer of type MultiLine string
667 | points - list of points
668 | '''
669 | features = list(vlayer.getFeatures())
670 | last_feature = features[-1]
671 | fid = last_feature.id()
672 | geom = last_feature.geometry()
673 | points = [QgsPointXY(x, y) for x, y in points]
674 | geom.addPointsXY(points)
675 | vlayer.changeGeometry(fid, geom)
676 |
677 |
678 | def add_feature_to_vlayer(vlayer, points):
679 | '''
680 | Adds new line feature to the vlayer
681 | '''
682 |
683 | feat = QgsFeature(vlayer.fields())
684 | polyline = [QgsPoint(x, y) for x, y in points]
685 | feat.setGeometry(QgsGeometry.fromPolyline(polyline))
686 | vlayer.addFeature(feat)
687 |
688 |
--------------------------------------------------------------------------------
/resources.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Resource object code
4 | #
5 | # Created by: The Resource Compiler for PyQt5 (Qt v5.9.5)
6 | #
7 | # WARNING! All changes made in this file will be lost!
8 |
9 | from PyQt5 import QtCore
10 |
11 | qt_resource_data = b"\
12 | \x00\x00\x1c\xa7\
13 | \x89\
14 | \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
15 | \x00\x00\x17\x00\x00\x00\x18\x08\x06\x00\x00\x00\x11\x7c\x66\x75\
16 | \x00\x00\x19\x00\x7a\x54\x58\x74\x52\x61\x77\x20\x70\x72\x6f\x66\
17 | \x69\x6c\x65\x20\x74\x79\x70\x65\x20\x65\x78\x69\x66\x00\x00\x78\
18 | \xda\xad\x9b\x69\x72\x1c\x39\x96\xad\xff\x63\x15\xbd\x04\xc7\x0c\
19 | \x2c\x07\xa3\x59\xef\xe0\x2d\xbf\xbf\x03\x38\x29\x8a\x1a\xb2\xb2\
20 | \xec\x49\x95\x0a\x32\xc2\x03\x0e\xdc\xe1\x0c\x80\x97\x59\xff\xef\
21 | \x7f\xb7\xf9\x1f\xfe\xa4\x9c\x1e\x13\x62\x2e\xa9\xa6\xf4\xf0\x27\
22 | \xd4\x50\x5d\xe3\x87\xf2\xdc\x3f\xf7\xd5\x3e\xe1\xfc\x7b\x7f\xc9\
23 | \xef\x67\xf6\xe7\xf7\x8d\xfd\xf8\x92\xe3\x2d\xcf\xab\xbf\xbf\xa6\
24 | \xf5\x5e\xdf\x78\x3f\xfe\xf8\x42\x0e\xef\xfb\xfd\xe7\xf7\x4d\x1e\
25 | \xef\x38\xe5\x1d\xe8\xfd\xe0\x63\x40\xaf\x3b\x3b\x7e\x98\xef\x24\
26 | \xdf\x81\xbc\xbb\xef\xdb\xf7\x77\x53\xdd\xfd\xa1\xa5\x2f\xcb\x79\
27 | \xff\xf3\xf9\x0c\xf1\x79\xf1\xf7\xdf\x43\x26\x18\x33\xf2\xa6\x77\
28 | \xc6\x2d\x6f\xfd\x73\xfe\x75\xf7\x4e\x9e\x59\xf8\xea\x9b\xde\x39\
29 | \xff\xea\x42\xeb\x03\x3f\x47\x5f\xf8\xd7\xfb\xf4\x6b\xfc\x8c\x7e\
30 | \xdb\xf1\xf7\x01\xfc\xfc\xe9\x5b\xfc\x9e\xf1\xbe\xef\x7f\x84\xc3\
31 | \xdc\xc8\xbe\x17\xa4\x6f\x71\x7a\xdf\xb7\xf1\xf7\xf1\x3b\x51\xfa\
32 | \x3a\x23\xeb\x3e\xef\xec\xbe\xce\x28\x6c\x5b\x9f\xaf\x7f\xbe\xc4\
33 | \x6f\xef\x59\xf6\x5e\x77\x75\x2d\x24\x43\xb8\xd2\xbb\xa8\x8f\xa5\
34 | \x9c\x9f\xb8\xb0\x33\x94\x3f\x5f\x4b\xfc\xcd\xfc\x17\xf9\x39\x9f\
35 | \xbf\x95\xbf\xe5\x69\xcf\x20\xf0\x93\xa5\x76\xf3\x74\x7e\xa9\xd6\
36 | \x11\xd3\x6d\x83\x9d\xb6\xd9\x6d\xd7\x79\x1d\x76\x30\xc5\xe0\x96\
37 | \xcb\xbc\x3a\x37\x9c\x3f\xef\x15\x9f\x5d\x75\xc3\xdf\x14\xf0\xd7\
38 | \x6e\x97\x0d\xf9\x99\xe4\xc2\xf9\x41\xe6\x3c\x6f\xbb\xcf\xb9\xd8\
39 | \x73\xdf\xaa\xfb\x71\xb3\xc2\x9d\xa7\xe5\x4a\x67\x19\x4c\x19\xfd\
40 | \xe9\xaf\xf9\xfe\xc6\x7f\xfb\xf7\xa7\x81\xf6\x56\x99\x5b\xfb\x94\
41 | \x37\x4e\x51\x09\x76\xaa\x2f\xa6\xa1\xcc\xe9\x5f\xae\x22\x21\x76\
42 | \xbf\x31\x8d\x27\xbe\xd6\xdc\x97\xe7\xfb\x1f\x25\xd6\x93\xc1\x78\
43 | \xc2\x5c\x58\x60\x7b\xfa\x1d\xa2\x47\xfb\xa3\xb6\xfc\xc9\xb3\x7f\
44 | \xa2\xe1\xd2\xf0\x84\xb7\xb1\xe7\x3b\x00\x21\xe2\xde\x91\xc9\x58\
45 | \x4f\x06\x9e\x64\x7d\xb4\xc9\x3e\xd9\xb9\x6c\x2d\x71\x2c\xe4\xa7\
46 | \x31\x73\xe7\x83\xeb\x64\xc0\x46\x13\xdd\x64\x96\x2e\x50\xf7\x24\
47 | \xa7\x38\xdd\x9b\xef\x64\x7b\xae\x75\xd1\xdd\xb7\x81\x17\x12\x11\
48 | \x7d\xf2\x99\xd4\xd0\x40\x24\x2b\x84\x18\x12\xfd\x56\x28\xa1\x66\
49 | \xa2\x8f\x21\xc6\x98\x62\x8e\x25\xd6\xd8\x92\x4f\x21\xc5\x94\x00\
50 | \x2b\xe1\x54\xcb\x3e\x87\x1c\x73\xca\x39\x97\x5c\x73\x2b\xbe\x84\
51 | \x12\x4b\x2a\xb9\x94\x52\x4b\xab\xae\x7a\x60\x2c\x9a\x9a\x6a\xae\
52 | \xa5\xd6\xda\x1a\x37\x6d\xa1\x31\x56\xe3\xfa\xc6\x1b\xdd\x75\xdf\
53 | \x43\x8f\x3d\xf5\xdc\x4b\xaf\xbd\x0d\xca\x67\x84\x11\x47\x1a\x79\
54 | \x94\x51\x47\x9b\x6e\xfa\x09\x04\x98\x99\x66\x9e\x65\xd6\xd9\x96\
55 | \x5d\x94\xd2\x0a\x2b\xae\xb4\xf2\x2a\xab\xae\xb6\xa9\xb5\xed\x77\
56 | \xd8\x71\xa7\x9d\x77\xd9\x75\xb7\xcf\xac\xd9\xb7\x6d\x7f\xca\xda\
57 | \xf7\xcc\xfd\x3d\x6b\xf6\xcd\x9a\x3b\x89\xd2\x75\xf9\x47\xd6\x78\
58 | \x3b\xe7\x8f\x21\xac\xe0\x24\x2a\x67\x64\xcc\x05\x4b\xc6\xb3\x32\
59 | \x40\x41\x3b\xe5\xec\x29\x36\x04\xa7\xcc\x29\x67\x4f\x75\xde\x78\
60 | \x1f\x1d\xb3\x8c\x4a\xce\xb4\xca\x18\x19\x0c\xcb\xba\xb8\xed\x67\
61 | \xee\x7e\x64\xee\x8f\x79\x33\x44\xf7\xdf\xe6\xcd\xfd\x2e\x73\x46\
62 | \xa9\xfb\xff\x91\x39\xa3\xd4\x7d\xc9\xdc\xaf\x79\xfb\x4d\xd6\x66\
63 | \x3b\x70\xeb\x4f\x82\xd4\x85\xc4\x14\x84\xf4\xb4\x1f\x17\xb5\x56\
64 | \x73\xdc\xab\x87\xbc\x3b\xff\xcc\x56\xf7\xb4\xa9\xd5\xc9\x9f\x51\
65 | \x57\x2a\xcb\xe6\xb5\x76\x8b\xf9\x59\x4e\x94\xb0\xa8\xf4\x02\x82\
66 | \x8d\xd0\xeb\xde\x0c\x14\x93\x5f\x9d\x18\xd5\x14\x3a\x41\x80\x4a\
67 | \x83\xdb\x71\xcd\x68\xd7\xf6\xbc\x2c\x37\x63\xab\x96\x36\xd0\x1c\
68 | \x4b\x4b\xf5\x99\xcd\x95\xe6\x7a\x1d\xfd\x39\x3f\x01\xbd\x86\xdf\
69 | \x57\x06\x7d\x41\xd4\xc8\xf7\x1c\x3f\xd6\x0d\x82\xe9\x9d\xb5\xc7\
70 | \x50\xf1\x64\xd7\x67\xf4\xcb\xb6\x5c\x7a\xe2\xf7\x14\x97\x1d\xbd\
71 | \x4c\x72\x60\x4b\x62\xd4\xda\xbc\xe9\x3d\x95\xc7\xcd\x4e\x54\xdb\
72 | \x9a\x4f\x21\x54\xbb\xd6\xb2\x7d\x4d\x3e\xce\x18\xfb\x93\x99\x6c\
73 | \xc8\xb5\xc5\x6e\xeb\x48\xe0\x7c\x58\xa3\xac\xb1\x0a\x61\xdf\x36\
74 | \xb2\x9a\xec\x9b\x35\x24\xcc\xf2\x85\xd6\x42\x58\x6b\xfa\xa7\x8c\
75 | \x52\x83\x85\x25\x63\x2c\x3b\x39\x3f\x59\x01\x4c\x40\xb6\x6b\xd3\
76 | \xec\x76\x9f\x67\xfe\x73\xf3\x2e\xaf\x83\xa0\x78\xe6\x6a\x28\xb3\
77 | \xde\x68\xbb\xe1\x53\xac\xa5\x8c\x10\xfc\xa4\xb4\x42\x9b\x8b\x0a\
78 | \x48\x91\x5b\xa6\xcc\x37\x3a\x90\x94\xb5\xec\xdc\xcf\xb2\xfb\xb2\
79 | \x67\xa0\xd2\x9a\xdf\xcb\x26\xe3\x52\x3b\x6f\xf8\xb5\x93\xdf\x3b\
80 | \x0f\xaf\xdf\x98\x72\x80\xb8\x06\x0b\xd8\x8b\xb2\x21\xfb\x71\xe7\
81 | \xba\x6a\x45\x06\x55\x92\x44\x31\x51\x37\x10\x5b\x9e\x6b\xe4\xbd\
82 | \xb6\x29\xf9\x84\xd5\xb5\x37\xe0\x77\x05\x4c\xf9\xae\xe1\xac\x80\
83 | \x81\x59\x81\x5d\x23\x25\xcb\xf0\x84\x7c\x8d\x4e\x4a\x27\x6d\xd7\
84 | \xb6\x3f\xb7\x36\x7f\xfd\xea\x3f\x2c\x49\x53\xf6\xbb\x33\xff\x3c\
85 | \x4d\x65\x6e\xff\x26\x16\x7f\x0a\x85\x89\x81\x6f\x54\x16\x98\xc0\
86 | \xb5\xdd\xf9\xdf\xd8\x61\x50\x5d\x63\xec\xbf\x7d\x91\xef\x7d\x06\
87 | \x8a\xd0\x64\xf3\xc6\x4a\xa1\x3a\x81\x4a\x1e\xee\xfe\x25\xbe\x96\
88 | \x31\x91\x14\x71\xee\xd4\x3b\xa5\xa2\x16\x66\x11\xbd\x89\xc8\x53\
89 | \x78\x92\x33\x11\x04\x74\xd4\x7f\xce\x2e\x4f\xae\x7a\x1a\x75\x14\
90 | \x47\x9c\xb5\xae\x0c\x0e\x3d\xd3\x33\xea\xa6\xa3\xc3\x1c\xa7\x1d\
91 | \x5a\xa5\x5b\x47\x9d\xa9\x6e\x0a\x1b\x6c\x0a\x9d\x55\x18\xba\x8c\
92 | \xee\xa1\x85\x1b\x98\xd3\x26\xc1\xa6\xd3\x96\xdf\x63\x75\x9a\x74\
93 | \x32\x7c\xe3\xf7\xa8\xcf\xd6\xaa\x7b\x14\xda\x23\x77\x61\x41\x63\
94 | \xf6\x4f\x27\x55\x79\xac\x3d\x0d\xcb\xab\xc3\x02\x22\xb4\x5f\x6b\
95 | \x1f\x4d\x5f\x9a\x1a\x1d\x84\x1e\x5d\xed\xda\xd7\x74\x75\x15\x3a\
96 | \x06\xa0\xa1\xee\x43\xa1\x03\x76\x58\x1b\x46\x1b\x99\x26\xd9\xe0\
97 | \x51\x74\x6d\xb4\x68\xbb\xf7\x6d\x06\xf8\xd0\xa7\x33\xff\x95\xdd\
98 | \xbb\x12\x29\xde\xfb\xba\x4b\x5d\x7d\x11\x4e\x94\x89\x42\x0c\x7e\
99 | \xcf\x25\xc5\x85\xd2\x32\xa7\x6f\x00\xf7\x98\x07\xc1\xdc\x81\x45\
100 | \x3d\x74\x78\xb5\x80\x08\x59\x18\x70\x2d\x2f\x05\xb8\xd4\x10\x08\
101 | \x8d\xf5\xe4\x31\xed\x00\x7e\x81\xa5\xae\x0c\xac\xd2\xb3\x37\xac\
102 | \x41\x6d\xdb\x66\xeb\x23\x94\xd5\xbd\x24\x11\xf8\x2b\xfc\x8b\x05\
103 | \x6f\xa0\x14\xdc\xb6\xcd\xf9\xb6\xed\x88\x6f\xdb\xa6\xdb\xb6\x61\
104 | \x52\x88\x46\x95\x68\x03\xe8\x40\x88\x46\xd9\xd9\x3f\xca\x17\xdd\
105 | \x4f\x81\xa7\x1c\x18\x3f\x37\xbb\x9e\x1e\xa2\xa5\x0c\xc8\x14\xb4\
106 | \x74\x7e\x96\xdc\xfe\xf2\x6a\xbe\xbc\x91\x28\x27\x28\xa5\xf5\x34\
107 | \x61\xc1\x50\xd3\x6a\xab\xba\x98\x43\x2a\x61\xa4\x27\x84\x07\xfd\
108 | \xed\x46\xcb\x24\x60\xd5\x38\x4f\x97\x9e\xa2\x74\x7e\x1b\xb5\xef\
109 | \xee\x2a\xc9\x70\x4a\xb2\x8f\x03\x59\x2b\x86\xe5\x61\xcc\x94\x2b\
110 | \x21\x1e\xac\x78\x17\x2a\x88\xec\x35\xde\xdd\x91\x28\xb7\x9b\x14\
111 | \x07\x74\x92\x0e\x73\xf3\x11\x07\xd8\x56\x52\x8e\xa1\x54\xfe\xf3\
112 | \xd5\x46\xb2\x19\xe2\xa6\x76\x7b\x04\x9f\xb2\xcd\x39\x84\x51\xe3\
113 | \xe8\x74\x53\x0d\x9b\x68\x50\x2e\xa4\xa9\xef\x49\x1f\x91\x35\xe4\
114 | \xaa\xbb\x58\x5e\xc6\x7c\xbe\x67\xfc\xf3\xf5\x20\x3b\x08\x7c\xd2\
115 | \x4e\x03\xd5\x4c\xda\x7d\xf2\x37\xeb\xdd\x19\x18\x0d\xad\xb8\x3d\
116 | \x64\xc7\x2a\x48\xfb\x72\x00\xfe\x1a\x56\xcd\x4f\x0d\x56\xda\x66\
117 | \xbb\x79\x8a\x1d\x0a\x8d\x89\x22\xf2\x0b\x7f\x43\x91\x6f\xd5\xf8\
118 | \x96\x41\x98\xd5\x30\x64\x48\x60\x0f\x15\x34\x21\x4e\x50\x03\x44\
119 | \x4f\xd4\x1b\x6a\x3a\x72\xdf\x68\x27\xd0\xde\xe5\x0d\xab\x3b\x35\
120 | \xd1\x0b\x7c\x1e\xc1\xe3\x1a\xbb\xdf\xae\xd3\x1b\x2c\x09\x16\xf1\
121 | \xaa\x91\x97\x89\x9e\xe7\xe5\xa2\x97\x89\xc8\x0a\x5c\xd4\x04\x34\
122 | \xbe\xf5\x08\xa4\xf8\x90\x01\x08\x1a\x13\x92\x64\xbe\x55\x13\x50\
123 | \xda\x4d\xc4\x60\x4d\xa7\x1a\xb1\x75\x33\x9b\xa1\xb4\x2e\x91\xc4\
124 | \x8a\x7d\xa1\x04\x72\xf0\x69\xba\x31\x1d\x93\x18\xb0\xe9\xb2\xb1\
125 | \xd6\x2c\x86\x4a\xd4\x66\x1b\x1e\xf2\x07\x70\x8d\xeb\xff\x48\x8e\
126 | \xad\x95\xf5\xac\x36\x29\xe8\x3b\x1f\x25\xf6\x00\xcd\x99\xce\x09\
127 | \xf0\x36\x90\x1c\x33\x11\xdc\x6d\x74\xca\x41\xbf\xbc\x92\xf8\x01\
128 | \x8d\x29\x10\xfc\xf9\x63\x3e\xbf\x1f\x73\x2b\x06\x04\x3d\x13\x0a\
129 | \x04\xa5\x61\xfa\x80\xe6\xdb\xa3\xdc\x85\xd3\xb8\xc2\xed\x95\xc7\
130 | \x41\xfe\x47\x3c\xed\x45\x08\xac\xe6\xcc\x7c\xdd\xf7\x3f\x2f\x79\
131 | \x2f\x80\x45\xde\x6b\xce\x15\xb3\x82\x2c\x22\x1a\xda\x6c\x32\x48\
132 | \xc9\xb4\xe0\x06\x29\x01\x5a\xee\x51\x04\xff\x80\x36\xf3\xa3\x17\
133 | \xe4\x38\x18\xc0\x8e\x26\x4c\x7e\xf0\xb4\x7d\x66\x14\x47\xde\x4f\
134 | \x9a\x25\x01\x8b\xa8\x42\xe2\x8f\x90\x82\x09\xfb\x44\xef\x07\xf7\
135 | \xcc\x2e\xe9\x38\x10\xf6\xe0\x5b\xf3\xbc\xed\x51\x05\x35\xf2\x1e\
136 | \xaa\x20\x21\x00\xcd\x2b\x0a\x5a\x46\x32\x72\x43\xba\xa2\xf0\xd2\
137 | \xd7\x5d\x0a\x72\x55\x0c\xd8\x20\x16\x7a\x8d\x82\x75\xe4\xf5\x2c\
138 | \x8e\xc2\x3b\x69\x6a\x0f\xf5\x57\xeb\x34\x91\x24\xd1\xaf\x9d\x62\
139 | \x24\x16\xd3\x82\x39\x4f\xdb\x71\x6c\x2e\xa5\x78\x5b\x54\x17\xa4\
140 | \x79\xe2\x10\x98\x07\x02\x0a\xf8\xa1\x5f\xdc\x09\x38\x98\x7a\xf9\
141 | \xcf\x50\xb3\x7b\x63\xca\x5b\x85\x17\x81\x8b\x5e\x98\xd1\x9c\x0a\
142 | \x07\xc2\xb6\x7a\x9f\xdf\x2e\x0f\x68\xc2\x6f\x2d\x08\xb4\x64\xd8\
143 | \x75\xb7\xa3\x46\x7c\xa1\x14\xad\x64\x27\x12\xf9\xd9\x88\x36\xc0\
144 | \x7c\xcc\xb0\x24\x4a\x51\xa2\x8e\x34\x04\xbc\xaa\x30\x0b\x62\x93\
145 | \xfe\x3e\x00\xf6\xfc\x78\xcd\x35\x52\x47\x2c\x15\x15\xdc\x88\xc1\
146 | \x00\x6c\x73\xda\x94\x5f\x9d\xd9\x27\xf9\x29\x8b\x1c\x3b\xe5\xec\
147 | \x50\x67\x69\x42\x37\x53\xfd\x05\x30\x79\x20\x0d\x1d\xc7\xbc\x66\
148 | \x2a\x70\x1c\x36\xab\x66\x2f\x7a\x14\xfd\x3b\xda\x31\x8f\x2e\xd9\
149 | \xf8\x31\x3a\x22\xbb\x15\x5c\xc3\x0c\x69\x25\xa8\xa0\xa4\x50\xa1\
150 | \x29\xfa\x7a\x71\x57\xc6\xa6\xb3\x19\xdb\x1b\x57\x18\xbc\x25\xc0\
151 | \x48\x6b\xa5\x9e\xc7\x69\xd5\x52\xf6\xad\x5f\x1c\xc4\xad\x77\x44\
152 | \xbe\xf8\x6b\x44\x6a\x97\x8e\xe9\x01\x58\x73\x0b\x54\xa0\xf9\x69\
153 | \x19\xd3\x18\x81\x84\x35\x75\xfc\xa9\xba\x57\x14\x0c\x7f\xb9\x82\
154 | \xca\xb3\x54\x02\x75\xe7\xcf\x05\xb9\x1c\x79\x75\x3f\x7e\x3f\x54\
155 | \x59\x1a\x55\x2e\x0b\xa5\x41\x3b\xd4\x08\xb6\x4d\x20\x8c\x74\xfb\
156 | \xa9\x25\x92\xe9\x99\x01\xef\x49\xa1\x95\xcc\xd7\xa9\x56\xe8\x29\
157 | \x57\x00\x41\x6d\x92\x27\xc1\xa6\x83\x07\x05\x89\x8f\xa1\xac\x6c\
158 | \xf6\x20\x13\xe5\x96\xe0\x0b\xbb\xa0\xb2\x75\x51\xe1\x99\x2f\x2a\
159 | \x1c\x48\xbd\xb8\x70\x6a\x71\x24\x64\xe1\xa1\x5c\x21\xef\xba\x4c\
160 | \x0b\xf8\xc2\x21\x40\xaf\x8d\x07\x79\x07\x4d\x83\xc2\x78\x2a\x93\
161 | \x23\x34\xd4\x21\xb0\x81\xf5\x01\x95\xfc\xa7\x2a\xff\xf9\x16\xe6\
162 | \xfb\x3d\xf6\xe7\x15\x42\xcb\x7d\xd1\x52\x55\x7d\xf1\x32\x1d\xf2\
163 | \x6f\x2a\x67\x51\x13\x3a\x65\xc3\xd3\xa8\x4b\xf3\xbc\xd0\x01\x2e\
164 | \x7f\x40\x87\x2a\x5d\xd0\xf1\xf6\x5b\x99\x77\x49\x9f\x97\x9c\x76\
165 | \xb9\xe0\xf1\x5e\xc4\x25\xe6\x02\x11\x10\xd2\xe2\x81\x90\xf5\x48\
166 | \xf6\x00\x97\x2a\x7d\x71\xc1\x19\x64\x54\xb7\xdf\x08\xcd\x2e\x51\
167 | \x01\x05\xf7\x05\xf8\x7e\x5e\x63\xfe\x70\x11\x89\x0d\x40\x37\xed\
168 | \xcb\x67\x15\xf5\x44\xf3\xb3\x9c\x3f\x23\x84\xf9\x4f\x20\x22\x57\
169 | \x7c\xd7\xb2\xe8\x51\x32\x84\x20\x0d\x71\x35\x35\x48\xf6\x43\x65\
170 | \xe4\x5c\x8d\xe5\x66\xad\x9f\x9c\xa1\xc8\x28\x7b\xf2\x55\xd7\x73\
171 | \x06\xaa\x36\x34\x49\xd5\xd9\x34\x51\x0c\x0d\xab\x67\x3c\xb7\x68\
172 | \x6e\x54\xc9\xcd\x5e\xcd\x2b\x8c\xb9\xcc\xeb\xbc\x9e\x5d\xb5\x85\
173 | \x88\x07\x69\x28\x3e\xbc\x35\x4a\x13\xce\x3f\xf3\x03\x37\x21\x35\
174 | \x47\x76\x98\x0b\xf6\x77\x12\x8b\x23\xdd\xd0\x76\x4d\x48\x8f\x68\
175 | \x4f\x06\x16\x1e\xf2\xbb\x1a\x10\x1c\xfb\x30\x72\x02\x8f\xf8\xa0\
176 | \x8e\xe7\xe2\x43\xdc\x42\x9f\x8f\x0c\x32\xa6\x0c\xfe\x73\x5d\x1a\
177 | \x95\xeb\x56\x70\x6b\x16\xf7\xe4\x60\x1a\x3e\x9a\x1e\x88\xd2\x41\
178 | \x74\x65\xba\x23\xb5\xf2\x67\x39\xf1\x5b\x61\xd9\x8c\x03\x16\x15\
179 | \x30\xc2\x7b\xd8\x21\xcd\x5b\x25\xc0\xda\x08\x6f\x79\xde\xc6\x54\
180 | \x79\x82\xbb\xb7\x3c\xe3\x47\x79\xd2\x75\x94\x4d\xae\x06\x49\x3c\
181 | \x68\x4a\xe4\xec\x86\xfa\x64\x35\x53\xf6\x08\xea\x86\xc8\x06\xc1\
182 | \x42\x07\x88\x40\x40\xf4\x57\xb4\xae\x1e\xf8\x09\xb2\x2f\x4e\x9b\
183 | \x94\xd0\xfd\x86\x24\xe1\x1e\xb8\x1f\xac\x05\x46\x08\x27\x86\x00\
184 | \xce\x6c\x98\x11\x2c\x75\x88\xd3\xe7\x2c\x89\x57\x7f\x53\x3c\x1f\
185 | \xe4\xd2\xb8\x39\x10\x3b\x41\x43\x03\x19\xe5\xb0\xb8\x2d\x12\xc1\
186 | \xb9\x82\xad\xa4\x2b\x01\x0a\xaa\x3e\xf6\x4c\x32\xf1\x21\x9e\x37\
187 | \x81\xd6\xd4\x40\x35\x64\x9a\x77\x2d\x2e\x1c\x1b\x33\x5f\x1b\xfb\
188 | \xe1\x2a\x02\xc9\xe0\x80\x27\xca\xf7\x37\x04\xf3\xc9\x2f\x1d\x2f\
189 | \x0c\x3c\xe7\xa1\x21\x1d\xc8\x96\x46\x05\x6c\x8b\xed\x87\x6b\x51\
190 | \x2b\x68\x12\x43\x7d\x61\x4d\x1c\x9f\xe1\x5c\xe2\x87\x16\x91\xbe\
191 | \xa1\xa4\x31\x83\x3b\x4b\x69\xb7\x3e\x7d\x5d\x31\x65\x2e\x7d\xa2\
192 | \xf7\x9e\xd2\x9d\x81\xb9\x68\x33\x60\xd7\x50\x9a\xe9\x4e\xce\x83\
193 | \xda\x54\x1d\x8d\xdf\x6b\xe8\x2f\xaf\x7e\xf9\x0c\x1e\x54\x86\x85\
194 | \xb0\xdf\xba\xe4\x36\x04\xbb\x7d\xaa\xf5\xb0\x5c\xc5\xed\x8f\x2d\
195 | \x1d\x18\xe8\x16\xb2\xe2\xb8\x39\x42\x8d\x62\x67\x6a\xa4\x1f\x89\
196 | \x8b\x58\x83\x7a\x50\x5b\x72\xb4\xa8\xe0\x5b\x50\x86\xc2\xfd\xe7\
197 | \xda\x13\x62\xec\xa2\x2d\x10\x08\xe3\xc2\xfe\xd5\xb3\x0d\x13\x24\
198 | \x28\x69\xdd\xfc\xfd\x92\xd0\x51\x74\xcb\x6b\xbf\x66\x12\xca\x29\
199 | \x56\xc7\xc0\x5b\x2c\x9e\xcd\x56\xbc\x57\x16\x24\xb8\x00\x36\x18\
200 | \x3e\xc7\x4e\xd1\x21\xba\x1d\xfe\x8c\xb6\xc1\xbd\x9d\x3a\xa3\xd7\
201 | \xf9\x1e\x10\x1e\x84\xcc\xa5\xfb\x6b\x96\xe7\x15\x8b\xb5\x09\x58\
202 | \x9e\x2c\x9d\xbc\x7a\x36\xcc\x25\x41\x63\x82\x11\xe9\x8c\xbe\x63\
203 | \x42\x08\x24\xe7\x32\x0e\x4a\xa5\x8b\xe6\xc1\x31\xb8\x35\x06\x46\
204 | \xcb\x02\xd8\x2e\x7a\x22\x45\xb9\x00\x54\x09\xc7\xa3\x9d\x17\xd2\
205 | \x4f\xbe\x2c\x2e\x53\xd5\x9c\x16\x91\xc7\x20\x25\xc5\xbe\xda\x81\
206 | \x73\xa7\xc6\xc4\x6c\x98\xc8\x08\x8b\xe0\x0f\x09\x87\x98\x84\xb7\
207 | \x7c\x21\xee\xa5\x6a\xe0\x00\xb2\x1a\x3f\xf1\x15\x68\x78\x1f\xd1\
208 | \xf8\x3e\xa7\x49\xb3\x61\xe6\x86\xc4\xac\xd5\x7a\xe3\x7b\x14\xc0\
209 | \xb0\xc8\x94\x4e\x05\x01\x2e\xcf\x59\x66\xa5\x7c\x0f\x9d\x43\x23\
210 | \x68\x48\xe8\xda\xc1\xa8\xac\xab\x7a\x1c\x10\x31\xa4\xf6\x23\xd8\
211 | \xeb\x3c\xd5\x60\x97\x65\x52\x94\xe7\x43\x0f\xd9\xb0\x10\xe5\x00\
212 | \xaf\x5e\xfa\xe3\x3f\xc0\x86\x3a\x37\xbf\x2b\xbc\x12\x26\x39\x47\
213 | \xd0\xd7\xda\xbc\xd7\x1e\xa6\xcc\x6e\x92\xe9\xf9\xfa\xb6\xcd\x82\
214 | \x48\x8c\x24\x2e\x17\xe3\xd7\x58\xf1\x03\x24\x77\x20\x14\x74\x76\
215 | \x74\x32\x4a\xc0\x13\x94\xb4\x3b\x16\xb7\x69\x7b\xf9\x5c\xcb\xa5\
216 | \xa0\x7b\xf0\xda\x26\xfc\x32\xc4\x1d\xc1\x68\x88\x20\x04\x25\x6a\
217 | \x1d\x78\xa3\x92\xde\x4d\x37\xd4\x14\x98\x0a\xc8\x58\xed\xe7\x21\
218 | \x5d\xb3\xff\x6c\xaf\x4e\x09\x3a\xc8\x0a\x90\x59\x0e\x0e\x1b\xd5\
219 | \x80\x26\x57\xc8\x9c\x63\x8e\xc2\x65\xb0\xde\xe1\xf6\x05\x4b\xca\
220 | \x6f\x6f\xe2\xa1\x5d\x0f\x46\xb9\x96\xa2\x90\x8d\x07\x99\x41\x28\
221 | \x1d\xc6\xd8\x2d\xc4\x5c\x37\x54\x93\x55\x75\x2e\x9f\xd6\xd2\x76\
222 | \x20\x30\x9b\x5a\xa1\x8c\x9f\x50\x65\x7c\xf2\x51\x9b\xa0\x8f\x1c\
223 | \x16\x09\x8a\x63\xb5\x24\x69\xb3\xa4\xd1\x9a\xe6\xef\xe9\x62\x83\
224 | \x34\xc7\xe9\x2f\x59\x3e\x32\xef\x65\xf9\x8e\x66\x03\x89\x1c\x97\
225 | \xe5\x05\x0a\x22\xa0\xed\x91\x2f\x14\xdc\x31\x8e\xf5\x6a\xb8\x24\
226 | \x16\xa4\x77\x16\xda\xd6\xa0\x2a\xb6\x28\x1b\x6f\x67\x3b\xf5\x31\
227 | \xc2\xba\x72\x18\x69\x1d\x86\x6f\x55\xe3\x1c\x31\x98\x59\x20\x48\
228 | \x9c\x58\xee\x7c\x28\x3a\x14\x20\xd2\xc7\xd3\x54\x47\x67\x3b\x3b\
229 | \xce\xf8\x60\x60\xa5\x89\x82\xa3\xc9\x02\x53\xc1\xd7\x55\xcc\x1b\
230 | \xe2\x5a\xc2\x7f\x67\xc8\xa0\x14\x57\x19\x3a\xfa\x74\x5e\xb2\xab\
231 | \x2b\x94\xab\x91\x0b\x03\x7d\x87\x0c\x48\x17\x8a\x6d\x38\xb1\x54\
232 | \x40\xca\x57\x99\xff\x13\xe0\xfd\x52\x90\x79\x80\x5d\x84\xad\xc8\
233 | \x11\x45\x29\x40\x12\x23\x99\xc2\xcf\x88\x43\xfc\xad\xd7\x2e\xf2\
234 | \xd4\x1b\x40\xc9\xf0\x2a\xf8\x08\x0a\x1a\xbc\x0f\xb4\x42\xc9\x44\
235 | \x84\x6a\x9e\x10\xd5\x23\xc9\x10\xaa\x20\x47\x2e\xe4\x80\x46\xa0\
236 | \x86\xf2\x94\xe5\x03\x3e\xea\xb1\x5f\xa0\x43\xee\x7c\xfa\x40\x7b\
237 | \x33\x38\xc3\xd7\xa8\x3a\xaf\x4d\x39\xd2\xe7\x2b\x64\x3a\x1c\x9e\
238 | \xdd\x6a\x2f\xd6\xc7\xa7\x57\x9c\x71\x72\x95\xfa\xd6\xc2\xb5\x97\
239 | \x5c\x7b\xb6\x19\xfd\x8e\xe3\x2d\xc3\x55\x62\x4a\xf9\x1b\x7c\x2d\
240 | \xec\x4f\xff\x92\x49\x54\x20\xaa\x58\xbb\x1b\x36\x60\x81\x19\x2e\
241 | \xdf\xbd\x95\x9e\xb4\xbb\x9d\xa3\x3f\x6d\x7f\x9b\x5e\x3a\x65\x8d\
242 | \x4b\xf0\x74\xbd\xb9\x6d\x0f\x4c\x3d\x97\xd5\x7c\x95\x7e\xcc\x20\
243 | \x0a\x75\x8e\xaa\x00\xc9\xa0\x1e\x8a\x0e\x03\x89\x15\x4b\xda\xf3\
244 | \x16\xf0\xda\x07\x1f\xe2\x83\x76\xf2\x4e\xf6\x4c\xaf\xcd\xd5\x93\
245 | \xaf\xfc\x91\x49\x12\xe6\xd4\x53\x3b\x10\x8a\xff\x20\x61\xe7\xd5\
246 | \xfc\xfc\x46\x5c\xd0\xa7\x45\x4d\xa0\xc9\x8a\x4e\x8b\xf0\x0e\x49\
247 | \xe6\x3f\x55\xed\xbe\x55\x4a\x08\x16\x8f\x0b\x25\x8e\xd8\x28\x8a\
248 | \x5f\x91\x8d\x77\x58\x88\xb0\xc1\x9a\xde\xf2\xd3\x4b\x24\xd9\x29\
249 | \x2d\x3a\x52\x0a\x03\xc3\x06\xba\x4a\x7b\x01\xc4\xb5\x2f\x5f\x6e\
250 | \x85\xd7\x78\xb6\x50\x1f\xf9\xb0\x2b\xd6\x41\xdb\x6c\x30\x01\x19\
251 | \xa1\x28\xde\xa0\xea\x51\x4d\x20\x78\x90\x81\x04\xd2\x25\x1f\x6d\
252 | \x7f\x4a\xc2\x51\x2a\x45\x3d\xb9\xe1\x01\x08\x54\x44\x49\x87\x96\
253 | \xd1\x54\xc9\x96\xb1\x7c\x35\x3a\x0d\xc0\xf6\xef\xe1\xd4\x56\xd8\
254 | \xd1\xf9\x1c\xa5\x9a\x5a\xd2\xc6\x14\x9d\x83\xfc\x28\x7c\x31\xa2\
255 | \xeb\x93\x8d\x34\xc8\xa6\xdc\x74\x6e\x91\xc0\x94\xd1\x74\xc0\x01\
256 | \x75\x1b\x97\x71\x1b\x04\x07\xa1\x8b\x47\xc3\x42\x82\x7c\xc8\x1a\
257 | \x59\x48\x9d\x72\x44\xb4\x01\x24\xd1\xa3\xfc\x20\x65\x83\xdc\xcc\
258 | \xd5\xe9\xfc\x2a\x93\xb3\x06\x7c\x01\xa7\xd4\x59\xc6\x66\xe1\x88\
259 | \x9d\x76\x01\xa5\x0e\x28\x6e\xdc\x5d\xe4\xe5\xec\x26\x39\xf0\xfd\
260 | \xa8\xab\x78\xa5\xb9\xd5\x76\x0d\x7a\x8c\x8c\xcc\x8c\x06\xc0\xf9\
261 | \x4a\xf4\x6e\xb0\xcd\x8c\x59\xa4\xa8\xca\x71\x46\x75\x58\x68\xf6\
262 | \xd1\x48\x15\x98\xd1\x49\x59\x55\x1e\x24\x68\x60\xc3\x28\x7f\x0a\
263 | \x5f\x4b\xe0\xd1\x54\x2b\x5c\xbf\xac\x2d\x63\x6f\xe8\x8a\xa3\xc3\
264 | \x8b\x96\xed\x59\x50\xef\x8b\x6f\x4d\x5a\x43\xe1\x04\x30\xac\x68\
265 | \x07\xcb\x02\x45\xc6\xd6\x2d\x2c\x89\x01\x85\xfd\xdd\xb6\x59\x9b\
266 | \x7e\x70\x14\xc5\xc1\xd2\x12\x7a\xaa\xff\x15\xbe\x3c\x8a\x2c\x38\
267 | \x44\x65\x18\x94\x93\x60\x20\x49\xa7\xa6\x38\x91\x42\x5c\x8a\xfa\
268 | \x81\x45\xb4\xfd\x8c\x21\x2f\x2f\x3f\x42\x33\x01\x72\xb0\xa2\xc7\
269 | \x92\x1c\xc9\xad\x00\xec\xa4\x89\x73\xbd\x28\x06\x00\x0c\xf7\x29\
270 | \xbc\xf0\xc4\x00\x7b\x21\xd8\x98\xd3\xc7\xa5\x23\x97\x6a\xf9\x75\
271 | \x73\xe0\xf7\xaf\xb0\xef\xc2\xf6\x59\x1d\x9e\x14\x54\x59\xa7\xd7\
272 | \x50\xe4\xc1\xc1\x31\x07\x2b\x12\xfe\xd6\xa6\x45\x4e\xa7\xa8\x0b\
273 | \x85\xec\x2e\x54\xe0\x98\x65\xb2\xf9\x9a\xca\x94\x9c\x42\x9f\xc7\
274 | \x83\x5f\xb3\xb8\xad\xc1\x86\xff\x43\xf1\xab\xf6\x01\xf4\x8c\xeb\
275 | \x0c\x4e\x31\x03\x2c\xe4\xf5\x13\x8b\x29\xe1\x6c\xf0\xae\x63\xfc\
276 | \x0e\x15\xda\x7a\xa9\x30\xf6\x43\x85\xf9\x52\x21\xc2\x16\xcd\x0f\
277 | \x35\xc1\x03\x01\xa0\x5b\x50\xa3\x8c\xaf\x9f\x2d\xe8\x54\x58\x9b\
278 | \x3c\xad\x2c\xbe\x57\x4d\x22\x9b\xb1\x58\x8b\x3b\xd0\x8a\x33\x02\
279 | \x10\x92\xf6\x23\xb9\x3e\xa4\x59\x6b\xd1\x4e\xf0\xd9\x0f\x0d\xd3\
280 | \x87\xbb\xa9\x20\xc3\xd2\x28\xa5\x1e\x75\x7a\x52\x96\x76\xb4\xd0\
281 | \x0a\x28\x2c\x81\xf9\xcc\xe1\xe0\x5d\x20\xf4\x3a\x1b\xa0\x29\xfe\
282 | \x05\x04\x9b\x3f\x61\x30\x10\xfc\xa0\x9f\xb3\x8c\x28\x2c\xb8\xb3\
283 | \xa7\xce\x9f\x48\xa1\x84\x7c\x77\xd7\x9e\x4a\x21\xe6\xf7\xcc\xe4\
284 | \x68\xc8\x3c\xdc\x7b\x68\x12\x47\xa2\xb2\x33\x81\x68\x60\x0b\x56\
285 | \x08\x63\xd7\x56\x87\x43\x45\xfd\x03\x14\xc5\x76\x1d\xed\x86\xb4\
286 | \x99\xa0\x2c\x5d\xbb\x1f\x49\x95\x65\x10\x7d\x6b\xf5\x87\xac\x4c\
287 | \x58\x8a\x5a\x5c\x74\x51\xbf\xf5\x12\x3b\x85\x9c\x6f\x84\x9e\x62\
288 | \x57\xab\x77\x5f\xc0\x2a\xb9\x59\x96\x5c\x92\x71\xd6\x84\x3d\x31\
289 | \xd6\x2d\x09\xb0\x48\x82\xc7\x18\x65\x3a\xc4\x52\x77\x51\x87\xc1\
290 | \x42\x87\xce\xf7\xd1\xb0\x5e\x0f\x05\xd5\xe8\x71\x5c\xaf\x1c\xd5\
291 | \x79\x66\x95\xbf\x41\x20\x54\xd7\x92\xa1\xf0\x7e\x2d\xfc\x1f\x0d\
292 | \xf0\x85\xc0\x71\xcf\xf5\xbc\xef\x9f\xcf\x23\xcc\x76\xd4\x92\x4e\
293 | \x28\x8c\x7b\x66\xf4\xc3\x67\x9d\xbf\xa4\x8e\xb7\xd1\xee\xc7\x90\
294 | \xf1\xc8\xb4\x3a\x2c\x24\xd6\xd3\x86\x23\x94\x03\x41\x8b\x96\xc9\
295 | \x83\x7b\xae\x18\xa3\x0d\x3d\x3a\xad\xe1\xd7\x44\xcf\x4d\x47\x54\
296 | \x43\xb1\xb3\x60\x71\xc7\x2d\x3b\x57\x11\x90\xe0\x1c\x92\xa9\x4b\
297 | \x3b\x81\xcb\x7d\xa0\xec\x07\xf8\x8c\x7e\xdc\x4d\xee\x56\xc7\x82\
298 | \x45\x1e\xa3\x3c\xfd\x6c\x20\x54\x0f\x3c\x12\xb8\x1c\xfe\x86\xdd\
299 | \x78\x90\xd9\xb9\x25\x91\x46\x62\x54\xfc\x1d\xf2\xad\x6e\xbe\x34\
300 | \x86\x54\xed\x11\x0e\x64\x10\x1e\xb5\xf1\xee\x75\x68\x2b\x13\xad\
301 | \x70\x3d\x47\x6f\xdf\xee\xfd\xfd\xd6\x09\x1b\xbe\xbb\xf1\x94\xed\
302 | \x1e\xc3\x2f\x7a\x7d\xc9\x0d\x6a\x33\xf6\xd7\x99\xb3\xbc\xfb\x5d\
303 | \x3d\xcc\xf1\xeb\xc4\xa5\xd8\xce\xdc\x75\x9c\xb9\xbe\xcd\xbd\xe4\
304 | \x29\xa8\x9e\x72\xb2\xda\xfe\x0e\xe3\xcf\x0b\x2f\xdb\xd0\x92\x12\
305 | \x19\xed\xee\xce\x50\xe2\x32\xab\xf3\x1e\xe5\x32\x06\x55\x07\x86\
306 | \x1d\x79\x4a\xa2\x3c\x40\x00\x8b\xb1\xda\x7e\x8e\x40\xd6\xa3\x3d\
307 | \x50\x6a\x6a\x46\x83\x43\xd2\xfe\x0c\xa9\x87\xb7\x67\xa5\xda\xa1\
308 | \x55\xf8\xa7\xd5\x01\xe1\x01\x03\xa8\x1b\x60\x1a\x00\xaa\x32\xa0\
309 | \xb1\x42\x8a\x41\x7b\x55\xb5\x04\x18\xbe\x7d\x30\xbc\x81\xe2\x79\
310 | \xd5\xc9\xc9\x0a\x7a\xb2\x08\xc2\x1a\x4d\xd2\xdd\x49\xf6\x66\x8a\
311 | \x3a\xef\x46\x3f\x6b\xe4\x87\x56\x42\x3f\x6c\x1a\xbf\x81\xff\x8f\
312 | \x4f\xc0\xad\x2f\xce\x77\x34\xa4\x6e\xe7\x69\x73\x94\x9f\x68\x02\
313 | \xd0\x60\xee\xc8\x06\x78\x45\xa7\x8f\x20\x69\x42\xf6\xe1\x12\xcf\
314 | \xf6\x50\xd7\xe9\x04\x8b\x85\x64\xe1\xc8\x27\xa1\x09\xd2\x82\xc7\
315 | \xf2\x30\xac\xb8\xc3\x67\x95\xb2\xf5\xb0\x06\x93\x22\x0e\x14\x83\
316 | \x36\x93\xb9\xdc\x6b\xc7\x30\x00\x03\x0e\x83\x10\xa5\x8f\xa7\xd7\
317 | \x46\x67\xb3\x60\xef\x57\xd7\x66\xae\x6d\xfb\xe2\xda\xd0\x64\x85\
318 | \x76\x66\xf1\x18\xf2\x1e\xf8\x9d\x42\xd1\xf9\x45\xef\x4f\xc5\x6a\
319 | \xce\x91\x82\x5d\x7c\x97\xdb\x01\x6a\x5b\x8f\xed\xd0\xaa\x06\xa7\
320 | \x82\x57\xc0\xe4\xfb\x47\x8d\x6c\x85\x03\xf4\x28\xad\x71\x3b\x93\
321 | \x7e\x2b\xe1\x4f\x3e\x1e\xfa\x87\x27\xe4\xe6\x1e\x03\x74\xb6\x1f\
322 | \x32\xef\x3d\x39\x47\xc1\x3c\x77\x17\x79\x97\xd5\xe6\x3d\x0d\xb9\
323 | \x3b\x8f\x9f\xc7\x25\xf7\x92\x8f\x0b\xb2\x39\x9f\xeb\xac\x84\x15\
324 | \xd3\xb6\xac\x84\xdc\x22\x00\x31\xe7\x72\x1a\x48\x11\x4f\x3d\xea\
325 | \x14\x00\x43\x35\xfb\x72\x3b\x56\x47\xb8\xd5\x10\xda\x00\x26\xd0\
326 | \xf0\x37\x4d\xcb\x0f\x22\x7e\xab\x85\x40\x53\xcc\x3a\x22\x84\xbf\
327 | \x6e\xff\x3e\xea\x16\xdc\x16\x8a\x11\x6a\x04\x14\xea\xa5\xc6\x2e\
328 | \x95\x60\x29\xce\x63\xfe\xcd\x71\xff\xf5\x9e\x92\xad\x7c\x69\xf1\
329 | \xdd\x00\x00\xf8\x41\x3b\x0a\x7d\x40\xc6\xcc\x53\x7b\x91\x13\xc9\
330 | \x93\xd0\xe1\x28\x13\xf2\x39\x31\xe3\x16\x11\x06\xe2\x1a\xfc\x29\
331 | \xfa\x81\xba\xde\x8a\x3e\x35\xd0\x55\x02\x09\xc2\x09\x49\x78\x7b\
332 | \x0f\x2e\x5f\x97\xae\xed\x77\x65\xdb\xbe\x72\xfd\x6e\x7b\xea\x60\
333 | \xbf\x1a\x96\x2b\x87\x5e\x71\xe8\x7c\x65\xac\xd8\x4b\xa6\x9e\x9c\
334 | \x36\xca\x41\x92\x58\x64\x46\x4a\xd8\x5e\x9b\xa5\x7a\x0a\x60\xbc\
335 | \x4f\x01\xe4\x2f\x4f\x01\x50\x6d\xc6\xdd\xe7\x1a\x14\xd6\x8f\x6d\
336 | \x7a\x1d\x8d\x2e\x81\x49\x00\x5c\x72\x38\xfb\xf8\x6b\x44\x32\x45\
337 | \x26\x98\x97\xb6\x63\x3f\x48\x4d\x94\x76\x02\x63\x0e\xa9\x09\x65\
338 | \xee\x93\x00\x7a\x3e\xe4\x3e\xd4\x21\xd3\xb9\x2f\x8c\xc1\xcb\xc4\
339 | \x4b\xfe\x16\xfa\xaf\xf4\x63\x72\x82\xf8\x93\xd7\x70\xe4\x46\x09\
340 | \xe6\xce\x59\xbb\x8c\x67\xd6\x77\xce\x3f\x66\x7c\xf7\xdd\xbe\x4e\
341 | \xf9\x73\xc2\x77\xba\x8c\x86\x02\xec\xd3\xe8\xb4\x20\x37\xf7\xd7\
342 | \xd9\x58\xf4\x11\x7d\xab\x9d\x80\xae\x13\x5b\xc0\x06\x71\x9d\x5d\
343 | \x9a\x43\xbd\x69\xa9\x8e\xc7\x2c\xd0\xa5\x81\x84\x3a\x5f\xff\xa0\
344 | \x5f\xb2\xd9\xfc\x3f\x92\xef\x4b\xbd\x2f\xf1\xd2\xb4\x97\x7b\x19\
345 | \xf8\x83\x7d\xff\x2b\xf2\x35\xbf\x61\xdf\xbf\x92\xef\xaf\x64\xac\
346 | \xce\x1e\xd9\x28\x78\x9b\x62\x81\x1f\x8f\xc5\x0a\xb1\x63\x92\xd0\
347 | \xd1\x60\x3f\x7a\x16\xdc\xa8\x09\x29\x03\xce\x7b\x04\x93\xcd\xf9\
348 | \x09\x54\xdc\x39\xf8\x73\xe8\x35\x9f\x10\xde\x3a\x2e\x27\x46\xfa\
349 | \x3e\x7a\x18\xe1\x0e\xf7\x74\x6d\xf5\xfb\xaa\x8d\xb3\x3f\x29\x4a\
350 | \x62\x69\xb5\x33\x76\x6f\xee\x82\xb6\x66\xfc\x34\x38\x8f\x87\x46\
351 | \x9c\xbd\x85\xe2\x62\x3c\xb7\x66\x89\xe2\x49\x20\x21\x3e\x59\x9b\
352 | \xac\x2b\x23\x08\xba\x47\x15\x27\x6d\x4d\x7d\x5a\x88\x8c\x1d\x80\
353 | \x49\x47\x5b\xc5\xd0\xc4\xf7\x48\x6f\x03\xb6\x68\x6e\x26\x8c\xf9\
354 | \x8f\xbd\xe1\xed\x15\x73\xac\x13\xc6\x64\x74\x81\xe4\x14\xc6\xe9\
355 | \xa1\x34\xf8\xbf\x5b\x9d\x7c\xc8\x15\x7a\x95\x9c\x33\x0b\x1c\x39\
356 | \xe8\x00\xdd\x59\xf7\x4a\x27\x50\x0f\xe8\x6f\x34\x5f\x0a\x93\xa6\
357 | \x45\xc0\x3a\xc8\x4a\xcf\x88\x60\x02\x1e\x49\xdd\x25\x06\x4c\xef\
358 | \x39\x20\x81\x34\x58\x09\x7e\xa6\xf7\xb1\xd4\x61\xb1\xc6\x20\x69\
359 | \x90\x6c\x1f\xa5\xcb\x39\xa5\xc7\xad\x27\x2d\xa7\x6a\x8e\x58\x27\
360 | \xed\x25\x52\x26\xc2\x51\x24\x8e\x3d\x47\xca\xa7\xd7\x56\x88\x1f\
361 | \x4f\x20\x61\xb8\x11\x34\x8c\x0b\x77\xb5\xe9\xfd\xa0\x53\x93\x4e\
362 | \xbf\x25\xa1\xe6\xe1\x0f\x94\x52\x47\x29\x60\xd0\x08\x81\xe4\xf8\
363 | \xb2\x18\x33\x7a\x60\xd1\xb4\xe7\x88\xa4\x7f\x7d\x86\xe7\xcb\xad\
364 | \xee\x7d\x96\xb6\xc3\x51\x36\x81\x28\x7b\x80\xb2\x53\x05\xcc\x49\
365 | \xcf\x06\x55\x29\x9c\x15\xa7\x9e\x18\xd7\xc3\x50\xdf\x26\xfa\x65\
366 | \x9e\xf7\x18\x25\x84\x1c\xdc\x6d\x6e\x7b\xce\x6f\x7f\x3c\xec\x84\
367 | \x41\xa4\xe4\xf4\x68\x15\x0a\x9b\x0f\xab\x9e\x12\xe2\x5a\x96\xd4\
368 | \xa5\xee\xe4\x56\x3e\xbe\x7f\xbe\x2d\xb7\xd2\x3e\x9e\x14\xbb\xe7\
369 | \x5b\xa5\xac\x8b\x61\x7e\xe9\x51\x86\x7a\x05\xd6\x01\x8f\x77\xfe\
370 | \x0f\x0a\x67\x4a\x10\x3a\x1d\x70\x61\x30\x53\xd6\xc3\x30\xbf\xec\
371 | \x87\xfe\x20\x56\xf3\x6f\x99\x75\xc1\xa3\x3a\xf7\x71\x08\xbe\x02\
372 | \xd4\x7c\xf0\xaa\xf9\x37\xc4\x7a\x77\xc6\x6c\xb1\xa2\xd6\x1f\x12\
373 | \x79\xb3\xf8\x62\x14\xcb\xa5\x1d\x71\xab\xc7\x24\xb1\x3d\x18\x41\
374 | \x34\x20\xa4\xe6\x74\x1e\x1f\x62\x41\x46\x96\xfc\x79\x6e\xdc\xdd\
375 | \xc7\xa9\xb1\xee\x37\xbb\x0e\xde\x2a\xee\x17\xa8\x45\x64\xbb\x44\
376 | \xd7\xf8\x73\x6c\x7c\x56\x7f\x0e\x0a\xe9\x76\x49\x37\xbf\x99\x72\
377 | \x3e\x27\x89\xfe\x1c\x12\x1e\xe9\xfa\x7e\x28\x43\x71\x3f\x36\xf7\
378 | \xf3\x73\xd2\xa8\x27\x86\x49\x3b\x2d\xa3\xf9\xf4\xde\x76\x27\x52\
379 | \x50\xfb\x63\x45\x4b\x28\x22\x7a\x64\xf4\x80\xef\x89\xda\x68\x43\
380 | \xfa\x61\xf3\xaa\x36\x19\x6c\x33\xe8\xad\x27\x40\xad\x44\x2e\x20\
381 | \xfd\x9a\x2c\x95\x5d\x82\x91\x2c\x79\x7f\x9e\x53\x90\x71\x6f\x9d\
382 | \xd0\xee\x40\x14\x08\x73\x47\x35\x26\x6d\x96\x6e\x85\x5e\x3b\x40\
383 | \xd1\xd0\xc7\xd0\x9d\x4b\x1f\x5b\xe3\x2d\x8d\xfc\x63\x6f\x7c\x0c\
384 | \x2c\x56\x3d\xb6\x5a\x4f\x56\x08\xdf\xd1\x12\xfd\x34\xaa\xb6\x00\
385 | \xb5\xf9\x6d\xcf\xe6\xb7\xc1\xd8\x83\x73\x43\x0f\x2e\xe1\x36\x12\
386 | \xa2\x9d\xc1\x02\x64\x4b\x1f\xc7\xaa\x75\x94\x73\xe2\x3c\xb5\x11\
387 | \xd3\xac\x05\x97\x1f\xe6\xfa\xf8\x7b\xc2\x30\x10\xa0\x50\xdb\x73\
388 | \x0f\x7c\x47\x39\xfa\x41\xdb\x76\x5a\x86\xb6\x1c\x55\x2c\x20\xbb\
389 | \xd4\x62\xc0\x9d\x03\xb2\xb0\x9c\x97\xc5\x2e\x52\x09\x5d\x6e\xfa\
390 | \x19\x13\x7e\x9b\x98\xbf\x9a\xad\xc9\x14\x26\x39\xf5\x01\x9a\x20\
391 | \xe2\xa5\x82\xff\x2a\x40\xa0\x31\xe6\x78\x76\x84\x96\x76\xb6\x60\
392 | \x85\xaa\x53\x79\x4b\xb7\x9c\xc7\x26\xcf\x69\x59\xaa\xda\xe4\x39\
393 | \x27\x35\xae\x3d\xe3\x3f\x3e\x26\xfc\x55\xfa\xbd\xfb\x7b\x9f\x4f\
394 | \x44\xfd\x72\xb0\x68\xed\x01\x9c\x83\x00\xa8\xb0\x03\x39\x07\x71\
395 | \xf4\x18\x93\xff\xaa\x16\x9c\x3d\xa8\x61\x3e\x60\x23\x7d\xc2\xdb\
396 | \x8c\x44\x0b\x5b\x31\x4e\xd4\xea\x96\xf3\xe8\x5d\x9b\xb7\xc8\xfe\
397 | \x39\x2e\xb0\x8d\xf7\x3e\x9f\x77\x79\x81\xcd\xff\xf4\x70\xe2\x7b\
398 | \x93\x0b\x6c\xe9\xe3\xb9\xc4\x8f\xd1\xd5\x5d\xf5\x13\x7d\x40\x83\
399 | \x83\x3f\x27\x6b\xde\xeb\x54\x21\xa7\x47\x0d\x19\xb9\xf1\xd6\xc3\
400 | \xec\x00\x38\xd9\xbf\xa7\x1e\x41\x07\x36\x6e\x5a\x2d\x3b\xc1\x0d\
401 | \x6b\xd0\x25\x8f\xb6\x77\xa6\x36\xbc\x6a\x48\xe6\x79\x83\xf5\xf3\
402 | \x6e\xb6\xd7\x16\x8f\x9e\xbc\xa5\x5a\x90\x80\xf8\x61\x7b\x4f\x55\
403 | \x0a\x23\x70\xd1\xaf\xdb\x41\xe6\xdd\x0f\x7a\x9f\x29\xc2\x36\xe1\
404 | \xd8\xbc\x94\x98\xbf\x4f\x92\x4e\x96\x71\x61\x6f\xc5\xac\x73\x9b\
405 | \xf3\x7c\x65\x11\x0b\x06\x00\xfb\x60\x85\xeb\xf5\x64\x6d\x61\x9a\
406 | \x2c\x6a\x09\x03\x82\x7e\x20\xdc\x7a\xa0\x52\x96\x9a\x58\x68\xa3\
407 | \x47\xff\x6f\x8e\xf4\xb1\x31\xf4\x2a\x60\x6b\x5f\x05\x9c\x8f\x02\
408 | \x66\xa0\x14\x05\x2c\x51\x25\x1b\xcf\xb6\x90\x25\x18\x90\xb0\x0c\
409 | \xef\xfb\xac\xf2\x40\x72\x8c\x7f\x2a\x29\xf3\xdf\xd6\xe2\x8f\x63\
410 | \xc5\x73\xde\x82\x60\x8f\x91\x3a\x00\xdc\x1a\xb4\x88\x9c\xa1\x41\
411 | \x91\x25\x83\xee\x49\xa2\xaf\x1e\xc0\x1a\x1b\x91\xdb\x01\x5c\x44\
412 | \x82\xe9\x38\xc4\x82\x4d\xb0\x29\x45\x06\x6e\x5c\x81\xf8\xbb\x23\
413 | \x9f\x1d\x70\x9b\x58\x3e\x35\x6e\x24\x72\x88\xed\x7b\x08\x24\x5d\
414 | \xb2\xcf\xe9\xea\x3c\x47\xd3\x3f\x25\xc7\xfc\x2e\x3b\xd4\xd2\xc1\
415 | \xca\x5e\xf5\x7f\x9c\x21\x76\x03\x45\x8b\x68\x3a\x06\x87\xf6\x00\
416 | \x15\x1f\xf3\x7f\x0f\xdf\x71\x6d\x51\xed\x3d\xc8\x00\x00\x01\x84\
417 | \x69\x43\x43\x50\x49\x43\x43\x20\x70\x72\x6f\x66\x69\x6c\x65\x00\
418 | \x00\x28\x91\x7d\x91\x3d\x48\xc3\x40\x1c\xc5\x5f\x53\x45\x29\x15\
419 | \x07\x3b\x88\x38\x64\xa8\x4e\x16\x44\x45\xd4\x49\xab\x50\x84\x0a\
420 | \xa1\x56\x68\xd5\xc1\xe4\xd2\x2f\x68\xd2\x90\xa4\xb8\x38\x0a\xae\
421 | \x05\x07\x3f\x16\xab\x0e\x2e\xce\xba\x3a\xb8\x0a\x82\xe0\x07\x88\
422 | \x8b\xab\x93\xa2\x8b\x94\xf8\xbf\xa4\xd0\x22\xc6\x83\xe3\x7e\xbc\
423 | \xbb\xf7\xb8\x7b\x07\x08\xf5\x32\xd3\xac\x8e\x51\x40\xd3\x6d\x33\
424 | \x95\x88\x8b\x99\xec\xaa\xd8\xf5\x8a\x30\x42\x08\x62\x1a\x33\x32\
425 | \xb3\x8c\x39\x49\x4a\xc2\x77\x7c\xdd\x23\xc0\xd7\xbb\x18\xcf\xf2\
426 | \x3f\xf7\xe7\xe8\x51\x73\x16\x03\x02\x22\xf1\x2c\x33\x4c\x9b\x78\
427 | \x83\x78\x72\xd3\x36\x38\xef\x13\x47\x58\x51\x56\x89\xcf\x89\x47\
428 | \x4c\xba\x20\xf1\x23\xd7\x15\x8f\xdf\x38\x17\x5c\x16\x78\x66\xc4\
429 | \x4c\xa7\xe6\x89\x23\xc4\x62\xa1\x8d\x95\x36\x66\x45\x53\x23\x9e\
430 | \x20\x8e\xaa\x9a\x4e\xf9\x42\xc6\x63\x95\xf3\x16\x67\xad\x5c\x65\
431 | \xcd\x7b\xf2\x17\x86\x73\xfa\xca\x32\xd7\x69\x0e\x22\x81\x45\x2c\
432 | \x41\x82\x08\x05\x55\x94\x50\x86\x8d\x18\xad\x3a\x29\x16\x52\xb4\
433 | \x1f\xf7\xf1\x0f\xb8\x7e\x89\x5c\x0a\xb9\x4a\x60\xe4\x58\x40\x05\
434 | \x1a\x64\xd7\x0f\xfe\x07\xbf\xbb\xb5\xf2\xe3\x63\x5e\x52\x38\x0e\
435 | \x74\xbe\x38\xce\xc7\x10\xd0\xb5\x0b\x34\x6a\x8e\xf3\x7d\xec\x38\
436 | \x8d\x13\x20\xf8\x0c\x5c\xe9\x2d\x7f\xa5\x0e\x4c\x7d\x92\x5e\x6b\
437 | \x69\xd1\x23\xa0\x77\x1b\xb8\xb8\x6e\x69\xca\x1e\x70\xb9\x03\xf4\
438 | \x3f\x19\xb2\x29\xbb\x52\x90\xa6\x90\xcf\x03\xef\x67\xf4\x4d\x59\
439 | \xa0\xef\x16\x08\xad\x79\xbd\x35\xf7\x71\xfa\x00\xa4\xa9\xab\xe4\
440 | \x0d\x70\x70\x08\x0c\x17\x28\x7b\xdd\xe7\xdd\xdd\xed\xbd\xfd\x7b\
441 | \xa6\xd9\xdf\x0f\xc5\x0e\x72\xc8\xe0\x28\xf0\x84\x00\x00\x00\x06\
442 | \x62\x4b\x47\x44\x00\xee\x00\xd0\x00\x4a\x75\x27\x5f\x7f\x00\x00\
443 | \x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\
444 | \x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe3\x0c\x0a\x03\
445 | \x3a\x24\xed\xa5\x4a\x18\x00\x00\x01\x98\x49\x44\x41\x54\x48\xc7\
446 | \xad\x93\x31\x4f\xc2\x40\x18\x86\xdf\x3b\xaa\x83\x02\x56\xfe\x42\
447 | \x9d\x4c\x44\x56\x13\x13\x31\xea\x22\x8b\xa3\x1b\x8b\x7f\xc0\x8d\
448 | \x9f\x80\x93\xbf\xc1\x4d\x37\x16\x58\x8c\x06\x13\x13\x56\x81\xc4\
449 | \x8d\xbf\x20\x95\x8a\x89\xd2\xde\xe7\x80\x57\xdb\x72\xbd\x36\xd4\
450 | \x77\x6a\xbf\x6b\x9f\xef\xfd\xde\xbb\x63\x44\x74\xf8\x6d\x3f\x59\
451 | \xee\xa4\x57\x17\xee\xfb\x3e\x32\x8a\x1b\x1b\xcf\x46\x71\xef\x66\
452 | \xd5\x3c\x18\xb1\xaf\x71\xf7\x62\x36\xbe\xbf\x26\xf2\xf2\xc1\x8f\
453 | \x0a\x56\x13\xce\xa8\x01\xef\xe8\x36\x11\x98\x7b\x38\x0f\xbd\x33\
454 | \x96\xfb\x58\xd9\x3c\xb9\xe4\xee\xa4\x57\x8f\x82\x75\x3f\xaa\x14\
455 | \x35\x40\xe4\xe5\xdd\x49\xaf\xce\x9c\x51\x83\xe2\x7e\x92\xee\x75\
456 | \x22\x22\x88\xe3\x3b\x85\x11\xfe\x69\x24\xb9\x92\x0d\x88\x08\xc5\
457 | \xad\xab\x85\xf5\xf8\xe6\x62\xcd\xf0\xa6\x43\xe4\xd6\x77\x94\xcb\
458 | \xce\xa8\x81\x82\xd5\x44\xc1\x6a\x2a\x41\xb2\xae\x8c\x6a\x3a\x84\
459 | \x21\x1f\x00\x28\x9b\x24\xc5\xa2\x82\xfa\xc1\x44\x17\x82\x8b\x49\
460 | \x92\x93\x49\xcd\x9c\x01\x84\x20\x08\x41\x8b\xf0\x65\x9b\x88\xd6\
461 | \xf6\x1c\x76\xf6\x1a\xaa\x1b\x69\x46\x54\xc5\x35\x73\x06\x89\x4d\
462 | \x17\xe0\x72\xa4\x50\x2d\x06\x54\xaa\x74\xf4\xb7\x35\xeb\x75\x7f\
463 | \x7b\x39\x55\x47\x25\xe8\xcf\xb9\xca\xb1\x4e\xa5\x4a\xc7\x07\x47\
464 | \xb3\xce\xe4\x3c\x0e\x2c\x37\x36\x94\x79\x5a\xd7\x32\xe3\x34\x60\
465 | \xed\x69\x89\xdb\xac\xb4\xe0\x58\x78\x70\x6c\x65\x96\x29\xc0\x3e\
466 | \x9c\x73\x06\x21\x48\x3b\xb6\xee\xf2\x24\x9e\xf3\xa8\xdb\xac\x60\
467 | \xce\xd9\x1c\x6e\x96\xdb\x4b\x8d\xad\x03\xfb\xce\xed\x41\xcd\x3f\
468 | \xeb\x59\xc1\x01\x4d\x38\x80\x96\xce\x31\xe7\xcc\x77\x12\x74\xa6\
469 | \xab\xff\xea\x91\xd9\x83\xda\x2e\x80\x2e\x00\x13\xff\x27\x1b\x40\
470 | \x95\x9b\xe5\x76\x1f\x40\x35\x38\x41\x46\xb5\x00\x54\xcd\x72\xbb\
471 | \xff\x03\xcd\x08\xd3\x25\xa2\x07\xc5\x7b\x00\x00\x00\x00\x49\x45\
472 | \x4e\x44\xae\x42\x60\x82\
473 | "
474 |
475 | qt_resource_name = b"\
476 | \x00\x07\
477 | \x07\x3b\xe0\xb3\
478 | \x00\x70\
479 | \x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\
480 | \x00\x0d\
481 | \x07\xad\x20\xa2\
482 | \x00\x72\
483 | \x00\x61\x00\x73\x00\x74\x00\x65\x00\x72\x00\x5f\x00\x74\x00\x72\x00\x61\x00\x63\x00\x65\x00\x72\
484 | \x00\x08\
485 | \x0a\x61\x5a\xa7\
486 | \x00\x69\
487 | \x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
488 | "
489 |
490 | qt_resource_struct_v1 = b"\
491 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
492 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
493 | \x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
494 | \x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
495 | "
496 |
497 | qt_resource_struct_v2 = b"\
498 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
499 | \x00\x00\x00\x00\x00\x00\x00\x00\
500 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
501 | \x00\x00\x00\x00\x00\x00\x00\x00\
502 | \x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
503 | \x00\x00\x00\x00\x00\x00\x00\x00\
504 | \x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
505 | \x00\x00\x01\x6e\xed\xf5\x73\x4c\
506 | "
507 |
508 | qt_version = QtCore.qVersion().split('.')
509 | if qt_version < ['5', '8', '0']:
510 | rcc_version = 1
511 | qt_resource_struct = qt_resource_struct_v1
512 | else:
513 | rcc_version = 2
514 | qt_resource_struct = qt_resource_struct_v2
515 |
516 | def qInitResources():
517 | QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
518 |
519 | def qCleanupResources():
520 | QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
521 |
522 | qInitResources()
523 |
--------------------------------------------------------------------------------