diff --git a/.gitignore b/.gitignore index c19000b..9fd14ba 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,5 @@ DigitizingTools.zip help/build/ ui*.py *.pyc -tools/dt_icons_rc.py +dt_icons_rc.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 184c538..8aa4032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,87 @@ # Change Log -All notable changes to this project since Version 0.8.0 will be documented in this file. +All notable changes to this project since Version 0.8.0 will be documented in this file. Bug-fix releases are not documented seperately; their changes are listed in the Unreleased section until a new version is released. -## [Unreleased](https://github.com/bstroebl/DigitizingTools/compare/v0.10.2...develop) +## [Unreleased](https://github.com/bstroebl/DigitizingTools/compare/v1.5.0...develop) +### Added +- Icelandic translation (thanks to Sveinn í Felli) + +### Fixed +- Fix highlighting (Fix #65) +- Fix the use of QgsRubberBand (Fix #60) (thanks to Andrea Giudiceandrea) + +## [1.5.0](https://github.com/bstroebl/DigitizingTools/compare/v1.4.0...v1.5.0) - 2019-09-11 +### Fixed +- Support Geopackage's fid default values (thanks to Andy Wicht) + +### Added +- Improve performance while splitting (thanks to Andy Wicht) +- Rekindle cut/clip with polygon with slightly changed function, fixes #47 +- Highlight geometry used for cutting/clipping, will be implemented in other tools, soon + +## [1.4.0](https://github.com/bstroebl/DigitizingTools/compare/v1.3.0...v1.4.0) - 2019-07-19 +### Removed +- Tools that have become obsolete by processing's in-place editing: cut with polygon (use Difference), clip with polygon (use Clip), split selected feature with selected line from another layer (use Split with lines) + +## [1.3.0](https://github.com/bstroebl/DigitizingTools/compare/v1.2.0...v1.3.0) - 2019-03-07 +### Fixed +- Handle different CRS of layer and project in fill-ring/fill-gap tools +- Split feature tool: Show dialog only for multi features with more than one part, ensure that tool stays enabled on save layer edits + +### Added +- add topological points when splitting and topolgical editing is switched on +- Optionally copy cutting polygon in edit layer + +## [1.2.0](https://github.com/bstroebl/DigitizingTools/compare/v1.1.0...v1.2.0) - 2018-9-10 +### Fixed +- Fix occasional runtime error when trying to identify localization. +- Use new QgsVectorLayerUtils class for creating new features +- Merge new features with sequence value, too +- Adapt messages to new api +- Handle different CRS of layer and project + +### Added +- Enable merge tool for all data providers. If no primary key field is present the internal id is presented as _Feature ID _ to the user. +- Enable SplitFeatureTool if CRS of layer and project are different + +## [1.1.0](https://github.com/bstroebl/DigitizingTools/compare/v1.0.0...v1.1.0) - 2018-5-17 +### Added +- Cut using the same layer as is the current edit layer, selection defines the cutting polygons, cutting will be performed on all polygons + +### Fixed +- Activate tools for shape files, too. Reason: wkbType now always returns the multi type whereas in QGIS2 it used to return the single type +- Adapt more code to Qt5 api +- Remove editing command when splitting has been cancelled +- Show snap match in split feature tool before a rubber band exists, so first point of rubber band can snap, too + +## [1.0.0] (https://github.com/bstroebl/DigitizingTools/compare/v0.11.3...v1.0.0) - 2018-05-15 +### General +- Adapted to QGIS 3 + +### Added +- Improve split-feature tool: use tracing, fixes #20, show dotted rubberband for sketch, remove last point in rubberBand with backspace + +### Changed +- Highlight feature being preserved in merge features tool, fixes #24 + +### Fixed +- Activate split multi part and extract part tools for any layer (not just for multi layers). In case the user tries to save a multi feature the data provider will deal with this. +- always catch backspace key while tool is active + +## [0.11.3] (https://github.com/bstroebl/DigitizingTools/compare/v0.11.0...v0.11.3) - 2017-09-14 +### Changed since 0.11.0 +- Use Highlight color from settings in split feature +- Rename function "Exchange geometries" into "Exchange Attributes" to make it more in line with the naming of QGIS' standard tools + +### Fixed since 0.11.0 +- Remove run-time error if user chooses "No to All" in split feature +- Prevent endless loop if no splitting occures in multi-geometry feature + +## [0.11.0](https://github.com/bstroebl/DigitizingTools/compare/v0.10.0...v0.11.0) - 2017-06-15 +### Added +- Split features tool that replaces core's split features and makes core's split part unnecessary, see [#19](https://github.com/bstroebl/DigitizingTools/issues/19) + +### Removed +- Prolong line has been removed because functionality is contained in core since QGIS 2.16 ## [0.10.2](https://github.com/bstroebl/DigitizingTools/compare/v0.10.1...v0.10.2) - 2016-06-30 ### Fixed diff --git a/license.txt b/LICENSE similarity index 100% rename from license.txt rename to LICENSE diff --git a/Makefile b/Makefile index 0244168..2da04af 100644 --- a/Makefile +++ b/Makefile @@ -22,9 +22,20 @@ PLUGIN_UPLOAD = $(CURDIR)/plugin_upload.py # Makefile for a PyQGIS plugin +#Add iso code for any locales you want to support here (space separated) +# default is no locales +# LOCALES = af +LOCALES = digitizingtools_de digitizingtools_fr + +# If locales are enabled, set the name of the lrelease binary on your system. If +# you have trouble compiling the translations, you may have to specify the full path to +# lrelease +LRELEASE = lrelease +#LRELEASE = lrelease-qt4 + # translation SOURCES = digitizingtools.py __init__.py dtDialog.py tools/dtutils.py -TRANSLATIONS = i18n/digitizingtools_de.ts i18n/digitizingtools_pt.ts +#TRANSLATIONS = i18n/digitizingtools_de.ts i18n/digitizingtools_pt.ts # global @@ -33,67 +44,97 @@ PLUGINNAME = DigitizingTools PY_FILES = digitizingtools.py __init__.py dtDialog.py TOOLS = tools/dtutils.py tools/dtsplitmultipart.py tools/dtcutter.py tools/dtclipper.py \ - tools/dtfillring.py tools/dtfillgap.py tools/dtsplitter.py tools/dtprolongline.py \ - tools/dtprolonglinetool.py tools/dtmovenodebyarea.py tools/dtmovesidebydistance.py \ - tools/ui_dtmovenodebyarea.py tools/ui_dtmovesidebydistance.py \ + tools/dtfillring.py tools/dtfillgap.py tools/dtsplitfeature.py \ + tools/dtmovenodebyarea.py tools/dtmovesidebydistance.py \ tools/dtmovenodebyarea_dialog.py tools/dtmovesidebydistance_dialog.py \ - tools/dtmovesidebyarea.py tools/ui_dtmovesidebyarea.py tools/dtmovesidebyarea_dialog.py \ + tools/dtmovesidebyarea.py tools/dtmovesidebyarea_dialog.py \ tools/dtflipline.py tools/dttools.py tools/dtmedianline.py tools/dtmedianlinetool.py \ - tools/dtextractpart.py tools/dtmerge.py tools/dtexchangegeometry.py + tools/dtextractpart.py tools/dtmerge.py tools/dtexchangegeometry.py tools/dtToolsDialog.py \ + tools/ui_dtmovenodebyarea.ui tools/ui_dtmovesidebydistance.ui tools/ui_dtmovesidebyarea.ui \ + tools/ui_dtchooseremaining.ui -EXTRAS = metadata.txt license.txt digitizingtools.png +EXTRAS = metadata.txt LICENSE digitizingtools.png -UI_FILES = ui_about.py tools/ui_dtmovenodebyarea.py tools/ui_dtmovesidebydistance.py tools/ui_dtmovesidebyarea.py +UI_FILES = ui_about.ui ui_dtcutter.ui -RESOURCE_FILES = tools/dt_icons_rc.py +RESOURCE_SRC=$(shell grep '^ *@@g;s/.*>//g' | tr '\n' ' ') +COMPILED_RESOURCE_FILES = dt_icons_rc.py HELP = help/build/html +QGISDIR=.local/share/QGIS/QGIS3/profiles/default + default: compile -compile: $(UI_FILES) $(RESOURCE_FILES) +compile: $(COMPILED_RESOURCE_FILES) + +%.py : %.qrc $(RESOURCES_SRC) + pyrcc5 -o $*.py $< + +%.qm : %.ts + $(LRELEASE) $< + +#compile: $(UI_FILES) $(RESOURCE_FILES) #compile: $(UI_FILES) -compile: +#compile3: -%_rc.py : %.qrc - pyrcc5 -o $*_rc.py -py2 $< +#%_rc.py : %.qrc +# pyrcc5 -o $*_rc.py -py2 $< -%.py : %.ui - pyuic5 -o $@ $< +#%.py : %.ui +# pyuic5 -o $@ $< %.qm : %.ts lrelease $< # The deploy target only works on unix like operating system where -# the Python plugin directory is located at: -# $HOME/.qgis/python/plugins deploy: compile transcompile - mkdir -p $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) - mkdir -p $(HOME)/.qgis3/python/plugins/$(PLUGINNAME)/tools - cp -vf $(PY_FILES) $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) - cp -vf $(TOOLS) $(HOME)/.qgis3/python/plugins/$(PLUGINNAME)/tools - cp -vf $(UI_FILES) $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) - cp -vf $(RESOURCE_FILES) $(HOME)/.qgis3/python/plugins/$(PLUGINNAME)/tools - cp -vf $(EXTRAS) $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) - cp -vfr i18n $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) -# cp -vfr icons $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) - cp -vfr $(HELP) $(HOME)/.qgis3/python/plugins/$(PLUGINNAME)/help + @echo + @echo "------------------------------------------" + @echo "Deploying plugin to your .qgis3 directory." + @echo "------------------------------------------" + # The deploy target only works on unix like operating system where + # the Python plugin directory is located at: + # $HOME/$(QGISDIR)/python/plugins + mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + mkdir -p $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/tools + cp -vf $(PY_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(UI_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vf $(TOOLS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/tools + cp -vf $(COMPILED_RESOURCE_FILES) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/tools + cp -vf $(EXTRAS) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + cp -vfr i18n $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) + #cp -vfr $(HELP) $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME)/help # The dclean target removes compiled python files from plugin directory # also delets any .svn entry dclean: - find $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete - find $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) -iname ".svn" -prune -exec rm -Rf {} \; + @echo + @echo "-----------------------------------" + @echo "Removing any compiled python files." + @echo "-----------------------------------" + find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname "*.pyc" -delete + find $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) -iname ".git" -prune -exec rm -Rf {} \; # The derase deletes deployed plugin derase: - rm -Rf $(HOME)/.qgis3/python/plugins/$(PLUGINNAME) + @echo + @echo "-------------------------" + @echo "Removing deployed plugin." + @echo "-------------------------" + rm -Rf $(HOME)/$(QGISDIR)/python/plugins/$(PLUGINNAME) # The zip target deploys the plugin and creates a zip file with the deployed # content. You can then upload the zip file on http://plugins.qgis.org zip: deploy dclean + @echo + @echo "---------------------------" + @echo "Creating plugin zip bundle." + @echo "---------------------------" + # The zip target deploys the plugin and creates a zip file with the deployed + # content. You can then upload the zip file on http://plugins.qgis.org rm -f $(PLUGINNAME).zip - cd $(HOME)/.qgis3/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME) + cd $(HOME)/$(QGISDIR)/python/plugins; zip -9r $(CURDIR)/$(PLUGINNAME).zip $(PLUGINNAME) # Create a zip package of the plugin named $(PLUGINNAME).zip. # This requires use of git (your plugin development directory must be a @@ -101,30 +142,59 @@ zip: deploy dclean # To use, pass a valid commit or tag as follows: # make package VERSION=Version_0.3.2 package: compile - rm -f $(PLUGINNAME).zip - git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION) - echo "Created package: $(PLUGINNAME).zip" + # Create a zip package of the plugin named $(PLUGINNAME).zip. + # This requires use of git (your plugin development directory must be a + # git repository). + # To use, pass a valid commit or tag as follows: + # make package VERSION=Version_0.3.2 + @echo + @echo "------------------------------------" + @echo "Exporting plugin to zip package. " + @echo "------------------------------------" + rm -f $(PLUGINNAME).zip + git archive --prefix=$(PLUGINNAME)/ -o $(PLUGINNAME).zip $(VERSION) + echo "Created package: $(PLUGINNAME).zip" upload: zip + @echo + @echo "-------------------------------------" + @echo "Uploading plugin to QGIS Plugin repo." + @echo "-------------------------------------" $(PLUGIN_UPLOAD) $(PLUGINNAME).zip -# transup -# update .ts translation files transup: - pylupdate4 Makefile - -# transcompile -# compile translation files into .qm binary format -transcompile: $(TRANSLATIONS:.ts=.qm) + @echo + @echo "------------------------------------------------" + @echo "Updating translation files with any new strings." + @echo "------------------------------------------------" + @chmod +x scripts/update-strings.sh + @scripts/update-strings.sh $(LOCALES) + +transcompile: + @echo + @echo "----------------------------------------" + @echo "Compiled translation files to .qm files." + @echo "----------------------------------------" + @chmod +x scripts/compile-strings.sh + @scripts/compile-strings.sh $(LRELEASE) $(LOCALES) -# transclean -# deletes all .qm files transclean: + @echo + @echo "------------------------------------" + @echo "Removing compiled translation files." + @echo "------------------------------------" rm -f i18n/*.qm clean: - rm $(UI_FILES) $(RESOURCE_FILES) + @echo + @echo "------------------------------------" + @echo "Removing uic and rcc generated files" + @echo "------------------------------------" + rm $(COMPILED_UI_FILES) $(COMPILED_RESOURCE_FILES) -# build documentation with sphinx doc: + @echo + @echo "------------------------------------" + @echo "Building documentation using sphinx." + @echo "------------------------------------" cd help; make html diff --git a/README b/README index f3d45a5..dcf3d34 100644 --- a/README +++ b/README @@ -1,2 +1,4 @@ DigitizingTools: a QGIS plugin Subsumes different tools useful during digitizing sessions + +DO NOT install the plugin directly from github, this is the source code only; install via the extensions menu in QGIS. diff --git a/__init__.py b/__init__.py index d8b5bdc..c5bf53f 100644 --- a/__init__.py +++ b/__init__.py @@ -21,8 +21,10 @@ This script initializes the plugin, making it known to QGIS. """ +from __future__ import absolute_import + def classFactory(iface): # load RectOvalDigit class from file RectOvalDigit - from digitizingtools import DigitizingTools + from .digitizingtools import DigitizingTools return DigitizingTools(iface) diff --git a/digitizingtools.py b/digitizingtools.py index 24d8309..e086e44 100644 --- a/digitizingtools.py +++ b/digitizingtools.py @@ -21,14 +21,17 @@ * * ***************************************************************************/ """ +from __future__ import absolute_import +from builtins import object # Import the PyQt and QGIS libraries -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtWidgets from qgis.core import * -from dtDialog import DigitizingToolsAbout import os.path, sys # Set up current path. currentPath = os.path.dirname( __file__ ) sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/tools')) +sys.path.append(os.path.abspath(os.path.dirname(__file__))) +from .dtDialog import DigitizingToolsAbout #import the tools import dtsplitmultipart @@ -36,9 +39,8 @@ import dtclipper import dtfillring import dtfillgap -import dtsplitter import dtflipline -import dtprolongline +import dtsplitfeature import dtmovenodebyarea import dtmovesidebydistance import dtmovesidebyarea @@ -47,16 +49,21 @@ import dtmerge import dtexchangegeometry -class DigitizingTools: +class DigitizingTools(object): """Main class for the plugin""" def __init__(self, iface): # Save reference to the QGIS interface self.iface = iface # initialize plugin directory - self.plugin_dir = QtCore.QFileInfo(QgsApplication.qgisUserDbFilePath()).path() + "/python/plugins/DigitizingTools" + self.plugin_dir = QtCore.QFileInfo(QgsApplication.qgisUserDatabaseFilePath()).path() + "/python/plugins/DigitizingTools" # initialize locale + QgsMessageLog.logMessage("dir = " + self.plugin_dir) localePath = "" - locale = QtCore.QSettings().value("locale/userLocale", "en", type=str)[0:2] + + try: + locale = QtCore.QSettings().value("locale/userLocale", "en", type=str)[0:2] + except: + locale = "en" if QtCore.QFileInfo(self.plugin_dir).exists(): localePath = self.plugin_dir + "/i18n/digitizingtools_" + locale + ".qm" @@ -64,9 +71,7 @@ def __init__(self, iface): if QtCore.QFileInfo(localePath).exists(): self.translator = QtCore.QTranslator() self.translator.load(localePath) - - if QtCore.qVersion() > '4.3.3': - QtCore.QCoreApplication.installTranslator(self.translator) + QtCore.QCoreApplication.installTranslator(self.translator) def initGui(self): """Customize QGIS' GUI""" @@ -75,17 +80,18 @@ def initGui(self): self.toolBar.setObjectName("DigitizingTools") #. Add a menu - self.menuLabel = QtGui.QApplication.translate( "DigitizingTools","&DigitizingTools" ) - self.digitizingtools_help = QtGui.QAction( QtGui.QApplication.translate("DigitizingTools", "Help" ), self.iface.mainWindow() ) - self.digitizingtools_about = QtGui.QAction( QtGui.QApplication.translate("DigitizingTools", "About" ), self.iface.mainWindow() ) + self.menuLabel = QtWidgets.QApplication.translate( "DigitizingTools","&DigitizingTools" ) + self.digitizingtools_help = QtWidgets.QAction( QtWidgets.QApplication.translate("DigitizingTools", "Help" ), self.iface.mainWindow() ) + self.digitizingtools_about = QtWidgets.QAction( QtWidgets.QApplication.translate("DigitizingTools", "About" ), self.iface.mainWindow() ) self.digitizingtools_about.setObjectName("DtAbout") - self.digitizingtools_settings = QtGui.QAction( QtGui.QApplication.translate("DigitizingTools", "Settings" ), self.iface.mainWindow() ) + self.digitizingtools_settings = QtWidgets.QAction( QtWidgets.QApplication.translate("DigitizingTools", "Settings" ), self.iface.mainWindow() ) self.iface.addPluginToMenu(self.menuLabel, self.digitizingtools_about) #. Add the tools self.multiPartSplitter = dtsplitmultipart.DtSplitMultiPartTool(self.iface, self.toolBar) self.partExtractor = dtextractpart.DtExtractPartTool(self.iface, self.toolBar) + self.splitfeature = dtsplitfeature.DtSplitFeature(self.iface, self.toolBar) self.merger = dtmerge.DtMerge(self.iface, self.toolBar) self.exchangeGeometry = dtexchangegeometry.DtExchangeGeometry(self.iface, self.toolBar) self.cutter = dtcutter.DtCutWithPolygon(self.iface, self.toolBar) @@ -93,9 +99,7 @@ def initGui(self): self.ringFiller = dtfillring.DtFillRing(self.iface, self.toolBar) self.gapFiller = dtfillgap.DtFillGap(self.iface, self.toolBar) self.gapFillerAll = dtfillgap.DtFillGapAllLayers(self.iface, self.toolBar) - self.splitter = dtsplitter.DtSplitWithLine(self.iface, self.toolBar) self.flipLine = dtflipline.DtFlipLine(self.iface, self.toolBar) - self.prolongLine = dtprolongline.DtProlongLine(self.iface, self.toolBar) self.moveNodeByArea = dtmovenodebyarea.DtMoveNodeByArea(self.iface, self.toolBar) self.moveSideByDistance = dtmovesidebydistance.DtMoveSideByDistance(self.iface, self.toolBar) self.moveSideByArea = dtmovesidebyarea.DtMoveSideByArea(self.iface, self.toolBar) diff --git a/documentation/use_of_in_place_processing.mp4 b/documentation/use_of_in_place_processing.mp4 new file mode 100644 index 0000000..773e76d Binary files /dev/null and b/documentation/use_of_in_place_processing.mp4 differ diff --git a/dtDialog.py b/dtDialog.py index b9697df..758e9c9 100644 --- a/dtDialog.py +++ b/dtDialog.py @@ -20,15 +20,20 @@ ***************************************************************************/ """ -from PyQt4 import QtGui, QtCore -from ui_about import Ui_about +from qgis.PyQt import QtWidgets, QtCore, uic +import os +from dtutils import dtGetVectorLayersByType -class DigitizingToolsAbout(QtGui.QDialog): +ABOUT_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'ui_about.ui')) + +CUTTER_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'ui_dtcutter.ui')) + +class DigitizingToolsAbout(QtWidgets.QDialog, ABOUT_CLASS): def __init__(self, iface): - QtGui.QDialog.__init__(self) - # Set up the user interface from Designer. - self.ui = Ui_about() - self.ui.setupUi(self) + QtWidgets.QDialog.__init__(self) + self.setupUi(self) # keep reference to QGIS interface self.iface = iface @@ -52,11 +57,44 @@ def __init__(self, iface): aboutText += " terms of the GNU General Public License as published by the Free Software Foundation;" aboutText += " either version 2 of the License, or (at your option) any later version." #QtGui.QMessageBox.information(None, "", aboutText) - self.ui.textArea.setPlainText(aboutText) + self.textArea.setPlainText(aboutText) +class DtChooseCutterLayer(QtWidgets.QDialog, CUTTER_CLASS): + def __init__(self, iface, isPolygonLayer, lastChoice): + QtWidgets.QDialog.__init__(self) + self.setupUi(self) + # keep reference to QGIS interface + self.iface = iface + self.isPolygonLayer = isPolygonLayer + self.setWindowTitle(QtCore.QCoreApplication.translate("dtCutterDialog", + "Choose Layer")) + self.lblCutter.setText(QtCore.QCoreApplication.translate("dtCutterDialog", + "cutter layer")) + self.chkCopy.setText(QtCore.QCoreApplication.translate("dtCutterDialog", + "add cutter polygon to edit layer")) + self.cutterLayer = lastChoice[0] + self.copyPoly = lastChoice[1] + self.initialize() + def initialize(self): + self.cbxLayer.clear() + layerList = dtGetVectorLayersByType(self.iface, 2, False) + for keyValue, valueArray in list(layerList.items()): + self.cbxLayer.addItem(keyValue, valueArray) + if self.cutterLayer != None: + if self.cutterLayer.id() == valueArray[0]: + self.cbxLayer.setCurrentText(keyValue) + if not self.isPolygonLayer: + self.chkCopy.setChecked(False) + else: + self.chkCopy.setChecked(self.copyPoly) + self.chkCopy.setEnabled(self.isPolygonLayer) + def accept(self): + self.cutterLayer = self.cbxLayer.currentData()[1] + self.copyPoly = self.chkCopy.isChecked() + self.done(1) diff --git a/dt_icons_rc.qrc b/dt_icons_rc.qrc new file mode 100644 index 0000000..87222ef --- /dev/null +++ b/dt_icons_rc.qrc @@ -0,0 +1,26 @@ + + + tools/icons/splitter.png + tools/icons/cutter.png + tools/icons/cutter_batch.png + tools/icons/clipper.png + tools/icons/clipper_batch.png + tools/icons/fillRing.png + tools/icons/fillRingBatch.png + tools/icons/fillGap.png + tools/icons/fillGapBatch.png + tools/icons/fillGapAll.png + tools/icons/MultiToSingle.png + tools/icons/MultiToSingleBatch.png + tools/icons/flipLine.png + tools/icons/flipLineBatch.png + tools/icons/MovePolygonNodeByArea.png + tools/icons/ParallelMovePolygonSideByArea.png + tools/icons/ParallelMovePolygonSideByDistance.png + tools/icons/medianLine.png + tools/icons/ExtractPart.png + tools/icons/Merge.png + tools/icons/exchangeGeometry.png + tools/icons/splitfeature.png + + diff --git a/i18n/digitizingtools_de.qm b/i18n/digitizingtools_de.qm index c0c5d4c..51119d6 100644 Binary files a/i18n/digitizingtools_de.qm and b/i18n/digitizingtools_de.qm differ diff --git a/i18n/digitizingtools_de.ts b/i18n/digitizingtools_de.ts index 3dda2f3..c1e5cbe 100644 --- a/i18n/digitizingtools_de.ts +++ b/i18n/digitizingtools_de.ts @@ -1,35 +1,34 @@ - - + DigitizingTools - + &DigitizingTools - + Help Hilfe - + About Über - + Settings Einstellungen - + is disabled because layer CRS and project CRS do not match! wird deaktiviert weil das KBS des Layers und das KBS des Projektes nicht übereinstimmen! - + The geometry type of the result is not valid in this layer! Der Geometrietyp des Ergebnissses ist in diesem Layer ungültig! @@ -144,172 +143,142 @@ digitizingtools - - Cut with polygon from another layer - Mit einem Polygon eines anderen Layers ausstanzen - - - - Please provide a polygon layer to cut with. - Bitte einen Polygonlayer zum Ausstanzen bereitstellen. - - - - cutter layer - Ausstanzlayer - - - + No Selection in layer Keine Auswahl in Layer - + Use all features for process? Alle Objekte zum Prozessieren benutzen? - + There are Es gibt - + features selected in layer gewählte Objekte im Layer - + There are invalid geometries in layer Es gibt ungültige Geometrien im Layer - + Snap Tolerance Fangtoleranz - + Could not snap vertex Konnte keinen Stützpunkt fangen - + Have you set the tolerance in Settings > Snapping Options? Haben Sie die Toleranz in Einstellungen > Fangoptionen eingetragen? - + Error occured during Während dieses Prozesses trat ein Fehler auf - + Cutter Stanze - + A feature would be completely removed by cutting. Delete this feature's dataset altogether? Durch das Ausstanzen würde ein Objekt komplett entfernt werden. Soll der Datensatz dieses Objektes gelöscht werden? - - Splitter - Aufteilen - - - - splitter layer - Aufteilungslayer - - - - Please provide a line layer to split with. - Bitte stellen Sie einen Linienlayer zum Aufteilen bereit. - - - + splitting of feature Objekt aufteilen - + Move polygon node (along a side) to achieve target area Stützpunkt (entlang einer Seite) so verschieben, dass eine Zielfläche erreicht wird - + Move polygon node by area Stützpunkt durch Festlegen einer Zielfläche verschieben - + Please select one polygon to edit. Bitte wählen Sie das Polygon aus, das bearbeitet werden soll. - + Please select only one polygon to edit. Bitte wählen Sie nur ein Polygon aus. - + Cancel Abbrechen - + Target Area not valid. Die Zielfläche ist ungültig. - + Not enough vertices selected. Sie haben nicht genug Knoten ausgewählt. - + Selected vertices should be consecutive on the selected polygon. Die ausgewählten Knoten müssen einander benachbart sein. - + Vertices not on the selected polygon. Die ausgewählten Knoten gehören nicht zum ausgewählten Polygon. - + Parallel move of polygon side to given distance Paralleles Verschieben einer Seite durch Festlegen einer Entfernung - + Move polygon side by distance Verschieben einer Seite durch Festlegen einer Entfernung - + Target Distance not valid. Die Entfernung ist ungültig. - + Polygon side not selected. Sie haben keine Polygonseite ausgewählt. - + Selected segment should be on the selected polygon. Das ausgewählte Segment muß auf dem ausgewählten Polygon liegen. - + Parallel move of polygon side to target area Paralleles Verschieben einer Seite durch Festlegen einer Zielfläche - + Move polygon side by area Verschieben einer Seite durch Festlegen einer Zielfläche @@ -364,113 +333,146 @@ Alle Risse in den ausgewählten Objekten mit neuen Objekten fluten - + Digitize median line between adjacent polygons Mittellinie zwischen zwei benachbarten Polygonen erzeugen - + Digitize median line Mittellinie erzeugen - + Please clear selection. Bitte löschen Sie die Auswahl. - - Please select only one feature to split with. - Bitte wählen Sie nur ein Objekt aus, mit dem aufgeteilt werden soll. + + Clip with polygon (interactive) + Mit einem Polygon ausschneiden (interaktiv) - - Please select the features to be splitted. - Bitte wählen Sie die Objekte aus, die aufgeteilt werden sollen. + + Clip with selected polygons + Mit den ausgewählten Polygonen ausschneiden - - Split selected features with selected line from another layer - Ausgewählte Objekte mit ausgewählter Linie eines anderen Layers aufteilen + + Clipper + Ausschneiden - - Clip with polygon from another layer - Mit einem Polygon eines anderen Layers ausschneiden (clip) + + Fill gap between polygons of all visible layers with a new feature + Riss zwischen Polygonen aller sichtbaren Layer mit einem neuen Objekt fluten - - Clipper - Clipper + + Split off one part and add it as a new feature + Teil abspalten und als neues Objekt einfügen - - clipper layer - Ausschneidelayer + + Merge selected features + Gewählte Objekte verschmelzen + + + + Merge + Verschmelzen - - Please provide a polygon layer to clip with. - Bitte einen Polygonlayer zum Ausschneiden bereitstellen. + + Exchange attributes between selected features + Attribute zwischen gewählten Objekten vertauschen - - Please select only one feature to clip with. - Bitte wählen Sie nur ein Objekt aus, mit dem ausgeschnitten werden soll. + + Split Features + Objekte zerteilen - - Fill gap between polygons of all visible layers with a new feature - Riss zwischen Polygonen aller sichtbaren Layer mit einem neuen Objekt fluten + + Split Multipart Feature + Multiobjekt zerteilen - - Amend Line - Linie ergänzen + + Create new feature from this part? + Diesen Teil als neues Objekt erzeugen? - - Split off one part and add it as a new feature - Teil abspalten und als neues Objekt einfügen + + Choose which already existing feature should remain + Wählen Sie das bereits existierende Objekt, das übrigbleiben soll - - Merge selected features - Gewählte Objekte verschmelzen + + Fix geometries before commiting changes. + Reparieren Sie die Geometrien vor dem Speichern. - - Merge - Verschmelzen + + An error occured during flipping + Während des Umdrehens trat ein Fehler auf - - Choose which feature should remain - Wählen Sie das Objekt, das übrigbleiben soll + + New geometry + Neue Geometrie - - Exchange the geomteries between selected features - Geometrien zwischen gewählten Objekten vertauschen + + Geometry's type is not compatible with the following layer: + Der Geometrietyp ist mit dem folgenden Layer nicht kompatibel: + + + + Cut with polygon (interactive) + Mit einem Polygon ausstanzen (interaktiv) + + + + Cut with selected polygons + Mit den ausgewählten Polygonen ausstanzen dtAbout - + Subsumes different tools useful during digitizing sessions Fasst verschiedene nützliche Digitalisierwerkzeuge zusammen - + List of Contributors: Folgende Personen haben beigetragen: + + dtCutterDialog + + + Choose Layer + Layer wählen + + + + cutter layer + Ausstanzlayer + + + + add cutter polygon to edit layer + füge das Ausstanzpolygon dem Bearbeitungslayer hinzu + + dtutils - + Choose Layer Layer wählen @@ -478,32 +480,22 @@ editcommand - + Split features Geometrien aufgeteilt - + Cut Features Objekte ausgestanzt - - Delete Features - Objekte gelöscht - - - - Prolong Line - Linie verlängert - - - + Move Node By Area Stützpunkt durch Festlegen einer Zielfläche verschoben - + Move Side By Distance Seite durch Festlegen einer Entfernung verschoben @@ -518,17 +510,17 @@ Ringe gefüllt - + Move Side By Area Seite durch Festlegen einer Zielfläche verschoben - + Fill gap Riss geflutet - + Fill gaps Risse geflutet @@ -553,19 +545,24 @@ Teil extrahiert - + Clip Features Objekte ausgeschnitten - Exchange geometries - Geometrien vertauscht + Exchange attributes + Attribute vertauscht - + Merge Features Objekte verschmolzen + + + Features split + Objekte geteilt + diff --git a/i18n/digitizingtools_fr.ts b/i18n/digitizingtools_fr.ts index e900cfa..f5096ba 100755 --- a/i18n/digitizingtools_fr.ts +++ b/i18n/digitizingtools_fr.ts @@ -23,7 +23,7 @@ Paramètres - + is disabled because layer CRS and project CRS do not match! Désactivé du fait que le SCR du projet et de la couche diffèrent ! @@ -228,7 +228,7 @@ S'il vous plaît fournir une couche de ligne de découpage. - + splitting of feature Découpage de l'objet @@ -422,11 +422,6 @@ Fill gap between polygons of all visible layers with a new feature - - - Amend Line - - Split off one part and add it as a new feature @@ -449,7 +444,22 @@ - Exchange the geomteries between selected features + Exchange attributes between selected features + + + + + Split Features + + + + + Split Multipart Feature + + + + + Create new feature from this part? @@ -491,11 +501,6 @@ Delete Features Détruire des objets - - - Prolong Line - Prolonger une ligne - Move Node By Area @@ -558,7 +563,7 @@ - Exchange geometries + Exchange attributes @@ -566,5 +571,10 @@ Merge Features + + + Features split + + diff --git a/i18n/digitizingtools_is.qm b/i18n/digitizingtools_is.qm new file mode 100644 index 0000000..bc711f2 Binary files /dev/null and b/i18n/digitizingtools_is.qm differ diff --git a/i18n/digitizingtools_is.ts b/i18n/digitizingtools_is.ts new file mode 100644 index 0000000..a5a26d3 --- /dev/null +++ b/i18n/digitizingtools_is.ts @@ -0,0 +1,577 @@ + + + + is + Icelandic + Sveinn í Felli <sv1@fellsnet.is> + 2023-11-16 15:10+0000 + + Lokalize 22.04.3 + # Sveinn í Felli <sv1@fellsnet.is>, 2023. + MIME-Version,Content-Type,Content-Transfer-Encoding,Plural-Forms,X-Language,X-Source-Language,X-Qt-Contexts,Last-Translator,PO-Revision-Date,Project-Id-Version,Language-Team,Language,X-Generator + + DigitizingTools + + + &DigitizingTools + &Hnitunarverkfæri + + + + Help + Hjálp + + + + About + Um hugbúnaðinn + + + + Settings + Stillingar + + + + is disabled because layer CRS and project CRS do not match! + er óvirkt þar sem viðmiðskerfi þekju og viðmiðskerfi verkefnis samsvara ekki! + + + + The geometry type of the result is not valid in this layer! + Tegund lögunar í útkomunni er ekki gild í þessari þekju! + + + + DtMoveNodeByArea + + + Move Node By Area + Færa hnút miðað við flöt + + + + Area of the selected polygon: + Flötur valins fláka: + + + + Target Area: + Marksvæði: + + + + Move Node + Flytja hnút + + + + Close + Loka + + + + DtMoveSideByArea + + + Parallel Move Side By Area + Færa hlið samhliða miðað við flöt + + + + Area of the selected polygon: + Flötur valins fláka: + + + + Target Area: + Marksvæði: + + + + Move Side + Færa hlið + + + + Close + Loka + + + + Size of side to be moved: + Stærð hliðar sem á að færa: + + + + Fixed + Föst + + + + Variable + Breytileg + + + + DtMoveSideByDistance + + + Move Side By Distance + Færa hlið miðað við vegalengd + + + + Move Distance: + Færsluvegalengd: + + + + Move Side + Færa hlið + + + + Close + Loka + + + + about + + + About + Um hugbúnaðinn + + + + DigitizingTools + Hnitunarverkfæri + + + + digitizingtools + + + No Selection in layer + Ekkert er valið á þekju + + + + Use all features for process? + Nota allar allar fitjur í vinnslu? + + + + There are + Það eru + + + + features selected in layer + fitjur valdar á þekjunni + + + + There are invalid geometries in layer + Það eru ógildar laganir í þekju + + + + Snap Tolerance + Þolvik grips + + + + Could not snap vertex + Gat ekki gripið brotpunkt + + + + Have you set the tolerance in Settings > Snapping Options? + Hefurðu stillt þolvik í Stillingar > Valkostir grips? + + + + Error occured during + Villa kom upp við + + + + Cutter + Skeri + + + + A feature would be completely removed by cutting. Delete this feature's dataset altogether? + Fitja myndi hverfa algerlega við klippingu. Á að eyða alveg gagnasafni þessarar fitju? + + + + splitting of feature + skipting fitju + + + + Move polygon node (along a side) to achieve target area + Færa hnút fláka (meðfram hlið) til að fá fram markflöt + + + + Move polygon node by area + Færa hnút fláka miðað við flöt + + + + Please select one polygon to edit. + Veldu einn fláka til að breyta. + + + + Please select only one polygon to edit. + Veldu aðeins einn fláka til að breyta. + + + + Cancel + Hætta við + + + + Target Area not valid. + Marksvæði er ekki gilt. + + + + Not enough vertices selected. + Ekki nógu margir brotpunktar valdir. + + + + Selected vertices should be consecutive on the selected polygon. + Valdir brotpunktar ættu að vera samfelldir á völdum fláka. + + + + Vertices not on the selected polygon. + Brotpunktar ekki á völdum fláka. + + + + Parallel move of polygon side to given distance + Færa hlið fláka samhliða um tiltekna vegalengd + + + + Move polygon side by distance + Færa hlið fláka miðað við vegalengd + + + + Target Distance not valid. + Markvegalengd er ekki gild. + + + + Polygon side not selected. + Hlið fláka ekki valin. + + + + Selected segment should be on the selected polygon. + Valinn bútur ætti að vera á völdum fláka. + + + + Parallel move of polygon side to target area + Færa hlið fláka samhliða á marksvæði + + + + Move polygon side by area + Færa hlið fláka miðað við flöt + + + + Fill gap + Fylla bil + + + + There are no gaps between the polygons. + Það eru engin bil á milli flákanna. + + + + Split selected multi-part features to single part + Kljúfa valdar marghluta-fitjur í einshluta-fitjur + + + + Flip selected lines + Fletta völdum línum + + + + Split multi-part feature to single part (interactive mode) + Kljúfa marghluta-fitjur í einshluta-fitjur (gagnvirkur hamur) + + + + Flip line (interactive mode) + Fletta línu (gagnvirkur hamur) + + + + Fill ring with a new feature (interactive mode) + Fylla hring með nýrri fitju (gagnvirkur hamur) + + + + Fill all rings in selected polygons with new features + Fylla alla hringi í völdum fitjum með nýjum fitjum + + + + Fill gap with a new feature (interactive mode) + Fylla bil með nýrri fitju (gagnvirkur hamur) + + + + Fill all gaps between selected polygons with new features + Fylla öll bil í völdum fitjum með nýjum fitjum + + + + Digitize median line between adjacent polygons + Hnita miðlínu á milli aðliggjandi fláka + + + + Digitize median line + Hnita miðlínu + + + + Please clear selection. + Hreinsaðu valið. + + + + Clip with polygon (interactive) + Afmarka með fláka (gagnvirkt) + + + + Clip with selected polygons + Afmarka með völdum flákum + + + + Clipper + Klippari + + + + Fill gap between polygons of all visible layers with a new feature + Fylla bil milli fláka á öllum sýnilegum þekjum með nýrri fitju + + + + Split off one part and add it as a new feature + Kljúfa frá einn hluta og bæta honum við sem nýrri fitju + + + + Merge selected features + Sameina valdar fitjur + + + + Merge + Sameina + + + + Exchange attributes between selected features + Miðla eigindum á milli valinna fitja + + + + Split Features + Kljúfa fitjur + + + + Split Multipart Feature + Kljúfa marghluta-fitju + + + + Create new feature from this part? + Búa til nýja fitju úr þessum hluta? + + + + Choose which already existing feature should remain + Veldu hvaða fyrirliggjandi fitju ætti að vera eftir + + + + Fix geometries before commiting changes. + Lagfærðu laganir áður en breytingar eru sendar inn. + + + + An error occured during flipping + Villa kom upp við flettingu + + + + New geometry + Ný lögun + + + + Geometry's type is not compatible with the following layer: + Tegund lögunar er ekki samhæfð við eftirfarandi þekju: + + + + Cut with polygon (interactive) + Klippa með fláka (gagnvirkt) + + + + Cut with selected polygons + Klippa með völdum flákum + + + + dtAbout + + + Subsumes different tools useful during digitizing sessions + Tekur saman ýmis verkfæri sem eru notadrjúg við hnitun + + + + List of Contributors: + Listi yfir þátttakendur: + + + + dtCutterDialog + + + Choose Layer + Veldu þekju + + + + cutter layer + skurðarþekja + + + + add cutter polygon to edit layer + bæta skurðarfláka á breytingaþekju + + + + dtutils + + + Choose Layer + Veldu þekju + + + + editcommand + + + Split features + Kljúfa fitjur + + + + Cut Features + Klippa fitjur + + + + Move Node By Area + Færa hnút miðað við flöt + + + + Move Side By Distance + Færa hlið miðað við vegalengd + + + + Fill ring + Fylla hring + + + + Fill rings + Fylla hringi + + + + Move Side By Area + Færa hlið miðað við flöt + + + + Fill gap + Fylla bil + + + + Fill gaps + Fylla í bil + + + + Flip lines + Fletta línum + + + + Flip line + Fletta línu + + + + Split feature + Kljúfa fitju + + + + Extract part + Ná í hluta + + + + Clip Features + Afmarka fitjur + + + + Exchange attributes + Miðla eigindum + + + + Merge Features + Sameina fitjur + + + + Features split + Fitjum skipt + + + diff --git a/metadata.txt b/metadata.txt index 9a8b906..c6c35c0 100644 --- a/metadata.txt +++ b/metadata.txt @@ -10,11 +10,11 @@ [general] name=Digitizing Tools -qgisMinimumVersion=2.8 +qgisMinimumVersion=3.4.0 description=Subsumes different tools useful during digitizing sessions about=DigitizingTools is meant to be a compilation of tools missing in basic QGIS, especially when digitizing on existing features. It is a collaborative effort and does not contain CAD like functions meant for construction. category=Vector -version=0.10.2 +version=1.5.3 # end of mandatory metadata @@ -37,5 +37,5 @@ experimental=False deprecated=False # Author contact information -author=Bernhard Ströbl (Kommunale Immobilien Jena), Angelos Tzotsos (NTUA) -email=bernhard.stroebl@jena.de +author=Bernhard Ströbl, Angelos Tzotsos (NTUA) +email=b.stroebl@stroweb.de diff --git a/scripts/compile-strings.sh b/scripts/compile-strings.sh new file mode 100644 index 0000000..9d76083 --- /dev/null +++ b/scripts/compile-strings.sh @@ -0,0 +1,12 @@ +#!/bin/bash +LRELEASE=$1 +LOCALES=$2 + + +for LOCALE in ${LOCALES} +do + echo "Processing: ${LOCALE}.ts" + # Note we don't use pylupdate with qt .pro file approach as it is flakey + # about what is made available. + $LRELEASE i18n/${LOCALE}.ts +done diff --git a/scripts/run-env-linux.sh b/scripts/run-env-linux.sh new file mode 100644 index 0000000..668247c --- /dev/null +++ b/scripts/run-env-linux.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +QGIS_PREFIX_PATH=/usr/local/qgis-2.0 +if [ -n "$1" ]; then + QGIS_PREFIX_PATH=$1 +fi + +echo ${QGIS_PREFIX_PATH} + + +export QGIS_PREFIX_PATH=${QGIS_PREFIX_PATH} +export QGIS_PATH=${QGIS_PREFIX_PATH} +export LD_LIBRARY_PATH=${QGIS_PREFIX_PATH}/lib +export PYTHONPATH=${QGIS_PREFIX_PATH}/share/qgis/python:${QGIS_PREFIX_PATH}/share/qgis/python/plugins:${PYTHONPATH} + +echo "QGIS PATH: $QGIS_PREFIX_PATH" +export QGIS_DEBUG=0 +export QGIS_LOG_FILE=/tmp/inasafe/realtime/logs/qgis.log + +export PATH=${QGIS_PREFIX_PATH}/bin:$PATH + +echo "This script is intended to be sourced to set up your shell to" +echo "use a QGIS 2.0 built in $QGIS_PREFIX_PATH" +echo +echo "To use it do:" +echo "source $BASH_SOURCE /your/optional/install/path" +echo +echo "Then use the make file supplied here e.g. make guitest" diff --git a/scripts/update-strings.sh b/scripts/update-strings.sh new file mode 100644 index 0000000..03dcfd7 --- /dev/null +++ b/scripts/update-strings.sh @@ -0,0 +1,56 @@ +#!/bin/bash +LOCALES=$* + +# Get newest .py files so we don't update strings unnecessarily + +CHANGED_FILES=0 +PYTHON_FILES=`find . -regex ".*\(ui\|py\)$" -type f` +for PYTHON_FILE in $PYTHON_FILES +do + CHANGED=$(stat -c %Y $PYTHON_FILE) + if [ ${CHANGED} -gt ${CHANGED_FILES} ] + then + CHANGED_FILES=${CHANGED} + fi +done + +# Qt translation stuff +# for .ts file +UPDATE=false +for LOCALE in ${LOCALES} +do + TRANSLATION_FILE="i18n/$LOCALE.ts" + if [ ! -f ${TRANSLATION_FILE} ] + then + # Force translation string collection as we have a new language file + touch ${TRANSLATION_FILE} + UPDATE=true + break + fi + + MODIFICATION_TIME=$(stat -c %Y ${TRANSLATION_FILE}) + if [ ${CHANGED_FILES} -gt ${MODIFICATION_TIME} ] + then + # Force translation string collection as a .py file has been updated + UPDATE=true + break + fi +done + +if [ ${UPDATE} == true ] +# retrieve all python files +then + print ${PYTHON_FILES} + # update .ts + echo "Please provide translations by editing the translation files below:" + for LOCALE in ${LOCALES} + do + echo "i18n/"${LOCALE}".ts" + # Note we don't use pylupdate with qt .pro file approach as it is flakey + # about what is made available. + pylupdate4 -noobsolete ${PYTHON_FILES} -ts i18n/${LOCALE}.ts + done +else + echo "No need to edit any translation files (.ts) because no python files" + echo "has been updated since the last update translation. " +fi diff --git a/tools/dtToolsDialog.py b/tools/dtToolsDialog.py new file mode 100644 index 0000000..bfa3c9a --- /dev/null +++ b/tools/dtToolsDialog.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + DigitizingTools + A QGIS plugin + Subsumes different tools useful during digitizing sessions + ------------------- + begin : 2017-12-12 + copyright : (C) 2017 by Bernhard Ströbl + email : bernhard.stroebl@jena.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +""" + +from qgis.PyQt import QtWidgets, QtCore, uic +from dtutils import dtGetHighlightSettings +from qgis.core import * +from qgis.gui import QgsHighlight +import os + +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'ui_dtchooseremaining.ui')) + +class DigitizingToolsChooseRemaining(QtWidgets.QDialog, FORM_CLASS): + def __init__(self, iface, editLayer, pkValues, featDict, title): + QtWidgets.QDialog.__init__(self) + self.setupUi(self) + self.iface = iface + self.editLayer = editLayer + self.pkValues = pkValues + self.featDict = featDict + self.chooseId.addItems(list(self.pkValues.keys())) + self.setWindowTitle(title) + self.label.setText(QtWidgets.QApplication.translate( + "digitizingtools", "Choose which already existing feature should remain")) + self.buttonBox.rejected.connect(self.reject) + self.buttonBox.accepted.connect(self.accept) + + def clearHighlight(self): + try: + self.hl.hide() + except: + pass + + @QtCore.pyqtSlot(int) + def on_chooseId_currentIndexChanged(self, thisIndex): + self.clearHighlight() + aPkValue = self.chooseId.currentText() + aGeom = self.featDict[self.pkValues[aPkValue]].geometry() + hlColor, hlFillColor, hlBuffer, hlMinWidth = dtGetHighlightSettings() + self.hl = QgsHighlight(self.iface.mapCanvas(), aGeom, self.editLayer) + self.hl.setColor(hlColor) + self.hl.setFillColor(hlFillColor) + self.hl.setBuffer(hlBuffer) + self.hl.setWidth(hlMinWidth) + self.hl.show() + + @QtCore.pyqtSlot() + def reject(self): + self.clearHighlight() + self.done(0) + + @QtCore.pyqtSlot() + def accept(self): + self.clearHighlight() + self.pkValueToKeep = self.chooseId.currentText() + self.done(1) diff --git a/tools/dt_icons.qrc b/tools/dt_icons.qrc deleted file mode 100644 index c568898..0000000 --- a/tools/dt_icons.qrc +++ /dev/null @@ -1,24 +0,0 @@ - - - icons/splitter.png - icons/cutter.png - icons/clipper.png - icons/fillRing.png - icons/fillRingBatch.png - icons/fillGap.png - icons/fillGapBatch.png - icons/fillGapAll.png - icons/MultiToSingle.png - icons/MultiToSingleBatch.png - icons/flipLine.png - icons/flipLineBatch.png - icons/prolongline.png - icons/MovePolygonNodeByArea.png - icons/ParallelMovePolygonSideByArea.png - icons/ParallelMovePolygonSideByDistance.png - icons/medianLine.png - icons/ExtractPart.png - icons/Merge.png - icons/exchangeGeometry.png - - diff --git a/tools/dtclipper.py b/tools/dtclipper.py index e7210bc..69329a9 100644 --- a/tools/dtclipper.py +++ b/tools/dtclipper.py @@ -16,20 +16,23 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtGui from qgis.core import * import dt_icons_rc import dtutils -from dttools import DtSingleButton +from dttools import DtDualToolSelectPolygon -class DtClipWithPolygon(DtSingleButton): +class DtClipWithPolygon(DtDualToolSelectPolygon): '''Clip from active editable layer with selected polygon from another layer''' def __init__(self, iface, toolBar): - DtSingleButton.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/clipper.png"), QtCore.QCoreApplication.translate( - "digitizingtools", "Clip with polygon from another layer"), - geometryTypes = [2, 3, 5, 6], dtName = "dtClipper") + "digitizingtools", "Clip with polygon (interactive)"), + QtGui.QIcon(":/clipper_batch.png"), + QtCore.QCoreApplication.translate( + "digitizingtools", "Clip with selected polygons"), + geometryTypes = [3, 6], dtName = "dtClipper") self.enable() def process(self): @@ -37,116 +40,71 @@ def process(self): title = QtCore.QCoreApplication.translate( "digitizingtools", "Clipper") - clipperLayer = dtutils.dtChooseVectorLayer( - self.iface, 2, msg = QtCore.QCoreApplication.translate( - "digitizingtools", "clipper layer" - )) + processLayer = self.iface.activeLayer() + msgLst = dtutils.dtGetNoSelMessage() + noSelMsg1 = msgLst[0] - if clipperLayer == None: + if processLayer.selectedFeatureCount() == 0: self.iface.messageBar().pushMessage( - title, QtCore.QCoreApplication.translate( - "digitizingtools", "Please provide a polygon layer to clip with." - )) + title, noSelMsg1 + " " + processLayer.name()) + return None else: - passiveLayer = self.iface.activeLayer() - msgLst = dtutils.dtGetNoSelMessage() - noSelMsg1 = msgLst[0] - noSelMsg2 = msgLst[1] - - if clipperLayer.selectedFeatureCount() == 0: - self.iface.messageBar().pushMessage( - title, noSelMsg1 + " " + clipperLayer.name()) - return None - elif clipperLayer.selectedFeatureCount() != 1: - numSelSplitMsg = dtutils.dtGetManySelMessage(clipperLayer) - self.iface.messageBar().pushMessage(title, numSelSplitMsg + \ + clipperGeoms = [] + + for feat in processLayer.selectedFeatures(): + clipperGeom = feat.geometry() + + if not clipperGeom.isGeosValid(): + thisWarning = dtutils.dtGetInvalidGeomWarning(processLayer) + dtutils.dtShowWarning(self.iface, thisWarning) + continue + else: + clipperGeoms.append(clipperGeom) + + if len(clipperGeoms) == 0: + return None # could be only invalid geoms selected + + processLayer.invertSelection() + idsToProcess = [] + processLayer.beginEditCommand( QtCore.QCoreApplication.translate( - "digitizingtools", - " Please select only one feature to clip with." + "editcommand", "Clip Features" )) + featuresBeingClipped = 0 + + for aFeat in processLayer.selectedFeatures(): + idsToProcess.append(aFeat.id()) + + for clipperGeom in clipperGeoms: + bbox = clipperGeom.boundingBox() + processLayer.selectByRect(bbox) # make a new selection + + for selFeat in processLayer.selectedFeatures(): + if idsToProcess.count(selFeat.id()) == 0: + continue + + selGeom = selFeat.geometry() + + if not selGeom.isGeosValid(): + thisWarning = dtutils.dtGetInvalidGeomWarning(processLayer) + dtutils.dtShowWarning(self.iface, thisWarning) + continue + + if clipperGeom.intersects(selGeom): # we have a candidate + newGeom = selGeom.intersection(clipperGeom) + + if newGeom != None: + if not newGeom.isEmpty(): + if newGeom.area() > 0: + selFeat.setGeometry(newGeom) + processLayer.updateFeature(selFeat) + featuresBeingClipped += 1 + + processLayer.removeSelection() + self.iface.mapCanvas().refresh() + + if featuresBeingClipped == 0: + processLayer.destroyEditCommand() else: - if passiveLayer.selectedFeatureCount() == 0: - msgLst = dtutils.dtGetNoSelMessage() - noSelMsg1 = msgLst[0] - noSelMsg2 = msgLst[1] - reply = QtGui.QMessageBox.question(None, title, - noSelMsg1 + " " + passiveLayer.name() + "\n" + noSelMsg2, - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No ) - - if reply == QtGui.QMessageBox.Yes: - passiveLayer.invertSelection() - else: - return None - - idsToProcess = [] - - for aFeat in passiveLayer.selectedFeatures(): - idsToProcess.append(aFeat.id()) - - if clipperLayer.selectedFeatureCount() > 0: - # determine srs, we work in the project's srs - clipperCRSSrsid = clipperLayer.crs().srsid() - passiveCRSSrsid = passiveLayer.crs().srsid() - mc = self.iface.mapCanvas() - renderer = mc.mapRenderer() - projectCRSSrsid = renderer.destinationCrs().srsid() - passiveLayer.beginEditCommand( - QtCore.QCoreApplication.translate( - "editcommand", "Clip Features" - )) - featuresBeingClipped = 0 - - for feat in clipperLayer.selectedFeatures(): - clipperGeom = QgsGeometry(feat.geometry()) - - if not clipperGeom.isGeosValid(): - thisWarning = dtutils.dtGetInvalidGeomWarning(clipperLayer) - dtutils.dtShowWarning(self.iface, thisWarning) - continue - - if clipperCRSSrsid != projectCRSSrsid: - clipperGeom.transform(QgsCoordinateTransform( - clipperCRSSrsid, projectCRSSrsid - )) - - bbox = clipperGeom.boundingBox() - passiveLayer.select(bbox, False) # make a new selection - - for selFeat in passiveLayer.selectedFeatures(): - if idsToProcess.count(selFeat.id()) == 0: - continue - - selGeom = QgsGeometry(selFeat.geometry()) - - if not selGeom.isGeosValid(): - thisWarning = dtutils.dtGetInvalidGeomWarning(passiveLayer) - dtutils.dtShowWarning(self.iface, thisWarning) - continue - - if passiveCRSSrsid != projectCRSSrsid: - selGeom.transform( - QgsCoordinateTransform(passiveCRSSrsid, - projectCRSSrsid - )) - - if clipperGeom.intersects(selGeom): # we have a candidate - newGeom = selGeom.intersection(clipperGeom) - - if newGeom != None: - if not newGeom.isGeosEmpty(): - if passiveCRSSrsid != projectCRSSrsid: - newGeom.transform(QgsCoordinateTransform( - projectCRSSrsid, passiveCRSSrsid)) - - selFeat.setGeometry(newGeom) - passiveLayer.updateFeature(selFeat) - featuresBeingClipped += 1 - - if featuresBeingClipped == 0: - passiveLayer.destroyEditCommand() - else: - passiveLayer.endEditCommand() - - passiveLayer.removeSelection() - self.iface.mapCanvas().refresh() + processLayer.endEditCommand() diff --git a/tools/dtcutter.py b/tools/dtcutter.py index 1b3fecf..cd8e2af 100644 --- a/tools/dtcutter.py +++ b/tools/dtcutter.py @@ -16,20 +16,23 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtGui, QtWidgets from qgis.core import * import dt_icons_rc import dtutils -from dttools import DtSingleButton +from dttools import DtDualToolSelectPolygon -class DtCutWithPolygon(DtSingleButton): +class DtCutWithPolygon(DtDualToolSelectPolygon): '''Cut out from active editable layer with selected polygon from another layer''' def __init__(self, iface, toolBar): - DtSingleButton.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/cutter.png"), - QtCore.QCoreApplication.translate("digitizingtools", "Cut with polygon from another layer"), - geometryTypes = [2, 3, 5, 6], dtName = "dtCutter") + QtCore.QCoreApplication.translate("digitizingtools", "Cut with polygon (interactive)"), + QtGui.QIcon(":/cutter_batch.png"), + QtCore.QCoreApplication.translate("digitizingtools", "Cut with selected polygons"), + geometryTypes = [3, 6], dtName = "dtCutter") self.enable() + self.lastChoice = [None, False] def process(self): '''Function that does all the real work''' @@ -37,127 +40,108 @@ def process(self): showEmptyWarning = True choice = None fidsToDelete = [] - cutterLayer = dtutils.dtChooseVectorLayer(self.iface, 2, msg = QtCore.QCoreApplication.translate("digitizingtools", "cutter layer")) + processLayer = self.iface.activeLayer() - if cutterLayer == None: - self.iface.messageBar().pushMessage(title, QtCore.QCoreApplication.translate("digitizingtools", "Please provide a polygon layer to cut with.")) + if processLayer.selectedFeatureCount() == 0: + msgLst = dtutils.dtGetNoSelMessage() + noSelMsg1 = msgLst[0] + noSelMsg2 = msgLst[1] else: - passiveLayer = self.iface.activeLayer() - - if cutterLayer.selectedFeatureCount() == 0: - msgLst = dtutils.dtGetNoSelMessage() - noSelMsg1 = msgLst[0] - noSelMsg2 = msgLst[1] - reply = QtGui.QMessageBox.question(None, title, - noSelMsg1 + " " + cutterLayer.name() + "\n" + noSelMsg2, - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No ) - - if reply == QtGui.QMessageBox.Yes: - cutterLayer.invertSelection() - else: - return None + cutterGeoms = [] - if passiveLayer.selectedFeatureCount() == 0: - msgLst = dtutils.dtGetNoSelMessage() - noSelMsg1 = msgLst[0] - noSelMsg2 = msgLst[1] - reply = QtGui.QMessageBox.question(None, title, - noSelMsg1 + " " + passiveLayer.name() + "\n" + noSelMsg2, - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No ) + for feat in processLayer.selectedFeatures(): + cutterGeom = feat.geometry() - if reply == QtGui.QMessageBox.Yes: - passiveLayer.invertSelection() + if not cutterGeom.isGeosValid(): + thisWarning = dtutils.dtGetInvalidGeomWarning(processLayer) + dtutils.dtShowWarning(self.iface, thisWarning) + continue else: - return None + cutterGeoms.append(cutterGeom) - idsToProcess = [] + if len(cutterGeoms) == 0: + return None # could be only invalid geoms selected - for aFeat in passiveLayer.selectedFeatures(): - idsToProcess.append(aFeat.id()) + processLayer.invertSelection() + idsToProcess = [] - if cutterLayer.selectedFeatureCount() > 0: - # determine srs, we work in the project's srs - cutterCRSSrsid = cutterLayer.crs().srsid() - passiveCRSSrsid = passiveLayer.crs().srsid() - mc = self.iface.mapCanvas() - renderer = mc.mapRenderer() - projectCRSSrsid = renderer.destinationCrs().srsid() - passiveLayer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Cut Features")) - featuresBeingCut = 0 + for aFeat in processLayer.selectedFeatures(): + # was: if isSameLayer: + # for aFeat in processLayer.getFeatures() + idsToProcess.append(aFeat.id()) - for feat in cutterLayer.selectedFeatures(): - cutterGeom = QgsGeometry(feat.geometry()) + processLayer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Cut Features")) + featuresBeingCut = 0 + noMatchWarning = dtutils.dtGetNotMatchingGeomWarning(processLayer) - if not cutterGeom.isGeosValid(): - thisWarning = dtutils.dtGetInvalidGeomWarning(cutterLayer) - dtutils.dtShowWarning(self.iface, thisWarning) - continue + for cutterGeom in cutterGeoms: + if cutterGeom.wkbType() == 6: + cutterGeom = QgsGeometry.fromMultiPolygonXY(cutterGeom.asMultiPolygon()) + else: + cutterGeom = QgsGeometry.fromPolygonXY(cutterGeom.asPolygon()) - if cutterCRSSrsid != projectCRSSrsid: - cutterGeom.transform(QgsCoordinateTransform(cutterCRSSrsid, projectCRSSrsid)) + bbox = cutterGeom.boundingBox() - bbox = cutterGeom.boundingBox() - passiveLayer.select(bbox, False) # make a new selection + processLayer.selectByRect(bbox) # make a new selection - for selFeat in passiveLayer.selectedFeatures(): - if idsToProcess.count(selFeat.id()) == 0: - continue + for selFeat in processLayer.selectedFeatures(): + if idsToProcess.count(selFeat.id()) == 0: + continue - selGeom = QgsGeometry(selFeat.geometry()) + selGeom = selFeat.geometry() - if not selGeom.isGeosValid(): - thisWarning = dtutils.dtGetInvalidGeomWarning(passiveLayer) - dtutils.dtShowWarning(self.iface, thisWarning) - continue + if selGeom.isGeosEqual(cutterGeom): + continue # do not cut the same geometry - if passiveCRSSrsid != projectCRSSrsid: - selGeom.transform(QgsCoordinateTransform(passiveCRSSrsid, projectCRSSrsid)) + if not selGeom.isGeosValid(): + thisWarning = dtutils.dtGetInvalidGeomWarning(processLayer) + dtutils.dtShowWarning(self.iface, thisWarning) + continue - if cutterGeom.intersects(selGeom): # we have a candidate - newGeom = selGeom.difference(cutterGeom) + if cutterGeom.intersects(selGeom): # we have a candidate + newGeom = selGeom.difference(cutterGeom) - if newGeom != None: - if newGeom.isGeosEmpty(): - #selGeom is completely contained in cutterGeom - if showEmptyWarning: - choice = QtGui.QMessageBox.question(None, title, - QtCore.QCoreApplication.translate("digitizingtools", - "A feature would be completely removed by cutting. Delete this feature\'s dataset altogether?"), - QtGui.QMessageBox.Yes | QtGui.QMessageBox.YesToAll | QtGui.QMessageBox.No | QtGui.QMessageBox.NoToAll | QtGui.QMessageBox.Cancel) - - if choice == QtGui.QMessageBox.Cancel: - passiveLayer.destroyEditCommand() - return None - else: - showEmptyWarning = (choice == QtGui.QMessageBox.Yes or choice == QtGui.QMessageBox.No) - - if choice == QtGui.QMessageBox.Yes or choice == QtGui.QMessageBox.YesToAll: - fidsToDelete.append(selFeat.id()) + if newGeom != None: + if newGeom.isEmpty() or newGeom.area() == 0: + #selGeom is completely contained in cutterGeom + if showEmptyWarning: + choice = QtWidgets.QMessageBox.question(None, title, + QtCore.QCoreApplication.translate("digitizingtools", + "A feature would be completely removed by cutting. Delete this feature\'s dataset altogether?"), + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.YesToAll | + QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.NoToAll | + QtWidgets.QMessageBox.Cancel) + if choice == QtWidgets.QMessageBox.Cancel: + processLayer.destroyEditCommand() + processLayer.removeSelection() + return None else: - if passiveCRSSrsid != projectCRSSrsid: - newGeom.transform(QgsCoordinateTransform( projectCRSSrsid, passiveCRSSrsid)) - - selFeat.setGeometry(newGeom) - passiveLayer.updateFeature(selFeat) - #if passiveLayer.changeGeometry(selFeat.id(), newGeom): - featuresBeingCut += 1 - - if featuresBeingCut > 0: - passiveLayer.endEditCommand() - else: - passiveLayer.destroyEditCommand() - - passiveLayer.removeSelection() - - if len(fidsToDelete) > 0: - passiveLayer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Delete Features")) - for fid in fidsToDelete: - if not passiveLayer.deleteFeature(fid): - passiveLayer.destroyEditCommand() - return None + showEmptyWarning = (choice == QtWidgets.QMessageBox.Yes or choice == QtWidgets.QMessageBox.No) + + if choice == QtWidgets.QMessageBox.Yes or choice == QtWidgets.QMessageBox.YesToAll: + fidsToDelete.append(selFeat.id()) + else: + if not self.geometryTypeMatchesLayer(processLayer, newGeom): + newMsg = QtCore.QCoreApplication.translate( + "digitizingtools", "New geometry") + dtutils.dtShowWarning(self.iface, newMsg + ": " + noMatchWarning) + + selFeat.setGeometry(newGeom) + processLayer.updateFeature(selFeat) + featuresBeingCut += 1 + + if len(fidsToDelete) > 0: + for fid in fidsToDelete: + if not processLayer.deleteFeature(fid): + processLayer.destroyEditCommand() + return None - passiveLayer.endEditCommand() + if featuresBeingCut > 0 or len(fidsToDelete) > 0: + processLayer.endEditCommand() + else: # nothing happened + processLayer.destroyEditCommand() - self.iface.mapCanvas().refresh() + processLayer.removeSelection() + self.iface.mapCanvas().refresh() diff --git a/tools/dtexchangegeometry.py b/tools/dtexchangegeometry.py index a93ecf3..c8fe224 100644 --- a/tools/dtexchangegeometry.py +++ b/tools/dtexchangegeometry.py @@ -18,7 +18,7 @@ (at your option) any later version. """ -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtGui from qgis.core import * import dtutils import dt_icons_rc @@ -26,10 +26,10 @@ class DtExchangeGeometry(DtSingleButton): def __init__(self, iface, toolBar): - DtSingleButton.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/exchangeGeometry.png"), QtCore.QCoreApplication.translate("digitizingtools", - "Exchange the geomteries between selected features"), + "Exchange attributes between selected features"), geometryTypes = [1, 2, 3, 4, 5, 6], dtName = "dtExchangeGeometry") self.enable() @@ -42,7 +42,7 @@ def process(self): feature2 = feats[1] geom2 = QgsGeometry(feature2.geometry()) layer.beginEditCommand(QtCore.QCoreApplication.translate( - "editcommand", "Exchange geometries")) + "editcommand", "Exchange attributes")) feature1.setGeometry(geom2) layer.updateFeature(feature1) feature2.setGeometry(geom1) diff --git a/tools/dtextractpart.py b/tools/dtextractpart.py index aefe549..9787a31 100644 --- a/tools/dtextractpart.py +++ b/tools/dtextractpart.py @@ -18,7 +18,7 @@ (at your option) any later version. """ -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtGui from qgis.core import * import dtutils import dt_icons_rc @@ -26,13 +26,13 @@ class DtExtractPartTool(DtSingleTool): def __init__(self, iface, toolBar): - DtSingleTool.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/ExtractPart.png"), QtCore.QCoreApplication.translate("digitizingtools", "Split off one part and add it as a new feature"), - geometryTypes = [4, 5, 6], dtName = "dtExtractPart") + geometryTypes = [1, 2, 3, 4, 5, 6], dtName = "dtExtractPart") - self.tool = DtSelectPartTool(self.canvas, self.iface) + self.tool = DtSelectPartTool(self.iface) self.tool.partSelected.connect(self.partSelected) self.enable() diff --git a/tools/dtfillgap.py b/tools/dtfillgap.py index 52d50fa..ee3f943 100644 --- a/tools/dtfillgap.py +++ b/tools/dtfillgap.py @@ -16,7 +16,7 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtGui from qgis.core import * from qgis.gui import * import dt_icons_rc @@ -26,7 +26,7 @@ class DtFillGap(DtDualToolSelectGap): '''Fill gaps between selected features of the active layer with new features''' def __init__(self, iface, toolBar): - DtDualToolSelectGap.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/fillGap.png"), QtCore.QCoreApplication.translate("digitizingtools", "Fill gap with a new feature (interactive mode)"), @@ -63,10 +63,9 @@ def process(self): rings = dtutils.dtExtractRings(multiGeom) if len(rings) == 0: - self.iface.messageBar().pushMessage(self.title, + self.iface.messageBar().pushWarning(self.title, QtCore.QCoreApplication.translate("digitizingtools", - "There are no gaps between the polygons."), - level=QgsMessageBar.WARNING, duration = 10) + "There are no gaps between the polygons.")) else: defaultAttributeMap = dtutils.dtGetDefaultAttributeMap(layer) layer.featureAdded.connect(self.featureAdded) @@ -105,13 +104,13 @@ def featureAdded(self, newFid): class DtFillGapAllLayers(DtSingleTool): '''Fill gaps between the polygons of all visible layers with new features''' def __init__(self, iface, toolBar): - DtSingleTool.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/fillGapAll.png"), QtCore.QCoreApplication.translate("digitizingtools", "Fill gap between polygons of all visible layers with a new feature"), geometryTypes = [3, 6], dtName = "dtFillGapAll") - self.tool = DtSelectGapTool(self.canvas, self.iface, True) + self.tool = DtSelectGapTool(self.iface, True) self.tool.gapSelected.connect(self.gapFound) self.enable() diff --git a/tools/dtfillring.py b/tools/dtfillring.py index e62ad0a..bd8fc89 100644 --- a/tools/dtfillring.py +++ b/tools/dtfillring.py @@ -16,7 +16,7 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtGui from qgis.core import * import dt_icons_rc import dtutils @@ -25,7 +25,7 @@ class DtFillRing(DtDualToolSelectRing): '''Fill selected ring/all rings in selected feature in active polygon layer''' def __init__(self, iface, toolBar): - DtDualToolSelectRing.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/fillRing.png"), QtCore.QCoreApplication.translate("digitizingtools", "Fill ring with a new feature (interactive mode)"), QtGui.QIcon(":/fillRingBatch.png"), diff --git a/tools/dtflipline.py b/tools/dtflipline.py index b42869b..0531a1f 100644 --- a/tools/dtflipline.py +++ b/tools/dtflipline.py @@ -16,7 +16,7 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtGui from qgis.core import * from qgis.gui import * import dt_icons_rc @@ -25,7 +25,7 @@ class DtFlipLine(DtDualToolSelectFeature): '''Flip line direction tool''' def __init__(self, iface, toolBar): - DtDualToolSelectFeature.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/flipLine.png"), QtCore.QCoreApplication.translate("digitizingtools", "Flip line (interactive mode)"), QtGui.QIcon(":/flipLineBatch.png"), @@ -70,16 +70,16 @@ def process(self): newNodes = [] if layer.wkbType() == 2 or layer.wkbType() == -2147483646: - newGeom = QgsGeometry.fromPolyline(newNodes) + newGeom = QgsGeometry.fromPolylineXY(newNodes) else: - newGeom = QgsGeometry.fromMultiPolyline(newNodes) + newGeom = QgsGeometry.fromMultiPolylineXY(newNodes) if not layer.changeGeometry(feat.id(), newGeom): hadError = True if hadError: - self.iface.messageBar().pushMessage(QtCore.QCoreApplication.translate("digitizingtools", - "An error occured during flipping", level=QgsMessageBar.CRITICAL)) + self.iface.messageBar().pushCritical(QtCore.QCoreApplication.translate("digitizingtools", + "An error occured during flipping")) layer.destroyEditCommand() else: layer.endEditCommand() diff --git a/tools/dtmedianline.py b/tools/dtmedianline.py index bb45b81..c2e8c61 100644 --- a/tools/dtmedianline.py +++ b/tools/dtmedianline.py @@ -16,13 +16,16 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -from PyQt4 import QtCore, QtGui + +from builtins import range +from builtins import object +from qgis.PyQt import QtCore, QtGui, QtWidgets from qgis.core import * from qgis.gui import * from dtmedianlinetool import DtMedianLineTool -class DtMedianLine(): +class DtMedianLine(object): '''Digitize median line by selecting vertices on adjacent polygons''' def __init__(self, iface, toolBar): @@ -39,7 +42,7 @@ def __init__(self, iface, toolBar): self.point_list = [] #create action - self.median_digitizer = QtGui.QAction(QtGui.QIcon(":/medianLine.png"), + self.median_digitizer = QtWidgets.QAction(QtGui.QIcon(":/medianLine.png"), QtCore.QCoreApplication.translate("digitizingtools", "Digitize median line between adjacent polygons"), self.iface.mainWindow()) @@ -63,15 +66,13 @@ def enableTool(self): self.tool.activate() self.canvas.setMapTool(self.tool) #Connect to the DtSelectVertexTool - QtCore.QObject.connect(self.tool, QtCore.SIGNAL( - "vertexFound(PyQt_PyObject)"), self.storePoints) + self.tool.vertexFound.connect(self.storePoints) def disableTool(self): self.reset() #self.tool.deactivate() self.canvas.unsetMapTool(self.tool) - QtCore.QObject.disconnect(self.tool, QtCore.SIGNAL( - "vertexFound(PyQt_PyObject)"), self.storePoints) + self.tool.vertexFound.disconnect(self.storePoints) self.tool.deactivate() def deactivate(self): @@ -90,7 +91,7 @@ def run(self): #self.lineLayer = layer self.median_digitizer.setChecked(True) else: - QtGui.QMessageBox.information(None, title, + QtWidgets.QMessageBox.information(None, title, QtCore.QCoreApplication.translate("digitizingtools", "Please clear selection.")) @@ -179,8 +180,7 @@ def enable(self): def getCadLayerByName(cadname): - layermap = QgsMapLayerRegistry.instance().mapLayers() - for name, layer in layermap.iteritems(): + for anId, layer in QgsProject.instance().mapLayers().items(): if layer.name() == cadname: if layer.isValid(): return layer @@ -215,7 +215,7 @@ def addGeometryToCadLayer(g): feat.setGeometry(g) pr.addFeatures([feat]) vl.updateExtents() - QgsMapLayerRegistry.instance().addMapLayer(vl, True) + QgsProject.instance().addMapLayer(vl) else: layer = getCadLayerByName(theName) pr = layer.dataProvider() diff --git a/tools/dtmedianlinetool.py b/tools/dtmedianlinetool.py index 85269e2..428e61e 100644 --- a/tools/dtmedianlinetool.py +++ b/tools/dtmedianlinetool.py @@ -20,23 +20,21 @@ (at your option) any later version. """ -from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from qgis.PyQt import QtCore, QtGui from qgis.core import * from qgis.gui import * class DtMedianLineTool(QgsMapTool): finishedDigitizing = QtCore.pyqtSignal() - vertexFound = QtCore.pyqtSignal() + vertexFound = QtCore.pyqtSignal(list) def __init__(self, parent): - QgsMapTool.__init__(self, parent.canvas) + super().__init__(parent.canvas) self.canvas = parent.canvas self.parent = parent self.markers = [] #custom cursor - self.cursor = QCursor(QPixmap(["16 16 3 1", + self.cursor = QtGui.QCursor(QtGui.QPixmap(["16 16 3 1", " c None", ". c #FF0000", "+ c #FFFFFF", @@ -76,15 +74,14 @@ def canvasReleaseEvent(self, event): if layer is not None: #the clicked point is our starting point - startingPoint = QPoint(x, y) + startingPoint = QtCore.QPoint(x, y) #we need a snapper, so we use the MapCanvas snappingUtils (new in 2.8.x) snapper = self.canvas.snappingUtils() snapper.setCurrentLayer(layer) #snapType, snapTolerance, snapUnits = snapper.defaultSettings() # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment - snapType = 1 - snapMatch = snapper.snapToCurrentLayer(startingPoint, snapType) + snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Vertex) #if we have found a vertex if snapMatch.isValid(): @@ -101,7 +98,7 @@ def canvasReleaseEvent(self, event): m.setPenWidth(3) m.setCenter(p) self.markers.append(m) - self.emit(SIGNAL("vertexFound(PyQt_PyObject)"), [p]) + self.vertexFound.emit([p]) else: pass diff --git a/tools/dtmerge.py b/tools/dtmerge.py index 3e2e445..5d497bc 100644 --- a/tools/dtmerge.py +++ b/tools/dtmerge.py @@ -18,16 +18,18 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -from PyQt4 import QtCore, QtGui + +from builtins import str +from qgis.PyQt import QtCore, QtGui, QtWidgets from qgis.core import * -from qgis.gui import QgsMessageBar import dt_icons_rc from dttools import DtSingleButton +from dtToolsDialog import DigitizingToolsChooseRemaining class DtMerge(DtSingleButton): '''Merge selected features of active layer''' def __init__(self, iface, toolBar): - DtSingleButton.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/Merge.png"), QtCore.QCoreApplication.translate("digitizingtools", "Merge selected features"), @@ -38,43 +40,66 @@ def process(self): '''Function that does all the real work''' title = QtCore.QCoreApplication.translate("digitizingtools", "Merge") processLayer = self.iface.activeLayer() - pkFld = processLayer.pendingPkAttributesList()[0] - pkValues = [] + pkAtts = processLayer.primaryKeyAttributes() + + if len(pkAtts) == 1: + pkFld = pkAtts[0] + else: + pkFld = None + + pkValues = {} featDict = {} fidsToDelete = [] for aFeat in processLayer.selectedFeatures(): - aPkValue = aFeat[pkFld] - pkValues.append(str(aPkValue)) - featDict[str(aPkValue)] = aFeat - - pkValueToKeep, ok = QtGui.QInputDialog.getItem( - None, title, QtCore.QCoreApplication.translate( - "digitizingtools", "Choose which feature should remain"), - pkValues) - - if ok: + aFid = aFeat.id() + featDict[aFid] = aFeat + + if aFid >= 0: # only already existing features + if pkFld == None: + pkValues["Feature ID " + str(aFid)] = aFid + else: + aPkValue = aFeat[pkFld] + pkValues[str(aPkValue)] = aFid + + if len(pkValues) > 1: + dlg = DigitizingToolsChooseRemaining(self.iface, processLayer, pkValues, featDict, title) + doContinue = dlg.exec_() + + if doContinue == 1: + pkValueToKeep = dlg.pkValueToKeep + elif len(pkValues) == 1: + doContinue = 1 + pkValueToKeep = list(pkValues.keys())[0] + else: # all new features + doContinue = 1 + pkValueToKeep = None + + if doContinue == 1: processLayer.beginEditCommand( QtCore.QCoreApplication.translate("editcommand", "Merge Features")) - outFeat = featDict.pop(pkValueToKeep) + if pkValueToKeep != None: + outFeat = featDict.pop(pkValues[pkValueToKeep]) + else: + outFeat = featDict.popitem()[1] # use any + outFid = outFeat.id() outGeom = QgsGeometry(outFeat.geometry()) - for aFeatVal in featDict.itervalues(): + for aFeatVal in list(featDict.values()): fidsToDelete.append(aFeatVal.id()) outGeom = outGeom.combine(QgsGeometry(aFeatVal.geometry())) if not self.geometryTypeMatchesLayer(processLayer, outGeom): - self.iface.messageBar().pushMessage("DigitizingTools", - QtGui.QApplication.translate("DigitizingTools", - "The geometry type of the result is not valid in this layer!"), - level=QgsMessageBar.CRITICAL, duration = 10) + self.iface.messageBar().pushCritical("DigitizingTools", + QtWidgets.QApplication.translate("DigitizingTools", + "The geometry type of the result is not valid in this layer!")) processLayer.destroyEditCommand() else: processLayer.removeSelection() - processLayer.changeGeometry(outFid, outGeom) + success = processLayer.changeGeometry(outFid, outGeom) for aFid in fidsToDelete: if not processLayer.deleteFeature(aFid): @@ -94,11 +119,7 @@ def enable(self): except: pass - doEnable = len(layer.pendingPkAttributesList()) == 1 - - if doEnable: - doEnable = layer.selectedFeatureCount() > 1 - + doEnable = layer.selectedFeatureCount() > 1 self.act.setEnabled(doEnable) layer.selectionChanged.connect(self.enable) diff --git a/tools/dtmovenodebyarea.py b/tools/dtmovenodebyarea.py index eed1d62..a9b0585 100644 --- a/tools/dtmovenodebyarea.py +++ b/tools/dtmovenodebyarea.py @@ -17,18 +17,15 @@ (at your option) any later version. """ -from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from builtins import object +from qgis.PyQt import QtCore, QtGui, QtWidgets from qgis.core import * import dt_icons_rc -import math from dttools import DtSelectVertexTool -from ui_dtmovenodebyarea import Ui_DtMoveNodeByArea from dtmovenodebyarea_dialog import DtMoveNodeByArea_Dialog -class DtMoveNodeByArea(): +class DtMoveNodeByArea(object): '''Automatically move polygon node (along a given side of polygon) in order to achieve a desired polygon area''' def __init__(self, iface, toolBar): # Save reference to the QGIS interface @@ -45,28 +42,28 @@ def __init__(self, iface, toolBar): self.selected_feature = None #create action - self.node_mover = QtGui.QAction(QtGui.QIcon(":/MovePolygonNodeByArea.png"), - QtCore.QCoreApplication.translate("digitizingtools", "Move polygon node (along a side) to achieve target area"), self.iface.mainWindow()) + self.node_mover = QtWidgets.QAction(QtGui.QIcon(":/MovePolygonNodeByArea.png"), + QtWidgets.QApplication.translate("digitizingtools", "Move polygon node (along a side) to achieve target area"), self.iface.mainWindow()) self.node_mover.triggered.connect(self.run) self.iface.currentLayerChanged.connect(self.enable) toolBar.addAction(self.node_mover) self.enable() - self.tool = DtSelectVertexTool(self.canvas, self.iface, 2) + self.tool = DtSelectVertexTool(self.iface, 2) def showDialog(self): - flags = Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMaximizeButtonHint # QgisGui.ModalDialogFlags + flags = QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMaximizeButtonHint # QgisGui.ModalDialogFlags self.gui = DtMoveNodeByArea_Dialog(self.iface.mainWindow(), flags) self.gui.initGui() self.gui.show() - QObject.connect(self.gui, SIGNAL("unsetTool()"), self.unsetTool) - QObject.connect(self.gui, SIGNAL("moveNode()"), self.moveNode) + self.gui.unsetTool.connect(self.unsetTool) + self.gui.moveNode.connect(self.moveNode) def enableVertexTool(self): self.canvas.setMapTool(self.tool) #Connect to the DtSelectVertexTool - QObject.connect(self.tool, SIGNAL("vertexFound(PyQt_PyObject)"), self.storeVertexPointsAndMarkers) + self.tool.vertexFound.connect(self.storeVertexPointsAndMarkers) def unsetTool(self): self.m1 = None @@ -79,14 +76,14 @@ def unsetTool(self): def run(self): '''Function that does all the real work''' layer = self.iface.activeLayer() - if(layer.dataProvider().geometryType() == 6): + if(layer.dataProvider().wkbType() == 6): self.multipolygon_detected = True - title = QtCore.QCoreApplication.translate("digitizingtools", "Move polygon node by area") + title = QtWidgets.QApplication.translate("digitizingtools", "Move polygon node by area") if layer.selectedFeatureCount() == 0: - QtGui.QMessageBox.information(None, title, QtCore.QCoreApplication.translate("digitizingtools", "Please select one polygon to edit.")) + QtWidgets.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", "Please select one polygon to edit.")) elif layer.selectedFeatureCount() > 1: - QtGui.QMessageBox.information(None, title, QtCore.QCoreApplication.translate("digitizingtools", "Please select only one polygon to edit.")) + QtWidgets.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", "Please select only one polygon to edit.")) else: #One selected feature self.selected_feature = layer.selectedFeatures()[0] @@ -107,7 +104,7 @@ def enable(self): self.node_mover.setEnabled(False) layer = self.iface.activeLayer() - if layer <> None: + if layer != None: #Only for vector layers. if layer.type() == QgsMapLayer.VectorLayer: # only for polygon layers @@ -134,33 +131,33 @@ def moveNode(self): pass if (new_a == -1.0): - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Target Area not valid.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Target Area not valid.")) return if self.p1 == None or self.p2 == None: - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Not enough vertices selected.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Not enough vertices selected.")) else: - interp1 = self.selected_feature.geometry().intersects(QgsGeometry.fromPoint(self.p1)) - interp2 = self.selected_feature.geometry().intersects(QgsGeometry.fromPoint(self.p2)) - touch_p1_p2 = self.selected_feature.geometry().touches(QgsGeometry.fromPolyline([self.p1, self.p2])) + interp1 = self.selected_feature.geometry().intersects(QgsGeometry.fromPointXY(self.p1)) + interp2 = self.selected_feature.geometry().intersects(QgsGeometry.fromPointXY(self.p2)) + touch_p1_p2 = self.selected_feature.geometry().touches(QgsGeometry.fromPolyline([QgsPoint(self.p1), QgsPoint(self.p2)])) if (interp1 and interp2): if (not touch_p1_p2): - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Selected vertices should be consecutive on the selected polygon.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Selected vertices should be consecutive on the selected polygon.")) else: new_geom = createNewGeometry(self.selected_feature.geometry(), self.p1, self.p2, new_a, self.multipolygon_detected) fid = self.selected_feature.id() layer = self.iface.activeLayer() - layer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Move Node By Area")) + layer.beginEditCommand(QtWidgets.QApplication.translate("editcommand", "Move Node By Area")) layer.changeGeometry(fid,new_geom) self.canvas.refresh() layer.endEditCommand() #wkt_tmp1 = self.selected_feature.geometry().exportToWkt() #wkt_tmp2 = new_geom.exportToWkt() #tmp_str = wkt_tmp1 + " Initial Area:" + str(self.selected_feature.geometry().area()) + " " + wkt_tmp2 + " Final Area:" + str(new_geom.area()) - #title = QtCore.QCoreApplication.translate("digitizingtools", "Move polygon node by area") - #QtGui.QMessageBox.information(None, title, QtCore.QCoreApplication.translate("digitizingtools", tmp_str)) + #title = QtWidgets.QApplication.translate("digitizingtools", "Move polygon node by area") + #QtGui.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", tmp_str)) else: - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Vertices not on the selected polygon.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Vertices not on the selected polygon.")) # p1 is the stable node (red) and p2 is the node to move (blue) def createNewGeometry(geom, p1, p2, new_area, multipolygon): @@ -213,14 +210,14 @@ def createNewGeometry(geom, p1, p2, new_area, multipolygon): (x2a,y2a, x2b,y2b)=move_vertex(x1,y1,x2,y2,x3,y3,area_diff) - p2a = QgsPoint(x2a,y2a) - p2b = QgsPoint(x2b,y2b) + p2a = QgsPointXY(x2a,y2a) + p2b = QgsPointXY(x2b,y2b) pointList[p2_indx] = p2a - geom1 = QgsGeometry.fromPolygon( [ pointList ] ) + geom1 = QgsGeometry.fromPolygonXY( [ pointList ] ) pointList[p2_indx] = p2b - geom2 = QgsGeometry.fromPolygon( [ pointList ] ) + geom2 = QgsGeometry.fromPolygonXY( [ pointList ] ) diff_geom1 = abs(geom1.area() - new_area) diff_geom2 = abs(geom2.area() - new_area) diff --git a/tools/dtmovenodebyarea_dialog.py b/tools/dtmovenodebyarea_dialog.py index 5440b9f..a99728b 100644 --- a/tools/dtmovenodebyarea_dialog.py +++ b/tools/dtmovenodebyarea_dialog.py @@ -17,31 +17,34 @@ (at your option) any later version. """ -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * +from builtins import str +from qgis.PyQt import QtCore, QtWidgets, uic +import os -from ui_dtmovenodebyarea import Ui_DtMoveNodeByArea +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'ui_dtmovenodebyarea.ui')) + +class DtMoveNodeByArea_Dialog(QtWidgets.QDialog, FORM_CLASS): + unsetTool = QtCore.pyqtSignal() + moveNode = QtCore.pyqtSignal() -class DtMoveNodeByArea_Dialog(QDialog, QObject, Ui_DtMoveNodeByArea): - def __init__(self, parent, flags): - QDialog.__init__(self, parent, flags) + super().__init__(parent, flags) self.setupUi(self) - + def initGui(self): pass - + def writeArea(self, area): self.area_label.setText(str(area)) self.targetArea.setText(str(area)) - - @pyqtSignature("on_buttonClose_clicked()") + + @QtCore.pyqtSlot() def on_buttonClose_clicked(self): - self.emit(SIGNAL("unsetTool()")) + self.unsetTool.emit() self.close() - - @pyqtSignature("on_moveButton_clicked()") + + @QtCore.pyqtSlot() def on_moveButton_clicked(self): - self.emit(SIGNAL("moveNode()")) + self.moveNode.emit() pass diff --git a/tools/dtmovesidebyarea.py b/tools/dtmovesidebyarea.py index d1377db..ff1e549 100644 --- a/tools/dtmovesidebyarea.py +++ b/tools/dtmovesidebyarea.py @@ -2,8 +2,6 @@ """ dtmovesidebyarea ```````````````` -""" -""" Part of DigitizingTools, a QGIS plugin that subsumes different tools neded during digitizing sessions @@ -17,19 +15,18 @@ (at your option) any later version. """ -from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from __future__ import print_function +from builtins import range +from builtins import object +from qgis.PyQt import QtCore, QtGui, QtWidgets from qgis.core import * from qgis.gui import * - import dt_icons_rc import math from dttools import DtSelectSegmentTool -from ui_dtmovesidebyarea import Ui_DtMoveSideByArea from dtmovesidebyarea_dialog import DtMoveSideByArea_Dialog -class DtMoveSideByArea(): +class DtMoveSideByArea(object): '''Parallel move polygon side in order to achieve a desired polygon area''' def __init__(self, iface, toolBar): # Save reference to the QGIS interface @@ -42,31 +39,31 @@ def __init__(self, iface, toolBar): # p1 is always the left point self.p1 = None self.p2 = None - self.rb1 = QgsRubberBand(self.canvas, False) + self.rb1 = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.selected_feature = None #create action - self.side_mover = QtGui.QAction(QtGui.QIcon(":/ParallelMovePolygonSideByArea.png"), - QtCore.QCoreApplication.translate("digitizingtools", "Parallel move of polygon side to target area"), self.iface.mainWindow()) + self.side_mover = QtWidgets.QAction(QtGui.QIcon(":/ParallelMovePolygonSideByArea.png"), + QtWidgets.QApplication.translate("digitizingtools", "Parallel move of polygon side to target area"), self.iface.mainWindow()) self.side_mover.triggered.connect(self.run) self.iface.currentLayerChanged.connect(self.enable) toolBar.addAction(self.side_mover) self.enable() - self.tool = DtSelectSegmentTool(self.canvas, self.iface) + self.tool = DtSelectSegmentTool(self.iface) def showDialog(self): - flags = Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMaximizeButtonHint # QgisGui.ModalDialogFlags + flags = QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMaximizeButtonHint # QgisGui.ModalDialogFlags self.gui = DtMoveSideByArea_Dialog(self.iface.mainWindow(), flags) self.gui.initGui() self.gui.show() - QObject.connect(self.gui, SIGNAL("unsetTool()"), self.unsetTool) - QObject.connect(self.gui, SIGNAL("moveSide()"), self.moveSide) + self.gui.unsetTool.connect(self.unsetTool) + self.gui.moveSide.connect(self.moveSide) def enableSegmentTool(self): self.canvas.setMapTool(self.tool) #Connect to the DtSelectVertexTool - QObject.connect(self.tool, SIGNAL("segmentFound(PyQt_PyObject)"), self.storeSegmentPoints) + self.tool.segmentFound.connect(self.storeSegmentPoints) def unsetTool(self): self.p1 = None @@ -77,14 +74,14 @@ def unsetTool(self): def run(self): '''Function that does all the real work''' layer = self.iface.activeLayer() - if(layer.dataProvider().geometryType() == 6): + if(layer.dataProvider().wkbType() == 6): self.multipolygon_detected = True - title = QtCore.QCoreApplication.translate("digitizingtools", "Move polygon side by area") + title = QtWidgets.QApplication.translate("digitizingtools", "Move polygon side by area") if layer.selectedFeatureCount() == 0: - QtGui.QMessageBox.information(None, title, QtCore.QCoreApplication.translate("digitizingtools", "Please select one polygon to edit.")) + QtWidgets.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", "Please select one polygon to edit.")) elif layer.selectedFeatureCount() > 1: - QtGui.QMessageBox.information(None, title, QtCore.QCoreApplication.translate("digitizingtools", "Please select only one polygon to edit.")) + QtWidgets.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", "Please select only one polygon to edit.")) else: #One selected feature self.selected_feature = layer.selectedFeatures()[0] @@ -109,7 +106,7 @@ def enable(self): self.side_mover.setEnabled(False) layer = self.iface.activeLayer() - if layer <> None: + if layer != None: #Only for vector layers. if layer.type() == QgsMapLayer.VectorLayer: # only for polygon layers @@ -135,15 +132,15 @@ def moveSide(self): pass if (new_a == -1.0): - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Target Area not valid.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Target Area not valid.")) return if self.p1 == None or self.p2 == None: - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Polygon side not selected.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Polygon side not selected.")) else: - touch_p1_p2 = self.selected_feature.geometry().touches(QgsGeometry.fromPolyline([self.p1, self.p2])) + touch_p1_p2 = self.selected_feature.geometry().touches(QgsGeometry.fromPolyline([QgsPoint(self.p1), QgsPoint(self.p2)])) if (not touch_p1_p2): - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Selected segment should be on the selected polygon.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Selected segment should be on the selected polygon.")) else: #Select tool to create new geometry here if self.gui.method == "fixed": @@ -154,7 +151,7 @@ def moveSide(self): #Store new geometry on the memory buffer fid = self.selected_feature.id() layer = self.iface.activeLayer() - layer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Move Side By Area")) + layer.beginEditCommand(QtWidgets.QApplication.translate("editcommand", "Move Side By Area")) layer.changeGeometry(fid,new_geom) self.canvas.refresh() layer.endEditCommand() @@ -223,8 +220,8 @@ def moveFixed(geom, p1, p2, new_area, multipolygon): geom_mid = getParallelGeomByDistance(geom, p1, p2, p1_indx, p2_indx, dist_mid, multipolygon) area_mid = geom_mid.area() if ((math.fabs(area_mid-new_area)) < EPSILON): - print "wanted area reached" - print area_mid + print ("wanted area reached") + print (area_mid) break elif (area_mid < new_area): dist_start = dist_mid @@ -245,7 +242,7 @@ def getParallelGeomByDistance(geom, p1, p2, p1_indx, p2_indx, dist, multipolygon (p3, p4) = getParallelLinePointsByDistance(p1, p2, dist) pointList[p1_indx] = p3 pointList[p2_indx] = p4 - new_geom = QgsGeometry.fromPolygon( [ pointList ] ) + new_geom = QgsGeometry.fromPolygonXY( [ pointList ] ) return new_geom @@ -260,11 +257,11 @@ def getParallelLinePointsByDistance(p1, p2, dist): dn = ( (p1.x()-p2.x())**2 + (p1.y()-p2.y())**2 )**0.5 x3 = p1.x() + dist*(p1.y()-p2.y()) / dn y3 = p1.y() - dist*(p1.x()-p2.x()) / dn - p3 = QgsPoint(x3, y3) + p3 = QgsPointXY(x3, y3) x4 = p2.x() + dist*(p1.y()-p2.y()) / dn y4 = p2.y() - dist*(p1.x()-p2.x()) / dn - p4 = QgsPoint(x4, y4) + p4 = QgsPointXY(x4, y4) g = (p3,p4) return g @@ -329,12 +326,12 @@ def moveVariable(geom, p1, p2, new_area, multipolygon): (x5,y5,x6,y6) = move_vertex_trapezoid(x1,y1,x2,y2,x3,y3,x4,y4,area_diff) - p5 = QgsPoint(x5,y5) - p6 = QgsPoint(x6,y6) + p5 = QgsPointXY(x5,y5) + p6 = QgsPointXY(x6,y6) pointList[p1_indx] = p5 pointList[p2_indx] = p6 - new_geom = QgsGeometry.fromPolygon( [ pointList ] ) + new_geom = QgsGeometry.fromPolygonXY( [ pointList ] ) return new_geom diff --git a/tools/dtmovesidebyarea_dialog.py b/tools/dtmovesidebyarea_dialog.py index ebc9c19..47d7636 100644 --- a/tools/dtmovesidebyarea_dialog.py +++ b/tools/dtmovesidebyarea_dialog.py @@ -17,42 +17,45 @@ (at your option) any later version. """ -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * +from builtins import str +from qgis.PyQt import QtCore, QtWidgets, uic +import os -from ui_dtmovesidebyarea import Ui_DtMoveSideByArea +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'ui_dtmovesidebyarea.ui')) + +class DtMoveSideByArea_Dialog(QtWidgets.QDialog, FORM_CLASS): + unsetTool = QtCore.pyqtSignal() + moveSide = QtCore.pyqtSignal() -class DtMoveSideByArea_Dialog(QDialog, QObject, Ui_DtMoveSideByArea): - def __init__(self, parent, flags): - QDialog.__init__(self, parent, flags) + super().__init__(parent, flags) self.setupUi(self) self.method = "fixed" - + def initGui(self): self.radioFixed.setChecked(True) self.radioVariable.setChecked(False) pass - + def writeArea(self, area): self.area_label.setText(str(area)) self.targetArea.setText(str(area)) - - @pyqtSignature("on_radioFixed_clicked()") - def on_radioFixed_clicked(self): + + @QtCore.pyqtSlot() + def on_radioFixed_clicked(self): self.method = "fixed" - @pyqtSignature("on_radioVariable_clicked()") - def on_radioVariable_clicked(self): + @QtCore.pyqtSlot() + def on_radioVariable_clicked(self): self.method = "variable" - - @pyqtSignature("on_buttonClose_clicked()") + + @QtCore.pyqtSlot() def on_buttonClose_clicked(self): - self.emit(SIGNAL("unsetTool()")) + self.unsetTool.emit() self.close() - - @pyqtSignature("on_moveButton_clicked()") + + @QtCore.pyqtSlot() def on_moveButton_clicked(self): - self.emit(SIGNAL("moveSide()")) + self.moveSide.emit() pass diff --git a/tools/dtmovesidebydistance.py b/tools/dtmovesidebydistance.py index 08158ce..38f10c8 100644 --- a/tools/dtmovesidebydistance.py +++ b/tools/dtmovesidebydistance.py @@ -17,18 +17,16 @@ (at your option) any later version. """ -from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from builtins import object +from qgis.PyQt import QtCore, QtGui, QtWidgets from qgis.core import * from qgis.gui import * import dt_icons_rc from dttools import DtSelectSegmentTool -from ui_dtmovesidebydistance import Ui_DtMoveSideByDistance from dtmovesidebydistance_dialog import DtMoveSideByDistance_Dialog -class DtMoveSideByDistance(): +class DtMoveSideByDistance(object): '''Automatically move polygon node (along a given side of polygon) in order to achieve a desired polygon area''' def __init__(self, iface, toolBar): # Save reference to the QGIS interface @@ -41,33 +39,33 @@ def __init__(self, iface, toolBar): # p1 is always the left point self.p1 = None self.p2 = None - self.rb1 = QgsRubberBand(self.canvas, False) + self.rb1 = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) #self.m1 = None self.selected_feature = None #create action - self.side_mover = QtGui.QAction(QtGui.QIcon(":/ParallelMovePolygonSideByDistance.png"), - QtCore.QCoreApplication.translate("digitizingtools", "Parallel move of polygon side to given distance"), self.iface.mainWindow()) + self.side_mover = QtWidgets.QAction(QtGui.QIcon(":/ParallelMovePolygonSideByDistance.png"), + QtWidgets.QApplication.translate("digitizingtools", "Parallel move of polygon side to given distance"), self.iface.mainWindow()) self.side_mover.triggered.connect(self.run) self.iface.currentLayerChanged.connect(self.enable) toolBar.addAction(self.side_mover) self.enable() - self.tool = DtSelectSegmentTool(self.canvas, self.iface) + self.tool = DtSelectSegmentTool(self.iface) def showDialog(self): - flags = Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMaximizeButtonHint # QgisGui.ModalDialogFlags + flags = QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMaximizeButtonHint # QgisGui.ModalDialogFlags self.gui = DtMoveSideByDistance_Dialog(self.iface.mainWindow(), flags) self.gui.initGui() self.gui.show() - QObject.connect(self.gui, SIGNAL("unsetTool()"), self.unsetTool) - QObject.connect(self.gui, SIGNAL("moveSide()"), self.moveSide) + self.gui.unsetTool.connect(self.unsetTool) + self.gui.moveSide.connect(self.moveSide) def enableSegmentTool(self): self.canvas.setMapTool(self.tool) #Connect to the DtSelectVertexTool - QObject.connect(self.tool, SIGNAL("segmentFound(PyQt_PyObject)"), self.storeSegmentPoints) + self.tool.segmentFound.connect(self.storeSegmentPoints) def unsetTool(self): self.p1 = None @@ -78,14 +76,14 @@ def unsetTool(self): def run(self): '''Function that does all the real work''' layer = self.iface.activeLayer() - if(layer.dataProvider().geometryType() == 6): + if(layer.dataProvider().wkbType() == 6): self.multipolygon_detected = True - title = QtCore.QCoreApplication.translate("digitizingtools", "Move polygon side by distance") + title = QtWidgets.QApplication.translate("digitizingtools", "Move polygon side by distance") if layer.selectedFeatureCount() == 0: - QtGui.QMessageBox.information(None, title, QtCore.QCoreApplication.translate("digitizingtools", "Please select one polygon to edit.")) + QtWidgets.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", "Please select one polygon to edit.")) elif layer.selectedFeatureCount() > 1: - QtGui.QMessageBox.information(None, title, QtCore.QCoreApplication.translate("digitizingtools", "Please select only one polygon to edit.")) + QtWidgets.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", "Please select only one polygon to edit.")) else: #One selected feature self.selected_feature = layer.selectedFeatures()[0] @@ -109,7 +107,7 @@ def enable(self): self.side_mover.setEnabled(False) layer = self.iface.activeLayer() - if layer <> None: + if layer != None: #Only for vector layers. if layer.type() == QgsMapLayer.VectorLayer: # only for polygon layers @@ -135,20 +133,20 @@ def moveSide(self): pass if (dist == 0.0): - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Target Distance not valid.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Target Distance not valid.")) return if self.p1 == None or self.p2 == None: - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Polygon side not selected.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Polygon side not selected.")) else: - touch_p1_p2 = self.selected_feature.geometry().touches(QgsGeometry.fromPolyline([self.p1, self.p2])) + touch_p1_p2 = self.selected_feature.geometry().touches(QgsGeometry.fromPolyline([QgsPoint(self.p1), QgsPoint(self.p2)])) if (not touch_p1_p2): - QMessageBox.information(None, QCoreApplication.translate("digitizingtools", "Cancel"), QCoreApplication.translate("digitizingtools", "Selected segment should be on the selected polygon.")) + QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Selected segment should be on the selected polygon.")) else: new_geom = createNewGeometry(self.selected_feature.geometry(), self.p1, self.p2, dist, self.multipolygon_detected) fid = self.selected_feature.id() layer = self.iface.activeLayer() - layer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Move Side By Distance")) + layer.beginEditCommand(QtWidgets.QApplication.translate("editcommand", "Move Side By Distance")) layer.changeGeometry(fid,new_geom) self.canvas.refresh() layer.endEditCommand() @@ -181,7 +179,7 @@ def createNewGeometry(geom, p1, p2, new_distance, multipolygon): pointList[p1_indx] = p3 pointList[p2_indx] = p4 - new_geom = QgsGeometry.fromPolygon( [ pointList ] ) + new_geom = QgsGeometry.fromPolygonXY( [ pointList ] ) return new_geom @@ -196,11 +194,11 @@ def getParallelLinePoints(p1, p2, dist): dn = ( (p1.x()-p2.x())**2 + (p1.y()-p2.y())**2 )**0.5 x3 = p1.x() + dist*(p1.y()-p2.y()) / dn y3 = p1.y() - dist*(p1.x()-p2.x()) / dn - p3 = QgsPoint(x3, y3) + p3 = QgsPointXY(x3, y3) x4 = p2.x() + dist*(p1.y()-p2.y()) / dn y4 = p2.y() - dist*(p1.x()-p2.x()) / dn - p4 = QgsPoint(x4, y4) + p4 = QgsPointXY(x4, y4) g = (p3,p4) return g diff --git a/tools/dtmovesidebydistance_dialog.py b/tools/dtmovesidebydistance_dialog.py index ab21a59..6b44971 100644 --- a/tools/dtmovesidebydistance_dialog.py +++ b/tools/dtmovesidebydistance_dialog.py @@ -17,27 +17,29 @@ (at your option) any later version. """ -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * +from qgis.PyQt import QtWidgets, QtCore, uic +import os -from ui_dtmovesidebydistance import Ui_DtMoveSideByDistance +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'ui_dtmovesidebydistance.ui')) + +class DtMoveSideByDistance_Dialog(QtWidgets.QDialog, FORM_CLASS): + unsetTool = QtCore.pyqtSignal() + moveSide = QtCore.pyqtSignal() -class DtMoveSideByDistance_Dialog(QDialog, QObject, Ui_DtMoveSideByDistance): - def __init__(self, parent, flags): - QDialog.__init__(self, parent, flags) + super().__init__(parent, flags) self.setupUi(self) - + def initGui(self): pass - - @pyqtSignature("on_buttonClose_clicked()") + + @QtCore.pyqtSlot() def on_buttonClose_clicked(self): - self.emit(SIGNAL("unsetTool()")) + self.unsetTool.emit() self.close() - - @pyqtSignature("on_moveButton_clicked()") + + @QtCore.pyqtSlot() def on_moveButton_clicked(self): - self.emit(SIGNAL("moveSide()")) + self.moveSide.emit() pass diff --git a/tools/dtprolongline.py b/tools/dtprolongline.py deleted file mode 100644 index 7117eca..0000000 --- a/tools/dtprolongline.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -""" -dtprolongline -````````````` -""" -""" -Part of DigitizingTools, a QGIS plugin that -subsumes different tools neded during digitizing sessions - -* begin : 2013-02-25 -* copyright : (C) 2013 by Bernhard Ströbl -* email : bernhard.stroebl@jena.de - -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. -""" -from PyQt4 import QtCore, QtGui -from qgis.core import * -from qgis.gui import * -import dt_icons_rc -from dtprolonglinetool import DtProlongLineTool -from dttools import DtSingleEditTool - -class DtProlongLine(DtSingleEditTool): - '''Prolong an existing line''' - def __init__(self, iface, toolBar): - DtSingleEditTool.__init__(self, iface, toolBar, - QtGui.QIcon(":/prolongline.png"), - QtCore.QCoreApplication.translate("digitizingtools", "Amend Line"), - geometryTypes = [2, 5], dtName = "dtProlongLine") - - self.tool = DtProlongLineTool(self.canvas, self.iface) - self.tool.startedDigitizing.connect(self.digitizingStarted) - self.tool.finishedDigitizing.connect(self.digitizingFinished) - self.tool.stoppedDigitizing.connect(self.digitizingStopped) - self.reset() - self.enable() - - def reset(self): - self.editLayer = None - self.lineFeature = None - self.rubberBand = None - - def digitizingStarted(self, layer, feature, startPoint, rubberBand): - self.editLayer = layer - self.lineFeature = feature - - def digitizingFinished(self, digitizedGeom): - lineGeom = QgsGeometry(self.lineFeature.geometry()) - newGeom = lineGeom.combine(digitizedGeom) - # step 5 change geometry in layer - self.editLayer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Prolong Line")) - self.lineFeature.setGeometry(newGeom) - self.editLayer.updateFeature(self.lineFeature) - self.editLayer.endEditCommand() - self.reset() - - def digitizingStopped(self): - self.reset() - - def process(self): - self.canvas.setMapTool(self.tool) - self.act.setChecked(True) - - diff --git a/tools/dtprolonglinetool.py b/tools/dtprolonglinetool.py deleted file mode 100644 index 4215f5b..0000000 --- a/tools/dtprolonglinetool.py +++ /dev/null @@ -1,162 +0,0 @@ -# -*- coding: utf-8 -*- -""" -dtprolonglinetool -````````````` -""" -""" -Part of DigitizingTools, a QGIS plugin that -subsumes different tools neded during digitizing sessions -some code adopted/adapted from: - 'CadTools Plugin', Copyright (C) Stefan Ziegler - -* begin : 2013-02-25 -* copyright : (C) 2013 by Bernhard Ströbl -* email : bernhard.stroebl@jena.de - -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. -""" -from PyQt4 import QtCore, QtGui -from qgis.core import * -from qgis.gui import * -import dtutils -from dttools import DtMapTool - -class DtProlongLineTool(DtMapTool): - startedDigitizing = QtCore.pyqtSignal(QgsVectorLayer, QgsFeature, QgsPoint, QgsRubberBand) - finishedDigitizing = QtCore.pyqtSignal(QgsGeometry) - stoppedDigitizing = QtCore.pyqtSignal() - - def __init__(self, canvas, iface): - DtMapTool.__init__(self, canvas, iface) - self.iface = iface - self.marker = None - self.rubberBand = None - settings = QtCore.QSettings() - settings.beginGroup("Qgis/digitizing") - a = settings.value("line_color_alpha",200,type=int) - b = settings.value("line_color_blue",0,type=int) - g = settings.value("line_color_green",0,type=int) - r = settings.value("line_color_red",255,type=int) - lw = settings.value("line_width",1,type=int) - settings.endGroup() - self.rubberBandColor = QtGui.QColor(r, g, b, a) - self.rubberBandWidth = lw - self.reset() - - def reset(self, emitSignal = False): - self.lineFeature = None - - if self.rubberBand != None: - self.rubberBand.reset() - self.canvas.scene().removeItem(self.rubberBand) - self.rubberBand = None - - if self.marker != None: - self.canvas.scene().removeItem(self.marker) - self.marker = None - # only emit signal if digitizing has already started - if emitSignal: - self.stoppedDigitizing.emit() - - def canvasMoveEvent(self, event): - # move the last point - if self.rubberBand != None: - x = event.pos().x() - y = event.pos().y() - thisPoint = QtCore.QPoint(x, y) - # try to snap - snapper = self.canvas.snappingUtils() - # snap to any layer within snap tolerance - snapMatch = snapper.snapToMap(thisPoint) - - if not snapMatch.isValid(): - mapToPixel = self.canvas.getCoordinateTransform() - self.rubberBand.movePoint(self.rubberBand.numberOfVertices() -1, - mapToPixel.toMapCoordinates(thisPoint)) - else: - self.rubberBand.movePoint(self.rubberBand.numberOfVertices() -1, - snapMatch.point()) - - def canvasReleaseEvent(self, event): - layer = self.canvas.currentLayer() - - if layer <> None: - #Get the click - x = event.pos().x() - y = event.pos().y() - thisPoint = QtCore.QPoint(x, y) - #QgsMapToPixel instance - mapToPixel = self.canvas.getCoordinateTransform() - - if event.button() == QtCore.Qt.LeftButton: - if self.lineFeature == None: - # step 1: snap to a start/end point of an existing line - #we snap to the current layer (we don't have exclude points and use the tolerances from the qgis properties) - snapper = self.canvas.snappingUtils() - snapper.setCurrentLayer(layer) - - # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment - snapType = 1 - snapMatch = snapper.snapToCurrentLayer(thisPoint, snapType) - snappedVertex = snapMatch.point() - - if not snapMatch.isValid(): - dtutils.showSnapSettingsWarning(self.iface) - else: - snappedIdx = snapMatch.vertexIndex() - fid = snapMatch.featureId() - self.lineFeature = QgsFeature() - # get the snapped feature - featureFound = layer.getFeatures( - QgsFeatureRequest().setFilterFid(fid)).nextFeature(self.lineFeature) - - if featureFound: - #check if this is the start/end vertex of the line - if snappedIdx == 0 or \ - QgsGeometry(self.lineFeature.geometry()).vertexAt(snappedIdx + 1) == QgsPoint(0, 0): - - #mark the vertex - startPoint = QgsPoint() - startPoint.setX(snappedVertex.x()) - startPoint.setY(snappedVertex.y()) - self.marker = QgsVertexMarker(self.canvas) - self.marker.setIconType(1) - self.marker.setColor(QtGui.QColor(255,0,0)) - self.marker.setIconSize(12) - self.marker.setPenWidth (3) - self.marker.setCenter(startPoint) - # step 2: create a QgsRubberBand - self.rubberBand = QgsRubberBand(self.canvas) - self.rubberBand.setColor(self.rubberBandColor) - self.rubberBand.setWidth(self.rubberBandWidth) - self.rubberBand.addPoint(startPoint) - self.rubberBand.addPoint(startPoint) # second point to be moved - self.startedDigitizing.emit(layer, self.lineFeature, startPoint, self.rubberBand) - else: - self.lineFeature = None - else: - self.lineFeature = None - else: # step 3: have user digitize line - self.rubberBand.addPoint(mapToPixel.toMapCoordinates(thisPoint)) - else: # right click - if self.lineFeature != None: # step 4: end digitizing merge rubbber band and existing geometry - self.rubberBand.removeLastPoint() - - if self.rubberBand.numberOfVertices() > 1: - rbGeom = self.rubberBand.asGeometry() - self.finishedDigitizing.emit(rbGeom) - self.reset() - else: - self.reset(True) - - self.canvas.refresh() - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Escape: - self.reset(True ) - - def deactivate(self): - self.reset(True) diff --git a/tools/dtsplitfeature.py b/tools/dtsplitfeature.py new file mode 100644 index 0000000..a6a8da9 --- /dev/null +++ b/tools/dtsplitfeature.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +""" +dtsplitfeature +````````````` +""" +""" +Part of DigitizingTools, a QGIS plugin that +subsumes different tools neded during digitizing sessions + +* begin : 2017-06-12 +* copyright : (C) 2017 by Bernhard Ströbl +* email : bernhard.stroebl@jena.de + +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. +""" + +from builtins import str +from builtins import range +from qgis.PyQt import QtCore, QtGui, QtWidgets +from qgis.core import * +from qgis.gui import * +import dt_icons_rc +from dttools import DtSingleEditTool, DtSplitFeatureTool +import dtutils + +class DtSplitFeature(DtSingleEditTool): + '''Split feature''' + def __init__(self, iface, toolBar): + super().__init__(iface, toolBar, + QtGui.QIcon(":/splitfeature.png"), + QtCore.QCoreApplication.translate("digitizingtools", "Split Features"), + geometryTypes = [2, 3, 5, 6], crsWarning = False, dtName = "dtSplitFeature") + + self.tool = DtSplitFeatureTool(self.iface) + self.tool.finishedDigitizing.connect(self.digitizingFinished) + self.reset() + self.enable() + + def reset(self): + self.editLayer = None + self.feature = None + self.rubberBand = None + + def digitizingFinished(self, splitGeom): + title = QtCore.QCoreApplication.translate("digitizingtools", "Split Features") + hlColor, hlFillColor, hlBuffer, hlMinWidth = dtutils.dtGetHighlightSettings() + selIds = self.editLayer.selectedFeatureIds() + self.editLayer.removeSelection() + + if self.editLayer.crs().srsid() != QgsProject.instance().crs().srsid(): + splitGeom.transform(QgsCoordinateTransform( + QgsProject.instance().crs(), self.editLayer.crs(), + QgsProject.instance() + )) + splitterPList = dtutils.dtExtractPoints(splitGeom) + featuresToAdd = [] # store new features in this array + featuresToKeep = {} # store geoms that will stay with their id as key + featuresToSplit = {} + topoEditEnabled = QgsProject.instance().topologicalEditing() + topoTestPointsAll = [] # store all topoTestPoints for all parts + + for aFeat in self.editLayer.getFeatures(QgsFeatureRequest(splitGeom.boundingBox())): + anId = aFeat.id() + + # work either on selected or all features if no selection exists + if len(selIds) == 0 or selIds.count(anId) != 0: + aGeom = aFeat.geometry() + + if splitGeom.intersects(aGeom): + featuresToSplit[anId] = aFeat + + if len(featuresToSplit) > 0: + self.editLayer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Features split")) + + for anId, aFeat in list(featuresToSplit.items()): + aGeom = aFeat.geometry() + wasMultipart = aGeom.isMultipart() and len(aGeom.asGeometryCollection()) > 1 + splitResult = [] + geomsToSplit = [] + + if wasMultipart: + keepGeom = None + + for aPart in aGeom.asGeometryCollection(): + if splitGeom.intersects(aPart): + geomsToSplit.append(aPart) + else: + if keepGeom == None: + keepGeom = aPart + else: + keepGeom = keepGeom.combine(aPart) + else: + geomsToSplit.append(aGeom) + + for thisGeom in geomsToSplit: + try: + result, newGeometries, topoTestPoints = thisGeom.splitGeometry(splitterPList, topoEditEnabled) + except: + self.iface.messageBar().pushCritical(title, + dtutils.dtGetErrorMessage() + QtCore.QCoreApplication.translate( + "digitizingtools", "splitting of feature") + " " + str(aFeat.id())) + return None + + topoTestPointsAll.append(topoTestPoints) + + if result == 0: # success + if len(newGeometries) > 0: + splitResult = newGeometries + + if wasMultipart: + splitResult.append(thisGeom) + + if wasMultipart and len(splitResult) > 1: + takeThisOne = -1 + + while takeThisOne == -1: + for i in range(len(splitResult)): + aNewGeom = splitResult[i] + hl = QgsHighlight(self.iface.mapCanvas(), aNewGeom, self.editLayer) + hl.setColor(hlColor) + hl.setFillColor(hlFillColor) + hl.setBuffer(hlBuffer) + hl.setWidth(hlMinWidth) + hl.show() + answer = QtWidgets.QMessageBox.question( + None, QtCore.QCoreApplication.translate("digitizingtools", "Split Multipart Feature"), + QtCore.QCoreApplication.translate("digitizingtools", "Create new feature from this part?"), + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.NoToAll) + hl.hide() + hl = None + if answer == QtWidgets.QMessageBox.Yes: + takeThisOne = i + break + elif answer == QtWidgets.QMessageBox.NoToAll: + keepGeom = aGeom + newGeoms = [] + takeThisOne = -2 + break + elif answer == QtWidgets.QMessageBox.Cancel: + self.editLayer.destroyEditCommand() + return None + + if takeThisOne == -2: + break + elif takeThisOne >= 0: + newGeoms = [splitResult.pop(takeThisOne)] + + if len(splitResult) > 0: #should be + for aNewGeom in splitResult: + if keepGeom == None: + keepGeom = aNewGeom + else: + keepGeom = keepGeom.combine(aNewGeom) + else: # singlePart + keepGeom = thisGeom + newGeoms = newGeometries + + newFeatures = dtutils.dtMakeFeaturesFromGeometries(self.editLayer, aFeat, newGeoms) + featuresToAdd = featuresToAdd + newFeatures + + aFeat.setGeometry(keepGeom) + featuresToKeep[anId] = aFeat + + for anId, aFeat in list(featuresToKeep.items()): + aGeom = aFeat.geometry() + self.editLayer.updateFeature(aFeat) + + if len(featuresToAdd) > 0: + if self.editLayer.addFeatures(featuresToAdd): + + for topoTestPoint in topoTestPointsAll: + for pt in topoTestPoint: + self.editLayer.addTopologicalPoints(pt) + + self.editLayer.endEditCommand() + else: + self.editLayer.destroyEditCommand() + else: + self.editLayer.destroyEditCommand() + + if hasattr(self.editLayer, "selectByIds"): # since QGIS 2.16 + self.editLayer.selectByIds(selIds) + else: + self.editLayer.setSelectedFeatures(selIds) + + def process(self): + self.canvas.setMapTool(self.tool) + self.act.setChecked(True) + self.editLayer = self.iface.activeLayer() + + diff --git a/tools/dtsplitmultipart.py b/tools/dtsplitmultipart.py index 5a3d69c..615a55a 100644 --- a/tools/dtsplitmultipart.py +++ b/tools/dtsplitmultipart.py @@ -21,7 +21,7 @@ * * ***************************************************************************/ """ -from PyQt4 import QtCore, QtGui +from qgis.PyQt import QtCore, QtGui from qgis.core import * import dtutils import dt_icons_rc @@ -29,12 +29,12 @@ class DtSplitMultiPartTool(DtDualToolSelectFeature): def __init__(self, iface, toolBar): - DtDualToolSelectFeature.__init__(self, iface, toolBar, + super().__init__(iface, toolBar, QtGui.QIcon(":/MultiToSingle.png"), QtCore.QCoreApplication.translate("digitizingtools", "Split multi-part feature to single part (interactive mode)"), QtGui.QIcon(":/MultiToSingleBatch.png"), QtCore.QCoreApplication.translate("digitizingtools", "Split selected multi-part features to single part"), - geometryTypes = [4, 5, 6], dtName = "dtSplitMultiPart") + geometryTypes = [1, 2, 3, 4, 5, 6], dtName = "dtSplitMultiPart") def process(self): layer = self.iface.mapCanvas().currentLayer() @@ -69,5 +69,5 @@ def process(self): # add new features to layer if len(newFeatures) > 0: - layer.addFeatures(newFeatures, False) + layer.addFeatures(newFeatures) layer.endEditCommand() diff --git a/tools/dtsplitter.py b/tools/dtsplitter.py deleted file mode 100644 index 64324ed..0000000 --- a/tools/dtsplitter.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -""" -dtsplitter -````````````` -""" -""" -Part of DigitizingTools, a QGIS plugin that -subsumes different tools neded during digitizing sessions - -* begin : 2013-02-25 -* copyright : (C) 2013 by Bernhard Ströbl -* email : bernhard.stroebl@jena.de - -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. -""" - -from PyQt4 import QtCore, QtGui -from qgis.core import * -import dt_icons_rc -import dtutils -from dttools import DtSingleButton - -class DtSplitWithLine(DtSingleButton): - '''Split selected features in active editable layer with selected line from another layer''' - def __init__(self, iface, toolBar): - DtSingleButton.__init__(self, iface, toolBar, - QtGui.QIcon(":/splitter.png"), - QtCore.QCoreApplication.translate("digitizingtools", "Split selected features with selected line from another layer"), - geometryTypes = [2, 3, 5, 6], dtName = "dtSplitWithLine") - - self.enable() - - def process(self): - '''Function that does all the real work''' - title = QtCore.QCoreApplication.translate("digitizingtools", "Splitter") - splitterLayer = dtutils.dtChooseVectorLayer(self.iface, 1, msg = QtCore.QCoreApplication.translate("digitizingtools", "splitter layer")) - - if splitterLayer == None: - self.iface.messageBar().pushMessage(title, QtCore.QCoreApplication.translate("digitizingtools", "Please provide a line layer to split with.")) - else: - passiveLayer = self.iface.activeLayer() - msgLst = dtutils.dtGetNoSelMessage() - noSelMsg1 = msgLst[0] - - if splitterLayer.selectedFeatureCount() == 0: - self.iface.messageBar().pushMessage(title, noSelMsg1 + " " + splitterLayer.name()) - return None - elif splitterLayer.selectedFeatureCount() != 1: - numSelSplitMsg = dtutils.dtGetManySelMessage(splitterLayer) - self.iface.messageBar().pushMessage(title, numSelSplitMsg + \ - QtCore.QCoreApplication.translate("digitizingtools", " Please select only one feature to split with.")) - else: - if passiveLayer.selectedFeatureCount() == 0: - self.iface.messageBar().pushMessage(title, noSelMsg1 + " " + passiveLayer.name() + ".\n" + \ - QtCore.QCoreApplication.translate("digitizingtools", " Please select the features to be splitted.")) - return None - - # determine srs, we work in the project's srs - splitterCRSSrsid = splitterLayer.crs().srsid() - passiveCRSSrsid = passiveLayer.crs().srsid() - mc = self.iface.mapCanvas() - renderer = mc.mapRenderer() - projectCRSSrsid = renderer.destinationCrs().srsid() - passiveLayer.beginEditCommand(QtCore.QCoreApplication.translate("editcommand", "Split features")) - featuresBeingSplit = 0 - featuresToAdd = [] - - for feat in splitterLayer.selectedFeatures(): - splitterGeom = QgsGeometry(feat.geometry()) - - if not splitterGeom.isGeosValid(): - thisWarning = dtutils.dtGetInvalidGeomWarning(splitterLayer) - dtutils.dtShowWarning(self.iface, thisWarning) - continue - - if splitterCRSSrsid != projectCRSSrsid: - splitterGeom.transform(QgsCoordinateTransform(splitterCRSSrsid, projectCRSSrsid)) - - for selFeat in passiveLayer.selectedFeatures(): - selGeom = QgsGeometry(selFeat.geometry()) - - if not selGeom.isGeosValid(): - thisWarning = dtutils.dtGetInvalidGeomWarning(passiveLayer) - dtutils.dtShowWarning(self.iface, thisWarning) - continue - - if passiveCRSSrsid != projectCRSSrsid: - selGeom.transform(QgsCoordinateTransform(passiveCRSSrsid, projectCRSSrsid)) - - if splitterGeom.intersects(selGeom): # we have a candidate - splitterPList = dtutils.dtExtractPoints(splitterGeom) - - try: - result, newGeometries, topoTestPoints = selGeom.splitGeometry(splitterPList, False) #QgsProject.instance().topologicalEditing()) - except: - self.iface.messageBar().pushMessage(title, - dtutils.dtGetErrorMessage() + QtCore.QCoreApplication.translate("digitizingtools", "splitting of feature") + " " + str(selFeat.id()), - level=QgsMessageBar.CRITICAL) - return None - - if result == 0: - selFeat.setGeometry(selGeom) - passiveLayer.updateFeature(selFeat) - - if len(newGeometries) > 0: - featuresBeingSplit += 1 - newFeatures = dtutils.dtMakeFeaturesFromGeometries(passiveLayer, selFeat, newGeometries) - - for newFeat in newFeatures: - newGeom = QgsGeometry(newFeat.geometry()) - if passiveCRSSrsid != projectCRSSrsid: - newGeom.transform(QgsCoordinateTransform( projectCRSSrsid, passiveCRSSrsid)) - newFeat.setGeometry(newGeom) - - featuresToAdd.append(newFeat) - - if featuresBeingSplit > 0: - passiveLayer.addFeatures(featuresToAdd, False) - passiveLayer.endEditCommand() - passiveLayer.removeSelection() - else: - passiveLayer.destroyEditCommand() diff --git a/tools/dttools.py b/tools/dttools.py index 936c0eb..a536b7c 100644 --- a/tools/dttools.py +++ b/tools/dttools.py @@ -16,16 +16,42 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -from PyQt4 import QtGui, QtCore + +from builtins import range +from builtins import object +from qgis.PyQt import QtGui, QtCore, QtWidgets from qgis.core import * from qgis.gui import * import dtutils -class DtTool(): +class DtTool(object): '''Abstract class; parent for any Dt tool or button''' - def __init__(self, iface, geometryTypes): + def __init__(self, iface, geometryTypes, **kw): self.iface = iface self.canvas = self.iface.mapCanvas() + + #custom cursor + self.cursor = QtGui.QCursor(QtGui.QPixmap(["16 16 3 1", + " c None", + ". c #FF0000", + "+ c #FFFFFF", + " ", + " +.+ ", + " ++.++ ", + " +.....+ ", + " +. .+ ", + " +. . .+ ", + " +. . .+ ", + " ++. . .++", + " ... ...+... ...", + " ++. . .++", + " +. . .+ ", + " +. . .+ ", + " ++. .+ ", + " ++.....+ ", + " ++.++ ", + " +.+ "])) + self.geometryTypes = [] self.shapeFileGeometryTypes = [] @@ -34,17 +60,17 @@ def __init__(self, iface, geometryTypes): for aGeomType in geometryTypes: if aGeomType == 1: # wkbPoint self.geometryTypes.append(1) - self.shapeFileGeometryTypes.append(1) + self.shapeFileGeometryTypes.append(4) self.geometryTypes.append(-2147483647) #wkbPoint25D self.shapeFileGeometryTypes.append(-2147483647) elif aGeomType == 2: # wkbLineString self.geometryTypes.append(2) - self.shapeFileGeometryTypes.append(2) + self.shapeFileGeometryTypes.append(5) self.geometryTypes.append(-2147483646) #wkbLineString25D self.shapeFileGeometryTypes.append(-2147483646) elif aGeomType == 3: # wkbPolygon self.geometryTypes.append(3) - self.shapeFileGeometryTypes.append(3) + self.shapeFileGeometryTypes.append(6) self.geometryTypes.append(-2147483645) #wkbPolygon25D self.shapeFileGeometryTypes.append(-2147483645) elif aGeomType == 4: # wkbMultiPoint @@ -59,7 +85,7 @@ def __init__(self, iface, geometryTypes): self.shapeFileGeometryTypes.append(-2147483646) #wkbLineString25D elif aGeomType == 6: # wkbMultiPolygon self.geometryTypes.append(6) - self.shapeFileGeometryTypes.append(3) # wkbPolygon + self.shapeFileGeometryTypes.append(6) # wkbPolygon self.geometryTypes.append(-2147483642) #wkbMultiPolygon25D self.shapeFileGeometryTypes.append(-2147483645) #wkbPolygon25D @@ -81,7 +107,10 @@ def geometryTypeMatchesLayer(self, layer, geom): # does not distinguish between single and multi match = (layer.wkbType() == 1 and geom.wkbType() == 4) or \ (layer.wkbType() == 2 and geom.wkbType() == 5) or \ - (layer.wkbType() == 3 and geom.wkbType() == 6) + (layer.wkbType() == 3 and geom.wkbType() == 6) or \ + (layer.wkbType() == 4 and geom.wkbType() == 1) or \ + (layer.wkbType() == 5 and geom.wkbType() == 2) or \ + (layer.wkbType() == 6 and geom.wkbType() == 3) else: # are we trying a single into a multi layer? match = (layer.wkbType() == 4 and geom.wkbType() == 1) or \ @@ -108,9 +137,9 @@ class DtSingleButton(DtTool): geometryTypes [array:integer] 0=point, 1=line, 2=polygon''' def __init__(self, iface, toolBar, icon, tooltip, geometryTypes = [1, 2, 3], dtName = None): - DtTool.__init__(self, iface, geometryTypes) + super().__init__(iface, geometryTypes) - self.act = QtGui.QAction(icon, tooltip, self.iface.mainWindow()) + self.act = QtWidgets.QAction(icon, tooltip, self.iface.mainWindow()) self.act.triggered.connect(self.process) if dtName != None: @@ -129,7 +158,7 @@ def enable(self): self.act.setEnabled(False) layer = self.iface.activeLayer() - if layer <> None: + if layer != None: #Only for vector layers. if layer.type() == QgsMapLayer.VectorLayer: if self.allowedGeometry(layer): @@ -148,7 +177,7 @@ def enable(self): class DtSingleTool(DtSingleButton): '''Abstract class for a tool''' def __init__(self, iface, toolBar, icon, tooltip, geometryTypes = [0, 1, 2], crsWarning = True, dtName = None): - DtSingleButton.__init__(self, iface, toolBar, icon, tooltip, geometryTypes, dtName) + super().__init__(iface, toolBar, icon, tooltip, geometryTypes, dtName) self.tool = None self.act.setCheckable(True) self.canvas.mapToolSet.connect(self.toolChanged) @@ -170,7 +199,7 @@ def reset(self): class DtSingleEditTool(DtSingleTool): '''Abstract class for a tool for interactive editing''' def __init__(self, iface, toolBar, icon, tooltip, geometryTypes = [0, 1, 2], crsWarning = True, dtName = None): - DtSingleTool.__init__(self, iface, toolBar, icon, tooltip, geometryTypes, dtName) + super().__init__(iface, toolBar, icon, tooltip, geometryTypes, dtName) self.crsWarning = crsWarning self.editLayer = None @@ -183,7 +212,7 @@ def enable(self): doEnable = False layer = self.iface.activeLayer() - if layer <> None: + if layer != None: if layer.type() == 0: #Only for vector layers. if self.allowedGeometry(layer): doEnable = layer.isEditable() @@ -199,17 +228,18 @@ def enable(self): layer.editingStopped.connect(self.enable) if self.editLayer != None: # we have a current edit session, activeLayer may have changed or editing status of self.editLayer - try: - self.editLayer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected - except: - pass - try: - self.editLayer.editingStopped.disconnect(self.enable) # when it becomes active layer again - except: - pass - - self.tool.reset() - self.reset() + if self.editLayer != layer: + try: + self.editLayer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected + except: + pass + try: + self.editLayer.editingStopped.disconnect(self.enable) # when it becomes active layer again + except: + pass + + self.tool.reset() + self.reset() if not doEnable: self.deactivate() @@ -220,10 +250,9 @@ def enable(self): projectCRSSrsid = mapSet.destinationCrs().srsid() if layerCRSSrsid != projectCRSSrsid: - self.iface.messageBar().pushMessage("DigitizingTools", self.act.toolTip() + " " + - QtGui.QApplication.translate("DigitizingTools", - "is disabled because layer CRS and project CRS do not match!"), - level=QgsMessageBar.WARNING, duration = 10) + self.iface.messageBar().pushWarning("DigitizingTools", self.act.toolTip() + " " + + QtWidgets.QApplication.translate("DigitizingTools", + "is disabled because layer CRS and project CRS do not match!")) doEnable = False self.act.setEnabled(doEnable) @@ -237,31 +266,31 @@ class DtDualTool(DtTool): geometryTypes [array:integer] 0=point, 1=line, 2=polygon''' def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], dtName = None): - DtTool.__init__(self, iface, geometryTypes) + super().__init__(iface, geometryTypes) self.iface.currentLayerChanged.connect(self.enable) self.canvas.mapToolSet.connect(self.toolChanged) #create button - self.button = QtGui.QToolButton(toolBar) + self.button = QtWidgets.QToolButton(toolBar) self.button.clicked.connect(self.runSlot) self.button.toggled.connect(self.hasBeenToggled) #create menu - self.menu = QtGui.QMenu(toolBar) + self.menu = QtWidgets.QMenu(toolBar) if dtName != None: self.menu.setObjectName(dtName) self.menu.triggered.connect(self.menuTriggered) self.button.setMenu(self.menu) - self.button.setPopupMode(QtGui.QToolButton.MenuButtonPopup) + self.button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) # create actions - self.act = QtGui.QAction(icon, tooltip, self.iface.mainWindow()) + self.act = QtWidgets.QAction(icon, tooltip, self.iface.mainWindow()) if dtName != None: self.act.setObjectName(dtName + "Action") self.act.setToolTip(tooltip) - self.act_batch = QtGui.QAction(iconBatch, tooltipBatch, self.iface.mainWindow()) + self.act_batch = QtWidgets.QAction(iconBatch, tooltipBatch, self.iface.mainWindow()) if dtName != None: self.act_batch.setObjectName(dtName + "BatchAction") @@ -306,8 +335,9 @@ def hasBeenToggled(self, isChecked): raise NotImplementedError("Should have implemented hasBeenToggled") def deactivate(self): - if self.button.isChecked(): - self.button.toggle() + if self.button != None: + if self.button.isChecked(): + self.button.toggle() def runSlot(self, isChecked): if self.batchMode: @@ -327,7 +357,7 @@ def enable(self): self.button.setEnabled(False) layer = self.iface.activeLayer() - if layer <> None: + if layer != None: #Only for vector layers. if layer.type() == QgsMapLayer.VectorLayer: @@ -355,8 +385,8 @@ class DtDualToolSelectFeature(DtDualTool): '''Abstract class for a DtDualToo which uses the DtSelectFeatureTool for interactive mode''' def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], dtName = None): - DtDualTool.__init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName) - self.tool = DtSelectFeatureTool(self.canvas, self.iface) + super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName) + self.tool = DtSelectFeatureTool(iface) def featureSelectedSlot(self, fids): if len(fids) >0: @@ -375,13 +405,20 @@ def hasBeenToggled(self, isChecked): else: self.canvas.unsetMapTool(self.tool) +class DtDualToolSelectPolygon(DtDualToolSelectFeature): + '''Abstract class for a DtDualToo which uses the DtSelectFeatureTool for interactive mode''' + + def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [3, 6], dtName = None): + super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName) + self.tool = DtSelectPolygonTool(iface) + class DtDualToolSelectVertex(DtDualTool): '''Abstract class for a DtDualTool which uses the DtSelectVertexTool for interactive mode numVertices [integer] nnumber of vertices to be snapped until vertexFound signal is emitted''' def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], numVertices = 1, dtName = None): - DtDualTool.__init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName) - self.tool = DtSelectVertexTool(self.canvas, self.iface, numVertices) + super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName) + self.tool = DtSelectVertexTool(self.iface, numVertices) def hasBeenToggled(self, isChecked): try: @@ -406,9 +443,9 @@ class DtDualToolSelectRing(DtDualTool): def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], dtName = None): - DtDualTool.__init__(self, iface, toolBar, icon, tooltip, + super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName) - self.tool = DtSelectRingTool(self.canvas, self.iface) + self.tool = DtSelectRingTool(self.iface) def hasBeenToggled(self, isChecked): try: @@ -434,9 +471,9 @@ class DtDualToolSelectGap(DtDualTool): def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], dtName = None, allLayers = False): - DtDualTool.__init__(self, iface, toolBar, icon, tooltip, + super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName) - self.tool = DtSelectGapTool(self.canvas, self.iface, allLayers) + self.tool = DtSelectGapTool(self.iface, allLayers) def hasBeenToggled(self, isChecked): try: @@ -454,34 +491,10 @@ def hasBeenToggled(self, isChecked): def gapFound(self, selectGapResult): raise NotImplementedError("Should have implemented gapFound") -class DtMapTool(QgsMapTool, DtTool): - '''abstract subclass of QgsMapTool''' - def __init__(self, canvas, iface): - QgsMapTool.__init__(self, canvas) - DtTool.__init__(self, iface, []) - self.canvas = canvas - - #custom cursor - self.cursor = QtGui.QCursor(QtGui.QPixmap(["16 16 3 1", - " c None", - ". c #FF0000", - "+ c #FFFFFF", - " ", - " +.+ ", - " ++.++ ", - " +.....+ ", - " +. .+ ", - " +. . .+ ", - " +. . .+ ", - " ++. . .++", - " ... ...+... ...", - " ++. . .++", - " +. . .+ ", - " +. . .+ ", - " ++. .+ ", - " ++.....+ ", - " ++.++ ", - " +.+ "])) +class DtMapToolEdit(QgsMapToolEdit, DtTool): + '''abstract subclass of QgsMapToolEdit''' + def __init__(self, iface, **kw): + super().__init__(canvas = iface.mapCanvas(), iface = iface, geometryTypes = []) def activate(self): self.canvas.setCursor(self.cursor) @@ -492,20 +505,93 @@ def deactivate(self): def reset(self, emitSignal = False): pass - def isZoomTool(self): - return False + def transformed(self, thisLayer, thisQgsPoint): + layerCRSSrsid = thisLayer.crs().srsid() + projectCRSSrsid = QgsProject.instance().crs().srsid() - def isTransient(self): - return False - - def isEditTool(self): - return True + if layerCRSSrsid != projectCRSSrsid: + transQgsPoint = QgsGeometry.fromPointXY(thisQgsPoint) + transQgsPoint.transform(QgsCoordinateTransform( + QgsProject.instance().crs(), thisLayer.crs(), + QgsProject.instance())) + return transQgsPoint.asPoint() + else: + return thisQgsPoint -class DtSelectFeatureTool(DtMapTool): +class DtSelectFeatureTool(DtMapToolEdit): featureSelected = QtCore.pyqtSignal(list) - def __init__(self, canvas, iface): - DtMapTool.__init__(self, canvas, iface) + def __init__(self, iface): + super().__init__(iface) + self.currentHighlight = [None, None] # feature, highlightGraphic + self.ignoreFids = [] # featureids that schould be ignored when looking for a feature + + def highlightFeature(self, layer, feature): + '''highlight the feature if it has a geometry''' + geomType = layer.geometryType() + returnGeom = None + + if geomType <= 2: + if geomType == 0: + marker = QgsVertexMarker(self.iface.mapCanvas()) + marker.setIconType(3) # ICON_BOX + marker.setColor(self.rubberBandColor) + marker.setIconSize(12) + marker.setPenWidth (3) + marker.setCenter(feature.geometry().centroid().asPoint()) + returnGeom = marker + else: + settings = QtCore.QSettings() + settings.beginGroup("Qgis/digitizing") + a = settings.value("line_color_alpha",200,type=int) + b = settings.value("line_color_blue",0,type=int) + g = settings.value("line_color_green",0,type=int) + r = settings.value("line_color_red",255,type=int) + lw = settings.value("line_width",1,type=int) + settings.endGroup() + rubberBandColor = QtGui.QColor(r, g, b, a) + rubberBandWidth = lw + rubberBand = QgsRubberBand(self.iface.mapCanvas()) + rubberBand.setColor(rubberBandColor) + rubberBand.setWidth(rubberBandWidth) + rubberBand.setToGeometry(feature.geometry(), layer) + returnGeom = rubberBand + + self.currentHighlight = [feature, returnGeom] + return returnGeom + else: + return None + + def removeHighlight(self): + highlightGeom = self.currentHighlight[1] + + if highlightGeom != None: + self.iface.mapCanvas().scene().removeItem(highlightGeom) + + self.currentHighlight = [None, None] + + def highlightNext(self, layer, startingPoint): + if self.currentHighlight != [None, None]: + self.ignoreFids.append(self.currentHighlight[0].id()) + + # will return the first feature, if there is only one will return this feature + found = self.getFeatureForPoint(layer, startingPoint) + + if len(found) == 0: + self.removeHighlight() + return 0 + else: + aFeat = found[0] + numFeatures = found[1] + + if self.currentHighlight != [None, None]: + if aFeat.id() != self.currentHighlight[0].id(): + self.removeHighlight() + self.highlightFeature(layer, found[0]) + else: + self.highlightFeature(layer, found[0]) + + return numFeatures def getFeatureForPoint(self, layer, startingPoint, inRing = False): ''' @@ -516,7 +602,8 @@ def getFeatureForPoint(self, layer, startingPoint, inRing = False): if self.isPolygonLayer(layer): mapToPixel = self.canvas.getCoordinateTransform() - thisQgsPoint = mapToPixel.toMapCoordinates(startingPoint) + #thisQgsPoint = mapToPixel.toMapCoordinates(startingPoint) + thisQgsPoint = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint)) spatialIndex = dtutils.dtSpatialindex(layer) featureIds = spatialIndex.nearestNeighbor(thisQgsPoint, 0) # if we use 0 as neighborCount then only features that contain the point @@ -549,9 +636,8 @@ def getFeatureForPoint(self, layer, startingPoint, inRing = False): #we need a snapper, so we use the MapCanvas snapper snapper = self.canvas.snappingUtils() snapper.setCurrentLayer(layer) - snapType, snapTolerance, snapUnits = snapper.defaultSettings() - # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment - snapMatch = snapper.snapToCurrentLayer(startingPoint, snapType) + # snapType = 0: no snap, 1 = vertex, 2 vertex & segment, 3 = segment + snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.All) if not snapMatch.isValid(): dtutils.showSnapSettingsWarning(self.iface) @@ -578,7 +664,7 @@ def canvasReleaseEvent(self,event): layer = self.canvas.currentLayer() - if layer <> None: + if layer != None: #the clicked point is our starting point startingPoint = QtCore.QPoint(x,y) found = self.getFeatureForPoint(layer, startingPoint) @@ -586,17 +672,102 @@ def canvasReleaseEvent(self,event): if len(found) > 0: feat = found[0] layer.removeSelection() - layer.setSelectedFeatures([feat.id()]) + layer.select(feat.id()) self.featureSelected.emit([feat.id()]) +class DtSelectPolygonTool(DtSelectFeatureTool): + def __init__(self, iface): + super().__init__(iface) + + def getFeatureForPoint(self, layer, startingPoint): + ''' + return the feature this QPoint is in and the total amount of features + ''' + result = [] + mapToPixel = self.canvas.getCoordinateTransform() + #thisQgsPoint = mapToPixel.toMapCoordinates(startingPoint) + thisQgsPoint = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint)) + spatialIndex = dtutils.dtSpatialindex(layer) + featureIds = spatialIndex.nearestNeighbor(thisQgsPoint, 0) + # if we use 0 as neighborCount then only features that contain the point + # are included + foundFeatures = [] + + while True: + for fid in featureIds: + if self.ignoreFids.count(fid) == 0: + feat = dtutils.dtGetFeatureForId(layer, fid) + + if feat != None: + geom = QgsGeometry(feat.geometry()) + + if geom.contains(thisQgsPoint): + foundFeatures.append(feat) + + if len(foundFeatures) == 0: + if len(self.ignoreFids) == 0: #there is no feaure at this point + break #while + else: + self.ignoreFids.pop(0) # remove first and try again + elif len(foundFeatures) > 0: # return first feature + feat = foundFeatures[0] + result.append(feat) + result.append(len(featureIds)) + break #while + + return result + + + + def canvasReleaseEvent(self,event): + ''' + - if user clicks left and no feature is highlighted, highlight first feature + - if user clicks left and there is a highlighted feature use this feature as selected + - if user clicks right, highlight another feature + ''' + #Get the click + x = event.pos().x() + y = event.pos().y() + + layer = self.canvas.currentLayer() + + if layer != None: + startingPoint = QtCore.QPoint(x,y) + #the clicked point is our starting point + + if event.button() == QtCore.Qt.RightButton: # choose another feature + self.highlightNext(layer, startingPoint) + elif event.button() == QtCore.Qt.LeftButton: + if self.currentHighlight == [None, None]: # first click + numFeatures = self.highlightNext(layer, startingPoint) + else: # user accepts highlighted geometry + mapToPixel = self.canvas.getCoordinateTransform() + thisQgsPoint = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint)) + feat = self.currentHighlight[0] + + if feat.geometry().contains(thisQgsPoint): # is point in highlighted feature? + numFeatures = 1 + else: # mabe user clicked somewhere else + numFeatures = self.highlightNext(layer, startingPoint) + + if numFeatures == 1: + feat = self.currentHighlight[0] + self.removeHighlight() + layer.removeSelection() + layer.select(feat.id()) + self.featureSelected.emit([feat.id()]) + + def reset(self): + self.removeHighlight() + class DtSelectRingTool(DtSelectFeatureTool): ''' a map tool to select a ring in a polygon ''' ringSelected = QtCore.pyqtSignal(list) - def __init__(self, canvas, iface): - DtSelectFeatureTool.__init__(self, canvas, iface) + def __init__(self, iface): + super().__init__(iface) def canvasReleaseEvent(self,event): #Get the click @@ -605,7 +776,7 @@ def canvasReleaseEvent(self,event): layer = self.canvas.currentLayer() - if layer <> None: + if layer != None: #the clicked point is our starting point startingPoint = QtCore.QPoint(x,y) found = self.getFeatureForPoint(layer, startingPoint, inRing = True) @@ -617,7 +788,7 @@ def canvasReleaseEvent(self,event): def reset(self, emitSignal = False): pass -class DtSelectGapTool(DtMapTool): +class DtSelectGapTool(DtMapToolEdit): ''' a map tool to select a gap between polygons, if allLayers is True then the gap is searched between polygons of @@ -625,8 +796,8 @@ class DtSelectGapTool(DtMapTool): ''' gapSelected = QtCore.pyqtSignal(list) - def __init__(self, canvas, iface, allLayers): - DtMapTool.__init__(self, canvas, iface) + def __init__(self, iface, allLayers): + super().__init__(iface) self.allLayers = allLayers def canvasReleaseEvent(self,event): @@ -638,22 +809,19 @@ def canvasReleaseEvent(self,event): visibleLayers = [] if self.allLayers: - legendIface = self.iface.legendInterface() - - for aLayer in legendIface.layers(): + for aLayer in self.iface.layerTreeCanvasBridge().rootGroup().checkedLayers(): if 0 == aLayer.type(): - if legendIface.isLayerVisible(aLayer) and \ - self.isPolygonLayer(aLayer): + if self.isPolygonLayer(aLayer): visibleLayers.append(aLayer) else: - if layer <> None: + if layer != None: visibleLayers.append(layer) if len(visibleLayers) > 0: #the clicked point is our starting point startingPoint = QtCore.QPoint(x,y) mapToPixel = self.canvas.getCoordinateTransform() - thisQgsPoint = mapToPixel.toMapCoordinates(startingPoint) + thisQgsPoint = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint)) multiGeom = None for aLayer in visibleLayers: @@ -665,7 +833,7 @@ def canvasReleaseEvent(self,event): spatialIndex = dtutils.dtSpatialindex(aLayer) # get the 100 closest Features featureIds = spatialIndex.nearestNeighbor(thisQgsPoint, 100) - aLayer.setSelectedFeatures(featureIds) + aLayer.select(featureIds) multiGeom = dtutils.dtCombineSelectedPolygons(aLayer, self.iface, multiGeom) @@ -691,8 +859,8 @@ class DtSelectPartTool(DtSelectFeatureTool): '''signal sends featureId of clickedd feature, number of part selected and geometry of part''' partSelected = QtCore.pyqtSignal(list) - def __init__(self, canvas, iface): - DtSelectFeatureTool.__init__(self, canvas, iface) + def __init__(self, iface): + super().__init__(iface) def canvasReleaseEvent(self,event): #Get the click @@ -701,7 +869,7 @@ def canvasReleaseEvent(self,event): layer = self.canvas.currentLayer() - if layer <> None: + if layer != None: #the clicked point is our starting point startingPoint = QtCore.QPoint(x,y) found = self.getFeatureForPoint(layer, startingPoint) @@ -757,12 +925,12 @@ def canvasReleaseEvent(self,event): break -class DtSelectVertexTool(DtMapTool): +class DtSelectVertexTool(DtMapToolEdit): '''select and mark numVertices vertices in the active layer''' vertexFound = QtCore.pyqtSignal(list) - def __init__(self, canvas, iface, numVertices = 1): - DtMapTool.__init__(self, canvas, iface) + def __init__(self, iface, numVertices = 1): + super().__init__(iface) # desired number of marked vertex until signal self.numVertices = numVertices @@ -781,7 +949,7 @@ def canvasReleaseEvent(self,event): layer = self.canvas.currentLayer() - if layer <> None: + if layer != None: #the clicked point is our starting point startingPoint = QtCore.QPoint(x,y) @@ -790,8 +958,7 @@ def canvasReleaseEvent(self,event): snapper.setCurrentLayer(layer) # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment - snapType = 1 - snapMatch = snapper.snapToCurrentLayer(startingPoint, snapType) + snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Vertex) if not snapMatch.isValid(): #warn about missing snapping tolerance if appropriate @@ -829,12 +996,12 @@ def reset(self, emitSignal = False): self.fids = [] self.count = 0 -class DtSelectSegmentTool(DtMapTool): +class DtSelectSegmentTool(DtMapToolEdit): segmentFound = QtCore.pyqtSignal(list) - def __init__(self, canvas, iface): - DtMapTool.__init__(self, canvas, iface) - self.rb1 = QgsRubberBand(self.canvas, False) + def __init__(self, iface): + super().__init__(iface) + self.rb1 = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) def canvasReleaseEvent(self,event): #Get the click @@ -843,7 +1010,7 @@ def canvasReleaseEvent(self,event): layer = self.canvas.currentLayer() - if layer <> None: + if layer != None: #the clicked point is our starting point startingPoint = QtCore.QPoint(x,y) @@ -853,7 +1020,7 @@ def canvasReleaseEvent(self,event): # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment snapType = 2 - snapMatch = snapper.snapToCurrentLayer(startingPoint, snapType) + snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Edge) if not snapMatch.isValid(): #warn about missing snapping tolerance if appropriate @@ -875,3 +1042,233 @@ def canvasReleaseEvent(self,event): def reset(self, emitSignal = False): self.rb1.reset() + +class DtSplitFeatureTool(QgsMapToolAdvancedDigitizing, DtTool): + finishedDigitizing = QtCore.pyqtSignal(QgsGeometry) + + def __init__(self, iface): + super().__init__(canvas = iface.mapCanvas(), cadDockWidget = iface.cadDockWidget(), + iface = iface, geometryTypes = []) + self.marker = None + self.rubberBand = None + self.sketchRubberBand = self.createRubberBand() + self.sketchRubberBand.setLineStyle(QtCore.Qt.DotLine) + self.rbPoints = [] # array to store points in rubber band because + # api to access points does not work properly or I did not figure it out :) + self.currentMousePosition = None + self.snapPoint = None + self.reset() + + def activate(self): + super().activate() + self.canvas.setCursor(self.cursor) + self.canvas.installEventFilter(self) + self.snapPoint = None + self.rbPoints = [] + + def eventFilter(self, source, event): + ''' + we need an eventFilter here to filter out Backspace key presses + as otherwise the selected objects in the edit layer get deleted + if user hits Backspace + The eventFilter() function must return true if the event should be filtered, + (i.e. stopped); otherwise it must return false, see + http://doc.qt.io/qt-5/qobject.html#installEventFilter + ''' + + if event.type() == QtCore.QEvent.KeyPress: + if event.key() == QtCore.Qt.Key_Backspace: + if self.rubberBand != None: + if self.rubberBand.numberOfVertices() >= 2: # QgsRubberBand has always 2 vertices + if self.currentMousePosition != None: + self.removeLastPoint() + self.redrawSketchRubberBand([self.toMapCoordinates(self.currentMousePosition)]) + return True + else: + return False + else: + return False + + def eventToQPoint(self, event): + x = event.pos().x() + y = event.pos().y() + thisPoint = QtCore.QPoint(x, y) + return thisPoint + + def initRubberBand(self, firstPoint): + if self.rubberBand == None: + # create a QgsRubberBand + self.rubberBand = self.createRubberBand() + self.rubberBand.addPoint(firstPoint) + self.rbPoints.append(firstPoint) + + def removeLastPoint(self): + ''' remove the last point in self.rubberBand''' + if len (self.rbPoints) > 1: #first point will not be removed + self.rbPoints.pop() + #we recreate rubberBand because it contains doubles + self.rubberBand.reset() + + for aPoint in self.rbPoints: + self.rubberBand.addPoint(QgsPointXY(aPoint)) + + + def trySnap(self, event): + self.removeSnapMarker() + self.snapPoint = None + # try to snap + thisPoint = self.eventToQPoint(event) + snapper = self.canvas.snappingUtils() + # snap to any layer within snap tolerance + snapMatch = snapper.snapToMap(thisPoint) + + if not snapMatch.isValid(): + return False + else: + self.snapPoint = snapMatch.point() + self.markSnap(self.snapPoint) + return True + + def markSnap(self, thisPoint): + self.marker = QgsVertexMarker(self.canvas) + self.marker.setIconType(1) + self.marker.setColor(QtGui.QColor(255,0,0)) + self.marker.setIconSize(12) + self.marker.setPenWidth (3) + self.marker.setCenter(thisPoint) + + def removeSnapMarker(self): + if self.marker != None: + self.canvas.scene().removeItem(self.marker) + self.marker = None + + def clear(self): + if self.rubberBand != None: + self.rubberBand.reset() + self.canvas.scene().removeItem(self.rubberBand) + self.rubberBand = None + + if self.snapPoint != None: + self.removeSnapMarker() + self.snapPoint = None + + self.sketchRubberBand.reset() + self.rbPoints = [] + + def reset(self): + self.clear() + self.canvas.removeEventFilter(self) + + def redrawSketchRubberBand(self, points): + if self.rubberBand != None and len(self.rbPoints) > 0: + self.sketchRubberBand.reset() + sketchStartPoint = self.rbPoints[len(self.rbPoints) -1] + self.sketchRubberBand.addPoint(QgsPointXY(sketchStartPoint)) + + if len(points) == 1: + self.sketchRubberBand.addPoint(QgsPointXY(sketchStartPoint)) + self.sketchRubberBand.movePoint( + self.sketchRubberBand.numberOfVertices() -1, points[0]) + #for p in range(self.rubberBand.size()): + # self.debug("Part " + str(p)) + # for v in range(self.rubberBand.partSize(p)): + # vertex = self.rubberBand.getPoint(0,j=v) + # self.debug("Vertex " + str(v) + " = "+ str(vertex.x()) + ", " + str(vertex.y())) + + + + #startPoint = self.rubberBand.getPoint(0, self.rubberBand.partSize(0) -1) + #self.debug("StartPoint " + str(startPoint)) + #self.sketchRubberBand.addPoint(startPoint) + #self.sketchRubberBand.addPoint(points[len(points) - 1]) + else: + for aPoint in points: + self.sketchRubberBand.addPoint(aPoint) + + def cadCanvasMoveEvent(self, event): + pass + #self.debug("cadCanvasMoveEvent") + + def cadCanvasPressEvent(self, event): + pass + #self.debug("cadCanvasPressEvent") + + def cadCanvasReleaseEvent(self, event): + pass + #self.debug("cadCanvasReleaseEvent") + + def canvasMoveEvent(self, event): + self.snapPoint = None + thisPoint = self.eventToQPoint(event) + hasSnap = self.trySnap(event) + + if self.rubberBand != None: + if hasSnap: + #if self.canvas.snappingUtils().config().enabled(): # is snapping active? + tracer = QgsMapCanvasTracer.tracerForCanvas(self.canvas) + + if tracer.actionEnableTracing().isChecked(): # tracing is pressed in + tracer.configure() + #startPoint = self.rubberBand.getPoint(0, self.rubberBand.numberOfVertices() -1) + startPoint = self.rbPoints[len(self.rbPoints) -1] + pathPoints, pathError = tracer.findShortestPath(QgsPointXY(startPoint), self.snapPoint) + + if pathError == 0: #ErrNone + pathPoints.pop(0) # remove first point as it is identical with starPoint + self.redrawSketchRubberBand(pathPoints) + else: + self.redrawSketchRubberBand([self.snapPoint]) + else: + self.redrawSketchRubberBand([self.snapPoint]) + else: + self.redrawSketchRubberBand([self.toMapCoordinates(thisPoint)]) + + self.currentMousePosition = thisPoint + + def canvasReleaseEvent(self, event): + layer = self.canvas.currentLayer() + + if layer != None: + thisPoint = self.eventToQPoint(event) + #QgsMapToPixel instance + + if event.button() == QtCore.Qt.LeftButton: + if self.rubberBand == None: + if self.snapPoint == None: + self.initRubberBand(self.toMapCoordinates(thisPoint)) + else: # last mouse move created a snap + self.initRubberBand(self.snapPoint) + self.snapPoint = None + self.removeSnapMarker() + else: # merge sketchRubberBand into rubberBand + sketchGeom = self.sketchRubberBand.asGeometry() + verticesSketchGeom = sketchGeom.vertices() + self.rubberBand.addGeometry(sketchGeom) + # rubberBand now contains a double point because it's former end point + # and sketchRubberBand's start point are identical + # so we remove the last point before adding new ones + self.rbPoints.pop() + + while verticesSketchGeom.hasNext(): + # add the new points + self.rbPoints.append(verticesSketchGeom.next()) + + self.redrawSketchRubberBand([self.toMapCoordinates(thisPoint)]) + + if self.snapPoint != None: + self.snapPoint = None + self.removeSnapMarker() + else: # right click + if self.rubberBand.numberOfVertices() > 1: + rbGeom = self.rubberBand.asGeometry() + self.finishedDigitizing.emit(rbGeom) + + self.clear() + self.canvas.refresh() + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Escape: + self.clear() + + def deactivate(self): + self.reset() diff --git a/tools/dtutils.py b/tools/dtutils.py index 26f69cd..3f45156 100644 --- a/tools/dtutils.py +++ b/tools/dtutils.py @@ -22,12 +22,14 @@ (at your option) any later version. """ -from PyQt4 import QtCore, QtGui +from builtins import range +from builtins import str +from qgis.PyQt import QtCore, QtGui, QtWidgets from qgis.core import * from qgis.gui import * def debug(msg): - QtGui.QMessageBox.information(None, "debug", unicode(msg)) + QtWidgets.QMessageBox.information(None, "debug", str(msg)) def dtGetFeatureForId(layer, fid): '''Function that returns the QgsFeature with FeatureId *fid* in QgsVectorLayer *layer*''' @@ -38,22 +40,6 @@ def dtGetFeatureForId(layer, fid): else: return None -def dtCreateFeature(layer): - '''Create an empty feature for the *layer*''' - if isinstance(layer, QgsVectorLayer): - newFeature = QgsFeature() - provider = layer.dataProvider() - fields = layer.pendingFields() - - newFeature.initAttributes(fields.count()) - - for i in range(fields.count()): - newFeature.setAttribute(i, provider.defaultValue(i)) - - return newFeature - else: - return None - def dtCopyFeature(layer, srcFeature = None, srcFid = None): '''Copy the QgsFeature with FeatureId *srcFid* in *layer* and return it. Alternatively the source Feature can be given as paramter. The feature is not added to the layer!''' @@ -61,17 +47,23 @@ def dtCopyFeature(layer, srcFeature = None, srcFid = None): srcFeature = dtGetFeatureForId(layer, srcFid) if srcFeature: - newFeature = dtCreateFeature(layer) - - #copy the attribute values# - pkFields = layer.dataProvider().pkAttributeIndexes() - fields = layer.pendingFields() - for i in range(fields.count()): - # do not copy the PK value if there is a PK field - if i in pkFields: - continue - else: - newFeature.setAttribute(i, srcFeature[i]) + #get layer type + layerType = layer.geometryType() + + if layerType == 0: + dummyGeomTxt = 'Point()' + elif layerType == 1: + dummyGeomTxt = 'LineString()' + elif layerType == 2: + dummyGeomTxt = 'Polygon()' + + #set dummy geom + dummyGeom = QgsGeometry.fromWkt(dummyGeomTxt) + + #copy the attribute values + attributes = {i: v for i, v in enumerate(srcFeature.attributes())} + + newFeature = QgsVectorLayerUtils.createFeature(layer, dummyGeom, attributes) return newFeature else: @@ -93,17 +85,17 @@ def dtGetVectorLayersByType(iface, geomType = None, skipActive = False): *geomType*; geomTypes are 0: point, 1: line, 2: polygon If *skipActive* is True the active Layer is not included.''' layerList = {} - for aLayer in iface.legendInterface().layers(): + for anId, aLayer in QgsProject.instance().mapLayers().items(): if 0 == aLayer.type(): # vectorLayer - if skipActive and (iface.mapCanvas().currentLayer().id() == aLayer.id()): + if skipActive and (iface.mapCanvas().currentLayer().id() == anId): continue else: if geomType: if isinstance(geomType, int): if aLayer.geometryType() == geomType: - layerList[aLayer.name()] = aLayer.id() + layerList[aLayer.name()] = [anId, aLayer] else: - layerList[aLayer.name()] = aLayer.id() + layerList[aLayer.name()] = [anId, aLayer] return layerList def dtChooseVectorLayer(iface, geomType = None, skipActive = True, msg = None): @@ -120,15 +112,12 @@ def dtChooseVectorLayer(iface, geomType = None, skipActive = True, msg = None if not msg: msg = "" - selectedLayer, ok = QtGui.QInputDialog.getItem(None, QtGui.QApplication.translate("dtutils", "Choose Layer"), - msg, chooseFrom, editable = False) + selectedLayer, ok = QtWidgets.QInputDialog.getItem(None, + QtWidgets.QApplication.translate("dtutils", "Choose Layer"), + msg, chooseFrom, editable = False) if ok: - for aLayer in iface.legendInterface().layers(): - if 0 == aLayer.type(): - if aLayer.id() == layerList[selectedLayer]: - retValue = aLayer - break + retValue = layerList[selectedLayer][1] return retValue @@ -151,6 +140,14 @@ def dtGetInvalidGeomWarning(layer): invalidGeomMsg += layer.name() return invalidGeomMsg +def dtGetNotMatchingGeomWarning(layer): + notMatchingGeomMsg = QtCore.QCoreApplication.translate( + "digitizingtools", "Geometry's type is not compatible with the following layer: ") + notMatchingGeomMsg += layer.name() + ". " + notMatchingGeomMsg += QtCore.QCoreApplication.translate( + "digitizingtools", "Fix geometries before commiting changes.") + return notMatchingGeomMsg + def showSnapSettingsWarning(iface): title = QtCore.QCoreApplication.translate("digitizingtools", "Snap Tolerance") msg1 = QtCore.QCoreApplication.translate( @@ -158,12 +155,10 @@ def showSnapSettingsWarning(iface): msg2 = QtCore.QCoreApplication.translate("digitizingtools", "Have you set the tolerance in Settings > Snapping Options?") - iface.messageBar().pushMessage(title, msg1 + " " + msg2, - level=QgsMessageBar.WARNING, duration = 10) + dtShowWarning(iface, msg1 + " " + msg2, title) -def dtShowWarning(iface, msg): - iface.messageBar().pushMessage(msg, - level=QgsMessageBar.WARNING, duration = 10) +def dtShowWarning(iface, msg, title = None): + iface.messageBar().pushWarning(title, msg) def dtGetErrorMessage(): '''Returns the default error message which can be appended''' @@ -209,12 +204,12 @@ def dtExtractRings(geom): for poly in multi_geom: if len(poly) > 1: for aRing in poly[1:]: - rings.append(QgsGeometry.fromPolygon([aRing])) + rings.append(QgsGeometry.fromPolygonXY([aRing])) else: poly = geom.asPolygon() if len(poly) > 1: for aRing in poly[1:]: - rings.append(QgsGeometry.fromPolygon([aRing])) + rings.append(QgsGeometry.fromPolygonXY([aRing])) return rings @@ -264,11 +259,11 @@ def dtSpatialindex(layer): """ idx = QgsSpatialIndex() for ft in layer.getFeatures(): - idx.insertFeature(ft) + idx.addFeature(ft) return idx def dtDeleteRings(poly): - outGeom = QgsGeometry.fromPolygon(poly) + outGeom = QgsGeometry.fromPolygonXY(poly) if len(poly) > 1: # we have rings @@ -282,7 +277,25 @@ def dtGetDefaultAttributeMap(layer): attributeMap = {} dp = layer.dataProvider() - for i in range(len(layer.pendingFields())): + for i in range(len(layer.fields())): attributeMap[i] = dp.defaultValue(i) return attributeMap + +def dtGetHighlightSettings(): + '''highlight a geom in a layer with highlight color from settings''' + s = QtCore.QSettings() + s.beginGroup("Map/highlight") + buffer = s.value("buffer", "0.5") + hexColor = s.value("color", "#ff0000") + colorAlpha = s.value("colorAlpha", "128") + minWidth = s.value("minWidth", "1") + s.endGroup() + color = QtGui.QColor() + color.setNamedColor(hexColor) + fillColor = QtGui.QColor() + r, g, b, a = color.getRgb() + fillColor.setRgb(r, g, b, int(colorAlpha)) + + return [color, fillColor, float(buffer), int(minWidth)] + diff --git a/tools/icons/clipper_batch.png b/tools/icons/clipper_batch.png new file mode 100644 index 0000000..06ec015 Binary files /dev/null and b/tools/icons/clipper_batch.png differ diff --git a/tools/icons/clipper_batch.svg b/tools/icons/clipper_batch.svg new file mode 100644 index 0000000..20c917a --- /dev/null +++ b/tools/icons/clipper_batch.svg @@ -0,0 +1,224 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/tools/icons/cutter_batch.png b/tools/icons/cutter_batch.png new file mode 100644 index 0000000..e576797 Binary files /dev/null and b/tools/icons/cutter_batch.png differ diff --git a/tools/icons/cutter_batch.svg b/tools/icons/cutter_batch.svg new file mode 100644 index 0000000..16fdd00 --- /dev/null +++ b/tools/icons/cutter_batch.svg @@ -0,0 +1,199 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/tools/icons/prolongline.png b/tools/icons/prolongline.png deleted file mode 100644 index 8fa4046..0000000 Binary files a/tools/icons/prolongline.png and /dev/null differ diff --git a/tools/icons/prolongline.svg b/tools/icons/prolongline.svg deleted file mode 100644 index 53d0354..0000000 --- a/tools/icons/prolongline.svg +++ /dev/null @@ -1,724 +0,0 @@ - - - - - polygon split - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - polygon split - 2011-04-20 - - - Robert Szczepanek, Anita Graser - - - - - - - - - - polygon split - - - GIS icons 0.2 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/icons/splitfeature.png b/tools/icons/splitfeature.png new file mode 100644 index 0000000..ee52a30 Binary files /dev/null and b/tools/icons/splitfeature.png differ diff --git a/tools/icons/splitfeature.svg b/tools/icons/splitfeature.svg new file mode 100644 index 0000000..ca0e264 --- /dev/null +++ b/tools/icons/splitfeature.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tools/ui_dtchooseremaining.ui b/tools/ui_dtchooseremaining.ui new file mode 100644 index 0000000..2060f75 --- /dev/null +++ b/tools/ui_dtchooseremaining.ui @@ -0,0 +1,41 @@ + + + dtchooseremaining + + + + 0 + 0 + 286 + 101 + + + + Dialog + + + true + + + + + + TextLabel + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/ui_dtcutter.ui b/ui_dtcutter.ui new file mode 100644 index 0000000..98f6bdc --- /dev/null +++ b/ui_dtcutter.ui @@ -0,0 +1,84 @@ + + + Dialog + + + + 0 + 0 + 263 + 129 + + + + Choose Layer + + + true + + + + + + cutter layer + + + + + + + + + + add cutter polygon to edit layer + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/uml/dttools.class.violet.html b/uml/dttools.class.violet.html new file mode 100644 index 0000000..d921a32 --- /dev/null +++ b/uml/dttools.class.violet.html @@ -0,0 +1,1688 @@ + + + + + + + + + This file was generated with Violet UML Editor 2.1.0. +   ( View Source / Download Violet ) +
+
+ +
+
+ embedded diagram image + + \ No newline at end of file diff --git a/uml/dttools2.class.violet.html b/uml/dttools2.class.violet.html new file mode 100644 index 0000000..422b2c9 --- /dev/null +++ b/uml/dttools2.class.violet.html @@ -0,0 +1,1461 @@ + + + + + + + + + This file was generated with Violet UML Editor 2.1.0. +   ( View Source / Download Violet ) +
+
+ +
+
+ embedded diagram image + + \ No newline at end of file diff --git a/uml/split_rubberBand.activity.violet.html b/uml/split_rubberBand.activity.violet.html new file mode 100644 index 0000000..58da8b4 --- /dev/null +++ b/uml/split_rubberBand.activity.violet.html @@ -0,0 +1,3012 @@ + + + + + + + + + This file was generated with Violet UML Editor 2.1.0. +   ( View Source / Download Violet ) +
+
+ +
+
+ embedded diagram image + + \ No newline at end of file