diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..7f8ac9e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,99 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '38 1 * * 2' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" diff --git a/CHANGELOG b/CHANGELOG index 701cf30..7daf450 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,25 @@ +2.0 (03.04/2026) +- Compatibility to Qgis 4 & Qt6 +- Push algorithm to processing +- Ui launch processing algs +1.0 (18.11.2020) +- Fix unkown projection when new layer is created +- Multipart lines supported +0.9 (09.11.2020) +- Move plugin to his self toolbar + +0.8 (23.05.2020) +- fix import resources + +0.7 (17.05.2020) +- fix save layer to file +- remove unattended column in temporary layer + +0.6 (10.11.2019) +- updated to QGIS 3.x +- fix projection according to project coordinates +- quick documentation + 0.5 (24.11.2013) -updated to QGIS 2.0 API (thanks to Matthew Petroff) -fix unicode error @@ -21,4 +43,4 @@ smoothing: Hermite Spline Interpolation -supported algorithms: generalization: Remove small objects simplifying: Douglas-Peucker, Lang, Vertex Reduction -smoothing: Boyle's Forward-Looking, Chaiken's, McMaster's Distance-Weighting, McMaster's Sliding Averaging \ No newline at end of file +smoothing: Boyle's Forward-Looking, Chaiken's, McMaster's Distance-Weighting, McMaster's Sliding Averaging diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..33b3621 --- /dev/null +++ b/LICENSE @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (c) 2019, GISCAN / QGIS + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ada28d --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# ![Logo Generalizer](icon.png)Generalizer +Generalizer plugin for QGIS 3. Originally written by Piotr Pociask + +Plugin to Generalize, Simplify or Smooth lines in QGIS + +Functions : +- Remove small objects + +- Simplify : + * Douglas-Peucker Algorithm + + ![Douglas](https://grass.osgeo.org/grass79/manuals/v_generalize_simplify.png "Original line is black") + + * Jenk's Algorithm + * Lang Algorithm + * Reumann-Witkam Algorithm + * Vertex Reduction + + - Smooth : + * Boyle's Forward-Looking Algorithm + + ![Boyle's](https://grasswiki.osgeo.org/w/images/thumb/V.generalize.pic15.png/400px-V.generalize.pic15.png "Original line is black") + + * Chaiken's Algorithm + + ![Chaiken's smooth](https://grass.osgeo.org/grass79/manuals/v_generalize_smooth.png "Original line is black") + + ![Chaiken's](https://grasswiki.osgeo.org/w/images/thumb/V.generalize.pic7.png/400px-V.generalize.pic7.png "Original is black") + + * Hermite Spline Interpolation + + ![Hermite](https://grasswiki.osgeo.org/w/images/thumb/V.generalize.pic8.png/400px-V.generalize.pic8.png "Original is black") + + ![Hermite and Chaiken](https://grasswiki.osgeo.org/w/images/thumb/V.generalize.pic9.png/400px-V.generalize.pic9.png "Original = black, Green = Chaiken, Blue = Hermite") + + * McMaster's Distance-Weighting Algorithm + + ![Distance Weighting](https://grasswiki.osgeo.org/w/images/thumb/V.generalize.pic13.png/400px-V.generalize.pic13.png "Original line is black") + + * McMaster's Sliding Averaging Algorithm + + ![Sliding](https://grasswiki.osgeo.org/w/images/thumb/V.generalize.pic11.png/400px-V.generalize.pic11.png "Original line is black") + + ![Sliding2](https://grasswiki.osgeo.org/w/images/thumb/V.generalize.pic12.png/400px-V.generalize.pic12.png "Original line is black") + + * Snakes Algorithm : + Slowest smoothing algorithm + + ![Snakes](https://grasswiki.osgeo.org/w/images/thumb/V.generalize.pic14.png/400px-V.generalize.pic14.png "Original line is black") + + + +Which algorithm to choose : https://grasswiki.osgeo.org/wiki/V.generalize_tutorial diff --git a/__init__.py b/__init__.py index 7f2a335..133adc7 100644 --- a/__init__.py +++ b/__init__.py @@ -2,11 +2,12 @@ /*************************************************************************** generalizer A QGIS plugin -"Lines generalization and smoothing (partially based on v.generalize GRASS module)" +"Lines generalization and smoothing (inpired from v.generalize GRASS module)" ------------------- begin : 2011-08-17 copyright : (C) 2011 by Piotr Pociask - email : ppociask (at) o2 pl + (C) 2019 - 2026 by Sylvain POULAIN + email : sylvain dot poulain (at) giscan dot com ***************************************************************************/ /*************************************************************************** @@ -19,7 +20,13 @@ ***************************************************************************/ This script initializes the plugin, making it known to QGIS. """ + + def classFactory(iface): - # load generalizer class from file generalizer - from generalizer import generalizer + """Load generalizer class from file generalizer. + + :param iface: A QGIS interface instance. + :type iface: QgsInterface + """ + from .generalizer import generalizer return generalizer(iface) diff --git a/dialogs.py b/dialogs.py index f13d061..4afc5ce 100644 --- a/dialogs.py +++ b/dialogs.py @@ -1,5 +1,6 @@ -from PyQt4.QtGui import * -from PyQt4.QtCore import * +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import * from os.path import splitext, dirname @@ -9,14 +10,14 @@ def saveDialog(parent): key = '/UI/lastShapefileDir' outDir = settings.value(key) - filter = 'Shapefiles (*.shp)' - outFilePath = QFileDialog.getSaveFileName(parent, parent.tr('Save output shapefile'), outDir, filter) - outFilePath = unicode(outFilePath) + filter = 'GeoPackage (*.gpkg)' + outFilePath, __ = QFileDialog.getSaveFileName(parent, parent.tr('Save output GeoPackage'), outDir, filter) + outFilePath = str(outFilePath) if outFilePath: root, ext = splitext(outFilePath) - if ext.upper() != '.SHP': - outFilePath = '%s.shp' % outFilePath + if ext.upper() != '.GPKG': + outFilePath = '%s.gpkg' % outFilePath outDir = dirname(outFilePath) settings.setValue(key, outDir) @@ -27,5 +28,5 @@ def openDir(parent): key = '/UI/lastShapefileDir' outDir = settings.value(key) - outPath = QFileDialog.getExistingDirectory(parent, 'Generalizer', outDir)#, QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) + outPath = QFileDialog.getExistingDirectory(parent, 'Generalizer', outDir) return outPath diff --git a/generalizer.py b/generalizer.py index 3f69971..ebd8ac2 100644 --- a/generalizer.py +++ b/generalizer.py @@ -2,11 +2,12 @@ /*************************************************************************** generalizer A QGIS plugin - Lines generalization (smooth and simplify) based on v.generalize GRASS module - ------------------- +Lines generalization and smoothing (inpired from v.generalize GRASS module) ------------------- begin : 2011-08-17 copyright : (C) 2011 by Piotr Pociask email : ppociask (at) o2 pl + adapted to QGIS 3 : 2019-11-10 by Sylvain POULAIN + email : sylvain.poulain (at) giscan.com ***************************************************************************/ /*************************************************************************** @@ -18,36 +19,163 @@ * * ***************************************************************************/ """ + + # Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from builtins import object +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import QAction, QMessageBox + from qgis.core import * # Initialize Qt resources from file resources.py -import resources +from .resources import * # Import the code for the dialog -from generalizerdialog import generalizerDialog, getLayersNames +from .generalizerdialog import generalizerDialog, getLayersNames +from .generalizer_provider import GeneralizerProvider +import os.path + class generalizer: def __init__(self, iface): # Save reference to the QGIS interface self.iface = iface + + self.canvas = iface.mapCanvas() + + # initialize plugin directory + self.plugin_dir = os.path.dirname(__file__) + + # Declare instance attributes + self.actions = [] + self.menu = self.tr(u'&Generalizer') + # TODO: We are going to let the user set this up in a future iteration + self.toolbar = self.iface.addToolBar(u'Generalizer') + self.toolbar.setObjectName(u'Generalizer') + + #print "** INITIALIZING Generalizer" + + self.pluginIsActive = False + self.dockwidget = None + self.output_layer = None + self.provider = None + + # noinspection PyMethodMayBeStatic + def tr(self, message): + """Get the translation for a string using Qt translation API. + + We implement this ourselves since we do not inherit QObject. + + :param message: String for translation. + :type message: str, QString + + :returns: Translated version of message. + :rtype: QString + """ + # noinspection PyTypeChecker,PyArgumentList,PyCallByClass + return QCoreApplication.translate('Generalizer', message) + + def add_action( + self, + icon_path, + text, + callback, + enabled_flag=True, + add_to_menu=True, + add_to_toolbar=True, + status_tip=None, + whats_this=None, + parent=None): + """Add a toolbar icon to the toolbar. + + :param icon_path: Path to the icon for this action. Can be a resource + path (e.g. ':/plugins/foo/bar.png') or a normal file system path. + :type icon_path: str + + :param text: Text that should be shown in menu items for this action. + :type text: str + + :param callback: Function to be called when the action is triggered. + :type callback: function + + :param enabled_flag: A flag indicating if the action should be enabled + by default. Defaults to True. + :type enabled_flag: bool + + :param add_to_menu: Flag indicating whether the action should also + be added to the menu. Defaults to True. + :type add_to_menu: bool + + :param add_to_toolbar: Flag indicating whether the action should also + be added to the toolbar. Defaults to True. + :type add_to_toolbar: bool + + :param status_tip: Optional text to show in a popup when mouse pointer + hovers over the action. + :type status_tip: str + + :param parent: Parent widget for the new action. Defaults None. + :type parent: QWidget + + :param whats_this: Optional text to show in the status bar when the + mouse pointer hovers over the action. + + :returns: The action that was created. Note that the action is also + added to self.actions list. + :rtype: QAction + """ + + icon = QIcon(icon_path) + text = "Generalizer" + action = QAction(icon, text, parent) + action.triggered.connect(callback) + action.setEnabled(enabled_flag) + + if status_tip is not None: + action.setStatusTip(status_tip) + + if whats_this is not None: + action.setWhatsThis(whats_this) + + if add_to_toolbar: + self.toolbar.addAction(action) + + if add_to_menu: + self.iface.addPluginToVectorMenu( + self.menu, + action) + + self.actions.append(action) + + return action + def initGui(self): - # Create action that will start plugin configuration - self.action = QAction(QIcon(":/plugins/generalizer/icon.png"), \ - "Generalizer", self.iface.mainWindow()) - # connect the action to the run method - self.action.triggered.connect( self.run ) + """Create the menu entries and toolbar icons inside the QGIS GUI.""" + from qgis.core import QgsApplication + icon_path = ':/plugins/Generalizer3/icon.png' + self.add_action( + icon_path, + text=self.tr(u''), + callback=self.run, + parent=self.iface.mainWindow()) + + self.provider = GeneralizerProvider() + QgsApplication.processingRegistry().addProvider(self.provider) - # Add toolbar button and menu item - self.iface.addToolBarIcon(self.action) - self.iface.addPluginToMenu("&Generalizer", self.action) def unload(self): - # Remove the plugin menu item and icon - self.iface.removePluginMenu("&Generalizer",self.action) - self.iface.removeToolBarIcon(self.action) + """Removes the plugin menu item and icon from QGIS GUI.""" + from qgis.core import QgsApplication + for action in self.actions: + self.iface.removePluginVectorMenu( + self.tr(u'&Generalizer'), + action) + self.iface.removeToolBarIcon(action) + del self.toolbar + if self.provider: + QgsApplication.processingRegistry().removeProvider(self.provider) # run method that performs all the real work def run(self): @@ -60,7 +188,10 @@ def run(self): dlg = generalizerDialog(self.iface) # show the dialog dlg.show() - result = dlg.exec_() + try: + result = dlg.exec() + except: + result = dlg.exec_() # See if OK was pressed if result == 1: # do something useful (delete the line containing pass and diff --git a/generalizer_algorithms.py b/generalizer_algorithms.py new file mode 100644 index 0000000..e945f4f --- /dev/null +++ b/generalizer_algorithms.py @@ -0,0 +1,500 @@ +from qgis.core import ( + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterNumber, + QgsProcessingUtils, + QgsFeatureSink, + QgsWkbTypes, + QgsGeometry, + QgsFeature, + QgsPointXY, + QgsPoint, + QgsProcessingException, + Qgis, +) +from qgis.PyQt.QtGui import QIcon +import os + +from . import smooth, simplify, points as pts_module + + +def _icon(): + return QIcon(os.path.join(os.path.dirname(__file__), 'icon.png')) + + +def _layer_to_line_pnts(geom_part): + """Converts a list of QgsPointXY (QGIS 4) or tuples into line_pnts.""" + return pts_module.Vect_new_line_struct(geom_part) + + +def _process_layer(source, sink, feedback, process_func, **kwargs): + """A common loop over all features, applying process_func to each one.""" + features = list(source.getFeatures()) + total = len(features) + for i, feat in enumerate(features): + if feedback.isCanceled(): + break + feedback.setProgress(int(i / total * 100)) + + geom = feat.geometry() + out_feat = QgsFeature(feat) + + if geom is None or geom.isEmpty(): + sink.addFeature(out_feat, QgsFeatureSink.Flag.FastInsert) + continue + + if geom.isMultipart(): + parts = geom.asMultiPolyline() + new_parts = [] + for part in parts: + p = process_func(part, **kwargs) + coords = [QgsPointXY(p.x[n], p.y[n]) for n in range(p.n_points)] + if len(coords) > 1: + new_parts.append(coords) + if len(new_parts) > 1: + out_feat.setGeometry(QgsGeometry.fromMultiPolylineXY(new_parts)) + elif len(new_parts) == 1: + out_feat.setGeometry(QgsGeometry.fromPolylineXY(new_parts[0])) + else: + continue + else: + part = geom.asPolyline() + p = process_func(part, **kwargs) + coords = [QgsPointXY(p.x[n], p.y[n]) for n in range(p.n_points)] + if len(coords) < 2: + continue + out_feat.setGeometry(QgsGeometry.fromPolylineXY(coords)) + + sink.addFeature(out_feat, QgsFeatureSink.Flag.FastInsert) + + +def _run_remove(part, **kw): + p = pts_module.Vect_new_line_struct(part) + p1 = pts_module.point(); p2 = pts_module.point() + length = 0. + for i in range(p.n_points - 1): + pts_module.point_assign(p, i, p1) + pts_module.point_assign(p, i + 1, p2) + length += pts_module.point_dist(p1, p2) + if length < kw['threshold']: + p.x = []; p.y = []; p.n_points = 0 + return p + +def _run_dp(part, **kw): + l_tuples = [(pt.x(), pt.y()) if hasattr(pt, 'x') else (pt[0], pt[1]) for pt in part] + tmp = simplify.douglas_peucker(l_tuples, kw['threshold']) + return pts_module.Vect_new_line_struct(tmp) + +def _run_jenks(part, **kw): + p = pts_module.Vect_new_line_struct(part) + simplify.jenks(p, kw['threshold'], kw['angle_threshold']) + return p + +def _run_lang(part, **kw): + p = pts_module.Vect_new_line_struct(part) + simplify.lang(p, kw['threshold'], kw['look_ahead']) + return p + +def _run_rw(part, **kw): + p = pts_module.Vect_new_line_struct(part) + simplify.reumann_witkam(p, kw['threshold']) + return p + +def _run_reduction(part, **kw): + p = pts_module.Vect_new_line_struct(part) + simplify.vertex_reduction(p, kw['threshold']) + return p + +def _run_boyle(part, **kw): + p = pts_module.Vect_new_line_struct(part) + smooth.boyle(p, kw['look_ahead']) + return p + +def _run_chaiken(part, **kw): + p = pts_module.Vect_new_line_struct(part) + smooth.chaiken(p, kw['level'], kw['weight']) + return p + +def _run_hermite(part, **kw): + p = pts_module.Vect_new_line_struct(part) + smooth.hermite(p, kw['threshold'], kw['tightness']) + return p + +def _run_distance(part, **kw): + p = pts_module.Vect_new_line_struct(part) + smooth.distance_weighting(p, kw['slide'], kw['look_ahead']) + return p + +def _run_sliding(part, **kw): + p = pts_module.Vect_new_line_struct(part) + smooth.sliding_averaging(p, kw['slide'], kw['look_ahead']) + return p + +def _run_snakes(part, **kw): + p = pts_module.Vect_new_line_struct(part) + smooth.snakes(p, kw['alpha'], kw['beta']) + return p + + +class _BaseGeneralizerAlgorithm(QgsProcessingAlgorithm): + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' + + def icon(self): + return _icon() + + def group(self): + return self.groupName() + + def groupId(self): + return self.group().lower().replace(' ', '_').replace("'", '') + + def groupName(self): + raise NotImplementedError + + def createInstance(self): + return self.__class__() + + def _add_io_params(self): + self.addParameter(QgsProcessingParameterFeatureSource( + self.INPUT, 'Input line layer', + [QgsProcessing.TypeVectorLine])) + self.addParameter(QgsProcessingParameterFeatureSink( + self.OUTPUT, 'Output layer')) + + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + if source is None: + raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) + + (sink, dest_id) = self.parameterAsSink( + parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) + if sink is None: + raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) + + kwargs = self._get_kwargs(parameters, context) + _process_layer(source, sink, feedback, self._run_func, **kwargs) + return {self.OUTPUT: dest_id} + + def _get_kwargs(self, parameters, context): + raise NotImplementedError + + def _run_func(self, part, **kw): + raise NotImplementedError + + +class RemoveSmallObjectsAlgorithm(_BaseGeneralizerAlgorithm): + THRESHOLD = 'THRESHOLD' + + def name(self): return 'remove_small_objects' + def displayName(self): return 'Remove small objects' + def groupName(self): return 'Generalize' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.THRESHOLD, 'Threshold', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0001, defaultValue=1.0)) + + def _get_kwargs(self, p, c): + return {'threshold': self.parameterAsDouble(p, self.THRESHOLD, c)} + + def _run_func(self, part, **kw): return _run_remove(part, **kw) + + def shortHelpString(self): + return 'Removes line features shorter than the threshold distance.' + + +class DouglasPeuckerAlgorithm(_BaseGeneralizerAlgorithm): + THRESHOLD = 'THRESHOLD' + + def name(self): return 'douglas_peucker' + def displayName(self): return 'Douglas-Peucker' + def groupName(self): return 'Simplify' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.THRESHOLD, 'Threshold', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0001, defaultValue=1.0)) + + def _get_kwargs(self, p, c): + return {'threshold': self.parameterAsDouble(p, self.THRESHOLD, c)} + + def _run_func(self, part, **kw): return _run_dp(part, **kw) + + def shortHelpString(self): + return 'Simplifies lines using the Douglas-Peucker algorithm. Higher threshold = more simplification.' + + +class JenksAlgorithm(_BaseGeneralizerAlgorithm): + THRESHOLD = 'THRESHOLD' + ANGLE_THRESHOLD = 'ANGLE_THRESHOLD' + + def name(self): return 'jenks' + def displayName(self): return "Jenks' algorithm" + def groupName(self): return 'Simplify' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.THRESHOLD, 'Threshold', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0, defaultValue=1.0)) + self.addParameter(QgsProcessingParameterNumber( + self.ANGLE_THRESHOLD, 'Angle threshold (degrees)', + type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0, maxValue=180.0, defaultValue=3.0)) + + def _get_kwargs(self, p, c): + return { + 'threshold': self.parameterAsDouble(p, self.THRESHOLD, c), + 'angle_threshold': self.parameterAsDouble(p, self.ANGLE_THRESHOLD, c), + } + + def _run_func(self, part, **kw): return _run_jenks(part, **kw) + + def shortHelpString(self): + return "Simplifies lines using Jenks' algorithm." + + +class LangAlgorithm(_BaseGeneralizerAlgorithm): + THRESHOLD = 'THRESHOLD' + LOOK_AHEAD = 'LOOK_AHEAD' + + def name(self): return 'lang' + def displayName(self): return 'Lang algorithm' + def groupName(self): return 'Simplify' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.THRESHOLD, 'Threshold', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0001, defaultValue=1.0)) + self.addParameter(QgsProcessingParameterNumber( + self.LOOK_AHEAD, 'Look ahead', type=QgsProcessingParameterNumber.Type.Integer, + minValue=1, defaultValue=8)) + + def _get_kwargs(self, p, c): + return { + 'threshold': self.parameterAsDouble(p, self.THRESHOLD, c), + 'look_ahead': self.parameterAsInt(p, self.LOOK_AHEAD, c), + } + + def _run_func(self, part, **kw): return _run_lang(part, **kw) + + def shortHelpString(self): + return 'Simplifies lines using the Lang algorithm.' + + +class ReumannWitkamAlgorithm(_BaseGeneralizerAlgorithm): + THRESHOLD = 'THRESHOLD' + + def name(self): return 'reumann_witkam' + def displayName(self): return 'Reumann-Witkam algorithm' + def groupName(self): return 'Simplify' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.THRESHOLD, 'Threshold', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0001, defaultValue=1.0)) + + def _get_kwargs(self, p, c): + return {'threshold': self.parameterAsDouble(p, self.THRESHOLD, c)} + + def _run_func(self, part, **kw): return _run_rw(part, **kw) + + def shortHelpString(self): + return 'Simplifies lines using the Reumann-Witkam algorithm.' + + +class VertexReductionAlgorithm(_BaseGeneralizerAlgorithm): + THRESHOLD = 'THRESHOLD' + + def name(self): return 'vertex_reduction' + def displayName(self): return 'Vertex reduction' + def groupName(self): return 'Simplify' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.THRESHOLD, 'Threshold', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0001, defaultValue=1.0)) + + def _get_kwargs(self, p, c): + return {'threshold': self.parameterAsDouble(p, self.THRESHOLD, c)} + + def _run_func(self, part, **kw): return _run_reduction(part, **kw) + + def shortHelpString(self): + return 'Removes vertices closer than the threshold distance.' + + +class BoyleAlgorithm(_BaseGeneralizerAlgorithm): + LOOK_AHEAD = 'LOOK_AHEAD' + + def name(self): return 'boyle' + def displayName(self): return "Boyle's forward-looking algorithm" + def groupName(self): return 'Smooth' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.LOOK_AHEAD, 'Look ahead', type=QgsProcessingParameterNumber.Type.Integer, + minValue=2, defaultValue=7)) + + def _get_kwargs(self, p, c): + return {'look_ahead': self.parameterAsInt(p, self.LOOK_AHEAD, c)} + + def _run_func(self, part, **kw): return _run_boyle(part, **kw) + + def shortHelpString(self): + return "Smooths lines using Boyle's forward-looking algorithm." + + +class ChaikenAlgorithm(_BaseGeneralizerAlgorithm): + LEVEL = 'LEVEL' + WEIGHT = 'WEIGHT' + + def name(self): return 'chaiken' + def displayName(self): return "Chaiken's algorithm" + def groupName(self): return 'Smooth' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.LEVEL, 'Level', type=QgsProcessingParameterNumber.Type.Integer, + minValue=1, defaultValue=1)) + self.addParameter(QgsProcessingParameterNumber( + self.WEIGHT, 'Weight', type=QgsProcessingParameterNumber.Type.Double, + minValue=1.0, defaultValue=3.0)) + + def _get_kwargs(self, p, c): + return { + 'level': self.parameterAsInt(p, self.LEVEL, c), + 'weight': self.parameterAsDouble(p, self.WEIGHT, c), + } + + def _run_func(self, part, **kw): return _run_chaiken(part, **kw) + + def shortHelpString(self): + return "Smooths lines using Chaiken's algorithm (corner cutting)." + + +class HermiteAlgorithm(_BaseGeneralizerAlgorithm): + THRESHOLD = 'THRESHOLD' + TIGHTNESS = 'TIGHTNESS' + + def name(self): return 'hermite' + def displayName(self): return 'Hermite spline interpolation' + def groupName(self): return 'Smooth' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.THRESHOLD, 'Threshold', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0001, defaultValue=1.0)) + self.addParameter(QgsProcessingParameterNumber( + self.TIGHTNESS, 'Tightness', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0, maxValue=1.0, defaultValue=0.5)) + + def _get_kwargs(self, p, c): + return { + 'threshold': self.parameterAsDouble(p, self.THRESHOLD, c), + 'tightness': self.parameterAsDouble(p, self.TIGHTNESS, c), + } + + def _run_func(self, part, **kw): return _run_hermite(part, **kw) + + def shortHelpString(self): + return 'Smooths lines using Hermite spline interpolation.' + + +class DistanceWeightingAlgorithm(_BaseGeneralizerAlgorithm): + SLIDE = 'SLIDE' + LOOK_AHEAD = 'LOOK_AHEAD' + + def name(self): return 'distance_weighting' + def displayName(self): return "McMaster's distance-weighting algorithm" + def groupName(self): return 'Smooth' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.SLIDE, 'Slide', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0, maxValue=1.0, defaultValue=0.5)) + self.addParameter(QgsProcessingParameterNumber( + self.LOOK_AHEAD, 'Look ahead (odd number)', type=QgsProcessingParameterNumber.Type.Integer, + minValue=3, defaultValue=7)) + + def _get_kwargs(self, p, c): + la = self.parameterAsInt(p, self.LOOK_AHEAD, c) + if la % 2 == 0: + la += 1 # forcer impair silencieusement + return {'slide': self.parameterAsDouble(p, self.SLIDE, c), 'look_ahead': la} + + def _run_func(self, part, **kw): return _run_distance(part, **kw) + + def shortHelpString(self): + return "Smooths lines using McMaster's distance-weighting algorithm. Look ahead must be odd." + + +class SlidingAveragingAlgorithm(_BaseGeneralizerAlgorithm): + SLIDE = 'SLIDE' + LOOK_AHEAD = 'LOOK_AHEAD' + + def name(self): return 'sliding_averaging' + def displayName(self): return "McMaster's sliding averaging algorithm" + def groupName(self): return 'Smooth' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.SLIDE, 'Slide', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0, maxValue=1.0, defaultValue=0.5)) + self.addParameter(QgsProcessingParameterNumber( + self.LOOK_AHEAD, 'Look ahead (odd number)', type=QgsProcessingParameterNumber.Type.Integer, + minValue=3, defaultValue=7)) + + def _get_kwargs(self, p, c): + la = self.parameterAsInt(p, self.LOOK_AHEAD, c) + if la % 2 == 0: + la += 1 + return {'slide': self.parameterAsDouble(p, self.SLIDE, c), 'look_ahead': la} + + def _run_func(self, part, **kw): return _run_sliding(part, **kw) + + def shortHelpString(self): + return "Smooths lines using McMaster's sliding averaging algorithm. Look ahead must be odd." + + +class SnakesAlgorithm(_BaseGeneralizerAlgorithm): + ALPHA = 'ALPHA' + BETA = 'BETA' + + def name(self): return 'snakes' + def displayName(self): return 'Snakes algorithm' + def groupName(self): return 'Smooth' + + def initAlgorithm(self, config=None): + self._add_io_params() + self.addParameter(QgsProcessingParameterNumber( + self.ALPHA, 'Alpha', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0, defaultValue=1.0)) + self.addParameter(QgsProcessingParameterNumber( + self.BETA, 'Beta', type=QgsProcessingParameterNumber.Type.Double, + minValue=0.0, defaultValue=0.5)) + + def _get_kwargs(self, p, c): + return { + 'alpha': self.parameterAsDouble(p, self.ALPHA, c), + 'beta': self.parameterAsDouble(p, self.BETA, c), + } + + def _run_func(self, part, **kw): return _run_snakes(part, **kw) + + def shortHelpString(self): + return 'Smooths lines using the Snakes algorithm (slowest but highest quality).' diff --git a/generalizer_provider.py b/generalizer_provider.py new file mode 100644 index 0000000..e84436d --- /dev/null +++ b/generalizer_provider.py @@ -0,0 +1,54 @@ +from qgis.core import QgsProcessingProvider +from qgis.PyQt.QtGui import QIcon +import os + +from .generalizer_algorithms import ( + RemoveSmallObjectsAlgorithm, + DouglasPeuckerAlgorithm, + JenksAlgorithm, + LangAlgorithm, + ReumannWitkamAlgorithm, + VertexReductionAlgorithm, + BoyleAlgorithm, + ChaikenAlgorithm, + HermiteAlgorithm, + DistanceWeightingAlgorithm, + SlidingAveragingAlgorithm, + SnakesAlgorithm, +) + + +class GeneralizerProvider(QgsProcessingProvider): + + def __init__(self): + super().__init__() + + def id(self): + return 'generalizer3' + + def name(self): + return 'Generalizer' + + def longName(self): + return 'Generalizer - line simplification and smoothing' + + def icon(self): + icon_path = os.path.join(os.path.dirname(__file__), 'icon.png') + return QIcon(icon_path) + + def loadAlgorithms(self): + for alg in [ + RemoveSmallObjectsAlgorithm(), + DouglasPeuckerAlgorithm(), + JenksAlgorithm(), + LangAlgorithm(), + ReumannWitkamAlgorithm(), + VertexReductionAlgorithm(), + BoyleAlgorithm(), + ChaikenAlgorithm(), + HermiteAlgorithm(), + DistanceWeightingAlgorithm(), + SlidingAveragingAlgorithm(), + SnakesAlgorithm(), + ]: + self.addAlgorithm(alg) diff --git a/generalizerdialog.py b/generalizerdialog.py index c09aff0..33925d9 100644 --- a/generalizerdialog.py +++ b/generalizerdialog.py @@ -1,55 +1,94 @@ """ /*************************************************************************** - generalizerDialog - A QGIS plugin - Lines generalization (smooth and simplify) based on v.generalize GRASS module - ------------------- + generalizerDialog — QGIS 4 +Lines generalization and smoothing (inpired from v.generalize GRASS module) begin : 2011-08-17 copyright : (C) 2011 by Piotr Pociask - email : ppociask (at) o2 pl - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * + adapted to QGIS 3 & 4: 2019 - 2026 by Sylvain POULAIN + email : sylvain dot poulain (at) giscan dotcom ***************************************************************************/ """ - -from PyQt4.QtCore import * -from PyQt4.QtGui import * - -from qgis.core import * -from qgis.gui import * - -import smooth, simplify, points -from dialogs import * - -from ui_generalizer import Ui_generalizer - -#global variable with short to full algorithm names -algorithm = {'remove':'Remove small objects', - 'DP':'Douglas-Peucker Algorithm', - 'lang':'Lang Algorithm', - 'reduction':'Vertex Reduction', - 'boyle':'Boyle\'s Forward-Looking Algorithm', - 'chaiken':'Chaiken\'s Algorithm', - 'hermite':'Hermite Spline Interpolation', - 'distance':'McMaster\'s Distance-Weighting Algorithm', - 'sliding':'McMaster\'s Sliding Averaging Algorithm', - 'snakes':'Snakes Algorithm', - 'jenks':'Jenk\'s Algorithm', - 'RW':'Reumann-Witkam Algorithm' - } +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtWidgets import (QDialog, QMessageBox, QInputDialog, + QDoubleSpinBox, QSpinBox, QTableWidgetItem) +from qgis.core import QgsProject, QgsMapLayer, QgsMessageLog, Qgis + +import processing + +from .dialogs import saveDialog, openDir +from .ui_generalizer import Ui_generalizer + +try: + UNCHECKED = Qt.CheckState.Unchecked + CHECKED = Qt.CheckState.Checked +except AttributeError: + UNCHECKED = Qt.Unchecked + CHECKED = Qt.Checked + +ALGO_ID = { + 'Remove small objects': 'generalizer3:remove_small_objects', + 'Douglas-Peucker Algorithm': 'generalizer3:douglas_peucker', + "Jenk's Algorithm": 'generalizer3:jenks', + 'Lang Algorithm': 'generalizer3:lang', + 'Reumann-Witkam Algorithm': 'generalizer3:reumann_witkam', + 'Vertex Reduction': 'generalizer3:vertex_reduction', + "Boyle's Forward-Looking Algorithm": 'generalizer3:boyle', + "Chaiken's Algorithm": 'generalizer3:chaiken', + 'Hermite Spline Interpolation': 'generalizer3:hermite', + "McMaster's Distance-Weighting Algorithm": 'generalizer3:distance_weighting', + "McMaster's Sliding Averaging Algorithm": 'generalizer3:sliding_averaging', + 'Snakes Algorithm': 'generalizer3:snakes', +} + +def _params_from_ui(alg_name, ui): + """Builds the Processing parameters from the dialog's widgets.""" + mapping = { + 'Remove small objects': lambda: {'THRESHOLD': ui.sbRemove_thresh.value()}, + 'Douglas-Peucker Algorithm': lambda: {'THRESHOLD': ui.sbDP_thresh.value()}, + "Jenk's Algorithm": lambda: {'THRESHOLD': ui.sbJenks_thresh.value(), + 'ANGLE_THRESHOLD': ui.sbJenks_angle.value()}, + 'Lang Algorithm': lambda: {'THRESHOLD': ui.sbLang_thresh.value(), + 'LOOK_AHEAD': ui.sbLang_LA.value()}, + 'Reumann-Witkam Algorithm': lambda: {'THRESHOLD': ui.sbRW_thresh.value()}, + 'Vertex Reduction': lambda: {'THRESHOLD': ui.sbReduction_thresh.value()}, + "Boyle's Forward-Looking Algorithm": lambda: {'LOOK_AHEAD': ui.sbBoyle_LA.value()}, + "Chaiken's Algorithm": lambda: {'LEVEL': ui.sbChaiken_level.value(), + 'WEIGHT': ui.sbChaiken_weight.value()}, + 'Hermite Spline Interpolation': lambda: {'THRESHOLD': ui.sbHermite_steps.value(), + 'TIGHTNESS': ui.sbHermite_tightness.value()}, + "McMaster's Distance-Weighting Algorithm": lambda: {'SLIDE': ui.sbDist_slide.value(), + 'LOOK_AHEAD': ui.sbDist_LA.value()}, + "McMaster's Sliding Averaging Algorithm": lambda: {'SLIDE': ui.sbSlide_slide.value(), + 'LOOK_AHEAD': ui.sbSlide_LA.value()}, + 'Snakes Algorithm': lambda: {'ALPHA': ui.sbSnakes_alpha.value(), + 'BETA': ui.sbSnakes_beta.value()}, + } + return mapping[alg_name]() + + +def _params_from_table(alg_name, par1, par2): + """Constructs the Processing parameters from the values in the batch array.""" + mapping = { + 'Remove small objects': lambda: {'THRESHOLD': par1}, + 'Douglas-Peucker Algorithm': lambda: {'THRESHOLD': par1}, + "Jenk's Algorithm": lambda: {'THRESHOLD': par1, 'ANGLE_THRESHOLD': par2 if par2 != -1 else 3.0}, + 'Lang Algorithm': lambda: {'THRESHOLD': par1, 'LOOK_AHEAD': int(par2)}, + 'Reumann-Witkam Algorithm': lambda: {'THRESHOLD': par1}, + 'Vertex Reduction': lambda: {'THRESHOLD': par1}, + "Boyle's Forward-Looking Algorithm": lambda: {'LOOK_AHEAD': int(par1)}, + "Chaiken's Algorithm": lambda: {'LEVEL': int(par1), 'WEIGHT': par2}, + 'Hermite Spline Interpolation': lambda: {'THRESHOLD': par1, 'TIGHTNESS': par2}, + "McMaster's Distance-Weighting Algorithm": lambda: {'SLIDE': par1, 'LOOK_AHEAD': int(par2)}, + "McMaster's Sliding Averaging Algorithm": lambda: {'SLIDE': par1, 'LOOK_AHEAD': int(par2)}, + 'Snakes Algorithm': lambda: {'ALPHA': par1, 'BETA': par2}, + } + return mapping[alg_name]() class generalizerDialog(QDialog): + def __init__(self, iface): QDialog.__init__(self) - # Set up the user interface from Designer. self.ui = Ui_generalizer() self.ui.setupUi(self) self.iface = iface @@ -57,709 +96,296 @@ def __init__(self, iface): self.ui.sbJenks_angle.setVisible(False) self.ui.label_8.setVisible(False) - #set signals - self.ui.bBrowse.clicked.connect( self.outFile ) - self.ui.bBrowseDir.clicked.connect( self.outDir ) - self.ui.bOk.clicked.connect( self.generalize ) - self.ui.cbAlgorithm.currentIndexChanged.connect( self.cbChange ) - self.ui.bHelp.clicked.connect( self.showHelp ) - self.ui.cbBatch.stateChanged.connect( self.BatchOn ) - self.ui.bAddAlg.clicked.connect( self.AddAlgorithm ) - self.ui.bDelAlg.clicked.connect( self.DelAlgorithm ) - self.ui.bEditAlg.clicked.connect( self.EditAlgorithm ) - self.ui.cbOutFile.stateChanged.connect( self.FileEnabled ) - self.ui.cbOutDir.stateChanged.connect( self.DirEnabled ) - - #load line layers to lists + self.ui.bBrowse.clicked.connect(self.outFile) + self.ui.bBrowseDir.clicked.connect(self.outDir) + self.ui.bOk.clicked.connect(self.generalize) + self.ui.cbAlgorithm.currentIndexChanged.connect(self.cbChange) + self.ui.bHelp.clicked.connect(self.showHelp) + self.ui.cbBatch.stateChanged.connect(self.BatchOn) + self.ui.bAddAlg.clicked.connect(self.AddAlgorithm) + self.ui.bDelAlg.clicked.connect(self.DelAlgorithm) + self.ui.bEditAlg.clicked.connect(self.EditAlgorithm) + self.ui.cbOutFile.stateChanged.connect(self.FileEnabled) + self.ui.cbOutDir.stateChanged.connect(self.DirEnabled) + + self.cbChange(self.ui.cbAlgorithm.currentIndex()) + self.layerList = getLayersNames() self.ui.cbInput.addItems(self.layerList) self.ui.lstLayers.addItems(self.layerList) - [self.ui.lstLayers.item(i).setCheckState(Qt.Unchecked) for i in range(self.ui.lstLayers.count()) ] + [self.ui.lstLayers.item(i).setCheckState(UNCHECKED) for i in range(self.ui.lstLayers.count())] + def FileEnabled(self, state): - #enable or disable path to file enabled = self.ui.eOutput.isEnabled() self.ui.eOutput.setEnabled(not enabled) self.ui.bBrowse.setEnabled(not enabled) def DirEnabled(self, state): - #enable/disable directory enabled = self.ui.eDir.isEnabled() self.ui.eDir.setEnabled(not enabled) self.ui.bBrowseDir.setEnabled(not enabled) + def BatchOn(self, state): + self.ui.stackBatch.setCurrentIndex(0 if state == 0 else 1) + + def showHelp(self): + QMessageBox.information(self, 'Generalizer', ( + 'Generalizer v2.0\n\n' + 'Adapted to QGIS 4 by Sylvain POULAIN\n' + 'Originally writtent by Piotr Pociask\n\n' + 'Docs: https://github.com/giscan/Generalizer/wiki' + )) + + def outFile(self): + path = saveDialog(self) + if path: + self.ui.eOutput.setText(path) + + def outDir(self): + path = openDir(self) + if path: + self.ui.eDir.setText(path) + + def cbChange(self, index): + combo_to_stack = { + 1: 0, 3: 1, 4: 2, 5: 3, 6: 4, 7: 5, + 9: 6, 10: 7, 11: 8, 12: 9, 13: 10, 14: 11, + } + if index in {0, 2, 8}: + self.ui.cbAlgorithm.setCurrentIndex(index + 1) + return + if index in combo_to_stack: + self.ui.stackOptions.setCurrentIndex(combo_to_stack[index]) + def AddAlgorithm(self): - #add new algorithm in batch mode self.doAddAlgorithm(self.ui.tblBatchAlg.rowCount()) def EditAlgorithm(self): - #edit algorithm in batch mode if self.ui.tblBatchAlg.currentRow() == -1: QMessageBox.warning(self, 'Generalizer', 'Select algorithm to edit!') return - self.doAddAlgorithm(self.ui.tblBatchAlg.currentRow()) def doAddAlgorithm(self, index): - #add new algorithm in batch mode - global algorithm - - new = index > self.ui.tblBatchAlg.rowCount()-1 - items = [self.ui.cbAlgorithm.itemText(i) for i in range(self.ui.cbAlgorithm.count())] - algName = QInputDialog.getItem(None, 'Generalizer', 'Choose algorithm:', items, 1, False) - if not algName[1] or algName[0].startswith('-'): return - #QMessageBox.question(self, 'Generalizer', str(alg)) - par1 = None - par2 = None - - if algName[0] == algorithm['boyle']:#Boyle\'s Forward-Looking Algorithm': - par1 = QSpinBox() - par1.setRange(2, 999) - msg = QInputDialog.getInt(None, 'Generalizer', 'Look ahead:', 7, 2) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Look ahead') - - elif algName[0] == algorithm['sliding']:#'McMaster\'s Sliding Averaging Algorithm': - par1 = QDoubleSpinBox() - par1.setDecimals(2) - par1.setRange(0, 99.99) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Slide:', 0.5, 0, 99.99) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Slide') - - par2 = QSpinBox() - par2.setRange(3, 999) - par2.setSingleStep(2) - par2.setValue(6) - while par2.value()%2 == 0: - msg = QInputDialog.getInt(None, 'Generalizer', 'Look ahead (must be odd number):', par2.value()+1, 3, 999) - if not msg[1]: return - par2.setValue(msg[0]) - par2.setToolTip('Look ahead') - - - elif algName[0] == algorithm['distance']:#'McMaster\'s Distance-Weighting Algorithm': - par1 = QDoubleSpinBox() - par1.setDecimals(2) - par1.setRange(0, 99.99) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Slide:', 0.5, 0, 99.99) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Slide') - - par2 = QSpinBox() - par2.setRange(3, 999) - par2.setSingleStep(2) - par2.setValue(6) - while par2.value()%2 == 0: - msg = QInputDialog.getInt(None, 'Generalizer', 'Look ahead (must be odd number):', par2.value()+1, 3, 999) - if not msg[1]: return - par2.setValue(msg[0]) - par2.setToolTip('Look ahead') - - elif algName[0] == algorithm['chaiken']: #'Chaiken\'s Algorithm': - par1 = QSpinBox() - par1.setRange(0, 99) - msg = QInputDialog.getInt(None, 'Generalizer', 'Level:', 1, 0, 99) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Level') - - par2 = QDoubleSpinBox() - par2.setDecimals(2) - par2.setRange(1, 99.99) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Weight:', 3., 1, 99.99) - if not msg[1]: return - par2.setValue(msg[0]) - par2.setToolTip('Weight') - - elif algName[0] == algorithm['reduction']:#'Vertex Reduction': - par1 = QDoubleSpinBox() - par1.setDecimals(4) - par1.setRange(0.0001, 9999999.9999) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 0.0001, 0.0001, 9999999.9999, 4) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Threshold') - - elif algName[0] == algorithm['DP']:#'Douglas-Peucker Algorithm': - par1 = QDoubleSpinBox() - par1.setDecimals(4) - par1.setRange(0.0001, 9999999.9999) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 0.0001, 0.0001, 9999999.9999, 4) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Threshold') - - elif algName[0] == algorithm['remove']:#'Remove small objects': - par1 = QDoubleSpinBox() - par1.setDecimals(4) - par1.setRange(0.0001, 9999999.9999) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 0.0001, 0.0001, 9999999.9999, 4) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Threshold') - - elif algName[0] == algorithm['lang']:#'Lang Algorithm': - par1 = QDoubleSpinBox() - par1.setDecimals(2) - par1.setRange(0.0001, 9999999.9999) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 0.0001, 0.0001, 9999999.9999, 4) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Threshold') - - par2 = QSpinBox() - par2.setRange(1, 9999) - msg = QInputDialog.getInt(None, 'Generalizer', 'Look ahead:', 8, 1, 999) - if not msg[1]: return - par2.setValue(msg[0]) - par2.setToolTip('Look ahead') - - - elif algName[0] == algorithm['hermite']:#'Hermite Spline Interpolation': - par1 = QDoubleSpinBox() - par1.setDecimals(4) - par1.setRange(0.0001, 9999999.9999) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 2., 0.0001, 9999999.9999, 4) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Threshold') - - par2 = QDoubleSpinBox() - par2.setDecimals(2) - par2.setRange(0, 1) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Thightness:', 0.5, 0, 1, 2) - if not msg[1]: return - par2.setValue(msg[0]) - par2.setToolTip('Thightness') - - elif algName[0] == algorithm['snakes']:#'Snakes algorithm': - par1 = QDoubleSpinBox() - par1.setDecimals(2) - par1.setRange(0, 9999.99) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Alpha:', 1., 0.00, 9999.99, 2) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Alpha') - - par2 = QDoubleSpinBox() - par2.setDecimals(2) - par2.setRange(0, 9999.99) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Beta:', 0.5, 0., 9999.99, 2) - if not msg[1]: return - par2.setValue(msg[0]) - par2.setToolTip('Beta') - - elif algName[0] == algorithm['jenks']:#'Snakes algorithm': - par1 = QDoubleSpinBox() - par1.setDecimals(4) - par1.setRange(0, 9999999.9999) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 0.0001, 0.00, 9999999.9999, 4) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Threshold') - - """par2 = QDoubleSpinBox() - par2.setRange(0, 180) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Angle threshold:', 3, 0., 180, 2) - if not msg[1]: return - par2.setValue(msg[0]) - par2.setToolTip('Angle threshold') """ - - elif algName[0] == algorithm['RW']:#Reumann-Witkam Algorithm - par1 = QDoubleSpinBox() - par1.setDecimals(4) - par1.setRange(0, 9999999.9999) - msg = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 0.0001, 0.00, 9999999.9999, 4) - if not msg[1]: return - par1.setValue(msg[0]) - par1.setToolTip('Threshold') - - #QMessageBox.question(self, 'Generalizer', str(type(par1))) - itemAlg = QTableWidgetItem(algName[0]) - itemAlg.setFlags(Qt.ItemIsEnabled) - + choice = QInputDialog.getItem(None, 'Generalizer', 'Choose algorithm:', items, 1, False) + if not choice[1] or choice[0].startswith('-'): + return + name = choice[0] + par1 = par2 = None + + if name in ('Remove small objects', 'Douglas-Peucker Algorithm', + 'Reumann-Witkam Algorithm', 'Vertex Reduction'): + v = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 1.0, 0.0001, 9999999.0, 4) + if not v[1]: return + par1 = QDoubleSpinBox(); par1.setDecimals(4); par1.setRange(0.0001, 9999999.0) + par1.setValue(v[0]); par1.setToolTip('Threshold') + + elif name == "Boyle's Forward-Looking Algorithm": + v = QInputDialog.getInt(None, 'Generalizer', 'Look ahead:', 7, 2, 999) + if not v[1]: return + par1 = QSpinBox(); par1.setRange(2, 999); par1.setValue(v[0]); par1.setToolTip('Look ahead') + + elif name == "Chaiken's Algorithm": + v = QInputDialog.getInt(None, 'Generalizer', 'Level:', 1, 1, 99) + if not v[1]: return + par1 = QSpinBox(); par1.setRange(1, 99); par1.setValue(v[0]); par1.setToolTip('Level') + v2 = QInputDialog.getDouble(None, 'Generalizer', 'Weight:', 3.0, 1.0, 99.99, 2) + if not v2[1]: return + par2 = QDoubleSpinBox(); par2.setRange(1.0, 99.99); par2.setValue(v2[0]); par2.setToolTip('Weight') + + elif name == 'Hermite Spline Interpolation': + v = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 1.0, 0.0001, 9999999.0, 4) + if not v[1]: return + par1 = QDoubleSpinBox(); par1.setDecimals(4); par1.setRange(0.0001, 9999999.0) + par1.setValue(v[0]); par1.setToolTip('Threshold') + v2 = QInputDialog.getDouble(None, 'Generalizer', 'Tightness:', 0.5, 0.0, 1.0, 2) + if not v2[1]: return + par2 = QDoubleSpinBox(); par2.setRange(0.0, 1.0); par2.setValue(v2[0]); par2.setToolTip('Tightness') + + elif name == 'Lang Algorithm': + v = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 1.0, 0.0001, 9999999.0, 4) + if not v[1]: return + par1 = QDoubleSpinBox(); par1.setDecimals(4); par1.setRange(0.0001, 9999999.0) + par1.setValue(v[0]); par1.setToolTip('Threshold') + v2 = QInputDialog.getInt(None, 'Generalizer', 'Look ahead:', 8, 1, 9999) + if not v2[1]: return + par2 = QSpinBox(); par2.setRange(1, 9999); par2.setValue(v2[0]); par2.setToolTip('Look ahead') + + elif name in ("McMaster's Distance-Weighting Algorithm", + "McMaster's Sliding Averaging Algorithm"): + v = QInputDialog.getDouble(None, 'Generalizer', 'Slide:', 0.5, 0.0, 99.99, 2) + if not v[1]: return + par1 = QDoubleSpinBox(); par1.setRange(0.0, 99.99); par1.setValue(v[0]); par1.setToolTip('Slide') + la = 6 + while la % 2 == 0: + v2 = QInputDialog.getInt(None, 'Generalizer', 'Look ahead (odd):', la + 1, 3, 999) + if not v2[1]: return + la = v2[0] + par2 = QSpinBox(); par2.setRange(3, 999); par2.setSingleStep(2) + par2.setValue(la); par2.setToolTip('Look ahead') + + elif name == 'Snakes Algorithm': + v = QInputDialog.getDouble(None, 'Generalizer', 'Alpha:', 1.0, 0.0, 9999.99, 2) + if not v[1]: return + par1 = QDoubleSpinBox(); par1.setRange(0.0, 9999.99); par1.setValue(v[0]); par1.setToolTip('Alpha') + v2 = QInputDialog.getDouble(None, 'Generalizer', 'Beta:', 0.5, 0.0, 9999.99, 2) + if not v2[1]: return + par2 = QDoubleSpinBox(); par2.setRange(0.0, 9999.99); par2.setValue(v2[0]); par2.setToolTip('Beta') + + elif name == "Jenk's Algorithm": + v = QInputDialog.getDouble(None, 'Generalizer', 'Threshold:', 1.0, 0.0, 9999999.0, 4) + if not v[1]: return + par1 = QDoubleSpinBox(); par1.setDecimals(4); par1.setRange(0.0, 9999999.0) + par1.setValue(v[0]); par1.setToolTip('Threshold') + + new = index > self.ui.tblBatchAlg.rowCount() - 1 if new: - self.ui.tblBatchAlg.setRowCount(self.ui.tblBatchAlg.rowCount()+1) + self.ui.tblBatchAlg.setRowCount(self.ui.tblBatchAlg.rowCount() + 1) - self.ui.tblBatchAlg.setItem(index, 0, itemAlg) + item = QTableWidgetItem(name) + item.setFlags(Qt.ItemFlag.ItemIsEnabled) + self.ui.tblBatchAlg.setItem(index, 0, item) self.ui.tblBatchAlg.setCellWidget(index, 1, par1) - if par2 == None: + if par2 is None: self.ui.tblBatchAlg.setCellWidget(index, 2, None) - par2 = QTableWidgetItem(0) - par2.setFlags(Qt.ItemIsSelectable) - self.ui.tblBatchAlg.setItem(index, 2, par2) + empty = QTableWidgetItem('') + empty.setFlags(Qt.ItemFlag.ItemIsSelectable) + self.ui.tblBatchAlg.setItem(index, 2, empty) else: self.ui.tblBatchAlg.setCellWidget(index, 2, par2) def DelAlgorithm(self): - #del algorithm in batch mode if self.ui.tblBatchAlg.currentRow() == -1: QMessageBox.warning(self, 'Generalizer', 'Select algorithm to delete!') return - alg = self.ui.tblBatchAlg.item(self.ui.tblBatchAlg.currentRow(), 0).text() - msg = QMessageBox.question(self, 'Generalizer', 'Do you want to delete %s' % (alg), QMessageBox.Yes | QMessageBox.No) - - if msg == QMessageBox.Yes: + msg = QMessageBox.question(self, 'Generalizer', f'Delete {alg}?', + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + if msg == QMessageBox.StandardButton.Yes: self.ui.tblBatchAlg.removeRow(self.ui.tblBatchAlg.currentRow()) - def BatchOn(self, state): - #set batch mode on/off - if state == 0: - self.ui.stackBatch.setCurrentIndex(0) - else: - self.ui.stackBatch.setCurrentIndex(1) - - def showHelp(self): - #show information about plugin - QMessageBox.information(self, 'Generalizer', """Generalizer -Version 0.5 - -Created by -Piotr Pociask - -This plugin is marked as experimental. -If you find any bugs or have suggestions, -please contact with me: -opengis84 (at) gmail (dot) com - -""") - - def outFile(self): - """Open a file save dialog and set the output file path.""" - outFilePath = saveDialog(self) - if not outFilePath: - return - self.ui.eOutput.setText(outFilePath) - - def outDir(self): - #select directory to save layer(s) in created batch mode - outPath = openDir(self) - if outPath: - self.ui.eDir.setText(outPath) - - def cbChange(self, index): - #set parameters after algorithm change - if index == 0: self.ui.cbAlgorithm.setCurrentIndex(1) - elif index == 1: self.ui.stackOptions.setCurrentIndex(index-1) #generalization - elif index == 2: self.ui.cbAlgorithm.setCurrentIndex(3) - elif index < 8: self.ui.stackOptions.setCurrentIndex(index-2) #simplify - elif index == 8: self.ui.cbAlgorithm.setCurrentIndex(9) - else: self.ui.stackOptions.setCurrentIndex(index-3) #smooth - - - def GetArguments(self, par1=-1, par2=-1): - #set parameters to algorithm - if not self.ui.cbBatch.checkState(): - arguments = {} - arguments['remove_thresh'] = self.ui.sbRemove_thresh.value() - arguments['dp_thresh'] = self.ui.sbDP_thresh.value() - arguments['lang_thresh'] = self.ui.sbLang_thresh.value() - arguments['lang_LA'] = self.ui.sbLang_LA.value() - arguments['reduction_thresh'] = self.ui.sbReduction_thresh.value() - arguments['boyle_LA'] = self.ui.sbBoyle_LA.value() - arguments['slide_slide'] = self.ui.sbSlide_slide.value() - arguments['slide_LA'] = self.ui.sbSlide_LA.value() - arguments['dist_slide'] = self.ui.sbDist_slide.value() - arguments['dist_LA'] = self.ui.sbDist_LA.value() - arguments['chaiken_level'] = self.ui.sbChaiken_level.value() - arguments['chaiken_weight'] = self.ui.sbChaiken_weight.value() - arguments['hermite_thresh'] = self.ui.sbHermite_steps.value() - arguments['hermite_tightness'] = self.ui.sbHermite_tightness.value() - arguments['jenks_thresh'] = self.ui.sbJenks_thresh.value() - arguments['jenks_angle'] = self.ui.sbJenks_angle.value() - arguments['snakes_alpha'] = self.ui.sbSnakes_alpha.value() - arguments['snakes_beta'] = self.ui.sbSnakes_beta.value() - arguments['rw_thresh'] = self.ui.sbRW_thresh.value() - else: - arguments = {} - arguments['remove_thresh'] = par1 - arguments['dp_thresh'] = par1 - arguments['lang_thresh'] = par1 - arguments['lang_LA'] = par2 - arguments['reduction_thresh'] = par1 - arguments['boyle_LA'] = par1 - arguments['slide_slide'] = par1 - arguments['slide_LA'] = par2 - arguments['dist_slide'] = par1 - arguments['dist_LA'] = par2 - arguments['chaiken_level'] = par1 - arguments['chaiken_weight'] = par2 - arguments['hermite_thresh'] = par1 - arguments['hermite_tightness'] = par2 - arguments['jenks_thresh'] = par1 - arguments['jenks_angle'] = par2 - arguments['snakes_alpha'] = par1 - arguments['snakes_beta'] = par2 - arguments['rw_thresh'] = par1 - - if (arguments['slide_LA']%2 == 0) or (arguments['dist_LA']%2 == 0): - QMessageBox.critical(None, 'Generalizer', 'Look ehead parameter must be odd number!') + def _run(self, alg_name, layer, params, output='TEMPORARY_OUTPUT'): + """Calls processing.run() and returns the result layer or None.""" + proc_id = ALGO_ID.get(alg_name) + if proc_id is None: + QMessageBox.critical(None, 'Generalizer', f'Unknown algorithm: {alg_name}') + return None + try: + result = processing.run(proc_id, {'INPUT': layer, 'OUTPUT': output, **params}) + return result['OUTPUT'] + except Exception as e: + QgsMessageLog.logMessage(str(e), 'Generalizer3', level=Qgis.Critical) + QMessageBox.critical(None, 'Generalizer', f'Error in {alg_name}:\n{e}') return None - else: - return arguments - - def GetFunction(self, funcName): - #set function from name - global algorithm - - if funcName == algorithm['boyle']:#'Boyle\'s Forward-Looking Algorithm': - return self.boyle - elif funcName == algorithm['sliding']:#'McMaster\'s Sliding Averaging Algorithm': - return self.sliding_averaging - elif funcName == algorithm['distance']:#'McMaster\'s Distance-Weighting Algorithm': - return self.distance_weighting - elif funcName == algorithm['chaiken']:#'Chaiken\'s Algorithm': - return self.chaiken - elif funcName == algorithm['reduction']:#'Vertex Reduction': - return self.vertex_reduction - elif funcName == algorithm['DP']:#'Douglas-Peucker Algorithm': - return self.douglas_peucker - elif funcName == algorithm['remove']:#'Remove small objects': - return self.remove - elif funcName == algorithm['lang']:#'Lang Algorithm': - return self.lang - elif funcName == algorithm['hermite']:#'Hermite Spline Interpolation': - return self.hermite - elif funcName == algorithm['jenks']:#'Jenk's Algorithm': - return self.jenks - elif funcName == algorithm['snakes']:#'Snakes': - return self.snakes - elif funcName == algorithm['RW']:#'Reumann-Witkam Algorithm': - return self.reumann_witkam - - def NameFromFunc(self, func, arguments): - if func == self.boyle: - return '-boyle_LA-' + str(arguments['boyle_LA']) - elif func == self.sliding_averaging: - return '-slide_slide-' + str(arguments['slide_slide']) + '_LA-' + str(arguments['slide_LA']) - elif func == self.distance_weighting: - return '-dist_slide-' + str(arguments['dist_slide']) + '_LA-' + str(arguments['dist_LA']) - elif func == self.chaiken: - return '-chaiken_level-' + str(arguments['chaiken_level']) + '_weight-' + str(arguments['chaiken_weight']) - elif func == self.vertex_reduction: - return '-reduction_thresh-' + str(arguments['reduction_thresh']) - elif func == self.douglas_peucker: - return '-DP_thresh-' + str(arguments['dp_thresh']) - elif func == self.remove: - return '-remove_thresh-' + str(arguments['remove_thresh']) - elif func == self.lang: - return '-lang_thresh-' + str(arguments['lang_thresh']) + '_LA-' + str(arguments['lang_LA']) - elif func == self.hermite: - return '-hermite_thresh-' + str(arguments['hermite_thresh']) + '_tight-' + str(arguments['hermite_tightness']) - elif func == self.jenks: - return '-jenks_thresh-' + str(arguments['jenks_thresh']) + '_angle-' + str(arguments['jenks_angle']) - elif func == self.snakes: - return '-snakes_alpha-' + str(arguments['snakes_alpha']) + '_beta-' + str(arguments['snakes_beta']) - elif func == self.reumann_witkam: - return '-RW_thresh-' + str(arguments['rw_thresh']) - - - def LoadLayers(self, fileList): - #load created layer - msg = QMessageBox.question(self, 'Generalizer', 'New layer(s) created. \n Add to TOC?', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) - if msg == QMessageBox.Yes: - for filePath in fileList: - if "\\" in filePath: - out_name = filePath[(len(filePath) - filePath.rfind("\\")) - 1:] - else: - out_name = filePath[(len(filePath) - filePath.rfind("/")) - 1:] - - if out_name.endswith(".shp"): - out_name = out_name[:len(out_name) - 4] - - self.iface.addVectorLayer(filePath, out_name, "ogr") - - def doGeneralize(self, iLayerName, iLayer, oPath, func, arguments): - #do calculations - fet = QgsFeature() + def generalize(self): + if self.ui.cbBatch.isChecked(): + if self.ui.cbOutDir.isChecked() and self.ui.eDir.text() == '': + QMessageBox.critical(None, 'Generalizer', 'Enter output directory!') + return - iProvider = iLayer.dataProvider() - fields = iProvider.fields() + layers = [ + self.ui.lstLayers.item(i).text() + for i in range(self.ui.lstLayers.count()) + if self.ui.lstLayers.item(i).checkState() == CHECKED + ] + if not layers: + QMessageBox.warning(None, 'Generalizer', 'Select at least one layer!') + return - if oPath == 'memory': #create memory layer - if iLayer.wkbType() == QGis.WKBLineString: - mLayer = QgsVectorLayer('LineString', iLayerName + '_memory', 'memory')#self.NameFromFunc(func, arguments), 'memory') - else: - mLayer = QgsVectorLayer('MultiLineString', iLayerName + '_memory', 'memory')#self.NameFromFunc(func, arguments), 'memory') - - mProvider = mLayer.dataProvider() - mProvider.addAttributes( [key for key in fields] ) - - for feat in iProvider.getFeatures(): - geom = feat.geometry() - if geom.isMultipart(): - lm = geom.asMultiPolyline() - l = [] - for ls in lm: - p = func(ls, **arguments) - l2 = [] - for n in range(p.n_points): - l2.append(QgsPoint(p.x[n], p.y[n])) - if len(l2) > 1: - l.append(l2) - if len(l) > 1: - fet.setGeometry(QgsGeometry.fromMultiPolyline(l)) - elif len(l) == 1: #jesli z obiektu wieloczesciowego zostaje tylko jedna linia (np. przy usuwaniu malych obiektow) - fet.setGeometry(QgsGeometry.fromPolyline(l[0])) - else: - ls = geom.asPolyline() - p = func(ls, **arguments) - l = [] - for n in range(p.n_points): - l.append(QgsPoint(p.x[n], p.y[n])) - if len(l) > 1: - fet.setGeometry(QgsGeometry.fromPolyline(l)) - else: - continue #jak linia jest pusta to przejdz do nastepnej - fet.setAttributes(feat.attributes()) - mProvider.addFeatures([fet]) - mLayer.updateFields() - mLayer.updateExtents() - return mLayer - - else: #write shapefile on disk - writer = QgsVectorFileWriter(oPath, iProvider.encoding(), fields, QGis.WKBLineString, iLayer.crs()) - if writer.hasError() != QgsVectorFileWriter.NoError: - QMessageBox.critical(None, 'Generalizer', 'Error when creating shapefile: %s' % (writer.hasError())) - - for feat in iProvider.getFeatures(): - geom = feat.geometry() - if geom.isMultipart(): - lm = geom.asMultiPolyline() - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(lm) ) - l = [] - for ls in lm: - p = func(ls, **arguments) - l2 = [] - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(p.n_points) ) - for n in range(p.n_points): - l2.append(QgsPoint(p.x[n], p.y[n])) - if len(l2) > 1: - l.append(l2) - if len(l) > 1: - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(l) ) - fet.setGeometry(QgsGeometry.fromMultiPolyline(l)) - else: - ls = geom.asPolyline() - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(ls) ) - p = func(ls, **arguments) - l = [] - for n in range(p.n_points): - l.append(QgsPoint(p.x[n], p.y[n])) - - if len(l) > 1: - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(l) ) - fet.setGeometry(QgsGeometry.fromPolyline(l)) - fet.setAttributes(feat.attributes()) - writer.addFeature(fet) - - del writer - return self.ui.eOutput.text() - - - def batchGeneralize(self, layers): - outNames = [] - for layer in layers: - vLayer = getMapLayerByName(layer) - for i in range(self.ui.tblBatchAlg.rowCount()): - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(i) ) - alg = self.ui.tblBatchAlg.item(i, 0).text() - func = self.GetFunction(alg) - - par1 = self.ui.tblBatchAlg.cellWidget(i,1).value() - if not func in [self.remove, self.douglas_peucker, self.vertex_reduction, self.boyle, self.jenks, self.reumann_witkam]: - par2 = self.ui.tblBatchAlg.cellWidget(i,2).value() - else: - par2 = -1 - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(par2) ) - - arguments = self.GetArguments(par1, par2) - if self.ui.cbOutDir.isChecked() and i == self.ui.tblBatchAlg.rowCount()-1: - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(i) ) - path = self.ui.eDir.text() - if path.contains("\\"): - out_name = path + '\\' + layer + '_new.shp' + outNames = [] + for layer_name in layers: + layer = getMapLayerByName(layer_name) + if layer is None: + continue + + current = layer + n_algs = self.ui.tblBatchAlg.rowCount() + for i in range(n_algs): + alg_name = self.ui.tblBatchAlg.item(i, 0).text() + w1 = self.ui.tblBatchAlg.cellWidget(i, 1) + w2 = self.ui.tblBatchAlg.cellWidget(i, 2) + par1 = w1.value() if w1 else 1.0 + par2 = w2.value() if w2 else -1 + params = _params_from_table(alg_name, par1, par2) + + is_last = (i == n_algs - 1) + if self.ui.cbOutDir.isChecked() and is_last: + sep = '\\' if '\\' in self.ui.eDir.text() else '/' + out_path = f"{self.ui.eDir.text()}{sep}{layer_name}_new.gpkg" + outNames.append(out_path) + output = out_path else: - out_name = path + '/' + layer + '_new.shp' - outNames.append(out_name) - vLayer = self.doGeneralize(layer, vLayer, out_name, func, arguments) - else: - vLayer = self.doGeneralize(layer, vLayer, 'memory', func, arguments) + output = 'TEMPORARY_OUTPUT' - if not self.ui.cbOutDir.isChecked(): - QgsMapLayerRegistry.instance().addMapLayer(vLayer) - - if self.ui.cbOutDir.isChecked(): - self.LoadLayers(outNames) - - - - def generalize(self): - if self.ui.cbBatch.isChecked(): - if self.ui.cbOutDir.isChecked(): - if self.ui.eDir.text() == '': - QMessageBox.critical(None, 'Generalizer', 'Enter output directory!') - return + current = self._run(alg_name, current, params, output) + if current is None: + break + else: + if not self.ui.cbOutDir.isChecked(): + QgsProject.instance().addMapLayer(current) + if self.ui.cbOutDir.isChecked() and outNames: + self._loadLayers(outNames) - layers = [self.ui.lstLayers.item(i).text() for i in range(self.ui.lstLayers.count()) if self.ui.lstLayers.item(i).checkState() ] - self.batchGeneralize(layers) - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(layers) ) else: - if self.ui.cbInput.currentText() == '': + if not self.ui.cbInput.currentText(): QMessageBox.critical(None, 'Generalizer', 'No line layers!') return - arguments = self.GetArguments() - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(arguments) ) - if arguments == None: - return + alg_name = self.ui.cbAlgorithm.currentText() + layer = getMapLayerByName(self.ui.cbInput.currentText()) + params = _params_from_ui(alg_name, self.ui) - func = self.GetFunction(self.ui.cbAlgorithm.currentText()) if self.ui.cbOutFile.isChecked(): - if self.ui.eOutput.text() == '': + if not self.ui.eOutput.text(): QMessageBox.critical(None, 'Generalizer', 'Enter output file name!') return - filePath = self.doGeneralize(self.ui.cbInput.currentText(), getMapLayerByName(self.ui.cbInput.currentText()), self.ui.eOutput.text(), func, arguments) - self.LoadLayers([filePath]) + result = self._run(alg_name, layer, params, self.ui.eOutput.text()) + if result: + self._loadLayers([self.ui.eOutput.text()]) else: - mLayer = self.doGeneralize(self.ui.cbInput.currentText(), getMapLayerByName(self.ui.cbInput.currentText()), 'memory', func, arguments) - QgsMapLayerRegistry.instance().addMapLayer(mLayer) + result = self._run(alg_name, layer, params) + if result: + QgsProject.instance().addMapLayer(result) - #self.close() - #refresh layer list self.layerList = getLayersNames() self.ui.cbInput.clear() self.ui.lstLayers.clear() self.ui.cbInput.addItems(self.layerList) self.ui.lstLayers.addItems(self.layerList) - [self.ui.lstLayers.item(i).setCheckState(Qt.Unchecked) for i in range(self.ui.lstLayers.count()) ] - - def remove(self, l, **kwargs): - #Remove Small Objects - thresh = kwargs['remove_thresh'] - length = 0. - p = points.Vect_new_line_struct(l) - p1 = points.point() - p2 = points.point() - - for i in range(p.n_points-1): - points.point_assign(p, i, p1) - points.point_assign(p, i+1, p2) - length = length + points.point_dist(p1, p2) - - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str( length ) ) - if length < thresh: - p.x = [] - p.y = [] - p.n_points = 0 - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str( p.x ) ) + [self.ui.lstLayers.item(i).setCheckState(UNCHECKED) for i in range(self.ui.lstLayers.count())] - return p - - def boyle(self,l, **kwargs): - #Boyle's Forward-Looking Algorithm - p = points.Vect_new_line_struct(l) - n = smooth.boyle(p, kwargs['boyle_LA']) - - return p - - def sliding_averaging(self,l, **kwargs): - #McMaster's Sliding Averaging Algorithm - p = points.Vect_new_line_struct(l) - n = smooth.sliding_averaging(p, kwargs['slide_slide'], kwargs['slide_LA']) - - return p - - def distance_weighting(self,l, **kwargs): - #McMaster's Distance Weighting Algorithm - p = points.Vect_new_line_struct(l) - n = smooth.distance_weighting(p, kwargs['dist_slide'], kwargs['dist_LA']) - - return p - - def chaiken(self,l, **kwargs): - #Chaiken's Algorithm - p = points.Vect_new_line_struct(l) - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(p.n_points) ) - n = smooth.chaiken(p, kwargs['chaiken_level'], kwargs['chaiken_weight']) - - return p - - def vertex_reduction(self,l, **kwargs): - #Vertex Reduction - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(l) ) - p = points.Vect_new_line_struct(l) - n = simplify.vertex_reduction(p, kwargs['reduction_thresh']) - - return p - - def douglas_peucker(self,l, **kwargs): - #Douglas-peucker Algorithm - tmp = simplify.douglas_peucker(l, kwargs['dp_thresh']) - p = points.Vect_new_line_struct(tmp) - - return p - - def lang(self,l, **kwargs): - #Vertex Reduction - p = points.Vect_new_line_struct(l) - n = simplify.lang(p, kwargs['lang_thresh'], kwargs['lang_LA']) - - return p - - def hermite(self,l, **kwargs): - #Vertex Reduction - p = points.Vect_new_line_struct(l) - n = smooth.hermite(p, kwargs['hermite_thresh'], kwargs['hermite_tightness']) - - return p - - def jenks(self,l, **kwargs): - #Jenk's Algorithm - #QInputDialog.getText( self.iface.mainWindow(), "m", "e", QLineEdit.Normal, str(kwargs['jenks_angle']) ) - p = points.Vect_new_line_struct(l) - n = simplify.jenks(p, kwargs['jenks_thresh'], kwargs['jenks_angle']) - - return p - - def snakes(self,l, **kwargs): - #Snakes - p = points.Vect_new_line_struct(l) - n = smooth.snakes(p, kwargs['snakes_alpha'], kwargs['snakes_beta']) - - return p - - def reumann_witkam(self,l, **kwargs): - #Snakes - p = points.Vect_new_line_struct(l) - n = simplify.reumann_witkam(p, kwargs['rw_thresh']) - - return p + def _loadLayers(self, fileList): + msg = QMessageBox.question(self, 'Generalizer', 'New layer(s) created.\nAdd to TOC?', + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.Yes) + if msg == QMessageBox.StandardButton.Yes: + for filePath in fileList: + sep = '\\' if '\\' in filePath else '/' + out_name = filePath.split(sep)[-1].removesuffix('.gpkg') + self.iface.addVectorLayer(filePath, out_name, 'ogr') def getLayersNames(): - layermap = QgsMapLayerRegistry.instance().mapLayers() - layerlist = [] - for name, layer in layermap.iteritems(): - if layer.type() == QgsMapLayer.VectorLayer: - if layer.geometryType() == QGis.Line: - layerlist.append( unicode( layer.name() ) ) - - return layerlist - -def getMapLayerByName(myName): - layermap = QgsMapLayerRegistry.instance().mapLayers() - for name, layer in layermap.iteritems(): - if layer.name() == myName: - if layer.isValid(): - return layer - else: - return None + layermap = QgsProject.instance().mapLayers() + result = [] + for layer in layermap.values(): + if layer.type() == QgsMapLayer.LayerType.VectorLayer: + try: + is_line = layer.geometryType() == Qgis.GeometryType.Line + except AttributeError: + from qgis.core import QgsWkbTypes + is_line = layer.geometryType() == QgsWkbTypes.LineGeometry + if is_line: + result.append(str(layer.name())) + return result + + +def getMapLayerByName(name): + for layer in QgsProject.instance().mapLayers().values(): + if layer.name() == name and layer.isValid(): + return layer + return None diff --git a/matrix.py b/matrix.py index d67f8c0..10f24e1 100644 --- a/matrix.py +++ b/matrix.py @@ -1,3 +1,4 @@ +from builtins import object class MATRIX(object): def __init__(self, rows, cols): self.rows = rows @@ -104,4 +105,4 @@ def matrix_mult(a, b): i = i+1 - return res \ No newline at end of file + return res diff --git a/metadata.txt b/metadata.txt index 0bfca62..ede5cbf 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,32 +1,82 @@ -# This file contains metadata for your plugin. Beginning -# with version 1.8 this is the preferred way to supply information about a -# plugin. The current method of embedding metadata in __init__.py will -# be supported until version 2.0 - -# This file should be included when you package your plugin. - -# Mandatory items: - - [general] -name=Generalizer -qgisMinimumVersion=2.0 +name=Generalizer3 +email=sylvain.poulain@giscan.com +qgisMinimumVersion=4.0 +qgisMaximumVersion=4.99 description=Lines generalization and smoothing (partially based on v.generalize GRASS module) -version=0.5 -author=Piotr Pociask -email= +about=Plugin to Generalize, Simplify or Smooth lines in QGIS + Functions : + Remove small objects + Simplify : + Douglas-Peucker Algorithm + Jenk's Algorithm + Lang Algorithm + Reumann-Witkam Algorithm + Vertex Reduction + Smooth : + Boyle's Forward-Looking Algorithm + Chaiken's Algorithm + Hermite Spline Interpolation + McMaster's Distance-Weighting Algorithm + Distance Weighting + McMaster's Sliding Averaging Algorithm + Snakes Algorithm : Slowest smoothing algorithm +version=2.0 +author=Piotr Pociask, Matthew Petroff, Sylvain POULAIN + # end of mandatory metadata # Optional items: -homepage=https://github.com/p0cisk/Generalizer -tracker=https://github.com/p0cisk/Generalizer/issues -repository=https://github.com/p0cisk/Generalizer +homepage=https://github.com/giscan/Generalizer +tracker=https://github.com/giscan/Generalizer/issues +repository=https://github.com/giscan/Generalizer icon=icon.png +tags=line,vector,smooth,simplify # experimental flag -experimental=True +experimental=False # deprecated flag (applies to the whole plugin, not just a single version deprecated=False + +changelog= + 2.0 (03.04/2026) + - Compatibility to Qgis 4 & Qt6 + - Push algorithm to processing + - Ui launch processing algs + 1.0 (18.11.2020) + - Fix unkown projection when new layer is created + - Multipart lines supported + 0.9 (09.11.2020) + - Move plugin to his self toolbar + 0.8 (23.05.2020) + - fix import resources + 0.7 (17.05.2020) + - fix save layer to file + - remove unattended column in temporary layer + 0.6 (10.11.2019) + - updated to QGIS 3.x + - fix projection according to project coordinates + - quick documentation + 0.5 (24.11.2013) + -updated to QGIS 2.0 API (thanks to Matthew Petroff) + -fix unicode error + 0.4 (27.10.2013) + -add setDecimal() method in batch mode. Added by Bakhtiar Hasmanan + 0.3 (15.09.2011) + -new algorithms: + smoothing: Snakes Algorithm + simplifying: Jenk's Algorithm, Reumann-Witkam Algorithm + 0.2 (11.09.2011) + -added batch mode + -choose to save layer(s) on disk or to store in memory + -new algorithms: + smoothing: Hermite Spline Interpolation + 0.1 (28.08.2011) + -first release + -supported algorithms: + generalization: Remove small objects + simplifying: Douglas-Peucker, Lang, Vertex Reduction + smoothing: Boyle's Forward-Looking, Chaiken's, McMaster's Distance-Weighting, McMaster's Sliding Averaging diff --git a/points.py b/points.py index 382b57f..1e1f5d8 100644 --- a/points.py +++ b/points.py @@ -1,4 +1,10 @@ -from PyQt4.QtGui import * +from builtins import object + +try: + from qgis.PyQt.QtGui import * +except ImportError: + from PyQt5.QtGui import * + from math import sqrt, acos, pi, fabs class line_pnts(object): @@ -28,8 +34,8 @@ def add_points(self, pts): self.n_points = len(self.x) def repleace_point(self, index, p): - self.x[index] = p.x - self.y[index] = p.y + self.x[int(index)] = p.x + self.y[int(index)] = p.y def repleace_all_pts(self, pts): self.x = pts.x[:] @@ -73,8 +79,8 @@ def point_substract(a, b, res): res.y = a.y - b.y def point_assign(points, index, res): - res.x = points.x[index] - res.y = points.y[index] + res.x = points.x[int(index)] + res.y = points.y[int(index)] def point_dist(a, b): @@ -131,9 +137,14 @@ def Vect_new_line_struct(l): res = line_pnts() for i in l: - res.x.append(i[0]) - res.y.append(i[1]) - res.n_points = len(l) + # QGIS 4: asPolyline() retourne des QgsPointXY — accepter les deux formats + if hasattr(i, 'x') and callable(i.x): + res.x.append(i.x()) + res.y.append(i.y()) + else: + res.x.append(i[0]) + res.y.append(i[1]) + res.n_points = len(res.x) return res @@ -160,4 +171,4 @@ def compare_points(a, b): tmp = line_pnts(pts) pts.add_point(p2) -print tmp.n_points, pts.n_points""" \ No newline at end of file +print tmp.n_points, pts.n_points""" diff --git a/resources.py b/resources.py index a45ea74..78a7745 100644 --- a/resources.py +++ b/resources.py @@ -2,14 +2,16 @@ # Resource object code # -# Created: N 28. sie 10:51:26 2011 -# by: The Resource Compiler for PyQt (Qt v4.7.1) +# Created by: The Resource Compiler for PyQt5 (Qt v5.15.1) # # WARNING! All changes made in this file will be lost! -from PyQt4 import QtCore +try: + from qgis.PyQt import QtCore +except ImportError: + from PyQt5 import QtCore -qt_resource_data = "\ +qt_resource_data = b"\ \x00\x00\x17\xc5\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -395,32 +397,51 @@ \xae\x42\x60\x82\ " -qt_resource_name = "\ +qt_resource_name = b"\ \x00\x07\ \x07\x3b\xe0\xb3\ \x00\x70\ \x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\ -\x00\x0b\ -\x08\xd8\x99\x42\ -\x00\x67\ -\x00\x65\x00\x6e\x00\x65\x00\x72\x00\x61\x00\x6c\x00\x69\x00\x7a\x00\x65\x00\x72\ +\x00\x0c\ +\x01\x89\x95\x73\ +\x00\x47\ +\x00\x65\x00\x6e\x00\x65\x00\x72\x00\x61\x00\x6c\x00\x69\x00\x7a\x00\x65\x00\x72\x00\x33\ \x00\x08\ \x0a\x61\x5a\xa7\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ " -qt_resource_struct = "\ +qt_resource_struct_v1 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ -\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x32\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ " +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x32\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x73\x87\x86\xe3\x8a\ +" + +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() diff --git a/resources.qrc b/resources.qrc index dd94b82..adf8cb0 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,5 +1,5 @@ - + icon.png diff --git a/simplify.py b/simplify.py index 787b5a6..6320595 100644 --- a/simplify.py +++ b/simplify.py @@ -1,5 +1,10 @@ -from PyQt4.QtGui import * -from points import * +from __future__ import absolute_import +from builtins import range +try: + from qgis.PyQt.QtGui import * +except ImportError: + from PyQt5.QtGui import * +from .points import * import math @@ -270,4 +275,4 @@ def reumann_witkam(points, thresh): print l print 'X,Y' for i in range(len(p.x)): - print p.x[i], ',', p.y[i]""" \ No newline at end of file + print p.x[i], ',', p.y[i]""" diff --git a/smooth.py b/smooth.py index c6640b9..3c9e910 100644 --- a/smooth.py +++ b/smooth.py @@ -1,6 +1,12 @@ -from PyQt4.QtGui import * -from points import * -from matrix import * +from __future__ import print_function +from __future__ import absolute_import +from builtins import range +try: + from qgis.PyQt.QtWidgets import * +except ImportError: + from PyQt5.QtWidgets import * +from .points import * +from .matrix import * GRASS_EPSILON = 1.0e-15 @@ -77,7 +83,7 @@ def sliding_averaging(points, slide, look_ahead): point_assign(points, i, s) point_scalar(s, 1. - slide, s) point_scalar(p, sc*slide, tmp) - points_add(tmp, s, res[i]) + points_add(tmp, s, res[int(i)]) if i+half+1 < n: point_assign(points, i-half, tmp) point_substract(p, tmp, p) @@ -87,7 +93,7 @@ def sliding_averaging(points, slide, look_ahead): i = half while i+half < n: - points.repleace_point(i, res[i]) + points.repleace_point(i, res[int(i)]) i = i + 1 @@ -153,12 +159,12 @@ def distance_weighting(points, slide, look_ahead): else: point_scalar(s, slide/dists, tmp) point_scalar(c, 1.-slide, s) - points_add(s, tmp, res[i]) + points_add(s, tmp, res[int(i)]) i = i + 1 i = half while i+half < n: - points.repleace_point(i, res[i]) + points.repleace_point(i, res[int(i)]) i = i + 1 @@ -166,6 +172,10 @@ def distance_weighting(points, slide, look_ahead): def chaiken(points, level, weight): + from qgis.core import QgsMessageLog, Qgis + QgsMessageLog.logMessage(f"chaiken weight: {weight} ({type(weight)})", + "Generalizer3", + level=Qgis.Info) n = 0 i = 0 j = 0 @@ -181,6 +191,7 @@ def chaiken(points, level, weight): if n < 3: return n + weight = float(weight) d1 = 1./(1+weight) d2 = float(weight)/(1+weight) @@ -386,14 +397,19 @@ def snakes(points, alpha, beta): g.a[i][i]= g.a[i][i] + 1. i = i+1 - print 2 + # fix_print_with_import + print(2) ginv = matrix_inverse(g) - print 3 + # fix_print_with_import + # fix_print_with_import + print(3) xout = matrix_mult(ginv, xcoord) yout = matrix_mult(ginv, ycoord) - print 4 + # fix_print_with_import + # fix_print_with_import + print(4) i = 1 while i < n-1: @@ -412,4 +428,4 @@ def snakes(points, alpha, beta): print l print 'X;Y' for i in range(len(p.x)): - print p.x[i], ';', p.y[i]""" \ No newline at end of file + print p.x[i], ';', p.y[i]""" diff --git a/ui_generalizer.py b/ui_generalizer.py index ee4aa1f..3d25e0a 100644 --- a/ui_generalizer.py +++ b/ui_generalizer.py @@ -1,25 +1,25 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'C:\Documents and Settings\Pocisk\.qgis\python\plugins\generalizer\ui_generalizer.ui' +# Form implementation generated from reading ui file 'ui_generalizer.ui' # -# Created: Tue Sep 13 17:03:23 2011 -# by: PyQt4 UI code generator 4.8.3 +# Created by: PyQt6 UI code generator 6.10.2 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets -from PyQt4 import QtCore, QtGui class Ui_generalizer(object): def setupUi(self, generalizer): generalizer.setObjectName("generalizer") - generalizer.resize(493, 348) - self.gridLayout_20 = QtGui.QGridLayout(generalizer) + generalizer.resize(493, 427) + self.gridLayout_20 = QtWidgets.QGridLayout(generalizer) self.gridLayout_20.setObjectName("gridLayout_20") - self.cbBatch = QtGui.QCheckBox(generalizer) + self.cbBatch = QtWidgets.QCheckBox(parent=generalizer) self.cbBatch.setObjectName("cbBatch") self.gridLayout_20.addWidget(self.cbBatch, 0, 0, 1, 1) - self.bHelp = QtGui.QPushButton(generalizer) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Fixed) + self.bHelp = QtWidgets.QPushButton(parent=generalizer) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.bHelp.sizePolicy().hasHeightForWidth()) @@ -27,38 +27,38 @@ def setupUi(self, generalizer): self.bHelp.setFlat(True) self.bHelp.setObjectName("bHelp") self.gridLayout_20.addWidget(self.bHelp, 0, 2, 1, 1) - self.bOk = QtGui.QPushButton(generalizer) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Fixed) + self.bOk = QtWidgets.QPushButton(parent=generalizer) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.bOk.sizePolicy().hasHeightForWidth()) self.bOk.setSizePolicy(sizePolicy) self.bOk.setObjectName("bOk") self.gridLayout_20.addWidget(self.bOk, 2, 1, 1, 1) - self.bCancel = QtGui.QPushButton(generalizer) + self.bCancel = QtWidgets.QPushButton(parent=generalizer) self.bCancel.setObjectName("bCancel") self.gridLayout_20.addWidget(self.bCancel, 2, 2, 1, 1) - self.stackBatch = QtGui.QStackedWidget(generalizer) + self.stackBatch = QtWidgets.QStackedWidget(parent=generalizer) self.stackBatch.setObjectName("stackBatch") - self.page_6 = QtGui.QWidget() + self.page_6 = QtWidgets.QWidget() self.page_6.setObjectName("page_6") - self.gridLayout_19 = QtGui.QGridLayout(self.page_6) + self.gridLayout_19 = QtWidgets.QGridLayout(self.page_6) self.gridLayout_19.setObjectName("gridLayout_19") - self.label = QtGui.QLabel(self.page_6) + self.label = QtWidgets.QLabel(parent=self.page_6) self.label.setObjectName("label") self.gridLayout_19.addWidget(self.label, 0, 0, 1, 1) - self.cbInput = QtGui.QComboBox(self.page_6) + self.cbInput = QtWidgets.QComboBox(parent=self.page_6) self.cbInput.setObjectName("cbInput") self.gridLayout_19.addWidget(self.cbInput, 1, 0, 1, 2) - self.groupBox = QtGui.QGroupBox(self.page_6) + self.groupBox = QtWidgets.QGroupBox(parent=self.page_6) self.groupBox.setObjectName("groupBox") - self.gridLayout_2 = QtGui.QGridLayout(self.groupBox) + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) self.gridLayout_2.setObjectName("gridLayout_2") - self.label_3 = QtGui.QLabel(self.groupBox) + self.label_3 = QtWidgets.QLabel(parent=self.groupBox) self.label_3.setObjectName("label_3") self.gridLayout_2.addWidget(self.label_3, 0, 0, 1, 1) - self.cbAlgorithm = QtGui.QComboBox(self.groupBox) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) + self.cbAlgorithm = QtWidgets.QComboBox(parent=self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.cbAlgorithm.sizePolicy().hasHeightForWidth()) @@ -80,16 +80,16 @@ def setupUi(self, generalizer): self.cbAlgorithm.addItem("") self.cbAlgorithm.addItem("") self.gridLayout_2.addWidget(self.cbAlgorithm, 0, 1, 1, 1) - self.stackOptions = QtGui.QStackedWidget(self.groupBox) + self.stackOptions = QtWidgets.QStackedWidget(parent=self.groupBox) self.stackOptions.setObjectName("stackOptions") - self.page_11 = QtGui.QWidget() + self.page_11 = QtWidgets.QWidget() self.page_11.setObjectName("page_11") - self.gridLayout_11 = QtGui.QGridLayout(self.page_11) + self.gridLayout_11 = QtWidgets.QGridLayout(self.page_11) self.gridLayout_11.setObjectName("gridLayout_11") - self.label_12 = QtGui.QLabel(self.page_11) + self.label_12 = QtWidgets.QLabel(parent=self.page_11) self.label_12.setObjectName("label_12") self.gridLayout_11.addWidget(self.label_12, 0, 0, 1, 1) - self.sbRemove_thresh = QtGui.QDoubleSpinBox(self.page_11) + self.sbRemove_thresh = QtWidgets.QDoubleSpinBox(parent=self.page_11) self.sbRemove_thresh.setDecimals(4) self.sbRemove_thresh.setMinimum(0.0001) self.sbRemove_thresh.setMaximum(9999999.9999) @@ -97,14 +97,14 @@ def setupUi(self, generalizer): self.sbRemove_thresh.setObjectName("sbRemove_thresh") self.gridLayout_11.addWidget(self.sbRemove_thresh, 0, 1, 1, 1) self.stackOptions.addWidget(self.page_11) - self.page_12 = QtGui.QWidget() + self.page_12 = QtWidgets.QWidget() self.page_12.setObjectName("page_12") - self.gridLayout_12 = QtGui.QGridLayout(self.page_12) + self.gridLayout_12 = QtWidgets.QGridLayout(self.page_12) self.gridLayout_12.setObjectName("gridLayout_12") - self.label_18 = QtGui.QLabel(self.page_12) + self.label_18 = QtWidgets.QLabel(parent=self.page_12) self.label_18.setObjectName("label_18") self.gridLayout_12.addWidget(self.label_18, 0, 0, 1, 1) - self.sbDP_thresh = QtGui.QDoubleSpinBox(self.page_12) + self.sbDP_thresh = QtWidgets.QDoubleSpinBox(parent=self.page_12) self.sbDP_thresh.setDecimals(4) self.sbDP_thresh.setMinimum(0.0001) self.sbDP_thresh.setMaximum(9999999.9999) @@ -112,152 +112,152 @@ def setupUi(self, generalizer): self.sbDP_thresh.setObjectName("sbDP_thresh") self.gridLayout_12.addWidget(self.sbDP_thresh, 0, 1, 1, 1) self.stackOptions.addWidget(self.page_12) - self.page_2 = QtGui.QWidget() + self.page_2 = QtWidgets.QWidget() self.page_2.setObjectName("page_2") - self.gridLayout_4 = QtGui.QGridLayout(self.page_2) + self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2) self.gridLayout_4.setObjectName("gridLayout_4") - self.label_7 = QtGui.QLabel(self.page_2) + self.label_7 = QtWidgets.QLabel(parent=self.page_2) self.label_7.setObjectName("label_7") self.gridLayout_4.addWidget(self.label_7, 0, 0, 1, 1) - self.sbJenks_thresh = QtGui.QDoubleSpinBox(self.page_2) + self.sbJenks_thresh = QtWidgets.QDoubleSpinBox(parent=self.page_2) self.sbJenks_thresh.setDecimals(4) self.sbJenks_thresh.setMaximum(9999999.9999) self.sbJenks_thresh.setProperty("value", 0.0001) self.sbJenks_thresh.setObjectName("sbJenks_thresh") self.gridLayout_4.addWidget(self.sbJenks_thresh, 0, 1, 1, 1) - self.label_8 = QtGui.QLabel(self.page_2) + self.label_8 = QtWidgets.QLabel(parent=self.page_2) self.label_8.setObjectName("label_8") self.gridLayout_4.addWidget(self.label_8, 1, 0, 1, 1) - self.sbJenks_angle = QtGui.QDoubleSpinBox(self.page_2) + self.sbJenks_angle = QtWidgets.QDoubleSpinBox(parent=self.page_2) self.sbJenks_angle.setMaximum(180.0) self.sbJenks_angle.setProperty("value", 3.0) self.sbJenks_angle.setObjectName("sbJenks_angle") self.gridLayout_4.addWidget(self.sbJenks_angle, 1, 1, 1, 1) self.stackOptions.addWidget(self.page_2) - self.page_13 = QtGui.QWidget() + self.page_13 = QtWidgets.QWidget() self.page_13.setObjectName("page_13") - self.gridLayout_3 = QtGui.QGridLayout(self.page_13) + self.gridLayout_3 = QtWidgets.QGridLayout(self.page_13) self.gridLayout_3.setObjectName("gridLayout_3") - self.label_13 = QtGui.QLabel(self.page_13) + self.label_13 = QtWidgets.QLabel(parent=self.page_13) self.label_13.setObjectName("label_13") self.gridLayout_3.addWidget(self.label_13, 0, 0, 1, 1) - self.sbLang_thresh = QtGui.QDoubleSpinBox(self.page_13) + self.sbLang_thresh = QtWidgets.QDoubleSpinBox(parent=self.page_13) self.sbLang_thresh.setDecimals(4) self.sbLang_thresh.setMinimum(0.0001) self.sbLang_thresh.setMaximum(9999999.9999) self.sbLang_thresh.setProperty("value", 0.0001) self.sbLang_thresh.setObjectName("sbLang_thresh") self.gridLayout_3.addWidget(self.sbLang_thresh, 0, 1, 1, 1) - self.label_19 = QtGui.QLabel(self.page_13) + self.label_19 = QtWidgets.QLabel(parent=self.page_13) self.label_19.setObjectName("label_19") self.gridLayout_3.addWidget(self.label_19, 1, 0, 1, 1) - self.sbLang_LA = QtGui.QSpinBox(self.page_13) + self.sbLang_LA = QtWidgets.QSpinBox(parent=self.page_13) self.sbLang_LA.setMinimum(1) self.sbLang_LA.setMaximum(9999) self.sbLang_LA.setProperty("value", 8) self.sbLang_LA.setObjectName("sbLang_LA") self.gridLayout_3.addWidget(self.sbLang_LA, 1, 1, 1, 1) self.stackOptions.addWidget(self.page_13) - self.page_4 = QtGui.QWidget() + self.page_4 = QtWidgets.QWidget() self.page_4.setObjectName("page_4") - self.gridLayout_6 = QtGui.QGridLayout(self.page_4) + self.gridLayout_6 = QtWidgets.QGridLayout(self.page_4) self.gridLayout_6.setObjectName("gridLayout_6") - self.label_11 = QtGui.QLabel(self.page_4) + self.label_11 = QtWidgets.QLabel(parent=self.page_4) self.label_11.setObjectName("label_11") self.gridLayout_6.addWidget(self.label_11, 0, 0, 1, 1) - self.sbRW_thresh = QtGui.QDoubleSpinBox(self.page_4) + self.sbRW_thresh = QtWidgets.QDoubleSpinBox(parent=self.page_4) self.sbRW_thresh.setDecimals(4) self.sbRW_thresh.setMaximum(9999999.9999) self.sbRW_thresh.setProperty("value", 0.0001) self.sbRW_thresh.setObjectName("sbRW_thresh") self.gridLayout_6.addWidget(self.sbRW_thresh, 0, 1, 1, 1) self.stackOptions.addWidget(self.page_4) - self.page_14 = QtGui.QWidget() + self.page_14 = QtWidgets.QWidget() self.page_14.setObjectName("page_14") - self.gridLayout_14 = QtGui.QGridLayout(self.page_14) + self.gridLayout_14 = QtWidgets.QGridLayout(self.page_14) self.gridLayout_14.setObjectName("gridLayout_14") - self.label_20 = QtGui.QLabel(self.page_14) + self.label_20 = QtWidgets.QLabel(parent=self.page_14) self.label_20.setObjectName("label_20") self.gridLayout_14.addWidget(self.label_20, 0, 0, 1, 1) - self.sbReduction_thresh = QtGui.QDoubleSpinBox(self.page_14) + self.sbReduction_thresh = QtWidgets.QDoubleSpinBox(parent=self.page_14) self.sbReduction_thresh.setDecimals(4) self.sbReduction_thresh.setMinimum(0.0001) self.sbReduction_thresh.setMaximum(9999999.9999) self.sbReduction_thresh.setObjectName("sbReduction_thresh") self.gridLayout_14.addWidget(self.sbReduction_thresh, 0, 1, 1, 1) self.stackOptions.addWidget(self.page_14) - self.page_15 = QtGui.QWidget() + self.page_15 = QtWidgets.QWidget() self.page_15.setObjectName("page_15") - self.gridLayout_15 = QtGui.QGridLayout(self.page_15) + self.gridLayout_15 = QtWidgets.QGridLayout(self.page_15) self.gridLayout_15.setObjectName("gridLayout_15") - self.label_21 = QtGui.QLabel(self.page_15) + self.label_21 = QtWidgets.QLabel(parent=self.page_15) self.label_21.setObjectName("label_21") self.gridLayout_15.addWidget(self.label_21, 0, 0, 1, 1) - self.sbBoyle_LA = QtGui.QSpinBox(self.page_15) + self.sbBoyle_LA = QtWidgets.QSpinBox(parent=self.page_15) self.sbBoyle_LA.setMinimum(2) self.sbBoyle_LA.setMaximum(999) self.sbBoyle_LA.setProperty("value", 7) self.sbBoyle_LA.setObjectName("sbBoyle_LA") self.gridLayout_15.addWidget(self.sbBoyle_LA, 0, 1, 1, 1) self.stackOptions.addWidget(self.page_15) - self.page_16 = QtGui.QWidget() + self.page_16 = QtWidgets.QWidget() self.page_16.setObjectName("page_16") - self.gridLayout_16 = QtGui.QGridLayout(self.page_16) + self.gridLayout_16 = QtWidgets.QGridLayout(self.page_16) self.gridLayout_16.setObjectName("gridLayout_16") - self.label_22 = QtGui.QLabel(self.page_16) + self.label_22 = QtWidgets.QLabel(parent=self.page_16) self.label_22.setObjectName("label_22") self.gridLayout_16.addWidget(self.label_22, 0, 0, 1, 1) - self.sbChaiken_level = QtGui.QSpinBox(self.page_16) + self.sbChaiken_level = QtWidgets.QSpinBox(parent=self.page_16) self.sbChaiken_level.setMinimum(1) self.sbChaiken_level.setMaximum(99) self.sbChaiken_level.setObjectName("sbChaiken_level") self.gridLayout_16.addWidget(self.sbChaiken_level, 0, 1, 1, 1) - self.label_23 = QtGui.QLabel(self.page_16) + self.label_23 = QtWidgets.QLabel(parent=self.page_16) self.label_23.setObjectName("label_23") self.gridLayout_16.addWidget(self.label_23, 1, 0, 1, 1) - self.sbChaiken_weight = QtGui.QDoubleSpinBox(self.page_16) + self.sbChaiken_weight = QtWidgets.QDoubleSpinBox(parent=self.page_16) self.sbChaiken_weight.setMinimum(1.0) self.sbChaiken_weight.setProperty("value", 3.0) self.sbChaiken_weight.setObjectName("sbChaiken_weight") self.gridLayout_16.addWidget(self.sbChaiken_weight, 1, 1, 1, 1) self.stackOptions.addWidget(self.page_16) - self.page = QtGui.QWidget() + self.page = QtWidgets.QWidget() self.page.setObjectName("page") - self.gridLayout = QtGui.QGridLayout(self.page) + self.gridLayout = QtWidgets.QGridLayout(self.page) self.gridLayout.setObjectName("gridLayout") - self.label_2 = QtGui.QLabel(self.page) + self.label_2 = QtWidgets.QLabel(parent=self.page) self.label_2.setObjectName("label_2") self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1) - self.label_4 = QtGui.QLabel(self.page) + self.label_4 = QtWidgets.QLabel(parent=self.page) self.label_4.setObjectName("label_4") self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1) - self.sbHermite_tightness = QtGui.QDoubleSpinBox(self.page) + self.sbHermite_tightness = QtWidgets.QDoubleSpinBox(parent=self.page) self.sbHermite_tightness.setProperty("value", 0.5) self.sbHermite_tightness.setObjectName("sbHermite_tightness") self.gridLayout.addWidget(self.sbHermite_tightness, 1, 1, 1, 1) - self.sbHermite_steps = QtGui.QDoubleSpinBox(self.page) + self.sbHermite_steps = QtWidgets.QDoubleSpinBox(parent=self.page) self.sbHermite_steps.setDecimals(4) self.sbHermite_steps.setMaximum(9999999.9999) self.sbHermite_steps.setProperty("value", 0.0001) self.sbHermite_steps.setObjectName("sbHermite_steps") self.gridLayout.addWidget(self.sbHermite_steps, 0, 1, 1, 1) self.stackOptions.addWidget(self.page) - self.page_17 = QtGui.QWidget() + self.page_17 = QtWidgets.QWidget() self.page_17.setObjectName("page_17") - self.gridLayout_17 = QtGui.QGridLayout(self.page_17) + self.gridLayout_17 = QtWidgets.QGridLayout(self.page_17) self.gridLayout_17.setObjectName("gridLayout_17") - self.label_24 = QtGui.QLabel(self.page_17) + self.label_24 = QtWidgets.QLabel(parent=self.page_17) self.label_24.setObjectName("label_24") self.gridLayout_17.addWidget(self.label_24, 0, 0, 1, 1) - self.sbDist_slide = QtGui.QDoubleSpinBox(self.page_17) + self.sbDist_slide = QtWidgets.QDoubleSpinBox(parent=self.page_17) self.sbDist_slide.setSingleStep(0.1) self.sbDist_slide.setProperty("value", 0.5) self.sbDist_slide.setObjectName("sbDist_slide") self.gridLayout_17.addWidget(self.sbDist_slide, 0, 1, 1, 1) - self.label_25 = QtGui.QLabel(self.page_17) + self.label_25 = QtWidgets.QLabel(parent=self.page_17) self.label_25.setObjectName("label_25") self.gridLayout_17.addWidget(self.label_25, 1, 0, 1, 1) - self.sbDist_LA = QtGui.QSpinBox(self.page_17) + self.sbDist_LA = QtWidgets.QSpinBox(parent=self.page_17) self.sbDist_LA.setMinimum(3) self.sbDist_LA.setMaximum(999) self.sbDist_LA.setSingleStep(2) @@ -265,22 +265,22 @@ def setupUi(self, generalizer): self.sbDist_LA.setObjectName("sbDist_LA") self.gridLayout_17.addWidget(self.sbDist_LA, 1, 1, 1, 1) self.stackOptions.addWidget(self.page_17) - self.page_18 = QtGui.QWidget() + self.page_18 = QtWidgets.QWidget() self.page_18.setObjectName("page_18") - self.gridLayout_18 = QtGui.QGridLayout(self.page_18) + self.gridLayout_18 = QtWidgets.QGridLayout(self.page_18) self.gridLayout_18.setObjectName("gridLayout_18") - self.label_26 = QtGui.QLabel(self.page_18) + self.label_26 = QtWidgets.QLabel(parent=self.page_18) self.label_26.setObjectName("label_26") self.gridLayout_18.addWidget(self.label_26, 0, 0, 1, 1) - self.sbSlide_slide = QtGui.QDoubleSpinBox(self.page_18) + self.sbSlide_slide = QtWidgets.QDoubleSpinBox(parent=self.page_18) self.sbSlide_slide.setSingleStep(0.1) self.sbSlide_slide.setProperty("value", 0.5) self.sbSlide_slide.setObjectName("sbSlide_slide") self.gridLayout_18.addWidget(self.sbSlide_slide, 0, 1, 1, 1) - self.label_27 = QtGui.QLabel(self.page_18) + self.label_27 = QtWidgets.QLabel(parent=self.page_18) self.label_27.setObjectName("label_27") self.gridLayout_18.addWidget(self.label_27, 1, 0, 1, 1) - self.sbSlide_LA = QtGui.QSpinBox(self.page_18) + self.sbSlide_LA = QtWidgets.QSpinBox(parent=self.page_18) self.sbSlide_LA.setMinimum(3) self.sbSlide_LA.setMaximum(999) self.sbSlide_LA.setSingleStep(2) @@ -288,22 +288,22 @@ def setupUi(self, generalizer): self.sbSlide_LA.setObjectName("sbSlide_LA") self.gridLayout_18.addWidget(self.sbSlide_LA, 1, 1, 1, 1) self.stackOptions.addWidget(self.page_18) - self.page_3 = QtGui.QWidget() + self.page_3 = QtWidgets.QWidget() self.page_3.setObjectName("page_3") - self.gridLayout_5 = QtGui.QGridLayout(self.page_3) + self.gridLayout_5 = QtWidgets.QGridLayout(self.page_3) self.gridLayout_5.setObjectName("gridLayout_5") - self.label_9 = QtGui.QLabel(self.page_3) + self.label_9 = QtWidgets.QLabel(parent=self.page_3) self.label_9.setObjectName("label_9") self.gridLayout_5.addWidget(self.label_9, 0, 0, 1, 1) - self.sbSnakes_alpha = QtGui.QDoubleSpinBox(self.page_3) + self.sbSnakes_alpha = QtWidgets.QDoubleSpinBox(parent=self.page_3) self.sbSnakes_alpha.setMaximum(9999.99) self.sbSnakes_alpha.setProperty("value", 1.0) self.sbSnakes_alpha.setObjectName("sbSnakes_alpha") self.gridLayout_5.addWidget(self.sbSnakes_alpha, 0, 1, 1, 1) - self.label_10 = QtGui.QLabel(self.page_3) + self.label_10 = QtWidgets.QLabel(parent=self.page_3) self.label_10.setObjectName("label_10") self.gridLayout_5.addWidget(self.label_10, 1, 0, 1, 1) - self.sbSnakes_beta = QtGui.QDoubleSpinBox(self.page_3) + self.sbSnakes_beta = QtWidgets.QDoubleSpinBox(parent=self.page_3) self.sbSnakes_beta.setMaximum(9999.99) self.sbSnakes_beta.setProperty("value", 0.5) self.sbSnakes_beta.setObjectName("sbSnakes_beta") @@ -311,32 +311,32 @@ def setupUi(self, generalizer): self.stackOptions.addWidget(self.page_3) self.gridLayout_2.addWidget(self.stackOptions, 1, 0, 1, 2) self.gridLayout_19.addWidget(self.groupBox, 2, 0, 1, 2) - self.eOutput = QtGui.QLineEdit(self.page_6) + self.eOutput = QtWidgets.QLineEdit(parent=self.page_6) self.eOutput.setEnabled(False) self.eOutput.setObjectName("eOutput") self.gridLayout_19.addWidget(self.eOutput, 4, 0, 1, 1) - self.bBrowse = QtGui.QPushButton(self.page_6) + self.bBrowse = QtWidgets.QPushButton(parent=self.page_6) self.bBrowse.setEnabled(False) self.bBrowse.setObjectName("bBrowse") self.gridLayout_19.addWidget(self.bBrowse, 4, 1, 1, 1) - self.cbOutFile = QtGui.QCheckBox(self.page_6) + self.cbOutFile = QtWidgets.QCheckBox(parent=self.page_6) self.cbOutFile.setObjectName("cbOutFile") self.gridLayout_19.addWidget(self.cbOutFile, 3, 0, 1, 1) self.stackBatch.addWidget(self.page_6) - self.page_10 = QtGui.QWidget() + self.page_10 = QtWidgets.QWidget() self.page_10.setObjectName("page_10") - self.gridLayout_21 = QtGui.QGridLayout(self.page_10) + self.gridLayout_21 = QtWidgets.QGridLayout(self.page_10) self.gridLayout_21.setObjectName("gridLayout_21") - self.lstLayers = QtGui.QListWidget(self.page_10) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.lstLayers = QtWidgets.QListWidget(parent=self.page_10) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lstLayers.sizePolicy().hasHeightForWidth()) self.lstLayers.setSizePolicy(sizePolicy) self.lstLayers.setObjectName("lstLayers") self.gridLayout_21.addWidget(self.lstLayers, 1, 0, 1, 1) - self.tblBatchAlg = QtGui.QTableWidget(self.page_10) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.tblBatchAlg = QtWidgets.QTableWidget(parent=self.page_10) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tblBatchAlg.sizePolicy().hasHeightForWidth()) @@ -344,37 +344,37 @@ def setupUi(self, generalizer): self.tblBatchAlg.setObjectName("tblBatchAlg") self.tblBatchAlg.setColumnCount(3) self.tblBatchAlg.setRowCount(0) - item = QtGui.QTableWidgetItem() + item = QtWidgets.QTableWidgetItem() self.tblBatchAlg.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() + item = QtWidgets.QTableWidgetItem() self.tblBatchAlg.setHorizontalHeaderItem(1, item) - item = QtGui.QTableWidgetItem() + item = QtWidgets.QTableWidgetItem() self.tblBatchAlg.setHorizontalHeaderItem(2, item) self.gridLayout_21.addWidget(self.tblBatchAlg, 3, 0, 3, 1) - self.bAddAlg = QtGui.QPushButton(self.page_10) + self.bAddAlg = QtWidgets.QPushButton(parent=self.page_10) self.bAddAlg.setObjectName("bAddAlg") self.gridLayout_21.addWidget(self.bAddAlg, 3, 1, 1, 1) - self.bDelAlg = QtGui.QPushButton(self.page_10) + self.bDelAlg = QtWidgets.QPushButton(parent=self.page_10) self.bDelAlg.setObjectName("bDelAlg") self.gridLayout_21.addWidget(self.bDelAlg, 4, 1, 1, 1) - self.bEditAlg = QtGui.QPushButton(self.page_10) + self.bEditAlg = QtWidgets.QPushButton(parent=self.page_10) self.bEditAlg.setObjectName("bEditAlg") self.gridLayout_21.addWidget(self.bEditAlg, 5, 1, 1, 1) - self.eDir = QtGui.QLineEdit(self.page_10) + self.eDir = QtWidgets.QLineEdit(parent=self.page_10) self.eDir.setEnabled(False) self.eDir.setObjectName("eDir") self.gridLayout_21.addWidget(self.eDir, 7, 0, 1, 1) - self.bBrowseDir = QtGui.QPushButton(self.page_10) + self.bBrowseDir = QtWidgets.QPushButton(parent=self.page_10) self.bBrowseDir.setEnabled(False) self.bBrowseDir.setObjectName("bBrowseDir") self.gridLayout_21.addWidget(self.bBrowseDir, 7, 1, 1, 1) - self.cbOutDir = QtGui.QCheckBox(self.page_10) + self.cbOutDir = QtWidgets.QCheckBox(parent=self.page_10) self.cbOutDir.setObjectName("cbOutDir") self.gridLayout_21.addWidget(self.cbOutDir, 6, 0, 1, 1) - self.label_5 = QtGui.QLabel(self.page_10) + self.label_5 = QtWidgets.QLabel(parent=self.page_10) self.label_5.setObjectName("label_5") self.gridLayout_21.addWidget(self.label_5, 2, 0, 1, 1) - self.label_6 = QtGui.QLabel(self.page_10) + self.label_6 = QtWidgets.QLabel(parent=self.page_10) self.label_6.setObjectName("label_6") self.gridLayout_21.addWidget(self.label_6, 0, 0, 1, 1) self.stackBatch.addWidget(self.page_10) @@ -386,7 +386,7 @@ def setupUi(self, generalizer): self.stackBatch.setCurrentIndex(0) self.cbAlgorithm.setCurrentIndex(1) self.stackOptions.setCurrentIndex(0) - self.bCancel.clicked.connect( generalizer.close ) + self.bCancel.clicked.connect(generalizer.close) # type: ignore QtCore.QMetaObject.connectSlotsByName(generalizer) generalizer.setTabOrder(self.cbBatch, self.cbInput) generalizer.setTabOrder(self.cbInput, self.cbAlgorithm) @@ -420,58 +420,61 @@ def setupUi(self, generalizer): generalizer.setTabOrder(self.bBrowseDir, self.cbOutDir) def retranslateUi(self, generalizer): - generalizer.setWindowTitle(QtGui.QApplication.translate("generalizer", "Generalizer", None, QtGui.QApplication.UnicodeUTF8)) - self.cbBatch.setText(QtGui.QApplication.translate("generalizer", "Batch mode", None, QtGui.QApplication.UnicodeUTF8)) - self.bHelp.setText(QtGui.QApplication.translate("generalizer", "?", None, QtGui.QApplication.UnicodeUTF8)) - self.bOk.setText(QtGui.QApplication.translate("generalizer", "OK", None, QtGui.QApplication.UnicodeUTF8)) - self.bCancel.setText(QtGui.QApplication.translate("generalizer", "Close", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("generalizer", "Input line layer:", None, QtGui.QApplication.UnicodeUTF8)) - self.groupBox.setTitle(QtGui.QApplication.translate("generalizer", "Options", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("generalizer", "Alghoritm:", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(0, QtGui.QApplication.translate("generalizer", "-----Generalizing-----", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(1, QtGui.QApplication.translate("generalizer", "Remove small objects", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(2, QtGui.QApplication.translate("generalizer", "-----Simplifying-----", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(3, QtGui.QApplication.translate("generalizer", "Douglas-Peucker Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(4, QtGui.QApplication.translate("generalizer", "Jenk\'s Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(5, QtGui.QApplication.translate("generalizer", "Lang Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(6, QtGui.QApplication.translate("generalizer", "Reumann-Witkam Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(7, QtGui.QApplication.translate("generalizer", "Vertex Reduction", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(8, QtGui.QApplication.translate("generalizer", "-----Smoothing-----", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(9, QtGui.QApplication.translate("generalizer", "Boyle\'s Forward-Looking Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(10, QtGui.QApplication.translate("generalizer", "Chaiken\'s Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(11, QtGui.QApplication.translate("generalizer", "Hermite Spline Interpolation", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(12, QtGui.QApplication.translate("generalizer", "McMaster\'s Distance-Weighting Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(13, QtGui.QApplication.translate("generalizer", "McMaster\'s Sliding Averaging Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.cbAlgorithm.setItemText(14, QtGui.QApplication.translate("generalizer", "Snakes Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.label_12.setText(QtGui.QApplication.translate("generalizer", "Threshold:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_18.setText(QtGui.QApplication.translate("generalizer", "Threshold:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_7.setText(QtGui.QApplication.translate("generalizer", "Threshold:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_8.setText(QtGui.QApplication.translate("generalizer", "Angle threshold:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_13.setText(QtGui.QApplication.translate("generalizer", "Threshold:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_19.setText(QtGui.QApplication.translate("generalizer", "Looka ahead:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_11.setText(QtGui.QApplication.translate("generalizer", "Threshold:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_20.setText(QtGui.QApplication.translate("generalizer", "Threshold:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_21.setText(QtGui.QApplication.translate("generalizer", "Look ahead:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_22.setText(QtGui.QApplication.translate("generalizer", "Level:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_23.setText(QtGui.QApplication.translate("generalizer", "Weight:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("generalizer", "Threshold:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_4.setText(QtGui.QApplication.translate("generalizer", "Thightness:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_24.setText(QtGui.QApplication.translate("generalizer", "Slide:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_25.setText(QtGui.QApplication.translate("generalizer", "Look ahead:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_26.setText(QtGui.QApplication.translate("generalizer", "Slide:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_27.setText(QtGui.QApplication.translate("generalizer", "Look ahead:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_9.setText(QtGui.QApplication.translate("generalizer", "Alpha:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_10.setText(QtGui.QApplication.translate("generalizer", "Beta:", None, QtGui.QApplication.UnicodeUTF8)) - self.bBrowse.setText(QtGui.QApplication.translate("generalizer", "Browse", None, QtGui.QApplication.UnicodeUTF8)) - self.cbOutFile.setText(QtGui.QApplication.translate("generalizer", "Save output layer", None, QtGui.QApplication.UnicodeUTF8)) - self.tblBatchAlg.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("generalizer", "Algorithm", None, QtGui.QApplication.UnicodeUTF8)) - self.tblBatchAlg.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("generalizer", "Parameter 1", None, QtGui.QApplication.UnicodeUTF8)) - self.tblBatchAlg.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("generalizer", "Parameter 2", None, QtGui.QApplication.UnicodeUTF8)) - self.bAddAlg.setText(QtGui.QApplication.translate("generalizer", "Add", None, QtGui.QApplication.UnicodeUTF8)) - self.bDelAlg.setText(QtGui.QApplication.translate("generalizer", "Delete", None, QtGui.QApplication.UnicodeUTF8)) - self.bEditAlg.setText(QtGui.QApplication.translate("generalizer", "Edit", None, QtGui.QApplication.UnicodeUTF8)) - self.bBrowseDir.setText(QtGui.QApplication.translate("generalizer", "Browse", None, QtGui.QApplication.UnicodeUTF8)) - self.cbOutDir.setText(QtGui.QApplication.translate("generalizer", "Save output layer(s) to folder", None, QtGui.QApplication.UnicodeUTF8)) - self.label_5.setText(QtGui.QApplication.translate("generalizer", "Algorithm(s)", None, QtGui.QApplication.UnicodeUTF8)) - self.label_6.setText(QtGui.QApplication.translate("generalizer", "Choose layer(s)", None, QtGui.QApplication.UnicodeUTF8)) - + _translate = QtCore.QCoreApplication.translate + generalizer.setWindowTitle(_translate("generalizer", "Generalizer")) + self.cbBatch.setText(_translate("generalizer", "Batch mode")) + self.bHelp.setText(_translate("generalizer", "?")) + self.bOk.setText(_translate("generalizer", "OK")) + self.bCancel.setText(_translate("generalizer", "Close")) + self.label.setText(_translate("generalizer", "Input line layer:")) + self.groupBox.setTitle(_translate("generalizer", "Options")) + self.label_3.setText(_translate("generalizer", "Algorithm:")) + self.cbAlgorithm.setItemText(0, _translate("generalizer", "-----Generalizing-----")) + self.cbAlgorithm.setItemText(1, _translate("generalizer", "Remove small objects")) + self.cbAlgorithm.setItemText(2, _translate("generalizer", "-----Simplifying-----")) + self.cbAlgorithm.setItemText(3, _translate("generalizer", "Douglas-Peucker Algorithm")) + self.cbAlgorithm.setItemText(4, _translate("generalizer", "Jenks\' Algorithm")) + self.cbAlgorithm.setItemText(5, _translate("generalizer", "Lang Algorithm")) + self.cbAlgorithm.setItemText(6, _translate("generalizer", "Reumann-Witkam Algorithm")) + self.cbAlgorithm.setItemText(7, _translate("generalizer", "Vertex Reduction")) + self.cbAlgorithm.setItemText(8, _translate("generalizer", "-----Smoothing-----")) + self.cbAlgorithm.setItemText(9, _translate("generalizer", "Boyle\'s Forward-Looking Algorithm")) + self.cbAlgorithm.setItemText(10, _translate("generalizer", "Chaiken\'s Algorithm")) + self.cbAlgorithm.setItemText(11, _translate("generalizer", "Hermite Spline Interpolation")) + self.cbAlgorithm.setItemText(12, _translate("generalizer", "McMaster\'s Distance-Weighting Algorithm")) + self.cbAlgorithm.setItemText(13, _translate("generalizer", "McMaster\'s Sliding Averaging Algorithm")) + self.cbAlgorithm.setItemText(14, _translate("generalizer", "Snakes Algorithm")) + self.label_12.setText(_translate("generalizer", "Threshold:")) + self.label_18.setText(_translate("generalizer", "Threshold:")) + self.label_7.setText(_translate("generalizer", "Threshold:")) + self.label_8.setText(_translate("generalizer", "Angle threshold:")) + self.label_13.setText(_translate("generalizer", "Threshold:")) + self.label_19.setText(_translate("generalizer", "Look ahead:")) + self.label_11.setText(_translate("generalizer", "Threshold:")) + self.label_20.setText(_translate("generalizer", "Threshold:")) + self.label_21.setText(_translate("generalizer", "Look ahead:")) + self.label_22.setText(_translate("generalizer", "Level:")) + self.label_23.setText(_translate("generalizer", "Weight:")) + self.label_2.setText(_translate("generalizer", "Threshold:")) + self.label_4.setText(_translate("generalizer", "Tightness:")) + self.label_24.setText(_translate("generalizer", "Slide:")) + self.label_25.setText(_translate("generalizer", "Look ahead:")) + self.label_26.setText(_translate("generalizer", "Slide:")) + self.label_27.setText(_translate("generalizer", "Look ahead:")) + self.label_9.setText(_translate("generalizer", "Alpha:")) + self.label_10.setText(_translate("generalizer", "Beta:")) + self.bBrowse.setText(_translate("generalizer", "Browse")) + self.cbOutFile.setText(_translate("generalizer", "Save output layer")) + item = self.tblBatchAlg.horizontalHeaderItem(0) + item.setText(_translate("generalizer", "Algorithm")) + item = self.tblBatchAlg.horizontalHeaderItem(1) + item.setText(_translate("generalizer", "Parameter 1")) + item = self.tblBatchAlg.horizontalHeaderItem(2) + item.setText(_translate("generalizer", "Parameter 2")) + self.bAddAlg.setText(_translate("generalizer", "Add")) + self.bDelAlg.setText(_translate("generalizer", "Delete")) + self.bEditAlg.setText(_translate("generalizer", "Edit")) + self.bBrowseDir.setText(_translate("generalizer", "Browse")) + self.cbOutDir.setText(_translate("generalizer", "Save output layer(s) to folder")) + self.label_5.setText(_translate("generalizer", "Algorithm(s)")) + self.label_6.setText(_translate("generalizer", "Choose layer(s)")) diff --git a/ui_generalizer.ui b/ui_generalizer.ui index 06189d1..d79eb2a 100644 --- a/ui_generalizer.ui +++ b/ui_generalizer.ui @@ -7,7 +7,7 @@ 0 0 493 - 348 + 427 @@ -86,7 +86,7 @@ - Alghoritm: + Algorithm: @@ -123,7 +123,7 @@ - Jenk's Algorithm + Jenks' Algorithm @@ -181,7 +181,7 @@ - 0 + 7 @@ -309,7 +309,7 @@ - Looka ahead: + Look ahead: @@ -450,7 +450,7 @@ - Thightness: + Tightness: