| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274 |
- # -*- coding: utf-8 -*-
- """
- dttools
- `````````````
- """
- """
- 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 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(object):
- '''Abstract class; parent for any Dt tool or button'''
- 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 = []
- # ESRI shapefile does not distinguish between single and multi geometries
- # source of wkbType numbers: http://gdal.org/java/constant-values.html
- for aGeomType in geometryTypes:
- if aGeomType == 1: # wkbPoint
- self.geometryTypes.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(5)
- self.geometryTypes.append(-2147483646) #wkbLineString25D
- self.shapeFileGeometryTypes.append(-2147483646)
- elif aGeomType == 3: # wkbPolygon
- self.geometryTypes.append(3)
- self.shapeFileGeometryTypes.append(6)
- self.geometryTypes.append(-2147483645) #wkbPolygon25D
- self.shapeFileGeometryTypes.append(-2147483645)
- elif aGeomType == 4: # wkbMultiPoint
- self.geometryTypes.append(4)
- self.shapeFileGeometryTypes.append(1) # wkbPoint
- self.geometryTypes.append(-2147483644) #wkbMultiPoint25D
- self.shapeFileGeometryTypes.append(-2147483647) #wkbPoint25D
- elif aGeomType == 5: # wkbMultiLineString
- self.geometryTypes.append(5)
- self.shapeFileGeometryTypes.append(2) # wkbLineString
- self.geometryTypes.append(-2147483643) #wkbMultiLineString25D
- self.shapeFileGeometryTypes.append(-2147483646) #wkbLineString25D
- elif aGeomType == 6: # wkbMultiPolygon
- self.geometryTypes.append(6)
- self.shapeFileGeometryTypes.append(6) # wkbPolygon
- self.geometryTypes.append(-2147483642) #wkbMultiPolygon25D
- self.shapeFileGeometryTypes.append(-2147483645) #wkbPolygon25D
- def allowedGeometry(self, layer):
- '''check if this layer's geometry type is within the list of allowed types'''
- if layer.dataProvider().storageType() == u'ESRI Shapefile': # does not distinguish between single and multi
- result = self.shapeFileGeometryTypes.count(layer.wkbType()) >= 1
- else:
- result = self.geometryTypes.count(layer.wkbType()) == 1
- return result
- def geometryTypeMatchesLayer(self, layer, geom):
- '''check if the passed geom's geometry type matches the layer's type'''
- match = layer.wkbType() == geom.wkbType()
- if not match:
- if layer.dataProvider().storageType() == u'ESRI Shapefile':
- # 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) 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 \
- (layer.wkbType() == 5 and geom.wkbType() == 2) or \
- (layer.wkbType() == 6 and geom.wkbType() == 3)
- return match
- def isPolygonLayer(self, layer):
- ''' check if this layer is a polygon layer'''
- polygonTypes = [3, 6, -2147483645, -2147483642]
- result = layer.wkbType() in polygonTypes
- return result
- def debug(self, str):
- title = "DigitizingTools Debugger"
- QgsMessageLog.logMessage(title + "\n" + str)
- class DtSingleButton(DtTool):
- '''Abstract class for a single button
- icon [QtGui.QIcon]
- tooltip [str]
- geometryTypes [array:integer] 0=point, 1=line, 2=polygon'''
- def __init__(self, iface, toolBar, icon, tooltip, geometryTypes = [1, 2, 3], dtName = None):
- super().__init__(iface, geometryTypes)
- self.act = QtWidgets.QAction(icon, tooltip, self.iface.mainWindow())
- self.act.triggered.connect(self.process)
- if dtName != None:
- self.act.setObjectName(dtName)
- self.iface.currentLayerChanged.connect(self.enable)
- toolBar.addAction(self.act)
- self.geometryTypes = geometryTypes
- def process(self):
- raise NotImplementedError("Should have implemented process")
- def enable(self):
- '''Enables/disables the corresponding button.'''
- # Disable the Button by default
- self.act.setEnabled(False)
- layer = self.iface.activeLayer()
- if layer != None:
- #Only for vector layers.
- if layer.type() == QgsMapLayer.VectorLayer:
- if self.allowedGeometry(layer):
- self.act.setEnabled(layer.isEditable())
- try:
- layer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected
- except:
- pass
- try:
- layer.editingStopped.disconnect(self.enable) # when it becomes active layer again
- except:
- pass
- layer.editingStarted.connect(self.enable)
- layer.editingStopped.connect(self.enable)
- class DtSingleTool(DtSingleButton):
- '''Abstract class for a tool'''
- def __init__(self, iface, toolBar, icon, tooltip, geometryTypes = [0, 1, 2], crsWarning = True, dtName = None):
- super().__init__(iface, toolBar, icon, tooltip, geometryTypes, dtName)
- self.tool = None
- self.act.setCheckable(True)
- self.canvas.mapToolSet.connect(self.toolChanged)
- def toolChanged(self, thisTool):
- if thisTool != self.tool:
- self.deactivate()
- def deactivate(self):
- if self.tool != None:
- self.tool.reset()
- self.reset()
- self.act.setChecked(False)
- def reset(self):
- pass
- 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):
- super().__init__(iface, toolBar, icon, tooltip, geometryTypes, dtName)
- self.crsWarning = crsWarning
- self.editLayer = None
- def reset(self):
- self.editLayer = None
- def enable(self):
- '''Enables/disables the corresponding button.'''
- # Disable the Button by default
- doEnable = False
- layer = self.iface.activeLayer()
- if layer != None:
- if layer.type() == 0: #Only for vector layers.
- if self.allowedGeometry(layer):
- doEnable = layer.isEditable()
- try:
- layer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected
- except:
- pass
- try:
- layer.editingStopped.disconnect(self.enable) # when it becomes active layer again
- except:
- pass
- layer.editingStarted.connect(self.enable)
- 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
- 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()
- if doEnable and self.crsWarning:
- layerCRSSrsid = layer.crs().srsid()
- mapSet = self.canvas.mapSettings()
- projectCRSSrsid = mapSet.destinationCrs().srsid()
- if layerCRSSrsid != projectCRSSrsid:
- 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)
- class DtDualTool(DtTool):
- '''Abstract class for a tool with interactive and batch mode
- icon [QtGui.QIcon] for interactive mode
- tooltip [str] for interactive mode
- iconBatch [QtGui.QIcon] for batch mode
- tooltipBatch [str] for batch mode
- geometryTypes [array:integer] 0=point, 1=line, 2=polygon'''
- def __init__(self, iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes = [1, 2, 3], dtName = None):
- super().__init__(iface, geometryTypes)
- self.iface.currentLayerChanged.connect(self.enable)
- self.canvas.mapToolSet.connect(self.toolChanged)
- #create button
- self.button = QtWidgets.QToolButton(toolBar)
- self.button.clicked.connect(self.runSlot)
- self.button.toggled.connect(self.hasBeenToggled)
- #create menu
- 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(QtWidgets.QToolButton.MenuButtonPopup)
- # create actions
- self.act = QtWidgets.QAction(icon, tooltip, self.iface.mainWindow())
- if dtName != None:
- self.act.setObjectName(dtName + "Action")
- self.act.setToolTip(tooltip)
- self.act_batch = QtWidgets.QAction(iconBatch, tooltipBatch, self.iface.mainWindow())
- if dtName != None:
- self.act_batch.setObjectName(dtName + "BatchAction")
- self.act_batch.setToolTip(tooltipBatch)
- self.menu.addAction(self.act)
- self.menu.addAction(self.act_batch)
- # set the interactive action as default action, user needs to click the button to activate it
- self.button.setIcon(self.act.icon())
- self.button.setToolTip(self.act.toolTip())
- self.button.setCheckable(True)
- self.batchMode = False
- # add button to toolBar
- toolBar.addWidget(self.button)
- self.geometryTypes = geometryTypes
- # run the enable slot
- self.enable()
- def menuTriggered(self, thisAction):
- if thisAction == self.act:
- self.batchMode = False
- self.button.setCheckable(True)
- if not self.button.isChecked():
- self.button.toggle()
- else:
- self.batchMode = True
- if self.button.isCheckable():
- if self.button.isChecked():
- self.button.toggle()
- self.button.setCheckable(False)
- self.runSlot(False)
- self.button.setIcon(thisAction.icon())
- self.button.setToolTip(thisAction.toolTip())
- def toolChanged(self, thisTool):
- if thisTool != self.tool:
- self.deactivate()
- def hasBeenToggled(self, isChecked):
- raise NotImplementedError("Should have implemented hasBeenToggled")
- def deactivate(self):
- if self.button != None:
- if self.button.isChecked():
- self.button.toggle()
- def runSlot(self, isChecked):
- if self.batchMode:
- layer = self.iface.activeLayer()
- if layer.selectedFeatureCount() > 0:
- self.process()
- else:
- if not isChecked:
- self.button.toggle()
- def process(self):
- raise NotImplementedError("Should have implemented process")
- def enable(self):
- # Disable the Button by default
- self.button.setEnabled(False)
- layer = self.iface.activeLayer()
- if layer != None:
- #Only for vector layers.
- if layer.type() == QgsMapLayer.VectorLayer:
- # only for certain layers
- if self.allowedGeometry(layer):
- if not layer.isEditable():
- self.deactivate()
- self.button.setEnabled(layer.isEditable())
- try:
- layer.editingStarted.disconnect(self.enable) # disconnect, will be reconnected
- except:
- pass
- try:
- layer.editingStopped.disconnect(self.enable) # when it becomes active layer again
- except:
- pass
- layer.editingStarted.connect(self.enable)
- layer.editingStopped.connect(self.enable)
- else:
- self.deactivate()
- 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):
- super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName)
- self.tool = DtSelectFeatureTool(iface)
- def featureSelectedSlot(self, fids):
- if len(fids) >0:
- self.process()
- def hasBeenToggled(self, isChecked):
- try:
- self.tool.featureSelected.disconnect(self.featureSelectedSlot)
- # disconnect if it was already connected, so slot gets called only once!
- except:
- pass
- if isChecked:
- self.canvas.setMapTool(self.tool)
- self.tool.featureSelected.connect(self.featureSelectedSlot)
- 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):
- super().__init__(iface, toolBar, icon, tooltip, iconBatch, tooltipBatch, geometryTypes, dtName)
- self.tool = DtSelectVertexTool(self.iface, numVertices)
- def hasBeenToggled(self, isChecked):
- try:
- self.tool.vertexFound.disconnect(self.vertexSnapped)
- # disconnect if it was already connected, so slot gets called only once!
- except:
- pass
- if isChecked:
- self.canvas.setMapTool(self.tool)
- self.tool.vertexFound.connect(self.vertexSnapped)
- else:
- self.canvas.unsetMapTool(self.tool)
- def vertexSnapped(self, snapResult):
- raise NotImplementedError("Should have implemented vertexSnapped")
- class DtDualToolSelectRing(DtDualTool):
- '''
- Abstract class for a DtDualTool which uses the DtSelectRingTool for interactive mode
- '''
- def __init__(self, iface, toolBar, icon, tooltip, iconBatch,
- tooltipBatch, geometryTypes = [1, 2, 3], dtName = None):
- super().__init__(iface, toolBar, icon, tooltip,
- iconBatch, tooltipBatch, geometryTypes, dtName)
- self.tool = DtSelectRingTool(self.iface)
- def hasBeenToggled(self, isChecked):
- try:
- self.tool.ringSelected.disconnect(self.ringFound)
- # disconnect if it was already connected, so slot gets called only once!
- except:
- pass
- if isChecked:
- self.canvas.setMapTool(self.tool)
- self.tool.ringSelected.connect(self.ringFound)
- else:
- self.canvas.unsetMapTool(self.tool)
- def ringFound(self, selectRingResult):
- raise NotImplementedError("Should have implemented ringFound")
- class DtDualToolSelectGap(DtDualTool):
- '''
- Abstract class for a DtDualTool which uses the DtSelectGapTool for interactive mode
- '''
- def __init__(self, iface, toolBar, icon, tooltip, iconBatch,
- tooltipBatch, geometryTypes = [1, 2, 3], dtName = None,
- allLayers = False):
- super().__init__(iface, toolBar, icon, tooltip,
- iconBatch, tooltipBatch, geometryTypes, dtName)
- self.tool = DtSelectGapTool(self.iface, allLayers)
- def hasBeenToggled(self, isChecked):
- try:
- self.tool.gapSelected.disconnect(self.gapFound)
- # disconnect if it was already connected, so slot gets called only once!
- except:
- pass
- if isChecked:
- self.canvas.setMapTool(self.tool)
- self.tool.gapSelected.connect(self.gapFound)
- else:
- self.canvas.unsetMapTool(self.tool)
- def gapFound(self, selectGapResult):
- raise NotImplementedError("Should have implemented gapFound")
- 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)
- def deactivate(self):
- self.reset()
- def reset(self, emitSignal = False):
- pass
- def transformed(self, thisLayer, thisQgsPoint):
- layerCRSSrsid = thisLayer.crs().srsid()
- projectCRSSrsid = QgsProject.instance().crs().srsid()
- 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(DtMapToolEdit):
- featureSelected = QtCore.pyqtSignal(list)
- 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):
- '''
- return the feature this QPoint is in (polygon layer)
- or this QPoint snaps to (point or line layer)
- '''
- result = []
- if self.isPolygonLayer(layer):
- 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
- for fid in featureIds:
- feat = dtutils.dtGetFeatureForId(layer, fid)
- if feat != None:
- geom = QgsGeometry(feat.geometry())
- if geom.contains(thisQgsPoint):
- result.append(feat)
- result.append([])
- return result
- break
- else:
- if inRing:
- rings = dtutils.dtExtractRings(geom)
- if len(rings) > 0:
- for aRing in rings:
- if aRing.contains(thisQgsPoint):
- result.append(feat)
- result.append([])
- result.append(aRing)
- return result
- break
- else:
- #we need a snapper, so we use the MapCanvas snapper
- snapper = self.canvas.snappingUtils()
- snapper.setCurrentLayer(layer)
- # 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)
- else:
- feat = dtutils.dtGetFeatureForId(layer, snapMatch.featureId())
- if feat != None:
- result.append(feat)
- if snapMatch.hasVertex():
- result.append([snapMatch.point(), None])
- if snapMatch.hasEdge():
- result.append(snapMatch.edgePoints())
- return result
- return result
- def canvasReleaseEvent(self,event):
- #Get the click
- x = event.pos().x()
- y = event.pos().y()
- layer = self.canvas.currentLayer()
- if layer != None:
- #the clicked point is our starting point
- startingPoint = QtCore.QPoint(x,y)
- found = self.getFeatureForPoint(layer, startingPoint)
- if len(found) > 0:
- feat = found[0]
- layer.removeSelection()
- 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, iface):
- super().__init__(iface)
- def canvasReleaseEvent(self,event):
- #Get the click
- x = event.pos().x()
- y = event.pos().y()
- layer = self.canvas.currentLayer()
- if layer != None:
- #the clicked point is our starting point
- startingPoint = QtCore.QPoint(x,y)
- found = self.getFeatureForPoint(layer, startingPoint, inRing = True)
- if len(found) == 3:
- aRing = found[2]
- self.ringSelected.emit([aRing])
- def reset(self, emitSignal = False):
- pass
- class DtSelectGapTool(DtMapToolEdit):
- '''
- a map tool to select a gap between polygons, if allLayers
- is True then the gap is searched between polygons of
- all currently visible polygon layers
- '''
- gapSelected = QtCore.pyqtSignal(list)
- def __init__(self, iface, allLayers):
- super().__init__(iface)
- self.allLayers = allLayers
- def canvasReleaseEvent(self,event):
- #Get the click
- x = event.pos().x()
- y = event.pos().y()
- layer = self.canvas.currentLayer()
- visibleLayers = []
- if self.allLayers:
- for aLayer in self.iface.layerTreeCanvasBridge().rootGroup().checkedLayers():
- if 0 == aLayer.type():
- if self.isPolygonLayer(aLayer):
- visibleLayers.append(aLayer)
- else:
- 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 = self.transformed(layer, mapToPixel.toMapCoordinates(startingPoint))
- multiGeom = None
- for aLayer in visibleLayers:
- if not self.allLayers and aLayer.selectedFeatureCount() > 0:
- #we assume, that the gap is between the selected polyons
- hadSelection = True
- else:
- hadSelection = False
- spatialIndex = dtutils.dtSpatialindex(aLayer)
- # get the 100 closest Features
- featureIds = spatialIndex.nearestNeighbor(thisQgsPoint, 100)
- aLayer.select(featureIds)
- multiGeom = dtutils.dtCombineSelectedPolygons(aLayer, self.iface, multiGeom)
- if self.allLayers or not hadSelection:
- aLayer.removeSelection()
- if multiGeom == None:
- return None
- if multiGeom != None:
- rings = dtutils.dtExtractRings(multiGeom)
- if len(rings) > 0:
- for aRing in rings:
- if aRing.contains(thisQgsPoint):
- self.gapSelected.emit([aRing])
- break
- def reset(self, emitSignal = False):
- pass
- class DtSelectPartTool(DtSelectFeatureTool):
- '''signal sends featureId of clickedd feature, number of part selected and geometry of part'''
- partSelected = QtCore.pyqtSignal(list)
- def __init__(self, iface):
- super().__init__(iface)
- def canvasReleaseEvent(self,event):
- #Get the click
- x = event.pos().x()
- y = event.pos().y()
- layer = self.canvas.currentLayer()
- if layer != None:
- #the clicked point is our starting point
- startingPoint = QtCore.QPoint(x,y)
- found = self.getFeatureForPoint(layer, startingPoint)
- if len(found) > 0:
- feat = found[0]
- snappedPoints = found[1]
- if len(snappedPoints) > 0:
- snappedVertex = snappedPoints[0]
- else:
- snappedVertex = None
- geom = QgsGeometry(feat.geometry())
- # if feature geometry is multipart start split processing
- if geom.isMultipart():
- # Get parts from original feature
- parts = geom.asGeometryCollection()
- mapToPixel = self.canvas.getCoordinateTransform()
- thisQgsPoint = mapToPixel.toMapCoordinates(startingPoint)
- for i in range(len(parts)):
- # find the part that was snapped
- aPart = parts[i]
- if self.isPolygonLayer(layer):
- if aPart.contains(thisQgsPoint):
- self.partSelected.emit([feat.id(), i, aPart])
- break
- else:
- points = dtutils.dtExtractPoints(aPart)
- for j in range(len(points)):
- aPoint = points[j]
- if snappedVertex != None:
- if aPoint.x() == snappedVertex.x() and \
- aPoint.y() == snappedVertex.y():
- self.partSelected.emit([feat.id(), i, aPart])
- break
- else:
- try:
- nextPoint = points[j + 1]
- except:
- break
- if aPoint.x() == snappedPoints[0].x() and \
- aPoint.y() == snappedPoints[0].y() and \
- nextPoint.x() == snappedPoints[1].x() and \
- nextPoint.y() == snappedPoints[1].y():
- self.partSelected.emit([feat.id(), i, aPart])
- break
- class DtSelectVertexTool(DtMapToolEdit):
- '''select and mark numVertices vertices in the active layer'''
- vertexFound = QtCore.pyqtSignal(list)
- def __init__(self, iface, numVertices = 1):
- super().__init__(iface)
- # desired number of marked vertex until signal
- self.numVertices = numVertices
- # number of marked vertex
- self.count = 0
- # arrays to hold markers and vertex points
- self.markers = []
- self.points = []
- self.fids = []
- def canvasReleaseEvent(self,event):
- if self.count < self.numVertices: #not yet enough
- #Get the click
- x = event.pos().x()
- y = event.pos().y()
- layer = self.canvas.currentLayer()
- if layer != None:
- #the clicked point is our starting point
- startingPoint = QtCore.QPoint(x,y)
- #we need a snapper, so we use the MapCanvas snapper
- snapper = self.canvas.snappingUtils()
- snapper.setCurrentLayer(layer)
- # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment
- snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Vertex)
- if not snapMatch.isValid():
- #warn about missing snapping tolerance if appropriate
- dtutils.showSnapSettingsWarning(self.iface)
- else:
- #mark the vertex
- p = snapMatch.point()
- m = QgsVertexMarker(self.canvas)
- m.setIconType(1)
- if self.count == 0:
- m.setColor(QtGui.QColor(255,0,0))
- else:
- m.setColor(QtGui.QColor(0, 0, 255))
- m.setIconSize(12)
- m.setPenWidth (3)
- m.setCenter(p)
- self.points.append(p)
- self.markers.append(m)
- fid = snapMatch.featureId() # QgsFeatureId of the snapped geometry
- self.fids.append(fid)
- self.count += 1
- if self.count == self.numVertices:
- self.vertexFound.emit([self.points, self.markers, self.fids])
- #self.emit(SIGNAL("vertexFound(PyQt_PyObject)"), [self.points, self.markers])
- def reset(self, emitSignal = False):
- for m in self.markers:
- self.canvas.scene().removeItem(m)
- self.markers = []
- self.points = []
- self.fids = []
- self.count = 0
- class DtSelectSegmentTool(DtMapToolEdit):
- segmentFound = QtCore.pyqtSignal(list)
- def __init__(self, iface):
- super().__init__(iface)
- self.rb1 = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
- def canvasReleaseEvent(self,event):
- #Get the click
- x = event.pos().x()
- y = event.pos().y()
- layer = self.canvas.currentLayer()
- if layer != None:
- #the clicked point is our starting point
- startingPoint = QtCore.QPoint(x,y)
- #we need a snapper, so we use the MapCanvas snapper
- snapper = self.canvas.snappingUtils()
- snapper.setCurrentLayer(layer)
- # snapType = 0: no snap, 1 = vertex, 2 = segment, 3 = vertex & segment
- snapType = 2
- snapMatch = snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Edge)
- if not snapMatch.isValid():
- #warn about missing snapping tolerance if appropriate
- dtutils.showSnapSettingsWarning(self.iface)
- else:
- #if we have found a linesegment
- edge = snapMatch.edgePoints()
- p1 = edge[0]
- p2 = edge[1]
- # we like to mark the segment that is choosen, so we need a rubberband
- self.rb1.reset()
- color = QtGui.QColor(255,0,0)
- self.rb1.setColor(color)
- self.rb1.setWidth(2)
- self.rb1.addPoint(p1)
- self.rb1.addPoint(p2)
- self.rb1.show()
- self.segmentFound.emit([self.rb1.getPoint(0, 0), self.rb1.getPoint(0, 1), self.rb1])
- 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()
|