class PolygonDrawMapVisualisation(object): def __init__(self, canvas): self.canvas = canvas self.points = [] # temp layer for side profile trac self.rb = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.rb.setColor(Qt.red) self.rb.setFillColor(QColor(255, 0, 0, 64)) self.rb.setLineStyle(Qt.SolidLine) self.rb.setWidth(1) self.reset() def close(self): self.points = [] self.reset() # delete the rubberband we've been re-using self.canvas.scene().removeItem(self.rb) def show(self): self.rb.show() def hide(self): self.rb.hide() def add_point(self, point): self.points.append(point) self.rb.addPoint(point, True) self.rb.show() def reset(self): self.points = [] self.rb.reset(QgsWkbTypes.PolygonGeometry)
class MapSelectionTool(QgsMapTool): polygonCreated = pyqtSignal(QgsPointXY, QgsPointXY, Qt.KeyboardModifiers) def __init__(self, canvas: QgsMapCanvas) -> None: QgsMapTool.__init__(self, canvas) mFillColor = QColor(254, 178, 76, 63) self.canvas = canvas self.active = True self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(1) self.reset() def reset(self) -> None: self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e: QgsMapMouseEvent) -> None: self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e: QgsMapMouseEvent) -> None: self.isEmittingPoint = False self.rubberBand.hide() self.polygonCreated.emit(self.startPoint, self.endPoint, QApplication.keyboardModifiers()) def canvasMoveEvent(self, e: QgsMapMouseEvent) -> None: if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint: QgsPointXY, endPoint: QgsPointXY) -> None: self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def deactivate(self) -> None: self.rubberBand.hide() QgsMapTool.deactivate(self)
class RectangleAreaTool(QgsMapTool): rectangleCreated = pyqtSignal(float, float, float, float) def __init__(self, canvas, action): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.active = False self.setAction(action) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) mFillColor = QColor(254, 178, 76, 63) self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(1) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False self.rubberBand.hide() self.rectangleCreated.emit(self.startPoint.x(), self.startPoint.y(), self.endPoint.x(), self.endPoint.y()) def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates( e.pos() ) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def deactivate(self): self.rubberBand.hide() QgsMapTool.deactivate(self)
def highlight(self,geometry): def processEvents(): try: qApp.processEvents() except: QApplication.processEvents() highlight = QgsRubberBand(self.canvas, geometry.type()) highlight.setColor(QColor("#36AF6C")) highlight.setFillColor(QColor("#36AF6C")) highlight.setWidth(2) highlight.setToGeometry(geometry,self.canvas.currentLayer()) processEvents() sleep(.1) highlight.hide() processEvents() sleep(.1) highlight.show() processEvents() sleep(.1) highlight.reset() processEvents()
def highlight(self,geometry): def processEvents(): try: QtGui.qApp.processEvents() except: QtWidgets.QApplication.processEvents() highlight = QgsRubberBand(self.iface.mapCanvas(), geometry.type()) highlight.setColor(QtGui.QColor("#36AF6C")) highlight.setFillColor(QtGui.QColor("#36AF6C")) highlight.setWidth(2) highlight.setToGeometry(geometry,self.iface.mapCanvas().currentLayer()) processEvents() sleep(.1) highlight.hide() processEvents() sleep(.1) highlight.show() processEvents() sleep(.1) highlight.reset() processEvents()
class MultiLayerSelection(QgsMapTool): finished = QtCore.pyqtSignal(list) def __init__(self, canvas, iface): """ Tool Behaviours: (all behaviours start edition, except for rectangle one) 1- Left Click: Clears previous selection, selects feature, sets feature layer as active layer. The selection is done with the following priority: Point, Line then Polygon. Selection is only done in visible layer. 2- Control + Left Click: Adds to selection selected feature. This selection follows the priority in item 1. 3- Right Click: Opens feature form 4- Control + Right Click: clears selection and set feature's layer as activeLayer. activeLayer's definition follows priority of item 1; 5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangl'e are added to selection """ self.iface = iface self.canvas = canvas self.toolAction = None QgsMapTool.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon) self.hoverRubberBand = QgsRubberBand(self.canvas, QGis.Polygon) mFillColor = QColor(254, 178, 76, 63) self.rubberBand.setColor(mFillColor) self.hoverRubberBand.setColor(QColor(255, 0, 0, 90)) self.rubberBand.setWidth(1) self.reset() self.blackList = self.getBlackList() self.cursorChanged = False self.cursorChangingHotkey = QtCore.Qt.Key_Alt self.menuHovered = False # indicates hovering actions over context menu def keyPressEvent(self, e): """ Reimplemetation of keyPressEvent() in order to handle cursor changing hotkey (Alt). """ if e.key() == self.cursorChangingHotkey and not self.cursorChanged: self.cursorChanged = True QtGui.QApplication.setOverrideCursor(QCursor( Qt.PointingHandCursor)) else: self.cursorChanged = False QtGui.QApplication.restoreOverrideCursor() def getBlackList(self): settings = QSettings() settings.beginGroup('PythonPlugins/DsgTools/Options') valueList = settings.value('valueList') if valueList: valueList = valueList.split(';') return valueList else: return ['moldura'] def reset(self): """ Resets rubber band. """ self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QGis.Polygon) def canvasMoveEvent(self, e): """ Used only on rectangle select. """ if self.menuHovered: # deactivates rubberband when the context menu is "destroyed" self.hoverRubberBand.reset(QGis.Polygon) if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): """ Builds rubberband rect. """ self.rubberBand.reset(QGis.Polygon) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPoint(startPoint.x(), startPoint.y()) point2 = QgsPoint(startPoint.x(), endPoint.y()) point3 = QgsPoint(endPoint.x(), endPoint.y()) point4 = QgsPoint(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): """ Builds rectangle from self.startPoint and self.endPoint """ if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def setAction(self, action): self.toolAction = action self.toolAction.setCheckable(True) def canvasReleaseEvent(self, e): """ After the rectangle is built, here features are selected. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: firstGeom = self.checkSelectedLayers() self.isEmittingPoint = False r = self.rectangle() if r is None: return layers = self.canvas.layers() for layer in layers: #ignore layers on black list and features that are not vector layers if not isinstance(layer, QgsVectorLayer) or ( self.layerHasPartInBlackList(layer.name())): continue if firstGeom is not None and layer.geometryType() != firstGeom: # if there are features already selected, shift will only get the same type geometry # if more than one ty of geometry is present, only the strongest will be selected continue #builds bbRect and select from layer, adding selection bbRect = self.canvas.mapSettings().mapToLayerCoordinates( layer, r) layer.select(bbRect, True) self.rubberBand.hide() def canvasPressEvent(self, e): """ Method used to build rectangle if shift is held, otherwise, feature select/deselect and identify is done. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = True self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) else: self.isEmittingPoint = False self.createContextMenu(e) def getCursorRect(self, e): """ Calculates small cursor rectangle around mouse position. Used to facilitate operations """ p = self.toMapCoordinates(e.pos()) w = self.canvas.mapUnitsPerPixel() * 10 return QgsRectangle(p.x() - w, p.y() - w, p.x() + w, p.y() + w) def layerHasPartInBlackList(self, lyrName): """ Verifies if terms in black list appear on lyrName """ for item in self.getBlackList(): if item.lower() in lyrName.lower(): return True return False def getPrimitiveDict(self, e, hasControlModifier=False): """ Builds a dict with keys as geometryTypes of layer, which are QGis.Point (value 0), QGis.Line (value 1) or QGis.Polygon (value 2), and values as layers from self.iface.legendInterface().layers(). When self.iface.legendInterface().layers() is called, a list of layers ordered according to lyr order in TOC is returned. """ #these layers are ordered by view order primitiveDict = dict() firstGeom = self.checkSelectedLayers() for lyr in self.iface.legendInterface().layers(): #ordered layers #layer types other than VectorLayer are ignored, as well as layers in black list and layers that are not visible if (lyr.type() != QgsMapLayer.VectorLayer) or ( self.layerHasPartInBlackList(lyr.name()) ) or not self.iface.legendInterface().isLayerVisible(lyr): continue if hasControlModifier and ( not firstGeom) and (not primitiveDict.keys() or lyr.geometryType() < firstGeom): firstGeom = lyr.geometryType() geomType = lyr.geometryType() if geomType not in primitiveDict.keys(): primitiveDict[geomType] = [] #removes selection if (not hasControlModifier and e.button() == QtCore.Qt.LeftButton ) or (hasControlModifier and e.button() == QtCore.Qt.RightButton): lyr.removeSelection() primitiveDict[geomType].append(lyr) if hasControlModifier and firstGeom in [0, 1, 2]: return {firstGeom: primitiveDict[firstGeom]} else: return primitiveDict def deactivate(self): """ Deactivate tool. """ QtGui.QApplication.restoreOverrideCursor() self.hoverRubberBand.reset(QGis.Polygon) try: if self.toolAction: self.toolAction.setChecked(False) if self is not None: QgsMapTool.deactivate(self) except: pass def activate(self): """ Activate tool. """ if self.toolAction: self.toolAction.setChecked(True) QgsMapTool.activate(self) def setSelectionFeature(self, layer, feature, selectAll=False, setActiveLayer=False): """ Selects a given feature on canvas. :param layer: (QgsVectorLayer) layer containing the target feature. :param feature: (QgsFeature) taget feature to be selected. :param selectAll: (bool) indicates whether or not this fuction was called from a select all command. so it doesn't remove selection from those that are selected already from the list. :param setActiveLayer: (bool) indicates whether method should set layer as active. """ idList = layer.selectedFeaturesIds() featId = feature.id() if featId not in idList: idList.append(featId) elif not selectAll: idList.pop(idList.index(featId)) layer.setSelectedFeatures(idList) if setActiveLayer: layer.startEditing() self.iface.setActiveLayer(layer) return def setSelectionListFeature(self, dictLayerFeature, selectAll=True): """ Selects all features on canvas of a given dict. :param dictLayerFeature: (dict) dict of layers/features to be selected. :param selectAll: (bool) indicates if "All"-command comes from a "Select All". In that case, selected features won't be deselected. """ for layer in dictLayerFeature.keys(): geomType = layer.geometryType() # ID list of features already selected idList = layer.selectedFeaturesIds() # restart feature ID list for each layer featIdList = [] for feature in dictLayerFeature[layer]: featId = feature.id() if featId not in idList: idList.append(featId) elif not selectAll: idList.pop(idList.index(featId)) layer.setSelectedFeatures(idList) layer.startEditing() # last layer is set active and self.iface.setActiveLayer(layer) def openMultipleFeatureForm(self, dictLayerFeature): """ Opens all features Attribute Tables of a given list. :param dictLayerFeature: (dict) dict of layers/features to have their feature form exposed. """ for layer, features in dictLayerFeature.iteritems(): for feat in features: self.iface.openFeatureForm(layer, feat, showModal=False) def filterStrongestGeometry(self, dictLayerFeature): """ Filter a given dict of features for its strongest geometry. :param dictLayerFeature: (dict) a dict of layers and its features to be filtered. :return: (dict) filtered dict with only layers of the strongest geometry on original dict. """ strongest_geometry = 3 outDict = dict() if dictLayerFeature: for lyr in dictLayerFeature.keys(): # to retrieve strongest geometry value if strongest_geometry > lyr.geometryType(): strongest_geometry = lyr.geometryType() if strongest_geometry == 0: break for lyr in dictLayerFeature.keys(): if lyr.geometryType() == strongest_geometry: outDict[lyr] = dictLayerFeature[lyr] return outDict def createRubberBand(self, feature, layer, geom): """ Creates a rubber band around from a given a standard feature string. :param feature: taget feature to be highlighted :param layer: layer containing the target feature :param geom: int indicating geometry type of target feature """ if geom == 0: self.hoverRubberBand.reset(QGis.Point) elif geom == 1: self.hoverRubberBand.reset(QGis.Line) else: self.hoverRubberBand.reset(QGis.Polygon) self.hoverRubberBand.addGeometry(feature.geometry(), layer) # to inform the code that menu has been hovered over self.menuHovered = True def createMultipleRubberBand(self, dictLayerFeature): """ Creates rubberbands around features. :param dictLayerFeature: (dict) dict of layer/features to have rubberbands built around. """ # only one type of geometry at a time will have rubberbands around it geom = dictLayerFeature.keys()[0].geometryType() if geom == 0: self.hoverRubberBand.reset(QGis.Point) elif geom == 1: self.hoverRubberBand.reset(QGis.Line) else: self.hoverRubberBand.reset(QGis.Polygon) for layer, features in dictLayerFeature.iteritems(): for feat in features: self.hoverRubberBand.addGeometry(feat.geometry(), layer) self.menuHovered = True def checkSelectedLayers(self): """ Checks if there are layers selected on canvas. If there are, returns the geometry type of selected feature(s). If more than one type of feature is selected, the "strongest" geometry is returned. """ geom = None for layer in self.iface.legendInterface().layers(): if isinstance(layer, QgsVectorLayer): selection = layer.selectedFeatures() if len(selection): if geom == None: geom = layer.geometryType() continue elif layer.geometryType() < geom: geom = layer.geometryType() continue return geom def addCallBackToAction(self, action, onTriggeredAction, onHoveredAction=None): """ Adds action the command to the action. If onHoveredAction is given, signal "hovered" is applied with given action. :param action: (QAction) associated with target context menu. :param onTriggeredAction: (object) action to be executed when the given action is triggered. :param onHoveredAction: (object) action to be executed whilst the given action is hovered. """ action.triggered[()].connect(onTriggeredAction) if onHoveredAction: action.hovered[()].connect(onHoveredAction) def getCallback(self, e, layer, feature, geomType=None, selectAll=True): """ Gets the callback for an action. :param e: (QMouseEvent) mouse event on canvas. :param layer: (QgsVectorLayer) layer to be treated. :param feature: (QgsFeature) feature to be treated. :param geomType: (int) code indicating layer geometry type. It is retrieved OTF in case it's not given. :return: (tuple-of function_lambda) callbacks for triggered and hovered signals. """ if not geomType: geomType = layer.geometryType() if e.button() == QtCore.Qt.LeftButton: # line added to make sure the action is associated with current loop value, # lambda function is used with standard parameter set to current loops value. triggeredAction = lambda t=[ layer, feature ]: self.setSelectionFeature( t[0], feature=t[1], selectAll=selectAll, setActiveLayer=True) hoveredAction = lambda t=[layer, feature]: self.createRubberBand( feature=t[1], layer=t[0], geom=geomType) elif e.button() == QtCore.Qt.RightButton: selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if selected: triggeredAction = lambda layer=layer: self.iface.setActiveLayer( layer) hoveredAction = None else: triggeredAction = lambda t=[ layer, feature ]: self.iface.openFeatureForm(t[0], t[1], showModal=False) hoveredAction = lambda t=[ layer, feature ]: self.createRubberBand( feature=t[1], layer=t[0], geom=geomType) return triggeredAction, hoveredAction def getCallbackMultipleFeatures(self, e, dictLayerFeature, selectAll=True): """ Sets the callback of an action with a list features as target. :param e: (QMouseEvent) mouse event on canvas. :param dictLayerFeature: (dict) dictionary containing layers/features to be treated. :return: (tuple-of function_lambda) callbacks for triggered and hovered signals. """ # setting the action for the "All" options if e.button() == QtCore.Qt.LeftButton: triggeredAction = lambda t=dictLayerFeature: self.setSelectionListFeature( dictLayerFeature=t, selectAll=selectAll) else: triggeredAction = lambda t=dictLayerFeature: self.openMultipleFeatureForm( dictLayerFeature=t) # to trigger "Hover" signal on QMenu for the multiple options hoveredAction = lambda t=dictLayerFeature: self.createMultipleRubberBand( dictLayerFeature=t) return triggeredAction, hoveredAction def createSubmenu(self, e, parentMenu, menuDict, genericAction, selectAll): """ Creates a submenu in a given parent context menu and populates it, with classes/feature sublevels from the menuDict. :param e: (QMouseEvent) mouse event on canvas. If menuDict has only 1 class in it, method will populate parent QMenu. :param parentMenu: (QMenu) menu containing the populated submenu :param menuDict: (dict) dictionary containing all classes and their features to be filled into submenu. :param genericAction: (str) text to be shown into generic action description on the outter level of submenu. :return: (dict) mapping of classes and their own QMenu object. """ # creating a dict to handle all "menu" for each class submenuDict = dict() for cl in menuDict.keys(): # menu for features of each class className = cl.name() geomType = cl.geometryType() # get layer database name dsUri = cl.dataProvider().dataSourceUri() temp = [] if '/' in dsUri or '\\' in dsUri: db_name = dsUri else: db_name = cl.dataProvider().dataSourceUri().split("'")[1] if len(menuDict) == 1: # if dictionaty has only 1 class, no need for an extra QMenu - features will be enlisted directly for feat in menuDict[cl]: s = '{0}.{1} (feat_id = {2})'.format( db_name, className, feat.id()) # inserting action for each feature action = parentMenu.addAction(s) triggeredAction, hoveredAction = self.getCallback( e=e, layer=cl, feature=feat, geomType=geomType, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # inserting generic action, if necessary if len(menuDict[cl]) > 1: # if there are more than 1 feature to be filled, "All"-command should be added action = parentMenu.addAction( self.tr("{0} From Class {1}").format( genericAction, className)) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature=menuDict, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # there is no mapping of class to be exposed, only information added to parent QMenu itself return dict() submenuDict[cl] = QtGui.QMenu(title='{0}.{1}'.format( db_name, className), parent=parentMenu) parentMenu.addMenu(submenuDict[cl]) # inserting an entry for every feature of each class in its own context menu for feat in menuDict[cl]: s = 'feat_id = {0}'.format(feat.id()) action = submenuDict[cl].addAction(s) triggeredAction, hoveredAction = self.getCallback( e=e, layer=cl, feature=feat, geomType=geomType, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # set up list for the "All"-commands temp.append([cl, feat, geomType]) # adding generic action for each class if len(menuDict[cl]) > 1: # if there are more than 1 feature to be filled, "All"-command should be added action = submenuDict[cl].addAction( self.tr("{0} From Class {1}").format( genericAction, className)) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature={cl: menuDict[cl]}, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) return submenuDict def setContextMenuStyle(self, e, dictMenuSelected, dictMenuNotSelected): """ Defines how many "submenus" the context menu should have. There are 3 context menu scenarios to be handled: :param e: (QMouseEvent) mouse event on canvas. :param dictMenuSelected: (dict) dictionary of classes and its selected features being treatead. :param dictMenuNotSelected: (dict) dictionary of classes and its non selected features being treatead. """ # finding out filling conditions selectedDict = bool(dictMenuSelected) notSelectedDict = bool(dictMenuNotSelected) # finding out if one of either dictionaty are filled ("Exclusive or") selectedXORnotSelected = (selectedDict != notSelectedDict) # setting "All"-command name if e.button() == QtCore.Qt.RightButton: genericAction = self.tr('Open All Attribute Tables') else: genericAction = self.tr('Select All Features') # in case one of given dict is empty if selectedXORnotSelected: if selectedDict: menuDict, menu = dictMenuSelected, QtGui.QMenu( title=self.tr('Selected Features')) genericAction = self.tr('Deselect All Features') # if the dictionary is from selected features, we want commands to be able to deselect them selectAll = False else: menuDict, menu = dictMenuNotSelected, QtGui.QMenu( title=self.tr('Not Selected Features')) genericAction = self.tr('Select All Features') # if the dictionary is from non-selected features, we want commands to be able to select them selectAll = True if e.button() == QtCore.Qt.RightButton: genericAction = self.tr('Open All Attribute Tables') self.createSubmenu(e=e, parentMenu=menu, menuDict=menuDict, genericAction=genericAction, selectAll=selectAll) if len(menuDict) != 1 and len(menuDict.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = menu.addAction(genericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature=menuDict, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) elif selectedDict: # if both of them is empty one more QMenu level is added menu = QtGui.QMenu() selectedMenu = QtGui.QMenu(title=self.tr('Selected Features')) notSelectedMenu = QtGui.QMenu( title=self.tr('Not Selected Features')) menu.addMenu(selectedMenu) menu.addMenu(notSelectedMenu) selectedGenericAction = self.tr('Deselect All Features') notSelectedGenericAction = self.tr('Select All Features') # selectAll is set to True as now we want command to Deselect Features in case they are selected self.createSubmenu(e=e, parentMenu=selectedMenu, menuDict=dictMenuSelected, genericAction=selectedGenericAction, selectAll=False) if len(dictMenuSelected) != 1 and len( dictMenuSelected.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = selectedMenu.addAction(selectedGenericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature=dictMenuSelected, selectAll=False) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) self.createSubmenu(e=e, parentMenu=notSelectedMenu, menuDict=dictMenuNotSelected, genericAction=notSelectedGenericAction, selectAll=True) if len(dictMenuNotSelected) != 1 and len( dictMenuNotSelected.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = notSelectedMenu.addAction(notSelectedGenericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures( e=e, dictLayerFeature=dictMenuNotSelected, selectAll=True) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) menu.exec_(self.canvas.viewport().mapToGlobal(e.pos())) def checkSelectedFeaturesOnDict(self, menuDict): """ Checks all selected features from a given dictionary ( { (QgsVectorLayer)layer : [ (QgsFeature)feat ] } ). :param menuDict: (dict) dictionary with layers and their features to be analyzed. :return: (tuple-of-dict) both dictionaries of selected and non-selected features of each layer. """ selectedFeaturesDict, notSelectedFeaturesDict = dict(), dict() for cl in menuDict.keys(): selectedFeats = [f.id() for f in cl.selectedFeatures()] for feat in menuDict[cl]: if feat.id() in selectedFeats: if cl not in selectedFeaturesDict.keys(): selectedFeaturesDict[cl] = [feat] else: selectedFeaturesDict[cl].append(feat) else: if cl not in notSelectedFeaturesDict.keys(): notSelectedFeaturesDict[cl] = [feat] else: notSelectedFeaturesDict[cl].append(feat) return selectedFeaturesDict, notSelectedFeaturesDict def reprojectSearchArea(self, layer, geom): """ Reprojects search area if necessary, according to what is being searched. :param layer: (QgsVectorLayer) layer which target rectangle has to have same SRC. :param geom: (QgsRectangle) rectangle representing search area. """ #geom always have canvas coordinates epsg = self.canvas.mapSettings().destinationCrs().authid() #getting srid from something like 'EPSG:31983' srid = layer.crs().authid() if epsg == srid: return geom crsSrc = QgsCoordinateReferenceSystem(epsg) crsDest = QgsCoordinateReferenceSystem(srid) # Creating a transformer coordinateTransformer = QgsCoordinateTransform( crsSrc, crsDest) # here we have to put authid, not srid auxGeom = QgsGeometry.fromRect(geom) auxGeom.transform(coordinateTransformer) return auxGeom.boundingBox() def createContextMenu(self, e): """ Creates the context menu for overlapping layers. :param e: mouse event caught from canvas. """ selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if selected: firstGeom = self.checkSelectedLayers() # setting a list of features to iterate over layerList = self.getPrimitiveDict(e, hasControlModifier=selected) layers = [] for key in layerList.keys(): layers += layerList[key] if layers: rect = self.getCursorRect(e) lyrFeatDict = dict() for layer in layers: if not isinstance(layer, QgsVectorLayer): continue geomType = layer.geometryType() # iterate over features inside the mouse bounding box bbRect = self.canvas.mapSettings().mapToLayerCoordinates( layer, rect) for feature in layer.getFeatures(QgsFeatureRequest(bbRect)): geom = feature.geometry() if geom: searchRect = self.reprojectSearchArea(layer, rect) if selected: # if Control was held, appending behaviour is different if not firstGeom: firstGeom = geomType elif firstGeom > geomType: firstGeom = geomType if geomType == firstGeom and geom.intersects( searchRect): # only appends features if it has the same geometry as first selected feature if layer in lyrFeatDict.keys(): lyrFeatDict[layer].append(feature) else: lyrFeatDict[layer] = [feature] else: if geom.intersects(searchRect): if layer in lyrFeatDict.keys(): lyrFeatDict[layer].append(feature) else: lyrFeatDict[layer] = [feature] lyrFeatDict = self.filterStrongestGeometry(lyrFeatDict) if lyrFeatDict: moreThanOneFeat = lyrFeatDict.values() and len( lyrFeatDict.values()) > 1 or len( lyrFeatDict.values()[0]) > 1 if moreThanOneFeat: # if there are overlapping features (valid candidates only) selectedFeaturesDict, notSelectedFeaturesDict = self.checkSelectedFeaturesOnDict( menuDict=lyrFeatDict) self.setContextMenuStyle( e=e, dictMenuSelected=selectedFeaturesDict, dictMenuNotSelected=notSelectedFeaturesDict) else: layer = lyrFeatDict.keys()[0] feature = lyrFeatDict[layer][0] selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if e.button() == QtCore.Qt.LeftButton: # if feature is selected, we want it to be de-selected self.setSelectionFeature(layer=layer, feature=feature, selectAll=False, setActiveLayer=True) elif selected: self.iface.setActiveLayer(layer) else: self.iface.openFeatureForm(layer, feature, showModal=False)
class RectangleMapTool(QgsMapToolEmitPoint): boxCreated = pyqtSignal(QgsRectangle) def __init__(self, canvas): self.canvas = canvas QgsMapToolEmitPoint.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes().GeometryType(2)) self.rubberBand.setColor(Qt.red) self.rubberBand.setBrushStyle(Qt.Dense6Pattern) self.rubberBand.setWidth(1) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes().GeometryType(2)) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False r = self.rectangle() if r is not None: self.boxCreated.emit(r) def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes().GeometryType(2)) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): return None self.rubberBand.hide() srsCrs = self.canvas.mapSettings().destinationCrs() dstCrs = QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem.EpsgCrsId) coordinateTransformer = QgsCoordinateTransform(srsCrs, dstCrs, QgsProject.instance()) rect = coordinateTransformer.transform( QgsRectangle(self.startPoint, self.endPoint)) return rect
class RectangleAreaTool(QgsMapTool): rectangleCreated = pyqtSignal(float, float, float, float) def __init__(self, canvas, action): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.active = False self.setAction(action) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) mFillColor = QColor(254, 178, 76, 63) self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(1) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False self.rubberBand.hide() self.transformCoordinates() self.rectangleCreated.emit(self.startPoint.x(), self.startPoint.y(), self.endPoint.x(), self.endPoint.y()) def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def transformCoordinates(self): if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): return None # Defining the crs from src and destiny epsg = self.canvas.mapSettings().destinationCrs().authid() crsSrc = QgsCoordinateReferenceSystem(epsg) crsDest = QgsCoordinateReferenceSystem(4326) # Creating a transformer coordinateTransformer = QgsCoordinateTransform(crsSrc, crsDest, QgsProject.instance()) # Transforming the points self.startPoint = coordinateTransformer.transform(self.startPoint) self.endPoint = coordinateTransformer.transform(self.endPoint) def deactivate(self): self.rubberBand.hide() QgsMapTool.deactivate(self)
class RectFromCenterTool(QgsMapTool): msgbar = pyqtSignal(str) rbFinished = pyqtSignal(object) rb_reset_signal = pyqtSignal() def __init__(self, canvas): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.nbPoints = 0 self.rb = None self.mCtrl = None self.shift = False self.xc, self.yc, self.p2 = None, None, None self.distance = QgsDistanceArea() self.distance.setSourceCrs(QgsCoordinateReferenceSystem(4326), QgsProject.instance().transformContext()) self.distance.setEllipsoid('WGS84') self.x_length = 0 self.y_length = 0 # our own fancy cursor self.cursor = QCursor( QPixmap([ "16 16 3 1", " c None", ". c #FF0000", "+ c #17a51a", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. . .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. . .+ ", " ++.....+ ", " ++.++ ", " +.+ " ])) self.curr_geom = None self.last_currpoint = None self.curr_angle = 0.0 self.total_angle = 0.0 self.rectangle = Rectangle() self.angle_ini_rot = 0.0 self.ini_geom = None def keyPressEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = True self.point_ini_rot = self.toMapCoordinates( self.canvas.mouseLastXY()) self.angle_ini_rot = self.curr_angle if event.key() == Qt.Key_Shift: self.shift = True def keyReleaseEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = False if event.key() == Qt.Key_Escape: self.nbPoints = 0 self.xc, self.yc, self.p2 = None, None, None if self.rb: self.rb.reset(True) self.rb = None self.canvas.refresh() self.rb_reset_signal.emit() return def changegeomSRID(self, geom): layer = self.canvas.currentLayer() layerCRSSrsid = layer.crs().srsid() projectCRSSrsid = QgsMapSettings().destinationCrs().srsid() if layerCRSSrsid != projectCRSSrsid: g = QgsGeometry.fromPoint(geom) g.transform(QgsCoordinateTransform(projectCRSSrsid, layerCRSSrsid)) ret_point = g.asPoint() else: ret_point = geom return ret_point def canvasPressEvent(self, event): layer = self.canvas.currentLayer() if self.nbPoints == 0: color = QColor(255, 0, 0, 128) if self.rb: self.rb.reset() self.rb = None self.rb = QgsRubberBand(self.canvas, True) self.rb.setColor(color) self.rb.setWidth(1) self.canvas.refresh() self.rb_reset_signal.emit() point = self.toLayerCoordinates(layer, event.pos()) point_map = self.toMapCoordinates(layer, point) if self.nbPoints == 0: self.xc = point_map.x() self.yc = point_map.y() else: self.p2 = point_map self.nbPoints += 1 if self.nbPoints == 2: # geom = self.rectangle.getRectFromCenter(QgsPointXY(self.xc, self.yc), self.p2, self.curr_angle) self.nbPoints = 0 self.xc, self.yc, self.p2 = None, None, None self.last_currpoint = None self.curr_angle = 0.0 self.total_angle = 0.0 self.mCtrl = False self.rbFinished.emit(self.curr_geom) self.curr_geom = None if self.rb: return def canvasMoveEvent(self, event): if not self.rb or not self.xc or not self.yc: return currpoint = self.toMapCoordinates(event.pos()) self.msgbar.emit( "Adjust lengths along track and across track. Hold Ctrl to adjust track orientation. Click when finished." ) if not self.mCtrl: self.curr_geom = self.rectangle.get_rect_from_center( QgsPointXY(self.xc, self.yc), currpoint, self.curr_angle) self.ini_geom = self.curr_geom if self.curr_angle != 0: self.curr_geom, self.curr_angle = self.rectangle.get_rect_rotated( self.curr_geom, QgsPointXY(self.xc, self.yc), delta=self.curr_angle) self.x_length = self.distance.measureLine( self.curr_geom.asPolygon()[0][0], self.curr_geom.asPolygon()[0][1]) self.y_length = self.distance.measureLine( self.curr_geom.asPolygon()[0][1], self.curr_geom.asPolygon()[0][2]) self.curr_geom = self.rectangle.get_rect_projection( self.curr_geom, QgsPointXY(self.xc, self.yc), self.x_length, self.y_length) elif self.ini_geom is not None: if self.last_currpoint is None: self.last_currpoint = currpoint self.curr_geom, self.curr_angle = self.rectangle.get_rect_rotated( self.ini_geom, QgsPointXY(self.xc, self.yc), currpoint, self.point_ini_rot, self.angle_ini_rot) self.last_currpoint = currpoint self.curr_geom = self.rectangle.get_rect_projection( self.curr_geom, QgsPointXY(self.xc, self.yc), self.x_length, self.y_length) if self.curr_geom is not None: self.rb.setToGeometry(self.curr_geom, None) def show_settings_warning(self): pass def activate(self): self.canvas.setCursor(self.cursor) def deactivate(self): self.nbPoints = 0 self.xc, self.yc, self.p2 = None, None, None if self.rb: self.rb.reset(True) self.rb.hide() self.rb = None self.canvas.refresh() def is_zoom_tool(self): return False def is_transient(self): return False def is_edit_tool(self): return True
class DrawRectangle(QgsMapTool): """ This class is responsible for drawing a rectangle on the map and returning the coordinates of it. """ rectangleCreated = pyqtSignal(float, float, float, float) def __init__(self, canvas, parent): """ Initialize the draw rectangle class :param canvas: Canvas to be displayed :param parent: Parent dialog, which initialized the class (should be JobAdaptDialog) """ QgsMapTool.__init__(self, canvas) self.canvas = canvas self.active = False self.parent = parent self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.rubberBand.setColor(QColor(254, 178, 76, 63)) self.rubberBand.setWidth(1) self.startPoint = None self.endPoint = None self.isEmittingPoint = False self.reset() def reset(self): """ Reseting the rectangle """ self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): """ Called if a mouse button got pressed on the map canvas, taking the starting point of the rectangle. :param e: Event """ self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.show_rect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): """ Called if a mouse button got released on the map canvas, finishing the selection and returning to the parent dialog. :param e: Event """ self.isEmittingPoint = False self.rubberBand.show() self.parent.draw_rect(self.startPoint.x(), self.startPoint.y(), self.endPoint.x(), self.endPoint.y()) def canvasMoveEvent(self, e): """ Called if a mouse moves over the map canvas, taking the end point of the rectangle. :param e: Event """ if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.show_rect(self.startPoint, self.endPoint) def show_rect(self, start_point, end_point): """ Showing the rectangle with the given start and end point. :param start_point: Point: Starting point of the rectangle drawing :param end_point: Point: End point of the rectangle drawing """ self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if start_point.x() == end_point.x() or start_point.y() == end_point.y( ): return point1 = QgsPointXY(start_point.x(), start_point.y()) point2 = QgsPointXY(start_point.x(), end_point.y()) point3 = QgsPointXY(end_point.x(), end_point.y()) point4 = QgsPointXY(end_point.x(), start_point.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def deactivate(self): """ Deactivating the rectangle """ self.rubberBand.hide() QgsMapTool.deactivate(self)
class QgepMapToolConnectNetworkElements(QgsMapTool): """ This map tool connects wastewater networkelements. It works on two lists of layers: source layers with fields with a foreign key to a networkelement target layers which depict networkelements (reaches and network nodes) The tool will snap to source layers first and once one is chosen to a target layer. It will then ask which field(s) should be connected and perform the update on the database """ def __init__(self, iface, action): QgsMapTool.__init__(self, iface.mapCanvas()) self.iface = iface self.action = action self.rbline = QgsRubberBand(self.iface.mapCanvas(), QGis.Line) self.rbline.setColor(QColor('#f4530e')) self.rbline.setWidth(3) self.rbmarkers = QgsRubberBand(self.iface.mapCanvas(), QGis.Point) self.rbmarkers.setColor(QColor('#f4530e')) self.rbmarkers.setIconSize(6) self.source_snapper = QgepAreaSnapper(self.iface.mapCanvas()) self.target_snapper = QgepAreaSnapper(self.iface.mapCanvas()) self.source_feature = QgsFeature() self.rb_source_feature = QgsRubberBand(self.iface.mapCanvas()) self.rb_source_feature.setColor(QColor('#f49e79')) self.rb_source_feature.setWidth(3) self.target_feature = QgsFeature() self.rb_target_feature = QgsRubberBand(self.iface.mapCanvas()) self.rb_target_feature.setColor(QColor('#f49e79')) self.rb_target_feature.setWidth(3) def activate(self): """ Called by QGIS whenever the tool is activated. """ # A dict of layers # and for each layer the fields to use as foreign key # as well as the possible target layers # Reaches can be connected to reaches and nodes # Catchment areas only to nodes self.network_element_sources = { QgepLayerManager.layer('vw_qgep_reach'): { 'fields': [ ('rp_to_fk_wastewater_networkelement', QCoreApplication.translate('QgepMapToolConnectNetworkElements', 'Reach Point To')), ('rp_from_fk_wastewater_networkelement', QCoreApplication.translate('QgepMapToolConnectNetworkElements', 'Reach Point From')) ], 'target_layers': [ QgepLayerManager.layer('vw_wastewater_node'), QgepLayerManager.layer('vw_qgep_reach') ]}, QgepLayerManager.layer('od_catchment_area'): {'fields': [ ('fk_wastewater_networkelement_rw_current', QCoreApplication.translate( 'QgepMapToolConnectNetworkElements', 'Rainwater current')), ('fk_wastewater_networkelement_rw_planned', QCoreApplication.translate( 'QgepMapToolConnectNetworkElements', 'Rainwater planned')), ('fk_wastewater_networkelement_ww_current', QCoreApplication.translate( 'QgepMapToolConnectNetworkElements', 'Wastewater current')), ('fk_wastewater_networkelement_ww_planned', QCoreApplication.translate( 'QgepMapToolConnectNetworkElements', 'Wastewater planned')) ], 'target_layers': [ QgepLayerManager.layer('vw_wastewater_node') ]} } self.setSnapLayers(self.source_snapper, self.network_element_sources.keys()) self.reset() self.action.setChecked(True) self.iface.mapCanvas().setCursor(QCursor(Qt.CrossCursor)) def setSnapLayers(self, snapper, layers): snap_layers = list() for layer in layers: if layer: snap_layer = QgsSnappingUtils.LayerConfig( layer, QgsPointLocator.All, 16, QgsTolerance.Pixels) snap_layers.append(snap_layer) snapper.setLayers(snap_layers) snapper.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced) def canvasMoveEvent(self, event): """ When the mouse moves, update the rubberbands. """ pt = event.originalMapPoint() snap_match = self.snapper.snapToMap(pt) if snap_match.isValid(): if snap_match.type() != QgsPointLocator.Area: pt = snap_match.point() self.matchpoint = pt if self.source_match: # There is already a source feature : snap to target feature # candidates if self.target_feature.id() != snap_match.featureId(): self.target_feature = self.get_feature_for_match( snap_match) self.rb_target_feature.setToGeometry( self.target_feature.geometry(), snap_match.layer()) self.rb_target_feature.show() self.rbmarkers.movePoint(pt) else: # Snapped to source feature, update source feature rubber band # and target layer snapper if self.source_feature.id() != snap_match.featureId(): self.source_feature = self.get_feature_for_match( snap_match) self.rb_source_feature.setToGeometry( self.source_feature.geometry(), snap_match.layer()) self.setSnapLayers(self.target_snapper, self.network_element_sources[ snap_match.layer()]['target_layers']) self.rb_source_feature.show() self.rbmarkers.movePoint(pt, 0) self.rbmarkers.show() else: self.rbmarkers.hide() if self.source_match: self.rb_target_feature.hide() else: self.rb_source_feature.hide() self.rbline.movePoint(pt) self.snapresult = snap_match def canvasReleaseEvent(self, event): """ On a click update the rubberbands and the snapping results if it's a left click. Reset if it's a right click. """ if event.button() == Qt.LeftButton: if self.snapresult.isValid(): if self.source_match: self.connect_features(self.source_match, self.snapresult) else: self.rbline.show() self.rbline.addPoint(self.matchpoint) self.source_match = self.snapresult self.snapper = self.target_snapper else: self.reset() def deactivate(self): """ Called by QGIS whenever this tool is deactivated. """ self.reset() self.action.setChecked(False) def reset(self): """ Resets the tool to a pristine state """ self.source_match = None self.rbline.hide() self.rbline.reset() self.rbmarkers.hide() self.rbmarkers.reset(QGis.Point) self.rbmarkers.addPoint(QgsPoint()) self.snapresult = None self.source_match = None self.snapper = self.source_snapper self.source_feature = QgsFeature() self.target_feature = QgsFeature() self.rb_source_feature.reset() self.rb_target_feature.reset() def get_feature_for_match(self, match): """ Get the feature for a snapping result @param match: The QgsPointLocator.SnapMatch object @return: A feature """ return next(match.layer().getFeatures(QgsFeatureRequest().setFilterFid(match.featureId()))) def connect_features(self, source, target): """ Connects the source feature with the target feature. @param source: A QgsPointLocator.Match object. Its foreign key will be updated. A dialog will be opened which asks the user for which foreign key(s) he wants to update. @param target: A QgsPointLocator.Match object. This feature will be used as link target. Its obj_id attribute will be used as primary key. """ dlg = QDialog(self.iface.mainWindow()) dlg.setWindowTitle(self.tr('Select properties to connect')) dlg.setLayout(QFormLayout()) properties = list() for prop in self.network_element_sources[source.layer()]['fields']: cbx = QCheckBox(prop[1]) cbx.setObjectName(prop[0]) properties.append(cbx) dlg.layout().addWidget(cbx) btn_box = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel) dlg.layout().addWidget(btn_box) btn_box.accepted.connect(dlg.accept) btn_box.rejected.connect(dlg.reject) source_feature = self.get_feature_for_match(source) target_feature = self.get_feature_for_match(target) if dlg.exec_(): for cbx in properties: if cbx.isChecked(): source_feature[cbx.objectName()] = target_feature['obj_id'] if source.layer().updateFeature(source_feature): self.iface.messageBar().pushMessage('QGEP', self.tr('Connected {} to {}').format( source_feature[ 'identifier'], target_feature['identifier']), QgsMessageBar.INFO, 5) else: self.iface.messageBar().pushMessage('QGEP', self.tr( 'Error connecting features'), QgsMessageBar.WARNING, 5) self.reset()
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) icon = roam_style.iconsize() self.projecttoolbar.setIconSize(QSize(icon, icon)) self.firstshow = True self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.bridge = QgsLayerTreeMapCanvasBridge(QgsProject.instance().layerTreeRoot(), self.canvas) self.bridge.setAutoSetupOnFirstLayer(False) QgsProject.instance().writeProject.connect(self.bridge.writeProject) QgsProject.instance().readProject.connect(self.bridge.readProject) # self.canvas.setInteractive(False) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) self.snapping = SnappingUtils(self.canvas, self) self.canvas.setSnappingUtils(self.snapping) QgsProject.instance().readProject.connect(self.snapping.readConfigFromProject) if hasattr(self.canvas, 'setParallelRenderingEnabled'): threadcount = QThread.idealThreadCount() threadcount = 2 if threadcount > 2 else 1 QgsApplication.setMaxThreads(threadcount) self.canvas.setParallelRenderingEnabled(True) pal = QgsPalLabeling() self.canvas.mapRenderer().setLabelingEngine(pal) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(":/icons/gps", self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) if roam.config.settings.get('north_arrow', False): self.northarrow = NorthArrow(":/icons/north", self.canvas) self.northarrow.setPos(10, 10) self.canvas.scene().addItem(self.northarrow) self.scalebar_enabled = roam.config.settings.get('scale_bar', False) if self.scalebar_enabled: self.scalebar = ScaleBarItem(self.canvas) self.canvas.scene().addItem(self.scalebar) self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu) gpsspacewidget = QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget(self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction(self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.marker = GPSMarker(self.canvas) self.marker.hide() self.currentfeatureband = CurrentSelection(self.canvas) self.currentfeatureband.setIconSize(30) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(186, 93, 212, 50)) self.currentfeatureband.setOutlineColour(QColor(186, 93, 212)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(165, 111, 212, 75)) self.gpsband.setWidth(5) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.openfeatureform.connect(self.feature_form_loaded) RoamEvents.sync_complete.connect(self.refresh_map) RoamEvents.snappingChanged.connect(self.snapping_changed) self.snappingbutton = QToolButton() self.snappingbutton.setText("Snapping: On") self.snappingbutton.setAutoRaise(True) self.snappingbutton.pressed.connect(self.toggle_snapping) spacer = QWidget() spacer2 = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.scalewidget = QgsScaleComboBox() self.scalebutton = QToolButton() self.scalebutton.setAutoRaise(True) self.scalebutton.setMaximumHeight(self.statusbar.height()) self.scalebutton.pressed.connect(self.selectscale) self.scalebutton.setText("Scale") self.scalelist = BigList(parent=self.canvas, centeronparent=True, showsave=False) self.scalelist.hide() self.scalelist.setlabel("Map Scale") self.scalelist.setmodel(self.scalewidget.model()) self.scalelist.closewidget.connect(self.scalelist.close) self.scalelist.itemselected.connect(self.update_scale_from_item) self.scalelist.itemselected.connect(self.scalelist.close) self.positionlabel = QLabel('') self.gpslabel = QLabel("GPS: Not active") self.statusbar.addWidget(self.snappingbutton) self.statusbar.addWidget(spacer2) self.statusbar.addWidget(self.gpslabel) self.statusbar.addPermanentWidget(self.scalebutton) self.canvas.extentsChanged.connect(self.updatestatuslabel) self.canvas.scaleChanged.connect(self.updatestatuslabel) GPS.gpsposition.connect(self.update_gps_label) GPS.gpsdisconnected.connect(self.gps_disconnected) self.connectButtons() def clear_plugins(self): toolbars = self.findChildren(QToolBar) for toolbar in toolbars: if toolbar.property("plugin_toolbar"): self.removeToolBar(toolbar) toolbar.deleteLater() def add_plugins(self, pluginnames): for name in pluginnames: # Get the plugin try: plugin_mod = plugins.loaded_plugins[name] except KeyError: continue if not hasattr(plugin_mod, 'toolbars'): roam.utils.warning("No toolbars() function found in {}".format(name)) continue toolbars = plugin_mod.toolbars() self.load_plugin_toolbars(toolbars) def load_plugin_toolbars(self, toolbars): for ToolBarClass in toolbars: toolbar = ToolBarClass(plugins.api, self) self.addToolBar(Qt.BottomToolBarArea, toolbar) toolbar.setProperty("plugin_toolbar", True) def snapping_changed(self, snapping): """ Called when the snapping settings have changed. Updates the label in the status bar. :param snapping: """ if snapping: self.snappingbutton.setText("Snapping: On") else: self.snappingbutton.setText("Snapping: Off") def toggle_snapping(self): """ Toggle snapping on or off. """ snap = not snapping global snapping snapping = snap RoamEvents.snappingChanged.emit(snapping) def selectscale(self): """ Show the select scale widget. :return: """ self.scalelist.show() def update_scale_from_item(self, index): """ Update the canvas scale from the selected scale item. :param index: The index of the selected item. """ scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole)) self.canvas.zoomScale(1.0 / scale) def update_gps_label(self, position, gpsinfo): """ Update the GPS label in the status bar with the GPS status. :param position: The current GPS position. :param gpsinfo: The current extra GPS information. """ self.gpslabel.setText("GPS: PDOP {0:.2f} HDOP {1:.2f} VDOP {2:.2f}".format(gpsinfo.pdop, gpsinfo.hdop, gpsinfo.vdop)) def gps_disconnected(self): """ Called when the GPS is disconnected. Updates the label in the status bar with the message. :return: """ self.gpslabel.setText("GPS Not Active") def updatestatuslabel(self, *args): """ Update the status bar labels when the information has changed. """ extent = self.canvas.extent() self.positionlabel.setText("Map Center: {}".format(extent.center().toString())) scale = 1.0 / self.canvas.scale() scale = self.scalewidget.toString(scale) self.scalebutton.setText(scale) def refresh_map(self): """ Refresh the map """ self.canvas.refresh() def updatescale(self): """ Update the scale of the map with the current scale from the scale widget :return: """ self.canvas.zoomScale(1.0 / self.scalewidget.scale()) def init_qgisproject(self, doc): """ Called when the project file is read for the firs time. :param doc: The XML doc. :return: The current canvas CRS :note: This method is old and needs to be refactored into something else. """ return self.canvas.mapSettings().destinationCrs() def showEvent(self, *args, **kwargs): """ Handle the show event of the of the map widget. We have to do a little hack here to make the QGIS map refresh. """ if QGis.QGIS_VERSION_INT == 20200 and self.firstshow: self.canvas.refresh() self.canvas.repaint() self.firstshow = False def feature_form_loaded(self, form, feature, *args): """ Called when the feature form is loaded. :param form: The Form object. Holds a reference to the forms layer. :param feature: The current capture feature """ self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): """ Highlight the selection on the canvas. This updates all selected objects based on the result set. :param results: A dict-of-list of layer-features. """ self.clear_selection() for layer, features in results.iteritems(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0)) band.setIconSize(25) band.setWidth(5) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) band.setZValue(self.currentfeatureband.zValue() - 1) for feature in features: band.addGeometry(feature.geometry(), layer) self.canvas.update() def highlight_active_selection(self, layer, feature, features): """ Update the current active selected feature. :param layer: The layer of the active feature. :param feature: The active feature. :param features: The other features in the set to show as non active selection. :return: """ self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) self.canvas.update() def clear_selection(self): """ Clear the selection from the canvas. Resets all selection rubbber bands. :return: """ # Clear the main selection rubber band self.canvas.scene().update() self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.itervalues(): band.reset() self.canvas.update() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): """ Push a feature on the edit stack so the feature can have the geometry edited. :note: This is a big hack and I don't like it! :param form: The form for the current feature :param feature: The active feature. """ def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() self.canvas.mapTool().setEditMode(True, feature.geometry()) break self.editfeaturestack.append((form, feature)) self.load_form(form) trigger_default_action() def clear_temp_objects(self): """ Clear all temp objects from the canvas. :return: """ def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): """ Called when the settings have been updated in the Roam config. :param settings: A dict of the settings. """ self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging def set_gps(self, gps, logging): """ Set the GPS for the map widget. Connects GPS signals """ self.gps = gps self.gpslogging = logging self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) def gps_update_canvas(self, position, gpsinfo): """ Updates the map canvas based on the GPS position. By default if the GPS is outside the canvas extent the canvas will move to center on the GPS. Can be turned off in settings. :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.marker.show() self.marker.setCenter(position, gpsinfo) def gps_first_fix(self, postion, gpsinfo): """ Called the first time the GPS gets a fix. If set this will zoom to the GPS after the first fix :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): """ Zoom to ta given position on the map.. """ rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def gps_disconnected(self): """ Called when the GPS is disconnected """ self.marker.hide() def select_data_entry(self): """ Open the form selection widget to allow the user to pick the active capture form. """ def showformerror(form): pass def actions(): for form in self.project.forms: if not self.form_valid_for_capture(form): continue action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format(form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form", wrap=5) formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): """ Called when the project is loaded. Main entry point for a loade project. :param project: The Roam project that has been loaded. """ self.project = project self.actionPan.trigger() firstform = self.first_capture_form() if firstform: self.load_form(firstform) self.dataentryselection.setVisible(True) else: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = QgsMapLayerRegistry.instance().mapLayers().values() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.refresh() projectscales, _ = QgsProject.instance().readBoolEntry("Scales", "/useProjectScales") if projectscales: projectscales, _ = QgsProject.instance().readListEntry("Scales", "/ScalesList") self.scalewidget.updateScales(projectscales) else: scales = ["1:50000", "1:25000", "1:10000", "1:5000", "1:2500", "1:1000", "1:500", "1:250", "1:200", "1:100"] scales = roam.config.settings.get('scales', scales) self.scalewidget.updateScales(scales) if self.scalebar_enabled: self.scalebar.update() self.actionPan.toggle() self.clear_plugins() self.add_plugins(project.enabled_plugins) def setMapTool(self, tool, *args): """ Set the active map tool in the canvas. :param tool: The QgsMapTool to set. """ if tool == self.canvas.mapTool(): return if hasattr(tool, "setSnapping"): tool.setSnapping(snapping) self.canvas.setMapTool(tool) def connectButtons(self): """ Connect the default buttons in the interface. Zoom, pan, etc """ def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24, 24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = PanTool(self.canvas) self.infoTool = InfoTool(self.canvas) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/select')) self.actionRaster.triggered.connect(self.toggleRasterLayers) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def form_valid_for_capture(self, form): """ Check if the given form is valid for capture. :param form: The form to check. :return: True if valid form for capture """ return form.has_geometry and self.project.layer_can_capture(form.QGISLayer) def first_capture_form(self): """ Return the first valid form for capture. """ for form in self.project.forms: if self.form_valid_for_capture(form): return form def load_form(self, form): """ Load the given form so it's the active one for capture :param form: The form to load """ self.clearCapatureTools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) def create_capture_buttons(self, form): """ Create the capture buttons in the toolbar for the given form. :param form: The active form. """ layer = form.QGISLayer tool = form.getMaptool()(self.canvas) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(partial(self.showUIMessage, form.label)) def add_new_feature(self, form, geometry): """ Add a new new feature to the given layer """ # TODO Extract into function. # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if layer.geometryType() in [QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon]: geometry.convertToMultiType() try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass feature = form.new_feature(geometry=geometry) RoamEvents.load_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) self.canvas.mapTool().setEditMode(False, None) def clearCapatureTools(self): """ Clear the capture tools from the toolbar. :return: True if the capture button was active at the time of clearing. """ captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ # Freeze the canvas to save on UI refresh self.canvas.freeze() tree = QgsProject.instance().layerTreeRoot() for node in tree.findLayers(): if node.layer().type() == QgsMapLayer.RasterLayer: if node.isVisible() == Qt.Checked: state = Qt.Unchecked else: state = Qt.Checked node.setVisible(state) self.canvas.freeze(False) self.canvas.refresh() def cleanup(self): """ Clean up when the project has changed. :return: """ self.bridge.clear() self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clearCapatureTools() self.canvas.freeze() self.canvas.clear() self.canvas.freeze(False) for action in self.layerbuttons: self.editgroup.removeAction(action)
class ShapeTool(QgsMapTool): #signal emitted when the mouse is clicked. This indicates that the tool finished its job toolFinished = pyqtSignal() def __init__(self, canvas, geometryType, param, type, color = QColor( 254, 178, 76, 63 )): """ Constructor """ QgsMapTool.__init__(self, canvas) self.canvas = canvas self.active = False self.geometryType = geometryType self.param=param self.type=type self.cursor=None self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon) self.setColor(color) self.reset() self.rotAngle = 0 self.currentCentroid = None self.rotate = False def setColor(self, mFillColor): """ Adjusting the color to create the rubber band """ self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(1) def reset(self): """ Resetting the rubber band """ self.startPoint = self.endPoint = None self.isEmittingPoint = False try: self.rubberBand.reset(QGis.Polygon) except: pass def rotateRect(self, centroid, e): """ Calculates the angle for the rotation. """ item_position = self.canvas.mapToGlobal(e.pos()) c = self.toCanvasCoordinates(centroid) c = self.canvas.mapToGlobal(c) rotAngle = pi - atan2(item_position.y() - c.y(), item_position.x() - c.x()) return rotAngle def canvasPressEvent(self, e): """ When the canvas is pressed the tool finishes its job """ # enforce mouse restoring if clicked right after rotation QtGui.QApplication.restoreOverrideCursor() self.canvas.unsetMapTool(self) self.toolFinished.emit() def canvasMoveEvent(self, e): """ Deals with mouse move event to update the rubber band position in the canvas """ ctrlIsHeld = QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier if e.button() != None and not ctrlIsHeld: if self.rotate: # change rotate status self.rotate = False QtGui.QApplication.restoreOverrideCursor() self.endPoint = self.toMapCoordinates( e.pos() ) elif e.button() != None and ctrlIsHeld \ and self.geometryType == self.tr(u"Square"): # calculate angle between mouse and last rubberband centroid before holding control self.rotAngle = self.rotateRect(self.currentCentroid, e) if not self.rotate: # only override mouse if it is not overriden already QtGui.QApplication.setOverrideCursor(QCursor(Qt2.BlankCursor)) self.rotate = True if self.geometryType == self.tr(u"Circle"): self.showCircle(self.endPoint) elif self.geometryType == self.tr(u"Square"): self.showRect(self.endPoint, sqrt(self.param)/2, self.rotAngle) def showCircle(self, startPoint): """ Draws a circle in the canvas """ nPoints = 50 x = startPoint.x() y = startPoint.y() if self.type == self.tr('distance'): r = self.param self.rubberBand.reset(QGis.Polygon) for itheta in range(nPoints+1): theta = itheta*(2.0*pi/nPoints) self.rubberBand.addPoint(QgsPoint(x+r*cos(theta), y+r*sin(theta))) self.rubberBand.show() else: r = sqrt(self.param/pi) self.rubberBand.reset(QGis.Polygon) for itheta in range(nPoints+1): theta = itheta*(2.0*pi/nPoints) self.rubberBand.addPoint(QgsPoint(x+r*cos(theta), y+r*sin(theta))) self.rubberBand.show() def showRect(self, startPoint, param, rotAngle=0): """ Draws a rectangle in the canvas """ self.rubberBand.reset(QGis.Polygon) x = startPoint.x() # center point x y = startPoint.y() # center point y # rotation angle is always applied in reference to center point # to avoid unnecessary calculations c = cos(rotAngle) s = sin(rotAngle) # translating coordinate system to rubberband centroid point1 = QgsPoint((- param), (- param)) point2 = QgsPoint((- param), ( param)) point3 = QgsPoint((param), ( param)) point4 = QgsPoint((param), (- param)) # rotating and moving to original coord. sys. point1_ = QgsPoint(point1.x()*c - point1.y()*s + x, point1.y()*c + point1.x()*s + y) point2_ = QgsPoint(point2.x()*c - point2.y()*s + x, point2.y()*c + point2.x()*s + y) point3_ = QgsPoint(point3.x()*c - point3.y()*s + x, point3.y()*c + point3.x()*s + y) point4_ = QgsPoint(point4.x()*c - point4.y()*s + x, point4.y()*c + point4.x()*s + y) self.rubberBand.addPoint(point1_, False) self.rubberBand.addPoint(point2_, False) self.rubberBand.addPoint(point3_, False) self.rubberBand.addPoint(point4_, True) self.rubberBand.show() self.currentCentroid = startPoint def deactivate(self): """ Deactivates the tool and hides the rubber band """ self.rubberBand.hide() QgsMapTool.deactivate(self) # restore mouse in case tool is disabled right after rotation QtGui.QApplication.restoreOverrideCursor() def activate(self): """ Activates the tool """ QgsMapTool.activate(self) def reproject(self, geom, canvasCrs): """ Reprojects geom from the canvas crs to the reference crs geom: geometry to be reprojected canvasCrs: canvas crs (from crs) """ destCrs = self.reference.crs() if canvasCrs.authid() != destCrs.authid(): coordinateTransformer = QgsCoordinateTransform(canvasCrs, destCrs) geom.transform(coordinateTransformer)
class RectangleAreaTool(QgsMapTool): rectangleCreated = pyqtSignal(float, float, float, float) def __init__(self, canvas, action): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.active = False self.setAction(action) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) mFillColor = QColor(254, 178, 76, 63) self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(1) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False self.rubberBand.hide() self.transformCoordinates() self.rectangleCreated.emit(self.startPoint.x(), self.startPoint.y(), self.endPoint.x(), self.endPoint.y()) def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates( e.pos() ) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def transformCoordinates(self): if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y(): return None # Defining the crs from src and destiny epsg = self.canvas.mapSettings().destinationCrs().authid() crsSrc = QgsCoordinateReferenceSystem(epsg) crsDest = QgsCoordinateReferenceSystem(4326) # Creating a transformer coordinateTransformer = QgsCoordinateTransform(crsSrc, crsDest, QgsProject.instance()) # Transforming the points self.startPoint = coordinateTransformer.transform(self.startPoint) self.endPoint = coordinateTransformer.transform(self.endPoint) def deactivate(self): self.rubberBand.hide() QgsMapTool.deactivate(self)
class QgepMapToolConnectNetworkElements(QgsMapTool): """ This map tool connects wastewater networkelements. It works on two lists of layers: source layers with fields with a foreign key to a networkelement target layers which depict networkelements (reaches and network nodes) The tool will snap to source layers first and once one is chosen to a target layer. It will then ask which field(s) should be connected and perform the update on the database """ def __init__(self, iface, action): QgsMapTool.__init__(self, iface.mapCanvas()) self.iface = iface self.action = action self.rbline = QgsRubberBand(self.iface.mapCanvas(), QGis.Line) self.rbline.setColor(QColor('#f4530e')) self.rbline.setWidth(3) self.rbmarkers = QgsRubberBand(self.iface.mapCanvas(), QGis.Point) self.rbmarkers.setColor(QColor('#f4530e')) self.rbmarkers.setIconSize(6) self.source_snapper = QgepAreaSnapper(self.iface.mapCanvas()) self.target_snapper = QgepAreaSnapper(self.iface.mapCanvas()) self.source_feature = QgsFeature() self.rb_source_feature = QgsRubberBand(self.iface.mapCanvas()) self.rb_source_feature.setColor(QColor('#f49e79')) self.rb_source_feature.setWidth(3) self.target_feature = QgsFeature() self.rb_target_feature = QgsRubberBand(self.iface.mapCanvas()) self.rb_target_feature.setColor(QColor('#f49e79')) self.rb_target_feature.setWidth(3) def activate(self): """ Called by QGIS whenever the tool is activated. """ source_snap_layers = list() target_snap_layers = list() # A dict of layers and the fields that are foreign keys # pointing to wastewater networkelements self.network_element_sources = { QgepLayerManager.layer('vw_qgep_reach'): [ ('rp_to_fk_wastewater_networkelement', QCoreApplication.translate('QgepMapToolConnectNetworkElements', 'Reach Point To')), ('rp_from_fk_wastewater_networkelement', QCoreApplication.translate('QgepMapToolConnectNetworkElements', 'Reach Point From')) ], QgepLayerManager.layer('od_catchment_area'): [ ('fk_wastewater_networkelement_rw_current', QCoreApplication.translate( 'QgepMapToolConnectNetworkElements', 'Rainwater current')), ('fk_wastewater_networkelement_rw_planned', QCoreApplication.translate( 'QgepMapToolConnectNetworkElements', 'Rainwater planned')), ('fk_wastewater_networkelement_ww_current', QCoreApplication.translate( 'QgepMapToolConnectNetworkElements', 'Wastewater current')), ('fk_wastewater_networkelement_ww_planned', QCoreApplication.translate( 'QgepMapToolConnectNetworkElements', 'Wastewater planned')) ] } # A list of layers that can be used as wastewater networkelement # targets self.network_element_targets = [ QgepLayerManager.layer('vw_wastewater_node'), QgepLayerManager.layer('vw_qgep_reach') ] for layer in self.network_element_sources.keys(): if layer: snap_layer = QgsSnappingUtils.LayerConfig( layer, QgsPointLocator.All, 16, QgsTolerance.Pixels) source_snap_layers.append(snap_layer) for layer in self.network_element_targets: if layer: snap_layer = QgsSnappingUtils.LayerConfig( layer, QgsPointLocator.All, 16, QgsTolerance.Pixels) target_snap_layers.append(snap_layer) self.source_snapper.setLayers(source_snap_layers) self.source_snapper.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced) self.target_snapper.setLayers(target_snap_layers) self.target_snapper.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced) self.reset() self.action.setChecked(True) self.iface.mapCanvas().setCursor(QCursor(Qt.CrossCursor)) def canvasMoveEvent(self, event): """ When the mouse moves, update the rubberbands. """ pt = event.originalMapPoint() snap_match = self.snapper.snapToMap(pt) if snap_match.isValid(): if snap_match.type() != QgsPointLocator.Area: pt = snap_match.point() self.matchpoint = pt if self.source_match: if self.target_feature.id() != snap_match.featureId(): self.target_feature = self.get_feature_for_match( snap_match) self.rb_target_feature.setToGeometry( self.target_feature.geometry(), snap_match.layer()) self.rb_target_feature.show() self.rbmarkers.movePoint(pt) else: if self.source_feature.id() != snap_match.featureId(): self.source_feature = self.get_feature_for_match( snap_match) self.rb_source_feature.setToGeometry( self.source_feature.geometry(), snap_match.layer()) self.rb_source_feature.show() self.rbmarkers.movePoint(pt, 0) self.rbmarkers.show() else: self.rbmarkers.hide() if self.source_match: self.rb_target_feature.hide() else: self.rb_source_feature.hide() self.rbline.movePoint(pt) self.snapresult = snap_match def canvasReleaseEvent(self, event): """ On a click update the rubberbands and the snapping results if it's a left click. Reset if it's a right click. """ if event.button() == Qt.LeftButton: if self.snapresult.isValid(): if self.source_match: self.connect_features(self.source_match, self.snapresult) else: self.rbline.show() self.rbline.addPoint(self.matchpoint) self.source_match = self.snapresult self.snapper = self.target_snapper else: self.reset() def deactivate(self): """ Called by QGIS whenever this tool is deactivated. """ self.reset() self.action.setChecked(False) def reset(self): """ Resets the tool to a pristine state """ self.source_match = None self.rbline.hide() self.rbline.reset() self.rbmarkers.hide() self.rbmarkers.reset(QGis.Point) self.rbmarkers.addPoint(QgsPoint()) self.snapresult = None self.source_match = None self.snapper = self.source_snapper self.source_feature = QgsFeature() self.target_feature = QgsFeature() self.rb_source_feature.reset() self.rb_target_feature.reset() def get_feature_for_match(self, match): """ Get the feature for a snapping result @param match: The QgsPointLocator.SnapMatch object @return: A feature """ return next(match.layer().getFeatures(QgsFeatureRequest().setFilterFid(match.featureId()))) def connect_features(self, source, target): """ Connects the source feature with the target feature. @param source: A QgsPointLocator.Match object. Its foreign key will be updated. A dialog will be opened which asks the user for which foreign key(s) he wants to update. @param target: A QgsPointLocator.Match object. This feature will be used as link target. Its obj_id attribute will be used as primary key. """ dlg = QDialog(self.iface.mainWindow()) dlg.setWindowTitle(self.tr('Select properties to connect')) dlg.setLayout(QFormLayout()) properties = list() for prop in self.network_element_sources[source.layer()]: cbx = QCheckBox(prop[1]) cbx.setObjectName(prop[0]) properties.append(cbx) dlg.layout().addWidget(cbx) btn_box = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel) dlg.layout().addWidget(btn_box) btn_box.accepted.connect(dlg.accept) btn_box.rejected.connect(dlg.reject) source_feature = self.get_feature_for_match(source) target_feature = self.get_feature_for_match(target) if dlg.exec_(): for cbx in properties: if cbx.isChecked(): source_feature[cbx.objectName()] = target_feature['obj_id'] if source.layer().updateFeature(source_feature): self.iface.messageBar().pushMessage('QGEP', self.tr('Connected {} to {}').format( source_feature[ 'identifier'], target_feature['identifier']), QgsMessageBar.INFO, 5) else: self.iface.messageBar().pushMessage('QGEP', self.tr( 'Error connecting features'), QgsMessageBar.WARNING, 5) self.reset()
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.snapping = True icon = roam_style.iconsize() self.projecttoolbar.setIconSize(QSize(icon, icon)) self.defaultextent = None self.current_form = None self.last_form = None self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.bridge = QgsLayerTreeMapCanvasBridge( QgsProject.instance().layerTreeRoot(), self.canvas) self.bridge.setAutoSetupOnFirstLayer(False) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.snappingutils = SnappingUtils(self.canvas, self) self.canvas.setSnappingUtils(self.snappingutils) threadcount = QThread.idealThreadCount() threadcount = 2 if threadcount > 2 else 1 QgsApplication.setMaxThreads(threadcount) self.canvas.setParallelRenderingEnabled(True) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) if roam.config.settings.get('north_arrow', False): self.northarrow = NorthArrow(":/icons/north", self.canvas) self.northarrow.setPos(10, 10) self.canvas.scene().addItem(self.northarrow) smallmode = roam.config.settings.get("smallmode", False) self.projecttoolbar.setSmallMode(smallmode) self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu) gpsspacewidget = QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget( self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction( self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.gpsMarker = GPSMarker(self.canvas) self.gpsMarker.hide() self.currentfeatureband = CurrentSelection(self.canvas) self.currentfeatureband.setIconSize(30) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(88, 64, 173, 50)) self.currentfeatureband.setOutlineColour(QColor(88, 64, 173)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(165, 111, 212, 75)) self.gpsband.setWidth(5) RoamEvents.refresh_map.connect(self.refresh_map) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.openfeatureform.connect(self.feature_form_loaded) RoamEvents.sync_complete.connect(self.refresh_map) RoamEvents.snappingChanged.connect(self.snapping_changed) self.snappingbutton = QToolButton() self.snappingbutton.setText("Snapping: On") self.snappingbutton.setAutoRaise(True) self.snappingbutton.pressed.connect(self.toggle_snapping) spacer = QWidget() spacer2 = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.scalewidget = QgsScaleComboBox() self.scalebutton = QToolButton() self.scalebutton.setAutoRaise(True) self.scalebutton.setMaximumHeight(self.statusbar.height()) self.scalebutton.pressed.connect(self.selectscale) self.scalebutton.setText("Scale") self.scalelist = BigList(parent=self.canvas, centeronparent=True, showsave=False) self.scalelist.hide() self.scalelist.setlabel("Map Scale") self.scalelist.setmodel(self.scalewidget.model()) self.scalelist.closewidget.connect(self.scalelist.close) self.scalelist.itemselected.connect(self.update_scale_from_item) self.scalelist.itemselected.connect(self.scalelist.close) self.positionlabel = QLabel('') self.gpslabel = QLabel("GPS: Not active") self.gpslabelposition = QLabel("") self.statusbar.addWidget(self.snappingbutton) self.statusbar.addWidget(spacer2) self.statusbar.addWidget(self.gpslabel) self.statusbar.addWidget(self.gpslabelposition) self.statusbar.addPermanentWidget(self.scalebutton) self.canvas.extentsChanged.connect(self.update_status_label) self.canvas.scaleChanged.connect(self.update_status_label) self.connectButtons() scalebar_enabled = roam.config.settings.get('scale_bar', False) self.scalebar_enabled = False if scalebar_enabled: roam.utils.warning( "Unsupported feature: Scale bar support not ported to QGIS 3 API yet." ) RoamEvents.raisemessage( "Unsupported feature", "Scale bar support not ported to QGIS 3 API yet", level=RoamEvents.CRITICAL) self.scalebar_enabled = False # self.scalebar = ScaleBarItem(self.canvas) # self.canvas.scene().addItem(self.scalebar) def clear_plugins(self) -> None: """ Clear all the plugin added toolbars from the map interface. """ toolbars = self.findChildren(QToolBar) for toolbar in toolbars: if toolbar.property("plugin_toolbar"): toolbar.unload() self.removeToolBar(toolbar) toolbar.deleteLater() def add_plugins(self, pluginnames) -> None: """ Add the given plugins to to the mapping interface. Adds the toolbars the plugin exposes as new toolbars for the user. :param pluginnames: The names of the plugins to load. Must already be loaded by the plugin loader """ for name in pluginnames: # Get the plugin try: plugin_mod = plugins.loaded_plugins[name] except KeyError: continue if not hasattr(plugin_mod, 'toolbars'): roam.utils.warning( "No toolbars() function found in {}".format(name)) continue toolbars = plugin_mod.toolbars() self.load_plugin_toolbars(toolbars) def load_plugin_toolbars(self, toolbars): """ Load the plugin toolbars into the mapping interface. :param toolbars: The list of toolbars class objects to load. :return: """ for ToolBarClass in toolbars: toolbar = ToolBarClass(plugins.api, self) self.addToolBar(Qt.BottomToolBarArea, toolbar) toolbar.setProperty("plugin_toolbar", True) def snapping_changed(self, snapping): """ Called when the snapping settings have changed. Updates the label in the status bar. :param snapping: """ self.snapping = snapping if snapping: self.snappingbutton.setText("Snapping: On") else: self.snappingbutton.setText("Snapping: Off") def toggle_snapping(self): """ Toggle snapping on or off. """ self.snapping = not self.snapping try: self.canvas.mapTool().toggle_snapping() except AttributeError: pass RoamEvents.snappingChanged.emit(self.snapping) def selectscale(self): """ Show the select scale widget. :return: """ self.scalelist.show() def update_scale_from_item(self, index): """ Update the canvas scale from the selected scale item. :param index: The index of the selected item. """ scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole)) self.canvas.zoomScale(1.0 / scale) def update_gps_fixed_label(self, fixed, gpsinfo): if not fixed: self.gpslabel.setText("GPS: Acquiring fix") self.gpslabelposition.setText("") quality_mappings = { 0: "invalid", 1: "GPS", 2: "DGPS", 3: "PPS", 4: "Real Time Kinematic", 5: "Float RTK", 6: "Estimated", 7: "Manual input mode", 8: "Simulation mode" } def update_gps_label(self, position, gpsinfo): """ Update the GPS label in the status bar with the GPS status. :param position: The current GPS position. :param gpsinfo: The current extra GPS information. """ if not self.gps.connected: return fixtype = self.quality_mappings.get(gpsinfo.quality, "") self.gpslabel.setText( "DOP P:<b>{0:.2f}</b> H:<b>{1:.2f}</b> V:<b>{2:.2f}</b> " "Fix: <b>{3}</b> " "Sats: <b>{4}</b> ".format(gpsinfo.pdop, gpsinfo.hdop, gpsinfo.vdop, fixtype, gpsinfo.satellitesUsed)) places = roam.config.settings.get("gpsplaces", 8) self.gpslabelposition.setText("X: <b>{x:.{places}f}</b> " "Y: <b>{y:.{places}f}</b> " "Z: <b>{z}m</b> ".format( x=position.x(), y=position.y(), z=gpsinfo.elevation, places=places)) def gps_disconnected(self): self.gpslabel.setText("GPS: Not Active") self.gpslabelposition.setText("") self.gpsMarker.hide() def zoom_to_feature(self, feature): """ Zoom to the given feature in the map. :param feature: :return: """ box = feature.geometry().boundingBox() xmin, xmax, ymin, ymax = box.xMinimum(), box.xMaximum(), box.yMinimum( ), box.yMaximum() xmin -= 5 xmax += 5 ymin -= 5 ymax += 5 box = QgsRectangle(xmin, ymin, xmax, ymax) self.canvas.setExtent(box) self.canvas.refresh() def update_status_label(self, *args) -> None: """ Update the status bar labels when the information has changed. """ extent = self.canvas.extent() self.positionlabel.setText("Map Center: {}".format( extent.center().toString())) scale = 1.0 / self.canvas.scale() scale = self.scalewidget.toString(scale) self.scalebutton.setText(scale) def refresh_map(self) -> None: """ Refresh the map """ self.canvas.refresh() def updatescale(self) -> None: """ Update the scale of the map with the current scale from the scale widget :return: """ self.canvas.zoomScale(1.0 / self.scalewidget.scale()) @property def crs(self) -> QgsCoordinateReferenceSystem: """ Get the CRS used that is being used in the canvas :return: The QgsCoordinateReferenceSystem that is used by the canvas """ return self.canvas.mapSettings().destinationCrs() def feature_form_loaded(self, form, feature, *args): """ Called when the feature form is loaded. :param form: The Form object. Holds a reference to the forms layer. :param feature: The current capture feature """ self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): """ Highlight the selection on the canvas. This updates all selected objects based on the result set. :param results: A dict-of-list of layer-features. """ self.clear_selection() for layer, features in results.items(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0)) band.setIconSize(25) band.setWidth(5) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) band.setZValue(self.currentfeatureband.zValue() - 1) for feature in features: band.addGeometry(feature.geometry(), layer) self.canvas.update() def highlight_active_selection(self, layer, feature, features): """ Update the current active selected feature. :param layer: The layer of the active feature. :param feature: The active feature. :param features: The other features in the set to show as non active selection. :return: """ self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) self.canvas.update() def clear_selection(self): """ Clear the selection from the canvas. Resets all selection rubber bands. :return: """ # Clear the main selection rubber band self.canvas.scene().update() self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.values(): band.reset() self.canvas.update() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): """ Push a feature on the edit stack so the feature can have the geometry edited. :note: This is a big hack and I don't like it! :param form: The form for the current feature :param feature: The active feature. """ def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() self.canvas.currentLayer().startEditing() self.canvas.mapTool().setEditMode(True, feature.geometry(), feature) break self.editfeaturestack.append((form, feature)) self.save_current_form() self.load_form(form) trigger_default_action() def save_current_form(self): self.last_form = self.current_form def restore_last_form(self): self.load_form(self.last_form) def clear_temp_objects(self): """ Clear all temp objects from the canvas. :return: """ def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() if hasattr(tool, "clearBand"): tool.clearBand() self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): """ Called when the settings have been updated in the Roam config. :param settings: A dict of the settings. """ self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging smallmode = settings.get("smallmode", False) self.projecttoolbar.setSmallMode(smallmode) def set_gps(self, gps, logging): """ Set the GPS for the map widget. Connects GPS signals """ self.gps = gps self.gpslogging = logging self.gps.gpsfixed.connect(self.update_gps_fixed_label) self.gps.gpsposition.connect(self.update_gps_label) self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) self.gpsMarker.setgps(self.gps) self.actionGPS.setgps(gps) def gps_update_canvas(self, position, gpsinfo): """ Updates the map canvas based on the GPS position. By default if the GPS is outside the canvas extent the canvas will move to center on the GPS. Can be turned off in settings. :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastgpsposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.gpsMarker.show() self.gpsMarker.setCenter(position, gpsinfo) def gps_first_fix(self, postion, gpsinfo): """ Called the first time the GPS gets a fix. If set this will zoom to the GPS after the first fix :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): """ Zoom to ta given position on the map.. """ rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def select_data_entry(self): """ Open the form selection widget to allow the user to pick the active capture form. """ def showformerror(form): pass def actions(): for form in self.project.forms: if not self.form_valid_for_capture(form): continue action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format( form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form", wrap=5) formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): """ Called when the project is loaded. Main entry point for a loade project. :param project: The Roam project that has been loaded. """ self.snappingutils.setConfig(QgsProject.instance().snappingConfig()) self.project = project self.actionPan.trigger() firstform = self.first_capture_form() if firstform: self.load_form(firstform) self.dataentryselection.setVisible(True) else: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = roam.api.utils.layers() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.refresh() projectscales, _ = QgsProject.instance().readBoolEntry( "Scales", "/useProjectScales") if projectscales: projectscales, _ = QgsProject.instance().readListEntry( "Scales", "/ScalesList") self.scalewidget.updateScales(projectscales) else: scales = [ "1:50000", "1:25000", "1:10000", "1:5000", "1:2500", "1:1000", "1:500", "1:250", "1:200", "1:100" ] scales = roam.config.settings.get('scales', scales) self.scalewidget.updateScales(scales) if self.scalebar_enabled: self.scalebar.update() red = QgsProject.instance().readNumEntry("Gui", "/CanvasColorRedPart", 255)[0] green = QgsProject.instance().readNumEntry("Gui", "/CanvasColorGreenPart", 255)[0] blue = QgsProject.instance().readNumEntry("Gui", "/CanvasColorBluePart", 255)[0] myColor = QColor(red, green, blue) self.canvas.setCanvasColor(myColor) self.actionPan.toggle() self.clear_plugins() self.add_plugins(project.enabled_plugins) def setMapTool(self, tool, *args): """ Set the active map tool in the canvas. :param tool: The QgsMapTool to set. """ if tool == self.canvas.mapTool(): return if hasattr(tool, "setSnapping"): tool.setSnapping(self.snapping) self.canvas.setMapTool(tool) def connectButtons(self): """ Connect the default buttons in the interface. Zoom, pan, etc """ def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24, 24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = QgsMapToolPan(self.canvas) self.infoTool = InfoTool(self.canvas) self.infoTool.setAction(self.actionInfo) self.zoomInTool.setAction(self.actionZoom_In) self.zoomOutTool.setAction(self.actionZoom_Out) self.panTool.setAction(self.actionPan) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/select')) self.actionRaster.triggered.connect(self.toggle_raster_layers) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ if self.defaultextent: self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def form_valid_for_capture(self, form): """ Check if the given form is valid for capture. :param form: The form to check. :return: True if valid form for capture """ return form.has_geometry and self.project.layer_can_capture( form.QGISLayer) def first_capture_form(self): """ Return the first valid form for capture. """ for form in self.project.forms: if self.form_valid_for_capture(form): return form def load_form(self, form): """ Load the given form so it's the active one for capture :param form: The form to load """ self.clear_capture_tools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) self.current_form = form def create_capture_buttons(self, form): """ Create the capture buttons in the toolbar for the given form. :param form: The active form. """ tool = form.getMaptool()(self.canvas, form.settings) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(self.show_invalid_geometry_message) def show_invalid_geometry_message(self, message) -> None: """ Shows the message to the user if the there is a invalid geometry capture. :param message: The message to show the user. """ RoamEvents.raisemessage("Invalid geometry capture", message, level=RoamEvents.CRITICAL) if self.canvas.currentLayer() is not None: self.canvas.currentLayer().rollBack() RoamEvents.editgeometry_invalid.emit() def add_new_feature(self, form, geometry: QgsGeometry): """ Add a new new feature to the given layer :param form: The form to use for the new feature. :param geometry: The new geometry to create the feature for. """ # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if geometry.isMultipart(): geometry.convertToMultiType() # Transform the new geometry back into the map layers geometry if it's needed transform = self.canvas.mapSettings().layerTransform(layer) if transform.isValid(): geometry.transform(transform, QgsCoordinateTransform.ReverseTransform) try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass feature = form.new_feature(geometry=geometry) RoamEvents.load_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) self.canvas.mapTool().setEditMode(False, None, None) self.restore_last_form() def clear_capture_tools(self): """ Clear the capture tools from the toolbar. :return: True if the capture button was active at the time of clearing. """ captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggle_raster_layers(self) -> None: """ Toggle all raster layers on or off. """ # Freeze the canvas to save on UI refresh dlg = PickActionDialog(msg="Raster visibility") actions = [ (":/icons/raster_0", "Off", partial(self._set_basemaps_opacity, 0), "photo_off"), (":/icons/raster_25", "25%", partial(self._set_basemaps_opacity, .25), "photo_25"), (":/icons/raster_50", "50%", partial(self._set_basemaps_opacity, .50), "photo_50"), (":/icons/raster_75", "75%", partial(self._set_basemaps_opacity, .75), "photo_75"), (":/icons/raster_100", "100%", partial(self._set_basemaps_opacity, 1), "photo_100"), ] # ":/icons/raster_100"), "100%", self, triggered=partial(self._set_raster_layer_value, 1), # objectName="photo_100") dialog_actions = [] for action in actions: icon = QIcon(action[0]) qaction = QAction(icon, action[1], self, triggered=action[2], objectName=action[3]) dialog_actions.append(qaction) dlg.addactions(dialog_actions) dlg.exec_() def _set_basemaps_opacity(self, value=0) -> None: """ Set the opacity for all basemap raster layers. :param value: The opacity value betwen 0 and 1 """ tree = QgsProject.instance().layerTreeRoot() for node in tree.findLayers(): layer = node.layer() if node.layer().type() == QgsMapLayer.RasterLayer: if value > 0: node.setItemVisibilityChecked(Qt.Checked) renderer = layer.renderer() renderer.setOpacity(value) if value == 0: node.setItemVisibilityChecked(Qt.Unchecked) self.canvas.refresh() def cleanup(self): """ Clean up when the project has changed. :return: """ # TODO Review cleanup # self.bridge.clear() self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clear_capture_tools() for action in self.layerbuttons: self.editgroup.removeAction(action)
class EarthMineQGIS(QObject): """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface super(EarthMineQGIS, self).__init__() self.movingfeature = None self.iface = iface self.viewer = None self.canvas = self.iface.mapCanvas() self.settings = QSettings() # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value("locale/userLocale")[0:2] locale_path = os.path.join(self.plugin_dir, "i18n", "EarthMineQGIS_{}.qm".format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > "4.3.3": QCoreApplication.installTranslator(self.translator) self.pointtool = QgsMapToolEmitPoint(self.canvas) self.pointtool.canvasClicked.connect(self.set_viewer_location) self.settingsdialog = SettingsDialog(self.iface.mainWindow()) self.actions = [] self.menu = self.tr(u"&Earthmine") self.toolbar = self.iface.addToolBar(u"EarthMineQGIS") self.toolbar.setObjectName(u"EarthMineQGIS") self.legend = self.iface.legendInterface() emcolor = QColor(1, 150, 51) self.tempband = QgsRubberBand(self.canvas, QGis.Line) self.tempband.setWidth(5) self.tempband.setColor(emcolor) self.tempbandpoints = QgsRubberBand(self.canvas, QGis.Point) self.tempbandpoints.setWidth(7) self.tempbandpoints.setColor(emcolor) self.movingband = QgsRubberBand(self.canvas, QGis.Point) self.movingband.setWidth(5) self.movingband.setColor(emcolor) self.layersignals = [] self.marker = None def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ":/icons/settings" self.add_action( icon_path, text=self.tr(u"Show Settings"), callback=self.show_settings, parent=self.iface.mainWindow() ) icon_path = ":/icons/viewer" self.add_action( icon_path, text=self.tr(u"Earthmine Viewer"), callback=self.open_viewer, parent=self.iface.mainWindow() ) self.marker = PostionMarker(self.canvas) self.marker.hide() self.viewer = Viewer(callbackobject=self) self.viewer.trackingChanged.connect(self.marker.setTracking) self.viewer.setLocationTriggered.connect(partial(self.canvas.setMapTool, self.pointtool)) self.viewer.updateFeatures.connect(self.update_earthmine_features) self.viewer.layerChanged.connect(self.iface.setActiveLayer) self.viewer.clearLine.connect(self.clear_bands) self.viewer.closed.connect(self.remove_items) self.iface.currentLayerChanged.connect(self.viewer.update_current_layer) cursor = QCursor(QPixmap(":/icons/location")) self.pointtool.setCursor(cursor) self.pointtool.setAction(self.viewer.setlocationaction) def remove_items(self): self.marker.setTracking(False) self.disconnect_projectsignals() self.iface.actionPan().trigger() def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" self.canvas.scene().removeItem(self.marker) del self.marker self.disconnect_projectsignals() for action in self.actions: self.iface.removePluginMenu(self.tr(u"&Earthmine"), action) self.iface.removeToolBarIcon(action) del self.toolbar self.iface.removeDockWidget(self.viewer) self.viewer.deleteLater() def disconnect_projectsignals(self): safe_disconnect(QgsMapLayerRegistry.instance().layerWasAdded, self.connect_layer_signals) safe_disconnect(QgsMapLayerRegistry.instance().layersRemoved, self.layers_removed) safe_disconnect(self.canvas.layersChanged, self.layers_changed) safe_disconnect(self.iface.projectRead, self.connect_signals) safe_disconnect(self.canvas.selectionChanged, self.selection_changed) safe_disconnect(self.canvas.selectionChanged, self.viewer.selection_changed) def clear_bands(self): self.tempband.reset(QGis.Line) self.tempbandpoints.reset(QGis.Point) def visible_layers(self): """ Return the visible layers shown in the map canvas :return: """ return (layer for layer, visible in self.layers_with_states() if visible) def layers_with_states(self): for layer in maplayers(): if not layer.type() == QgsMapLayer.VectorLayer: continue if not layer.geometryType() in [QGis.Point, QGis.Line]: continue yield layer, self.legend.isLayerVisible(layer) def _layer_feature_added(self, featureid): layer = self.sender() if not layer: return self.layer_feature_added(layer, featureid) def layer_feature_added(self, layer, featureid): if not self.viewer: return feature = layer.getFeatures(QgsFeatureRequest(featureid)).next() renderer = layer.rendererV2() transform = self.coordinatetransform(layer) featuredata = to_feature_data(layer.id(), feature, renderer, transform) geomtype = layer.geometryType() layerdata = dict(id=layer.id(), geomtype=QGis.vectorGeometryType(geomtype)) self.viewer.load_features(layerdata, featuredata) def _layer_feature_delete(self, featureid): layer = self.sender() if not layer: return self.layer_feature_delete(layer, featureid) def layer_feature_delete(self, layer, featureid): if not self.viewer: return self.viewer.remove_feature(layer.id(), featureid) def _layer_geometry_changed(self, featureid, geometry): layer = self.sender() if not layer: return self.layer_geometry_changed(layer, featureid, geometry) def layer_geometry_changed(self, layer, featureid, geometry): if not self.viewer: return geomtype = layer.geometryType() if geomtype == QGis.Point: geom = geometry.asPoint() transform = self.coordinatetransform(layer) point = transform.transform(geom, QgsCoordinateTransform.ReverseTransform) location = dict(lat=point.y(), lng=point.x()) self.viewer.edit_feature(layer.id(), featureid, [location]) elif geomtype == QGis.Line: self.layer_feature_delete(layer, featureid) self.layer_feature_added(layer, featureid) def connect_layer_signals(self, layer): if not layer.type() == QgsMapLayer.VectorLayer: return layer.featureAdded.connect(self._layer_feature_added) layer.featureDeleted.connect(self._layer_feature_delete) layer.editingStarted.connect(self.layer_editstate_changed) layer.editingStopped.connect(self.layer_editstate_changed) # HACK The new style doesn't work here # http://hub.qgis.org/issues/6573 signal = SIGNAL("geometryChanged(QgsFeatureId, QgsGeometry&)") self.connect(layer, signal, self._layer_geometry_changed) self.load_layer_features(layers=[layer]) def layer_editstate_changed(self): layer = self.sender() if layer == self.iface.activeLayer(): self.viewer.layer_changed(layer) def disconnect_signals(self): self.disconnect_projectsignals() for layer in maplayers(): if not layer.type() == QgsMapLayer.VectorLayer: return safe_disconnect(layer.featureAdded, self._layer_feature_added) safe_disconnect(layer.featureDeleted, self._layer_feature_delete) safe_disconnect(layer.editingStarted, self.layer_editstate_changed) safe_disconnect(layer.editingStopped, self.layer_editstate_changed) # HACK The new style doesn't work here # http://hub.qgis.org/issues/6573 signal = SIGNAL("geometryChanged(QgsFeatureId, QgsGeometry&)") self.disconnect(layer, signal, self._layer_geometry_changed) def connect_signals(self): for layer in maplayers(): self.connect_layer_signals(layer) self.center_on_canvas() def set_viewer_location(self, point, mousebutton): transform = self.coordinatetransform() point = transform.transform(point, QgsCoordinateTransform.ReverseTransform) self.viewer.set_location(point) def distancearea(self): area = QgsDistanceArea() dest = self.canvas.mapRenderer().destinationCrs() area.setSourceCrs(dest) return area, dest.mapUnits() def coordinatetransform(self, layer=None): """ Return the transform for WGS84 -> QGIS projection. """ source = QgsCoordinateReferenceSystem() source.createFromWkt( 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]' ) if not layer: dest = self.canvas.mapRenderer().destinationCrs() else: dest = layer.crs() transform = QgsCoordinateTransform(source, dest) return transform def earthmine_settings(self): settings = {} with settinggroup(self.settings, "plugins/Earthmine"): for key in ["serviceUrl", "baseDataUrl", "apiKey", "secretKey", "viewerUrl"]: if not self.settings.contains(key): raise EarthmineSettingsError("{} not set".format(key)) value = self.settings.value(key, type=str) if value is None: raise EarthmineSettingsError("{} not set".format(key)) settings[key] = value return settings @pyqtSlot() def ready(self): """ Called when the viewer is ready to be started. At this point the viewer hasn't been loaded so no other methods apart from startViewer will be handled. """ settings = self.earthmine_settings() self.viewer.startViewer(settings) @pyqtSlot() def viewerReady(self): """ Called once the viewer is loaded and ready to get location events. """ self.disconnect_signals() self.connect_signals() self.iface.projectRead.connect(self.connect_signals) self.canvas.layersChanged.connect(self.layers_changed) self.canvas.selectionChanged.connect(self.selection_changed) self.canvas.selectionChanged.connect(self.viewer.selection_changed) QgsMapLayerRegistry.instance().layersRemoved.connect(self.layers_removed) QgsMapLayerRegistry.instance().layerWasAdded.connect(self.connect_layer_signals) self.center_on_canvas() self.viewer.activelayercombo.setLayer(self.iface.activeLayer()) def center_on_canvas(self): point = self.canvas.extent().center() transform = self.coordinatetransform() point = transform.transform(point, QgsCoordinateTransform.ReverseTransform) self.viewer.set_location(point) self.viewer.infoaction.toggle() def selection_changed(self, layer): ids = [feature.id() for feature in layer.selectedFeatures()] if not ids: self.viewer.clear_selection(layer.id()) else: self.viewer.set_selection(layer.id(), ids) def layers_changed(self): layerstates = self.layers_with_states() for layer, visible in layerstates: layerid = layer.id() viewerloaded = self.viewer.layer_loaded(layerid) QgsMessageLog.instance().logMessage(layerid, "Earthmine") QgsMessageLog.instance().logMessage("Viewer State:" + str(viewerloaded), "Earthmine") QgsMessageLog.instance().logMessage("QGIS State:" + str(visible), "Earthmine") if (viewerloaded and visible) or (not viewerloaded and not visible): QgsMessageLog.instance().logMessage("Ignoring as states match", "Earthmine") continue if viewerloaded and not visible: QgsMessageLog.instance().logMessage( "Clearing layer because viewer loaded and disabled in QGIS", "Earthmine" ) self.viewer.clear_layer_features(layerid) continue if not viewerloaded and visible: QgsMessageLog.instance().logMessage("Loading layer", "Earthmine") self.load_layer_features(layers=[layer]) continue def layers_removed(self, layers): for layerid in layers: self.viewer.clear_layer_features(layerid) @pyqtSlot(str, float, float) def viewChanged(self, event, yaw, angle): self.marker.setAngle(angle) self.marker.setYaw(yaw) @pyqtSlot(str, str) def getInfo(self, layerid, featureid): featureid = int(featureid) activelayer = self.iface.activeLayer() if not activelayer: return activetool = self.viewer.active_tool() if not activetool in ["Info", "Select"]: return # Only show information for the active layer if not layerid == activelayer.id(): return layer = layer_by_id(layerid) if activetool == "Select": layer.setSelectedFeatures([featureid]) elif activetool == "Info": rq = QgsFeatureRequest(featureid) feature = layer.getFeatures(rq).next() dlg = get_feature_form(layer, feature) if dlg.dialog().exec_(): self.canvas.refresh() @pyqtSlot(str, str, float, float, bool) def featureMoved(self, layerid, featureid, lat, lng, end): layer = layer_by_id(layerid) transform = self.coordinatetransform(layer) point = transform.transform(lng, lat) if not end: self.movingband.show() self.movingband.setToGeometry(QgsGeometry.fromPoint(point), layer) self.movingband.updatePosition() self.movingband.update() else: self.movingband.hide() feature = feature_by_id(layer, featureid) startpoint = feature.geometry().asPoint() dx = point.x() - startpoint.x() dy = point.y() - startpoint.y() layer.beginEditCommand("Feature Moved") # Block signals for this move as the geometry changed signal will re add the geometry on use. layer.blockSignals(True) layer.translateFeature(feature.id(), dx, dy) layer.blockSignals(False) self.canvas.refresh() layer.endEditCommand() @pyqtSlot(str, str) def onError(self, message, stacktrace=None): self.iface.messageBar().pushMessage("Earthmine", message, QgsMessageBar.WARNING) QgsMessageLog.logMessage(stacktrace, "Earthmine") @pyqtSlot(float, float, float) def addPoint(self, lat, lng, z): layer = self.viewer.active_layer if not layer.isEditable(): self.iface.messageBar().pushMessage( "Earthmine", "Selected layer isn't editable. Please enable edit mode to add features", duration=3, level=QgsMessageBar.WARNING, ) return transform = self.coordinatetransform(layer) point = transform.transform(lng, lat) geom = QgsGeometry.fromPoint(point) self.add_feature(layer, geom, z) def add_feature(self, layer, geom, z=None): feature = QgsFeature(layer.pendingFields()) if z and self.viewer.copyZvalue: try: feature["Z"] = z except KeyError: QgsMessageLog.log("No Z found on layer {}".format(layer.name())) pass feature.setGeometry(geom) dlg = get_feature_form(layer, feature, isadd=True) if dlg.dialog().exec_(): self.canvas.refresh() @pyqtSlot(str, bool, str) def drawLine(self, points, end, stats): points = json.loads(points) stats = json.loads(stats) QgsMessageLog.logMessage(str(stats), "Earthmine") self.tempband.reset(QGis.Line) self.tempbandpoints.reset(QGis.Point) color = QColor(self.viewer.current_action_color) self.tempband.setColor(color) self.tempbandpoints.setColor(color) layer = self.viewer.active_layer transform = self.coordinatetransform(layer) earthminepoints = [] for point in points: newpoint = transform.transform(point["lng"], point["lat"]) self.tempband.addPoint(newpoint) self.tempbandpoints.addPoint(newpoint) empoint = EarthminePoint(newpoint, point) earthminepoints.append(empoint) if end and not self.viewer.mode == "Vertical": geom = self.tempband.asGeometry() self.add_feature(layer, geom) self.clear_bands() self.viewer.geom = EarthmineLine(earthminepoints, stats) self.tempband.show() self.tempbandpoints.show() @pyqtSlot(str, str, str, float) def locationChanged(self, lat, lng, yaw, angle): transform = self.coordinatetransform() point = transform.transform(float(lng), float(lat)) self.marker.setCenter(point) yaw = float(yaw) self.marker.setAngle(angle) self.marker.setYaw(yaw) self.marker.setTracking(self.viewer.tracking) if self.marker.tracking: rect = QgsRectangle(point, point) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(point): self.canvas.setExtent(rect) self.canvas.refresh() # Clear old features self.viewer.clear_features() self.load_layer_features(point) def update_earthmine_features(self, viewfeatures): self.viewer.clear_features() if viewfeatures: self.load_layer_features() def load_layer_features(self, point=None, layers=None): # TODO Move this logic into the viewer and let it track it's position if point is None and self.marker.map_pos is None: return if point is None: point = self.marker.map_pos area, units = self.distancearea() rect = search_area(units, area, point) if layers is None: layers = self.visible_layers() for layer in layers: transform = self.coordinatetransform(layer) # Transform the rect source = self.canvas.mapRenderer().destinationCrs() dest = layer.crs() recttransform = QgsCoordinateTransform(source, dest) rect = recttransform.transformBoundingBox(rect) features = list(get_features_in_area(layer, rect, transform, self.canvas.mapSettings())) geomtype = layer.geometryType() layerdata = dict(id=layer.id(), geomtype=QGis.vectorGeometryType(geomtype)) self.viewer.load_features(layerdata, features) # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate("EarthMineQGIS", message) def add_action( self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None, ): """Add a toolbar icon to the InaSAFE toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to s, "Earhtmine"how in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) return action def open_viewer(self): """Run method that performs all the real work""" try: settings = self.earthmine_settings() except EarthmineSettingsError as ex: self.onError(ex.message) self.show_settings() return url = settings["viewerUrl"] if not url.startswith("http"): url = url.replace("\\\\", "\\") url = QUrl.fromLocalFile(url) else: url = QUrl(url) if not self.viewer.isVisible(): self.iface.addDockWidget(Qt.RightDockWidgetArea, self.viewer) self.viewer.loadviewer(url) def show_settings(self): self.settingsdialog.show()
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.firstshow = True self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) if hasattr(self.canvas, 'setParallelRenderingEnabled'): self.canvas.setParallelRenderingEnabled(True) pal = QgsPalLabeling() self.canvas.mapRenderer().setLabelingEngine(pal) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(":/icons/gps", self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) gpsspacewidget= QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget(self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction(self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.marker = GPSMarker(self.canvas) self.marker.hide() self.currentfeatureband = QgsRubberBand(self.canvas) self.currentfeatureband.setIconSize(20) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(186, 93, 212, 76)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(0, 0, 212, 76)) self.gpsband.setWidth(5) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.featureformloaded.connect(self.feature_form_loaded) self.connectButtons() def init_qgisproject(self, doc): parser = ProjectParser(doc) canvasnode = parser.canvasnode self.canvas.freeze() self.canvas.mapRenderer().readXML(canvasnode) self.canvaslayers = parser.canvaslayers() self.canvas.setLayerSet(self.canvaslayers) #red = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorRedPart", 255 )[0]; #green = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorGreenPart", 255 )[0]; #blue = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorBluePart", 255 )[0]; #color = QColor(red, green, blue); #self.canvas.setCanvasColor(color) self.canvas.updateScale() return self.canvas.mapRenderer().destinationCrs() def showEvent(self, *args, **kwargs): if self.firstshow: self.canvas.refresh() self.canvas.repaint() self.firstshow = False def feature_form_loaded(self, form, feature, project, editmode): self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): self.clear_selection() for layer, features in results.iteritems(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0, 200)) band.setIconSize(20) band.setWidth(2) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) for feature in features: band.addGeometry(feature.geometry(), layer) def highlight_active_selection(self, layer, feature, features): self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) def clear_selection(self): # Clear the main selection rubber band self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.itervalues(): band.reset() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() break self.editfeaturestack.append((form, feature)) self.load_form(form) trigger_default_action() def clear_temp_objects(self): def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging def set_gps(self, gps, logging): self.gps = gps self.gpslogging = logging self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) def gps_update_canvas(self, position, gpsinfo): # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.marker.show() self.marker.setCenter(position) def gps_first_fix(self, postion, gpsinfo): zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def gps_disconnected(self): self.marker.hide() def select_data_entry(self): def showformerror(form): pass def actions(): for form in self.project.forms: action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format(form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form") formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): self.project = project self.actionPan.trigger() try: firstform = project.forms[0] self.load_form(firstform) self.dataentryselection.setVisible(True) except IndexError: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = QgsMapLayerRegistry.instance().mapLayers().values() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.freeze(False) self.canvas.refresh() def setMapTool(self, tool, *args): self.canvas.setMapTool(tool) def connectButtons(self): def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24,24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = PanTool(self.canvas) self.infoTool = InfoTool(self.canvas) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/info')) self.actionRaster.triggered.connect(self.toggleRasterLayers) self.infoTool.infoResults.connect(RoamEvents.selectionchanged.emit) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def load_form(self, form): self.clearCapatureTools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) def create_capture_buttons(self, form): tool = form.getMaptool()(self.canvas) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(partial(self.showUIMessage, form.label)) def add_new_feature(self, form, geometry): """ Add a new new feature to the given layer """ # TODO Extract into function. # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if layer.geometryType() in [QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon]: geometry.convertToMultiType() try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass layer = form.QGISLayer fields = layer.pendingFields() feature = QgsFeature(fields) feature.setGeometry(geometry) for index in xrange(fields.count()): pkindexes = layer.dataProvider().pkAttributeIndexes() if index in pkindexes and layer.dataProvider().name() == 'spatialite': continue value = layer.dataProvider().defaultValue(index) feature[index] = value RoamEvents.open_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) def clearCapatureTools(self): captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ if not self.canvaslayers: return #Freeze the canvas to save on UI refresh self.canvas.freeze() for layer in self.canvaslayers: if layer.layer().type() == QgsMapLayer.RasterLayer: layer.setVisible(not layer.isVisible()) # Really!? We have to reload the whole layer set every time? # WAT? self.canvas.setLayerSet(self.canvaslayers) self.canvas.freeze(False) self.canvas.refresh() def cleanup(self): self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clearCapatureTools() self.canvas.freeze() self.canvas.clear() self.canvas.freeze(False) for action in self.layerbuttons: self.editgroup.removeAction(action)
class SelectFeatureTool(QgsMapToolEmitPoint): foundFeature = pyqtSignal(QgsFeature, object, str) def __init__(self, canvas, layer, column, bindto, radius ): QgsMapToolEmitPoint.__init__(self, canvas) self.layer = layer self.column = column self.bindto = bindto self.canvas = canvas self.bindto = bindto self.searchradius = radius self.canvasClicked.connect(self.findFeature) self.canvas.setMapTool(self) self.band = QgsRubberBand(self.canvas) self.band.setColor(Qt.blue) self.band.setWidth(3) self.rect = QgsRectangle() self.cursor = QCursor(QPixmap(["16 16 3 1", " c None", ". c #32CD32", "+ c #32CD32", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ "])) def findFeature(self, point, button): searchRadius = QgsTolerance.toleranceInMapUnits( self.searchradius, self.layer, \ self.canvas.mapRenderer(), QgsTolerance.Pixels) self.rect.setXMinimum( point.x() - searchRadius ); self.rect.setXMaximum( point.x() + searchRadius ); self.rect.setYMinimum( point.y() - searchRadius ); self.rect.setYMaximum( point.y() + searchRadius ); self.layer.select( self.layer.pendingAllAttributesList(), self.rect, True, True) feature = QgsFeature() self.layer.nextFeature(feature) try: index = self.layer.fieldNameIndex(self.column) value = feature.attributeMap()[index] self.foundFeature.emit(feature, value, self.bindto) except KeyError: return def canvasMoveEvent(self, event): point = self.toMapCoordinates( event.pos() ) searchRadius = QgsTolerance.toleranceInMapUnits( self.searchradius, self.layer, \ self.canvas.mapRenderer(), QgsTolerance.Pixels) self.rect.setXMinimum( point.x() - searchRadius ); self.rect.setXMaximum( point.x() + searchRadius ); self.rect.setYMinimum( point.y() - searchRadius ); self.rect.setYMaximum( point.y() + searchRadius ); self.layer.select( [], self.rect, True, True) feature = QgsFeature() self.layer.nextFeature(feature) if not feature.isValid(): log("Not a vaild feature") return self.band.setToGeometry(feature.geometry(), None) def setActive(self): self.canvas.setMapTool(self) self.canvas.setCursor(self.cursor) def deactivate(self): self.band.hide()
class Dialog(QDialog, Ui_nbEditor_dialog): def __init__(self, iface, ml, mc): """Constructor for the dialog. Args: iface: QgsInterface instance. """ QDialog.__init__(self, iface.mainWindow()) self.setupUi(self) self.ml = ml self.mCanvas = mc self.mRubberBand = QgsRubberBand(self.mCanvas, True) self.mRubberBand.reset(QGis.Polygon) self.mRubberBand.setColor(Qt.red) self.mRubberBand.setWidth(2) self.ids = [] self.ini(0) self.pushCancel.clicked.connect(self.close) self.pushOK.clicked.connect(self.convert) self.comboBox.addItems(['','Intersections','Touches','Within distance']) self.comboBox.currentIndexChanged.connect(self.nbMethod) self.ml.selectionChanged.connect(self.map2tab) def ini(self, n): self.model = QStandardItemModel(n, 1) self.tableView.setModel(self.model) self.model.setHeaderData(0, Qt.Horizontal, 'Neighbouring IDs') self.tableView.setSelectionMode(QAbstractItemView.SingleSelection) self.selectionModel = QItemSelectionModel(self.model) self.tableView.setSelectionModel(self.selectionModel) self.tableView.horizontalHeader().setStretchLastSection(True) self.tableView.selectionModel().selectionChanged.connect(self.tab2map) self.progressBar.setValue(0) def settings(self): self.mod = min(self.ids) self.p = 1 if self.mod==1: self.p = 0 def map2tab(self): s = '' idx = self.tableView.selectionModel().selectedIndexes()[0] ts = str(self.model.itemData(idx)[0]) for fid in sorted(self.ml.selectedFeaturesIds()): s += '%s,' % str(int(fid)+self.p) s = s[:-1] if s!=ts: self.model.setData(idx, s) # in order to handle the symmetry if len(s)>len(ts): iLst = s.strip().replace(' ', '').split(',') jLst = ts.strip().replace(' ', '').split(',') else: iLst = ts.strip().replace(' ', '').split(',') jLst = s.strip().replace(' ', '').split(',') cent = str(idx.row()+self.p) dLst = list(set(iLst)-set(jLst)) for d in dLst: row = int(d)-self.p sor = str(self.model.itemData(self.model.index(row, 0))[0]) eLst = sor.strip().replace(' ', '').split(',') res = '' if cent in set(eLst): ii = eLst.index(cent) del eLst[ii] eLst = sorted(map(int, eLst)) for e in eLst: res += '%s,' % e res = res[:-1] else: u = sor + ',%s' % cent eLst = sorted(map(int, u.strip().replace(' ', '').split(','))) for e in eLst: res += '%s,' % e res = res[:-1] self.model.setData(self.model.index(row, 0, QModelIndex()), res) def nbWithinDist(self): dlg = xdist.Dialog() dlg.setModal(True) dlg.setWindowTitle("Between two objects") if dlg.exec_() == QDialog.Accepted: lDist = float(dlg.lineEdit.text()) if lDist==0: return feat = QgsFeature() provider = self.ml.dataProvider() e = provider.featureCount() self.settings() for ne in range(self.mod, e + self.mod): feat = QgsFeature() geom = QgsGeometry() fiter = self.ml.getFeatures(QgsFeatureRequest(ne)) if fiter.nextFeature(feat): geom = QgsGeometry(feat.geometry()) neighbours = self.hdist(feat, lDist) row = feat.id()-self.mod self.model.setData(self.model.index(row, 0, QModelIndex()), neighbours) self.progressBar.setValue(100*ne/e) def hdist(self, feata, lDist): geoma = QgsGeometry(feata.geometry()) feat = QgsFeature() provider = self.ml.dataProvider() feats = provider.getFeatures() self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, provider.featureCount())) ne = 0 neighbours = "" while feats.nextFeature(feat): ne += 1 self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) geomb = QgsGeometry(feat.geometry()) if feata.id()!=feat.id(): if geoma.distance(geomb)<=lDist: neighbours = neighbours + '%s,' % (feat.id()+self.p) return neighbours[:-1] def tab2map(self): QApplication.setOverrideCursor(Qt.WaitCursor) self.ml.selectionChanged.disconnect(self.map2tab) idx = self.tableView.selectionModel().selectedIndexes()[0] featureId = idx.row() + self.p s = self.model.itemData(idx) lst = s[0].strip().replace(' ', '').split(',') self.ml.removeSelection() for sid in lst: self.ml.select(int(sid)-self.p) provider = self.ml.dataProvider() feat = QgsFeature() layer = QgsVectorLayerCache(self.ml, provider.featureCount()) layer.featureAtId(idx.row()+self.mod, feat) geom = QgsGeometry(feat.geometry()) self.mRubberBand.setToGeometry(geom, self.ml) self.mRubberBand.show() self.ml.selectionChanged.connect(self.map2tab) QApplication.restoreOverrideCursor() def closeEvent(self,event): QApplication.setOverrideCursor(Qt.WaitCursor) self.ml.selectionChanged.disconnect(self.map2tab) self.ml.removeSelection() self.mRubberBand.hide() self.close() QApplication.restoreOverrideCursor() def convert(self): dlg = editor.Dialog() dlg.setModal(True) dlg.setWindowTitle("Neighbour list in BUGS format") num = "" adj = "" sumNumNeigh = 0 for row in range(0, self.model.rowCount()): ts = self.model.itemData(self.model.index(row, 0)) lst = ts[0].strip().replace(' ', '').split(',') num += '%s, ' % len(lst) sumNumNeigh += len(lst) lst.reverse() sor = ', '.join(lst) + ',' adj = adj + str(sor) + '\n' num = num[:-2] adj = adj[:-2] nblist = 'list(\nnum = c(%s),\nadj = c(%s),\nsumNumNeigh=%s)' % (num, adj, sumNumNeigh) dlg.plainTextEdit.appendPlainText(nblist) dlg.exec_() def nbMethod(self): QApplication.setOverrideCursor(Qt.WaitCursor) self.ml.selectionChanged.disconnect(self.map2tab) self.model.removeRows(0, self.model.rowCount(QModelIndex()), QModelIndex()) n = self.ml.dataProvider().featureCount() self.ini(n) self.ids = [] provider = self.ml.dataProvider() feats = provider.getFeatures() self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, n)) ne = 0 feat = QgsFeature() while feats.nextFeature(feat): ne += 1 self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) self.ids.append(feat.id()) if self.comboBox.currentText()=="Touches": if self.ml.geometryType()==0: return else: self.nbTouches() if self.comboBox.currentText()=="Intersections": if self.ml.geometryType()==0: return else: self.nbIntersects() if self.comboBox.currentText()=="Within distance": self.nbWithinDist() self.ml.selectionChanged.connect(self.map2tab) QApplication.restoreOverrideCursor() def nbTouches(self): feat = QgsFeature() provider = self.ml.dataProvider() e = provider.featureCount() self.settings() for ne in range(self.mod, e + self.mod): feat = QgsFeature() geom = QgsGeometry() fiter = self.ml.getFeatures(QgsFeatureRequest(ne)) if fiter.nextFeature(feat): geom = QgsGeometry(feat.geometry()) neighbours = self.htouch(feat) row = feat.id()-self.mod self.model.setData(self.model.index(row, 0, QModelIndex()), neighbours) self.progressBar.setValue(100*ne/e) def htouch(self, feata): geoma = QgsGeometry(feata.geometry()) feat = QgsFeature() provider = self.ml.dataProvider() feats = provider.getFeatures() self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, provider.featureCount())) ne = 0 neighbours = "" while feats.nextFeature(feat): ne += 1 self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) geomb = QgsGeometry(feat.geometry()) if feata.id()!=feat.id(): if geoma.touches(geomb)==True: neighbours = neighbours + '%s,' % (feat.id()+self.p) return neighbours[:-1] def nbIntersects(self): feat = QgsFeature() provider = self.ml.dataProvider() e = provider.featureCount() self.settings() for ne in range(self.mod, e + self.mod): feat = QgsFeature() geom = QgsGeometry() fiter = self.ml.getFeatures(QgsFeatureRequest(ne)) if fiter.nextFeature(feat): geom = QgsGeometry(feat.geometry()) neighbours = self.hintersect(feat) row = feat.id()-self.mod self.model.setData(self.model.index(row, 0, QModelIndex()), neighbours) self.progressBar.setValue(100*ne/e) def hintersect(self, feata): geoma = QgsGeometry(feata.geometry()) feat = QgsFeature() provider = self.ml.dataProvider() feats = provider.getFeatures() self.emit(SIGNAL("runStatus(PyQt_PyObject)"), 0) self.emit(SIGNAL("runRange(PyQt_PyObject)"), (0, provider.featureCount())) ne = 0 neighbours = "" while feats.nextFeature(feat): ne += 1 self.emit(SIGNAL("runStatus(PyQt_PyObject)"), ne) geomb = QgsGeometry(feat.geometry()) if feata.id()!=feat.id(): if geoma.intersects(geomb)==True: neighbours = neighbours + '%s,' % (feat.id()+self.p) return neighbours[:-1]
class TmMultipleSelection(QgsMapTool): canvasClicked = pyqtSignal() def __init__(self, iface, controller, layers, parent_manage=None, table_object=None): """ Class constructor """ self.layers = layers self.iface = iface self.canvas = self.iface.mapCanvas() self.parent_manage = parent_manage self.table_object = table_object # Call superclass constructor and set current action QgsMapTool.__init__(self, self.canvas) self.controller = controller self.rubber_band = QgsRubberBand(self.canvas, 2) self.rubber_band.setColor(QColor(255, 100, 255)) self.rubber_band.setFillColor(QColor(254, 178, 76, 63)) self.rubber_band.setWidth(1) self.reset() self.snapper = self.get_snapper() self.selected_features = [] def reset_rubber_band(self): try: self.rubber_band.reset(2) except: pass def reset(self): self.start_point = self.end_point = None self.is_emitting_point = False self.reset_rubber_band() def canvasPressEvent(self, e): if e.button() == Qt.LeftButton: self.start_point = self.toMapCoordinates(e.pos()) self.end_point = self.start_point self.is_emitting_point = True self.show_rect(self.start_point, self.end_point) def canvasReleaseEvent(self, event): self.is_emitting_point = False rectangle = self.get_rectangle() selected_rectangle = None key = QApplication.keyboardModifiers() if event.button() != Qt.LeftButton: self.rubber_band.hide() return # Disconnect signal to enhance process # We will reconnect it when processing last layer of the group if self.parent_manage: self.parent_manage.disconnect_signal_selection_changed() for layer in self.layers: if self.controller.is_layer_visible(layer): if self.parent_manage: self.parent_manage.connect_signal_selection_changed( self.table_object) # Selection by rectangle if rectangle: if selected_rectangle is None: selected_rectangle = self.canvas.mapSettings( ).mapToLayerCoordinates(layer, rectangle) # If Ctrl+Shift clicked: remove features from selection if key == (Qt.ControlModifier | Qt.ShiftModifier): layer.selectByRect(selected_rectangle, layer.RemoveFromSelection) # If Ctrl clicked: add features to selection elif key == Qt.ControlModifier: layer.selectByRect(selected_rectangle, layer.AddToSelection) # If Ctrl not clicked: add features to selection else: layer.selectByRect(selected_rectangle, layer.AddToSelection) # Selection one by one else: event_point = self.snapper_manager.get_event_point(event) result = self.snapper_manager.snap_to_background_layers( event_point) if self.snapper_manager.result_is_valid(): # Get the point. Leave selection self.snapper_manager.get_snapped_feature(result, True) self.rubber_band.hide() def canvasMoveEvent(self, e): if not self.is_emitting_point: return self.end_point = self.toMapCoordinates(e.pos()) self.show_rect(self.start_point, self.end_point) def show_rect(self, start_point, end_point): self.reset_rubber_band() if start_point.x() == end_point.x() or start_point.y() == end_point.y( ): return point1 = QgsPointXY(start_point.x(), start_point.y()) point2 = QgsPointXY(start_point.x(), end_point.y()) point3 = QgsPointXY(end_point.x(), end_point.y()) point4 = QgsPointXY(end_point.x(), start_point.y()) self.rubber_band.addPoint(point1, False) self.rubber_band.addPoint(point2, False) self.rubber_band.addPoint(point3, False) self.rubber_band.addPoint(point4, True) self.rubber_band.show() def get_rectangle(self): if self.start_point is None or self.end_point is None: return None elif self.start_point.x() == self.end_point.x() or self.start_point.y( ) == self.end_point.y(): return None return QgsRectangle(self.start_point, self.end_point) def deactivate(self): self.rubber_band.hide() QgsMapTool.deactivate(self) def activate(self): pass def get_snapper(self): """ Return snapper """ snapper = QgsMapCanvas.snappingUtils(self.canvas) return snapper
class ShapeTool(QgsMapTool): #signal emitted when the mouse is clicked. This indicates that the tool finished its job toolFinished = pyqtSignal() def __init__(self, canvas, geometryType, param, type, color = QColor( 254, 178, 76, 63 )): """ Constructor """ QgsMapTool.__init__(self, canvas) self.canvas = canvas self.active = False self.geometryType = self.tr(geometryType) self.param=param self.type=type self.cursor=None self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.setColor(color) self.reset() self.rotAngle = 0 self.currentCentroid = None self.rotate = False def setColor(self, mFillColor): """ Adjusting the color to create the rubber band """ self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(1) def reset(self): """ Resetting the rubber band """ self.startPoint = self.endPoint = None self.isEmittingPoint = False try: self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) except: pass def rotateRect(self, centroid, e): """ Calculates the angle for the rotation. """ item_position = self.canvas.mapToGlobal(e.pos()) c = self.toCanvasCoordinates(centroid) c = self.canvas.mapToGlobal(c) rotAngle = pi - atan2( item_position.y() - c.y(), item_position.x() - c.x()) return rotAngle def canvasPressEvent(self, e): """ When the canvas is pressed the tool finishes its job """ # enforce mouse restoring if clicked right after rotation QApplication.restoreOverrideCursor() self.canvas.unsetMapTool(self) self.toolFinished.emit() def _baseDistanceInMeters(self): """ Calculates the distance in meters of 2 points 1 unit map away on current canvas CRS. :return: (float) distance in meters between two points 1 map unit apart from each other. """ source_crs = self.canvas.mapSettings().destinationCrs() dest_crs = QgsCoordinateReferenceSystem(3857) tr = QgsCoordinateTransform( source_crs, dest_crs, QgsCoordinateTransformContext()) p1t = QgsGeometry().fromPointXY(QgsPointXY(1, 0)) p1t.transform(tr) p2t = QgsGeometry().fromPointXY(QgsPointXY(0, 0)) p2t.transform(tr) return QgsDistanceArea().measureLine(p1t.asPoint(), p2t.asPoint()) def getAdjustedSize(self, size): """ If map unit is not metric, the figure to be drawn needs to have its size adjusted. This is necessary because input parameters are designed to be meters on tool's GUI. :param size: (float) tool's radius/length reference size in meters. :return: (float) """ source_crs = self.canvas.mapSettings().destinationCrs() if source_crs.mapUnits() != QgsUnitTypes.DistanceMeters: return size / self._baseDistanceInMeters() return size def canvasMoveEvent(self, e): """ Deals with mouse move event to update the rubber band position in the canvas """ ctrlIsHeld = QApplication.keyboardModifiers() == Qt2.ControlModifier if e.button() != None and not ctrlIsHeld: if self.rotate: # change rotate status self.rotate = False QApplication.restoreOverrideCursor() self.endPoint = self.toMapCoordinates( e.pos() ) elif e.button() != None and ctrlIsHeld \ and self.geometryType == self.tr(u"Square"): # calculate angle between mouse and last rubberband centroid before holding control self.rotAngle = self.rotateRect(self.currentCentroid, e) if not self.rotate: # only override mouse if it is not overriden already QApplication.setOverrideCursor(QCursor(Qt2.BlankCursor)) self.rotate = True if self.geometryType == self.tr(u"Circle"): self.showCircle(self.endPoint) elif self.geometryType == self.tr(u"Square"): self.showRect(self.endPoint, sqrt(self.param)/2, self.rotAngle) def showCircle(self, startPoint): """ Draws a circle in the canvas """ nPoints = 50 x = startPoint.x() y = startPoint.y() if self.type == self.tr('distance'): r = self.getAdjustedSize(self.param) self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) for itheta in range(nPoints+1): theta = itheta*(2.0*pi/nPoints) self.rubberBand.addPoint(QgsPointXY(x+r*cos(theta), y+r*sin(theta))) self.rubberBand.show() else: r = self.getAdjustedSize(sqrt(self.param/pi)) self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) for itheta in range(nPoints+1): theta = itheta*(2.0*pi/nPoints) self.rubberBand.addPoint(QgsPointXY(x+r*cos(theta), y+r*sin(theta))) self.rubberBand.show() def showRect(self, startPoint, param, rotAngle=0): """ Draws a rectangle in the canvas """ self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) x = startPoint.x() # center point x y = startPoint.y() # center point y # rotation angle is always applied in reference to center point # to avoid unnecessary calculations c = cos(rotAngle) s = sin(rotAngle) # translating coordinate system to rubberband centroid param = self.getAdjustedSize(param) for posx, posy in ((-1, -1), (-1, 1), (1, 1), (1, -1)): px = posx * param py = posy * param pnt = QgsPointXY(px * c - py * s + x, py * c + px * s + y) self.rubberBand.addPoint(pnt, False) self.rubberBand.setVisible(True) self.rubberBand.updateRect() self.rubberBand.update() self.rubberBand.show() self.currentCentroid = startPoint def deactivate(self): """ Deactivates the tool and hides the rubber band """ self.rubberBand.hide() QgsMapTool.deactivate(self) # restore mouse in case tool is disabled right after rotation QApplication.restoreOverrideCursor() def activate(self): """ Activates the tool """ QgsMapTool.activate(self)
class MultipleSelection(QgsMapTool): def __init__(self, layers, geom_type, mincut=None, parent_manage=None, manage_new_psector=None, table_object=None, dialog=None): """ Class constructor :param layers: dict of list of layers {'arc': [v_edit_node, ...]} :param geom_type: :param mincut: :param parent_manage: :param manage_new_psector: :param table_object: :param dialog: """ self.layers = layers self.geom_type = geom_type self.iface = global_vars.iface self.canvas = global_vars.canvas self.mincut = mincut self.parent_manage = parent_manage self.manage_new_psector = manage_new_psector self.table_object = table_object self.dialog = dialog # Call superclass constructor and set current action QgsMapTool.__init__(self, self.canvas) self.controller = global_vars.controller self.rubber_band = QgsRubberBand(self.canvas, 2) self.rubber_band.setColor(QColor(255, 100, 255)) self.rubber_band.setFillColor(QColor(254, 178, 76, 63)) self.rubber_band.setWidth(1) self.reset() self.selected_features = [] def reset(self): self.start_point = self.end_point = None self.is_emitting_point = False self.reset_rubber_band() def canvasPressEvent(self, event): if event.button() == Qt.LeftButton: self.start_point = self.toMapCoordinates(event.pos()) self.end_point = self.start_point self.is_emitting_point = True self.show_rect(self.start_point, self.end_point) def canvasReleaseEvent(self, event): self.is_emitting_point = False rectangle = self.get_rectangle() selected_rectangle = None key = QApplication.keyboardModifiers() if event.button() != Qt.LeftButton: self.rubber_band.hide() return # Disconnect signal to enhance process # We will reconnect it when processing last layer of the group disconnect_signal_selection_changed() if self.manage_new_psector: self.manage_new_psector.disconnect_signal_selection_changed() for i, layer in enumerate(self.layers[self.geom_type]): if i == len(self.layers[self.geom_type]) - 1: if self.mincut: self.mincut.connect_signal_selection_changed( "mincut_connec") if self.parent_manage: connect_signal_selection_changed(self.dialog, self.table_object, layers=self.layers) if self.manage_new_psector: self.manage_new_psector.connect_signal_selection_changed( self.manage_new_psector.dlg_plan_psector, self.table_object) # Selection by rectangle if rectangle: if selected_rectangle is None: selected_rectangle = self.canvas.mapSettings( ).mapToLayerCoordinates(layer, rectangle) # If Ctrl+Shift clicked: remove features from selection if key == (Qt.ControlModifier | Qt.ShiftModifier): layer.selectByRect(selected_rectangle, layer.RemoveFromSelection) # If Ctrl clicked: add features to selection elif key == Qt.ControlModifier: layer.selectByRect(selected_rectangle, layer.AddToSelection) # If Ctrl not clicked: add features to selection else: layer.selectByRect(selected_rectangle, layer.AddToSelection) # Selection one by one else: event_point = get_event_point(event) result = snap_to_background_layers(event_point) if result.isValid(): # Get the point. Leave selection get_snapped_feature(result, True) self.rubber_band.hide() def canvasMoveEvent(self, event): if not self.is_emitting_point: return self.end_point = self.toMapCoordinates(event.pos()) self.show_rect(self.start_point, self.end_point) def show_rect(self, start_point, end_point): self.reset_rubber_band() if start_point.x() == end_point.x() or start_point.y() == end_point.y( ): return point1 = QgsPointXY(start_point.x(), start_point.y()) point2 = QgsPointXY(start_point.x(), end_point.y()) point3 = QgsPointXY(end_point.x(), end_point.y()) point4 = QgsPointXY(end_point.x(), start_point.y()) self.rubber_band.addPoint(point1, False) self.rubber_band.addPoint(point2, False) self.rubber_band.addPoint(point3, False) self.rubber_band.addPoint(point4, True) self.rubber_band.show() def get_rectangle(self): if self.start_point is None or self.end_point is None: return None elif self.start_point.x() == self.end_point.x() or self.start_point.y( ) == self.end_point.y(): return None return QgsRectangle(self.start_point, self.end_point) def deactivate(self): self.rubber_band.hide() QgsMapTool.deactivate(self) def activate(self): pass def reset_rubber_band(self): try: self.rubber_band.reset(2) except: pass
class QvPrint(QWidget): """Una classe del tipus QWidget que servirà per imprimir un area determinada. El widget conté un botó per imprimir, un per tornar a posicionar l'area d'impresió, i un comboBox per escollir l'escala. """ def __init__(self, project, canvas, poligon, parent=None): """Inicialització de la clase: Arguments: project {QgsProject().instance()} -- El projecte actiu canvas {QgsVectorLayer} -- El canvas sobre el que es coloca la rubberband. poligon {QgsPoligon} -- Poligon inicial. A revisar. """ # We inherit our parent's properties and methods. QWidget.__init__(self, parent) self.parent = parent # Creating a memory layer to draw later the rubberband. estatDirtybit = self.parent.canvisPendents self.layer = QgsVectorLayer('Point?crs=epsg:23031', "Capa temporal d'impressió", "memory") project.addMapLayer(self.layer, False) # We store safely the parameters as class variables. self.canvas = canvas self.project = project self.poligon = poligon # Offset inicial del rectangle d'impressió- TODO self.incX = 100 self.incY = 150 # Semafor per deixar fix el rectangle un cop fet click. self.pucMoure = True # Diccionari d'escales i proporcions que fixen el tamany del rectangle en pantalla. # Podria fer-se millor, pero Practicality beats Purity... self.dictEscales = { '100': 20, '200': 40, '250': 45, '500': 100, '1000': 200, '2000': 400, '2500': 450, '5000': 1000, '10000': 2000, '20000': 4000, '25000': 4500, '50000': 10000 } # We instanciate de PointTool tool, to wait for clicks # After that, we assign the tool to the canvas. rp = PointTool(self, self.canvas) canvas.setMapTool(rp) self.setupUI() self.rubberband = QgsRubberBand(self.canvas) self.rubberband.setColor(QColor(0, 0, 0, 50)) self.rubberband.setWidth(4) self.canvas.xyCoordinates.connect(self.mocMouse) self.pintarRectangle(self.poligon) self.rubberband.hide() self.parent.setDirtyBit(estatDirtybit) def setupUI(self): self.layout = QVBoxLayout(self) self.setLayout(self.layout) self.layout.setContentsMargins(10, 20, 10, 20) self.layout.setSpacing(14) # self.layout.setAlignment(Qt.AlignTop) self.layoutTitol = QHBoxLayout() self.lblTitol = QLabel("TÃtol: ") self.leTitol = QLineEdit(self) self.leTitol.setText(self.parent.titolProjecte) self.layoutTitol.addWidget(self.lblTitol) self.layoutTitol.addWidget(self.leTitol) self.cbOrientacio = QComboBox(self) self.cbOrientacio.addItems(['Vertical', 'Horitzontal']) self.cbOrientacio.SelectedItem = "Vertical" self.cbOrientacio.setCurrentIndex(1) self.cbOrientacio.currentTextChanged.connect(self.canviOrientacio) self.lblCBOrientacio = QLabel("Orientació: ") self.layoutCBOrientacio = QHBoxLayout() self.layoutCBOrientacio.addWidget(self.lblCBOrientacio) self.layoutCBOrientacio.addWidget(self.cbOrientacio) self.combo = QComboBox(self) llistaEscales = [key for key in self.dictEscales] self.combo.addItems(llistaEscales) self.combo.currentTextChanged.connect(self.canviEscala) self.lblEscales = QLabel("Escales") self.layEscales = QHBoxLayout() self.layEscales.addWidget(self.lblEscales) self.layEscales.addWidget(self.combo) self.cbMida = QComboBox(self) self.cbMida.addItems(['A0', 'A1', 'A2', 'A3', 'A4']) self.cbMida.currentTextChanged.connect(self.canviEscala) self.cbMida.setCurrentIndex(4) self.lblCBmida = QLabel("Paper: ") self.layoutCBmida = QHBoxLayout() self.layoutCBmida.addWidget(self.lblCBmida) self.layoutCBmida.addWidget(self.cbMida) self.boto = QvPushButton(text='Generar PDF', destacat=True, parent=self) self.boto.clicked.connect(self.printPlanol) self.boto.setFixedWidth(220) self.boto2 = QvPushButton(text='Emmarcar zona a imprimir', parent=self) self.boto2.clicked.connect(self.potsMoure) self.boto2.setFixedWidth(220) self.nota = QLabel( "NOTA: Alguns navegadors web alteren l'escala d'impressió dels PDFs. Per mà xima exactitud imprimiu des de l'Adobe Acrobat." ) styleheetLabel = ''' QLabel { color: grey; }''' self.nota.setStyleSheet(styleheetLabel) self.nota.setMaximumWidth(200) self.nota.setWordWrap(True) self.layout.addLayout(self.layoutTitol) self.layout.addLayout(self.layEscales) self.layout.addLayout(self.layoutCBmida) self.layout.addLayout(self.layoutCBOrientacio) self.layout.addWidget(self.boto2) self.layout.addWidget(self.boto) self.layout.addWidget(self.nota) # self.layout.addWidget(self.wFormat) # self.layout.addWidget(self.rbVertical) # self.layout.addWidget(self.rbHoritzontal) self.layout.addStretch() def potsMoure(self): # self.canvas.scene().removeItem(self.rubberband) self.pucMoure = True def canviEscala(self): self.pucMoure = True escala = int(self.dictEscales[self.combo.currentText()]) mida = self.cbMida.currentText() if mida == 'A3': escala *= math.sqrt(2) elif mida == 'A2': escala *= math.sqrt(2) * 2 elif mida == 'A1': escala *= math.sqrt(2) * 3 elif mida == 'A0': escala *= math.sqrt(2) * 4 if self.cbOrientacio.SelectedItem == "Horitzontal": self.incX = escala self.incY = escala * 1.5 else: self.incX = escala * 1.5 self.incY = escala def canviOrientacio(self): self.pucMoure = True if self.cbOrientacio.SelectedItem == "Vertical": self.cbOrientacio.SelectedItem = "Horitzontal" else: self.cbOrientacio.SelectedItem = "Vertical" self.incX, self.incY = self.incY, self.incX def canvasClickat(self): #??? print('Clickat, si') def mocMouse(self, p): if not self.isVisible(): self.rubberband.hide() self.pucMoure elif self.pucMoure: if self.canvas.rotation() == 0: self.posXY = [p.x() + self.incX / 2, p.y() + self.incY / 2] self.rubberband.movePoint( 0, QgsPointXY(p.x() + self.incX, p.y() + self.incY), 0) self.rubberband.movePoint(1, QgsPointXY(p.x() + self.incX, p.y()), 0) self.rubberband.movePoint(2, QgsPointXY(p.x(), p.y()), 0) self.rubberband.movePoint(3, QgsPointXY(p.x(), p.y() + self.incY), 0) self.rubberband.movePoint( 4, QgsPointXY(p.x() + self.incX, p.y() + self.incY), 0) else: alpha = math.radians(self.canvas.rotation()) beta = math.atan(self.incY / self.incX) d = math.sqrt(self.incX**2 + self.incY**2) self.posXY = [(2 * p.x() + d * math.cos(alpha + beta)) / 2, (2 * p.y() + d * math.sin(alpha + beta)) / 2] self.rubberband.movePoint( 0, QgsPointXY(p.x() + d * math.cos(alpha + beta), p.y() + d * math.sin(alpha + beta)), 0) self.rubberband.movePoint( 1, QgsPointXY(p.x() + self.incX * math.cos(alpha), p.y() + self.incX * math.sin(alpha)), 0) self.rubberband.movePoint(2, QgsPointXY(p.x(), p.y()), 0) self.rubberband.movePoint( 3, QgsPointXY( p.x() + self.incY * math.cos(math.radians(90 + 45)), p.y() + self.incY * math.sin(math.radians(90 + 45))), 0) self.rubberband.movePoint( 4, QgsPointXY(p.x() + d * math.cos(alpha + beta), p.y() + d * math.sin(alpha + beta)), 0) self.rubberband.show() def pintarRectangle(self, poligon): points = [ QgsPointXY(0, 0), QgsPointXY(0, 10), QgsPointXY(10, 10), QgsPointXY(0, 10), QgsPointXY(0, 0) ] poligono = QgsGeometry.fromRect(self.poligon) self.rubberband.setToGeometry(poligono, self.layer) def printPlanol(self): # # if self.checkRotacio.checkState(): # rotacio=44.75 # else: # rotacio=0 rotacio = self.canvas.rotation() if self.cbOrientacio.currentText() == "Vertical": if self.cbMida.currentText() == "A4": self.plantillaMapa = pathPlantilles + 'plantillaMapa.qpt' elif self.cbMida.currentText() == "A3": self.plantillaMapa = pathPlantilles + 'plantillaMapaA3.qpt' elif self.cbMida.currentText() == "A2": self.plantillaMapa = pathPlantilles + 'plantillaMapaA2.qpt' elif self.cbMida.currentText() == "A1": self.plantillaMapa = pathPlantilles + 'plantillaMapaA1.qpt' elif self.cbMida.currentText() == "A0": self.plantillaMapa = pathPlantilles + 'plantillaMapaA0.qpt' else: if self.cbMida.currentText() == "A4": self.plantillaMapa = pathPlantilles + 'plantillaMapaH.qpt' elif self.cbMida.currentText() == "A3": self.plantillaMapa = pathPlantilles + 'plantillaMapaA3H.qpt' elif self.cbMida.currentText() == "A2": self.plantillaMapa = pathPlantilles + 'plantillaMapaA2H.qpt' elif self.cbMida.currentText() == "A1": self.plantillaMapa = pathPlantilles + 'plantillaMapaA1H.qpt' elif self.cbMida.currentText() == "A0": self.plantillaMapa = pathPlantilles + 'plantillaMapaA0H.qpt' t = time.localtime() timestamp = time.strftime('%d-%b-%Y_%H%M%S', t) sortida = tempdir + 'sortida_' + timestamp self.imprimirPlanol(self.posXY[0], self.posXY[1], int(self.combo.currentText()), rotacio, self.cbMida.currentText(), self.plantillaMapa, sortida, 'PDF') QvApp().logRegistre('Impressió: ' + self.combo.currentText()) def imprimirPlanol(self, x, y, escala, rotacion, midaPagina, templateFile, fitxerSortida, tipusSortida): tInicial = time.time() template = QFile(templateFile) doc = QDomDocument() doc.setContent(template, False) layout = QgsLayout(self.project) # page=QgsLayoutItemPage(layout) # page.setPageSize(midaPagina) # layout.pageCollection().addPage(page) # layout.initializeDefaults() # p=layout.pageCollection().pages()[0] # p.setPageSize(midaPagina) context = QgsReadWriteContext() [items, ok] = layout.loadFromTemplate(doc, context) # p=layout.pageCollection().pages()[0] # p.setPageSize(midaPagina) if ok: refMap = layout.referenceMap() titol = layout.itemById('idNomMapa') dataMapa = layout.itemById('idData') if self.leTitol.text() != '': titol.setText(self.leTitol.text()) #comentat pk peta else: titol.setText('') try: t = time.localtime() dataMapa.setText(strftime('%b-%d-%Y %H:%M', t)) except: pass rect = refMap.extent() vector = QgsVector(x - rect.center().x(), y - rect.center().y()) rect += vector refMap.setExtent(rect) refMap.setScale(escala) refMap.setMapRotation(rotacion) #Depenent del tipus de sortida... exporter = QgsLayoutExporter(layout) # image_settings = exporter.ImageExportSettings() # image_settings.dpi = 30 # result = exporter.exportToImage('d:/dropbox/qpic/preview.png', image_settings) # imatge = QPixmap('d:/dropbox/qpic/preview.png') # self.ui.lblImatgeResultat.setPixmap(imatge) if tipusSortida == 'PDF': settings = QgsLayoutExporter.PdfExportSettings() settings.dpi = 300 settings.exportMetadata = False # fitxerSortida='d:/sortida_'+timestamp+'.PDF' fitxerSortida += '.PDF' result = exporter.exportToPdf( fitxerSortida, settings) #Cal desar el resultat (???) print(fitxerSortida) if tipusSortida == 'PNG': settings = QgsLayoutExporter.ImageExportSettings() settings.dpi = 300 # fitxerSortida='d:/sortida_'+timestamp+'.PNG' fitxerSortida += '.PNG' result = exporter.exportToImage( fitxerSortida, settings) #Cal desar el resultat (???) #Obra el document si està marcat checkObrirResultat QDesktopServices().openUrl(QUrl(fitxerSortida)) segonsEmprats = round(time.time() - tInicial, 1) #??? layersTemporals = self.project.mapLayersByName( "Capa temporal d'impressió") estatDirtybit = self.parent.canvisPendents for layer in layersTemporals: self.project.removeMapLayer(layer.id()) self.parent.setDirtyBit(estatDirtybit) def oculta(self): #Eliminem la capa temporal estatDirtybit = self.parent.canvisPendents layersTemporals = self.project.mapLayersByName( "Capa temporal d'impressió") for layer in layersTemporals: self.project.removeMapLayer(layer.id()) self.parent.setDirtyBit(estatDirtybit)
class RectTool(QgsMapToolEmitPoint): """Rectangle Map tool to capture mapped extent.""" def __init__(self, canvas): """ :param canvas: current map canvas :type canvas: QgsMapCanvas """ self.canvas = canvas QgsMapToolEmitPoint.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, True) # self.rubberBand.setFillColor(QColor(DEFAULT_COLOR)) self.rubberBand.setStrokeColor(QColor(DEFAULT_COLOR)) self.rubberBand.setWidth(2) self.reset() def reset(self): """reset rubberband and captured points.""" self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(True) def canvasPressEvent(self, e): """Initialize rectangle drawing.""" self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) updateLabels = pyqtSignal("QgsRectangle") def canvasReleaseEvent(self, e): """Emits rectangle when button is released, delete rubberband.""" self.isEmittingPoint = False r = self.rectangle() if r is not None: self.updateLabels.emit(r) self.rubberBand.hide() del self.rubberBand def canvasMoveEvent(self, e): """Draw rectangle""" if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): """ Build ruberband from two points. :param startPoint: first clicked point :type startPoint: QgsPointXY :param endPoint: Point at mouse release :type endPoint: QgsPointXY """ self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): """Build rectangle to emit.""" if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y(): return None crsSrc = self.canvas.mapSettings().destinationCrs() xform = transformToWGS(crsSrc) startPoint_WGS = xform.transform(self.startPoint) endPoint_WGS = xform.transform(self.endPoint) return QgsRectangle(startPoint_WGS, endPoint_WGS) def deactivate(self): super(RectTool, self).deactivate() self.deactivated.emit()
class MultiLayerSelection(QgsMapTool): finished = QtCore.pyqtSignal(list) def __init__(self, canvas, iface): """ Tool Behaviours: (all behaviours start edition, except for rectangle one) 1- Left Click: Clears previous selection, selects feature, sets feature layer as active layer. The selection is done with the following priority: Point, Line then Polygon. Selection is only done in visible layer. 2- Control + Left Click: Adds to selection selected feature. This selection follows the priority in item 1. 3- Right Click: Opens feature form 4- Control + Right Click: clears selection and set feature's layer as activeLayer. activeLayer's definition follows priority of item 1; 5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangle are added to selection """ self.iface=iface self.canvas = canvas self.toolAction = None QgsMapTool.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon) mFillColor = QColor( 254, 178, 76, 63 ) self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(1) self.reset() self.blackList = ['moldura'] def reset(self): """ Resets rubber band. """ self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QGis.Polygon) def canvasMoveEvent(self, e): """ Used only on rectangle select. """ if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates( e.pos() ) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): """ Builds rubberband rect. """ self.rubberBand.reset(QGis.Polygon) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPoint(startPoint.x(), startPoint.y()) point2 = QgsPoint(startPoint.x(), endPoint.y()) point3 = QgsPoint(endPoint.x(), endPoint.y()) point4 = QgsPoint(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): """ Builds rectangle from self.startPoint and self.endPoint """ if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def setAction(self, action): self.toolAction = action self.toolAction.setCheckable(True) def canvasReleaseEvent(self, e): """ After the rectangle is built, here features are selected. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = False r = self.rectangle() layers = self.canvas.layers() for layer in layers: #ignore layers on black list and features that are not vector layers if layer.type() != QgsMapLayer.VectorLayer or (self.layerHasPartInBlackList(layer.name())): continue if r is not None: #builds bbRect and select from layer, adding selection bbRect = self.canvas.mapSettings().mapToLayerCoordinates(layer, r) layer.select(bbRect, True) self.rubberBand.hide() def canvasPressEvent(self, e): """ Method used to build rectangle if shift is held, otherwise, feature select/deselect and identify is done. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = True self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) else: self.isEmittingPoint = False selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) self.selectFeatures(e, hasControlModifyer = selected) def getCursorRect(self, e): """ Calculates small cursor rectangle around mouse position. Used to facilitate operations """ p = self.toMapCoordinates(e.pos()) w = self.canvas.mapUnitsPerPixel() * 10 return QgsRectangle(p.x()-w, p.y()-w, p.x()+w, p.y()+w) def layerHasPartInBlackList(self, lyrName): """ Verifies if terms in black list appear on lyrName """ for item in self.blackList: if item.lower() in lyrName.lower(): return True return False def getPrimitiveDict(self, e, hasControlModifyer = False): """ Builds a dict with keys as geometryTypes of layer, which are QGis.Point (value 0), QGis.Line (value 1) or QGis.Polygon (value 2), and values as layers from self.iface.legendInterface().layers(). When self.iface.legendInterface().layers() is called, a list of layers ordered according to lyr order in TOC is returned. """ #these layers are ordered by view order primitiveDict = dict() for lyr in self.iface.legendInterface().layers(): #ordered layers #layer types other than VectorLayer are ignored, as well as layers in black list and layers that are not visible if (lyr.type() != QgsMapLayer.VectorLayer) or (self.layerHasPartInBlackList(lyr.name())) or not self.iface.legendInterface().isLayerVisible(lyr): continue geomType = lyr.geometryType() if geomType not in primitiveDict.keys(): primitiveDict[geomType] = [] #removes selection if (not hasControlModifyer and e.button() == QtCore.Qt.LeftButton) or (hasControlModifyer and e.button() == QtCore.Qt.RightButton): lyr.removeSelection() primitiveDict[geomType].append(lyr) return primitiveDict def selectFeatures(self, e, bbRect = None, hasControlModifyer = False): """ Method to select features acoording to mouse event e. Optional parameters: bbRect: if supplied, other rectangle is used hasControlModifyer: used to add to selection or not. """ rect = self.getCursorRect(e) primitiveDict = self.getPrimitiveDict(e, hasControlModifyer = hasControlModifyer) primitives = primitiveDict.keys() primitives.sort() #this sort enables search to be done in the order of Point (value 0), Line (value 1) and Polygon (value 2) for primitive in primitives: for lyr in primitiveDict[primitive]: bbRect = self.canvas.mapSettings().mapToLayerCoordinates(lyr, rect) for feat in lyr.getFeatures(QgsFeatureRequest(bbRect)): selectedIds = lyr.selectedFeaturesIds() #list of selected ids if feat.geometry().intersects(bbRect): #tests if feature intersects tool bounding box, otherwise skip it lyr.startEditing() #starts layer editting if e.button() == QtCore.Qt.RightButton: #set target, start edit and stop if hasControlModifyer: #sets active layer. Since hasControlModifyer indicates to this method to clear selection, this part of #the code completes the control + right click behaviour. self.iface.setActiveLayer(lyr) return else: #opens feature form. The tag showModal is to lock qgis window or not. #Current procedure is to imitate qgis way of doing things, so showModal = False self.iface.openFeatureForm(lyr,feat, showModal=False) return #if code reaches here, it means that it is an incremental selection. if feat.id() in selectedIds: lyr.modifySelection([],[feat.id()]) else: lyr.modifySelection([feat.id()],[]) if not hasControlModifyer: self.iface.setActiveLayer(lyr) return def deactivate(self): """ Deactivate tool. """ if self.toolAction: self.toolAction.setChecked(False) if self is not None: QgsMapTool.deactivate(self) def activate(self): """ Activate tool. """ if self.toolAction: self.toolAction.setChecked(True) QgsMapTool.activate(self)
class QGISRedMultiLayerSelection(QgsMapTool): def __init__(self, iface, canvas, action): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.iface = iface self.setAction(action) self.myRubberBand = QgsRubberBand(self.canvas, 3) # 3= Polygon mFillColor = QColor(255, 0, 0, 100) self.myRubberBand.setColor(mFillColor) self.myRubberBand.setWidth(2) self.myRubberBand.setLineStyle(2) self.rubberBand1 = None self.rubberBand2 = None self.reset() def deactivate(self): self.reset() self.myRubberBand.hide() QgsMapTool.deactivate(self) def activate(self): QgsMapTool.activate(self) """Methods""" def reset(self): self.initialPoint = None self.finalPoint = None self.isSelecting = False self.myRubberBand.reset(3) # 3= Polygon if self.rubberBand1 is not None: self.iface.mapCanvas().scene().removeItem(self.rubberBand1) if self.rubberBand2 is not None: self.iface.mapCanvas().scene().removeItem(self.rubberBand2) self.mousePoints = [] self.rubberBand1 = None self.rubberBand2 = None def createRubberBand(self, points): myPoints1 = [] for p in points: myPoints1.append(QgsPoint(p.x(), p.y())) myPoints1.remove(myPoints1[-1]) myPoints1.append(myPoints1[0]) if self.rubberBand1 is not None: self.iface.mapCanvas().scene().removeItem(self.rubberBand1) self.rubberBand1 = QgsRubberBand(self.iface.mapCanvas(), False) self.rubberBand1.setToGeometry(QgsGeometry.fromPolyline(myPoints1), None) self.rubberBand1.setColor(QColor(240, 40, 40)) self.rubberBand1.setWidth(1) self.rubberBand1.setLineStyle(Qt.SolidLine) myPoints2 = [] myPoints2.append(QgsPoint(points[-2].x(), points[-2].y())) myPoints2.append(QgsPoint(points[-1].x(), points[-1].y())) myPoints2.append(QgsPoint(points[0].x(), points[0].y())) if self.rubberBand2 is not None: self.iface.mapCanvas().scene().removeItem(self.rubberBand2) self.rubberBand2 = QgsRubberBand(self.iface.mapCanvas(), False) self.rubberBand2.setToGeometry(QgsGeometry.fromPolyline(myPoints2), None) self.rubberBand2.setColor(QColor(240, 40, 40)) self.rubberBand2.setWidth(1) self.rubberBand2.setLineStyle(Qt.DashLine) def showRectangle(self, initialPoint, finalPoint): self.myRubberBand.reset(3) if initialPoint.x() == finalPoint.x() or initialPoint.y( ) == finalPoint.y(): return point1 = QgsPointXY(initialPoint.x(), initialPoint.y()) point2 = QgsPointXY(initialPoint.x(), finalPoint.y()) point3 = QgsPointXY(finalPoint.x(), finalPoint.y()) point4 = QgsPointXY(finalPoint.x(), initialPoint.y()) self.myRubberBand.addPoint(point1, False) self.myRubberBand.addPoint(point2, False) self.myRubberBand.addPoint(point3, False) self.myRubberBand.addPoint(point4, False) self.myRubberBand.closePoints() self.myRubberBand.show() def getRectangle(self): if self.initialPoint is None or self.finalPoint is None: return None elif self.initialPoint.x() == self.finalPoint.x( ) or self.initialPoint.y() == self.finalPoint.y(): return None return QgsRectangle(self.initialPoint, self.finalPoint) """Events""" def canvasPressEvent(self, e): if e.button() == Qt.RightButton and len(self.mousePoints) > 0: poligon = QgsVectorLayer('Polygon', 'poly', "memory") pr = poligon.dataProvider() poly = QgsFeature() if len(self.mousePoints) > 3: self.mousePoints.remove(self.mousePoints[-1]) poly.setGeometry(QgsGeometry.fromPolygonXY([self.mousePoints])) pr.addFeatures([poly]) layers = self.canvas.layers() try: for layer in layers: if layer.type() == QgsMapLayer.RasterLayer: continue modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ShiftModifier: processing.run( 'qgis:selectbylocation', { 'INPUT': layer, 'PREDICATE': [0], 'INTERSECT': poligon, 'METHOD': 3 }) # Remove elif modifiers == Qt.ControlModifier: processing.run( 'qgis:selectbylocation', { 'INPUT': layer, 'PREDICATE': [0], 'INTERSECT': poligon, 'METHOD': 1 }) # Add else: processing.run( 'qgis:selectbylocation', { 'INPUT': layer, 'PREDICATE': [0], 'INTERSECT': poligon, 'METHOD': 0 }) # Set except Exception: self.iface.messageBar().pushMessage( "Warning", "Polygon not valid for selecting elements", level=1, duration=5) self.reset() poligon = None return elif e.button() == Qt.RightButton: self.canvas.unsetMapTool(self) self.deactivate() return # Rectangle if len(self.mousePoints) == 0: self.initialPoint = self.toMapCoordinates(e.pos()) self.finalPoint = self.initialPoint self.isSelecting = True self.showRectangle(self.initialPoint, self.finalPoint) # Poliline point = self.toMapCoordinates(e.pos()) self.mousePoints.append(point) if len(self.mousePoints) == 1: self.mousePoints.append(point) def canvasReleaseEvent(self, e): self.isSelecting = False rect = self.getRectangle() if rect is None: if len(self.mousePoints) > 0: self.createRubberBand(self.mousePoints) else: self.mousePoints = [] layers = self.canvas.layers() for layer in layers: if layer.type() == QgsMapLayer.RasterLayer: continue lRect = self.canvas.mapSettings().mapToLayerCoordinates( layer, rect) modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ShiftModifier: layer.selectByRect(lRect, 3) # Remove elif modifiers == Qt.ControlModifier: layer.selectByRect(lRect, 1) # Add else: layer.selectByRect(lRect, 0) # Set self.myRubberBand.hide() def canvasMoveEvent(self, e): point = self.toMapCoordinates(e.pos()) if self.isSelecting: self.finalPoint = point self.showRectangle(self.initialPoint, self.finalPoint) elif len(self.mousePoints) > 0: self.mousePoints[-1] = point self.createRubberBand(self.mousePoints)
class TrackWidget(QWidget, Ui_Track): def __init__(self, parent=None): super(TrackWidget, self).__init__(parent) self.setupUi(self) self.title = "Display track" self.canvas = None self.band = None self.color_btn = None self.center = False self.position = None self.geom_type = None self.marker = None self.hidden = False self.centerButton.setEnabled(False) icon = QIcon(":/resources/mActionSave.svg") self.save_track_pushButton.setIcon(icon) self.save_track_pushButton.setToolTip("Save Track") self.save_track_pushButton.clicked.connect(self.save_track) def init(self, title, canvas, default_color, geom_type, marker): self.canvas = canvas self.geom_type = geom_type self.track_groupBox.setTitle(title) if marker: # Add marker self.marker = marker self.with_marker = True else: self.with_marker = False # Add rubber band self.band = QgsRubberBand(self.canvas, self.geom_type) if self.geom_type == QgsWkbTypes.PointGeometry: self.band.setIcon(QgsRubberBand.ICON_CIRCLE) self.band.setIconSize(12) else: self.band.setWidth(3) # Add color button widget for picking color self.color_btn = QgsColorButton() self.horizontal_layout_color.insertWidget(1, self.color_btn, 0, Qt.AlignLeft) self.color_btn.colorChanged.connect(self.color_changed) self.color_btn.setDefaultColor(default_color) self.color_btn.setColor(default_color) # Set signals self.centerButton.clicked.connect(self.center_to_location) self.clearTrackButton.clicked.connect(self.clear_track) def color_changed(self): transparent_color = QColor(self.color_btn.color()) transparent_color.setAlpha(80) self.band.setColor(transparent_color) if self.with_marker: self.marker.set_color(QColor(self.color_btn.color())) def add_position(self, position): self.band.addPoint(position) def track_update_canvas(self, position, heading): self.centerButton.setEnabled(True) self.position = position self.add_position(position) if self.with_marker: self.marker.set_center(position, heading) if self.isHidden(): if self.with_marker: self.marker.hide() self.band.hide() else: if self.with_marker: self.marker.show() self.band.show() def center_to_location(self): """ Center to last received position on the map. """ rect = QgsRectangle(self.position, self.position) self.canvas.setExtent(rect) self.canvas.zoomScale(400) self.canvas.refresh() def hide_band(self): self.band.hide() def hide_marker(self): if self.with_marker: self.marker.hide() def clear_track(self): self.band.reset(self.geom_type) def save_track(self): """ Save the track to disk """ layer_name, selected_filter = QFileDialog.getSaveFileName(None, 'Save Track', "", 'Shapefile (*.shp);;KML (*.kml);;GPX (*.gpx)') if layer_name != '': if self.geom_type == QgsWkbTypes.PointGeometry: geometric_object = "MultiPoint?crs=epsg:4326" else: geometric_object = "LineString?crs=epsg:4326" layer = QgsVectorLayer( geometric_object, layer_name, "memory") feature = QgsFeature() feature.setGeometry(self.band.asGeometry()) layer.dataProvider().addFeatures([feature]) if selected_filter == "Shapefile (*.shp)": if not layer_name.endswith('.shp'): layer_name = layer_name + '.shp' ret = QgsVectorFileWriter.writeAsVectorFormat(layer, layer_name, "utf-8", QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.EpsgCrsId), "ESRI Shapefile") if ret == QgsVectorFileWriter.NoError: logger.info(layer.name() + " saved to " + layer_name) elif selected_filter == "KML (*.kml)": if not layer_name.endswith('.kml'): layer_name = layer_name + '.kml' QgsVectorFileWriter.writeAsVectorFormat(layer, layer_name, "utf-8", QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.EpsgCrsId), "KML") elif selected_filter == "GPX (*.gpx)": if not layer_name.endswith('.gpx'): layer_name = layer_name + '.gpx' ds_options = list() ds_options.append("GPX_USE_EXTENSIONS=TRUE") QgsVectorFileWriter.writeAsVectorFormat(layer, layer_name, "utf-8", QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.EpsgCrsId), "GPX", datasourceOptions=ds_options) def close(self): self.hide_band() self.hide_marker()
class ShowExtent(QgsMapTool): """Show an extent in the canvas""" ShowEnded = pyqtSignal() def __init__(self, canvas): """Constructor""" QgsMapTool.__init__(self, canvas) self.canvas = canvas self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) color = QColor(30, 230, 30, 65) self.rubberBand.setColor(color) self.rubberBand.setWidth(1) self.start_point = self.end_point = None def canvasPressEvent(self, event): """Change the outcome of the click event to end the ongoing process.""" _ = event self.rubberBand.hide() self.ShowEnded.emit() def show_extent(self, extent: QgsRectangle): """Display the extent on the canvas""" self.start_point = QgsPointXY(extent.xMinimum(), extent.yMinimum()) self.end_point = QgsPointXY(extent.xMaximum(), extent.yMaximum()) self.transform_coordinates() self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) point1 = QgsPointXY(self.start_point.x(), self.start_point.y()) point2 = QgsPointXY(self.start_point.x(), self.end_point.y()) point3 = QgsPointXY(self.end_point.x(), self.end_point.y()) point4 = QgsPointXY(self.end_point.x(), self.start_point.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) self.rubberBand.show() rect = QgsRectangle(self.start_point, self.end_point) self.canvas.setExtent(rect) def transform_coordinates(self): """Transform the coordinates in 4326.""" if self.start_point is None or self.end_point is None: return None if self.start_point.x() == self.end_point.x() or self.start_point.y( ) == self.end_point.y(): return None # Defining the crs from src and destiny epsg = self.canvas.mapSettings().destinationCrs().authid() crs_dest = QgsCoordinateReferenceSystem(epsg) crs_src = QgsCoordinateReferenceSystem('EPSG:4326') # Creating a transformer transformer = QgsCoordinateTransform(crs_src, crs_dest, QgsProject.instance()) # Transforming the points self.start_point = transformer.transform(self.start_point) self.end_point = transformer.transform(self.end_point)
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.firstshow = True self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) if hasattr(self.canvas, 'setParallelRenderingEnabled'): self.canvas.setParallelRenderingEnabled(True) pal = QgsPalLabeling() self.canvas.mapRenderer().setLabelingEngine(pal) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(":/icons/gps", self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) gpsspacewidget = QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget( self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction( self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.marker = GPSMarker(self.canvas) self.marker.hide() self.currentfeatureband = QgsRubberBand(self.canvas) self.currentfeatureband.setIconSize(20) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(186, 93, 212, 76)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(0, 0, 212, 76)) self.gpsband.setWidth(5) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.featureformloaded.connect(self.feature_form_loaded) self.connectButtons() def init_qgisproject(self, doc): parser = ProjectParser(doc) canvasnode = parser.canvasnode self.canvas.freeze() self.canvas.mapRenderer().readXML(canvasnode) self.canvaslayers = parser.canvaslayers() self.canvas.setLayerSet(self.canvaslayers) #red = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorRedPart", 255 )[0]; #green = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorGreenPart", 255 )[0]; #blue = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorBluePart", 255 )[0]; #color = QColor(red, green, blue); #self.canvas.setCanvasColor(color) self.canvas.updateScale() return self.canvas.mapRenderer().destinationCrs() def showEvent(self, *args, **kwargs): if self.firstshow: self.canvas.refresh() self.canvas.repaint() self.firstshow = False def feature_form_loaded(self, form, feature, project, editmode): self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): self.clear_selection() for layer, features in results.iteritems(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0, 200)) band.setIconSize(20) band.setWidth(2) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) for feature in features: band.addGeometry(feature.geometry(), layer) def highlight_active_selection(self, layer, feature, features): self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) def clear_selection(self): # Clear the main selection rubber band self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.itervalues(): band.reset() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() break self.editfeaturestack.append((form, feature)) self.load_form(form) trigger_default_action() def clear_temp_objects(self): def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging def set_gps(self, gps, logging): self.gps = gps self.gpslogging = logging self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) def gps_update_canvas(self, position, gpsinfo): # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.marker.show() self.marker.setCenter(position) def gps_first_fix(self, postion, gpsinfo): zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def gps_disconnected(self): self.marker.hide() def select_data_entry(self): def showformerror(form): pass def actions(): for form in self.project.forms: action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format( form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form") formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): self.project = project self.actionPan.trigger() try: firstform = project.forms[0] self.load_form(firstform) self.dataentryselection.setVisible(True) except IndexError: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = QgsMapLayerRegistry.instance().mapLayers().values() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.freeze(False) self.canvas.refresh() def setMapTool(self, tool, *args): self.canvas.setMapTool(tool) def connectButtons(self): def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24, 24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = PanTool(self.canvas) self.infoTool = InfoTool(self.canvas) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/info')) self.actionRaster.triggered.connect(self.toggleRasterLayers) self.infoTool.infoResults.connect(RoamEvents.selectionchanged.emit) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def load_form(self, form): self.clearCapatureTools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) def create_capture_buttons(self, form): tool = form.getMaptool()(self.canvas) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(partial(self.showUIMessage, form.label)) def add_new_feature(self, form, geometry): """ Add a new new feature to the given layer """ # TODO Extract into function. # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if layer.geometryType() in [ QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon ]: geometry.convertToMultiType() try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass layer = form.QGISLayer fields = layer.pendingFields() feature = QgsFeature(fields) feature.setGeometry(geometry) for index in xrange(fields.count()): pkindexes = layer.dataProvider().pkAttributeIndexes() if index in pkindexes and layer.dataProvider().name( ) == 'spatialite': continue value = layer.dataProvider().defaultValue(index) feature[index] = value RoamEvents.open_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) def clearCapatureTools(self): captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ if not self.canvaslayers: return #Freeze the canvas to save on UI refresh self.canvas.freeze() for layer in self.canvaslayers: if layer.layer().type() == QgsMapLayer.RasterLayer: layer.setVisible(not layer.isVisible()) # Really!? We have to reload the whole layer set every time? # WAT? self.canvas.setLayerSet(self.canvaslayers) self.canvas.freeze(False) self.canvas.refresh() def cleanup(self): self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clearCapatureTools() self.canvas.freeze() self.canvas.clear() self.canvas.freeze(False) for action in self.layerbuttons: self.editgroup.removeAction(action)
class MoveFeatureTool(QgsMapTool): def __init__(self, mission_track, canvas): QgsMapTool.__init__(self, canvas) self.band = None self.feature = None self.startcoord = None self.mission_track = mission_track self.layer = mission_track.get_mission_layer() self.clicked_outside_layer = False self.mCtrl = False self.rot_center = None self.rot_center_rb = None self.ini_rot_point = None self.last_rot_angle = 0.0 self.curr_angle = 0.0 self.distance = QgsDistanceArea() self.distance.setSourceCrs(QgsCoordinateReferenceSystem(4326), QgsProject.instance().transformContext()) self.distance.setEllipsoid('WGS84') self.ini_geom = next(self.layer.dataProvider().getFeatures()).geometry() logger.info(mission_track.get_mission_name()) def canvasMoveEvent(self, event): """ Override of QgsMapTool mouse move event """ if self.band and not self.mCtrl: point = self.toMapCoordinates(event.pos()) offset_x = point.x() - self.startcoord.x() offset_y = point.y() - self.startcoord.y() self.band.setTranslationOffset(offset_x, offset_y) self.band.updatePosition() self.band.update() if self.band and self.mCtrl: end_rot_point = self.toMapCoordinates(event.pos()) self.curr_angle = self.distance.bearing(self.rot_center, end_rot_point) \ - self.distance.bearing(self.rot_center, self.ini_rot_point)\ + self.last_rot_angle self.rotate_and_project_band(self.curr_angle) def canvasPressEvent(self, event): """ Override of QgsMapTool mouse press event """ if event.button() == Qt.LeftButton and not self.mCtrl: if self.band: self.band.hide() self.band = None self.feature = None logger.info("layer feature count {}".format(self.layer.featureCount())) if not self.layer: return logger.info("Trying to find feature in layer") point = self.toLayerCoordinates(self.layer, event.pos()) search_radius = (QgsTolerance.toleranceInMapUnits(10, self.layer, self.canvas().mapSettings(), QgsTolerance.Pixels)) rect = QgsRectangle() rect.setXMinimum(point.x() - search_radius) rect.setXMaximum(point.x() + search_radius) rect.setYMinimum(point.y() - search_radius) rect.setYMaximum(point.y() + search_radius) rq = QgsFeatureRequest().setFilterRect(rect) f = QgsFeature() self.layer.getFeatures(rq).nextFeature(f) if f.geometry(): self.band = self.create_rubber_band() self.band.setToGeometry(f.geometry(), self.layer) self.band.show() self.startcoord = self.toMapCoordinates(event.pos()) self.feature = f self.clicked_outside_layer = False return else: self.clicked_outside_layer = True def canvasReleaseEvent(self, event): """ Override of QgsMapTool mouse release event """ if event.button() == Qt.LeftButton and not self.mCtrl: if not self.band: if self.clicked_outside_layer and len(self.mission_track.find_waypoints_in_mission()) > 0: confirmation_msg = "Do you want to move the mission ? \n\n" \ "First waypoint will set on the marked point." reply = QMessageBox.question(self.parent(), 'Movement Confirmation', confirmation_msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: feats = self.layer.getFeatures() for f in feats: # will be only one if self.layer.geometryType() == QgsWkbTypes.LineGeometry: list_wp = f.geometry().asPolyline() self.startcoord = self.toLayerCoordinates(self.layer, list_wp[0]) elif self.layer.geometryType() == QgsWkbTypes.PointGeometry: wp = f.geometry().asPoint() self.startcoord = self.toLayerCoordinates(self.layer, wp) self.feature = f self.move_position(event.pos()) return if not self.layer: return if not self.feature: return self.move_position(event.pos()) def keyPressEvent(self, event): if event.key() == Qt.Key_Control and self.band is None \ and len(self.mission_track.find_waypoints_in_mission()) > 1: self.mCtrl = True self.show_rotation_center() self.show_rubber_band() self.rotate_and_project_band(self.last_rot_angle) self.ini_rot_point = self.toMapCoordinates(self.canvas().mouseLastXY()) def keyReleaseEvent(self, event): if event.key() == Qt.Key_Control and self.mCtrl: self.mCtrl = False self.rotate_and_project_mission() self.hide_rotation_center() self.hide_rubber_band() self.last_rot_angle = self.curr_angle def move_position(self, pos): start_point = self.toLayerCoordinates(self.layer, self.startcoord) end_point = self.toLayerCoordinates(self.layer, pos) # Find vertical distance to be translated a = self.distance.bearing(start_point, end_point) d = self.distance.measureLine(start_point, end_point) vertical_dist = abs(cos(a) * d) # If translating a point or if translation is small, # do a simple translation (very small error, proportional to vertical dist) if vertical_dist < 9000 or self.layer.geometryType() == QgsWkbTypes.PointGeometry: dx = end_point.x() - start_point.x() dy = end_point.y() - start_point.y() self.layer.startEditing() self.layer.translateFeature(self.feature.id(), dx, dy) self.layer.commitChanges() # If translation is big, translate and project (small and constant error due to approximations in calculations) else: ini_coords = next(self.layer.dataProvider().getFeatures()).geometry().asPolyline() end_coords = [] if len(ini_coords) > 1: dx = end_point.x() - start_point.x() dy = end_point.y() - start_point.y() end_c = QgsPointXY(ini_coords[0].x() + dx, ini_coords[0].y() + dy) end_coords.append(end_c) for i in range(1, len(ini_coords)): dist = self.distance.measureLine(ini_coords[i-1], ini_coords[i]) angle = self.distance.bearing(ini_coords[i-1], ini_coords[i]) end_c = endpoint(end_coords[i-1], dist, degrees(angle)) end_coords.append(end_c) feature = next(self.layer.dataProvider().getFeatures()) self.layer.startEditing() self.layer.changeGeometry(feature.id(), QgsGeometry.fromPolylineXY(end_coords)) self.layer.commitChanges() if self.band is not None: self.band.hide() self.band = None # get new waypoints and put them into mission structure feats = self.layer.getFeatures() for f in feats: # will be only one if self.layer.geometryType() == QgsWkbTypes.LineGeometry: list_wp = f.geometry().asPolyline() for wp in range(0, len(list_wp)): point = list_wp[wp] self.mission_track.change_position(wp, point) elif self.layer.geometryType() == QgsWkbTypes.PointGeometry: wp = f.geometry().asPoint() self.mission_track.change_position(0, wp) # rotation center and geometry have changed, set rot_center to none to recalculate when needed self.rot_center = None self.last_rot_angle = 0.0 self.ini_geom = next(self.layer.dataProvider().getFeatures()).geometry() def deactivate(self): """ Deactive the tool. """ self.hide_rotation_center() self.hide_rubber_band() def create_rubber_band(self): """ Creates a new rubber band. """ band = QgsRubberBand(self.canvas()) band.setColor(QColor("green")) band.setWidth(2) band.setLineStyle(Qt.DashLine) return band def show_rotation_center(self): """ Shows rotation center of the mission with a point rubber band """ if len(self.mission_track.find_waypoints_in_mission()) > 1: feature = next(self.layer.dataProvider().getFeatures()) list_wp = feature.geometry().asPolyline() if self.rot_center is None or self.rot_center_rb is None: self.rot_center = self.find_geometric_center(list_wp) self.rot_center_rb = QgsRubberBand(self.canvas()) self.rot_center_rb.setColor(QColor("black")) self.rot_center_rb.setWidth(3) self.rot_center_rb.setToGeometry(QgsGeometry.fromPointXY(self.rot_center), None) self.rot_center_rb.update() else: self.rot_center_rb.show() def hide_rotation_center(self): """ Hides the rotation center of the mission and deletes the point rubber band """ if self.rot_center_rb: self.rot_center_rb.hide() # self.rot_center_rb = None def find_geometric_center(self, list_wp): """ Finds geometric center from a list of waypoints :param list_wp: list of waypoints :return: geometric center of the list of waypoints """ center = QgsPointXY() max_x = None min_x = None max_y = None min_y = None # Geometric center for i in range(0, len(list_wp)): point = list_wp[i] if max_x is None or point.x() > max_x: max_x = point.x() if min_x is None or point.x() < min_x: min_x = point.x() if max_y is None or point.y() > max_y: max_y = point.y() if min_y is None or point.y() < min_y: min_y = point.y() center.setX((max_x + min_x)/2) center.setY((max_y + min_y)/2) return center def show_rubber_band(self): """ Creates and shows a rubber band with the geometry of the mission """ if len(self.mission_track.find_waypoints_in_mission()) > 1: self.band = self.create_rubber_band() self.band.setToGeometry(self.ini_geom, self.layer) def hide_rubber_band(self): """ Hides and deletes the rubber band of the geometry of the mission """ if self.band: self.band.hide() self.band = None def rotate_and_project_band(self, rot_angle=0.0): """ Rebuilds the initial geometry of the mission rotated with the angle defined by rot_angle and it gets stored in the geometry of self.band :param rot_angle: Angle used to rotate the geometry """ ini_coords = self.ini_geom.asPolyline() end_coords = [] if len(ini_coords) > 1: dist = self.distance.measureLine(self.rot_center, ini_coords[0]) angle = self.distance.bearing(self.rot_center, ini_coords[0]) + rot_angle end_first_wp = endpoint(self.rot_center, dist, degrees(angle)) end_coords.append(end_first_wp) for i in range(1, len(ini_coords)): dist = self.distance.measureLine(ini_coords[i-1], ini_coords[i]) angle = self.distance.bearing(ini_coords[i-1], ini_coords[i]) + rot_angle end_c = endpoint(end_coords[i-1], dist, degrees(angle)) end_coords.append(end_c) end_band_geom = QgsGeometry().fromPolylineXY(end_coords) self.band.setToGeometry(end_band_geom, self.layer) def rotate_and_project_mission(self): """ Copy the changes from the rotated and projected rubber band to the geometry of the mission """ list_wp = self.band.asGeometry().asPolyline() if len(list_wp) > 1: for i in range(0, len(list_wp)): point = list_wp[i] self.mission_track.change_position(i, point) feature = next(self.layer.dataProvider().getFeatures()) self.layer.startEditing() self.layer.changeGeometry(feature.id(), self.band.asGeometry()) self.layer.commitChanges()
class DiscoveryPlugin: def __init__(self, _iface): # Save reference to the QGIS interface self.iface = _iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # Variables to facilitate delayed queries and database connection management self.db_timer = QTimer() self.line_edit_timer = QTimer() self.line_edit_timer.setSingleShot(True) self.line_edit_timer.timeout.connect(self.reset_line_edit_after_move) self.next_query_time = None self.last_query_time = time.time() self.db_conn = None self.search_delay = 0.5 # s self.query_sql = '' self.query_text = '' self.query_dict = {} self.db_idle_time = 60.0 # s self.display_time = 5000 # ms self.bar_info_time = 30 # s self.search_results = [] self.tool_bar = None self.search_line_edit = None self.completer = None self.conn_info = {} self.marker = QgsVertexMarker(iface.mapCanvas()) self.marker.setIconSize(15) self.marker.setPenWidth(2) self.marker.setColor(QColor(226, 27, 28)) #51,160,44)) self.marker.setZValue(11) self.marker.setVisible(False) self.marker2 = QgsVertexMarker(iface.mapCanvas()) self.marker2.setIconSize(16) self.marker2.setPenWidth(4) self.marker2.setColor(QColor(255, 255, 255, 200)) self.marker2.setZValue(10) self.marker2.setVisible(False) self.is_displayed = False self.rubber_band = QgsRubberBand(iface.mapCanvas(), False) self.rubber_band.setVisible(False) self.rubber_band.setWidth(3) self.rubber_band.setStrokeColor(QColor(226, 27, 28)) self.rubber_band.setFillColor(QColor(226, 27, 28, 63)) def initGui(self): # Create a new toolbar self.tool_bar = self.iface.addToolBar('Discovery') self.tool_bar.setObjectName('Discovery_Plugin') # Create action that will start plugin configuration self.action_config = QAction( QIcon(os.path.join(self.plugin_dir, "discovery_logo.png")), u"Configure Discovery", self.tool_bar) self.action_config.triggered.connect(self.show_config_dialog) self.tool_bar.addAction(self.action_config) # Add combobox for configs self.config_combo = QComboBox() settings = QgsSettings() settings.beginGroup("/Discovery") config_list = settings.value("config_list") if config_list: for conf in config_list: self.config_combo.addItem(conf) elif settings.childGroups(): # support for prev version key = "Config1" config_list = [] config_list.append(key) settings.setValue("config_list", config_list) self.config_combo.addItem(key) settings.setValue(key + "data_type", settings.value("data_type")) settings.setValue(key + "file", settings.value("file")) settings.setValue(key + "connection", settings.value("connection")) settings.setValue(key + "schema", settings.value("schema")) settings.setValue(key + "table", settings.value("table")) settings.setValue(key + "search_column", settings.value("search_column")) settings.setValue(key + "echo_search_column", settings.value("echo_search_column")) settings.setValue(key + "display_columns", settings.value("display_columns")) settings.setValue(key + "geom_column", settings.value("geom_column")) settings.setValue(key + "scale_expr", settings.value("scale_expr")) settings.setValue(key + "bbox_expr", settings.value("bbox_expr")) delete_config_from_settings("", settings) self.tool_bar.addWidget(self.config_combo) # Add search edit box self.search_line_edit = QgsFilterLineEdit() self.search_line_edit.setPlaceholderText('Search for...') self.search_line_edit.setMaximumWidth(768) self.tool_bar.addWidget(self.search_line_edit) self.config_combo.currentIndexChanged.connect( self.change_configuration) # Set up the completer self.completer = QCompleter([]) # Initialise with en empty list self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setMaxVisibleItems(1000) self.completer.setModelSorting( QCompleter.UnsortedModel) # Sorting done in PostGIS self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion ) # Show all fetched possibilities self.completer.activated[QModelIndex].connect(self.on_result_selected) self.completer.highlighted[QModelIndex].connect( self.on_result_highlighted) self.search_line_edit.setCompleter(self.completer) # Connect any signals self.search_line_edit.textEdited.connect(self.on_search_text_changed) # Search results self.search_results = [] # Set up a timer to periodically perform db queries as required self.db_timer.timeout.connect(self.do_db_operations) self.db_timer.start(100) # Read config self.read_config(config_list[0] if config_list else "") self.locator_filter = locator_filter.DiscoveryLocatorFilter(self) self.iface.registerLocatorFilter(self.locator_filter) # Debug # import pydevd; pydevd.settrace('localhost', port=5678) def unload(self): # Stop timer self.db_timer.stop() # Disconnect any signals self.db_timer.timeout.disconnect(self.do_db_operations) self.completer.highlighted[QModelIndex].disconnect( self.on_result_highlighted) self.completer.activated[QModelIndex].disconnect( self.on_result_selected) self.search_line_edit.textEdited.disconnect( self.on_search_text_changed) # Remove the new toolbar self.tool_bar.clear() # Clear all actions self.iface.mainWindow().removeToolBar(self.tool_bar) self.iface.deregisterLocatorFilter(self.locator_filter) self.locator_filter = None def clear_suggestions(self): model = self.completer.model() model.setStringList([]) def on_search_text_changed(self, new_search_text): """ This function is called whenever the user modified the search text 1. Open a database connection 2. Make the query 3. Update the QStringListModel with these results 4. Store the other details in self.search_results """ self.query_text = new_search_text if len(new_search_text) < 3: # Clear any previous suggestions in case the user is 'backspacing' self.clear_suggestions() return if self.data_type == "postgres": query_text, query_dict = dbutils.get_search_sql( new_search_text, self.postgisgeomcolumn, self.postgissearchcolumn, self.echosearchcolumn, self.postgisdisplaycolumn, self.extra_expr_columns, self.postgisschema, self.postgistable) self.schedule_search(query_text, query_dict) elif self.data_type == "gpkg": query_text = (new_search_text, self.postgissearchcolumn, self.echosearchcolumn, self.postgisdisplaycolumn.split(","), self.extra_expr_columns, self.layer) self.schedule_search(query_text, None) elif self.data_type == "mssql": query_text = mssql_utils.get_search_sql( new_search_text, self.postgisgeomcolumn, self.postgissearchcolumn, self.echosearchcolumn, self.postgisdisplaycolumn, self.extra_expr_columns, self.postgisschema, self.postgistable) self.schedule_search(query_text, None) def do_db_operations(self): if self.next_query_time is not None and self.next_query_time < time.time( ): # It's time to run a query self.next_query_time = None # Prevent this query from being repeated self.last_query_time = time.time() self.perform_search() else: # We're not performing a query, close the db connection if it's been open for > 60s if time.time() > self.last_query_time + self.db_idle_time: self.db_conn = None def perform_search(self): db = self.get_db() self.search_results = [] suggestions = [] if self.data_type == "postgres": cur = db.cursor() try: cur.execute(self.query_sql, self.query_dict) except psycopg2.Error as e: err_info = "Failed to execute the search query. Please, check your settings. Error message:\n\n" err_info += f"{e.pgerror}" QMessageBox.critical(None, "Discovery", err_info) return result_set = cur.fetchall() elif self.data_type == "mssql": result_set = mssql_utils.execute(db, self.query_sql) elif self.data_type == "gpkg": result_set = gpkg_utils.search_gpkg(*self.query_sql) for row in result_set: geom, epsg, suggestion_text = row[0], row[1], row[2] extra_data = {} for idx, extra_col in enumerate(self.extra_expr_columns): extra_data[extra_col] = row[3 + idx] self.search_results.append( (geom, epsg, suggestion_text, extra_data)) suggestions.append(suggestion_text) model = self.completer.model() model.setStringList(suggestions) self.completer.complete() def schedule_search(self, query_text, query_dict): # Update the search text and the time after which the query should be executed self.query_sql = query_text self.query_dict = query_dict self.next_query_time = time.time() + self.search_delay def show_bar_info(self, info_text): """Optional show info bar message with selected result information""" self.iface.messageBar().clearWidgets() if self.bar_info_time: self.iface.messageBar().pushMessage("Discovery", info_text, level=Qgis.Info, duration=self.bar_info_time) def on_result_selected(self, result_index): # What to do when the user makes a selection self.select_result(self.search_results[result_index.row()]) def select_result(self, result_data): geometry_text, src_epsg, suggestion_text, extra_data = result_data location_geom = QgsGeometry.fromWkt(geometry_text) canvas = self.iface.mapCanvas() dst_srid = canvas.mapSettings().destinationCrs().authid() transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem(src_epsg), QgsCoordinateReferenceSystem(dst_srid), canvas.mapSettings().transformContext()) # Ensure the geometry from the DB is reprojected to the same SRID as the map canvas location_geom.transform(transform) location_centroid = location_geom.centroid().asPoint() # show temporary marker if location_geom.type() == QgsWkbTypes.PointGeometry: self.show_marker(location_centroid) elif location_geom.type() == QgsWkbTypes.LineGeometry or \ location_geom.type() == QgsWkbTypes.PolygonGeometry: self.show_line_rubber_band(location_geom) else: #unsupported geometry type pass # Adjust map canvas extent zoom_method = 'Move and Zoom' if zoom_method == 'Move and Zoom': # with higher priority try to use exact bounding box to zoom to features (if provided) bbox_str = eval_expression(self.bbox_expr, extra_data) rect = bbox_str_to_rectangle(bbox_str) if rect is not None: # transform the rectangle in case of OTF projection rect = transform.transformBoundingBox(rect) else: # bbox is not available - so let's just use defined scale # compute target scale. If the result is 2000 this means the target scale is 1:2000 rect = location_geom.boundingBox() if rect.isEmpty(): scale_denom = eval_expression(self.scale_expr, extra_data, default=2000.) rect = canvas.mapSettings().extent() rect.scale(scale_denom / canvas.scale(), location_centroid) else: # enlarge geom bbox to have some margin rect.scale(1.2) canvas.setExtent(rect) elif zoom_method == 'Move': current_extent = QgsGeometry.fromRect( self.iface.mapCanvas().extent()) dx = location_centroid.x() - location_centroid.x() dy = location_centroid.y() - location_centroid.y() current_extent.translate(dx, dy) canvas.setExtent(current_extent.boundingBox()) canvas.refresh() self.line_edit_timer.start(0) if self.info_to_clipboard: QApplication.clipboard().setText(suggestion_text) suggestion_text += ' (copied to clipboard)' self.show_bar_info(suggestion_text) def on_result_highlighted(self, result_idx): self.line_edit_timer.start(0) def reset_line_edit_after_move(self): self.search_line_edit.setText(self.query_text) def get_db(self): # Create a new new connection if required if self.db_conn is None: if self.data_type == "postgres": self.db_conn = dbutils.get_connection(self.conn_info) elif self.data_type == "mssql": self.db_conn = mssql_utils.get_mssql_conn(self.conn_info) return self.db_conn def change_configuration(self): self.search_line_edit.setText("") self.line_edit_timer.start(0) self.read_config(self.config_combo.currentText()) def read_config(self, key=""): # the following code reads the configuration file which setups the plugin to search in the correct database, # table and method settings = QgsSettings() settings.beginGroup("/Discovery") connection = settings.value(key + "connection", "", type=str) self.data_type = settings.value(key + "data_type", "", type=str) self.file = settings.value(key + "file", "", type=str) self.postgisschema = settings.value(key + "schema", "", type=str) self.postgistable = settings.value(key + "table", "", type=str) self.postgissearchcolumn = settings.value(key + "search_column", "", type=str) self.echosearchcolumn = settings.value(key + "echo_search_column", True, type=bool) self.postgisdisplaycolumn = settings.value(key + "display_columns", "", type=str) self.postgisgeomcolumn = settings.value(key + "geom_column", "", type=str) if settings.value("marker_time_enabled", True, type=bool): self.display_time = settings.value("marker_time", 5000, type=int) else: self.display_time = -1 if settings.value("bar_info_time_enabled", True, type=bool): self.bar_info_time = settings.value("bar_info_time", 30, type=int) else: self.bar_info_time = 0 self.info_to_clipboard = settings.value("info_to_clipboard", True, type=bool) scale_expr = settings.value(key + "scale_expr", "", type=str) bbox_expr = settings.value(key + "bbox_expr", "", type=str) if self.is_displayed: self.hide_marker() self.hide_rubber_band() self.is_displayed = False self.make_enabled(False) # assume the config is invalid first self.db_conn = None if self.data_type == "postgres": self.conn_info = dbutils.get_postgres_conn_info(connection) self.layer = None if len(connection) == 0 or len(self.postgisschema) == 0 or len(self.postgistable) == 0 or \ len(self.postgissearchcolumn) == 0 or len(self.postgisgeomcolumn) == 0: return if len(self.conn_info) == 0: iface.messageBar().pushMessage( "Discovery", "The database connection '%s' does not exist!" % connection, level=Qgis.Critical) return if self.data_type == "mssql": self.conn_info = mssql_utils.get_mssql_conn_info(connection) self.layer = None if len(connection) == 0 or len(self.postgisschema) == 0 or len(self.postgistable) == 0 or \ len(self.postgissearchcolumn) == 0 or len(self.postgisgeomcolumn) == 0: return if len(self.conn_info) == 0: iface.messageBar().pushMessage( "Discovery", "The database connection '%s' does not exist!" % connection, level=Qgis.Critical) return elif self.data_type == "gpkg": self.layer = QgsVectorLayer( self.file + '|layername=' + self.postgistable, self.postgistable, 'ogr') self.conn_info = None self.extra_expr_columns = [] self.scale_expr = None self.bbox_expr = None self.make_enabled(True) # optional scale expression when zooming in to results if len(scale_expr) != 0: expr = QgsExpression(scale_expr) if expr.hasParserError(): iface.messageBar().pushMessage("Discovery", "Invalid scale expression: " + expr.parserErrorString(), level=Qgis.Warning) else: self.scale_expr = scale_expr self.extra_expr_columns += expr.referencedColumns() # optional bbox expression when zooming in to results if len(bbox_expr) != 0: expr = QgsExpression(bbox_expr) if expr.hasParserError(): iface.messageBar().pushMessage("Discovery", "Invalid bbox expression: " + expr.parserErrorString(), level=Qgis.Warning) else: self.bbox_expr = bbox_expr self.extra_expr_columns += expr.referencedColumns() def show_config_dialog(self): dlg = config_dialog.ConfigDialog() if (self.config_combo.currentIndex() >= 0): dlg.configOptions.setCurrentIndex(self.config_combo.currentIndex()) if dlg.exec_(): dlg.write_config() self.config_combo.clear() for key in [ dlg.configOptions.itemText(i) for i in range(dlg.configOptions.count()) ]: self.config_combo.addItem(key) self.config_combo.setCurrentIndex(dlg.configOptions.currentIndex()) self.change_configuration() def make_enabled(self, enabled): self.search_line_edit.setEnabled(enabled) self.search_line_edit.setPlaceholderText( "Search for..." if enabled else "Search disabled: check configuration") def show_marker(self, point): for m in [self.marker, self.marker2]: m.setCenter(point) m.setOpacity(1.0) m.setVisible(True) if self.display_time == -1: self.is_displayed = True else: QTimer.singleShot(self.display_time, self.hide_marker) def hide_marker(self): opacity = self.marker.opacity() if opacity > 0.: # produce a fade out effect opacity -= 0.1 self.marker.setOpacity(opacity) self.marker2.setOpacity(opacity) QTimer.singleShot(100, self.hide_marker) else: self.marker.setVisible(False) self.marker2.setVisible(False) def show_line_rubber_band(self, geom): self.rubber_band.reset(geom.type()) self.rubber_band.setToGeometry(geom, None) self.rubber_band.setVisible(True) self.rubber_band.setOpacity(1.0) self.rubber_band.show() if self.display_time == -1: self.is_displayed = True else: QTimer.singleShot(self.display_time, self.hide_rubber_band) pass def hide_rubber_band(self): opacity = self.rubber_band.opacity() if opacity > 0.: # produce a fade out effect opacity -= 0.1 self.rubber_band.setOpacity(opacity) QTimer.singleShot(100, self.hide_rubber_band) else: self.rubber_band.setVisible(False) self.rubber_band.hide()
class RectangleMapTool(QgsMapTool): def __init__(self, canvas, callback): self.canvas = canvas QgsMapTool.__init__(self, self.canvas) self.callback = callback self.rubberBand = QgsRubberBand(self.canvas, True) self.rubberBand.setColor(QColor(227, 26, 28, 255)) self.rubberBand.setWidth(5) self.rubberBand.setLineStyle(Qt.PenStyle(Qt.DashLine)) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(True) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False r = self.rectangle() if r is not None: # print "Rectangle:", r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum() self.rubberBand.hide() self.callback(r) # self.deactivate() return None def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(True) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPoint(startPoint.x(), startPoint.y()) point2 = QgsPoint(startPoint.x(), endPoint.y()) point3 = QgsPoint(endPoint.x(), endPoint.y()) point4 = QgsPoint(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, False) self.rubberBand.addPoint(point1, True) # true to update canvas self.rubberBand.show() def rectangle(self): if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def deactivate(self): super(RectangleMapTool, self).deactivate() self.emit(QtCore.SIGNAL("deactivated()"))
class MultipleSelection(QgsMapTool): canvasClicked = pyqtSignal() def __init__(self, iface, controller, layers, mincut=None, parent_manage=None, table_object=None): """ Class constructor """ self.layers = layers self.iface = iface self.canvas = self.iface.mapCanvas() self.mincut = mincut self.parent_manage = parent_manage self.table_object = table_object # Call superclass constructor and set current action QgsMapTool.__init__(self, self.canvas) self.controller = controller self.rubber_band = QgsRubberBand(self.canvas, QGis.Polygon) self.rubber_band.setColor(QColor(255, 100, 255)) self.rubber_band.setFillColor(QColor(254, 178, 76, 63)) self.rubber_band.setWidth(1) self.reset() self.snapper = QgsMapCanvasSnapper(self.canvas) self.selected_features = [] def reset(self): self.start_point = self.end_point = None self.is_emitting_point = False self.rubber_band.reset(QGis.Polygon) def canvasPressEvent(self, e): if e.button() == Qt.LeftButton: self.start_point = self.toMapCoordinates(e.pos()) self.end_point = self.start_point self.is_emitting_point = True self.show_rect(self.start_point, self.end_point) def canvasReleaseEvent(self, e): self.is_emitting_point = False rectangle = self.get_rectangle() selected_rectangle = None key = QApplication.keyboardModifiers() if e.button() != Qt.LeftButton: self.rubber_band.hide() return # Disconnect signal to enhance process # We will reconnect it when processing last layer of the group if self.mincut: self.mincut.disconnect_signal_selection_changed() if self.parent_manage: self.parent_manage.disconnect_signal_selection_changed() for i in range(len(self.layers)): layer = self.layers[i] if (i == len(self.layers) - 1): if self.mincut: self.mincut.connect_signal_selection_changed( "mincut_connec") if self.parent_manage: self.parent_manage.connect_signal_selection_changed( self.table_object) # Selection by rectangle if rectangle: if selected_rectangle is None: selected_rectangle = self.canvas.mapSettings( ).mapToLayerCoordinates(layer, rectangle) # If Ctrl+Shift pressed: remove features from selection if key == (Qt.ControlModifier | Qt.ShiftModifier): layer.selectByRect(selected_rectangle, layer.RemoveFromSelection) # If Ctrl pressed: add features to selection elif key == Qt.ControlModifier: layer.selectByRect(selected_rectangle, layer.AddToSelection) # If Ctrl not pressed: add features to selection else: layer.selectByRect(selected_rectangle, layer.AddToSelection) # Selection one by one else: x = e.pos().x() y = e.pos().y() eventPoint = QPoint(x, y) (retval, result) = self.snapper.snapToBackgroundLayers( eventPoint) #@UnusedVariable if result: # Check feature for snap_point in result: # Get the point. Leave selection #point = QgsPoint(snap_point.snappedVertex) snapp_feat = next( snap_point.layer.getFeatures( QgsFeatureRequest().setFilterFid( snap_point.snappedAtGeometry))) snap_point.layer.select([snap_point.snappedAtGeometry]) self.rubber_band.hide() def canvasMoveEvent(self, e): if not self.is_emitting_point: return self.end_point = self.toMapCoordinates(e.pos()) self.show_rect(self.start_point, self.end_point) def show_rect(self, start_point, end_point): self.rubber_band.reset(QGis.Polygon) if start_point.x() == end_point.x() or start_point.y() == end_point.y( ): return point1 = QgsPoint(start_point.x(), start_point.y()) point2 = QgsPoint(start_point.x(), end_point.y()) point3 = QgsPoint(end_point.x(), end_point.y()) point4 = QgsPoint(end_point.x(), start_point.y()) self.rubber_band.addPoint(point1, False) self.rubber_band.addPoint(point2, False) self.rubber_band.addPoint(point3, False) self.rubber_band.addPoint(point4, True) self.rubber_band.show() def get_rectangle(self): if self.start_point is None or self.end_point is None: return None elif self.start_point.x() == self.end_point.x() or self.start_point.y( ) == self.end_point.y(): return None return QgsRectangle(self.start_point, self.end_point) def deactivate(self): self.rubber_band.hide() QgsMapTool.deactivate(self) def activate(self): pass
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.snapping = True icon = roam_style.iconsize() self.projecttoolbar.setIconSize(QSize(icon, icon)) self.current_form = None self.last_form = None self.firstshow = True self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.bridge = QgsLayerTreeMapCanvasBridge( QgsProject.instance().layerTreeRoot(), self.canvas) self.bridge.setAutoSetupOnFirstLayer(False) QgsProject.instance().writeProject.connect(self.bridge.writeProject) QgsProject.instance().readProject.connect(self.bridge.readProject) # self.canvas.setInteractive(False) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) self.snappingutils = SnappingUtils(self.canvas, self) self.canvas.setSnappingUtils(self.snappingutils) QgsProject.instance().readProject.connect( self.snappingutils.readConfigFromProject) if hasattr(self.canvas, 'setParallelRenderingEnabled'): threadcount = QThread.idealThreadCount() threadcount = 2 if threadcount > 2 else 1 QgsApplication.setMaxThreads(threadcount) self.canvas.setParallelRenderingEnabled(True) pal = QgsPalLabeling() self.canvas.mapRenderer().setLabelingEngine(pal) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(":/icons/gps", self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) if roam.config.settings.get('north_arrow', False): self.northarrow = NorthArrow(":/icons/north", self.canvas) self.northarrow.setPos(10, 10) self.canvas.scene().addItem(self.northarrow) smallmode = roam.config.settings.get("smallmode", False) self.projecttoolbar.setSmallMode(smallmode) self.scalebar_enabled = roam.config.settings.get('scale_bar', False) if self.scalebar_enabled: self.scalebar = ScaleBarItem(self.canvas) self.canvas.scene().addItem(self.scalebar) self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu) gpsspacewidget = QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget( self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction( self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.marker = GPSMarker(self.canvas) self.marker.hide() self.currentfeatureband = CurrentSelection(self.canvas) self.currentfeatureband.setIconSize(30) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(186, 93, 212, 50)) self.currentfeatureband.setOutlineColour(QColor(186, 93, 212)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(165, 111, 212, 75)) self.gpsband.setWidth(5) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.openfeatureform.connect(self.feature_form_loaded) RoamEvents.sync_complete.connect(self.refresh_map) RoamEvents.snappingChanged.connect(self.snapping_changed) self.snappingbutton = QToolButton() self.snappingbutton.setText("Snapping: On") self.snappingbutton.setAutoRaise(True) self.snappingbutton.pressed.connect(self.toggle_snapping) spacer = QWidget() spacer2 = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.scalewidget = QgsScaleComboBox() self.scalebutton = QToolButton() self.scalebutton.setAutoRaise(True) self.scalebutton.setMaximumHeight(self.statusbar.height()) self.scalebutton.pressed.connect(self.selectscale) self.scalebutton.setText("Scale") self.scalelist = BigList(parent=self.canvas, centeronparent=True, showsave=False) self.scalelist.hide() self.scalelist.setlabel("Map Scale") self.scalelist.setmodel(self.scalewidget.model()) self.scalelist.closewidget.connect(self.scalelist.close) self.scalelist.itemselected.connect(self.update_scale_from_item) self.scalelist.itemselected.connect(self.scalelist.close) self.positionlabel = QLabel('') self.gpslabel = QLabel("GPS: Not active") self.gpslabelposition = QLabel("") self.statusbar.addWidget(self.snappingbutton) self.statusbar.addWidget(spacer2) self.statusbar.addWidget(self.gpslabel) self.statusbar.addWidget(self.gpslabelposition) self.statusbar.addPermanentWidget(self.scalebutton) self.canvas.extentsChanged.connect(self.updatestatuslabel) self.canvas.scaleChanged.connect(self.updatestatuslabel) GPS.gpsposition.connect(self.update_gps_label) GPS.gpsdisconnected.connect(self.gps_disconnected) self.connectButtons() def clear_plugins(self): toolbars = self.findChildren(QToolBar) for toolbar in toolbars: if toolbar.property("plugin_toolbar"): toolbar.unload() self.removeToolBar(toolbar) toolbar.deleteLater() def add_plugins(self, pluginnames): for name in pluginnames: # Get the plugin try: plugin_mod = plugins.loaded_plugins[name] except KeyError: continue if not hasattr(plugin_mod, 'toolbars'): roam.utils.warning( "No toolbars() function found in {}".format(name)) continue toolbars = plugin_mod.toolbars() self.load_plugin_toolbars(toolbars) def load_plugin_toolbars(self, toolbars): for ToolBarClass in toolbars: toolbar = ToolBarClass(plugins.api, self) self.addToolBar(Qt.BottomToolBarArea, toolbar) toolbar.setProperty("plugin_toolbar", True) def snapping_changed(self, snapping): """ Called when the snapping settings have changed. Updates the label in the status bar. :param snapping: """ if snapping: self.snappingbutton.setText("Snapping: On") else: self.snappingbutton.setText("Snapping: Off") def toggle_snapping(self): """ Toggle snapping on or off. """ self.snapping = not self.snapping try: self.canvas.mapTool().toggle_snapping() except AttributeError: pass RoamEvents.snappingChanged.emit(self.snapping) def selectscale(self): """ Show the select scale widget. :return: """ self.scalelist.show() def update_scale_from_item(self, index): """ Update the canvas scale from the selected scale item. :param index: The index of the selected item. """ scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole)) self.canvas.zoomScale(1.0 / scale) def update_gps_label(self, position, gpsinfo): """ Update the GPS label in the status bar with the GPS status. :param position: The current GPS position. :param gpsinfo: The current extra GPS information. """ self.gpslabel.setText( "GPS: PDOP <b>{0:.2f}</b> HDOP <b>{1:.2f}</b> VDOP <b>{2:.2f}</b>" .format(gpsinfo.pdop, gpsinfo.hdop, gpsinfo.vdop)) places = roam.config.settings.get("gpsplaces", 8) self.gpslabelposition.setText( "X <b>{x:.{places}f}</b> Y <b>{y:.{places}f}</b>".format( x=position.x(), y=position.y(), places=places)) def gps_disconnected(self): """ Called when the GPS is disconnected. Updates the label in the status bar with the message. :return: """ self.gpslabel.setText("GPS Not Active") self.gpslabelposition.setText("") def zoom_to_feature(self, feature): box = feature.geometry().boundingBox() xmin, xmax, ymin, ymax = box.xMinimum(), box.xMaximum(), box.yMinimum( ), box.yMaximum() xmin -= 5 xmax += 5 ymin -= 5 ymax += 5 box = QgsRectangle(xmin, ymin, xmax, ymax) self.canvas.setExtent(box) self.canvas.refresh() def updatestatuslabel(self, *args): """ Update the status bar labels when the information has changed. """ extent = self.canvas.extent() self.positionlabel.setText("Map Center: {}".format( extent.center().toString())) scale = 1.0 / self.canvas.scale() scale = self.scalewidget.toString(scale) self.scalebutton.setText(scale) def refresh_map(self): """ Refresh the map """ self.canvas.refresh() def updatescale(self): """ Update the scale of the map with the current scale from the scale widget :return: """ self.canvas.zoomScale(1.0 / self.scalewidget.scale()) def init_qgisproject(self, doc): """ Called when the project file is read for the firs time. :param doc: The XML doc. :return: The current canvas CRS :note: This method is old and needs to be refactored into something else. """ return self.canvas.mapSettings().destinationCrs() def showEvent(self, *args, **kwargs): """ Handle the show event of the of the map widget. We have to do a little hack here to make the QGIS map refresh. """ if QGis.QGIS_VERSION_INT == 20200 and self.firstshow: self.canvas.refresh() self.canvas.repaint() self.firstshow = False def feature_form_loaded(self, form, feature, *args): """ Called when the feature form is loaded. :param form: The Form object. Holds a reference to the forms layer. :param feature: The current capture feature """ self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): """ Highlight the selection on the canvas. This updates all selected objects based on the result set. :param results: A dict-of-list of layer-features. """ self.clear_selection() for layer, features in results.iteritems(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0)) band.setIconSize(25) band.setWidth(5) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) band.setZValue(self.currentfeatureband.zValue() - 1) for feature in features: band.addGeometry(feature.geometry(), layer) self.canvas.update() def highlight_active_selection(self, layer, feature, features): """ Update the current active selected feature. :param layer: The layer of the active feature. :param feature: The active feature. :param features: The other features in the set to show as non active selection. :return: """ self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) self.canvas.update() def clear_selection(self): """ Clear the selection from the canvas. Resets all selection rubbber bands. :return: """ # Clear the main selection rubber band self.canvas.scene().update() self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.itervalues(): band.reset() self.canvas.update() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): """ Push a feature on the edit stack so the feature can have the geometry edited. :note: This is a big hack and I don't like it! :param form: The form for the current feature :param feature: The active feature. """ def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() self.canvas.mapTool().setEditMode(True, feature.geometry()) break self.editfeaturestack.append((form, feature)) self.save_current_form() self.load_form(form) trigger_default_action() def save_current_form(self): self.last_form = self.current_form def restore_last_form(self): self.load_form(self.last_form) def clear_temp_objects(self): """ Clear all temp objects from the canvas. :return: """ def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): """ Called when the settings have been updated in the Roam config. :param settings: A dict of the settings. """ self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging def set_gps(self, gps, logging): """ Set the GPS for the map widget. Connects GPS signals """ self.gps = gps self.gpslogging = logging self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) def gps_update_canvas(self, position, gpsinfo): """ Updates the map canvas based on the GPS position. By default if the GPS is outside the canvas extent the canvas will move to center on the GPS. Can be turned off in settings. :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.marker.show() self.marker.setCenter(position, gpsinfo) def gps_first_fix(self, postion, gpsinfo): """ Called the first time the GPS gets a fix. If set this will zoom to the GPS after the first fix :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): """ Zoom to ta given position on the map.. """ rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def gps_disconnected(self): """ Called when the GPS is disconnected """ self.marker.hide() def select_data_entry(self): """ Open the form selection widget to allow the user to pick the active capture form. """ def showformerror(form): pass def actions(): for form in self.project.forms: if not self.form_valid_for_capture(form): continue action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format( form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form", wrap=5) formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): """ Called when the project is loaded. Main entry point for a loade project. :param project: The Roam project that has been loaded. """ self.project = project self.actionPan.trigger() firstform = self.first_capture_form() if firstform: self.load_form(firstform) self.dataentryselection.setVisible(True) else: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = QgsMapLayerRegistry.instance().mapLayers().values() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.refresh() projectscales, _ = QgsProject.instance().readBoolEntry( "Scales", "/useProjectScales") if projectscales: projectscales, _ = QgsProject.instance().readListEntry( "Scales", "/ScalesList") self.scalewidget.updateScales(projectscales) else: scales = [ "1:50000", "1:25000", "1:10000", "1:5000", "1:2500", "1:1000", "1:500", "1:250", "1:200", "1:100" ] scales = roam.config.settings.get('scales', scales) self.scalewidget.updateScales(scales) if self.scalebar_enabled: self.scalebar.update() self.actionPan.toggle() self.clear_plugins() self.add_plugins(project.enabled_plugins) def setMapTool(self, tool, *args): """ Set the active map tool in the canvas. :param tool: The QgsMapTool to set. """ if tool == self.canvas.mapTool(): return if hasattr(tool, "setSnapping"): tool.setSnapping(self.snapping) self.canvas.setMapTool(tool) def connectButtons(self): """ Connect the default buttons in the interface. Zoom, pan, etc """ def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24, 24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = PanTool(self.canvas) self.infoTool = InfoTool(self.canvas) self.infoTool.setAction(self.actionInfo) self.zoomInTool.setAction(self.actionZoom_In) self.zoomOutTool.setAction(self.actionZoom_Out) self.panTool.setAction(self.actionPan) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/select')) self.actionRaster.triggered.connect(self.toggleRasterLayers) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def form_valid_for_capture(self, form): """ Check if the given form is valid for capture. :param form: The form to check. :return: True if valid form for capture """ return form.has_geometry and self.project.layer_can_capture( form.QGISLayer) def first_capture_form(self): """ Return the first valid form for capture. """ for form in self.project.forms: if self.form_valid_for_capture(form): return form def load_form(self, form): """ Load the given form so it's the active one for capture :param form: The form to load """ self.clearCaptureTools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) self.current_form = form def create_capture_buttons(self, form): """ Create the capture buttons in the toolbar for the given form. :param form: The active form. """ layer = form.QGISLayer tool = form.getMaptool()(self.canvas, form.settings) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(self.show_invalid_geometry_message) def show_invalid_geometry_message(self, message): RoamEvents.raisemessage("Invalid geometry capture", message, level=RoamEvents.CRITICAL) def add_new_feature(self, form, geometry): """ Add a new new feature to the given layer """ # TODO Extract into function. # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if layer.geometryType() in [ QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon ]: geometry.convertToMultiType() try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass feature = form.new_feature(geometry=geometry) RoamEvents.load_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) self.canvas.mapTool().setEditMode(False, None) self.restore_last_form() def clearCaptureTools(self): """ Clear the capture tools from the toolbar. :return: True if the capture button was active at the time of clearing. """ captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ # Freeze the canvas to save on UI refresh self.canvas.freeze() tree = QgsProject.instance().layerTreeRoot() for node in tree.findLayers(): if node.layer().type() == QgsMapLayer.RasterLayer: if node.isVisible() == Qt.Checked: state = Qt.Unchecked else: state = Qt.Checked node.setVisible(state) self.canvas.freeze(False) self.canvas.refresh() def cleanup(self): """ Clean up when the project has changed. :return: """ self.bridge.clear() self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clearCaptureTools() self.canvas.freeze() self.canvas.clear() self.canvas.freeze(False) for action in self.layerbuttons: self.editgroup.removeAction(action)
class RectangleMapTool(QgsMapTool): def __init__(self, canvas, callback): self.canvas = canvas QgsMapTool.__init__(self, self.canvas) self.callback = callback self.rubberBand = QgsRubberBand(self.canvas, True) self.rubberBand.setColor(QColor(227, 26, 28, 255)) self.rubberBand.setWidth(5) self.rubberBand.setLineStyle(Qt.PenStyle(Qt.DashLine)) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(True) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False r = self.rectangle() if r is not None: # print "Rectangle:", r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum() self.rubberBand.hide() self.callback(r) # self.deactivate() return None def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(True) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPoint(startPoint.x(), startPoint.y()) point2 = QgsPoint(startPoint.x(), endPoint.y()) point3 = QgsPoint(endPoint.x(), endPoint.y()) point4 = QgsPoint(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, False) self.rubberBand.addPoint(point1, True) # true to update canvas self.rubberBand.show() def rectangle(self): if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def deactivate(self): super(RectangleMapTool, self).deactivate() # self.emit(QtCore.SIGNAL("deactivated()")) self.deactivated().emit()
class RectangleMapTool(QgsMapToolEmitPoint): def __init__(self, iface, dbm, imageRegistry, apisLayer): self.iface = iface self.canvas = self.iface.mapCanvas() self.dbm = dbm self.imageRegistry = imageRegistry self.apisLayer = apisLayer QgsMapToolEmitPoint.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.rubberBand.setColor(QColor(255, 128, 0, 255)) self.rubberBand.setFillColor(QColor(255, 128, 0, 128)) self.rubberBand.setWidth(1) self.topic = 'image' # 'image', 'site', 'findspot' self.reset() self.imageSelectionListDlg = APISImageSelectionList( self.iface, self.dbm, self.imageRegistry, self.apisLayer, parent=self.iface.mainWindow()) self.siteSelectionListDlg = APISSiteSelectionList( self.iface, self.dbm, self.imageRegistry, self.apisLayer, parent=self.iface.mainWindow()) self.findspotSelectionListDlg = APISFindspotSelectionList( self.iface, self.dbm, self.imageRegistry, self.apisLayer, parent=self.iface.mainWindow()) self.worker = None def setTopic(self, topic): self.topic = topic def deactivate(self): super(RectangleMapTool, self).deactivate() self.deactivated.emit() def activate(self): self.canvas.setCursor( QgsApplication.getThemeCursor(QgsApplication.Cursor.Select)) def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False r = self.rectangle() #QMessageBox.warning(None, "Bild", u"Punkt: {0}".format(epsg)) srcCrs = self.canvas.mapSettings().destinationCrs() destCrs = QgsCoordinateReferenceSystem( 4312, QgsCoordinateReferenceSystem.EpsgCrsId) ct = QgsCoordinateTransform(srcCrs, destCrs, QgsProject.instance()) if r is None: #QMessageBox.warning(None, "Bild", u"Punkt: {0}".format(self.endPoint.wellKnownText())) p = QgsGeometry.fromPointXY(self.endPoint) p.transform(ct) #QMessageBox.warning(None, "Bild", u"Punkt: {0}".format(self.endPoint.x())) #self.openImageSelectionListDialogByLocation(p.asWkt(8)) self.startWorker(p.asWkt(8)) else: #.warning(None, "Bild", u"Rechteck: {0}, {1}, {2}, {3}".format(r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum())) r2 = QgsGeometry.fromRect(r) r2.transform(ct) #QMessageBox.warning(None, "Bild", u"Polygon: {0}".format(r2.asoWkt(8))) #self.openImageSelectionListDialogByLocation(r2.asoWkt(8)) self.startWorker(r2.asWkt(8)) #print "Rectangle:", r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum() def openImageSelectionListDialogByLocation(self, query): # progressMessageBar = self.iface.messageBar().createMessage("Luftbilder werden gesucht") # progress = QProgressBar() # progress.setMinimum(0) # progress.setMaximum(0) # progress.setAlignment(Qt.AlignLeft|Qt.AlignVCenter) # progressMessageBar.layout().addWidget(progress) # self.iface.messageBar().pushWidget(progressMessageBar, self.iface.messageBar().INFO) res = self.imageSelectionListDlg.loadImageListBySqlQuery(query) if res: self.imageSelectionListDlg.show() if self.imageSelectionListDlg.exec_(): pass self.rubberBand.hide() def openSiteSelectionListDialogByLocation(self, query): info = u"gefunden für den ausgewählten Bereich." res = self.siteSelectionListDlg.loadSiteListBySpatialQuery(query, info) if res: self.siteSelectionListDlg.show() if self.siteSelectionListDlg.exec_(): pass self.rubberBand.hide() def openFindspotSelectionListDialogByLocation(self, query): info = u"gefunden für den ausgewählten Bereich." res = self.findspotSelectionListDlg.loadFindspotListBySpatialQuery( query, info) if res: self.findspotSelectionListDlg.show() #if self.findspotSelectionListDlg.exec_(): # pass self.rubberBand.hide() def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def startWorker(self, geometry): # create a new worker instance if self.worker is None: worker = Worker(self.dbm, geometry, self.topic) # configure the QgsMessageBar messageBar = self.iface.messageBar().createMessage( u'Räumliche Suche wird durchgeführt ...', ) progressBar = QProgressBar() progressBar.setMinimum(0) progressBar.setMaximum(0) progressBar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) cancelButton = QPushButton() cancelButton.setText('Cancel') cancelButton.clicked.connect(self.killWorker) messageBar.layout().addWidget(progressBar) self.progressBar = progressBar messageBar.layout().addWidget(cancelButton) self.iface.messageBar().pushWidget(messageBar, Qgis.Info) self.messageBar = messageBar # start the worker in a new thread thread = QThread(self) worker.moveToThread(thread) worker.finished.connect(self.workerFinished) worker.error.connect(self.workerError) #worker.progress.connect(progressBar.setValue) thread.started.connect(worker.run) thread.start() self.thread = thread self.worker = worker def killWorker(self): self.worker.kill() self.progressBar.setMaximum(100) self.progressBar.setValue(100) def workerFinished(self, query, topic): # clean up the worker and thread self.worker.deleteLater() self.thread.quit() self.thread.wait() self.thread.deleteLater() # remove widget from message bar self.iface.messageBar().popWidget(self.messageBar) if query is not None: # report the result #query = result if not self.worker.killed: if topic == 'image': self.openImageSelectionListDialogByLocation(query) elif topic == 'site': self.openSiteSelectionListDialogByLocation(query) elif topic == 'findspot': self.openFindspotSelectionListDialogByLocation(query) else: self.rubberBand.hide() #self.iface.messageBar().pushMessage('Result') else: # notify the user that something went wrong self.iface.messageBar().pushMessage( 'Something went wrong! See the message log for more information.', level=Qgis.Critical, duration=3) self.worker = None def workerError(self, e, exception_string): QgsMessageLog.logMessage( 'APIS Search Worker thread raised an exception:\n'.format( exception_string), tag='APIS', level=Qgis.Critical)
class AssignBandValueTool(QgsMapTool): def __init__(self, iface, rasterLayer): """ Tool Behaviours: (all behaviours start edition, except for rectangle one) 1- Left Click: Creates a new point feature with the value from raster, according to selected attribute. 5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangle are selected and their value is set according to raster value and selected attribute. """ self.iface = iface self.canvas = self.iface.mapCanvas() QgsMapTool.__init__(self, self.canvas) self.toolAction = None self.qgsMapToolEmitPoint = QgsMapToolEmitPoint(self.canvas) self.geometryHandler = GeometryHandler(iface) self.rasterLayer = rasterLayer self.setRubberbandParameters() self.reset() self.auxList = [] self.decimals = self.getDecimals() def getDecimals(self): settings = QSettings() settings.beginGroup('PythonPlugins/DsgTools/Options') decimals = settings.value('decimals') if decimals: return int(decimals) else: return 0 def getSuppressOptions(self): qgisSettings = QSettings() qgisSettings.beginGroup('qgis/digitizing') setting = qgisSettings.value('disable_enter_attribute_values_dialog') qgisSettings.endGroup() return setting def setRubberbandParameters(self): self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.hoverRubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) mFillColor = QColor(254, 178, 76, 63) self.rubberBand.setColor(mFillColor) self.hoverRubberBand.setColor(QColor(255, 0, 0, 90)) self.rubberBand.setWidth(1) def reset(self): """ Resets rubber band. """ self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): """ Method used to build rectangle if shift is held, otherwise, feature select/deselect and identify is done. :param e: (QgsMouseEvent) mouse event. """ if e.button() == QtCore.Qt.LeftButton: self.auxList = [] if QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = True self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasMoveEvent(self, e): """ Used only on rectangle select. """ if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): """ Builds rubberband rect. """ self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): """ Builds rectangle from self.startPoint and self.endPoint """ if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def setAction(self, action): self.toolAction = action self.toolAction.setCheckable(True) def canvasReleaseEvent(self, e): """ After the rectangle is built, here features are selected. """ # tool was planned to work on left click if e.button() == QtCore.Qt.LeftButton: layer = self.iface.mapCanvas().currentLayer() if QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = False r = self.rectangle() if r is None: return bbRect = self.canvas.mapSettings().mapToLayerCoordinates( layer, r) self.rubberBand.hide() #select all stuff layer.selectByIds([]) #portar para o feature handler layer.selectByRect(bbRect, True) #mudar depois para o dsgmothafucka featDict = dict() pointDict = dict() for feat in layer.selectedFeatures(): featDict[feat.id()] = feat pointDict[feat.id()] = feat.geometry() pixelValueDict = self.getPixelValueFromPointDict( pointDict, self.rasterLayer) for idx in pointDict: value = pixelValueDict[idx] if value: self.auxList.append({ 'featId': idx, 'feat': featDict[idx], 'value': value }) else: value, pointGeom = self.getPixelValue(self.rasterLayer) if value: self.auxList.append({'geom': pointGeom, 'value': value}) #create context menu to select attribute if self.auxList: self.createContextMenuOnPosition(e, layer) def createContextMenuOnPosition(self, e, layer): menu = QMenu() callbackDict = dict() fieldList = [ field.name() for field in layer.fields() if field.isNumeric() ] for field in fieldList: action = menu.addAction(field) callback = partial(self.handleFeatures, field, layer) action.triggered.connect(callback) menu.exec_(self.canvas.viewport().mapToGlobal(e.pos())) def handleFeatures(self, selectedField, layer): layer.startEditing() for item in self.auxList: if 'featId' in item: feat = item['feat'] idx = feat.fieldNameIndex(selectedField) feat.setAttribute(idx, item['value']) layer.updateFeature(feat) else: self.geometryHandler.reprojectFeature(item['geom'], layer.crs()) feature = QgsVectorLayerUtils.createFeature( layer, item['geom']) self.addFeature(feature, layer, selectedField, item['value']) self.auxList = [] self.canvas.refresh() def addFeature(self, feature, layer, field, pointValue): fields = layer.fields() provider = layer.dataProvider() for i in range(fields.count()): value = provider.defaultValue( i) if fields[i].name() != field else pointValue if value is not None: feature.setAttribute(i, value) form = QgsAttributeDialog(layer, feature, False) form.setMode(int(QgsAttributeForm.AddFeatureMode)) formSuppress = layer.editFormConfig().suppress() if formSuppress == QgsEditFormConfig.SuppressDefault: if self.getSuppressOptions( ): #this is calculated every time because user can switch options while using tool layer.addFeature(feature) else: if not form.exec_(): feature.setAttributes(form.feature().attributes()) elif formSuppress == QgsEditFormConfig.SuppressOff: if not form.exec_(): feature.setAttributes(form.feature().attributes()) else: layer.addFeature(feature) def getCursorRect(self, e): """ Calculates small cursor rectangle around mouse position. Used to facilitate operations """ p = self.toMapCoordinates(e.pos()) w = self.canvas.mapUnitsPerPixel() * 10 return QgsRectangle(p.x() - w, p.y() - w, p.x() + w, p.y() + w) def deactivate(self): """ Deactivate tool. """ QApplication.restoreOverrideCursor() self.hoverRubberBand.reset(QgsWkbTypes.PolygonGeometry) try: if self.toolAction: self.toolAction.setChecked(False) if self is not None: QgsMapTool.deactivate(self) # self.canvas.unsetMapTool(self) except: pass def activate(self): """ Activate tool. """ if self.toolAction: self.toolAction.setChecked(True) QgsMapTool.activate(self) # self.iface.mapCanvas().setMapTool(self) layer = self.iface.mapCanvas().currentLayer() if not layer or not isinstance(layer, QgsVectorLayer): self.iface.messageBar().pushMessage( self.tr("Warning"), self.tr("Select a point vector layer as the active layer"), level=Qgis.Warning, duration=5) self.deactivate() def getPixelValue(self, rasterLayer): mousePos = self.qgsMapToolEmitPoint.toMapCoordinates( self.canvas.mouseLastXY()) mousePosGeom = QgsGeometry.fromPointXY(mousePos) return self.getPixelValueFromPoint(mousePosGeom, rasterLayer), mousePosGeom def getPixelValueFromPoint(self, mousePosGeom, rasterLayer, fromCanvas=True): """ """ rasterCrs = rasterLayer.crs() # if fromCanvas: # self.geometryHandler.reprojectFeature(mousePosGeom, rasterCrs, QgsProject.instance().crs()) # else: mousePosGeom = QgsGeometry(mousePosGeom) self.geometryHandler.reprojectFeature(mousePosGeom, rasterCrs, self.canvas.currentLayer().crs()) mousePos = mousePosGeom.asMultiPoint()[0] if mousePosGeom.isMultipart( ) else mousePosGeom.asPoint() # identify pixel(s) information i = rasterLayer.dataProvider().identify(mousePos, QgsRaster.IdentifyFormatValue) if i.isValid(): value = list(i.results().values())[0] if value: value = int(value) if self.decimals == 0 else round( value, self.decimals) return value else: return None def getPixelValueFromPointDict(self, pointDict, rasterLayer): """ pointDict = {'pointId':QgsGeometry} returns {'pointId': value} """ return { key: self.getPixelValueFromPoint(value, rasterLayer, fromCanvas=False) for key, value in pointDict.items() } #no python3 eh items()
class RectByFixedExtentTool(QgsMapTool): msgbar = pyqtSignal(str) rbFinished = pyqtSignal(object) rb_reset_signal = pyqtSignal() def __init__(self, canvas, x_length, y_length): QgsMapTool.__init__(self, canvas) self.canvas = canvas self.nbPoints = 0 self.rb = None self.x_p1, self.y_p1, self.x_p2, self.y_p2, self.x_p3, self.y_p3, self.x_p4, self.y_p4 = None, None, None, None, None, None, None, None self.distance = QgsDistanceArea() self.distance.setSourceCrs(QgsCoordinateReferenceSystem(4326), QgsProject.instance().transformContext()) self.distance.setEllipsoid('WGS84') self.fixed_p2, self.fixed_p3 = QgsPointXY(0, 0), QgsPointXY(0, 0) self.length = 0 self.mCtrl = None self.x_length = x_length self.y_length = y_length self.bearing = 0.0 # our own fancy cursor self.cursor = QCursor( QPixmap([ "16 16 3 1", " c None", ". c #FF0000", "+ c #1210f3", " ", " +.+ ", " ++.++ ", " +.....+ ", " +. .+ ", " +. . .+ ", " +. . .+ ", " ++. . .++", " ... ...+... ...", " ++. . .++", " +. . .+ ", " +. . .+ ", " ++. .+ ", " ++.....+ ", " ++.++ ", " +.+ " ])) self.rectangle = Rectangle() def keyPressEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = True def keyReleaseEvent(self, event): if event.key() == Qt.Key_Control: self.mCtrl = False if event.key() == Qt.Key_Escape: self.nbPoints = 0 self.x_p1, self.y_p1, self.x_p2, self.y_p2, self.x_p3, self.y_p3 = None, None, None, None, None, None self.fixed_p2, self.fixed_p3 = None, None if self.rb: self.rb.reset(True) self.rb = None self.canvas.refresh() self.rb_reset_signal.emit() return def canvasPressEvent(self, event): layer = self.canvas.currentLayer() if self.nbPoints == 0: color = QColor(255, 0, 0, 128) if self.rb: self.rb.reset() self.rb = None self.rb = QgsRubberBand(self.canvas, True) self.rb.setColor(color) self.rb.setWidth(1) self.msgbar.emit("Define bearing along track") self.rb_reset_signal.emit() elif self.nbPoints == 2: self.canvas.refresh() point = self.toLayerCoordinates(layer, event.pos()) point_map = self.toMapCoordinates(layer, point) if self.nbPoints == 0: self.x_p1 = point_map.x() self.y_p1 = point_map.y() elif self.nbPoints == 1: self.x_p2 = point_map.x() self.y_p2 = point_map.y() self.bearing = self.distance.bearing( QgsPointXY(self.x_p1, self.y_p1), QgsPointXY(self.x_p2, self.y_p2)) self.msgbar.emit("Define across track direction") else: self.x_p3 = point_map.x() self.y_p3 = point_map.y() self.nbPoints += 1 if self.nbPoints == 3: geom = self.rectangle.get_rect_by3_points( QgsPointXY(self.x_p1, self.y_p1), self.fixed_p2, self.fixed_p3, self.y_length) self.rb.setToGeometry(geom, None) self.nbPoints = 0 self.x_p1, self.y_p1, self.x_p2, self.y_p2, self.x_p3, self.y_p3 = None, None, None, None, None, None self.fixed_p2, self.fixed_p3 = None, None self.msgbar.emit("") self.rbFinished.emit(geom) if self.rb: return def canvasMoveEvent(self, event): if not self.rb: return currpoint = self.toMapCoordinates(event.pos()) if self.nbPoints == 1: self.bearing = self.distance.bearing( QgsPointXY(self.x_p1, self.y_p1), currpoint) self.fixed_p2 = self.distance.computeSpheroidProject( QgsPointXY(self.x_p1, self.y_p1), self.x_length, self.bearing) self.rb.setToGeometry( QgsGeometry.fromPolyline( [QgsPoint(self.x_p1, self.y_p1), QgsPoint(self.fixed_p2)]), None) curr_bearing = degrees(self.bearing) if curr_bearing < 0.0: curr_bearing = 360 + curr_bearing self.msgbar.emit( "Current distance: {:.3F} m, Current bearing: {:.3F} degrees". format(self.x_length, curr_bearing)) if self.nbPoints >= 2: # test if currpoint is left or right of the line defined by p1 and p2 side = calc_is_collinear(QgsPointXY(self.x_p1, self.y_p1), self.fixed_p2, currpoint) if side == 0: return None self.fixed_p3 = self.distance.computeSpheroidProject( QgsPointXY(self.x_p2, self.y_p2), self.y_length, self.bearing + radians(90) * side) geom = self.rectangle.get_rect_by3_points( QgsPointXY(self.x_p1, self.y_p1), self.fixed_p2, self.fixed_p3, self.y_length) self.rb.setToGeometry(geom, None) curr_bearing = degrees(self.bearing + radians(90) * side) if curr_bearing < 0.0: curr_bearing = 360 + curr_bearing self.msgbar.emit( "Current distance: {:.3F} m, Current bearing: {:.3F} degrees". format(self.y_length, curr_bearing)) def activate(self): self.canvas.setCursor(self.cursor) def deactivate(self): self.nbPoints = 0 self.x_p1, self.y_p1, self.x_p2, self.y_p2, self.x_p3, self.y_p3 = None, None, None, None, None, None self.fixed_p2, self.fixed_p3 = None, None if self.rb: self.rb.reset(True) self.rb.hide() self.rb = None self.canvas.refresh() def is_zoom_tool(self): return False def is_transient(self): return False def is_edit_tool(self): return True
class MultiLayerRectangleSelection(QgsMapTool): def __init__(self, canvas, action): self.canvas = canvas self.active = False QgsMapTool.__init__(self, self.canvas) self.setAction(action) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) mFillColor = QColor(254, 178, 76, 63) self.rubberBand.setColor(mFillColor) self.rubberBand.setWidth(1) self.reset() def reset(self): self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) def canvasPressEvent(self, e): self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) def canvasReleaseEvent(self, e): self.isEmittingPoint = False r = self.rectangle() layers = self.canvas.layers() for layer in layers: if layer.type() == QgsMapLayer.RasterLayer: continue if r is not None: lRect = self.canvas.mapSettings().mapToLayerCoordinates( layer, r) layer.selectByRect(lRect, False) self.rubberBand.hide() def canvasMoveEvent(self, e): if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates(e.pos()) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPointXY(startPoint.x(), startPoint.y()) point2 = QgsPointXY(startPoint.x(), endPoint.y()) point3 = QgsPointXY(endPoint.x(), endPoint.y()) point4 = QgsPointXY(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y( ) == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def deactivate(self): self.rubberBand.reset() try: if self is not None: QgsMapTool.deactivate(self) except: pass def activate(self): QgsMapTool.activate(self) def unload(self): self.deactivate()
class MultiLayerSelection(QgsMapTool): finished = QtCore.pyqtSignal(list) def __init__(self, canvas, iface): """ Tool Behaviours: (all behaviours start edition, except for rectangle one) 1- Left Click: Clears previous selection, selects feature, sets feature layer as active layer. The selection is done with the following priority: Point, Line then Polygon. Selection is only done in visible layer. 2- Control + Left Click: Adds to selection selected feature. This selection follows the priority in item 1. 3- Right Click: Opens feature form 4- Control + Right Click: clears selection and set feature's layer as activeLayer. activeLayer's definition follows priority of item 1; 5- Shift + drag and drop: draws a rectangle, then features that intersect this rectangl'e are added to selection """ self.iface = iface self.canvas = canvas self.toolAction = None QgsMapTool.__init__(self, self.canvas) self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon) self.hoverRubberBand = QgsRubberBand(self.canvas, QGis.Polygon) mFillColor = QColor( 254, 178, 76, 63 ) self.rubberBand.setColor(mFillColor) self.hoverRubberBand.setColor(QColor( 255, 0, 0, 90 )) self.rubberBand.setWidth(1) self.reset() self.blackList = self.getBlackList() self.cursorChanged = False self.cursorChangingHotkey = QtCore.Qt.Key_Alt self.menuHovered = False # indicates hovering actions over context menu def keyPressEvent(self, e): """ Reimplemetation of keyPressEvent() in order to handle cursor changing hotkey (Alt). """ if e.key() == self.cursorChangingHotkey and not self.cursorChanged: self.cursorChanged = True QtGui.QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor)) else: self.cursorChanged = False QtGui.QApplication.restoreOverrideCursor() def getBlackList(self): settings = QSettings() settings.beginGroup('PythonPlugins/DsgTools/Options') valueList = settings.value('valueList') if valueList: valueList = valueList.split(';') return valueList else: return ['moldura'] def reset(self): """ Resets rubber band. """ self.startPoint = self.endPoint = None self.isEmittingPoint = False self.rubberBand.reset(QGis.Polygon) def keyPressEvent(self, e): """ Reimplemetation of keyPressEvent() in order to handle cursor changing hotkey (F2). """ if e.key() == self.cursorChangingHotkey and not self.cursorChanged: self.cursorChanged = True QtGui.QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor)) else: self.cursorChanged = False QtGui.QApplication.restoreOverrideCursor() def canvasMoveEvent(self, e): """ Used only on rectangle select. """ if self.menuHovered: # deactivates rubberband when the context menu is "destroyed" self.hoverRubberBand.reset(QGis.Polygon) if not self.isEmittingPoint: return self.endPoint = self.toMapCoordinates( e.pos() ) self.showRect(self.startPoint, self.endPoint) def showRect(self, startPoint, endPoint): """ Builds rubberband rect. """ self.rubberBand.reset(QGis.Polygon) if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y(): return point1 = QgsPoint(startPoint.x(), startPoint.y()) point2 = QgsPoint(startPoint.x(), endPoint.y()) point3 = QgsPoint(endPoint.x(), endPoint.y()) point4 = QgsPoint(endPoint.x(), startPoint.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) # true to update canvas self.rubberBand.show() def rectangle(self): """ Builds rectangle from self.startPoint and self.endPoint """ if self.startPoint is None or self.endPoint is None: return None elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y(): return None return QgsRectangle(self.startPoint, self.endPoint) def setAction(self, action): self.toolAction = action self.toolAction.setCheckable(True) def canvasReleaseEvent(self, e): """ After the rectangle is built, here features are selected. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: firstGeom = self.checkSelectedLayers() self.isEmittingPoint = False r = self.rectangle() if r is None: return layers = self.canvas.layers() for layer in layers: #ignore layers on black list and features that are not vector layers if not isinstance(layer, QgsVectorLayer) or (self.layerHasPartInBlackList(layer.name())): continue if firstGeom is not None and layer.geometryType() != firstGeom: # if there are features already selected, shift will only get the same type geometry # if more than one ty of geometry is present, only the strongest will be selected continue #builds bbRect and select from layer, adding selection bbRect = self.canvas.mapSettings().mapToLayerCoordinates(layer, r) layer.select(bbRect, True) self.rubberBand.hide() def canvasPressEvent(self, e): """ Method used to build rectangle if shift is held, otherwise, feature select/deselect and identify is done. """ if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: self.isEmittingPoint = True self.startPoint = self.toMapCoordinates(e.pos()) self.endPoint = self.startPoint self.isEmittingPoint = True self.showRect(self.startPoint, self.endPoint) else: self.isEmittingPoint = False self.createContextMenu(e) def getCursorRect(self, e): """ Calculates small cursor rectangle around mouse position. Used to facilitate operations """ p = self.toMapCoordinates(e.pos()) w = self.canvas.mapUnitsPerPixel() * 10 return QgsRectangle(p.x()-w, p.y()-w, p.x()+w, p.y()+w) def layerHasPartInBlackList(self, lyrName): """ Verifies if terms in black list appear on lyrName """ for item in self.getBlackList(): if item.lower() in lyrName.lower(): return True return False def getPrimitiveDict(self, e, hasControlModifier=False): """ Builds a dict with keys as geometryTypes of layer, which are QGis.Point (value 0), QGis.Line (value 1) or QGis.Polygon (value 2), and values as layers from self.iface.legendInterface().layers(). When self.iface.legendInterface().layers() is called, a list of layers ordered according to lyr order in TOC is returned. """ #these layers are ordered by view order primitiveDict = dict() firstGeom = self.checkSelectedLayers() for lyr in self.iface.legendInterface().layers(): #ordered layers #layer types other than VectorLayer are ignored, as well as layers in black list and layers that are not visible if (lyr.type() != QgsMapLayer.VectorLayer) or (self.layerHasPartInBlackList(lyr.name())) or not self.iface.legendInterface().isLayerVisible(lyr): continue if hasControlModifier and (not firstGeom) and (not primitiveDict.keys() or lyr.geometryType() < firstGeom): firstGeom = lyr.geometryType() geomType = lyr.geometryType() if geomType not in primitiveDict.keys(): primitiveDict[geomType] = [] #removes selection if (not hasControlModifier and e.button() == QtCore.Qt.LeftButton) or (hasControlModifier and e.button() == QtCore.Qt.RightButton): lyr.removeSelection() primitiveDict[geomType].append(lyr) if hasControlModifier and firstGeom in [0, 1, 2]: return { firstGeom : primitiveDict[firstGeom] } else: return primitiveDict def deactivate(self): """ Deactivate tool. """ QtGui.QApplication.restoreOverrideCursor() self.hoverRubberBand.reset(QGis.Polygon) try: if self.toolAction: self.toolAction.setChecked(False) if self is not None: QgsMapTool.deactivate(self) except: pass def activate(self): """ Activate tool. """ if self.toolAction: self.toolAction.setChecked(True) QgsMapTool.activate(self) def setSelectionFeature(self, layer, feature, selectAll=False, setActiveLayer=False): """ Selects a given feature on canvas. :param layer: (QgsVectorLayer) layer containing the target feature. :param feature: (QgsFeature) taget feature to be selected. :param selectAll: (bool) indicates whether or not this fuction was called from a select all command. so it doesn't remove selection from those that are selected already from the list. :param setActiveLayer: (bool) indicates whether method should set layer as active. """ idList = layer.selectedFeaturesIds() # self.iface.setActiveLayer(layer) # esp acho q o problema eh aqui # layer.startEditing() featId = feature.id() if featId not in idList: idList.append(featId) elif not selectAll: idList.pop(idList.index(featId)) layer.setSelectedFeatures(idList) if setActiveLayer: layer.startEditing() if self.iface.activeLayer() != layer: self.iface.setActiveLayer(layer) return def setSelectionListFeature(self, dictLayerFeature, selectAll=True): """ Selects all features on canvas of a given dict. :param dictLayerFeature: (dict) dict of layers/features to be selected. :param selectAll: (bool) indicates if "All"-command comes from a "Select All". In that case, selected features won't be deselected. """ for layer in dictLayerFeature.keys(): geomType = layer.geometryType() # ID list of features already selected idList = layer.selectedFeaturesIds() # restart feature ID list for each layer featIdList = [] for feature in dictLayerFeature[layer]: featId = feature.id() if featId not in idList: idList.append(featId) elif not selectAll: idList.pop(idList.index(featId)) layer.setSelectedFeatures(idList) layer.startEditing() # last layer is set active and if self.iface.activeLayer() != layer: self.iface.setActiveLayer(layer) def openMultipleFeatureForm(self, dictLayerFeature): """ Opens all features Feature Forms of a given list. :param dictLayerFeature: (dict) dict of layers/features to have their feature form exposed. """ for layer, features in dictLayerFeature.iteritems(): for feat in features: self.iface.openFeatureForm(layer, feat, showModal=False) def filterStrongestGeometry(self, dictLayerFeature): """ Filter a given dict of features for its strongest geometry. :param dictLayerFeature: (dict) a dict of layers and its features to be filtered. :return: (dict) filtered dict with only layers of the strongest geometry on original dict. """ strongest_geometry = 3 outDict = dict() if dictLayerFeature: for lyr in dictLayerFeature.keys(): # to retrieve strongest geometry value if strongest_geometry > lyr.geometryType(): strongest_geometry = lyr.geometryType() if strongest_geometry == 0: break for lyr in dictLayerFeature.keys(): if lyr.geometryType() == strongest_geometry: outDict[lyr] = dictLayerFeature[lyr] return outDict def createRubberBand(self, feature, layer, geom): """ Creates a rubber band around from a given a standard feature string. :param feature: taget feature to be highlighted :param layer: layer containing the target feature :param geom: int indicating geometry type of target feature """ if geom == 0: self.hoverRubberBand.reset(QGis.Point) elif geom == 1: self.hoverRubberBand.reset(QGis.Line) else: self.hoverRubberBand.reset(QGis.Polygon) self.hoverRubberBand.addGeometry(feature.geometry(), layer) # to inform the code that menu has been hovered over self.menuHovered = True def createMultipleRubberBand(self, dictLayerFeature): """ Creates rubberbands around features. :param dictLayerFeature: (dict) dict of layer/features to have rubberbands built around. """ # only one type of geometry at a time will have rubberbands around it geom = dictLayerFeature.keys()[0].geometryType() if geom == 0: self.hoverRubberBand.reset(QGis.Point) elif geom == 1: self.hoverRubberBand.reset(QGis.Line) else: self.hoverRubberBand.reset(QGis.Polygon) for layer, features in dictLayerFeature.iteritems(): for feat in features: self.hoverRubberBand.addGeometry(feat.geometry(), layer) self.menuHovered = True def checkSelectedLayers(self): """ Checks if there are layers selected on canvas. If there are, returns the geometry type of selected feature(s). If more than one type of feature is selected, the "strongest" geometry is returned. """ geom = None for layer in self.iface.legendInterface().layers(): if isinstance(layer, QgsVectorLayer): selection = layer.selectedFeatures() if len(selection): if geom == None: geom = layer.geometryType() continue elif layer.geometryType() < geom: geom = layer.geometryType() continue return geom def addCallBackToAction(self, action, onTriggeredAction, onHoveredAction=None): """ Adds action the command to the action. If onHoveredAction is given, signal "hovered" is applied with given action. :param action: (QAction) associated with target context menu. :param onTriggeredAction: (object) action to be executed when the given action is triggered. :param onHoveredAction: (object) action to be executed whilst the given action is hovered. """ action.triggered[()].connect(onTriggeredAction) if onHoveredAction: action.hovered[()].connect(onHoveredAction) def getCallback(self, e, layer, feature, geomType=None, selectAll=True): """ Gets the callback for an action. :param e: (QMouseEvent) mouse event on canvas. :param layer: (QgsVectorLayer) layer to be treated. :param feature: (QgsFeature) feature to be treated. :param geomType: (int) code indicating layer geometry type. It is retrieved OTF in case it's not given. :return: (tuple-of function_lambda) callbacks for triggered and hovered signals. """ if not geomType: geomType = layer.geometryType() if e.button() == QtCore.Qt.LeftButton: # line added to make sure the action is associated with current loop value, # lambda function is used with standard parameter set to current loops value. triggeredAction = lambda t=[layer, feature] : self.setSelectionFeature(t[0], feature=t[1], selectAll=selectAll, setActiveLayer=True) hoveredAction = lambda t=[layer, feature] : self.createRubberBand(feature=t[1], layer=t[0], geom=geomType) elif e.button() == QtCore.Qt.RightButton: selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if selected: triggeredAction = lambda layer=layer : self.iface.setActiveLayer(layer) hoveredAction = None else: triggeredAction = lambda t=[layer, feature] : self.iface.openFeatureForm(t[0], t[1], showModal=False) hoveredAction = lambda t=[layer, feature] : self.createRubberBand(feature=t[1], layer=t[0], geom=geomType) return triggeredAction, hoveredAction def getCallbackMultipleFeatures(self, e, dictLayerFeature, selectAll=True): """ Sets the callback of an action with a list features as target. :param e: (QMouseEvent) mouse event on canvas. :param dictLayerFeature: (dict) dictionary containing layers/features to be treated. :return: (tuple-of function_lambda) callbacks for triggered and hovered signals. """ # setting the action for the "All" options if e.button() == QtCore.Qt.LeftButton: triggeredAction = lambda t=dictLayerFeature: self.setSelectionListFeature(dictLayerFeature=t, selectAll=selectAll) else: triggeredAction = lambda t=dictLayerFeature: self.openMultipleFeatureForm(dictLayerFeature=t) # to trigger "Hover" signal on QMenu for the multiple options hoveredAction = lambda t=dictLayerFeature : self.createMultipleRubberBand(dictLayerFeature=t) return triggeredAction, hoveredAction def createSubmenu(self, e, parentMenu, menuDict, genericAction, selectAll): """ Creates a submenu in a given parent context menu and populates it, with classes/feature sublevels from the menuDict. :param e: (QMouseEvent) mouse event on canvas. If menuDict has only 1 class in it, method will populate parent QMenu. :param parentMenu: (QMenu) menu containing the populated submenu :param menuDict: (dict) dictionary containing all classes and their features to be filled into submenu. :param genericAction: (str) text to be shown into generic action description on the outter level of submenu. :return: (dict) mapping of classes and their own QMenu object. """ # creating a dict to handle all "menu" for each class submenuDict = dict() # sort the layers from diciotnary classNameDict = { cl.name() : cl for cl in menuDict.keys() } layers = sorted(classNameDict.keys()) for className in layers: # menu for features of each class cl = classNameDict[className] geomType = cl.geometryType() # get layer database name dsUri = cl.dataProvider().dataSourceUri() temp = [] if '/' in dsUri or '\\' in dsUri: db_name = dsUri.split("|")[0] if "|" in dsUri else dsUri # data source is a file, not a postgres database dbIsFile = True elif 'memory' in dsUri: db_name = self.tr(u'{0} (Memory Layer)').format(className) dbIsFile = True else: db_name = dsUri.split("'")[1] dbIsFile = False if len(menuDict) == 1: # if dictionaty has only 1 class, no need for an extra QMenu - features will be enlisted directly # order features by ID to be displayer ordered featDict = { feat.id() : feat for feat in menuDict[cl] } orderedFeatIdList = sorted(featDict.keys()) for featId in orderedFeatIdList: feat = featDict[featId] if dbIsFile: s = u'{0} (feat_id = {1})'.format(db_name, featId) else: s = u'{0}.{1} (feat_id = {2})'.format(db_name, className, featId) # inserting action for each feature action = parentMenu.addAction(s) triggeredAction, hoveredAction = self.getCallback(e=e, layer=cl, feature=feat, geomType=geomType, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # inserting generic action, if necessary if len(menuDict[cl]) > 1: # if there are more than 1 feature to be filled, "All"-command should be added action = parentMenu.addAction(self.tr(u"{0} From Class {1}").format(genericAction, className)) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature=menuDict, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # there is no mapping of class to be exposed, only information added to parent QMenu itself return dict() if dbIsFile: title = db_name else: title = '{0}.{1}'.format(db_name, className) submenuDict[cl] = QtGui.QMenu(title=title, parent=parentMenu) parentMenu.addMenu(submenuDict[cl]) # inserting an entry for every feature of each class in its own context menu # order features by ID to be displayer ordered featDict = { feat.id() : feat for feat in menuDict[cl] } orderedFeatIdList = sorted(featDict.keys()) for featId in orderedFeatIdList: feat = featDict[featId] s = u'feat_id = {0}'.format(featId) action = submenuDict[cl].addAction(s) triggeredAction, hoveredAction = self.getCallback(e=e, layer=cl, feature=feat, geomType=geomType, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) # set up list for the "All"-commands temp.append([cl, feat, geomType]) # adding generic action for each class if len(menuDict[cl]) > 1: # if there are more than 1 feature to be filled, "All"-command should be added action = submenuDict[cl].addAction(self.tr(u"{0} From Class {1}").format(genericAction, className)) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature={ cl : menuDict[cl] }, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) return submenuDict def setContextMenuStyle(self, e, dictMenuSelected, dictMenuNotSelected): """ Defines how many "submenus" the context menu should have. There are 3 context menu scenarios to be handled: :param e: (QMouseEvent) mouse event on canvas. :param dictMenuSelected: (dict) dictionary of classes and its selected features being treatead. :param dictMenuNotSelected: (dict) dictionary of classes and its non selected features being treatead. """ # finding out filling conditions selectedDict = bool(dictMenuSelected) notSelectedDict = bool(dictMenuNotSelected) # finding out if one of either dictionaty are filled ("Exclusive or") selectedXORnotSelected = (selectedDict != notSelectedDict) # setting "All"-command name if e.button() == QtCore.Qt.RightButton: genericAction = self.tr('Open All Feature Forms') else: genericAction = self.tr('Select All Features') # in case one of given dict is empty if selectedXORnotSelected: if selectedDict: menuDict, menu = dictMenuSelected, QtGui.QMenu(title=self.tr('Selected Features')) genericAction = self.tr('Deselect All Features') # if the dictionary is from selected features, we want commands to be able to deselect them selectAll = False else: menuDict, menu = dictMenuNotSelected, QtGui.QMenu(title=self.tr('Not Selected Features')) genericAction = self.tr('Select All Features') # if the dictionary is from non-selected features, we want commands to be able to select them selectAll = True if e.button() == QtCore.Qt.RightButton: genericAction = self.tr('Open All Feature Forms') self.createSubmenu(e=e, parentMenu=menu, menuDict=menuDict, genericAction=genericAction, selectAll=selectAll) if len(menuDict) != 1 and len(menuDict.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = menu.addAction(genericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature=menuDict, selectAll=selectAll) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) elif selectedDict: # if both of them is empty one more QMenu level is added menu = QtGui.QMenu() selectedMenu = QtGui.QMenu(title=self.tr('Selected Features')) notSelectedMenu = QtGui.QMenu(title=self.tr('Not Selected Features')) menu.addMenu(selectedMenu) menu.addMenu(notSelectedMenu) selectedGenericAction = self.tr('Deselect All Features') notSelectedGenericAction = self.tr('Select All Features') # selectAll is set to True as now we want command to Deselect Features in case they are selected self.createSubmenu(e=e, parentMenu=selectedMenu, menuDict=dictMenuSelected, genericAction=selectedGenericAction, selectAll=False) if len(dictMenuSelected) != 1 and len(dictMenuSelected.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = selectedMenu.addAction(selectedGenericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature=dictMenuSelected, selectAll=False) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) self.createSubmenu(e=e, parentMenu=notSelectedMenu, menuDict=dictMenuNotSelected, genericAction=notSelectedGenericAction, selectAll=True) if len(dictMenuNotSelected) != 1 and len(dictMenuNotSelected.values()) > 1: # if there's only one class, "All"-command is given by createSubmenu method action = notSelectedMenu.addAction(notSelectedGenericAction) triggeredAction, hoveredAction = self.getCallbackMultipleFeatures(e=e, dictLayerFeature=dictMenuNotSelected, selectAll=True) self.addCallBackToAction(action=action, onTriggeredAction=triggeredAction, onHoveredAction=hoveredAction) menu.exec_(self.canvas.viewport().mapToGlobal(e.pos())) def checkSelectedFeaturesOnDict(self, menuDict): """ Checks all selected features from a given dictionary ( { (QgsVectorLayer)layer : [ (QgsFeature)feat ] } ). :param menuDict: (dict) dictionary with layers and their features to be analyzed. :return: (tuple-of-dict) both dictionaries of selected and non-selected features of each layer. """ selectedFeaturesDict, notSelectedFeaturesDict = dict(), dict() for cl in menuDict.keys(): selectedFeats = [f.id() for f in cl.selectedFeatures()] for feat in menuDict[cl]: if feat.id() in selectedFeats: if cl not in selectedFeaturesDict.keys(): selectedFeaturesDict[cl] = [feat] else: selectedFeaturesDict[cl].append(feat) else: if cl not in notSelectedFeaturesDict.keys(): notSelectedFeaturesDict[cl] = [feat] else: notSelectedFeaturesDict[cl].append(feat) return selectedFeaturesDict, notSelectedFeaturesDict def reprojectSearchArea(self, layer, geom): """ Reprojects search area if necessary, according to what is being searched. :param layer: (QgsVectorLayer) layer which target rectangle has to have same SRC. :param geom: (QgsRectangle) rectangle representing search area. """ #geom always have canvas coordinates epsg = self.canvas.mapSettings().destinationCrs().authid() #getting srid from something like 'EPSG:31983' srid = layer.crs().authid() if epsg == srid: return geom crsSrc = QgsCoordinateReferenceSystem(epsg) crsDest = QgsCoordinateReferenceSystem(srid) # Creating a transformer coordinateTransformer = QgsCoordinateTransform(crsSrc, crsDest) # here we have to put authid, not srid auxGeom = QgsGeometry.fromRect(geom) auxGeom.transform(coordinateTransformer) return auxGeom.boundingBox() def createContextMenu(self, e): """ Creates the context menu for overlapping layers. :param e: mouse event caught from canvas. """ selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if selected: firstGeom = self.checkSelectedLayers() # setting a list of features to iterate over layerList = self.getPrimitiveDict(e, hasControlModifier=selected) layers = [] for key in layerList.keys(): layers += layerList[key] if layers: rect = self.getCursorRect(e) lyrFeatDict = dict() for layer in layers: if not isinstance(layer, QgsVectorLayer): continue geomType = layer.geometryType() # iterate over features inside the mouse bounding box bbRect = self.canvas.mapSettings().mapToLayerCoordinates(layer, rect) for feature in layer.getFeatures(QgsFeatureRequest(bbRect)): geom = feature.geometry() if geom: searchRect = self.reprojectSearchArea(layer, rect) if selected: # if Control was held, appending behaviour is different if not firstGeom: firstGeom = geomType elif firstGeom > geomType: firstGeom = geomType if geomType == firstGeom and geom.intersects(searchRect): # only appends features if it has the same geometry as first selected feature if layer in lyrFeatDict.keys(): lyrFeatDict[layer].append(feature) else: lyrFeatDict[layer] = [feature] else: if geom.intersects(searchRect): if layer in lyrFeatDict.keys(): lyrFeatDict[layer].append(feature) else: lyrFeatDict[layer] = [feature] lyrFeatDict = self.filterStrongestGeometry(lyrFeatDict) if lyrFeatDict: moreThanOneFeat = lyrFeatDict.values() and len(lyrFeatDict.values()) > 1 or len(lyrFeatDict.values()[0]) > 1 if moreThanOneFeat: # if there are overlapping features (valid candidates only) selectedFeaturesDict, notSelectedFeaturesDict = self.checkSelectedFeaturesOnDict(menuDict=lyrFeatDict) self.setContextMenuStyle(e=e, dictMenuSelected=selectedFeaturesDict, dictMenuNotSelected=notSelectedFeaturesDict) else: layer = lyrFeatDict.keys()[0] feature = lyrFeatDict[layer][0] selected = (QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier) if e.button() == QtCore.Qt.LeftButton: # if feature is selected, we want it to be de-selected self.setSelectionFeature(layer=layer, feature=feature, selectAll=False, setActiveLayer=True) elif selected: if self.iface.activeLayer() != layer: self.iface.setActiveLayer(layer) else: self.iface.openFeatureForm(layer, feature, showModal=False)