dttools.py 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274
  1. # -*- coding: utf-8 -*-
  2. """
  3. dttools
  4. `````````````
  5. """
  6. """
  7. Part of DigitizingTools, a QGIS plugin that
  8. subsumes different tools neded during digitizing sessions
  9. * begin : 2013-02-25
  10. * copyright : (C) 2013 by Bernhard Ströbl
  11. * email : bernhard.stroebl@jena.de
  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 range
  18. from builtins import object
  19. from qgis.PyQt import QtGui, QtCore, QtWidgets
  20. from qgis.core import *
  21. from qgis.gui import *
  22. import dtutils
  23. class DtTool(object):
  24. '''Abstract class; parent for any Dt tool or button'''
  25. def __init__(self, iface, geometryTypes, **kw):
  26. self.iface = iface
  27. self.canvas = self.iface.mapCanvas()
  28. #custom cursor
  29. self.cursor = QtGui.QCursor(QtGui.QPixmap(["16 16 3 1",
  30. " c None",
  31. ". c #FF0000",
  32. "+ c #FFFFFF",
  33. " ",
  34. " +.+ ",
  35. " ++.++ ",
  36. " +.....+ ",
  37. " +. .+ ",
  38. " +. . .+ ",
  39. " +. . .+ ",
  40. " ++. . .++",
  41. " ... ...+... ...",
  42. " ++. . .++",
  43. " +. . .+ ",
  44. " +. . .+ ",
  45. " ++. .+ ",
  46. " ++.....+ ",
  47. " ++.++ ",
  48. " +.+ "]))
  49. self.geometryTypes = []
  50. self.shapeFileGeometryTypes = []
  51. # ESRI shapefile does not distinguish between single and multi geometries
  52. # source of wkbType numbers: http://gdal.org/java/constant-values.html
  53. for aGeomType in geometryTypes:
  54. if aGeomType == 1: # wkbPoint
  55. self.geometryTypes.append(1)
  56. self.shapeFileGeometryTypes.append(4)
  57. self.geometryTypes.append(-2147483647) #wkbPoint25D
  58. self.shapeFileGeometryTypes.append(-2147483647)
  59. elif aGeomType == 2: # wkbLineString
  60. self.geometryTypes.append(2)
  61. self.shapeFileGeometryTypes.append(5)
  62. self.geometryTypes.append(-2147483646) #wkbLineString25D
  63. self.shapeFileGeometryTypes.append(-2147483646)
  64. elif aGeomType == 3: # wkbPolygon
  65. self.geometryTypes.append(3)
  66. self.shapeFileGeometryTypes.append(6)
  67. self.geometryTypes.append(-2147483645) #wkbPolygon25D
  68. self.shapeFileGeometryTypes.append(-2147483645)
  69. elif aGeomType == 4: # wkbMultiPoint
  70. self.geometryTypes.append(4)
  71. self.shapeFileGeometryTypes.append(1) # wkbPoint
  72. self.geometryTypes.append(-2147483644) #wkbMultiPoint25D
  73. self.shapeFileGeometryTypes.append(-2147483647) #wkbPoint25D
  74. elif aGeomType == 5: # wkbMultiLineString
  75. self.geometryTypes.append(5)
  76. self.shapeFileGeometryTypes.append(2) # wkbLineString
  77. self.geometryTypes.append(-2147483643) #wkbMultiLineString25D
  78. self.shapeFileGeometryTypes.append(-2147483646) #wkbLineString25D
  79. elif aGeomType == 6: # wkbMultiPolygon
  80. self.geometryTypes.append(6)
  81. self.shapeFileGeometryTypes.append(6) # wkbPolygon
  82. self.geometryTypes.append(-2147483642) #wkbMultiPolygon25D
  83. self.shapeFileGeometryTypes.append(-2147483645) #wkbPolygon25D
  84. def allowedGeometry(self, layer):
  85. '''check if this layer's geometry type is within the list of allowed types'''
  86. if layer.dataProvider().storageType() == u'ESRI Shapefile': # does not distinguish between single and multi
  87. result = self.shapeFileGeometryTypes.count(layer.wkbType()) >= 1
  88. else:
  89. result = self.geometryTypes.count(layer.wkbType()) == 1
  90. return result
  91. def geometryTypeMatchesLayer(self, layer, geom):
  92. '''check if the passed geom's geometry type matches the layer's type'''
  93. match = layer.wkbType() == geom.wkbType()
  94. if not match:
  95. if layer.dataProvider().storageType() == u'ESRI Shapefile':
  96. # does not distinguish between single and multi
  97. match = (layer.wkbType() == 1 and geom.wkbType() == 4) or \
  98. (layer.wkbType() == 2 and geom.wkbType() == 5) or \
  99. (layer.wkbType() == 3 and geom.wkbType() == 6) or \
  100. (layer.wkbType() == 4 and geom.wkbType() == 1) or \
  101. (layer.wkbType() == 5 and geom.wkbType() == 2) or \
  102. (layer.wkbType() == 6 and geom.wkbType() == 3)
  103. else:
  104. # are we trying a single into a multi layer?
  105. match = (layer.wkbType() == 4 and geom.wkbType() == 1) or \
  106. (layer.wkbType() == 5 and geom.wkbType() == 2) or \
  107. (layer.wkbType() == 6 and geom.wkbType() == 3)
  108. return match
  109. def isPolygonLayer(self, layer):
  110. ''' check if this layer is a polygon layer'''
  111. polygonTypes = [3, 6, -2147483645, -2147483642]
  112. result = layer.wkbType() in polygonTypes
  113. return result
  114. def debug(self, str):
  115. title = "DigitizingTools Debugger"
  116. QgsMessageLog.logMessage(title + "\n" + str)
  117. class DtSingleButton(DtTool):
  118. '''Abstract class for a single button
  119. icon [QtGui.QIcon]
  120. tooltip [str]
  121. geometryTypes [array:integer] 0=point, 1=line, 2=polygon'''
  122. def __init__(self, iface, toolBar, icon, tooltip, geometryTypes = [1, 2, 3], dtName = None):
  123. super().__init__(iface, geometryTypes)
  124. self.act = QtWidgets.QAction(icon, tooltip, self.iface.mainWindow())
  125. self.act.triggered.connect(self.process)
  126. if dtName != None:
  127. self.act.setObjectName(dtName)
  128. self.iface.currentLayerChanged.connect(self.enable)
  129. toolBar.addAction(self.act)
  130. self.geometryTypes = geometryTypes
  131. def process(self):
  132. raise NotImplementedError("Should have implemented process")
  133. def enable(self):
  134. '''Enables/disables the corresponding button.'''
  135. # Disable the Button by default
  136. self.act.setEnabled(False)
  137. layer = self.iface.activeLayer()
  138. if layer != None:
  139. #Only for vector layers.
  140. if layer.type() == QgsMapLayer.VectorLayer:
  141. if self.allowedGeometry(layer):
  142. self.act.setEnabled(layer.isEditable())
  143. try:
  144. layer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected
  145. except:
  146. pass
  147. try:
  148. layer.editingStopped.disconnect(self.enable) # when it becomes active layer again
  149. except:
  150. pass
  151. layer.editingStarted.connect(self.enable)
  152. layer.editingStopped.connect(self.enable)
  153. class DtSingleTool(DtSingleButton):
  154. '''Abstract class for a tool'''
  155. def __init__(self, iface, toolBar, icon, tooltip, geometryTypes = [0, 1, 2], crsWarning = True, dtName = None):
  156. super().__init__(iface, toolBar, icon, tooltip, geometryTypes, dtName)
  157. self.tool = None
  158. self.act.setCheckable(True)
  159. self.canvas.mapToolSet.connect(self.toolChanged)
  160. def toolChanged(self, thisTool):
  161. if thisTool != self.tool:
  162. self.deactivate()
  163. def deactivate(self):
  164. if self.tool != None:
  165. self.tool.reset()
  166. self.reset()
  167. self.act.setChecked(False)
  168. def reset(self):
  169. pass
  170. class DtSingleEditTool(DtSingleTool):
  171. '''Abstract class for a tool for interactive editing'''
  172. def __init__(self, iface, toolBar, icon, tooltip, geometryTypes = [0, 1, 2], crsWarning = True, dtName = None):
  173. super().__init__(iface, toolBar, icon, tooltip, geometryTypes, dtName)
  174. self.crsWarning = crsWarning
  175. self.editLayer = None
  176. def reset(self):
  177. self.editLayer = None
  178. def enable(self):
  179. '''Enables/disables the corresponding button.'''
  180. # Disable the Button by default
  181. doEnable = False
  182. layer = self.iface.activeLayer()
  183. if layer != None:
  184. if layer.type() == 0: #Only for vector layers.
  185. if self.allowedGeometry(layer):
  186. doEnable = layer.isEditable()
  187. try:
  188. layer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected
  189. except:
  190. pass
  191. try:
  192. layer.editingStopped.disconnect(self.enable) # when it becomes active layer again
  193. except:
  194. pass
  195. layer.editingStarted.connect(self.enable)
  196. layer.editingStopped.connect(self.enable)
  197. if self.editLayer != None: # we have a current edit session, activeLayer may have changed or editing status of self.editLayer
  198. if self.editLayer != layer:
  199. try:
  200. self.editLayer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected
  201. except:
  202. pass
  203. try:
  204. self.editLayer.editingStopped.disconnect(self.enable) # when it becomes active layer again
  205. except:
  206. pass
  207. self.tool.reset()
  208. self.reset()
  209. if not doEnable:
  210. self.deactivate()
  211. if doEnable and self.crsWarning:
  212. layerCRSSrsid = layer.crs().srsid()
  213. mapSet = self.canvas.mapSettings()
  214. projectCRSSrsid = mapSet.destinationCrs().srsid()
  215. if layerCRSSrsid != projectCRSSrsid:
  216. self.iface.messageBar().pushWarning("DigitizingTools", self.act.toolTip() + " " +
  217. QtWidgets.QApplication.translate("DigitizingTools",
  218. "is disabled because layer CRS and project CRS do not match!"))
  219. doEnable = False
  220. self.act.setEnabled(doEnable)
  221. class DtDualTool(DtTool):
  222. '''Abstract class for a tool with interactive and batch mode
  223. icon [QtGui.QIcon] for interactive mode
  224. tooltip [str] for interactive mode
  225. iconBatch [QtGui.QIcon] for batch mode
  226. tooltipBatch [str] for batch mode
  227. geometryTypes [array:integer] 0=point, 1=line, 2=polygon'''
  228. def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], dtName = None):
  229. super().__init__(iface, geometryTypes)
  230. self.iface.currentLayerChanged.connect(self.enable)
  231. self.canvas.mapToolSet.connect(self.toolChanged)
  232. #create button
  233. self.button = QtWidgets.QToolButton(toolBar)
  234. self.button.clicked.connect(self.runSlot)
  235. self.button.toggled.connect(self.hasBeenToggled)
  236. #create menu
  237. self.menu = QtWidgets.QMenu(toolBar)
  238. if dtName != None:
  239. self.menu.setObjectName(dtName)
  240. self.menu.triggered.connect(self.menuTriggered)
  241. self.button.setMenu(self.menu)
  242. self.button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
  243. # create actions
  244. self.act = QtWidgets.QAction(icon, tooltip, self.iface.mainWindow())
  245. if dtName != None:
  246. self.act.setObjectName(dtName + "Action")
  247. self.act.setToolTip(tooltip)
  248. self.act_batch = QtWidgets.QAction(iconBatch, tooltipBatch, self.iface.mainWindow())
  249. if dtName != None:
  250. self.act_batch.setObjectName(dtName + "BatchAction")
  251. self.act_batch.setToolTip(tooltipBatch)
  252. self.menu.addAction(self.act)
  253. self.menu.addAction(self.act_batch)
  254. # set the interactive action as default action, user needs to click the button to activate it
  255. self.button.setIcon(self.act.icon())
  256. self.button.setToolTip(self.act.toolTip())
  257. self.button.setCheckable(True)
  258. self.batchMode = False
  259. # add button to toolBar
  260. toolBar.addWidget(self.button)
  261. self.geometryTypes = geometryTypes
  262. # run the enable slot
  263. self.enable()
  264. def menuTriggered(self, thisAction):
  265. if thisAction == self.act:
  266. self.batchMode = False
  267. self.button.setCheckable(True)
  268. if not self.button.isChecked():
  269. self.button.toggle()
  270. else:
  271. self.batchMode = True
  272. if self.button.isCheckable():
  273. if self.button.isChecked():
  274. self.button.toggle()
  275. self.button.setCheckable(False)
  276. self.runSlot(False)
  277. self.button.setIcon(thisAction.icon())
  278. self.button.setToolTip(thisAction.toolTip())
  279. def toolChanged(self, thisTool):
  280. if thisTool != self.tool:
  281. self.deactivate()
  282. def hasBeenToggled(self, isChecked):
  283. raise NotImplementedError("Should have implemented hasBeenToggled")
  284. def deactivate(self):
  285. if self.button != None:
  286. if self.button.isChecked():
  287. self.button.toggle()
  288. def runSlot(self, isChecked):
  289. if self.batchMode:
  290. layer = self.iface.activeLayer()
  291. if layer.selectedFeatureCount() > 0:
  292. self.process()
  293. else:
  294. if not isChecked:
  295. self.button.toggle()
  296. def process(self):
  297. raise NotImplementedError("Should have implemented process")
  298. def enable(self):
  299. # Disable the Button by default
  300. self.button.setEnabled(False)
  301. layer = self.iface.activeLayer()
  302. if layer != None:
  303. #Only for vector layers.
  304. if layer.type() == QgsMapLayer.VectorLayer:
  305. # only for certain layers
  306. if self.allowedGeometry(layer):
  307. if not layer.isEditable():
  308. self.deactivate()
  309. self.button.setEnabled(layer.isEditable())
  310. try:
  311. layer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected
  312. except:
  313. pass
  314. try:
  315. layer.editingStopped.disconnect(self.enable) # when it becomes active layer again
  316. except:
  317. pass
  318. layer.editingStarted.connect(self.enable)
  319. layer.editingStopped.connect(self.enable)
  320. else:
  321. self.deactivate()
  322. class DtDualToolSelectFeature(DtDualTool):
  323. '''Abstract class for a DtDualToo which uses the DtSelectFeatureTool for interactive mode'''
  324. def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], dtName = None):
  325. super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName)
  326. self.tool = DtSelectFeatureTool(iface)
  327. def featureSelectedSlot(self, fids):
  328. if len(fids) >0:
  329. self.process()
  330. def hasBeenToggled(self, isChecked):
  331. try:
  332. self.tool.featureSelected.disconnect(self.featureSelectedSlot)
  333. # disconnect if it was already connected, so slot gets called only once!
  334. except:
  335. pass
  336. if isChecked:
  337. self.canvas.setMapTool(self.tool)
  338. self.tool.featureSelected.connect(self.featureSelectedSlot)
  339. else:
  340. self.canvas.unsetMapTool(self.tool)
  341. class DtDualToolSelectPolygon(DtDualToolSelectFeature):
  342. '''Abstract class for a DtDualToo which uses the DtSelectFeatureTool for interactive mode'''
  343. def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [3, 6], dtName = None):
  344. super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName)
  345. self.tool = DtSelectPolygonTool(iface)
  346. class DtDualToolSelectVertex(DtDualTool):
  347. '''Abstract class for a DtDualTool which uses the DtSelectVertexTool for interactive mode
  348. numVertices [integer] nnumber of vertices to be snapped until vertexFound signal is emitted'''
  349. def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], numVertices = 1, dtName = None):
  350. super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName)
  351. self.tool = DtSelectVertexTool(self.iface, numVertices)
  352. def hasBeenToggled(self, isChecked):
  353. try:
  354. self.tool.vertexFound.disconnect(self.vertexSnapped)
  355. # disconnect if it was already connected, so slot gets called only once!
  356. except:
  357. pass
  358. if isChecked:
  359. self.canvas.setMapTool(self.tool)
  360. self.tool.vertexFound.connect(self.vertexSnapped)
  361. else:
  362. self.canvas.unsetMapTool(self.tool)
  363. def vertexSnapped(self, snapResult):
  364. raise NotImplementedError("Should have implemented vertexSnapped")
  365. class DtDualToolSelectRing(DtDualTool):
  366. '''
  367. Abstract class for a DtDualTool which uses the DtSelectRingTool for interactive mode
  368. '''
  369. def __init__(self, iface, toolBar, icon, tooltip, iconBatch,
  370. tooltipBatch, geometryTypes = [1, 2, 3], dtName = None):
  371. super().__init__(iface, toolBar, icon, tooltip,
  372. iconBatch, tooltipBatch, geometryTypes, dtName)
  373. self.tool = DtSelectRingTool(self.iface)
  374. def hasBeenToggled(self, isChecked):
  375. try:
  376. self.tool.ringSelected.disconnect(self.ringFound)
  377. # disconnect if it was already connected, so slot gets called only once!
  378. except:
  379. pass
  380. if isChecked:
  381. self.canvas.setMapTool(self.tool)
  382. self.tool.ringSelected.connect(self.ringFound)
  383. else:
  384. self.canvas.unsetMapTool(self.tool)
  385. def ringFound(self, selectRingResult):
  386. raise NotImplementedError("Should have implemented ringFound")
  387. class DtDualToolSelectGap(DtDualTool):
  388. '''
  389. Abstract class for a DtDualTool which uses the DtSelectGapTool for interactive mode
  390. '''
  391. def __init__(self, iface, toolBar, icon, tooltip, iconBatch,
  392. tooltipBatch, geometryTypes = [1, 2, 3], dtName = None,
  393. allLayers = False):
  394. super().__init__(iface, toolBar, icon, tooltip,
  395. iconBatch, tooltipBatch, geometryTypes, dtName)
  396. self.tool = DtSelectGapTool(self.iface, allLayers)
  397. def hasBeenToggled(self, isChecked):
  398. try:
  399. self.tool.gapSelected.disconnect(self.gapFound)
  400. # disconnect if it was already connected, so slot gets called only once!
  401. except:
  402. pass
  403. if isChecked:
  404. self.canvas.setMapTool(self.tool)
  405. self.tool.gapSelected.connect(self.gapFound)
  406. else:
  407. self.canvas.unsetMapTool(self.tool)
  408. def gapFound(self, selectGapResult):
  409. raise NotImplementedError("Should have implemented gapFound")
  410. class DtMapToolEdit(QgsMapToolEdit, DtTool):
  411. '''abstract subclass of QgsMapToolEdit'''
  412. def __init__(self, iface, **kw):
  413. super().__init__(canvas = iface.mapCanvas(), iface = iface, geometryTypes = [])
  414. def activate(self):
  415. self.canvas.setCursor(self.cursor)
  416. def deactivate(self):
  417. self.reset()
  418. def reset(self, emitSignal = False):
  419. pass
  420. def transformed(self, thisLayer, thisQgsPoint):
  421. layerCRSSrsid = thisLayer.crs().srsid()
  422. projectCRSSrsid = QgsProject.instance().crs().srsid()
  423. if layerCRSSrsid != projectCRSSrsid:
  424. transQgsPoint = QgsGeometry.fromPointXY(thisQgsPoint)
  425. transQgsPoint.transform(QgsCoordinateTransform(
  426. QgsProject.instance().crs(), thisLayer.crs(),
  427. QgsProject.instance()))
  428. return transQgsPoint.asPoint()
  429. else:
  430. return thisQgsPoint
  431. class DtSelectFeatureTool(DtMapToolEdit):
  432. featureSelected = QtCore.pyqtSignal(list)
  433. def __init__(self, iface):
  434. super().__init__(iface)
  435. self.currentHighlight = [None, None] # feature, highlightGraphic
  436. self.ignoreFids = [] # featureids that schould be ignored when looking for a feature
  437. def highlightFeature(self, layer, feature):
  438. '''highlight the feature if it has a geometry'''
  439. geomType = layer.geometryType()
  440. returnGeom = None
  441. if geomType <= 2:
  442. if geomType == 0:
  443. marker = QgsVertexMarker(self.iface.mapCanvas())
  444. marker.setIconType(3) # ICON_BOX
  445. marker.setColor(self.rubberBandColor)
  446. marker.setIconSize(12)
  447. marker.setPenWidth (3)
  448. marker.setCenter(feature.geometry().centroid().asPoint())
  449. returnGeom = marker
  450. else:
  451. settings = QtCore.QSettings()
  452. settings.beginGroup("Qgis/digitizing")
  453. a = settings.value("line_color_alpha",200,type=int)
  454. b = settings.value("line_color_blue",0,type=int)
  455. g = settings.value("line_color_green",0,type=int)
  456. r = settings.value("line_color_red",255,type=int)
  457. lw = settings.value("line_width",1,type=int)
  458. settings.endGroup()
  459. rubberBandColor = QtGui.QColor(r, g, b, a)
  460. rubberBandWidth = lw
  461. rubberBand = QgsRubberBand(self.iface.mapCanvas())
  462. rubberBand.setColor(rubberBandColor)
  463. rubberBand.setWidth(rubberBandWidth)
  464. rubberBand.setToGeometry(feature.geometry(), layer)
  465. returnGeom = rubberBand
  466. self.currentHighlight = [feature, returnGeom]
  467. return returnGeom
  468. else:
  469. return None
  470. def removeHighlight(self):
  471. highlightGeom = self.currentHighlight[1]
  472. if highlightGeom != None:
  473. self.iface.mapCanvas().scene().removeItem(highlightGeom)
  474. self.currentHighlight = [None, None]
  475. def highlightNext(self, layer, startingPoint):
  476. if self.currentHighlight != [None, None]:
  477. self.ignoreFids.append(self.currentHighlight[0].id())
  478. # will return the first feature, if there is only one will return this feature
  479. found = self.getFeatureForPoint(layer, startingPoint)
  480. if len(found) == 0:
  481. self.removeHighlight()
  482. return 0
  483. else:
  484. aFeat = found[0]
  485. numFeatures = found[1]
  486. if self.currentHighlight != [None, None]:
  487. if aFeat.id() != self.currentHighlight[0].id():
  488. self.removeHighlight()
  489. self.highlightFeature(layer, found[0])
  490. else:
  491. self.highlightFeature(layer, found[0])
  492. return numFeatures
  493. def getFeatureForPoint(self, layer, startingPoint, inRing = False):
  494. '''
  495. return the feature this QPoint is in (polygon layer)
  496. or this QPoint snaps to (point or line layer)
  497. '''
  498. result = []
  499. if self.isPolygonLayer(layer):
  500. mapToPixel = self.canvas.getCoordinateTransform()
  501. #thisQgsPoint = mapToPixel.toMapCoordinates(startingPoint)
  502. thisQgsPoint = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint))
  503. spatialIndex = dtutils.dtSpatialindex(layer)
  504. featureIds = spatialIndex.nearestNeighbor(thisQgsPoint, 0)
  505. # if we use 0 as neighborCount then only features that contain the point
  506. # are included
  507. for fid in featureIds:
  508. feat = dtutils.dtGetFeatureForId(layer, fid)
  509. if feat != None:
  510. geom = QgsGeometry(feat.geometry())
  511. if geom.contains(thisQgsPoint):
  512. result.append(feat)
  513. result.append([])
  514. return result
  515. break
  516. else:
  517. if inRing:
  518. rings = dtutils.dtExtractRings(geom)
  519. if len(rings) > 0:
  520. for aRing in rings:
  521. if aRing.contains(thisQgsPoint):
  522. result.append(feat)
  523. result.append([])
  524. result.append(aRing)
  525. return result
  526. break
  527. else:
  528. #we need a snapper, so we use the MapCanvas snapper
  529. snapper = self.canvas.snappingUtils()
  530. snapper.setCurrentLayer(layer)
  531. # snapType = 0: no snap, 1 = vertex, 2 vertex & segment, 3 = segment
  532. snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.All)
  533. if not snapMatch.isValid():
  534. dtutils.showSnapSettingsWarning(self.iface)
  535. else:
  536. feat = dtutils.dtGetFeatureForId(layer, snapMatch.featureId())
  537. if feat != None:
  538. result.append(feat)
  539. if snapMatch.hasVertex():
  540. result.append([snapMatch.point(), None])
  541. if snapMatch.hasEdge():
  542. result.append(snapMatch.edgePoints())
  543. return result
  544. return result
  545. def canvasReleaseEvent(self,event):
  546. #Get the click
  547. x = event.pos().x()
  548. y = event.pos().y()
  549. layer = self.canvas.currentLayer()
  550. if layer != None:
  551. #the clicked point is our starting point
  552. startingPoint = QtCore.QPoint(x,y)
  553. found = self.getFeatureForPoint(layer, startingPoint)
  554. if len(found) > 0:
  555. feat = found[0]
  556. layer.removeSelection()
  557. layer.select(feat.id())
  558. self.featureSelected.emit([feat.id()])
  559. class DtSelectPolygonTool(DtSelectFeatureTool):
  560. def __init__(self, iface):
  561. super().__init__(iface)
  562. def getFeatureForPoint(self, layer, startingPoint):
  563. '''
  564. return the feature this QPoint is in and the total amount of features
  565. '''
  566. result = []
  567. mapToPixel = self.canvas.getCoordinateTransform()
  568. #thisQgsPoint = mapToPixel.toMapCoordinates(startingPoint)
  569. thisQgsPoint = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint))
  570. spatialIndex = dtutils.dtSpatialindex(layer)
  571. featureIds = spatialIndex.nearestNeighbor(thisQgsPoint, 0)
  572. # if we use 0 as neighborCount then only features that contain the point
  573. # are included
  574. foundFeatures = []
  575. while True:
  576. for fid in featureIds:
  577. if self.ignoreFids.count(fid) == 0:
  578. feat = dtutils.dtGetFeatureForId(layer, fid)
  579. if feat != None:
  580. geom = QgsGeometry(feat.geometry())
  581. if geom.contains(thisQgsPoint):
  582. foundFeatures.append(feat)
  583. if len(foundFeatures) == 0:
  584. if len(self.ignoreFids) == 0: #there is no feaure at this point
  585. break #while
  586. else:
  587. self.ignoreFids.pop(0) # remove first and try again
  588. elif len(foundFeatures) > 0: # return first feature
  589. feat = foundFeatures[0]
  590. result.append(feat)
  591. result.append(len(featureIds))
  592. break #while
  593. return result
  594. def canvasReleaseEvent(self,event):
  595. '''
  596. - if user clicks left and no feature is highlighted, highlight first feature
  597. - if user clicks left and there is a highlighted feature use this feature as selected
  598. - if user clicks right, highlight another feature
  599. '''
  600. #Get the click
  601. x = event.pos().x()
  602. y = event.pos().y()
  603. layer = self.canvas.currentLayer()
  604. if layer != None:
  605. startingPoint = QtCore.QPoint(x,y)
  606. #the clicked point is our starting point
  607. if event.button() == QtCore.Qt.RightButton: # choose another feature
  608. self.highlightNext(layer, startingPoint)
  609. elif event.button() == QtCore.Qt.LeftButton:
  610. if self.currentHighlight == [None, None]: # first click
  611. numFeatures = self.highlightNext(layer, startingPoint)
  612. else: # user accepts highlighted geometry
  613. mapToPixel = self.canvas.getCoordinateTransform()
  614. thisQgsPoint = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint))
  615. feat = self.currentHighlight[0]
  616. if feat.geometry().contains(thisQgsPoint): # is point in highlighted feature?
  617. numFeatures = 1
  618. else: # mabe user clicked somewhere else
  619. numFeatures = self.highlightNext(layer, startingPoint)
  620. if numFeatures == 1:
  621. feat = self.currentHighlight[0]
  622. self.removeHighlight()
  623. layer.removeSelection()
  624. layer.select(feat.id())
  625. self.featureSelected.emit([feat.id()])
  626. def reset(self):
  627. self.removeHighlight()
  628. class DtSelectRingTool(DtSelectFeatureTool):
  629. '''
  630. a map tool to select a ring in a polygon
  631. '''
  632. ringSelected = QtCore.pyqtSignal(list)
  633. def __init__(self, iface):
  634. super().__init__(iface)
  635. def canvasReleaseEvent(self,event):
  636. #Get the click
  637. x = event.pos().x()
  638. y = event.pos().y()
  639. layer = self.canvas.currentLayer()
  640. if layer != None:
  641. #the clicked point is our starting point
  642. startingPoint = QtCore.QPoint(x,y)
  643. found = self.getFeatureForPoint(layer, startingPoint, inRing = True)
  644. if len(found) == 3:
  645. aRing = found[2]
  646. self.ringSelected.emit([aRing])
  647. def reset(self, emitSignal = False):
  648. pass
  649. class DtSelectGapTool(DtMapToolEdit):
  650. '''
  651. a map tool to select a gap between polygons, if allLayers
  652. is True then the gap is searched between polygons of
  653. all currently visible polygon layers
  654. '''
  655. gapSelected = QtCore.pyqtSignal(list)
  656. def __init__(self, iface, allLayers):
  657. super().__init__(iface)
  658. self.allLayers = allLayers
  659. def canvasReleaseEvent(self,event):
  660. #Get the click
  661. x = event.pos().x()
  662. y = event.pos().y()
  663. layer = self.canvas.currentLayer()
  664. visibleLayers = []
  665. if self.allLayers:
  666. for aLayer in self.iface.layerTreeCanvasBridge().rootGroup().checkedLayers():
  667. if 0 == aLayer.type():
  668. if self.isPolygonLayer(aLayer):
  669. visibleLayers.append(aLayer)
  670. else:
  671. if layer != None:
  672. visibleLayers.append(layer)
  673. if len(visibleLayers) > 0:
  674. #the clicked point is our starting point
  675. startingPoint = QtCore.QPoint(x,y)
  676. mapToPixel = self.canvas.getCoordinateTransform()
  677. thisQgsPoint = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint))
  678. multiGeom = None
  679. for aLayer in visibleLayers:
  680. if not self.allLayers and aLayer.selectedFeatureCount() > 0:
  681. #we assume, that the gap is between the selected polyons
  682. hadSelection = True
  683. else:
  684. hadSelection = False
  685. spatialIndex = dtutils.dtSpatialindex(aLayer)
  686. # get the 100 closest Features
  687. featureIds = spatialIndex.nearestNeighbor(thisQgsPoint, 100)
  688. aLayer.select(featureIds)
  689. multiGeom = dtutils.dtCombineSelectedPolygons(aLayer, self.iface, multiGeom)
  690. if self.allLayers or not hadSelection:
  691. aLayer.removeSelection()
  692. if multiGeom == None:
  693. return None
  694. if multiGeom != None:
  695. rings = dtutils.dtExtractRings(multiGeom)
  696. if len(rings) > 0:
  697. for aRing in rings:
  698. if aRing.contains(thisQgsPoint):
  699. self.gapSelected.emit([aRing])
  700. break
  701. def reset(self, emitSignal = False):
  702. pass
  703. class DtSelectPartTool(DtSelectFeatureTool):
  704. '''signal sends featureId of clickedd feature, number of part selected and geometry of part'''
  705. partSelected = QtCore.pyqtSignal(list)
  706. def __init__(self, iface):
  707. super().__init__(iface)
  708. def canvasReleaseEvent(self,event):
  709. #Get the click
  710. x = event.pos().x()
  711. y = event.pos().y()
  712. layer = self.canvas.currentLayer()
  713. if layer != None:
  714. #the clicked point is our starting point
  715. startingPoint = QtCore.QPoint(x,y)
  716. found = self.getFeatureForPoint(layer, startingPoint)
  717. if len(found) > 0:
  718. feat = found[0]
  719. snappedPoints = found[1]
  720. if len(snappedPoints) > 0:
  721. snappedVertex = snappedPoints[0]
  722. else:
  723. snappedVertex = None
  724. geom = QgsGeometry(feat.geometry())
  725. # if feature geometry is multipart start split processing
  726. if geom.isMultipart():
  727. # Get parts from original feature
  728. parts = geom.asGeometryCollection()
  729. mapToPixel = self.canvas.getCoordinateTransform()
  730. thisQgsPoint = mapToPixel.toMapCoordinates(startingPoint)
  731. for i in range(len(parts)):
  732. # find the part that was snapped
  733. aPart = parts[i]
  734. if self.isPolygonLayer(layer):
  735. if aPart.contains(thisQgsPoint):
  736. self.partSelected.emit([feat.id(), i, aPart])
  737. break
  738. else:
  739. points = dtutils.dtExtractPoints(aPart)
  740. for j in range(len(points)):
  741. aPoint = points[j]
  742. if snappedVertex != None:
  743. if aPoint.x() == snappedVertex.x() and \
  744. aPoint.y() == snappedVertex.y():
  745. self.partSelected.emit([feat.id(), i, aPart])
  746. break
  747. else:
  748. try:
  749. nextPoint = points[j + 1]
  750. except:
  751. break
  752. if aPoint.x() == snappedPoints[0].x() and \
  753. aPoint.y() == snappedPoints[0].y() and \
  754. nextPoint.x() == snappedPoints[1].x() and \
  755. nextPoint.y() == snappedPoints[1].y():
  756. self.partSelected.emit([feat.id(), i, aPart])
  757. break
  758. class DtSelectVertexTool(DtMapToolEdit):
  759. '''select and mark numVertices vertices in the active layer'''
  760. vertexFound = QtCore.pyqtSignal(list)
  761. def __init__(self, iface, numVertices = 1):
  762. super().__init__(iface)
  763. # desired number of marked vertex until signal
  764. self.numVertices = numVertices
  765. # number of marked vertex
  766. self.count = 0
  767. # arrays to hold markers and vertex points
  768. self.markers = []
  769. self.points = []
  770. self.fids = []
  771. def canvasReleaseEvent(self,event):
  772. if self.count < self.numVertices: #not yet enough
  773. #Get the click
  774. x = event.pos().x()
  775. y = event.pos().y()
  776. layer = self.canvas.currentLayer()
  777. if layer != None:
  778. #the clicked point is our starting point
  779. startingPoint = QtCore.QPoint(x,y)
  780. #we need a snapper, so we use the MapCanvas snapper
  781. snapper = self.canvas.snappingUtils()
  782. snapper.setCurrentLayer(layer)
  783. # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment
  784. snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Vertex)
  785. if not snapMatch.isValid():
  786. #warn about missing snapping tolerance if appropriate
  787. dtutils.showSnapSettingsWarning(self.iface)
  788. else:
  789. #mark the vertex
  790. p = snapMatch.point()
  791. m = QgsVertexMarker(self.canvas)
  792. m.setIconType(1)
  793. if self.count == 0:
  794. m.setColor(QtGui.QColor(255,0,0))
  795. else:
  796. m.setColor(QtGui.QColor(0, 0, 255))
  797. m.setIconSize(12)
  798. m.setPenWidth (3)
  799. m.setCenter(p)
  800. self.points.append(p)
  801. self.markers.append(m)
  802. fid = snapMatch.featureId() # QgsFeatureId of the snapped geometry
  803. self.fids.append(fid)
  804. self.count += 1
  805. if self.count == self.numVertices:
  806. self.vertexFound.emit([self.points, self.markers, self.fids])
  807. #self.emit(SIGNAL("vertexFound(PyQt_PyObject)"), [self.points, self.markers])
  808. def reset(self, emitSignal = False):
  809. for m in self.markers:
  810. self.canvas.scene().removeItem(m)
  811. self.markers = []
  812. self.points = []
  813. self.fids = []
  814. self.count = 0
  815. class DtSelectSegmentTool(DtMapToolEdit):
  816. segmentFound = QtCore.pyqtSignal(list)
  817. def __init__(self, iface):
  818. super().__init__(iface)
  819. self.rb1 = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
  820. def canvasReleaseEvent(self,event):
  821. #Get the click
  822. x = event.pos().x()
  823. y = event.pos().y()
  824. layer = self.canvas.currentLayer()
  825. if layer != None:
  826. #the clicked point is our starting point
  827. startingPoint = QtCore.QPoint(x,y)
  828. #we need a snapper, so we use the MapCanvas snapper
  829. snapper = self.canvas.snappingUtils()
  830. snapper.setCurrentLayer(layer)
  831. # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment
  832. snapType = 2
  833. snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Edge)
  834. if not snapMatch.isValid():
  835. #warn about missing snapping tolerance if appropriate
  836. dtutils.showSnapSettingsWarning(self.iface)
  837. else:
  838. #if we have found a linesegment
  839. edge = snapMatch.edgePoints()
  840. p1 = edge[0]
  841. p2 = edge[1]
  842. # we like to mark the segment that is choosen, so we need a rubberband
  843. self.rb1.reset()
  844. color = QtGui.QColor(255,0,0)
  845. self.rb1.setColor(color)
  846. self.rb1.setWidth(2)
  847. self.rb1.addPoint(p1)
  848. self.rb1.addPoint(p2)
  849. self.rb1.show()
  850. self.segmentFound.emit([self.rb1.getPoint(0, 0), self.rb1.getPoint(0, 1), self.rb1])
  851. def reset(self, emitSignal = False):
  852. self.rb1.reset()
  853. class DtSplitFeatureTool(QgsMapToolAdvancedDigitizing, DtTool):
  854. finishedDigitizing = QtCore.pyqtSignal(QgsGeometry)
  855. def __init__(self, iface):
  856. super().__init__(canvas = iface.mapCanvas(), cadDockWidget = iface.cadDockWidget(),
  857. iface = iface, geometryTypes = [])
  858. self.marker = None
  859. self.rubberBand = None
  860. self.sketchRubberBand = self.createRubberBand()
  861. self.sketchRubberBand.setLineStyle(QtCore.Qt.DotLine)
  862. self.rbPoints = [] # array to store points in rubber band because
  863. # api to access points does not work properly or I did not figure it out :)
  864. self.currentMousePosition = None
  865. self.snapPoint = None
  866. self.reset()
  867. def activate(self):
  868. super().activate()
  869. self.canvas.setCursor(self.cursor)
  870. self.canvas.installEventFilter(self)
  871. self.snapPoint = None
  872. self.rbPoints = []
  873. def eventFilter(self, source, event):
  874. '''
  875. we need an eventFilter here to filter out Backspace key presses
  876. as otherwise the selected objects in the edit layer get deleted
  877. if user hits Backspace
  878. The eventFilter() function must return true if the event should be filtered,
  879. (i.e. stopped); otherwise it must return false, see
  880. http://doc.qt.io/qt-5/qobject.html#installEventFilter
  881. '''
  882. if event.type() == QtCore.QEvent.KeyPress:
  883. if event.key() == QtCore.Qt.Key_Backspace:
  884. if self.rubberBand != None:
  885. if self.rubberBand.numberOfVertices() >= 2: # QgsRubberBand has always 2 vertices
  886. if self.currentMousePosition != None:
  887. self.removeLastPoint()
  888. self.redrawSketchRubberBand([self.toMapCoordinates(self.currentMousePosition)])
  889. return True
  890. else:
  891. return False
  892. else:
  893. return False
  894. def eventToQPoint(self, event):
  895. x = event.pos().x()
  896. y = event.pos().y()
  897. thisPoint = QtCore.QPoint(x, y)
  898. return thisPoint
  899. def initRubberBand(self, firstPoint):
  900. if self.rubberBand == None:
  901. # create a QgsRubberBand
  902. self.rubberBand = self.createRubberBand()
  903. self.rubberBand.addPoint(firstPoint)
  904. self.rbPoints.append(firstPoint)
  905. def removeLastPoint(self):
  906. ''' remove the last point in self.rubberBand'''
  907. if len (self.rbPoints) > 1: #first point will not be removed
  908. self.rbPoints.pop()
  909. #we recreate rubberBand because it contains doubles
  910. self.rubberBand.reset()
  911. for aPoint in self.rbPoints:
  912. self.rubberBand.addPoint(QgsPointXY(aPoint))
  913. def trySnap(self, event):
  914. self.removeSnapMarker()
  915. self.snapPoint = None
  916. # try to snap
  917. thisPoint = self.eventToQPoint(event)
  918. snapper = self.canvas.snappingUtils()
  919. # snap to any layer within snap tolerance
  920. snapMatch = snapper.snapToMap(thisPoint)
  921. if not snapMatch.isValid():
  922. return False
  923. else:
  924. self.snapPoint = snapMatch.point()
  925. self.markSnap(self.snapPoint)
  926. return True
  927. def markSnap(self, thisPoint):
  928. self.marker = QgsVertexMarker(self.canvas)
  929. self.marker.setIconType(1)
  930. self.marker.setColor(QtGui.QColor(255,0,0))
  931. self.marker.setIconSize(12)
  932. self.marker.setPenWidth (3)
  933. self.marker.setCenter(thisPoint)
  934. def removeSnapMarker(self):
  935. if self.marker != None:
  936. self.canvas.scene().removeItem(self.marker)
  937. self.marker = None
  938. def clear(self):
  939. if self.rubberBand != None:
  940. self.rubberBand.reset()
  941. self.canvas.scene().removeItem(self.rubberBand)
  942. self.rubberBand = None
  943. if self.snapPoint != None:
  944. self.removeSnapMarker()
  945. self.snapPoint = None
  946. self.sketchRubberBand.reset()
  947. self.rbPoints = []
  948. def reset(self):
  949. self.clear()
  950. self.canvas.removeEventFilter(self)
  951. def redrawSketchRubberBand(self, points):
  952. if self.rubberBand != None and len(self.rbPoints) > 0:
  953. self.sketchRubberBand.reset()
  954. sketchStartPoint = self.rbPoints[len(self.rbPoints) -1]
  955. self.sketchRubberBand.addPoint(QgsPointXY(sketchStartPoint))
  956. if len(points) == 1:
  957. self.sketchRubberBand.addPoint(QgsPointXY(sketchStartPoint))
  958. self.sketchRubberBand.movePoint(
  959. self.sketchRubberBand.numberOfVertices() -1, points[0])
  960. #for p in range(self.rubberBand.size()):
  961. # self.debug("Part " + str(p))
  962. # for v in range(self.rubberBand.partSize(p)):
  963. # vertex = self.rubberBand.getPoint(0,j=v)
  964. # self.debug("Vertex " + str(v) + " = "+ str(vertex.x()) + ", " + str(vertex.y()))
  965. #startPoint = self.rubberBand.getPoint(0, self.rubberBand.partSize(0) -1)
  966. #self.debug("StartPoint " + str(startPoint))
  967. #self.sketchRubberBand.addPoint(startPoint)
  968. #self.sketchRubberBand.addPoint(points[len(points) - 1])
  969. else:
  970. for aPoint in points:
  971. self.sketchRubberBand.addPoint(aPoint)
  972. def cadCanvasMoveEvent(self, event):
  973. pass
  974. #self.debug("cadCanvasMoveEvent")
  975. def cadCanvasPressEvent(self, event):
  976. pass
  977. #self.debug("cadCanvasPressEvent")
  978. def cadCanvasReleaseEvent(self, event):
  979. pass
  980. #self.debug("cadCanvasReleaseEvent")
  981. def canvasMoveEvent(self, event):
  982. self.snapPoint = None
  983. thisPoint = self.eventToQPoint(event)
  984. hasSnap = self.trySnap(event)
  985. if self.rubberBand != None:
  986. if hasSnap:
  987. #if self.canvas.snappingUtils().config().enabled(): # is snapping active?
  988. tracer = QgsMapCanvasTracer.tracerForCanvas(self.canvas)
  989. if tracer.actionEnableTracing().isChecked(): # tracing is pressed in
  990. tracer.configure()
  991. #startPoint = self.rubberBand.getPoint(0, self.rubberBand.numberOfVertices() -1)
  992. startPoint = self.rbPoints[len(self.rbPoints) -1]
  993. pathPoints, pathError = tracer.findShortestPath(QgsPointXY(startPoint), self.snapPoint)
  994. if pathError == 0: #ErrNone
  995. pathPoints.pop(0) # remove first point as it is identical with starPoint
  996. self.redrawSketchRubberBand(pathPoints)
  997. else:
  998. self.redrawSketchRubberBand([self.snapPoint])
  999. else:
  1000. self.redrawSketchRubberBand([self.snapPoint])
  1001. else:
  1002. self.redrawSketchRubberBand([self.toMapCoordinates(thisPoint)])
  1003. self.currentMousePosition = thisPoint
  1004. def canvasReleaseEvent(self, event):
  1005. layer = self.canvas.currentLayer()
  1006. if layer != None:
  1007. thisPoint = self.eventToQPoint(event)
  1008. #QgsMapToPixel instance
  1009. if event.button() == QtCore.Qt.LeftButton:
  1010. if self.rubberBand == None:
  1011. if self.snapPoint == None:
  1012. self.initRubberBand(self.toMapCoordinates(thisPoint))
  1013. else: # last mouse move created a snap
  1014. self.initRubberBand(self.snapPoint)
  1015. self.snapPoint = None
  1016. self.removeSnapMarker()
  1017. else: # merge sketchRubberBand into rubberBand
  1018. sketchGeom = self.sketchRubberBand.asGeometry()
  1019. verticesSketchGeom = sketchGeom.vertices()
  1020. self.rubberBand.addGeometry(sketchGeom)
  1021. # rubberBand now contains a double point because it's former end point
  1022. # and sketchRubberBand's start point are identical
  1023. # so we remove the last point before adding new ones
  1024. self.rbPoints.pop()
  1025. while verticesSketchGeom.hasNext():
  1026. # add the new points
  1027. self.rbPoints.append(verticesSketchGeom.next())
  1028. self.redrawSketchRubberBand([self.toMapCoordinates(thisPoint)])
  1029. if self.snapPoint != None:
  1030. self.snapPoint = None
  1031. self.removeSnapMarker()
  1032. else: # right click
  1033. if self.rubberBand.numberOfVertices() > 1:
  1034. rbGeom = self.rubberBand.asGeometry()
  1035. self.finishedDigitizing.emit(rbGeom)
  1036. self.clear()
  1037. self.canvas.refresh()
  1038. def keyPressEvent(self, event):
  1039. if event.key() == QtCore.Qt.Key_Escape:
  1040. self.clear()
  1041. def deactivate(self):
  1042. self.reset()