dtmovesidebydistance.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. # -*- coding: utf-8 -*-
  2. """
  3. dtmovesidebydistance
  4. ````````````````````
  5. """
  6. """
  7. Part of DigitizingTools, a QGIS plugin that
  8. subsumes different tools neded during digitizing sessions
  9. * begin : 2013-08-15
  10. * copyright : (C) 2013 by Angelos Tzotsos
  11. * email : tzotsos@gmail.com
  12. This program is free software; you can redistribute it and/or modify
  13. it under the terms of the GNU General Public License as published by
  14. the Free Software Foundation; either version 2 of the License, or
  15. (at your option) any later version.
  16. """
  17. from builtins import object
  18. from qgis.PyQt import QtCore, QtGui, QtWidgets
  19. from qgis.core import *
  20. from qgis.gui import *
  21. import dt_icons_rc
  22. from dttools import DtSelectSegmentTool
  23. from dtmovesidebydistance_dialog import DtMoveSideByDistance_Dialog
  24. class DtMoveSideByDistance(object):
  25. '''Automatically move polygon node (along a given side of polygon) in order to achieve a desired polygon area'''
  26. def __init__(self, iface, toolBar):
  27. # Save reference to the QGIS interface
  28. self.iface = iface
  29. self.canvas = self.iface.mapCanvas()
  30. self.gui = None
  31. self.multipolygon_detected = False
  32. # points of the selected segment
  33. # p1 is always the left point
  34. self.p1 = None
  35. self.p2 = None
  36. self.rb1 = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
  37. #self.m1 = None
  38. self.selected_feature = None
  39. #create action
  40. self.side_mover = QtWidgets.QAction(QtGui.QIcon(":/ParallelMovePolygonSideByDistance.png"),
  41. QtWidgets.QApplication.translate("digitizingtools", "Parallel move of polygon side to given distance"), self.iface.mainWindow())
  42. self.side_mover.triggered.connect(self.run)
  43. self.iface.currentLayerChanged.connect(self.enable)
  44. toolBar.addAction(self.side_mover)
  45. self.enable()
  46. self.tool = DtSelectSegmentTool(self.iface)
  47. def showDialog(self):
  48. flags = QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMaximizeButtonHint # QgisGui.ModalDialogFlags
  49. self.gui = DtMoveSideByDistance_Dialog(self.iface.mainWindow(), flags)
  50. self.gui.initGui()
  51. self.gui.show()
  52. self.gui.unsetTool.connect(self.unsetTool)
  53. self.gui.moveSide.connect(self.moveSide)
  54. def enableSegmentTool(self):
  55. self.canvas.setMapTool(self.tool)
  56. #Connect to the DtSelectVertexTool
  57. self.tool.segmentFound.connect(self.storeSegmentPoints)
  58. def unsetTool(self):
  59. self.p1 = None
  60. self.p2 = None
  61. self.selected_feature = None
  62. self.canvas.unsetMapTool(self.tool)
  63. def run(self):
  64. '''Function that does all the real work'''
  65. layer = self.iface.activeLayer()
  66. if(layer.dataProvider().wkbType() == 6):
  67. self.multipolygon_detected = True
  68. title = QtWidgets.QApplication.translate("digitizingtools", "Move polygon side by distance")
  69. if layer.selectedFeatureCount() == 0:
  70. QtWidgets.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", "Please select one polygon to edit."))
  71. elif layer.selectedFeatureCount() > 1:
  72. QtWidgets.QMessageBox.information(None, title, QtWidgets.QApplication.translate("digitizingtools", "Please select only one polygon to edit."))
  73. else:
  74. #One selected feature
  75. self.selected_feature = layer.selectedFeatures()[0]
  76. self.enableSegmentTool()
  77. self.showDialog()
  78. def storeSegmentPoints(self, result):
  79. if result[0].x() < result[1].x():
  80. self.p1 = result[0]
  81. self.p2 = result[1]
  82. elif result[0].x() == result[1].x():
  83. self.p1 = result[0]
  84. self.p2 = result[1]
  85. else:
  86. self.p1 = result[1]
  87. self.p2 = result[0]
  88. def enable(self):
  89. '''Enables/disables the corresponding button.'''
  90. # Disable the Button by default
  91. self.side_mover.setEnabled(False)
  92. layer = self.iface.activeLayer()
  93. if layer != None:
  94. #Only for vector layers.
  95. if layer.type() == QgsMapLayer.VectorLayer:
  96. # only for polygon layers
  97. if layer.geometryType() == 2:
  98. # enable if editable
  99. self.side_mover.setEnabled(layer.isEditable())
  100. try:
  101. layer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected
  102. except:
  103. pass
  104. try:
  105. layer.editingStopped.disconnect(self.enable) # when it becomes active layer again
  106. except:
  107. pass
  108. layer.editingStarted.connect(self.enable)
  109. layer.editingStopped.connect(self.enable)
  110. def moveSide(self):
  111. dist = 0.0
  112. try:
  113. dist = float(self.gui.targetDistance.text())
  114. except:
  115. pass
  116. if (dist == 0.0):
  117. QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Target Distance not valid."))
  118. return
  119. if self.p1 == None or self.p2 == None:
  120. QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Polygon side not selected."))
  121. else:
  122. touch_p1_p2 = self.selected_feature.geometry().touches(QgsGeometry.fromPolyline([QgsPoint(self.p1), QgsPoint(self.p2)]))
  123. if (not touch_p1_p2):
  124. QtWidgets.QMessageBox.information(None, QtWidgets.QApplication.translate("digitizingtools", "Cancel"), QtWidgets.QApplication.translate("digitizingtools", "Selected segment should be on the selected polygon."))
  125. else:
  126. new_geom = createNewGeometry(self.selected_feature.geometry(), self.p1, self.p2, dist, self.multipolygon_detected)
  127. fid = self.selected_feature.id()
  128. layer = self.iface.activeLayer()
  129. layer.beginEditCommand(QtWidgets.QApplication.translate("editcommand", "Move Side By Distance"))
  130. layer.changeGeometry(fid,new_geom)
  131. self.canvas.refresh()
  132. layer.endEditCommand()
  133. def createNewGeometry(geom, p1, p2, new_distance, multipolygon):
  134. pointList = []
  135. if(multipolygon):
  136. pointList = geom.asMultiPolygon()[0][0][0:-1]
  137. else:
  138. pointList = geom.asPolygon()[0][0:-1]
  139. #Read input polygon geometry as a list of QgsPoints
  140. #indices
  141. ind = 0
  142. ind_max = len(pointList)-1
  143. p1_indx = -1
  144. p2_indx = -1
  145. #find p1 and p2 in the list
  146. for tmp_point in pointList:
  147. if (tmp_point == p1):
  148. p1_indx = ind
  149. elif (tmp_point == p2):
  150. p2_indx = ind
  151. ind += 1
  152. (p3,p4)=getParallelLinePoints(p1,p2,new_distance)
  153. pointList[p1_indx] = p3
  154. pointList[p2_indx] = p4
  155. new_geom = QgsGeometry.fromPolygonXY( [ pointList ] )
  156. return new_geom
  157. def getParallelLinePoints(p1, p2, dist):
  158. """
  159. This function is adopted/adapted from 'CadTools Plugin', Copyright (C) Stefan Ziegler
  160. """
  161. if dist == 0:
  162. g = (p1, p2)
  163. return g
  164. dn = ( (p1.x()-p2.x())**2 + (p1.y()-p2.y())**2 )**0.5
  165. x3 = p1.x() + dist*(p1.y()-p2.y()) / dn
  166. y3 = p1.y() - dist*(p1.x()-p2.x()) / dn
  167. p3 = QgsPointXY(x3, y3)
  168. x4 = p2.x() + dist*(p1.y()-p2.y()) / dn
  169. y4 = p2.y() - dist*(p1.x()-p2.x()) / dn
  170. p4 = QgsPointXY(x4, y4)
  171. g = (p3,p4)
  172. return g