Example #1
0
 def startFlashFeature(self, featureGeometry, layerCrs):
     geomType = QgsWkbTypes.geometryType(featureGeometry.wkbType())
     rb = QgsRubberBand(iface.mapCanvas(), geomType)
     rb.addGeometry( featureGeometry, layerCrs )
     flashes=3
     duration=500
     if geomType == QgsWkbTypes.LineGeometry or geomType == QgsWkbTypes.PointGeometry:
         rb.setWidth( 2 )
         rb.setSecondaryStrokeColor( QColor( 255, 255, 255 ) )
     if geomType == QgsWkbTypes.PointGeometry :
         rb.setIcon( QgsRubberBand.ICON_CIRCLE )
     startColor = QColor(255, 0, 0, 255)
     startColor.setAlpha( 255 )
     endColor = QColor(255, 0, 0, 0)
     endColor.setAlpha( 0 )
     self.animation = QVariantAnimation( iface.mapCanvas() )
     self.animation.finished.connect(lambda a=self.animation, b=rb: self.finishedAnimation(a, b))
     self.animation.valueChanged.connect(lambda value, a=self.animation, b=rb, c=geomType: self.valueChangedAnimation(value, a, b, c))
     self.animation.setDuration( duration * flashes )
     self.animation.setStartValue( endColor )
     midStep = 0.2 / flashes
     for i in range(flashes):
         start = float(i  / flashes)
         self.animation.setKeyValueAt( start + midStep, startColor )
         end = float(( i + 1 ) / flashes)
         if not(end == 1.0):
             self.animation.setKeyValueAt( end, endColor )
     self.animation.setEndValue( endColor )
     self.animation.start()
class RubberBandPolygon(QgsMapTool):
    def __init__(self, canvas):
        QgsMapTool.__init__(self, canvas)
        self.mCanvas = canvas
        self.mRubberBand = None
        self.mRubberBand0 = QgsRubberBand(self.mCanvas, QGis.Polygon)
        self.mCursor = Qt.ArrowCursor
        self.mFillColor = QColor(254, 178, 76, 63)
        self.mBorderColour = QColor(254, 58, 29, 100)
        self.mRubberBand0.setBorderColor(self.mBorderColour)
        self.polygonGeom = None
        self.drawFlag = False
#         self.constructionLayer = constructionLayer

    def canvasPressEvent(self, e):
        if (self.mRubberBand == None):
            self.mRubberBand0.reset(QGis.Polygon)
            #             define._canvas.clearCache ()
            self.mRubberBand = QgsRubberBand(self.mCanvas, QGis.Polygon)
            self.mRubberBand0 = QgsRubberBand(self.mCanvas, QGis.Polygon)
            self.mRubberBand.setFillColor(self.mFillColor)
            self.mRubberBand.setBorderColor(self.mBorderColour)
            self.mRubberBand0.setFillColor(self.mFillColor)
            self.mRubberBand0.setBorderColor(self.mBorderColour)
        if (e.button() == Qt.LeftButton):
            self.mRubberBand.addPoint(self.toMapCoordinates(e.pos()))
        else:
            if (self.mRubberBand.numberOfVertices() > 2):
                self.polygonGeom = self.mRubberBand.asGeometry()
            else:
                return
#                 QgsMapToolSelectUtils.setSelectFeatures( self.mCanvas, polygonGeom, e )
            self.mRubberBand.reset(QGis.Polygon)
            self.mRubberBand0.addGeometry(self.polygonGeom, None)
            self.mRubberBand0.show()
            self.mRubberBand = None
            self.emit(SIGNAL("outputResult"), self.polygonGeom)

    def canvasMoveEvent(self, e):
        pass
        if (self.mRubberBand == None):
            return
        if (self.mRubberBand.numberOfVertices() > 0):
            self.mRubberBand.removeLastPoint(0)
            self.mRubberBand.addPoint(self.toMapCoordinates(e.pos()))

    def deactivate(self):
        #         self.rubberBand.reset(QGis.Point)
        QgsMapTool.deactivate(self)
        self.emit(SIGNAL("deactivated()"))
Example #3
0
    def showCrossedLines(self, point):
        currExt = iface.mapCanvas().extent()

        leftPt = QgsPoint(currExt.xMinimum(), point.y())
        rightPt = QgsPoint(currExt.xMaximum(), point.y())
        topPt = QgsPoint(point.x(), currExt.yMaximum())
        bottomPt = QgsPoint(point.x(), currExt.yMinimum())
        horizLine = QgsGeometry.fromPolyline([leftPt, rightPt])
        vertLine = QgsGeometry.fromPolyline([topPt, bottomPt])
        
        crossRb = QgsRubberBand(iface.mapCanvas(), QgsWkbTypes.LineGeometry)
        crossRb.setColor(Qt.red)
        crossRb.setWidth(2)
        crossRb.addGeometry(horizLine, None)
        crossRb.addGeometry(vertLine, None)
        QTimer.singleShot(1500, lambda r=crossRb: r.reset())
Example #4
0
    def highlight(self):
        curr_ext = self.canvas.extent()

        left_point = QgsPoint(curr_ext.xMinimum(), curr_ext.center().y())
        right_point = QgsPoint(curr_ext.xMaximum(), curr_ext.center().y())

        top_point = QgsPoint(curr_ext.center().x(), curr_ext.yMaximum())
        bottom_point = QgsPoint(curr_ext.center().x(), curr_ext.yMinimum())

        horiz_line = QgsGeometry.fromPolyline([left_point, right_point])
        vert_line = QgsGeometry.fromPolyline([top_point, bottom_point])

        cross_rb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        cross_rb.setColor(QColor(255, 0, 0))
        cross_rb.reset(QgsWkbTypes.LineGeometry)
        cross_rb.addGeometry(horiz_line, None)
        cross_rb.addGeometry(vert_line, None)

        QTimer.singleShot(600, cross_rb.reset)
        self.canvas.refresh()
Example #5
0
class MoveTool(QgsMapToolAdvancedDigitizing):
    """
    Map tool class to move or copy an object
    """

    def __init__(self, iface):
        """
        Constructor
        :param iface: interface
        """
        QgsMapToolAdvancedDigitizing.__init__(self,  iface.mapCanvas(), iface.cadDockWidget())
        self.__iface = iface
        self.icon_path = ':/plugins/VDLTools/icons/move_icon.png'
        self.text = QCoreApplication.translate("VDLTools", "Move/Copy a feature")
        self.setCursor(Qt.ArrowCursor)
        self.__isEditing = False
        self.__findVertex = False
        self.__onMove = False
        self.__layer = None
        self.__confDlg = None
        self.__lastFeatureId = None
        self.__selectedFeature = None
        self.__rubberBand = None
        self.__rubberSnap = None
        self.__newFeature = None
        self.__selectedVertex = None

    def activate(self):
        """
        When the action is selected
        """
        QgsMapToolAdvancedDigitizing.activate(self)
        if self.__layer.geometryType() == QGis.Point:
            self.setMode(self.CaptureLine)
        else:
            self.setMode(self.CaptureNone)

    def deactivate(self):
        """
        When the action is deselected
        """
        self.__cancel()
        QgsMapToolAdvancedDigitizing.deactivate(self)

    def toolName(self):
        """
        To get the tool name
        :return: tool name
        """
        return QCoreApplication.translate("VDLTools", "Move/Copy")

    def startEditing(self):
        """
        To set the action as enable, as the layer is editable
        """
        self.action().setEnabled(True)
        Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing)
        self.__layer.editingStopped.connect(self.stopEditing)

    def stopEditing(self):
        """
        To set the action as disable, as the layer is not editable
        """
        self.action().setEnabled(False)
        Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing)
        self.__layer.editingStarted.connect(self.startEditing)
        if self.canvas().mapTool() == self:
            self.__iface.actionPan().trigger()

    def setTool(self):
        """
        To set the current tool as this one
        """
        self.canvas().setMapTool(self)

    def __cancel(self):
        """
        To cancel used variables
        """
        if self.__rubberBand is not None:
            self.canvas().scene().removeItem(self.__rubberBand)
            self.__rubberBand.reset()
            self.__rubberBand = None
        if self.__rubberSnap is not None:
            self.canvas().scene().removeItem(self.__rubberSnap)
            self.__rubberSnap.reset()
            self.__rubberSnap = None
        self.__isEditing = False
        self.__findVertex = False
        self.__onMove = False
        self.__lastFeatureId = None
        self.__selectedFeature = None
        self.__confDlg = None
        self.__newFeature = None
        self.__selectedVertex = None
        self.__layer.removeSelection()
        if self.__layer.geometryType() == QGis.Point:
            self.setMode(self.CaptureLine)
        else:
            self.setMode(self.CaptureNone)

    def __removeLayer(self):
        """
        To remove the current working layer
        """
        if self.__layer is not None:
            if self.__layer.isEditable():
                Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing)
            else:
                Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing)
            self.__layer = None

    def setEnable(self, layer):
        """
        To check if we can enable the action for the selected layer
        :param layer: selected layer
        """
        if layer is not None and layer.type() == QgsMapLayer.VectorLayer:
            if layer == self.__layer:
                return

            if self.__layer is not None:
                if self.__layer.isEditable():
                    Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing)
                else:
                    Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing)
            self.__layer = layer

            if self.__layer.geometryType() == QGis.Point:
                self.setMode(self.CaptureLine)
            else:
                self.setMode(self.CaptureNone)

            if self.__layer.isEditable():
                self.action().setEnabled(True)
                self.__layer.editingStopped.connect(self.stopEditing)
            else:
                self.action().setEnabled(False)
                self.__layer.editingStarted.connect(self.startEditing)
                if self.canvas().mapTool() == self:
                    self.__iface.actionPan().trigger()
            return
        self.action().setEnabled(False)
        if self.canvas().mapTool() == self:
            self.__iface.actionPan().trigger()
        self.__removeLayer()

    def __pointPreview(self, point):
        """
        To create a point geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        point_v2 = GeometryV2.asPointV2(self.__selectedFeature.geometry(), self.__iface)
        self.__newFeature = QgsPointV2(point.x(), point.y())
        self.__newFeature.addZValue(point_v2.z())
        self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Point)
        self.__rubberBand.setToGeometry(QgsGeometry(self.__newFeature.clone()), None)

    def __linePreview(self, point):
        """
        To create a line geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        line_v2, curved = GeometryV2.asLineV2(self.__selectedFeature.geometry(), self.__iface)
        vertex = QgsPointV2()
        line_v2.pointAt(self.__selectedVertex, vertex)
        self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line)
        dx = vertex.x() - point.x()
        dy = vertex.y() - point.y()
        if isinstance(curved, (list, tuple)):
            self.__newFeature = QgsCompoundCurveV2()
            for pos in range(line_v2.nCurves()):
                curve_v2 = self.__newCurve(curved[pos], line_v2.curveAt(pos), dx, dy)
                self.__newFeature.addCurve(curve_v2)
                if pos == 0:
                    self.__rubberBand.setToGeometry(QgsGeometry(curve_v2.curveToLine()), None)
                else:
                    self.__rubberBand.addGeometry(QgsGeometry(curve_v2.curveToLine()), None)
        else:
            self.__newFeature = self.__newCurve(curved, line_v2, dx, dy)
            self.__rubberBand.setToGeometry(QgsGeometry(self.__newFeature.curveToLine()), None)

    @staticmethod
    def __newCurve(curved, line_v2, dx, dy):
        """
        To create a new moved line
        :param curved: if the line is curved
        :param line_v2: the original line
        :param dx: x translation
        :param dy: y translation
        :return: the new line
        """
        if curved:
            newCurve = QgsCircularStringV2()
        else:
            newCurve = QgsLineStringV2()
        points = []
        for pos in range(line_v2.numPoints()):
            x = line_v2.pointN(pos).x() - dx
            y = line_v2.pointN(pos).y() - dy
            pt = QgsPointV2(x, y)
            pt.addZValue(line_v2.pointN(pos).z())
            points.append(pt)
        newCurve.setPoints(points)
        return newCurve

    def __polygonPreview(self, point):
        """
        To create a polygon geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        polygon_v2, curved = GeometryV2.asPolygonV2(self.__selectedFeature.geometry(), self.__iface)
        vertex = polygon_v2.vertexAt(GeometryV2.polygonVertexId(polygon_v2, self.__selectedVertex))
        dx = vertex.x() - point.x()
        dy = vertex.y() - point.y()
        self.__newFeature = QgsCurvePolygonV2()
        self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line)
        line_v2 = self.__newCurve(curved[0], polygon_v2.exteriorRing(), dx, dy)
        self.__newFeature.setExteriorRing(line_v2)
        self.__rubberBand.setToGeometry(QgsGeometry(line_v2.curveToLine()), None)
        for num in range(polygon_v2.numInteriorRings()):
            line_v2 = self.__newCurve(curved[num+1], polygon_v2.interiorRing(num), dx, dy)
            self.__newFeature.addInteriorRing(line_v2)
            self.__rubberBand.addGeometry(QgsGeometry(line_v2.curveToLine()), None)

    def __onConfirmCancel(self):
        """
        When the Cancel button in Move Confirm Dialog is pushed
        """
        self.__confDlg.reject()

    def __onConfirmMove(self):
        """
        When the Move button in Move Confirm Dialog is pushed
        """
        geometry = QgsGeometry(self.__newFeature)
        if not geometry.isGeosValid():
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL, duration=0)
        self.__layer.changeGeometry(self.__selectedFeature.id(), geometry)
        self.__confDlg.accept()
        self.__cancel()

    def __onConfirmCopy(self):
        """
        When the Copy button in Move Confirm Dialog is pushed
        """
        geometry = QgsGeometry(self.__newFeature)
        if not geometry.isGeosValid():
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Geos geometry problem"), level=QgsMessageBar.CRITICAL, duration=0)
        feature = QgsFeature(self.__layer.pendingFields())
        feature.setGeometry(geometry)
        primaryKey = QgsDataSourceURI(self.__layer.source()).keyColumn()
        for field in self.__selectedFeature.fields():
            if field.name() != primaryKey:
                feature.setAttribute(field.name(), self.__selectedFeature.attribute(field.name()))
        if len(self.__selectedFeature.fields()) > 0 and self.__layer.editFormConfig().suppress() != \
                QgsEditFormConfig.SuppressOn:
            self.__iface.openFeatureForm(self.__layer, feature)
        else:
            self.__layer.addFeature(feature)
        self.__confDlg.accept()
        self.__cancel()

    def keyReleaseEvent(self, event):
        """
        When keyboard is pressed
        :param event: keyboard event
        """
        if event.key() == Qt.Key_Escape:
            self.__cancel()

    def cadCanvasMoveEvent(self, event):
        """
        When the mouse is moved
        :param event: mouse event
        """

        if type(event) == QMoveEvent:
            map_point = self.toMapCoordinates(event.pos())
        else:
            map_point = event.mapPoint()

        if not self.__isEditing and not self.__findVertex and not self.__onMove:
            laySettings = QgsSnappingUtils.LayerConfig(self.__layer, QgsPointLocator.All, 10,
                                                       QgsTolerance.Pixels)
            f_l = Finder.findClosestFeatureAt(map_point, self.canvas(), [laySettings])
            if f_l is not None and self.__lastFeatureId != f_l[0].id():
                self.__lastFeatureId = f_l[0].id()
                self.__layer.setSelectedFeatures([f_l[0].id()])
            if f_l is None:
                self.__layer.removeSelection()
                self.__lastFeatureId = None
        elif self.__findVertex:
            if self.__rubberBand is not None:
                self.__rubberBand.reset()
            closest = self.__selectedFeature.geometry().closestVertex(map_point)
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            self.__rubberBand.setIcon(4)
            self.__rubberBand.setIconSize(20)
            self.__rubberBand.setToGeometry(QgsGeometry().fromPoint(closest[0]), None)
        elif self.__onMove:
            if self.__rubberBand is not None:
                self.__rubberBand.reset()
            if self.__layer.geometryType() == QGis.Polygon:
                self.__polygonPreview(map_point)
            elif self.__layer.geometryType() == QGis.Line:
                self.__linePreview(map_point)
            else:
                self.__pointPreview(map_point)
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            self.__rubberBand.setWidth(2)
            if self.__layer.geometryType() != QGis.Point:
                self.__rubberBand.setLineStyle(Qt.DotLine)
            else:
                self.__rubberBand.setIcon(4)
                self.__rubberBand.setIconSize(8)
            if self.__rubberSnap is not None:
                self.__rubberSnap.reset()
            else:
                self.__rubberSnap = QgsRubberBand(self.canvas(), QGis.Point)
            self.__rubberSnap.setColor(color)
            self.__rubberSnap.setWidth(2)
            self.__rubberSnap.setIconSize(20)
            match = Finder.snap(map_point, self.canvas())
            if match.hasVertex() or match.hasEdge():
                point = match.point()
                if match.hasVertex():
                    if match.layer():
                        self.__rubberSnap.setIcon(4)
                    else:
                        self.__rubberSnap.setIcon(1)
                if match.hasEdge():
                    intersection = Finder.snapCurvedIntersections(point, self.canvas(), self)
                    if intersection is not None:
                        self.__rubberSnap.setIcon(1)
                        point = intersection
                    else:
                        self.__rubberSnap.setIcon(3)
                self.__rubberSnap.setToGeometry(QgsGeometry().fromPoint(point), None)

    def cadCanvasReleaseEvent(self, event):
        """
        When the mouse is clicked
        :param event: mouse event
        """
        if not self.__isEditing and not self.__findVertex and not self.__onMove:
            found_features = self.__layer.selectedFeatures()
            if len(found_features) > 0:
                if len(found_features) > 1:
                    self.__iface.messageBar().pushMessage(
                        QCoreApplication.translate("VDLTools", "One feature at a time"), level=QgsMessageBar.INFO)
                    return
                self.__selectedFeature = found_features[0]
                if self.__layer.geometryType() != QGis.Point:
                    self.__iface.messageBar().pushMessage(
                        QCoreApplication.translate("VDLTools",
                                                   "Select vertex for moving (ESC to undo)"),
                        level=QgsMessageBar.INFO, duration=3)
                    self.__findVertex = True
                    self.setMode(self.CaptureLine)
                    self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Point)
                else:
                    self.setMode(self.CaptureNone)
                    self.__onMove = True
        elif self.__findVertex:
            self.__findVertex = False
            self.setMode(self.CaptureNone)
            closest = self.__selectedFeature.geometry().closestVertex(event.mapPoint())
            self.__selectedVertex = closest[1]
            self.__onMove = True
        elif self.__onMove:
            self.__onMove = False
            mapPoint = event.mapPoint()
            match = Finder.snap(event.mapPoint(), self.canvas())
            if match.hasVertex() or match.hasEdge():
                mapPoint = match.point()
                if match.hasEdge():
                    intersection = Finder.snapCurvedIntersections(mapPoint, self.canvas(), self)
                    if intersection is not None:
                        mapPoint = intersection
            self.__isEditing = True
            if self.__rubberBand is not None:
                self.__rubberBand.reset()
            if self.__layer.geometryType() == QGis.Polygon:
                self.__polygonPreview(mapPoint)
            elif self.__layer.geometryType() == QGis.Line:
                self.__linePreview(mapPoint)
            else:
                self.__pointPreview(mapPoint)
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            if self.__layer.geometryType() != QGis.Point:
                self.__rubberBand.setWidth(2)
                self.__rubberBand.setLineStyle(Qt.DotLine)
            else:
                self.__rubberBand.setIcon(4)
                self.__rubberBand.setIconSize(20)
            self.__confDlg = MoveConfirmDialog()
            self.__confDlg.rejected.connect(self.__cancel)
            self.__confDlg.moveButton().clicked.connect(self.__onConfirmMove)
            self.__confDlg.copyButton().clicked.connect(self.__onConfirmCopy)
            self.__confDlg.cancelButton().clicked.connect(self.__onConfirmCancel)
            self.__confDlg.show()
class GeomapfishLocatorFilter(QgsLocatorFilter):

    USER_AGENT = b'Mozilla/5.0 QGIS GeoMapFish Locator Filter'

    def __init__(self, iface: QgisInterface = None):
        super().__init__()
        self.rubber_band = None
        self.settings = Settings()
        self.iface = None
        self.map_canvas = None
        self.current_timer = None
        self.transform = None

        # only get map_canvas on main thread, not when cloning
        if iface is not None:
            self.iface = iface
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transform)

            self.rubber_band = QgsRubberBand(self.map_canvas)
            self.rubber_band.setColor(QColor(255, 255, 50, 200))
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.create_transform()

    def name(self) -> str:
        return self.__class__.__name__

    def clone(self):
        return GeomapfishLocatorFilter()

    def displayName(self) -> str:
        name = self.settings.value("filter_name")
        if name != '':
            return name
        return self.tr('Geomapfish service')

    def prefix(self) -> str:
        return 'gmf'

    def hasConfigWidget(self) -> bool:
        return True

    def openConfigWidget(self, parent=None):
        ConfigDialog(parent).exec_()

    def create_transform(self):
        srv_crs_authid = self.settings.value('geomapfish_crs')
        src_crs = QgsCoordinateReferenceSystem(srv_crs_authid)
        assert src_crs.isValid()
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        self.transform = QgsCoordinateTransform(src_crs, dst_crs,
                                                QgsProject.instance())

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def emit_bad_configuration(self, err=None):
        result = QgsLocatorResult()
        result.filter = self
        result.displayString = self.tr('Locator filter is not configured.')
        result.description = err if err else self.tr(
            'Double-click to configure.')
        result.userData = FilterNotConfigured
        result.icon = QgsApplication.getThemeIcon('mIconWarning.svg')
        self.resultFetched.emit(result)
        return

    @pyqtSlot()
    def clear_results(self):
        if self.rubber_band:
            self.rubber_band.reset(QgsWkbTypes.PointGeometry)
        if self.current_timer is not None:
            self.current_timer.timeout.disconnect(self.clear_results)
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def fetchResults(self, search, context, feedback):
        try:
            self.dbg_info("start GMF locator search...")

            url = self.settings.value('geomapfish_url')

            if url == "":
                self.emit_bad_configuration()
                return

            params = {
                'query': search,
                'limit': str(self.settings.value('total_limit')),
                'partitionlimit': str(self.settings.value('category_limit'))
            }

            if len(search) < 2:
                return

            headers = {b'User-Agent': self.USER_AGENT}
            if self.settings.value('geomapfish_user') != '':
                user = self.settings.value('geomapfish_user')
                password = self.settings.value('geomapfish_pass')
                auth_data = "{}:{}".format(user, password)
                b64 = QByteArray(auth_data.encode()).toBase64()
                headers[QByteArray('Authorization'.encode())] = QByteArray(
                    'Basic '.encode()) + b64

            url = self.url_with_param(url, params)
            self.dbg_info(url)

            nam = NetworkAccessManager()
            feedback.canceled.connect(nam.abort)
            (response, content) = nam.request(url,
                                              headers=headers,
                                              blocking=True)
            self.handle_response(response, content)

        except RequestsExceptionUserAbort:
            pass
        except RequestsException as err:
            self.emit_bad_configuration(str(err))
            self.info(err)
        except Exception as e:
            self.info(str(e), Qgis.Critical)
            #exc_type, exc_obj, exc_traceback = sys.exc_info()
            #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
            #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical)
            #self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical)

        finally:
            self.finished.emit()

    def handle_response(self, response, content):
        try:
            if response.status_code != 200:
                self.info("Error with status code: {}".format(
                    response.status_code))
                return

            data = json.loads(content.decode('utf-8'))
            #self.dbg_info(data)

            features = data['features']
            for f in features:
                json_geom = json.dumps(f['geometry'])
                ogr_geom = ogr.CreateGeometryFromJson(json_geom)
                wkt = ogr_geom.ExportToWkt()
                geometry = QgsGeometry.fromWkt(wkt)
                self.dbg_info('---------')
                self.dbg_info(
                    QgsWkbTypes.geometryDisplayString(geometry.type()))
                self.dbg_info(f.keys())
                self.dbg_info('{} {}'.format(f['properties']['layer_name'],
                                             f['properties']['label']))
                self.dbg_info(f['bbox'])
                self.dbg_info(f['geometry'])
                if geometry is None:
                    continue
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = f['properties']['label']
                if Qgis.QGIS_VERSION_INT >= 30100:
                    result.group = self.beautify_group(
                        f['properties']['layer_name'])
                result.userData = geometry
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            #exc_type, exc_obj, exc_traceback = sys.exc_info()
            #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
            #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical)
            # self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical)

    def triggerResult(self, result):
        self.clear_results()
        if result.userData == FilterNotConfigured:
            self.openConfigWidget()
            if self.iface and hasattr(self.iface, 'invalidateLocatorResults'):
                # from QGIS 3.2 iface has invalidateLocatorResults
                self.iface.invalidateLocatorResults()
            return

        # this should be run in the main thread, i.e. mapCanvas should not be None
        geometry = result.userData
        geometry.transform(self.transform)

        self.rubber_band.reset(geometry.type())
        self.rubber_band.addGeometry(geometry, None)
        rect = geometry.boundingBox()
        rect.scale(1.5)
        self.map_canvas.setExtent(rect)
        self.map_canvas.refresh()

        self.current_timer = QTimer()
        self.current_timer.timeout.connect(self.clear_results)
        self.current_timer.setSingleShot(True)
        self.current_timer.start(5000)

    def beautify_group(self, group) -> str:
        if self.settings.value("remove_leading_digits"):
            group = re.sub('^[0-9]+', '', group)
        if self.settings.value("replace_underscore"):
            group = group.replace("_", " ")
        if self.settings.value("break_camelcase"):
            group = self.break_camelcase(group)
        return group

    def info(self, msg="", level=Qgis.Info):
        QgsMessageLog.logMessage('{} {}'.format(self.__class__.__name__, msg),
                                 'QgsLocatorFilter', level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)

    @staticmethod
    def break_camelcase(identifier) -> str:
        matches = re.finditer(
            '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)',
            identifier)
        return ' '.join([m.group(0) for m in matches])
Example #7
0
class AnnotationManager:
    def __init__(self, iface):
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(os.path.dirname(__file__), 'i18n',
                                   'annotationManager_{}.qm'.format(locale))

        self.translator = None
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

        if qVersion() > '4.3.3':
            QCoreApplication.installTranslator(self.translator)

        self.iface = iface
        self.iface.projectRead.connect(self.projectOpen)

        self.dock = QDockWidget(self.tr('Annotations'))
        self.manager = QWidget()
        toolbar = QToolBar()

        self.annotationList = QListWidget()
        self.annotationList.setSelectionMode(
            QAbstractItemView.ExtendedSelection)
        self.annotationList.itemSelectionChanged.connect(self.selectAnnotation)
        self.annotationList.itemChanged.connect(self.checkItem)
        action_refresh = QAction(
            QIcon(':/plugins/annotationManager/resources/mActionDraw.png'),
            self.tr('Refresh the annotations list'), self.manager)
        action_refresh.triggered.connect(self.refreshAnnotations)
        action_remove = QAction(
            QIcon(
                ':/plugins/annotationManager/resources/mActionRemoveAnnotation.png'
            ), self.tr('Remove the selected annotation'), self.manager)
        action_remove.triggered.connect(self.removeAnnotation)

        viewMenu = QMenu()
        action_showAll = QAction(
            QIcon(':/plugins/annotationManager/resources/mActionShowAll.png'),
            self.tr('Show all annotations'), self.manager)
        action_showAll.triggered.connect(self.showAll)
        action_hideAll = QAction(
            QIcon(':/plugins/annotationManager/resources/mActionHideAll.png'),
            self.tr('Hide all annotations'), self.manager)
        action_hideAll.triggered.connect(self.hideAll)
        action_showAllSelected = QAction(
            QIcon(':/plugins/annotationManager/resources/mActionShowAll.png'),
            self.tr('Show all selected annotations'), self.manager)
        action_showAllSelected.triggered.connect(self.showAllSelected)
        action_hideAllSelected = QAction(
            QIcon(':/plugins/annotationManager/resources/mActionHideAll.png'),
            self.tr('Hide all selected annotations'), self.manager)
        action_hideAllSelected.triggered.connect(self.hideAllSelected)
        viewMenu.addAction(action_showAll)
        viewMenu.addAction(action_hideAll)
        viewMenu.addAction(action_showAllSelected)
        viewMenu.addAction(action_hideAllSelected)
        viewButton = QToolButton()
        viewButton.setIcon(
            QIcon(':/plugins/annotationManager/resources/mActionShowAll.png'))
        viewButton.setPopupMode(2)
        viewButton.setMenu(viewMenu)

        toolbar.addAction(action_refresh)
        toolbar.addAction(action_remove)
        toolbar.addWidget(viewButton)
        toolbar.setIconSize(QSize(16, 16))

        p1_vertical = QVBoxLayout()
        p1_vertical.setContentsMargins(0, 0, 0, 0)
        p1_vertical.addWidget(toolbar)
        p1_vertical.addWidget(self.annotationList)
        self.manager.setLayout(p1_vertical)

        self.dock.setWidget(self.manager)
        self.dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                  | Qt.RightDockWidgetArea)
        self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dock)

        self.rb = QgsRubberBand(self.iface.mapCanvas(),
                                QgsWkbTypes.PolygonGeometry)

        self.project = QgsProject.instance()
        self.annotationManager = self.project.annotationManager()
        self.annotationManager.annotationAdded.connect(self.refreshAnnotations)
        self.annotationManager.annotationRemoved.connect(
            self.refreshAnnotations)

    def checkItem(self, item):
        index = self.annotationList.row(item)
        if item.checkState() == Qt.Checked:
            self.annotationManager.annotations()[index].setVisible(True)
        else:
            self.annotationManager.annotations()[index].setVisible(False)
            if item.isSelected():
                item.setSelected(False)
                self.rb.reset(QgsWkbTypes.PolygonGeometry)

    def selectAnnotation(self):
        self.rb.reset(QgsWkbTypes.PolygonGeometry)
        self.rb.setColor(QColor(0, 0, 255, 128))
        for item in self.annotationList.selectedItems():
            index = self.annotationList.row(item)
            mapTool = QgsMapTool(self.iface.mapCanvas())
            point = mapTool.toCanvasCoordinates(
                self.annotationManager.annotations()[index].mapPosition())
            pt1 = mapTool.toMapCoordinates(
                QPoint(point.x() - 10,
                       point.y() - 10))
            pt2 = mapTool.toMapCoordinates(
                QPoint(point.x() + 10,
                       point.y() + 10))
            rect = QgsRectangle(pt1, pt2)
            poly = QgsGeometry().fromRect(rect)
            self.rb.addGeometry(poly, None)

    def showAll(self):
        count = self.annotationList.count()
        for i in range(count):
            self.annotationList.item(i).setCheckState(Qt.Checked)

    def hideAll(self):
        count = self.annotationList.count()
        for i in range(count):
            self.annotationList.item(i).setCheckState(Qt.Unchecked)

    def showAllSelected(self):
        for item in self.annotationList.selectedItems():
            item.setCheckState(Qt.Checked)

    def hideAllSelected(self):
        for item in self.annotationList.selectedItems():
            item.setCheckState(Qt.Unchecked)

    def unload(self):
        del self.dock

    def tr(self, message):
        return QCoreApplication.translate('AnnotationManager', message)

    def refreshAnnotationTitle(self, annotation=None):
        if annotation is None:
            annotation = self.project.annotationManager().sender()
        item = self.annotationList.item(
            self.annotationManager.annotations().index(annotation))
        title = 'Annotation'
        if isinstance(annotation, QgsTextAnnotation):
            title = annotation.document().toPlainText().split('\n')[0]
            if len(title) > 40:
                title = title[:40] + '(...)'
        item.setText(title)

    def refreshAnnotations(self):
        self.annotationList.clearSelection()
        self.annotationList.clear()
        for annotation in self.annotationManager.annotations():
            item = QListWidgetItem()
            annotation.appearanceChanged.connect(self.refreshAnnotationTitle)
            if annotation.isVisible():
                item.setCheckState(Qt.Checked)
            else:
                item.setCheckState(Qt.Unchecked)
            item.setFlags(item.flags())
            self.annotationList.addItem(item)
            self.refreshAnnotationTitle(annotation)

    def removeAnnotation(self):
        if len(self.annotationList.selectedItems()) > 0:
            self.annotationManager.annotationRemoved.disconnect()
            trash = []
            for item in self.annotationList.selectedItems():
                index = self.annotationList.row(item)
                trash.append(self.annotationManager.annotations()[index])
            while trash:
                self.annotationManager.removeAnnotation(trash.pop())
            self.refreshAnnotations()
            self.annotationManager.annotationRemoved.connect(
                self.refreshAnnotations)

    def projectOpen(self):
        self.refreshAnnotations()

    def initGui(self):
        self.refreshAnnotations()
class SelectFeatureMapTool(QgsMapTool):

    def __init__(self, plugin):

        QgsMapTool.__init__(self, plugin.map_canvas)

        self.plugin = plugin
        self.doubleclick = False
        self.__pluginUnloaded = False

        settings = QSettings()
        qgsLineWidth = 2  # use fixed width
        qgsLineRed = settings.value("/qgis/digitizing/line_color_red", 255, type=int)
        qgsLineGreen = settings.value("/qgis/digitizing/line_color_green", 0, type=int)
        qgsLineBlue = settings.value("/qgis/digitizing/line_color_blue", 0, type=int)

        self.rubBandPol = QgsRubberBand(plugin.map_canvas, QGis.Line)
        self.rubBandPol.setColor(QColor(qgsLineRed, qgsLineGreen, qgsLineBlue))
        self.rubBandPol.setWidth(qgsLineWidth)

    def activate(self):

        super(SelectFeatureMapTool, self).activate()

        self.plugin.iface.mainWindow().statusBar().showMessage(self.tr("Click on a parcel!"))
        self.currentDialog = None
        self.dialogPosition = None
        self.currentPossessionType = "INDIVIDUAL"
#        self.plugin.mapCanvas.setCursor(QCursor(Qt.ArrowCursor))
        
    def canvasDoubleClickEvent(self, event):
        
        self.doubleclick = True
            
    def canvasReleaseEvent(self, event):

        if self.doubleclick:
            self.doubleclick = False
            return

        if event.button() <> Qt.LeftButton:
            return

        layer = self.plugin.getLayerByTableName('ca_parcel')

        if not layer:
            return
            
        # find out map coordinates from mouse click
        mapPoint = self.toLayerCoordinates(layer, event.pos())
        tolerance = self.plugin.getTolerance(layer)
        area = QgsRectangle(mapPoint.x() - tolerance, mapPoint.y() - tolerance, mapPoint.x() + tolerance, mapPoint.y() + tolerance)

        request = QgsFeatureRequest()
        request.setFilterRect(area).setFlags(QgsFeatureRequest.ExactIntersect)
        request.setSubsetOfAttributes([0])

        result = False

        for feature in layer.getFeatures(request):

            self.rubBandPol.reset(True)
            self.rubBandPol.addGeometry(feature.geometry(), layer)

            parcel_no = int(feature[0])

            possession_type = self.__possessionType(parcel_no)

            if not self.currentDialog or possession_type != self.currentPossessionType:

                if self.currentDialog:
                    self.dialogPosition = self.currentDialog.pos()
                    self.currentDialog.reject()

                # if possession_type == "INDIVIDUAL":
                #     self.currentDialog = PossessionDetailsDialog(self.plugin, self.plugin.iface.mainWindow())
                # else:
                #     self.currentDialog = CooperativePossessionDetailsDialog(self.plugin, self.plugin.iface.mainWindow())

                self.currentPossessionType = possession_type
                self.connect(self.currentDialog, SIGNAL("rejected()"), self.__dialogClosed)

            if self.dialogPosition:
                self.currentDialog.move(self.dialogPosition)

            self.currentDialog.setParcelNo(parcel_no)

            if self.currentDialog.isHidden():
                self.currentDialog.show()

            result = True
            break
                
        if not result:
            self.__resetTool()

    def __resetTool(self):

        if self.currentDialog:
            self.currentDialog.hide()
            self.dialogPosition = self.currentDialog.pos()

        self.rubBandPol.reset(True)

    def deactivate(self):

        if self.__pluginUnloaded:
            return

        self.plugin.iface.mainWindow().statusBar().showMessage("")
        self.doubleclick = False
        self.rubBandPol.reset(True)
        if self.currentDialog:
            self.currentDialog.reject()

        super(SelectFeatureMapTool, self).deactivate()

    def __dialogClosed(self):

        self.currentDialog = None
        #self.dialogPosition = None

    def setPluginUnloaded(self):

        self.__pluginUnloaded = True
class AdvancedIntersectionMapTool(QgsMapTool):
    def __init__(self, iface):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        QgsMapTool.__init__(self, self.mapCanvas)
        self.settings = MySettings()
        self.rubber = QgsRubberBand(self.mapCanvas)

        self.tolerance = self.settings.value("selectTolerance")
        units = self.settings.value("selectUnits")
        if units == "pixels":
            self.tolerance *= self.mapCanvas.mapUnitsPerPixel()

    def activate(self):
        QgsMapTool.activate(self)
        self.rubber.setWidth(self.settings.value("rubberWidth"))
        self.rubber.setColor(self.settings.value("rubberColor"))
        line_layer = MemoryLayers(self.iface).line_layer()
        # unset this tool if the layer is removed
        line_layer.layerDeleted.connect(self.unsetMapTool)
        self.layerId = line_layer.id()
        # create snapper for this layer
        self.snapLayer = QgsSnapper.SnapLayer()
        self.snapLayer.mLayer = line_layer
        self.snapLayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment
        self.snapLayer.mTolerance = self.settings.value("selectTolerance")
        if self.settings.value("selectUnits") == "map":
            self.snapLayer.mUnitType = QgsTolerance.MapUnits
        else:
            self.snapLayer.mUnitType = QgsTolerance.Pixels

    def unsetMapTool(self):
        self.mapCanvas.unsetMapTool(self)

    def deactivate(self):
        self.rubber.reset()
        line_layer = QgsMapLayerRegistry.instance().mapLayer(self.layerId)
        if line_layer is not None:
            line_layer.layerDeleted.disconnect(self.unsetMapTool)
        QgsMapTool.deactivate(self)

    def canvasMoveEvent(self, mouseEvent):
        # put the observations within tolerance in the rubber band
        self.rubber.reset()
        for f in self.getFeatures(mouseEvent.pos()):
            self.rubber.addGeometry(f.geometry(), None)

    def canvasPressEvent(self, mouseEvent):
        pos = mouseEvent.pos()
        observations = self.getFeatures(pos)
        point = self.toMapCoordinates(pos)
        self.doIntersection(point, observations)

    def getFeatures(self, pixPoint):
        snapper = QgsSnapper(self.mapCanvas.mapRenderer())
        snapper.setSnapLayers([self.snapLayer])
        snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances)
        ok, snappingResults = snapper.snapPoint(pixPoint, [])
        # output snapped features
        features = []
        alreadyGot = []
        for result in snappingResults:
            featureId = result.snappedAtGeometry
            f = QgsFeature()
            if featureId not in alreadyGot:
                if result.layer.getFeatures(QgsFeatureRequest().setFilterFid(featureId)).nextFeature(f) is not False:
                    features.append(QgsFeature(f))
                    alreadyGot.append(featureId)
        return features

    def doIntersection(self, initPoint, observations):
        nObs = len(observations)
        if nObs < 2:
            return
        self.rubber.reset()
        self.dlg = IntersectionDialog(self.iface, observations, initPoint)
        if not self.dlg.exec_() or self.dlg.solution is None:
            return
        intersectedPoint = self.dlg.solution
        self.saveIntersectionResult(self.dlg.report, intersectedPoint)
        self.saveDimension(intersectedPoint, self.dlg.observations)

    def saveIntersectionResult(self, report, intersectedPoint):
        # save the intersection result (point) and its report
        # check first
        while True:
            if not self.settings.value("advancedIntersectionWritePoint"):
                break  # if we do not place any point, skip
            layerid = self.settings.value("advancedIntersectionLayer")
            message = QCoreApplication.translate("IntersectIt",
                                                 "To place the intersection solution,"
                                                 " you must select a layer in the settings.")
            status, intLayer = self.checkLayerExists(layerid, message)
            if status == 2:
                continue
            if status == 3:
                return
            if self.settings.value("advancedIntersectionWriteReport"):
                reportField = self.settings.value("reportField")
                message = QCoreApplication.translate("IntersectIt",
                                                     "To save the intersection report, please select a field for it.")
                status = self.checkFieldExists(intLayer, reportField, message)
                if status == 2:
                    continue
                if status == 3:
                    return
            break
        # save the intersection results
        if self.settings.value("advancedIntersectionWritePoint"):
            f = QgsFeature()
            f.setGeometry(QgsGeometry().fromPoint(intersectedPoint))
            if self.settings.value("advancedIntersectionWriteReport"):
                irep = intLayer.dataProvider().fieldNameIndex(reportField)
                f.addAttribute(irep, report)
            intLayer.dataProvider().addFeatures([f])
            intLayer.updateExtents()
            self.mapCanvas.refresh()

    def saveDimension(self, intersectedPoint, observations):
        # check that dimension layer and fields have been set correctly
        if not self.settings.value("dimensionDistanceWrite") and not self.settings.value("dimensionOrientationWrite"):
            return  # if we do not place any dimension, skip
        obsTypes = ("Distance", "Orientation")
        recheck = True
        while recheck:
            # settings might change during checking,
            # so recheck both observation types whenever the settings dialog is shown
            recheck = False
            for obsType in obsTypes:
                while True:
                    if not self.settings.value("dimension"+obsType+"Write"):
                        break
                    # check layer
                    layerId = self.settings.value("dimension"+obsType+"Layer")
                    message = QCoreApplication.translate("IntersectIt",
                                                         "To place dimensions, "
                                                         "you must define a layer in the settings.")
                    status, dimLayer = self.checkLayerExists(layerId, message)
                    if status == 2:
                        recheck = True
                        continue
                    if status == 3:
                        return
                    # check fields
                    if self.settings.value("dimension"+obsType+"ObservationWrite"):
                        obsField = self.settings.value("dimension"+obsType+"ObservationField")
                        message = QCoreApplication.translate("IntersectIt",
                                                             "To save the observation in the layer,"
                                                             " please select a field for it.")
                        status = self.checkFieldExists(dimLayer, obsField, message)
                        if status == 2:
                            recheck = True
                            continue
                        if status == 3:
                            return
                    if self.settings.value("dimension"+obsType+"PrecisionWrite"):
                        precisionField = self.settings.value("dimension"+obsType+"PrecisionField")
                        message = QCoreApplication.translate("IntersectIt",
                                                             "To save the precision of observation,"
                                                             " please select a field for it.")
                        status = self.checkFieldExists(dimLayer, precisionField, message)
                        if status == 2:
                            recheck = True
                            continue
                        if status == 3:
                            return
                    break
        # save the intersection results
        for obsType in obsTypes:
            if self.settings.value("dimension"+obsType+"Write"):
                layerid = self.settings.value("dimension"+obsType+"Layer")
                layer = QgsMapLayerRegistry.instance().mapLayer(layerid)
                if layer is None:
                    continue
                initFields = layer.dataProvider().fields()
                features = []
                for obs in observations:
                    if obs["type"] != obsType.lower():
                        continue
                    f = QgsFeature()
                    f.setFields(initFields)
                    f.initAttributes(initFields.size())
                    if self.settings.value("dimension"+obsType+"ObservationWrite"):
                        f[self.settings.value("dimension"+obsType+"ObservationField")] = obs["observation"]
                    if self.settings.value("dimension"+obsType+"PrecisionWrite"):
                        f[self.settings.value("dimension"+obsType+"PrecisionField")] = obs["precision"]
                    p0 = QgsPoint(obs["x"], obs["y"])
                    p1 = intersectedPoint
                    if obs["type"] == "distance":
                        geom = Arc(p0, p1).geometry()
                    elif obs["type"] == "orientation":
                        geom = QgsGeometry().fromPolyline([p0, p1])
                    else:
                        raise NameError("Invalid observation %s" % obs["type"])
                    f.setGeometry(geom)
                    features.append(QgsFeature(f))
                if not layer.dataProvider().addFeatures(features):
                    self.iface.messageBar().pushMessage("Could not commit %s observations" % obsType,
                                                        QgsMessageBar.CRITICAL)
                layer.updateExtents()
        self.mapCanvas.refresh()

    def checkLayerExists(self, layerid, message):
        # returns:
        # 1: layer exists
        # 2: does not exist, settings has been open, so loop once more (i.e. continue)
        # 3: does not exist, settings not edited, so cancel
        layer = QgsMapLayerRegistry.instance().mapLayer(layerid)
        if layer is not None:
            return 1, layer

        reply = QMessageBox.question(self.iface.mainWindow(), "Intersect It",
                                     message + " Would you like to open settings?", QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if MySettingsDialog().exec_():
                return 2
        return 3

    def checkFieldExists(self, layer, field, message):
        # returns:
        # 1: field exists
        # 2: does not exist, settings has been open, so loop once more (i.e. continue)
        # 3: does not exist, settings not edited, so cancel
        if layer.dataProvider().fieldNameIndex(field) != -1:
            return 1

        reply = QMessageBox.question(self.iface.mainWindow(), "Intersect It",
                                     message + " Would you like to open settings?", QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if MySettingsDialog().exec_():
                return 2
        return 3
Example #10
0
class ReverseGeocodeTool(QgsMapTool):
    def __init__(self, iface, settings):
        self.canvas = iface.mapCanvas()
        QgsMapTool.__init__(self, self.canvas)
        self.iface = iface
        self.settings = settings
        self.reverseGeoCodeDialog = ReverseGeocodeDialog(
            self, self.iface, self.iface.mainWindow())
        self.iface.addDockWidget(Qt.TopDockWidgetArea,
                                 self.reverseGeoCodeDialog)
        self.reverseGeoCodeDialog.hide()
        self.epsg4326 = QgsCoordinateReferenceSystem('EPSG:4326')
        self.marker = None

        # Set up a polygon/line rubber band
        self.rubber = QgsRubberBand(self.canvas)
        self.rubber.setColor(QColor(255, 70, 0, 200))
        self.rubber.setWidth(5)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def activate(self):
        '''When activated set the cursor to a crosshair.'''
        self.canvas.setCursor(Qt.CrossCursor)
        self.show()

    def unload(self):
        self.iface.removeDockWidget(self.reverseGeoCodeDialog)
        self.reverseGeoCodeDialog = None
        if self.rubber:
            self.canvas.scene().removeItem(self.rubber)
            del self.rubber
        self.removeMarker()

    def addMarker(self, lat, lon):
        if self.marker:
            self.removeMarker()
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(self.epsg4326, canvasCrs,
                                           QgsProject.instance())
        center = transform.transform(lon, lat)
        self.marker = QgsVertexMarker(self.canvas)
        self.marker.setCenter(center)
        self.marker.setColor(QColor(255, 70, 0))
        self.marker.setIconSize(15)
        self.marker.setIconType(QgsVertexMarker.ICON_X)
        self.marker.setPenWidth(3)
        self.marker.show()

    def removeMarker(self):
        if self.marker:
            self.canvas.scene().removeItem(self.marker)
            self.marker = None

    def clearSelection(self):
        self.removeMarker()
        self.rubber.reset()

    def transform_geom(self, geometry):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        geom = QgsGeometry(geometry)
        geom.transform(
            QgsCoordinateTransform(self.epsg4326, canvasCrs,
                                   QgsProject.instance()))
        return geom

    def show(self):
        self.reverseGeoCodeDialog.show()

    def request(self, url):
        fetcher = QgsNetworkContentFetcher()
        fetcher.fetchContent(QUrl(url))
        evloop = QEventLoop()
        fetcher.finished.connect(evloop.quit)
        evloop.exec_(QEventLoop.ExcludeUserInputEvents)
        fetcher.finished.disconnect(evloop.quit)
        return fetcher.contentAsString()

    def canvasReleaseEvent(self, event):
        # Make sure the point is transfored to 4326
        self.clearSelection()
        pt = self.toMapCoordinates(event.pos())
        canvasCRS = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(canvasCRS, self.epsg4326,
                                           QgsProject.instance())
        pt = transform.transform(pt.x(), pt.y())
        url = '{}?format=json&lat={:f}&lon={:f}&zoom={:d}&addressdetails=0&polygon_text=1'.format(
            self.settings.reverseURL(), pt.y(), pt.x(),
            self.settings.levelOfDetail)
        # print( url )
        jsondata = self.request(url)

        try:
            jd = json.loads(jsondata)
            try:
                display_name = jd['display_name']
                self.setText(display_name)
            except KeyError:
                self.setText("[Could not find address]")
            try:
                wkt = jd['geotext']
                geometry = QgsGeometry.fromWkt(wkt)
                if geometry.wkbType() == QgsWkbTypes.Point:
                    pt = geometry.asPoint()
                    lon = pt.x()
                    lat = pt.y()
                    self.addMarker(lat, lon)
                else:
                    geometry = self.transform_geom(geometry)
                    self.rubber.addGeometry(geometry, None)
                    self.rubber.show()
            except KeyError:
                try:
                    lon = float(jd['lon'])
                    lat = float(jd['lat'])
                    self.addMarker(lat, lon)
                except:
                    pass
        except Exception:
            self.setText("Error: " + jsondata)

        if not self.reverseGeoCodeDialog.isVisible():
            self.show()

    def setText(self, text):
        self.reverseGeoCodeDialog.addressLineEdit.setText(text)
Example #11
0
class MoveTool(QgsMapToolAdvancedDigitizing):
    """
    Map tool class to move or copy an object
    """
    def __init__(self, iface):
        """
        Constructor
        :param iface: interface
        """
        QgsMapToolAdvancedDigitizing.__init__(self, iface.mapCanvas(),
                                              iface.cadDockWidget())
        self.__iface = iface
        self.icon_path = ':/plugins/VDLTools/icons/move_icon.png'
        self.text = QCoreApplication.translate("VDLTools",
                                               "Move/Copy a feature")
        self.setCursor(Qt.ArrowCursor)
        self.__isEditing = False
        self.__findVertex = False
        self.__onMove = False
        self.__layer = None
        self.__confDlg = None
        self.__lastFeatureId = None
        self.__selectedFeature = None
        self.__rubberBand = None
        self.__rubberSnap = None
        self.__newFeature = None
        self.__selectedVertex = None

    def activate(self):
        """
        When the action is selected
        """
        QgsMapToolAdvancedDigitizing.activate(self)
        if self.__layer.geometryType() == QGis.Point:
            self.setMode(self.CaptureLine)
        else:
            self.setMode(self.CaptureNone)

    def deactivate(self):
        """
        When the action is deselected
        """
        self.__cancel()
        QgsMapToolAdvancedDigitizing.deactivate(self)

    def toolName(self):
        """
        To get the tool name
        :return: tool name
        """
        return QCoreApplication.translate("VDLTools", "Move/Copy")

    def startEditing(self):
        """
        To set the action as enable, as the layer is editable
        """
        self.action().setEnabled(True)
        Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing)
        self.__layer.editingStopped.connect(self.stopEditing)

    def stopEditing(self):
        """
        To set the action as disable, as the layer is not editable
        """
        self.action().setEnabled(False)
        Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing)
        self.__layer.editingStarted.connect(self.startEditing)
        if self.canvas().mapTool() == self:
            self.__iface.actionPan().trigger()

    def setTool(self):
        """
        To set the current tool as this one
        """
        self.canvas().setMapTool(self)

    def __cancel(self):
        """
        To cancel used variables
        """
        if self.__rubberBand is not None:
            self.canvas().scene().removeItem(self.__rubberBand)
            self.__rubberBand.reset()
            self.__rubberBand = None
        if self.__rubberSnap is not None:
            self.canvas().scene().removeItem(self.__rubberSnap)
            self.__rubberSnap.reset()
            self.__rubberSnap = None
        self.__isEditing = False
        self.__findVertex = False
        self.__onMove = False
        self.__lastFeatureId = None
        self.__selectedFeature = None
        self.__confDlg = None
        self.__newFeature = None
        self.__selectedVertex = None
        self.__layer.removeSelection()
        if self.__layer.geometryType() == QGis.Point:
            self.setMode(self.CaptureLine)
        else:
            self.setMode(self.CaptureNone)

    def __removeLayer(self):
        """
        To remove the current working layer
        """
        if self.__layer is not None:
            if self.__layer.isEditable():
                Signal.safelyDisconnect(self.__layer.editingStopped,
                                        self.stopEditing)
            else:
                Signal.safelyDisconnect(self.__layer.editingStarted,
                                        self.startEditing)
            self.__layer = None

    def setEnable(self, layer):
        """
        To check if we can enable the action for the selected layer
        :param layer: selected layer
        """
        if layer is not None and layer.type() == QgsMapLayer.VectorLayer:
            if layer == self.__layer:
                return

            if self.__layer is not None:
                if self.__layer.isEditable():
                    Signal.safelyDisconnect(self.__layer.editingStopped,
                                            self.stopEditing)
                else:
                    Signal.safelyDisconnect(self.__layer.editingStarted,
                                            self.startEditing)
            self.__layer = layer

            if self.__layer.geometryType() == QGis.Point:
                self.setMode(self.CaptureLine)
            else:
                self.setMode(self.CaptureNone)

            if self.__layer.isEditable():
                self.action().setEnabled(True)
                self.__layer.editingStopped.connect(self.stopEditing)
            else:
                self.action().setEnabled(False)
                self.__layer.editingStarted.connect(self.startEditing)
                if self.canvas().mapTool() == self:
                    self.__iface.actionPan().trigger()
            return
        self.action().setEnabled(False)
        if self.canvas().mapTool() == self:
            self.__iface.actionPan().trigger()
        self.__removeLayer()

    def __pointPreview(self, point):
        """
        To create a point geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        point_v2 = GeometryV2.asPointV2(self.__selectedFeature.geometry(),
                                        self.__iface)
        self.__newFeature = QgsPointV2(point.x(), point.y())
        self.__newFeature.addZValue(point_v2.z())
        self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Point)
        self.__rubberBand.setToGeometry(QgsGeometry(self.__newFeature.clone()),
                                        None)

    def __linePreview(self, point):
        """
        To create a line geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        line_v2, curved = GeometryV2.asLineV2(
            self.__selectedFeature.geometry(), self.__iface)
        vertex = QgsPointV2()
        line_v2.pointAt(self.__selectedVertex, vertex)
        self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line)
        dx = vertex.x() - point.x()
        dy = vertex.y() - point.y()
        if isinstance(curved, (list, tuple)):
            self.__newFeature = QgsCompoundCurveV2()
            for pos in range(line_v2.nCurves()):
                curve_v2 = self.__newCurve(curved[pos], line_v2.curveAt(pos),
                                           dx, dy)
                self.__newFeature.addCurve(curve_v2)
                if pos == 0:
                    self.__rubberBand.setToGeometry(
                        QgsGeometry(curve_v2.curveToLine()), None)
                else:
                    self.__rubberBand.addGeometry(
                        QgsGeometry(curve_v2.curveToLine()), None)
        else:
            self.__newFeature = self.__newCurve(curved, line_v2, dx, dy)
            self.__rubberBand.setToGeometry(
                QgsGeometry(self.__newFeature.curveToLine()), None)

    @staticmethod
    def __newCurve(curved, line_v2, dx, dy):
        """
        To create a new moved line
        :param curved: if the line is curved
        :param line_v2: the original line
        :param dx: x translation
        :param dy: y translation
        :return: the new line
        """
        if curved:
            newCurve = QgsCircularStringV2()
        else:
            newCurve = QgsLineStringV2()
        points = []
        for pos in range(line_v2.numPoints()):
            x = line_v2.pointN(pos).x() - dx
            y = line_v2.pointN(pos).y() - dy
            pt = QgsPointV2(x, y)
            pt.addZValue(line_v2.pointN(pos).z())
            points.append(pt)
        newCurve.setPoints(points)
        return newCurve

    def __polygonPreview(self, point):
        """
        To create a polygon geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        polygon_v2, curved = GeometryV2.asPolygonV2(
            self.__selectedFeature.geometry(), self.__iface)
        vertex = polygon_v2.vertexAt(
            GeometryV2.polygonVertexId(polygon_v2, self.__selectedVertex))
        dx = vertex.x() - point.x()
        dy = vertex.y() - point.y()
        self.__newFeature = QgsCurvePolygonV2()
        self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line)
        line_v2 = self.__newCurve(curved[0], polygon_v2.exteriorRing(), dx, dy)
        self.__newFeature.setExteriorRing(line_v2)
        self.__rubberBand.setToGeometry(QgsGeometry(line_v2.curveToLine()),
                                        None)
        for num in range(polygon_v2.numInteriorRings()):
            line_v2 = self.__newCurve(curved[num + 1],
                                      polygon_v2.interiorRing(num), dx, dy)
            self.__newFeature.addInteriorRing(line_v2)
            self.__rubberBand.addGeometry(QgsGeometry(line_v2.curveToLine()),
                                          None)

    def __onConfirmCancel(self):
        """
        When the Cancel button in Move Confirm Dialog is pushed
        """
        self.__confDlg.reject()

    def __onConfirmMove(self):
        """
        When the Move button in Move Confirm Dialog is pushed
        """
        geometry = QgsGeometry(self.__newFeature)
        if not geometry.isGeosValid():
            self.__iface.messageBar().pushMessage(QCoreApplication.translate(
                "VDLTools", "Geos geometry problem"),
                                                  level=QgsMessageBar.CRITICAL,
                                                  duration=0)
        self.__layer.changeGeometry(self.__selectedFeature.id(), geometry)
        self.__confDlg.accept()
        self.__cancel()

    def __onConfirmCopy(self):
        """
        When the Copy button in Move Confirm Dialog is pushed
        """
        geometry = QgsGeometry(self.__newFeature)
        if not geometry.isGeosValid():
            self.__iface.messageBar().pushMessage(QCoreApplication.translate(
                "VDLTools", "Geos geometry problem"),
                                                  level=QgsMessageBar.CRITICAL,
                                                  duration=0)
        feature = QgsFeature(self.__layer.pendingFields())
        feature.setGeometry(geometry)
        primaryKey = QgsDataSourceURI(self.__layer.source()).keyColumn()
        for field in self.__selectedFeature.fields():
            if field.name() != primaryKey:
                feature.setAttribute(
                    field.name(),
                    self.__selectedFeature.attribute(field.name()))
        if len(self.__selectedFeature.fields()) > 0 and self.__layer.editFormConfig().suppress() != \
                QgsEditFormConfig.SuppressOn:
            self.__iface.openFeatureForm(self.__layer, feature)
        else:
            self.__layer.addFeature(feature)
        self.__confDlg.accept()
        self.__cancel()

    def keyReleaseEvent(self, event):
        """
        When keyboard is pressed
        :param event: keyboard event
        """
        if event.key() == Qt.Key_Escape:
            self.__cancel()

    def cadCanvasMoveEvent(self, event):
        """
        When the mouse is moved
        :param event: mouse event
        """

        if type(event) == QMoveEvent:
            map_point = self.toMapCoordinates(event.pos())
        else:
            map_point = event.mapPoint()

        if not self.__isEditing and not self.__findVertex and not self.__onMove:
            laySettings = QgsSnappingUtils.LayerConfig(self.__layer,
                                                       QgsPointLocator.All, 10,
                                                       QgsTolerance.Pixels)
            f_l = Finder.findClosestFeatureAt(map_point, self.canvas(),
                                              [laySettings])
            if f_l is not None and self.__lastFeatureId != f_l[0].id():
                self.__lastFeatureId = f_l[0].id()
                self.__layer.setSelectedFeatures([f_l[0].id()])
            if f_l is None:
                self.__layer.removeSelection()
                self.__lastFeatureId = None
        elif self.__findVertex:
            if self.__rubberBand is not None:
                self.__rubberBand.reset()
            closest = self.__selectedFeature.geometry().closestVertex(
                map_point)
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            self.__rubberBand.setIcon(4)
            self.__rubberBand.setIconSize(20)
            self.__rubberBand.setToGeometry(
                QgsGeometry().fromPoint(closest[0]), None)
        elif self.__onMove:
            if self.__rubberBand is not None:
                self.__rubberBand.reset()
            if self.__layer.geometryType() == QGis.Polygon:
                self.__polygonPreview(map_point)
            elif self.__layer.geometryType() == QGis.Line:
                self.__linePreview(map_point)
            else:
                self.__pointPreview(map_point)
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            self.__rubberBand.setWidth(2)
            if self.__layer.geometryType() != QGis.Point:
                self.__rubberBand.setLineStyle(Qt.DotLine)
            else:
                self.__rubberBand.setIcon(4)
                self.__rubberBand.setIconSize(8)
            if self.__rubberSnap is not None:
                self.__rubberSnap.reset()
            else:
                self.__rubberSnap = QgsRubberBand(self.canvas(), QGis.Point)
            self.__rubberSnap.setColor(color)
            self.__rubberSnap.setWidth(2)
            self.__rubberSnap.setIconSize(20)
            match = Finder.snap(map_point, self.canvas())
            if match.hasVertex() or match.hasEdge():
                point = match.point()
                if match.hasVertex():
                    if match.layer():
                        self.__rubberSnap.setIcon(4)
                    else:
                        self.__rubberSnap.setIcon(1)
                if match.hasEdge():
                    intersection = Finder.snapCurvedIntersections(
                        point, self.canvas(), self)
                    if intersection is not None:
                        self.__rubberSnap.setIcon(1)
                        point = intersection
                    else:
                        self.__rubberSnap.setIcon(3)
                self.__rubberSnap.setToGeometry(QgsGeometry().fromPoint(point),
                                                None)

    def cadCanvasReleaseEvent(self, event):
        """
        When the mouse is clicked
        :param event: mouse event
        """
        if not self.__isEditing and not self.__findVertex and not self.__onMove:
            found_features = self.__layer.selectedFeatures()
            if len(found_features) > 0:
                if len(found_features) > 1:
                    self.__iface.messageBar().pushMessage(
                        QCoreApplication.translate("VDLTools",
                                                   "One feature at a time"),
                        level=QgsMessageBar.INFO)
                    return
                self.__selectedFeature = found_features[0]
                if self.__layer.geometryType() != QGis.Point:
                    self.__iface.messageBar().pushMessage(
                        QCoreApplication.translate(
                            "VDLTools",
                            "Select vertex for moving (ESC to undo)"),
                        level=QgsMessageBar.INFO,
                        duration=3)
                    self.__findVertex = True
                    self.setMode(self.CaptureLine)
                    self.__rubberBand = QgsRubberBand(self.canvas(),
                                                      QGis.Point)
                else:
                    self.setMode(self.CaptureNone)
                    self.__onMove = True
        elif self.__findVertex:
            self.__findVertex = False
            self.setMode(self.CaptureNone)
            closest = self.__selectedFeature.geometry().closestVertex(
                event.mapPoint())
            self.__selectedVertex = closest[1]
            self.__onMove = True
        elif self.__onMove:
            self.__onMove = False
            mapPoint = event.mapPoint()
            match = Finder.snap(event.mapPoint(), self.canvas())
            if match.hasVertex() or match.hasEdge():
                mapPoint = match.point()
                if match.hasEdge():
                    intersection = Finder.snapCurvedIntersections(
                        mapPoint, self.canvas(), self)
                    if intersection is not None:
                        mapPoint = intersection
            self.__isEditing = True
            if self.__rubberBand is not None:
                self.__rubberBand.reset()
            if self.__layer.geometryType() == QGis.Polygon:
                self.__polygonPreview(mapPoint)
            elif self.__layer.geometryType() == QGis.Line:
                self.__linePreview(mapPoint)
            else:
                self.__pointPreview(mapPoint)
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            if self.__layer.geometryType() != QGis.Point:
                self.__rubberBand.setWidth(2)
                self.__rubberBand.setLineStyle(Qt.DotLine)
            else:
                self.__rubberBand.setIcon(4)
                self.__rubberBand.setIconSize(20)
            self.__confDlg = MoveConfirmDialog()
            self.__confDlg.rejected.connect(self.__cancel)
            self.__confDlg.moveButton().clicked.connect(self.__onConfirmMove)
            self.__confDlg.copyButton().clicked.connect(self.__onConfirmCopy)
            self.__confDlg.cancelButton().clicked.connect(
                self.__onConfirmCancel)
            self.__confDlg.show()
Example #12
0
class EditTool(MapTool):
    """
        Inspection tool which copies the feature to a new layer
        and copies selected data from the underlying feature.
    """

    finished = pyqtSignal(QgsVectorLayer, QgsFeature)

    def __init__(self, canvas, layers, snapradius=2):
        MapTool.__init__(self, canvas, layers)
        self.canvas = canvas
        self.radius = snapradius

        self.band = QgsRubberBand(self.canvas)
        self.band.setColor(QColor.fromRgb(224, 162, 16))
        self.band.setWidth(3)

        self.cursor = QCursor(
            QPixmap([
                "16 16 3 1", "      c None", ".     c #FF0000",
                "+     c #FFFFFF", "                ", "       +.+      ",
                "      ++.++     ", "     +.....+    ", "    +.     .+   ",
                "   +.   .   .+  ", "  +.    .    .+ ", " ++.    .    .++",
                " ... ...+... ...", " ++.    .    .++", "  +.    .    .+ ",
                "   +.   .   .+  ", "   ++.     .+   ", "    ++.....+    ",
                "      ++.++     ", "       +.+      "
            ]))

    def getFeatures(self, point):
        searchRadius = (QgsTolerance.toleranceInMapUnits(
            self.radius, self.layers[0], self.canvas.mapRenderer(),
            QgsTolerance.Pixels))
        point = self.toMapCoordinates(point)

        rect = QgsRectangle()
        rect.setXMinimum(point.x() - searchRadius)
        rect.setXMaximum(point.x() + searchRadius)
        rect.setYMinimum(point.y() - searchRadius)
        rect.setYMaximum(point.y() + searchRadius)

        rq = QgsFeatureRequest().setFilterRect(rect)

        self.band.reset()
        for layer in self.layers:
            rq = QgsFeatureRequest().setFilterRect(rect)
            for feature in layer.getFeatures(rq):
                if feature.isValid():
                    yield feature, layer

    def canvasMoveEvent(self, event):
        for feature, _ in self.getFeatures(point=event.pos()):
            self.band.addGeometry(feature.geometry(), None)

    def canvasReleaseEvent(self, event):
        features = list(self.getFeatures(point=event.pos()))

        if len(features) == 1:
            feature = features[0]
            self.finished.emit(feature[1], feature[0])
        elif len(features) > 0:
            listUi = ListFeaturesForm()
            listUi.loadFeatureList(features)
            listUi.openFeatureForm.connect(self.finished)
            listUi.exec_()

    def activate(self):
        """
        Set the tool as the active tool in the canvas. 

        @note: Should be moved out into qmap.py 
               and just expose a cursor to be used
        """
        self.canvas.setCursor(self.cursor)

    def deactivate(self):
        """
        Deactive the tool.
        """
        pass

    def isZoomTool(self):
        return False

    def isTransient(self):
        return False

    def isEditTool(self):
        return True
Example #13
0
class SoLocatorFilter(QgsLocatorFilter):

    HEADERS = {b'User-Agent': b'Mozilla/5.0 QGIS SoLocator Filter'}

    message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget)

    def __init__(self, iface: QgisInterface = None):
        """"
        :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise
        """
        super().__init__()

        self.iface = iface
        self.settings = Settings()

        #  following properties will only be used in main thread
        self.rubber_band = None
        self.map_canvas: QgsMapCanvas = None
        self.transform_ch = None
        self.current_timer = None
        self.result_found = False
        self.nam_fetch_feature = None

        if iface is not None:
            # happens only in main thread
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transforms)

            self.rubber_band = QgsRubberBand(self.map_canvas,
                                             QgsWkbTypes.PolygonGeometry)
            self.rubber_band.setColor(QColor(255, 50, 50, 200))
            self.rubber_band.setFillColor(QColor(255, 255, 50, 160))
            self.rubber_band.setBrushStyle(Qt.SolidPattern)
            self.rubber_band.setLineStyle(Qt.SolidLine)
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.create_transforms()

    def name(self):
        return 'SoLocator'

    def clone(self):
        return SoLocatorFilter()

    def priority(self):
        return QgsLocatorFilter.Highest

    def displayName(self):
        return 'SoLocator'

    def prefix(self):
        return 'sol'

    def clearPreviousResults(self):
        if self.rubber_band:
            self.rubber_band.reset(QgsWkbTypes.PointGeometry)

        if self.current_timer is not None:
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def hasConfigWidget(self):
        return True

    def openConfigWidget(self, parent=None):
        dlg = ConfigDialog(parent)
        dlg.exec_()

    def create_transforms(self):
        # this should happen in the main thread
        src_crs_ch = QgsCoordinateReferenceSystem('EPSG:2056')
        assert src_crs_ch.isValid()
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        self.transform_ch = QgsCoordinateTransform(src_crs_ch, dst_crs,
                                                   QgsProject.instance())

    def enabled_dataproducts(self):
        categories = DATA_PRODUCTS.keys()
        skipped = self.settings.value('skipped_dataproducts')
        return ','.join(list(filter(lambda id: id not in skipped, categories)))

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def fetchResults(self, search: str, context: QgsLocatorContext,
                     feedback: QgsFeedback):
        try:
            self.dbg_info("start solocator search...")

            if len(search) < 3:
                return

            self.result_found = False

            params = {
                'searchtext': str(search),
                'filter': self.enabled_dataproducts(),
                'limit': str(self.settings.value('results_limit'))
            }

            nam = NetworkAccessManager()
            feedback.canceled.connect(nam.abort)
            url = self.url_with_param(SEARCH_URL, params)
            self.dbg_info(url)
            try:
                (response, content) = nam.request(url,
                                                  headers=self.HEADERS,
                                                  blocking=True)
                self.handle_response(response, search)
            except RequestsExceptionUserAbort:
                pass
            except RequestsException as err:
                self.info(err, Qgis.Info)

            if not self.result_found:
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = self.tr('No result found.')
                result.userData = NoResult
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(e, Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def data_product_qgsresult(self, data: dict, sub_layer: bool, score: float,
                               stacktype) -> QgsLocatorResult:
        result = QgsLocatorResult()
        result.filter = self
        result.displayString = '{prefix}{title}'.format(
            prefix=' ↳ ' if sub_layer else '', title=data['display'])
        if stacktype == 'background':
            result.group = 'Hintergrundkarten'
        else:
            loading_mode: LoadingMode = self.settings.value(
                'default_layer_loading_mode')
            result.group = 'Vordergrundkarten (Doppelklick: {normal}, Ctrl-Doppelklick: {alt})'.format(
                normal=loading_mode, alt=loading_mode.alternate_mode())
        result.userData = DataProductResult(
            type=data['type'],
            dataproduct_id=data['dataproduct_id'],
            display=data['display'],
            dset_info=data['dset_info'],
            stacktype=stacktype,
            sublayers=data.get('sublayers', None))
        data_product = 'dataproduct'
        data_type = data['type']
        result.icon, result.description = dataproduct2icon_description(
            data_product, data_type)
        result.score = score
        return result

    def handle_response(self, response, search_text: str):
        try:
            if response.status_code != 200:
                if not isinstance(response.exception,
                                  RequestsExceptionUserAbort):
                    self.info("Error in main response with status code: "
                              "{} from {}".format(response.status_code,
                                                  response.url))
                return

            data = json.loads(response.content.decode('utf-8'))

            # Since results are ordered by score (0 to 1)
            # we use an ordering score to keep the same order than the one from the remote service
            score = 1

            # sub-filtering
            # dbg_info(data['result_counts'])
            if len(data['result_counts']) > 1:
                for _filter in data['result_counts']:
                    result = QgsLocatorResult()
                    result.filter = self
                    result.group = 'Suche verfeinern'
                    result.displayString = _filter['filterword']
                    if _filter['count']:
                        result.displayString += ' ({})'.format(
                            _filter['count'])
                    self.dbg_info(_filter)
                    result.icon, _ = dataproduct2icon_description(
                        _filter['dataproduct_id'], 'datasetview')
                    result.userData = FilterResult(_filter['filterword'],
                                                   search_text)
                    result.score = score
                    self.resultFetched.emit(result)
                    score -= 0.001

            for res in data['results']:
                # dbg_info(res)

                result = QgsLocatorResult()
                result.filter = self

                if 'feature' in res.keys():
                    f = res['feature']
                    # dbg_info("feature: {}".format(f))
                    result.displayString = f['display']
                    result.group = 'Orte'
                    result.userData = FeatureResult(
                        dataproduct_id=f['dataproduct_id'],
                        id_field_name=f['id_field_name'],
                        id_field_type=f['id_field_type'],
                        feature_id=f['feature_id'])
                    data_product = f['dataproduct_id']
                    data_type = None
                    result.icon, result.description = dataproduct2icon_description(
                        data_product, data_type)
                    result.score = score
                    self.resultFetched.emit(result)
                    score -= 0.001

                elif 'dataproduct' in res.keys():
                    dp = res['dataproduct']
                    # self.dbg_info("data_product: {}".format(dp))
                    result = self.data_product_qgsresult(
                        dp, False, score, dp['stacktype'])
                    self.resultFetched.emit(result)
                    score -= 0.001

                    # also give sublayers
                    for layer in dp.get('sublayers', []):
                        always_show_sublayers = True
                        if always_show_sublayers or search_text.lower(
                        ) in layer['display'].lower():
                            result = self.data_product_qgsresult(
                                layer, True, score, dp['stacktype'])
                            self.resultFetched.emit(result)
                            score -= 0.001

                else:
                    continue

                self.result_found = True

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def triggerResult(self, result: QgsLocatorResult):
        # this is run in the main thread, i.e. map_canvas is not None
        self.clearPreviousResults()

        ctrl_clicked = Qt.ControlModifier == QApplication.instance(
        ).queryKeyboardModifiers()
        self.dbg_info(("CTRL pressed: {}".format(ctrl_clicked)))

        user_data = self.get_user_data(result)
        if type(user_data) == NoResult:
            pass
        elif type(user_data) == FilterResult:
            self.filtered_search(user_data)
        elif type(user_data) == FeatureResult:
            self.fetch_feature(user_data)
        elif type(user_data) == DataProductResult:
            self.fetch_data_product(user_data, ctrl_clicked)
        else:
            self.info('Incorrect result. Please contact support',
                      Qgis.Critical)

    def filtered_search(self, filter_result: FilterResult):
        search_text = '{prefix} {filter_word}: {search}'.format(
            prefix=self.activePrefix(),
            filter_word=filter_result.filter_word,
            search=filter_result.search)
        # Compatibility for QGIS < 3.10
        # TODO: remove
        try:
            self.iface.locatorSearch(search_text)
        except AttributeError:
            for w in self.iface.mainWindow().findChildren(QgsFilterLineEdit):
                if hasattr(w.parent(), 'search') and hasattr(
                        w.parent(), 'invalidateResults'):
                    w.setText(search_text)
                    w.parent().setFocus(True)
                    return
            raise NameError('Locator not found')

    def highlight(self, geometry: QgsGeometry):
        self.clearPreviousResults()
        if geometry is None:
            return

        self.rubber_band.reset(geometry.type())
        self.rubber_band.addGeometry(geometry, None)

        rect = geometry.boundingBox()
        if not self.settings.value('keep_scale'):
            if rect.isEmpty():
                current_extent = self.map_canvas.extent()
                rect = current_extent.scaled(
                    self.settings.value('point_scale') /
                    self.map_canvas.scale(), rect.center())
            else:
                rect.scale(4)
        self.map_canvas.setExtent(rect)
        self.map_canvas.refresh()

        self.current_timer = QTimer()
        self.current_timer.timeout.connect(self.clearPreviousResults)
        self.current_timer.setSingleShot(True)
        self.current_timer.start(5000)

    def fetch_feature(self, feature: FeatureResult):
        self.dbg_info(feature)
        url = '{url}/{dataset}/{id}'.format(url=FEATURE_URL,
                                            dataset=feature.dataproduct_id,
                                            id=feature.feature_id)
        self.nam_fetch_feature = NetworkAccessManager()
        self.dbg_info(url)
        self.nam_fetch_feature.finished.connect(self.parse_feature_response)
        self.nam_fetch_feature.request(url,
                                       headers=self.HEADERS,
                                       blocking=False)

    def parse_feature_response(self, response):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                self.info("Error in feature response with status code: "
                          "{} from {}".format(response.status_code,
                                              response.url))
            return

        data = json.loads(response.content.decode('utf-8'))
        self.dbg_info(data.keys())
        self.dbg_info(data['properties'])
        self.dbg_info(data['geometry'])
        self.dbg_info(data['crs'])
        self.dbg_info(data['type'])

        assert data['crs']['properties'][
            'name'] == 'urn:ogc:def:crs:EPSG::2056'

        geometry_type = data['geometry']['type']
        geometry = QgsGeometry()

        if geometry_type.lower() == 'point':
            geometry = QgsGeometry.fromPointXY(
                QgsPointXY(data['geometry']['coordinates'][0],
                           data['geometry']['coordinates'][1]))

        elif geometry_type.lower() == 'polygon':
            rings = data['geometry']['coordinates']
            for r in range(0, len(rings)):
                for p in range(0, len(rings[r])):
                    rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1])
            geometry = QgsGeometry.fromPolygonXY(rings)

        elif geometry_type.lower() == 'multipolygon':
            islands = data['geometry']['coordinates']
            for i in range(0, len(islands)):
                for r in range(0, len(islands[i])):
                    for p in range(0, len(islands[i][r])):
                        islands[i][r][p] = QgsPointXY(islands[i][r][p][0],
                                                      islands[i][r][p][1])
            geometry = QgsGeometry.fromMultiPolygonXY(islands)

        else:
            # SoLocator does not handle {geometry_type} yet. Please contact support
            self.info(
                'SoLocator unterstützt den Geometrietyp {geometry_type} nicht.'
                ' Bitte kontaktieren Sie den Support.'.format(
                    geometry_type=geometry_type), Qgis.Warning)

        geometry.transform(self.transform_ch)
        self.highlight(geometry)

    def fetch_data_product(self, product: DataProductResult,
                           alternate_mode: bool):
        self.dbg_info(product)
        url = '{url}/{dataproduct_id}'.format(
            url=DATA_PRODUCT_URL, dataproduct_id=product.dataproduct_id)
        self.nam_fetch_feature = NetworkAccessManager()
        self.dbg_info(url)
        is_background = product.stacktype == 'background'
        self.dbg_info('is_background {}'.format(is_background))
        self.nam_fetch_feature.finished.connect(
            lambda response: self.parse_data_product_response(
                response, is_background, alternate_mode))
        self.nam_fetch_feature.request(url,
                                       headers=self.HEADERS,
                                       blocking=False)

    def parse_data_product_response(self, response, is_background: bool,
                                    alternate_mode: bool):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                self.info("Error in feature response with status code: "
                          "{} from {}".format(response.status_code,
                                              response.url))
            return

        data = json.loads(response.content.decode('utf-8'))
        LayerLoader(data, self.iface, is_background, alternate_mode)

    def info(self, msg="", level=Qgis.Info):
        self.logMessage(str(msg), level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)

    def get_user_data(self, result):
        if hasattr(result, 'getUserData'):
            return result.getUserData()
        else:
            return result.userData
Example #14
0
class FinderBox(QComboBox):

    running = False
    toFinish = 0

    searchStarted = pyqtSignal()
    searchFinished = pyqtSignal()

    def __init__(self, finders, iface, parent=None):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        self.rubber = QgsRubberBand(self.mapCanvas)
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

        QComboBox.__init__(self, parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.InsertAtTop)
        self.setMinimumHeight(27)
        self.setSizePolicy(QSizePolicy.Expanding,
                           QSizePolicy.Fixed)

        self.insertSeparator(0)
        self.lineEdit().returnPressed.connect(self.search)

        self.resultView = QTreeView()
        self.resultView.setHeaderHidden(True)
        self.resultView.setMinimumHeight(300)
        self.resultView.activated.connect(self.itemActivated)
        self.resultView.pressed.connect(self.itemPressed)
        self.setView(self.resultView)

        self.resultModel = ResultModel(self)
        self.setModel(self.resultModel)

        self.finders = finders
        for finder in self.finders.values():
            finder.resultFound.connect(self.resultFound)
            finder.limitReached.connect(self.limitReached)
            finder.finished.connect(self.finished)
            finder.message.connect(self.message)

    def __del__(self):
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def search(self):
        if self.running:
            return

        toFind = self.lineEdit().text()
        if not toFind or toFind == '':
            return

        self.running = True
        self.searchStarted.emit()

        self.resultModel.clearResults()
        self.resultModel.truncateHistory(MySettings().value("historyLength"))
        self.resultModel.setLoading(True)
        self.showPopup()

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        # create categories in special order and count activated ones
        for finder in self.finders.values():
            if finder.activated():
                self.resultModel.addResult(finder.name)

        bbox = self.mapCanvas.fullExtent()
        for finder in self.finders.values():
            if finder.activated():
                finder.start(toFind, bbox=bbox)

    def stop(self):
        for finder in self.finders.values():
            if finder.isRunning():
                finder.stop()

    def resultFound(self, finder, layername, value, geometry, srid):
        self.resultModel.addResult(finder.name, layername, value, geometry, srid)
        self.resultView.expandAll()

    def limitReached(self, finder, layername):
        self.resultModel.addEllipsys(finder.name, layername)

    def finished(self, finder):
        for finder in self.finders.values():
            if finder.isRunning():
                return

        self.running = False
        self.searchFinished.emit()

        self.resultModel.setLoading(False)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def message(self, finder, message, level):
        self.iface.messageBar().pushMessage("Quick Finder", message, level, 3)

    def itemActivated(self, index):
        item = self.resultModel.itemFromIndex(index)
        self.showItem(item)

    def itemPressed(self, index):
        item = self.resultModel.itemFromIndex(index)
        if QApplication.mouseButtons() == Qt.LeftButton:
            self.showItem(item)

    def showItem(self, item):
        if isinstance(item, ResultItem):
            self.resultModel.setSelected(item, self.resultView.palette())
            geometry = self.transformGeom(item)
            self.rubber.reset(geometry.type())
            self.rubber.setToGeometry(geometry, None)
            self.zoomToRubberBand()
            return

        if isinstance(item, GroupItem):
            child = item.child(0)
            if isinstance(child, ResultItem):
                self.resultModel.setSelected(item, self.resultView.palette())
                self.rubber.reset(child.geometry.type())
                for i in xrange(0, item.rowCount()):
                    geometry = self.transformGeom(item.child(i))
                    self.rubber.addGeometry(geometry, None)
                self.zoomToRubberBand()
            return

        if item.__class__.__name__ == 'QStandardItem':
            self.resultModel.setSelected(None, self.resultView.palette())
            self.rubber.reset()
            return

    def transformGeom(self, item):
        geometry = item.geometry
        src_crs = QgsCoordinateReferenceSystem()
        src_crs.createFromSrid(item.srid)
        dest_crs = self.mapCanvas.mapRenderer().destinationCrs()
        geom = item.geometry
        geom.transform( QgsCoordinateTransform(src_crs, dest_crs) )
        return geom

    def zoomToRubberBand(self):
        geom = self.rubber.asGeometry()
        if geom:
            rect = geom.boundingBox()
            rect.scale(1.5)
            self.mapCanvas.setExtent(rect)
            self.mapCanvas.refresh()
Example #15
0
class GeodesicMeasureDialog(QDialog, FORM_CLASS):
    def __init__(self, iface, parent):
        super(GeodesicMeasureDialog, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface
        self.canvas = iface.mapCanvas()
        settings = QSettings()

        self.restoreGeometry(settings.value("ShapeTools/MeasureDialogGeometry",
                                        QByteArray(), type=QByteArray))
        self.closeButton.clicked.connect(self.closeDialog)
        self.newButton.clicked.connect(self.newDialog)

        self.unitsComboBox.addItems(UNITS)

        self.tableWidget.setColumnCount(3)
        self.tableWidget.setSortingEnabled(False)
        self.tableWidget.setHorizontalHeaderLabels(['Heading To', 'Heading From', 'Distance'])
        
        self.unitsComboBox.activated.connect(self.unitsChanged)
        
        self.capturedPoints = []
        self.distances = []
        self.geod = Geodesic.WGS84
        self.activeMeasuring = True
        self.unitsChanged()
        self.currentDistance = 0.0
        
        color = QColor(222, 167, 67, 150)
        self.pointRb = QgsRubberBand(self.canvas, QGis.Point)
        self.pointRb.setColor(color)
        self.pointRb.setIconSize(10)
        self.lineRb = QgsRubberBand(self.canvas, QGis.Line)
        self.lineRb.setColor(color)
        self.lineRb.setWidth(3)
        self.tempRb = QgsRubberBand(self.canvas, QGis.Line)
        self.tempRb.setColor(color)
        self.tempRb.setWidth(3)

    def ready(self):
        return self.activeMeasuring
        
    def stop(self):
        self.activeMeasuring = False
        
    def closeEvent(self, event):
        self.closeDialog()
        
    def closeDialog(self):
        self.clear()
        QSettings().setValue(
            "ShapeTools/MeasureDialogGeometry", self.saveGeometry())
        self.close()
        
    def newDialog(self):
        self.clear()
        
    def unitsChanged(self):
        label = "Distance [{}]".format(UNITS[self.unitsComboBox.currentIndex()])
        item = QTableWidgetItem(label)
        self.tableWidget.setHorizontalHeaderItem(2, item)
        ptcnt = len(self.capturedPoints)
        if ptcnt >= 2:
            i = 0
            while i < ptcnt-1:
                item = QTableWidgetItem('{:.4f}'.format(self.unitDistance(self.distances[i])))
                self.tableWidget.setItem(i, 2, item)
                i += 1
            self.formatTotal()
        
    def motionReady(self):
        if len(self.capturedPoints) > 0 and self.activeMeasuring:
            return True
        return False
        
    def addPoint(self, pt, button):
        self.currentDistance = 0
        index = len(self.capturedPoints)
        if index > 0 and pt == self.capturedPoints[index-1]:
            # the clicked point is the same as the previous so just ignore it
            return
        self.capturedPoints.append(pt)
        # Add rubber band points
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(epsg4326, canvasCrs)
        ptCanvas = transform.transform(pt.x(), pt.y())
        self.pointRb.addPoint(ptCanvas, True)
        # If there is more than 1 point add it to the table
        if index > 0:
            (distance, startAngle, endAngle) = self.calcParameters(self.capturedPoints[index-1], self.capturedPoints[index])
            self.distances.append(distance)
            self.insertParams(index, distance, startAngle, endAngle)
            # Add Rubber Band Line
            linePts = self.getLinePts(distance, self.capturedPoints[index-1], self.capturedPoints[index])
            self.lineRb.addGeometry(QgsGeometry.fromPolyline( linePts ), None)
        self.formatTotal()
            
    def inMotion(self, pt):
        index = len(self.capturedPoints)
        if index <= 0:
            return
        (self.currentDistance, startAngle, endAngle) = self.calcParameters(self.capturedPoints[index-1], pt)
        self.insertParams(index, self.currentDistance, startAngle, endAngle)
        self.formatTotal()
        linePts = self.getLinePts(self.currentDistance, self.capturedPoints[index-1], pt)
        self.tempRb.setToGeometry(QgsGeometry.fromPolyline( linePts ), None)
        
    def calcParameters(self, pt1, pt2):
        l = self.geod.Inverse(pt1.y(), pt1.x(), pt2.y(), pt2.x())
        az2 = (l['azi2'] + 180) %360.0
        if az2 > 180:
            az2 = az2 - 360.0
        l2 = self.geod.Inverse(pt2.y(), pt2.x(), pt1.y(), pt1.x())
        return (l['s12'], l['azi1'], az2)
        
    def getLinePts(self, distance, pt1, pt2):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(epsg4326, canvasCrs)
        pt1c = transform.transform(pt1.x(), pt1.y())
        pt2c = transform.transform(pt2.x(), pt2.y())
        if distance < 10000:
            return [pt1c, pt2c]
        l = self.geod.InverseLine(pt1.y(), pt1.x(), pt2.y(), pt2.x())
        n = int(math.ceil(distance / 10000.0))
        if n > 20:
            n = 20
        seglen = distance / n
        pts = [pt1c]
        for i in range(1,n):
            s = seglen * i
            g = l.Position(s, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL)
            ptc = transform.transform(g['lon2'], g['lat2'])
            pts.append( ptc )
        pts.append(pt2c)
        return pts
        
    def insertParams(self, position, distance, startAngle, endAngle):
        if position > self.tableWidget.rowCount():
            self.tableWidget.insertRow(position-1)
        item = QTableWidgetItem('{:.4f}'.format(self.unitDistance(distance)))
        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.tableWidget.setItem(position-1, 2, item)
        item = QTableWidgetItem('{:.4f}'.format(startAngle))
        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.tableWidget.setItem(position-1, 0, item)
        item = QTableWidgetItem('{:.4f}'.format(endAngle))
        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.tableWidget.setItem(position-1, 1, item)
        
    def formatTotal(self):
        total = self.currentDistance
        ptcnt = len(self.capturedPoints)
        if ptcnt >= 2:
            i = 0
            while i < ptcnt-1:
                total += self.distances[i]
                i += 1
        self.distanceLineEdit.setText('{:.2f}'.format(self.unitDistance(total)))
        
        
    def clear(self):
        self.tableWidget.setRowCount(0)
        self.capturedPoints = []
        self.distances = []
        self.activeMeasuring = True
        self.currentDistance = 0.0
        self.distanceLineEdit.setText('')
        self.pointRb.reset(QGis.Point)
        self.lineRb.reset(QGis.Line)
        self.tempRb.reset(QGis.Line)
        
    def unitDistance(self, distance):
        units = self.unitsComboBox.currentIndex()
        if units == 0: # meters
            return distance
        elif units == 1: # kilometers
            return distance / 1000.0
        elif units == 2: # feet
            return distance * QGis.fromUnitToUnitFactor(QGis.Meters, QGis.Feet)
        elif units == 3: # yards
            return distance * QGis.fromUnitToUnitFactor(QGis.Meters, QGis.Feet) / 3.0
        elif units == 4: # miles
            return distance * QGis.fromUnitToUnitFactor(QGis.Meters, QGis.Miles)
        else: # nautical miles
            return distance * QGis.fromUnitToUnitFactor(QGis.Meters, QGis.NauticalMiles)
Example #16
0
class ReverseGeocodeTool(QgsMapTool):
    def __init__(self, iface, settings):
        self.canvas = iface.mapCanvas()
        QgsMapTool.__init__(self, self.canvas)
        self.iface = iface
        self.settings = settings
        self.reverseGeoCodeDialog = ReverseGeocodeDialog(
            self, self.iface, self.iface.mainWindow())
        self.iface.addDockWidget(Qt.TopDockWidgetArea,
                                 self.reverseGeoCodeDialog)
        self.reverseGeoCodeDialog.hide()
        self.epsg4326 = QgsCoordinateReferenceSystem('EPSG:4326')
        self.reply = None
        self.marker = None

        # Set up a polygon/line rubber band
        self.rubber = QgsRubberBand(self.canvas)
        self.rubber.setColor(QColor(255, 70, 0, 200))
        self.rubber.setWidth(5)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def activate(self):
        '''When activated set the cursor to a crosshair.'''
        self.canvas.setCursor(Qt.CrossCursor)
        self.show()

    def unload(self):
        self.iface.removeDockWidget(self.reverseGeoCodeDialog)
        self.reverseGeoCodeDialog = None
        if self.rubber:
            self.canvas.scene().removeItem(self.rubber)
            del self.rubber
        self.removeMarker()

    def addMarker(self, lat, lon):
        if self.marker:
            self.removeMarker()
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(self.epsg4326, canvasCrs,
                                           QgsProject.instance())
        center = transform.transform(lon, lat)
        self.marker = QgsVertexMarker(self.canvas)
        self.marker.setCenter(center)
        self.marker.setColor(QColor(255, 70, 0))
        self.marker.setIconSize(15)
        self.marker.setIconType(QgsVertexMarker.ICON_X)
        self.marker.setPenWidth(3)
        self.marker.show()

    def removeMarker(self):
        if self.marker:
            self.canvas.scene().removeItem(self.marker)
            self.marker = None

    def clearSelection(self):
        self.removeMarker()
        self.rubber.reset()

    def transform_geom(self, geometry):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        geom = QgsGeometry(geometry)
        geom.transform(
            QgsCoordinateTransform(self.epsg4326, canvasCrs,
                                   QgsProject.instance()))
        return geom

    def show(self):
        self.reverseGeoCodeDialog.show()

    def canvasReleaseEvent(self, event):
        # Make sure the point is transfored to 4326
        pt = self.toMapCoordinates(event.pos())
        canvasCRS = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(canvasCRS, self.epsg4326,
                                           QgsProject.instance())
        pt = transform.transform(pt.x(), pt.y())
        url = '{}?format=json&lat={:f}&lon={:f}&zoom={:d}&addressdetails=0&polygon_text=1'.format(
            self.settings.reverseURL(), pt.y(), pt.x(),
            self.settings.levelOfDetail)
        # print url
        qurl = QUrl(url)
        if self.reply is not None:
            self.reply.finished.disconnect(self.replyFinished)
            self.reply.abort()
            self.reply = None
        request = QNetworkRequest(qurl)
        request.setRawHeader(
            b"User-Agent",
            b"Mozilla/5.0 (Windows NT 6.1: WOW64; rv:52.0) Gecko/20100101 Firefox/52.0"
        )
        request.setRawHeader(b"Connection", b"keep-alive")
        request.setRawHeader(
            b"Accept",
            b"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
        self.reply = QgsNetworkAccessManager.instance().get(request)
        self.reply.finished.connect(self.replyFinished)
        if not self.reverseGeoCodeDialog.isVisible():
            self.show()

    def setText(self, text):
        self.reverseGeoCodeDialog.addressLineEdit.setText(text)

    @pyqtSlot()
    def replyFinished(self):
        error = self.reply.error()
        self.clearSelection()
        if error == QNetworkReply.NoError:
            data = self.reply.readAll().data()
            try:
                jd = json.loads(data)
                try:
                    display_name = jd['display_name']
                    self.setText(display_name)
                except KeyError:
                    self.setText("[Could not find address]")
                try:
                    wkt = jd['geotext']
                    geometry = QgsGeometry.fromWkt(wkt)
                    geometry = self.transform_geom(geometry)
                    self.rubber.addGeometry(geometry, None)
                    self.rubber.show()
                except KeyError:
                    try:
                        lon = float(jd['lon'])
                        lat = float(jd['lat'])
                        self.addMarker(lat, lon)
                    except:
                        pass
            except:
                self.setText("Error: " + data)

        else:
            self.setText("[Address error]")
        self.reply.deleteLater()
        self.reply = None
class LatLonTools:
    digitizerDialog = None
    
    def __init__(self, iface):
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.crossRb.setColor(Qt.red)
        self.provider = LatLonToolsProvider()
        self.toolbar = self.iface.addToolBar('Lat Lon Tools Toolbar')
        self.toolbar.setObjectName('LatLonToolsToolbar')

    def initGui(self):
        '''Initialize Lot Lon Tools GUI.'''
        # Initialize the Settings Dialog box
        self.settingsDialog = SettingsWidget(self, self.iface, self.iface.mainWindow())
        self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface)
        self.showMapTool = ShowOnMapTool(self.iface)
        
        # Add Interface for Coordinate Capturing
        icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.png")
        self.copyAction = QAction(icon, "Copy Latitude, Longitude", self.iface.mainWindow())
        self.copyAction.setObjectName('latLonToolsCopy')
        self.copyAction.triggered.connect(self.startCapture)
        self.copyAction.setCheckable(True)
        self.toolbar.addAction(self.copyAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction)
        
        # Add Interface for External Map
        icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png")
        self.externMapAction = QAction(icon, "Show in External Map", self.iface.mainWindow())
        self.externMapAction.setObjectName('latLonToolsExternalMap')
        self.externMapAction.triggered.connect(self.setShowMapTool)
        self.externMapAction.setCheckable(True)
        self.toolbar.addAction(self.externMapAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction)

        # Add Interface for Zoom to Coordinate
        icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.png")
        self.zoomToAction = QAction(icon, "Zoom To Latitude, Longitude", self.iface.mainWindow())
        self.zoomToAction.setObjectName('latLonToolsZoom')
        self.zoomToAction.triggered.connect(self.showZoomToDialog)
        self.toolbar.addAction(self.zoomToAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction)

        self.zoomToDialog = ZoomToLatLon(self, self.iface, self.iface.mainWindow())
        self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog)
        self.zoomToDialog.hide()
        
        # Add Interface for Multi point zoom
        icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.png')
        self.multiZoomToAction = QAction(icon, "Multi-location Zoom", self.iface.mainWindow())
        self.multiZoomToAction.setObjectName('latLonToolsMultiZoom')
        self.multiZoomToAction.triggered.connect(self.multiZoomTo)
        self.toolbar.addAction(self.multiZoomToAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction)

        self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog, self.iface.mainWindow())
        self.multiZoomDialog.hide()
        self.multiZoomDialog.setFloating(True)
        
        # Create the conversions menu
        menu = QMenu()
        icon = QIcon(os.path.dirname(__file__) + '/images/field2geom.png')
        action = menu.addAction(icon, "Fields to point layer", self.field2geom)
        action.setObjectName('latLonToolsField2Geom')
        icon = QIcon(os.path.dirname(__file__) + '/images/geom2field.png')
        action = menu.addAction(icon, "Point layer to fields", self.geom2Field)
        action.setObjectName('latLonToolsGeom2Field')
        icon = QIcon(os.path.dirname(__file__) + '/images/pluscodes.png')
        action = menu.addAction(icon, "Plus Codes to point layer", self.PlusCodestoLayer)
        action.setObjectName('latLonToolsPlusCodes2Geom')
        action = menu.addAction(icon, "Point layer to Plus Codes", self.toPlusCodes)
        action.setObjectName('latLonToolsGeom2PlusCodes')
        icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.png')
        action = menu.addAction(icon, "MGRS to point layer", self.MGRStoLayer)
        action.setObjectName('latLonToolsMGRS2Geom')
        icon = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.png')
        action = menu.addAction(icon, "Point layer to MGRS", self.toMGRS)
        action.setObjectName('latLonToolsGeom2MGRS')
        self.conversionsAction = QAction(icon, "Conversions", self.iface.mainWindow())
        self.conversionsAction.setMenu(menu)
        self.iface.addPluginToMenu('Lat Lon Tools', self.conversionsAction)
        
        # Add to Digitize Toolbar
        icon = QIcon(os.path.dirname(__file__) + '/images/latLonDigitize.png')
        self.digitizeAction = QAction(icon, "Lat Lon Digitize", self.iface.mainWindow())
        self.digitizeAction.setObjectName('latLonToolsDigitize')
        self.digitizeAction.triggered.connect(self.digitizeClicked)
        self.digitizeAction.setEnabled(False)
        self.toolbar.addAction(self.digitizeAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.digitizeAction)
        
        # Add Interface for copying the canvas extent
        icon = QIcon(os.path.dirname(__file__) + "/images/copycanvas.png")
        self.copyCanvasAction = QAction(icon, "Copy Canvas Bounding Box", self.iface.mainWindow())
        self.copyCanvasAction.setObjectName('latLonToolsCopyCanvas')
        self.copyCanvasAction.triggered.connect(self.copyCanvas)
        self.toolbar.addAction(self.copyCanvasAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.copyCanvasAction)
        
        # Initialize the Settings Dialog Box
        settingsicon = QIcon(os.path.dirname(__file__) + '/images/settings.png')
        self.settingsAction = QAction(settingsicon, "Settings", self.iface.mainWindow())
        self.settingsAction.setObjectName('latLonToolsSettings')
        self.settingsAction.triggered.connect(self.settings)
        self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction)
        
        # Help
        icon = QIcon(os.path.dirname(__file__) + '/images/help.png')
        self.helpAction = QAction(icon, "Help", self.iface.mainWindow())
        self.helpAction.setObjectName('latLonToolsHelp')
        self.helpAction.triggered.connect(self.help)
        self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction)
        
        
        self.iface.currentLayerChanged.connect(self.currentLayerChanged)
        self.canvas.mapToolSet.connect(self.unsetTool)
        self.enableDigitizeTool()
        # Add the processing provider
        
        QgsApplication.processingRegistry().addProvider(self.provider)
                
    def unsetTool(self, tool):
        '''Uncheck the Copy Lat Lon tool'''
        try:
            if not isinstance(tool, CopyLatLonTool):
                self.copyAction.setChecked(False)
                self.multiZoomDialog.stopCapture()
                self.mapTool.capture4326 = False
            if not isinstance(tool, ShowOnMapTool):
                self.externMapAction.setChecked(False)
        except:
            pass

    def unload(self):
        '''Unload LatLonTools from the QGIS interface'''
        self.zoomToDialog.removeMarker()
        self.multiZoomDialog.removeMarkers()
        self.canvas.unsetMapTool(self.mapTool)
        self.canvas.unsetMapTool(self.showMapTool)
        self.iface.removePluginMenu('Lat Lon Tools', self.copyAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.copyCanvasAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.conversionsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.helpAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.digitizeAction)
        self.iface.removeDockWidget(self.zoomToDialog)
        self.iface.removeDockWidget(self.multiZoomDialog)
        # Remove Toolbar Icons
        self.iface.removeToolBarIcon(self.copyAction)
        self.iface.removeToolBarIcon(self.copyCanvasAction)
        self.iface.removeToolBarIcon(self.zoomToAction)
        self.iface.removeToolBarIcon(self.externMapAction)
        self.iface.removeToolBarIcon(self.multiZoomToAction)
        self.iface.removeToolBarIcon(self.digitizeAction)
        del self.toolbar
        
        self.zoomToDialog = None
        self.multiZoomDialog = None
        self.settingsDialog = None
        self.showMapTool = None
        self.mapTool = None
        self.digitizerDialog = None
        QgsApplication.processingRegistry().removeProvider(self.provider)

    def startCapture(self):
        '''Set the focus of the copy coordinate tool and check it'''
        self.copyAction.setChecked(True)
        self.canvas.setMapTool(self.mapTool)

    def copyCanvas(self):
        extent = self.iface.mapCanvas().extent()
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        if settings.bBoxCrs == 0 and canvasCrs != epsg4326:
            transform = QgsCoordinateTransform(canvasCrs, epsg4326, QgsProject.instance())
            p1x, p1y = transform.transform(float(extent.xMinimum()), float(extent.yMinimum()))
            p2x, p2y = transform.transform(float(extent.xMaximum()), float(extent.yMaximum()))
            extent.set(p1x, p1y, p2x, p2y)
        delim = settings.bBoxDelimiter
        prefix = settings.bBoxPrefix
        suffix = settings.bBoxSuffix
        precision = settings.bBoxDigits
        outStr = ''
        minX = extent.xMinimum()
        minY = extent.yMinimum()
        maxX = extent.xMaximum()
        maxY = extent.yMaximum()
        if settings.bBoxFormat == 0: # minX,minY,maxX,maxY - using the delimiter
            outStr = '{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}'.format(
                minX, delim, minY, delim, maxX, delim, maxY, prec=precision)
        elif settings.bBoxFormat == 1: # minX,maxX,minY,maxY - Using the selected delimiter'
            outStr = '{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}'.format(
                minX, delim, maxX, delim, minY, delim, maxY, prec=precision)
        elif settings.bBoxFormat == 2: # x1 y1,x2 y2,x3 y3,x4 y4,x1 y1 - Polygon format
            outStr = '{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f}'.format(
                minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY, prec=precision)
        elif settings.bBoxFormat == 3: # x1,y1 x2,y2 x3,y3 x4,y4 x1,y1 - Polygon format
            outStr = '{:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f}'.format(
                minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY, prec=precision)
        elif settings.bBoxFormat == 4: # WKT Polygon
            outStr = extent.asWktPolygon()
        elif settings.bBoxFormat == 5: # bbox: [minX, minY, maxX, maxY] - MapProxy
            outStr = 'bbox: [{}, {}, {}, {}]'.format(
                minX, minY, maxX, maxY)
        elif settings.bBoxFormat == 6: # bbox: [minX, minY, maxX, maxY] - MapProxy
            outStr = 'bbox={},{},{},{}'.format(
                minX, minY, maxX, maxY)
        outStr = '{}{}{}'.format(prefix, outStr, suffix)
        clipboard = QApplication.clipboard()
        clipboard.setText(outStr)
        self.iface.messageBar().pushMessage("", "'{}' copied to the clipboard".format(outStr), level=Qgis.Info, duration=4)
        
    def setShowMapTool(self):
        '''Set the focus of the external map tool and check it'''
        self.externMapAction.setChecked(True)
        self.canvas.setMapTool(self.showMapTool)

    def showZoomToDialog(self):
        '''Show the zoom to docked widget.'''
        self.zoomToDialog.show()

    def multiZoomTo(self):
        '''Display the Multi-zoom to dialog box'''
        self.multiZoomDialog.show()

    def field2geom(self):
        '''Convert layer containing a point x & y coordinate to a new point layer'''
        results = processing.execAlgorithmDialog('latlontools:field2geom', {})

    def geom2Field(self):
        '''Convert layer geometry to a text string'''
        results = processing.execAlgorithmDialog('latlontools:geom2field', {})

    def toMGRS(self):
        '''Display the to MGRS  dialog box'''
        results = processing.execAlgorithmDialog('latlontools:point2mgrs', {})

    def MGRStoLayer(self):
        '''Display the to MGRS  dialog box'''
        results = processing.execAlgorithmDialog('latlontools:mgrs2point', {})

    def toPlusCodes(self):
        results = processing.execAlgorithmDialog('latlontools:point2pluscodes', {})

    def PlusCodestoLayer(self):
        results = processing.execAlgorithmDialog('latlontools:pluscodes2point', {})
    
    def settings(self):
        '''Show the settings dialog box'''
        self.settingsDialog.show()
        
    def help(self):
        '''Display a help page'''
        url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString()
        webbrowser.open(url, new=2)
        
    def settingsChanged(self):
        # Settings may have changed so we need to make sure the zoomToDialog window is configured properly
        self.zoomToDialog.configure()
        self.multiZoomDialog.settingsChanged()
            
 
    def zoomTo(self, srcCrs, lat, lon):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(srcCrs, canvasCrs, QgsProject.instance())
        x, y = transform.transform(float(lon), float(lat))
            
        rect = QgsRectangle(x, y, x, y)
        self.canvas.setExtent(rect)

        pt = QgsPointXY(x, y)
        self.highlight(pt)
        self.canvas.refresh()
        return pt
        
    def highlight(self, point):
        currExt = self.canvas.extent()
        
        leftPt = QgsPoint(currExt.xMinimum(), point.y())
        rightPt = QgsPoint(currExt.xMaximum(), point.y())
        
        topPt = QgsPoint(point.x(), currExt.yMaximum())
        bottomPt = QgsPoint(point.x(), currExt.yMinimum())
        
        horizLine = QgsGeometry.fromPolyline( [ leftPt , rightPt ] )
        vertLine = QgsGeometry.fromPolyline( [ topPt , bottomPt ] )
        
        self.crossRb.reset(QgsWkbTypes.LineGeometry)
        self.crossRb.addGeometry(horizLine, None)
        self.crossRb.addGeometry(vertLine, None)
        
        QTimer.singleShot(700, self.resetRubberbands)
        
    def resetRubberbands(self):
        self.crossRb.reset()
        
    def digitizeClicked(self):
        if self.digitizerDialog == None:
            from .digitizer import DigitizerWidget
            self.digitizerDialog = DigitizerWidget(self, self.iface, self.iface.mainWindow())
        self.digitizerDialog.show()
        
    def currentLayerChanged(self):
        layer = self.iface.activeLayer()
        if layer != None:
            try:
                layer.editingStarted.disconnect(self.layerEditingChanged)
            except:
                pass
            try:
                layer.editingStopped.disconnect(self.layerEditingChanged)
            except:
                pass
            
            if isinstance(layer, QgsVectorLayer):
                layer.editingStarted.connect(self.layerEditingChanged)
                layer.editingStopped.connect(self.layerEditingChanged)
                
        self.enableDigitizeTool()

    def layerEditingChanged(self):
        self.enableDigitizeTool()

    def enableDigitizeTool(self):
        self.digitizeAction.setEnabled(False)
        layer = self.iface.activeLayer()
        
        if layer != None and isinstance(layer, QgsVectorLayer) and (layer.geometryType() == QgsWkbTypes.PointGeometry) and layer.isEditable():
            self.digitizeAction.setEnabled(True)
        else:
            if self.digitizerDialog != None:
                self.digitizerDialog.close()
class GeomapfishLocatorFilter(QgsLocatorFilter):

    USER_AGENT = b'Mozilla/5.0 QGIS GeoMapFish Locator Filter'

    changed = pyqtSignal()

    def __init__(self, service: Service, iface: QgisInterface = None):
        super().__init__()
        self.service = service.clone()
        self.rubberband = None
        self.iface = None
        self.map_canvas = None
        self.current_timer = None
        self.settings = Settings()
        self.crs = QgsCoordinateReferenceSystem(self.service.crs)

        # only get map_canvas on main thread, not when cloning
        if iface is not None:
            self.iface = iface
            self.map_canvas = iface.mapCanvas()

            self.rubberband = QgsRubberBand(self.map_canvas)
            self.reset_rubberband()

    def name(self) -> str:
        return slugify(self.displayName())

    def clone(self):
        return GeomapfishLocatorFilter(self.service)

    def displayName(self) -> str:
        return self.service.name

    def prefix(self) -> str:
        return 'gmf'

    def hasConfigWidget(self) -> bool:
        return True

    def openConfigWidget(self, parent=None):
        cfg = FilterConfigurationDialog(self.service, parent)
        if cfg.exec_():
            self.service = cfg.service.clone()
            self.crs = QgsCoordinateReferenceSystem(self.service.crs)
            self.changed.emit()

    def reset_rubberband(self):
        # this should happen on main thread only!
        self.rubberband.setColor(self.settings.value('point_color'))
        self.rubberband.setIcon(self.rubberband.ICON_CIRCLE)
        self.rubberband.setIconSize(self.settings.value('point_size'))
        self.rubberband.setWidth(self.settings.value('line_width'))
        self.rubberband.setBrushStyle(Qt.NoBrush)

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url

    def emit_bad_configuration(self, err=None):
        result = QgsLocatorResult()
        result.filter = self
        result.displayString = self.tr('Locator filter is not configured.')
        result.description = err if err else self.tr(
            'Double-click to configure it.')
        result.userData = FilterNotConfigured
        result.icon = QgsApplication.getThemeIcon('mIconWarning.svg')
        self.resultFetched.emit(result)
        return

    @pyqtSlot()
    def clear_results(self):
        if self.rubberband:
            self.rubberband.reset(QgsWkbTypes.PointGeometry)
        if self.current_timer is not None:
            self.current_timer.timeout.disconnect(self.clear_results)
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def fetchResults(self, search, context, feedback):
        self.dbg_info("start GMF locator search...")
        url = self.service.url
        if not url:
            self.emit_bad_configuration()
            return
        if len(search) < 2:
            return

        params = {
            'query': search,
            'limit': str(self.service.total_limit),
            'partitionlimit': str(self.service.category_limit)
        }
        url = self.url_with_param(url, params)
        self.dbg_info(url.url())

        _request = QNetworkRequest(url)
        _request.setRawHeader(b'User-Agent', self.USER_AGENT)
        request = QgsBlockingNetworkRequest()
        if self.service.authid:
            request.setAuthCfg(self.service.authid)

        response = request.get(_request, False, feedback)
        if response == QgsBlockingNetworkRequest.NoError:
            self.handle_response(request.reply().content())
        else:
            error_msg = request.reply().errorString()
            self.emit_bad_configuration(error_msg)
            self.info(error_msg, Qgis.Critical)
        self.finished.emit()

    def handle_response(self, content: QByteArray):
        try:
            data = json.loads(str(content.data(), encoding='utf-8'))
            # self.dbg_info(data)

            features = data['features']
            for f in features:
                json_geom = json.dumps(f['geometry'])
                ogr_geom = ogr.CreateGeometryFromJson(json_geom)
                wkt = ogr_geom.ExportToWkt()
                geometry = QgsGeometry.fromWkt(wkt)
                self.dbg_info('---------')
                self.dbg_info(
                    QgsWkbTypes.geometryDisplayString(geometry.type()))
                self.dbg_info(f.keys())
                self.dbg_info('{} {}'.format(f['properties']['layer_name'],
                                             f['properties']['label']))
                self.dbg_info(f['bbox'])
                self.dbg_info(f['geometry'])
                if geometry is None:
                    continue
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = f['properties']['label']
                result.group = self.beautify_group(
                    f['properties']['layer_name'])
                result.userData = geometry
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            # exc_type, exc_obj, exc_traceback = sys.exc_info()
            # #filename = os.path.split(exc_traceback.tb_frame.f_code.co_filename)[1]
            # #self.info('{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical)
            # self.info(traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical)

    def triggerResult(self, result):
        self.clear_results()
        if result.userData == FilterNotConfigured:
            self.openConfigWidget()
            self.iface.invalidateLocatorResults()
            return

        # this should be run in the main thread, i.e. mapCanvas should not be None
        geometry = result.userData
        # geometry.transform(self.transform)
        dbg_info(str(geometry.asWkt()))
        dbg_info(geometry.type())

        try:
            rect = QgsReferencedRectangle(geometry.boundingBox(), self.crs)
            rect.scale(4)
            self.map_canvas.setReferencedExtent(rect)
        except AttributeError:
            # QGIS < 3.10 handling
            from qgis.core import QgsCoordinateTransform, QgsProject
            transform = QgsCoordinateTransform(
                self.crs,
                self.map_canvas.mapSettings().destinationCrs(),
                QgsProject.instance())
            geometry.transform(transform)
            rect = geometry.boundingBox()
            rect.scale(4)
            self.map_canvas.setExtent(rect)

        self.map_canvas.refresh()

        if geometry.type() == QgsWkbTypes.PolygonGeometry:
            nflash = 16
            color1: QColor = self.settings.value('polygon_color')
            color2 = color1
            color1.setAlpha(200)
            color2.setAlpha(100)
            self.map_canvas.flashGeometries(
                [geometry], self.crs, color1, color2, nflash,
                self.settings.value('highlight_duration') / nflash * 1000)
        else:
            self.rubberband.reset(geometry.type())
            self.rubberband.addGeometry(geometry, self.crs)

            self.current_timer = QTimer()
            self.current_timer.timeout.connect(self.clear_results)
            self.current_timer.setSingleShot(True)
            self.current_timer.start(
                self.settings.value('highlight_duration') * 1000)

    def beautify_group(self, group) -> str:
        if self.service.remove_leading_digits:
            group = re.sub('^[0-9]+', '', group)
        if self.service.replace_underscore:
            group = group.replace("_", " ")
        if self.service.break_camelcase:
            group = self.break_camelcase(group)
        return group

    def info(self, msg="", level=Qgis.Info):
        QgsMessageLog.logMessage('{} {}'.format(self.__class__.__name__, msg),
                                 'QgsLocatorFilter', level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)

    @staticmethod
    def break_camelcase(identifier) -> str:
        matches = re.finditer(
            '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)',
            identifier)
        return ' '.join([m.group(0) for m in matches])
Example #19
0
class Qgis2threejsDialog(QDialog):
  STYLE_MAX_COUNT = 4

  def __init__(self, iface, properties=None):
    QDialog.__init__(self, iface.mainWindow())
    self.iface = iface

    self.templateType = None
    self.currentItem = None
    self.currentPage = None
    topItemCount = len(ObjectTreeItem.topItemNames)
    if properties is None:
      self.properties = [None] * topItemCount
      for i in range(ObjectTreeItem.ITEM_OPTDEM, topItemCount):
        self.properties[i] = {}
    else:
      self.properties = properties

    # Set up the user interface from Designer.
    self.ui = ui = Ui_Qgis2threejsDialog()
    ui.setupUi(self)

    self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint)
    ui.lineEdit_OutputFilename.setPlaceholderText("[Temporary file]")
    ui.progressBar.setVisible(False)
    ui.label_MessageIcon.setVisible(False)

    ui.pushButton_Run.clicked.connect(self.run)
    ui.pushButton_Close.clicked.connect(self.reject)

    # set up map tool
    self.previousMapTool = None
    self.mapTool = RectangleMapTool(iface.mapCanvas())
    #self.mapTool = PointMapTool(iface.mapCanvas())

    # set up the template combo box
    self.initTemplateList()
    self.ui.comboBox_Template.currentIndexChanged.connect(self.currentTemplateChanged)

    # set up the properties pages
    self.pages = {}
    self.pages[ppages.PAGE_WORLD] = ppages.WorldPropertyPage(self)
    self.pages[ppages.PAGE_CONTROLS] = ppages.ControlsPropertyPage(self)
    self.pages[ppages.PAGE_DEM] = ppages.DEMPropertyPage(self)
    self.pages[ppages.PAGE_VECTOR] = ppages.VectorPropertyPage(self)
    container = ui.propertyPagesContainer
    for page in self.pages.itervalues():
      page.hide()
      container.addWidget(page)

    # build object tree
    self.topItemPages = {ObjectTreeItem.ITEM_WORLD: ppages.PAGE_WORLD, ObjectTreeItem.ITEM_CONTROLS: ppages.PAGE_CONTROLS, ObjectTreeItem.ITEM_DEM: ppages.PAGE_DEM}
    self.initObjectTree()
    self.ui.treeWidget.currentItemChanged.connect(self.currentObjectChanged)
    self.ui.treeWidget.itemChanged.connect(self.objectItemChanged)
    self.currentTemplateChanged()   # update item visibility

    ui.toolButton_Browse.clicked.connect(self.browseClicked)

    #iface.mapCanvas().mapToolSet.connect(self.mapToolSet)    # to show button to enable own map tool

    self.localBrowsingMode = True
    self.rb_quads = self.rb_point = None
    self.objectTypeManager = ObjectTypeManager()

  def showMessageBar(self, text, level=QgsMessageBar.INFO):
    # from src/gui/qgsmessagebaritem.cpp
    if level == QgsMessageBar.CRITICAL:
      msgIcon = "/mIconCritical.png"
      bgColor = "#d65253"
    elif level == QgsMessageBar.WARNING:
      msgIcon = "/mIconWarn.png"
      bgColor = "#ffc800"
    else:
      msgIcon = "/mIconInfo.png"
      bgColor = "#e7f5fe"
    stylesheet = "QLabel {{ background-color:{0}; }}".format(bgColor)

    label = self.ui.label_MessageIcon
    label.setPixmap(QgsApplication.getThemeIcon(msgIcon).pixmap(24))
    label.setStyleSheet(stylesheet)
    label.setVisible(True)

    label = self.ui.label_Status
    label.setText(text)
    label.setStyleSheet(stylesheet)

  def clearMessageBar(self):
    self.ui.label_MessageIcon.setVisible(False)
    self.ui.label_Status.setText("")
    self.ui.label_Status.setStyleSheet("QLabel { background-color: rgba(0, 0, 0, 0); }")

  def initTemplateList(self):
    cbox = self.ui.comboBox_Template
    cbox.clear()
    templateDir = QDir(tools.templateDir())
    for i, entry in enumerate(templateDir.entryList(["*.html", "*.htm"])):
      cbox.addItem(entry)

      config = tools.getTemplateConfig(templateDir.filePath(entry))
      # get template type
      templateType = config.get("type", "plain")
      cbox.setItemData(i, templateType, Qt.UserRole)

      # set tool tip text
      desc = config.get("description", "")
      if desc:
        cbox.setItemData(i, desc, Qt.ToolTipRole)

    # select the last used template
    templateName = QSettings().value("/Qgis2threejs/lastTemplate", "", type=unicode)
    if templateName:
      index = cbox.findText(templateName)
      if index != -1:
        cbox.setCurrentIndex(index)
      return index
    return -1

  def initObjectTree(self):
    tree = self.ui.treeWidget
    tree.clear()

    # add vector and raster layers into tree widget
    topItems = []
    for index, itemName in enumerate(ObjectTreeItem.topItemNames):
      item = QTreeWidgetItem(tree, [itemName])
      item.setData(0, Qt.UserRole, index)
      topItems.append(item)

    optDEMChecked = False
    for layer in self.iface.legendInterface().layers():
      layerType = layer.type()
      if layerType not in (QgsMapLayer.VectorLayer, QgsMapLayer.RasterLayer):
        continue

      parentId = None
      if layerType == QgsMapLayer.VectorLayer:
        geometry_type = layer.geometryType()
        if geometry_type in [QGis.Point, QGis.Line, QGis.Polygon]:
          parentId = ObjectTreeItem.ITEM_POINT + geometry_type    # - QGis.Point
      elif layerType == QgsMapLayer.RasterLayer and layer.providerType() == "gdal" and layer.bandCount() == 1:
        parentId = ObjectTreeItem.ITEM_OPTDEM
      if parentId is None:
        continue

      item = QTreeWidgetItem(topItems[parentId], [layer.name()])
      isVisible = self.properties[parentId].get(layer.id(), {}).get("visible", False)   #self.iface.legendInterface().isLayerVisible(layer)
      check_state = Qt.Checked if isVisible else Qt.Unchecked
      item.setData(0, Qt.CheckStateRole, check_state)
      item.setData(0, Qt.UserRole, layer.id())
      if parentId == ObjectTreeItem.ITEM_OPTDEM and isVisible:
        optDEMChecked = True

    for item in topItems:
      if item.data(0, Qt.UserRole) != ObjectTreeItem.ITEM_OPTDEM or optDEMChecked:
        tree.expandItem(item)

  def saveProperties(self, item, page):
    properties = page.properties()
    parent = item.parent()
    if parent is None:
      # top item: properties[topItemIndex]
      self.properties[item.data(0, Qt.UserRole)] = properties
    else:
      # layer item: properties[topItemIndex][layerId]
      topItemIndex = parent.data(0, Qt.UserRole)
      self.properties[topItemIndex][item.data(0, Qt.UserRole)] = properties

    if debug_mode:
      qDebug(str(self.properties))

  def currentTemplateChanged(self, index=None):
    cbox = self.ui.comboBox_Template
    templateType = cbox.itemData(cbox.currentIndex(), Qt.UserRole)
    if templateType == self.templateType:
      return

    tree = self.ui.treeWidget
    for i, name in enumerate(ObjectTreeItem.topItemNames):
      hidden = (templateType == "sphere" and name != "Controls")
      tree.topLevelItem(i).setHidden(hidden)

    itemToBeSelected = ObjectTreeItem.ITEM_CONTROLS if templateType == "sphere" else ObjectTreeItem.ITEM_DEM
    tree.setCurrentItem(tree.topLevelItem(itemToBeSelected))

    self.clearMessageBar()
    if templateType != "sphere":
      # show message if crs unit is degrees
      mapSettings = self.iface.mapCanvas().mapSettings() if QGis.QGIS_VERSION_INT >= 20300 else self.iface.mapCanvas().mapRenderer()
      if mapSettings.destinationCrs().mapUnits() in [QGis.Degrees]:
        self.showMessageBar("The unit of current CRS is degrees, so terrain will not appear well.", QgsMessageBar.WARNING)

    self.templateType = templateType

  def currentObjectChanged(self, currentItem, previousItem):
    # save properties of previous item
    if previousItem and self.currentPage:
      self.saveProperties(previousItem, self.currentPage)

    self.currentItem = currentItem
    self.currentPage = None
    # hide all pages
    for page in self.pages.itervalues():
      page.hide()

    parent = currentItem.parent()
    if parent is None:
      topItemIndex = currentItem.data(0, Qt.UserRole)
      pageType = self.topItemPages.get(topItemIndex, ppages.PAGE_NONE)
      page = self.pages.get(pageType, None)
      if page is None:
        return

      page.setup(self.properties[topItemIndex])
      page.show()

    else:
      parentId = parent.data(0, Qt.UserRole)
      layerId = currentItem.data(0, Qt.UserRole)
      if layerId is None:
        return

      layer = QgsMapLayerRegistry.instance().mapLayer(layerId)
      if layer is None:
        return

      layerType = layer.type()
      if layerType == QgsMapLayer.RasterLayer:
        page = self.pages[ppages.PAGE_DEM]
        page.setup(self.properties[parentId].get(layerId, None), layer, False)
      elif layerType == QgsMapLayer.VectorLayer:
        page = self.pages[ppages.PAGE_VECTOR]
        page.setup(self.properties[parentId].get(layerId, None), layer)
      else:
        return

      page.show()

    self.currentPage = page

  def objectItemChanged(self, item, column):
    parent = item.parent()
    if parent is None:
      return

    if item == self.currentItem:
      if self.currentPage:
        # update enablement of property widgets
        self.currentPage.itemChanged(item)
    else:
      # select changed item
      self.ui.treeWidget.setCurrentItem(item)

      # set visible property
      #visible = item.data(0, Qt.CheckStateRole) == Qt.Checked
      #parentId = parent.data(0, Qt.UserRole)
      #layerId = item.data(0, Qt.UserRole)
      #self.properties[parentId].get(layerId, {})["visible"] = visible

  def primaryDEMChanged(self, layerId):
    tree = self.ui.treeWidget
    parent = tree.topLevelItem(ObjectTreeItem.ITEM_OPTDEM)
    tree.blockSignals(True)
    for i in range(parent.childCount()):
      item = parent.child(i)
      isPrimary = item.data(0, Qt.UserRole) == layerId
      item.setDisabled(isPrimary)
    tree.blockSignals(False)

  def numericFields(self, layer):
    # get attributes of a sample feature and create numeric field name list
    numeric_fields = []
    f = QgsFeature()
    layer.getFeatures().nextFeature(f)
    for field in f.fields():
      isNumeric = False
      try:
        float(f.attribute(field.name()))
        isNumeric = True
      except:
        pass
      if isNumeric:
        numeric_fields.append(field.name())
    return numeric_fields

  def progress(self, percentage, statusMsg=None):
    ui = self.ui
    ui.progressBar.setValue(percentage)
    if percentage == 100:
      ui.progressBar.setVisible(False)
      ui.label_Status.setText("")
    else:
      ui.progressBar.setVisible(True)

    if statusMsg is not None:
      ui.label_Status.setText(statusMsg)
      ui.label_Status.repaint()

  def run(self):
    ui = self.ui
    filename = ui.lineEdit_OutputFilename.text()   # ""=Temporary file
    if filename != "" and QFileInfo(filename).exists() and QMessageBox.question(None, "Qgis2threejs", "Output file already exists. Overwrite it?", QMessageBox.Ok | QMessageBox.Cancel) != QMessageBox.Ok:
      return
    self.endPointSelection()

    # save properties of current object
    item = self.ui.treeWidget.currentItem()
    if item and self.currentPage:
      self.saveProperties(item, self.currentPage)

    ui.pushButton_Run.setEnabled(False)
    self.clearMessageBar()
    self.progress(0)

    canvas = self.iface.mapCanvas()
    cbox = self.ui.comboBox_Template
    templateName = cbox.currentText()
    templateType = cbox.itemData(cbox.currentIndex(), Qt.UserRole)
    htmlfilename = ui.lineEdit_OutputFilename.text()

    # world properties
    world = self.properties[ObjectTreeItem.ITEM_WORLD] or {}
    verticalExaggeration = world.get("lineEdit_zFactor", 1.5)
    verticalShift = world.get("lineEdit_zShift", 0)

    # export to javascript (three.js)
    mapTo3d = MapTo3D(canvas, verticalExaggeration=float(verticalExaggeration), verticalShift=float(verticalShift))
    context = OutputContext(templateName, templateType, mapTo3d, canvas, self.properties, self, self.objectTypeManager, self.localBrowsingMode)
    htmlfilename = exportToThreeJS(htmlfilename, context, self.progress)

    self.progress(100)
    ui.pushButton_Run.setEnabled(True)
    if htmlfilename is None:
      return
    self.clearRubberBands()

    # store last selections
    settings = QSettings()
    settings.setValue("/Qgis2threejs/lastTemplate", templateName)
    settings.setValue("/Qgis2threejs/lastControls", context.controls)

    # open browser
    if not tools.openHTMLFile(htmlfilename):
      return
    QDialog.accept(self)

  def reject(self):
    # save properties of current object
    item = self.ui.treeWidget.currentItem()
    if item and self.currentPage:
      self.saveProperties(item, self.currentPage)

    self.endPointSelection()
    self.clearRubberBands()
    QDialog.reject(self)

  def startPointSelection(self):
    canvas = self.iface.mapCanvas()
    if self.previousMapTool != self.mapTool:
      self.previousMapTool = canvas.mapTool()
    canvas.setMapTool(self.mapTool)
    self.pages[ppages.PAGE_DEM].toolButton_PointTool.setVisible(False)

  def endPointSelection(self):
    self.mapTool.reset()
    if self.previousMapTool is not None:
      self.iface.mapCanvas().setMapTool(self.previousMapTool)

  def mapToolSet(self, mapTool):
    return
    #TODO: unstable
    if mapTool != self.mapTool and self.currentPage is not None:
      if self.currentPage.pageType == ppages.PAGE_DEM and self.currentPage.isPrimary:
        self.currentPage.toolButton_PointTool.setVisible(True)

  def createRubberBands(self, quads, point=None):
    self.clearRubberBands()
    # create quads with rubber band
    self.rb_quads = QgsRubberBand(self.iface.mapCanvas(), QGis.Line)
    self.rb_quads.setColor(Qt.blue)
    self.rb_quads.setWidth(1)

    for quad in quads:
      points = []
      extent = quad.extent
      points.append(QgsPoint(extent.xMinimum(), extent.yMinimum()))
      points.append(QgsPoint(extent.xMinimum(), extent.yMaximum()))
      points.append(QgsPoint(extent.xMaximum(), extent.yMaximum()))
      points.append(QgsPoint(extent.xMaximum(), extent.yMinimum()))
      self.rb_quads.addGeometry(QgsGeometry.fromPolygon([points]), None)
      self.log(extent.toString())
    self.log("Quad count: %d" % len(quads))

    # create a point with rubber band
    if point:
      self.rb_point = QgsRubberBand(self.iface.mapCanvas(), QGis.Point)
      self.rb_point.setColor(Qt.red)
      self.rb_point.addPoint(point)

  def clearRubberBands(self):
    # clear quads and point
    if self.rb_quads:
      self.iface.mapCanvas().scene().removeItem(self.rb_quads)
      self.rb_quads = None
    if self.rb_point:
      self.iface.mapCanvas().scene().removeItem(self.rb_point)
      self.rb_point = None

  def browseClicked(self):
    directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0]
    if directory == "":
      directory = QDir.homePath()
    filename = QFileDialog.getSaveFileName(self, self.tr("Output filename"), directory, "HTML file (*.html *.htm)", options=QFileDialog.DontConfirmOverwrite)
    if filename != "":
      self.ui.lineEdit_OutputFilename.setText(filename)

  def log(self, msg):
    if debug_mode:
      qDebug(msg)
Example #20
0
class LatLonTools:
    def __init__(self, iface):
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.crossRb.setColor(Qt.red)

    def initGui(self):
        '''Initialize Lot Lon Tools GUI.'''
        # Initialize the Settings Dialog box
        self.settingsDialog = SettingsWidget(self, self.iface,
                                             self.iface.mainWindow())
        self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface)
        self.showMapTool = ShowOnMapTool(self.settingsDialog, self.iface)
        self.toMGRSDialog = ToMGRSWidget(self.iface, self.iface.mainWindow())
        self.MGRStoLayerDialog = MGRStoLayerWidget(self.iface,
                                                   self.iface.mainWindow())

        # Add Interface for Coordinate Capturing
        icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.png")
        self.copyAction = QAction(icon, "Copy Latitude, Longitude",
                                  self.iface.mainWindow())
        self.copyAction.triggered.connect(self.startCapture)
        self.copyAction.setCheckable(True)
        self.iface.addToolBarIcon(self.copyAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction)

        # Add Interface for Zoom to Coordinate
        icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.png")
        self.zoomToAction = QAction(icon, "Zoom To Latitude, Longitude",
                                    self.iface.mainWindow())
        self.zoomToAction.triggered.connect(self.showZoomToDialog)
        self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction)
        self.canvas.mapToolSet.connect(self.unsetTool)

        self.zoomToDialog = ZoomToLatLon(self, self.iface,
                                         self.iface.mainWindow())
        self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog)
        self.zoomToDialog.hide()

        # Add Interface for External Map
        icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png")
        self.externMapAction = QAction(icon, "Show in External Map",
                                       self.iface.mainWindow())
        self.externMapAction.triggered.connect(self.setShowMapTool)
        self.externMapAction.setCheckable(True)
        self.iface.addToolBarIcon(self.externMapAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction)

        # Add Interface for Multi point zoom
        icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.png')
        self.multiZoomToAction = QAction(icon, "Multi-location Zoom",
                                         self.iface.mainWindow())
        self.multiZoomToAction.triggered.connect(self.multiZoomTo)
        self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction)

        self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog,
                                               self.iface.mainWindow())
        self.multiZoomDialog.hide()
        self.multiZoomDialog.setFloating(True)

        # Add To MGRS conversion
        icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.png')
        icon2 = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.png')
        menu = QMenu()
        menu.addAction(icon, "MGRS to Geometry", self.MGRStoLayer)
        menu.addAction(icon2, "Geometry to MGRS", self.toMGRS)
        self.toMGRSAction = QAction(icon2, "MGRS Conversions",
                                    self.iface.mainWindow())
        self.toMGRSAction.setMenu(menu)
        self.iface.addPluginToMenu('Lat Lon Tools', self.toMGRSAction)

        # Initialize the Settings Dialog Box
        settingsicon = QIcon(
            os.path.dirname(__file__) + '/images/settings.png')
        self.settingsAction = QAction(settingsicon, "Settings",
                                      self.iface.mainWindow())
        self.settingsAction.triggered.connect(self.settings)
        self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction)

        # Help
        helpicon = QIcon(os.path.dirname(__file__) + '/images/help.png')
        self.helpAction = QAction(helpicon, "Help", self.iface.mainWindow())
        self.helpAction.triggered.connect(self.help)
        self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction)

    def unsetTool(self, tool):
        '''Uncheck the Copy Lat Lon tool'''
        try:
            if not isinstance(tool, CopyLatLonTool):
                self.copyAction.setChecked(False)
                self.multiZoomDialog.stopCapture()
                self.mapTool.capture4326 = False
            if not isinstance(tool, ShowOnMapTool):
                self.externMapAction.setChecked(False)
        except:
            pass

    def unload(self):
        '''Unload LatLonTools from the QGIS interface'''
        self.zoomToDialog.removeMarker()
        self.multiZoomDialog.removeMarkers()
        self.canvas.unsetMapTool(self.mapTool)
        self.canvas.unsetMapTool(self.showMapTool)
        self.iface.removePluginMenu('Lat Lon Tools', self.copyAction)
        self.iface.removeToolBarIcon(self.copyAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction)
        self.iface.removeToolBarIcon(self.externMapAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.toMGRSAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.helpAction)
        self.iface.removeDockWidget(self.zoomToDialog)
        self.iface.removeDockWidget(self.multiZoomDialog)
        self.zoomToDialog = None
        self.multiZoomDialog = None

    def startCapture(self):
        '''Set the focus of the copy coordinate tool and check it'''
        self.copyAction.setChecked(True)
        self.canvas.setMapTool(self.mapTool)

    def setShowMapTool(self):
        '''Set the focus of the external map tool and check it'''
        self.externMapAction.setChecked(True)
        self.canvas.setMapTool(self.showMapTool)

    def showZoomToDialog(self):
        '''Show the zoom to docked widget.'''
        self.zoomToDialog.show()

    def multiZoomTo(self):
        '''Display the Multi-zoom to dialog box'''
        self.multiZoomDialog.show()

    def toMGRS(self):
        '''Display the to MGRS  dialog box'''
        self.toMGRSDialog.show()

    def MGRStoLayer(self):
        '''Display the to MGRS  dialog box'''
        self.MGRStoLayerDialog.show()

    def settings(self):
        '''Show the settings dialog box'''
        self.settingsDialog.show()

    def help(self):
        '''Display a help page'''
        url = QUrl.fromLocalFile(os.path.dirname(__file__) +
                                 "/index.html").toString()
        webbrowser.open(url, new=2)

    def settingsChanged(self):
        # Settings may have changed so we need to make sure the zoomToDialog window is configured properly
        self.zoomToDialog.configure()

    def zoomTo(self, srcCrs, lat, lon):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(srcCrs, canvasCrs)
        x, y = transform.transform(float(lon), float(lat))

        rect = QgsRectangle(x, y, x, y)
        self.canvas.setExtent(rect)

        pt = QgsPoint(x, y)
        self.highlight(pt)
        self.canvas.refresh()
        return pt

    def highlight(self, point):
        currExt = self.canvas.extent()

        leftPt = QgsPoint(currExt.xMinimum(), point.y())
        rightPt = QgsPoint(currExt.xMaximum(), point.y())

        topPt = QgsPoint(point.x(), currExt.yMaximum())
        bottomPt = QgsPoint(point.x(), currExt.yMinimum())

        horizLine = QgsGeometry.fromPolyline([leftPt, rightPt])
        vertLine = QgsGeometry.fromPolyline([topPt, bottomPt])

        self.crossRb.reset(QgsWkbTypes.LineGeometry)
        self.crossRb.addGeometry(horizLine, None)
        self.crossRb.addGeometry(vertLine, None)

        QTimer.singleShot(700, self.resetRubberbands)

    def resetRubberbands(self):
        self.crossRb.reset()
Example #21
0
class EditTool(MapTool):
    """
        Inspection tool which copies the feature to a new layer
        and copies selected data from the underlying feature.
    """
    
    finished = pyqtSignal(QgsVectorLayer, QgsFeature)
    
    def __init__(self, canvas, layers, snapradius = 2):
        MapTool.__init__(self, canvas, layers)
        self.canvas = canvas
        self.radius = snapradius
        
        self.band = QgsRubberBand(self.canvas)
        self.band.setColor(QColor.fromRgb(224,162,16))
        self.band.setWidth(3)

        self.cursor = QCursor(QPixmap(["16 16 3 1",
            "      c None",
            ".     c #FF0000",
            "+     c #FFFFFF",
            "                ",
            "       +.+      ",
            "      ++.++     ",
            "     +.....+    ",
            "    +.     .+   ",
            "   +.   .   .+  ",
            "  +.    .    .+ ",
            " ++.    .    .++",
            " ... ...+... ...",
            " ++.    .    .++",
            "  +.    .    .+ ",
            "   +.   .   .+  ",
            "   ++.     .+   ",
            "    ++.....+    ",
            "      ++.++     ",
            "       +.+      "]))
        
    def getFeatures(self, point):
        searchRadius = (QgsTolerance.toleranceInMapUnits( self.radius, self.layers[0],
                                                        self.canvas.mapRenderer(), 
                                                        QgsTolerance.Pixels))
        point = self.toMapCoordinates(point)

        rect = QgsRectangle()                                                 
        rect.setXMinimum(point.x() - searchRadius)
        rect.setXMaximum(point.x() + searchRadius)
        rect.setYMinimum(point.y() - searchRadius)
        rect.setYMaximum(point.y() + searchRadius)
        
        rq = QgsFeatureRequest().setFilterRect(rect)

        self.band.reset()
        for layer in self.layers:
            rq = QgsFeatureRequest().setFilterRect(rect) 
            for feature in layer.getFeatures(rq):
                if feature.isValid():
                    yield feature, layer
        
    def canvasMoveEvent(self, event):
        for feature, _ in self.getFeatures(point = event.pos()):
            self.band.addGeometry(feature.geometry(), None)
                     
    def canvasReleaseEvent(self, event):
        features = list(self.getFeatures(point = event.pos()))
            
        if len(features) == 1:
            feature = features[0]
            self.finished.emit(feature[1], feature[0])
        elif len(features) > 0:
            listUi = ListFeaturesForm()
            listUi.loadFeatureList(features)
            listUi.openFeatureForm.connect(self.finished)
            listUi.exec_()
        
    def activate(self):
        """
        Set the tool as the active tool in the canvas. 

        @note: Should be moved out into qmap.py 
               and just expose a cursor to be used
        """
        self.canvas.setCursor(self.cursor)

    def deactivate(self):
        """
        Deactive the tool.
        """
        pass

    def isZoomTool(self):
        return False

    def isTransient(self):
        return False

    def isEditTool(self):
        return True
Example #22
0
class AdvancedIntersectionMapTool(QgsMapTool):
    def __init__(self, iface):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        QgsMapTool.__init__(self, self.mapCanvas)
        self.settings = MySettings()
        self.rubber = QgsRubberBand(self.mapCanvas)

        self.tolerance = self.settings.value("selectTolerance")
        units = self.settings.value("selectUnits")
        if units == "pixels":
            self.tolerance *= self.mapCanvas.mapUnitsPerPixel()

    def activate(self):
        QgsMapTool.activate(self)
        self.rubber.setWidth(self.settings.value("rubberWidth"))
        self.rubber.setColor(self.settings.value("rubberColor"))
        lineLayer = MemoryLayers(self.iface).lineLayer()
        # unset this tool if the layer is removed
        lineLayer.layerDeleted.connect(self.unsetMapTool)
        self.layerId = lineLayer.id()
        # create snapper for this layer
        self.snapLayer = QgsSnapper.SnapLayer()
        self.snapLayer.mLayer = lineLayer
        self.snapLayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment
        self.snapLayer.mTolerance = self.settings.value("selectTolerance")
        if self.settings.value("selectUnits") == "map":
            self.snapLayer.mUnitType = QgsTolerance.MapUnits
        else:
            self.snapLayer.mUnitType = QgsTolerance.Pixels

    def unsetMapTool(self):
        self.mapCanvas.unsetMapTool(self)

    def deactivate(self):
        self.rubber.reset()
        lineLayer = QgsMapLayerRegistry.instance().mapLayer(self.layerId)
        if lineLayer is not None:
            lineLayer.layerDeleted.disconnect(self.unsetMapTool)
        QgsMapTool.deactivate(self)

    def canvasMoveEvent(self, mouseEvent):
        # put the observations within tolerance in the rubber band
        self.rubber.reset()
        for f in self.getFeatures(mouseEvent.pos()):
            self.rubber.addGeometry(f.geometry(), None)

    def canvasPressEvent(self, mouseEvent):
        pos = mouseEvent.pos()
        observations = self.getFeatures(pos)
        point = self.toMapCoordinates(pos)
        self.doIntersection(point, observations)

    def getFeatures(self, pixPoint):
        snapper = QgsSnapper(self.mapCanvas.mapRenderer())
        snapper.setSnapLayers([self.snapLayer])
        snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances)
        ok, snappingResults = snapper.snapPoint(pixPoint, [])
        # output snapped features
        features = []
        alreadyGot = []
        for result in snappingResults:
            featureId = result.snappedAtGeometry
            f = QgsFeature()
            if featureId not in alreadyGot:
                if result.layer.getFeatures(QgsFeatureRequest().setFilterFid(
                        featureId)).nextFeature(f) is not False:
                    features.append(QgsFeature(f))
                    alreadyGot.append(featureId)
        return features

    def doIntersection(self, initPoint, observations):
        nObs = len(observations)
        if nObs < 2:
            return
        self.rubber.reset()
        self.dlg = IntersectionDialog(self.iface, observations, initPoint)
        if not self.dlg.exec_() or self.dlg.solution is None:
            return
        intersectedPoint = self.dlg.solution
        self.saveIntersectionResult(self.dlg.report, intersectedPoint)
        self.saveDimension(intersectedPoint, self.dlg.observations)

    def saveIntersectionResult(self, report, intersectedPoint):
        # save the intersection result (point) and its report
        # check first
        while True:
            if not self.settings.value("advancedIntersectionWritePoint"):
                break  # if we do not place any point, skip
            layerid = self.settings.value("advancedIntersectionLayer")
            message = QCoreApplication.translate(
                "IntersectIt", "To place the intersection solution,"
                " you must select a layer in the settings.")
            status, intLayer = self.checkLayerExists(layerid, message)
            if status == 2:
                continue
            if status == 3:
                return
            if self.settings.value("advancedIntersectionWriteReport"):
                reportField = self.settings.value("reportField")
                message = QCoreApplication.translate(
                    "IntersectIt",
                    "To save the intersection report, please select a field for it."
                )
                status = self.checkFieldExists(intLayer, reportField, message)
                if status == 2:
                    continue
                if status == 3:
                    return
            break
        # save the intersection results
        if self.settings.value("advancedIntersectionWritePoint"):
            f = QgsFeature()
            f.setGeometry(QgsGeometry().fromPoint(intersectedPoint))
            if self.settings.value("advancedIntersectionWriteReport"):
                irep = intLayer.dataProvider().fieldNameIndex(reportField)
                f.addAttribute(irep, report)
            intLayer.dataProvider().addFeatures([f])
            intLayer.updateExtents()
            self.mapCanvas.refresh()

    def saveDimension(self, intersectedPoint, observations):
        # check that dimension layer and fields have been set correctly
        if not self.settings.value(
                "dimensionDistanceWrite") and not self.settings.value(
                    "dimensionOrientationWrite"):
            return  # if we do not place any dimension, skip
        obsTypes = ("Distance", "Orientation")
        recheck = True
        while recheck:
            # settings might change during checking,
            # so recheck both observation types whenever the settings dialog is shown
            recheck = False
            for obsType in obsTypes:
                while True:
                    if not self.settings.value("dimension" + obsType +
                                               "Write"):
                        break
                    # check layer
                    layerId = self.settings.value("dimension" + obsType +
                                                  "Layer")
                    message = QCoreApplication.translate(
                        "IntersectIt", "To place dimensions, "
                        "you must define a layer in the settings.")
                    status, dimLayer = self.checkLayerExists(layerId, message)
                    if status == 2:
                        recheck = True
                        continue
                    if status == 3:
                        return
                    # check fields
                    if self.settings.value("dimension" + obsType +
                                           "ObservationWrite"):
                        obsField = self.settings.value("dimension" + obsType +
                                                       "ObservationField")
                        message = QCoreApplication.translate(
                            "IntersectIt",
                            "To save the observation in the layer,"
                            " please select a field for it.")
                        status = self.checkFieldExists(dimLayer, obsField,
                                                       message)
                        if status == 2:
                            recheck = True
                            continue
                        if status == 3:
                            return
                    if self.settings.value("dimension" + obsType +
                                           "PrecisionWrite"):
                        precisionField = self.settings.value("dimension" +
                                                             obsType +
                                                             "PrecisionField")
                        message = QCoreApplication.translate(
                            "IntersectIt",
                            "To save the precision of observation,"
                            " please select a field for it.")
                        status = self.checkFieldExists(dimLayer,
                                                       precisionField, message)
                        if status == 2:
                            recheck = True
                            continue
                        if status == 3:
                            return
                    break
        # save the intersection results
        for obsType in obsTypes:
            if self.settings.value("dimension" + obsType + "Write"):
                layerid = self.settings.value("dimension" + obsType + "Layer")
                layer = QgsMapLayerRegistry.instance().mapLayer(layerid)
                if layer is None:
                    continue
                initFields = layer.dataProvider().fields()
                features = []
                for obs in observations:
                    if obs["type"] != obsType.lower():
                        continue
                    f = QgsFeature()
                    f.setFields(initFields)
                    f.initAttributes(initFields.size())
                    if self.settings.value("dimension" + obsType +
                                           "ObservationWrite"):
                        f[self.settings.value(
                            "dimension" + obsType +
                            "ObservationField")] = obs["observation"]
                    if self.settings.value("dimension" + obsType +
                                           "PrecisionWrite"):
                        f[self.settings.value(
                            "dimension" + obsType +
                            "PrecisionField")] = obs["precision"]
                    p0 = QgsPoint(obs["x"], obs["y"])
                    p1 = intersectedPoint
                    if obs["type"] == "distance":
                        geom = Arc(p0, p1).geometry()
                    elif obs["type"] == "orientation":
                        geom = QgsGeometry().fromPolyline([p0, p1])
                    else:
                        raise NameError("Invalid observation %s" % obs["type"])
                    f.setGeometry(geom)
                    features.append(QgsFeature(f))
                if not layer.dataProvider().addFeatures(features):
                    self.iface.messageBar().pushMessage(
                        "Could not commit %s observations" % obsType,
                        QgsMessageBar.CRITICAL)
                layer.updateExtents()
        self.mapCanvas.refresh()

    def checkLayerExists(self, layerid, message):
        # returns:
        # 1: layer exists
        # 2: does not exist, settings has been open, so loop once more (i.e. continue)
        # 3: does not exist, settings not edited, so cancel
        layer = QgsMapLayerRegistry.instance().mapLayer(layerid)
        if layer is not None:
            return 1, layer

        reply = QMessageBox.question(
            self.iface.mainWindow(), "Intersect It",
            message + " Would you like to open settings?", QMessageBox.Yes,
            QMessageBox.No)
        if reply == QMessageBox.Yes:
            if MySettingsDialog().exec_():
                return 2
        return 3

    def checkFieldExists(self, layer, field, message):
        # returns:
        # 1: field exists
        # 2: does not exist, settings has been open, so loop once more (i.e. continue)
        # 3: does not exist, settings not edited, so cancel
        if layer.dataProvider().fieldNameIndex(field) != -1:
            return 1

        reply = QMessageBox.question(
            self.iface.mainWindow(), "Intersect It",
            message + " Would you like to open settings?", QMessageBox.Yes,
            QMessageBox.No)
        if reply == QMessageBox.Yes:
            if MySettingsDialog().exec_():
                return 2
        return 3
class SwissLocatorFilter(QgsLocatorFilter):

    HEADERS = {
        b'User-Agent': b'Mozilla/5.0 QGIS Swiss Geoportal Locator Filter'
    }

    message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget)

    def __init__(self,
                 filter_type: FilterType,
                 iface: QgisInterface = None,
                 crs: str = None):
        """"
        :param filter_type: the type of filter
        :param locale_lang: the language of the locale.
        :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise
        :param crs: if iface is not given, it shall be provided, see clone()
        """
        super().__init__()
        self.type = filter_type
        self.rubber_band = None
        self.feature_rubber_band = None
        self.iface = iface
        self.map_canvas = None
        self.settings = Settings()
        self.transform_ch = None
        self.transform_4326 = None
        self.map_tip = None
        self.current_timer = None
        self.crs = None
        self.event_loop = None
        self.result_found = False
        self.access_managers = {}
        self.nam_map_tip = None
        self.nam_fetch_feature = None

        if crs:
            self.crs = crs

        self.lang = get_language()

        self.searchable_layers = searchable_layers(self.lang, restrict=True)

        if iface is not None:
            # happens only in main thread
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transforms)

            self.rubber_band = QgsRubberBand(self.map_canvas,
                                             QgsWkbTypes.PointGeometry)
            self.rubber_band.setColor(QColor(255, 255, 50, 200))
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.feature_rubber_band = QgsRubberBand(
                self.map_canvas, QgsWkbTypes.PolygonGeometry)
            self.feature_rubber_band.setColor(QColor(255, 50, 50, 200))
            self.feature_rubber_band.setFillColor(QColor(255, 255, 50, 160))
            self.feature_rubber_band.setBrushStyle(Qt.SolidPattern)
            self.feature_rubber_band.setLineStyle(Qt.SolidLine)
            self.feature_rubber_band.setWidth(4)

            self.create_transforms()

    def name(self):
        return '{}_{}'.format(self.__class__.__name__,
                              FilterType(self.type).name)

    def clone(self):
        return SwissLocatorFilter(self.type, crs=self.crs)

    def priority(self):
        return self.settings.value(
            '{type}_priority'.format(type=self.type.value))

    def displayName(self):
        if self.type is FilterType.Location:
            return self.tr('Swiss Geoportal locations')
        elif self.type is FilterType.WMS:
            return self.tr('Swiss Geoportal / opendata.swiss WMS layers')
        elif self.type is FilterType.Feature:
            return self.tr('Swiss Geoportal features')
        else:
            raise NameError('Filter type is not valid.')

    def prefix(self):
        if self.type is FilterType.Location:
            return 'chl'
        elif self.type is FilterType.WMS:
            return 'chw'
        elif self.type is FilterType.Feature:
            return 'chf'
        else:
            raise NameError('Filter type is not valid.')

    def clearPreviousResults(self):
        self.rubber_band.reset(QgsWkbTypes.PointGeometry)
        self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry)
        if self.map_tip is not None:
            del self.map_tip
            self.map_tip = None
        if self.current_timer is not None:
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def hasConfigWidget(self):
        return True

    def openConfigWidget(self, parent=None):
        dlg = ConfigDialog(parent)
        wid = dlg.findChild(QTabWidget, "tabWidget", Qt.FindDirectChildrenOnly)
        tab = wid.findChild(QWidget, self.type.value)
        wid.setCurrentWidget(tab)
        dlg.exec_()

    def create_transforms(self):
        # this should happen in the main thread
        self.crs = self.settings.value('crs')
        if self.crs == 'project':
            map_crs = self.map_canvas.mapSettings().destinationCrs()
            if map_crs.isValid():
                self.crs = map_crs.authid().split(':')[1]
            if self.crs not in AVAILABLE_CRS:
                self.crs = '2056'
        assert self.crs in AVAILABLE_CRS
        src_crs_ch = QgsCoordinateReferenceSystem('EPSG:{}'.format(self.crs))
        assert src_crs_ch.isValid()
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        self.transform_ch = QgsCoordinateTransform(src_crs_ch, dst_crs,
                                                   QgsProject.instance())

        src_crs_4326 = QgsCoordinateReferenceSystem('EPSG:4326')
        self.transform_4326 = QgsCoordinateTransform(src_crs_4326, dst_crs,
                                                     QgsProject.instance())

    def group_info(self, group: str) -> (str, str):
        groups = {
            'zipcode': {
                'name': self.tr('ZIP code'),
                'layer': 'ch.swisstopo-vd.ortschaftenverzeichnis_plz'
            },
            'gg25': {
                'name': self.tr('Municipal boundaries'),
                'layer': 'ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill'
            },
            'district': {
                'name': self.tr('District'),
                'layer': 'ch.swisstopo.swissboundaries3d-bezirk-flaeche.fill'
            },
            'kantone': {
                'name': self.tr('Cantons'),
                'layer': 'ch.swisstopo.swissboundaries3d-kanton-flaeche.fill'
            },
            'gazetteer': {
                'name': self.tr('Index'),
                'layer': 'ch.swisstopo.swissnames3d'
            },  # there is also: ch.bav.haltestellen-oev ?
            'address': {
                'name': self.tr('Address'),
                'layer': 'ch.bfs.gebaeude_wohnungs_register'
            },
            'parcel': {
                'name': self.tr('Parcel'),
                'layer': None
            }
        }
        if group not in groups:
            self.info('Could not find group {} in dictionary'.format(group))
            return None, None
        return groups[group]['name'], groups[group]['layer']

    @staticmethod
    def rank2priority(rank) -> float:
        """
        Translate the rank from geoportal to the priority of the result
        see https://api3.geo.admin.ch/services/sdiservices.html#search
        :param rank: an integer from 1 to 7
        :return: the priority as a float from 0 to 1, 1 being a perfect match
        """
        return float(-rank / 7 + 1)

    @staticmethod
    def box2geometry(box: str) -> QgsRectangle:
        """
        Creates a rectangle from a Box definition as string
        :param box: the box as a string
        :return: the rectangle
        """
        coords = re.findall(r'\b(\d+(?:\.\d+)?)\b', box)
        if len(coords) != 4:
            raise InvalidBox('Could not parse: {}'.format(box))
        return QgsRectangle(float(coords[0]), float(coords[1]),
                            float(coords[2]), float(coords[3]))

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def fetchResults(self, search: str, context: QgsLocatorContext,
                     feedback: QgsFeedback):
        try:
            self.dbg_info("start Swiss locator search...")

            if len(search) < 2:
                return

            if len(search) < 4 and self.type is FilterType.Feature:
                return

            self.result_found = False

            swisstopo_base_url = 'https://api3.geo.admin.ch/rest/services/api/SearchServer'
            swisstopo_base_params = {
                'type':
                self.type.value,
                'searchText':
                str(search),
                'returnGeometry':
                'true',
                'lang':
                self.lang,
                'sr':
                self.crs,
                'limit':
                str(
                    self.settings.value(
                        '{type}_limit'.format(type=self.type.value)))
                # bbox Must be provided if the searchText is not.
                # A comma separated list of 4 coordinates representing
                # the bounding box on which features should be filtered (SRID: 21781).
            }
            # Locations, WMS layers
            if self.type is not FilterType.Feature:
                nam = NetworkAccessManager()
                feedback.canceled.connect(nam.abort)

                search_urls = [(swisstopo_base_url, swisstopo_base_params)]

                if self.settings.value('layers_include_opendataswiss'
                                       ) and self.type is FilterType.WMS:
                    search_urls.append(
                        ('https://opendata.swiss/api/3/action/package_search?',
                         {
                             'q': 'q=WMS+%C3' + str(search)
                         }))

                for (swisstopo_base_url, swisstopo_base_params) in search_urls:
                    swisstopo_base_url = self.url_with_param(
                        swisstopo_base_url, swisstopo_base_params)
                    self.dbg_info(swisstopo_base_url)
                    try:
                        (response, content) = nam.request(swisstopo_base_url,
                                                          headers=self.HEADERS,
                                                          blocking=True)
                        self.handle_response(response, search, feedback)
                    except RequestsExceptionUserAbort:
                        pass
                    except RequestsException as err:
                        self.info(err)

            # Feature search
            else:
                # Feature search is split in several requests
                # otherwise URL is too long
                self.access_managers = {}
                try:
                    layers = list(self.searchable_layers.keys())
                    assert len(layers) > 0
                    step = 30
                    for l in range(0, len(layers), step):
                        last = min(l + step - 1, len(layers) - 1)
                        swisstopo_base_params['features'] = ','.join(
                            layers[l:last])
                        self.access_managers[self.url_with_param(
                            swisstopo_base_url, swisstopo_base_params)] = None
                except IOError:
                    self.info(
                        'Layers data file not found. Please report an issue.',
                        Qgis.Critical)

                # init event loop
                # wait for all requests to end
                self.event_loop = QEventLoop()

                def reply_finished(response):
                    self.handle_response(response, search, feedback)
                    if response.url in self.access_managers:
                        self.access_managers[response.url] = None
                    for nam in self.access_managers.values():
                        if nam is not None:
                            return
                        self.event_loop.quit()

                feedback.canceled.connect(self.event_loop.quit)

                # init the network access managers, create the URL
                for swisstopo_base_url in self.access_managers:
                    self.dbg_info(swisstopo_base_url)
                    nam = NetworkAccessManager()
                    self.access_managers[swisstopo_base_url] = nam
                    nam.finished.connect(reply_finished)
                    nam.request(swisstopo_base_url,
                                headers=self.HEADERS,
                                blocking=False)
                    feedback.canceled.connect(nam.abort)

                # Let the requests end and catch all exceptions (and clean up requests)
                if len(self.access_managers) > 0:
                    try:
                        self.event_loop.exec_(
                            QEventLoop.ExcludeUserInputEvents)
                    except RequestsExceptionUserAbort:
                        pass
                    except RequestsException as err:
                        self.info(str(err))

            if not self.result_found:
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = self.tr('No result found.')
                result.userData = NoResult().as_definition()
                self.resultFetched.emit(result)

        except Exception as e:
            self.info(e, Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def handle_response(self, response, search: str, feedback: QgsFeedback):
        try:
            if response.status_code != 200:
                if not isinstance(response.exception,
                                  RequestsExceptionUserAbort):
                    self.info(
                        "Error in main response with status code: {} from {}".
                        format(response.status_code, response.url))
                return

            data = json.loads(response.content.decode('utf-8'))
            # self.dbg_info(data)

            if self.is_opendata_swiss_response(data):
                visited_capabilities = []

                for loc in data['result']['results']:
                    display_name = loc['title'].get(self.lang, "")
                    if not display_name:
                        # Fallback to german
                        display_name = loc['title']['de']

                    for res in loc['resources']:

                        url = res['url']
                        url_components = urlparse(url)
                        wms_url = url_components.scheme + '://' + url_components.netloc + '/' + url_components.path + '?'

                        result = QgsLocatorResult()
                        result.filter = self
                        result.group = 'opendata.swiss'
                        result.icon = QgsApplication.getThemeIcon(
                            "/mActionAddWmsLayer.svg")

                        if 'wms' in url.lower():
                            if res['media_type'] == 'WMS':
                                result.displayString = display_name
                                result.description = url

                                if res['title']['de'] == 'GetMap':
                                    layers = parse_qs(
                                        url_components.query)['LAYERS']
                                    result.userData = WMSLayerResult(
                                        layer=layers[0],
                                        title=display_name,
                                        url=wms_url).as_definition()
                                    self.result_found = True
                                    self.resultFetched.emit(result)

                            elif 'request=getcapabilities' in url.lower(
                            ) and url_components.netloc not in visited_capabilities:
                                visited_capabilities.append(
                                    url_components.netloc)

                                def parse_capabilities_result(response):
                                    capabilities = ET.fromstring(
                                        response.content)

                                    # Get xml namespace
                                    match = re.match(r'\{.*\}',
                                                     capabilities.tag)
                                    namespace = match.group(0) if match else ''

                                    # Search for layers containing the search term in the name or title
                                    for layer in capabilities.findall(
                                            './/{}Layer'.format(namespace)):
                                        layername = self.find_text(
                                            layer, '{}Name'.format(namespace))
                                        layertitle = self.find_text(
                                            layer, '{}Title'.format(namespace))
                                        if layername and (
                                                search in layername.lower() or
                                                search in layertitle.lower()):
                                            if not layertitle:
                                                layertitle = layername

                                            result.displayString = layertitle
                                            result.description = '{}?LAYERS={}'.format(
                                                url.replace(
                                                    'GetCapabilities',
                                                    'GetMap'), layername)
                                            result.userData = WMSLayerResult(
                                                layer=layername,
                                                title=layertitle,
                                                url=wms_url).as_definition()
                                            self.result_found = True
                                            self.resultFetched.emit(result)

                                    self.event_loop.quit()

                                # Retrieve Capabilities xml
                                self.event_loop = QEventLoop()
                                nam = NetworkAccessManager()
                                nam.finished.connect(parse_capabilities_result)
                                nam.request(url,
                                            headers=self.HEADERS,
                                            blocking=False)
                                feedback.canceled.connect(self.event_loop.quit)

                                try:
                                    self.event_loop.exec_(
                                        QEventLoop.ExcludeUserInputEvents)
                                except RequestsExceptionUserAbort:
                                    pass
                                except RequestsException as err:
                                    self.info(err)

            else:
                for loc in data['results']:
                    self.dbg_info("keys: {}".format(loc['attrs'].keys()))

                    result = QgsLocatorResult()
                    result.filter = self
                    result.group = 'Swiss Geoportal'
                    if loc['attrs']['origin'] == 'layer':
                        # available keys: ['origin', 'lang', 'layer', 'staging', 'title', 'topics', 'detail', 'label', 'id']
                        for key, val in loc['attrs'].items():
                            self.dbg_info('{}: {}'.format(key, val))
                        result.displayString = loc['attrs']['title']
                        result.description = loc['attrs']['layer']
                        result.userData = WMSLayerResult(
                            layer=loc['attrs']['layer'],
                            title=loc['attrs']['title'],
                            url='http://wms.geo.admin.ch/?VERSION%3D2.0.0'
                        ).as_definition()
                        result.icon = QgsApplication.getThemeIcon(
                            "/mActionAddWmsLayer.svg")
                        self.result_found = True
                        self.resultFetched.emit(result)

                    elif loc['attrs']['origin'] == 'feature':
                        for key, val in loc['attrs'].items():
                            self.dbg_info('{}: {}'.format(key, val))
                        layer = loc['attrs']['layer']
                        point = QgsPointXY(loc['attrs']['lon'],
                                           loc['attrs']['lat'])
                        if layer in self.searchable_layers:
                            layer_display = self.searchable_layers[layer]
                        else:
                            self.info(
                                self.
                                tr('Layer {} is not in the list of searchable layers.'
                                   ' Please report issue.'.format(layer)),
                                Qgis.Warning)
                            layer_display = layer
                        result.group = layer_display
                        result.displayString = loc['attrs']['detail']
                        result.userData = FeatureResult(
                            point=point,
                            layer=layer,
                            feature_id=loc['attrs']
                            ['feature_id']).as_definition()
                        result.icon = QIcon(
                            ":/plugins/swiss_locator/icons/swiss_locator.png")
                        self.result_found = True
                        self.resultFetched.emit(result)

                    else:  # locations
                        for key, val in loc['attrs'].items():
                            self.dbg_info('{}: {}'.format(key, val))
                        group_name, group_layer = self.group_info(
                            loc['attrs']['origin'])
                        if 'layerBodId' in loc['attrs']:
                            self.dbg_info("layer: {}".format(
                                loc['attrs']['layerBodId']))
                        if 'featureId' in loc['attrs']:
                            self.dbg_info("feature: {}".format(
                                loc['attrs']['featureId']))

                        result.displayString = strip_tags(
                            loc['attrs']['label'])
                        # result.description = loc['attrs']['detail']
                        # if 'featureId' in loc['attrs']:
                        #     result.description = loc['attrs']['featureId']
                        result.group = group_name
                        result.userData = LocationResult(
                            point=QgsPointXY(loc['attrs']['y'],
                                             loc['attrs']['x']),
                            bbox=self.box2geometry(
                                loc['attrs']['geom_st_box2d']),
                            layer=group_layer,
                            feature_id=loc['attrs']['featureId']
                            if 'featureId' in loc['attrs'] else None,
                            html_label=loc['attrs']['label']).as_definition()
                        result.icon = QIcon(
                            ":/plugins/swiss_locator/icons/swiss_locator.png")
                        self.result_found = True
                        self.resultFetched.emit(result)

        except Exception as e:
            self.info(str(e), Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            self.info(
                '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno),
                Qgis.Critical)
            self.info(
                traceback.print_exception(exc_type, exc_obj, exc_traceback),
                Qgis.Critical)

    def triggerResult(self, result: QgsLocatorResult):
        # this should be run in the main thread, i.e. mapCanvas should not be None

        # remove any map tip
        self.clearPreviousResults()

        user_data = NoResult
        try:
            swiss_result = result_from_data(result)
        except SystemError:
            self.message_emitted.emit(
                self.displayName(),
                self.
                tr('QGIS Swiss Locator encountered an error. Please <b>update to QGIS 3.16.2</b> or newer.'
                   ), Qgis.Warning, None)

        if type(swiss_result) == NoResult:
            return

        # WMS
        if type(swiss_result) == WMSLayerResult:
            url_with_params = 'contextualWMSLegend=0' \
                              '&crs=EPSG:{crs}' \
                              '&dpiMode=7' \
                              '&featureCount=10' \
                              '&format=image/png' \
                              '&layers={layer}' \
                              '&styles=' \
                              '&url={url}' \
                .format(crs=self.crs, layer=swiss_result.layer, url=swiss_result.url)
            wms_layer = QgsRasterLayer(url_with_params, result.displayString,
                                       'wms')
            label = QLabel()
            label.setTextFormat(Qt.RichText)
            label.setTextInteractionFlags(Qt.TextBrowserInteraction)
            label.setOpenExternalLinks(True)

            if 'geo.admin.ch' in swiss_result.url.lower():
                label.setText(
                    '<a href="https://map.geo.admin.ch/'
                    '?lang={}&bgLayer=ch.swisstopo.pixelkarte-farbe&layers={}">'
                    'Open layer in map.geo.admin.ch</a>'.format(
                        self.lang, swiss_result.layer))

            if not wms_layer.isValid():
                msg = self.tr('Cannot load WMS layer: {} ({})'.format(
                    swiss_result.title, swiss_result.layer))
                level = Qgis.Warning
                self.info(msg, level)
            else:
                msg = self.tr('WMS layer added to the map: {} ({})'.format(
                    swiss_result.title, swiss_result.layer))
                level = Qgis.Info

                QgsProject.instance().addMapLayer(wms_layer)

            self.message_emitted.emit(self.displayName(), msg, level, label)

        # Feature
        elif type(swiss_result) == FeatureResult:
            point = QgsGeometry.fromPointXY(swiss_result.point)
            point.transform(self.transform_4326)
            self.highlight(point)
            if self.settings.value('show_map_tip'):
                self.show_map_tip(swiss_result.layer, swiss_result.feature_id,
                                  point)
        # Location
        else:
            point = QgsGeometry.fromPointXY(swiss_result.point)
            if swiss_result.bbox.isNull():
                bbox = None
            else:
                bbox = QgsGeometry.fromRect(swiss_result.bbox)
                bbox.transform(self.transform_ch)
            layer = swiss_result.layer
            feature_id = swiss_result.feature_id
            if not point:
                return

            point.transform(self.transform_ch)

            self.highlight(point, bbox)

            if layer and feature_id:
                self.fetch_feature(layer, feature_id)

                if self.settings.value('show_map_tip'):
                    self.show_map_tip(layer, feature_id, point)
            else:
                self.current_timer = QTimer()
                self.current_timer.timeout.connect(self.clearPreviousResults)
                self.current_timer.setSingleShot(True)
                self.current_timer.start(5000)

    def highlight(self, point, bbox=None):
        if bbox is None:
            bbox = point
        self.rubber_band.reset(QgsWkbTypes.PointGeometry)
        self.rubber_band.addGeometry(point, None)
        rect = bbox.boundingBox()
        rect.scale(1.1)
        self.map_canvas.setExtent(rect)
        self.map_canvas.refresh()

    def fetch_feature(self, layer, feature_id):
        # Try to get more info
        self.nam_fetch_feature = NetworkAccessManager()
        url_detail = 'https://api3.geo.admin.ch/rest/services/api/MapServer/{layer}/{feature_id}' \
            .format(layer=layer, feature_id=feature_id)
        params = {'lang': self.lang, 'sr': self.crs}
        url_detail = self.url_with_param(url_detail, params)
        self.dbg_info(url_detail)
        self.nam_fetch_feature.finished.connect(self.parse_feature_response)
        self.nam_fetch_feature.request(url_detail,
                                       headers=self.HEADERS,
                                       blocking=False)

    def parse_feature_response(self, response):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                self.info(
                    "Error in feature response with status code: {} from {}".
                    format(response.status_code, response.url))
            return

        data = json.loads(response.content.decode('utf-8'))
        self.dbg_info(data)

        if 'feature' not in data or 'geometry' not in data['feature']:
            return

        if 'rings' in data['feature']['geometry']:
            rings = data['feature']['geometry']['rings']
            self.dbg_info(rings)
            for r in range(0, len(rings)):
                for p in range(0, len(rings[r])):
                    rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1])
            geometry = QgsGeometry.fromPolygonXY(rings)
            geometry.transform(self.transform_ch)

            self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry)
            self.feature_rubber_band.addGeometry(geometry, None)

    def show_map_tip(self, layer, feature_id, point):
        if layer and feature_id:
            url_html = 'https://api3.geo.admin.ch/rest/services/api/MapServer/{layer}/{feature_id}/htmlPopup' \
                .format(layer=layer, feature_id=feature_id)
            params = {'lang': self.lang, 'sr': self.crs}
            url_html = self.url_with_param(url_html, params)
            self.dbg_info(url_html)

            self.nam_map_tip = NetworkAccessManager()
            self.nam_map_tip.finished.connect(
                lambda response: self.parse_map_tip_response(response, point))
            self.nam_map_tip.request(url_html,
                                     headers=self.HEADERS,
                                     blocking=False)

    def parse_map_tip_response(self, response, point):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                self.info(
                    "Error in map tip response with status code: {} from {}".
                    format(response.status_code, response.url))
            return

        self.dbg_info(response.content.decode('utf-8'))
        self.map_tip = MapTip(self.iface, response.content.decode('utf-8'),
                              point.asPoint())
        self.map_tip.closed.connect(self.clearPreviousResults)

    def info(self, msg="", level=Qgis.Info):
        self.logMessage(str(msg), level)

    def dbg_info(self, msg=""):
        if DEBUG:
            self.info(msg)

    @staticmethod
    def break_camelcase(identifier):
        matches = re.finditer(
            '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)',
            identifier)
        return ' '.join([m.group(0) for m in matches])

    def is_opendata_swiss_response(self, json):
        return 'opendata.swiss' in json.get("help", [])

    def find_text(self, xmlElement, match):
        node = xmlElement.find(match)
        return node.text if node is not None else ''
Example #24
0
class PlanetSearchResultsView(QTreeView):
    """
    """

    checkedCountChanged = pyqtSignal(int)

    def __init__(self, parent, iface=None, api_key=None,
                 request_type=None, request=None,
                 response_timeout=RESPONSE_TIMEOUT,
                 sort_order=None):
        super().__init__(parent=parent)

        # noinspection PyTypeChecker
        self._parent: PlanetSearchResultsWidget = parent

        self._iface = iface
        self._api_key = api_key
        self._request_type = request_type
        self._request = request
        self._response_timeout = response_timeout
        self._sort_order = sort_order

        self._footprint = None
        self._setup_footprint()

        self._thumb_cache_dir: str = pluginSetting(
            'thumbCachePath', namespace=SETTINGS_NAMESPACE)

        self._checked_count = 0
        self._checked_queue = {}

        self._search_model = PlanetSearchResultsModel(
            parent=self,
            api_key=api_key,
            request_type=request_type,
            request=request,
            thumb_cache_dir=self._thumb_cache_dir,
            sort_order=self._sort_order
        )

        # Generic model, as background, until results come in
        # self._search_model = QStandardItemModel(0, 2, self)

        self._search_model.thumbnail_cache().set_job_subclass(
            PlanetQgisRenderJob
        )

        p = self.palette()
        p.setColor(QPalette.Highlight, ITEM_BACKGROUND_COLOR)
        self.setPalette(p)

        self.setModel(self._search_model)

        self.setIconSize(QSize(48, 48))
        self.setAlternatingRowColors(True)
        self.setHeaderHidden(False)

        # self.setColumnWidth(0, 250)
        self.setColumnWidth(1, 26)

        self.setIndentation(int(self.indentation() * 0.75))

        hv = self.header()
        hv.setStretchLastSection(False)
        hv.setSectionResizeMode(0, QHeaderView.Stretch)
        hv.setSectionResizeMode(1, QHeaderView.Fixed)
        if len(sort_order) > 1:
            if sort_order[1] == 'asc':
                sort_indicator = Qt.AscendingOrder
            else:
                sort_indicator = Qt.DescendingOrder
            hv.setSortIndicator(0, sort_indicator)
            hv.setSortIndicatorShown(True)

        self.viewport().setAttribute(Qt.WA_Hover)
        self.setMouseTracking(True)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)

        # self.setWordWrap(True)
        # self.setTextElideMode(Qt.ElideNone)
        self.item_delegate = PlanetNodeItemDelegate(parent=self)
        # noinspection PyUnresolvedReferences
        self.item_delegate.previewFootprint['PyQt_PyObject'].connect(
            self.preview_footprint)
        # noinspection PyUnresolvedReferences
        self.item_delegate.clearFootprint.connect(self.clear_footprint)
        self.setItemDelegateForColumn(0, self.item_delegate)
        self.act_delegate = PlanetNodeActionDelegate(parent=self)
        self.setItemDelegateForColumn(1, self.act_delegate)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        # noinspection PyUnresolvedReferences
        self.customContextMenuRequested['QPoint'].connect(self.open_menu)

        # noinspection PyUnresolvedReferences
        self.clicked['QModelIndex'].connect(self.item_clicked)

        # noinspection PyUnresolvedReferences
        self.expanded['QModelIndex'].connect(self.item_expanded)

    def search_model(self):
        return self._search_model

    def checked_count(self):
        return self._checked_count

    def checked_queue(self):
        return self._checked_queue

    def _setup_footprint(self):
        if self._iface:
            log.debug('iface is available, adding footprint support')
            self._footprint = QgsRubberBand(
                self._iface.mapCanvas(), QgsWkbTypes.PolygonGeometry)
            self._footprint.setFillColor(QColor(255, 255, 255, 10))
            self._footprint.setStrokeColor(PLANET_COLOR)
            self._footprint.setWidth(2)
        else:
            log.debug('iface is None, skipping footprint support')
            self._footprint = None

    # noinspection PyMethodMayBeStatic
    def qgsfeature_feature_from_node(self, node: PlanetNode):

        # TODO: Resolve geometry by node_type or do that under node.geometry()?
        # geom = None
        # if node.node_type() == NodeT.DAILY_SCENE_IMAGE:
        #     geom = node.geometry()

        # TODO: Add node
        # feature_collect = {
        #     'type': 'FeatureCollection',
        #     'features': [
        #         {
        #             'type': 'Feature',
        #             'geometry': node.geometry(),
        #             'properties': {
        #                 'id': node.item_id()
        #             }
        #         }
        #     ]
        # }

        feature_collect = {
            'type': 'FeatureCollection',
            'features': [
                node.resource()
            ]
        }

        feature_collect_json = json.dumps(feature_collect)

        # noinspection PyUnusedLocal
        features = []
        # noinspection PyBroadException
        try:
            utf8 = QTextCodec.codecForName('UTF-8')
            # TODO: Add node id, properties as fields?
            fields = QgsFields()
            features = QgsJsonUtils().stringToFeatureList(
                string=feature_collect_json, fields=fields, encoding=utf8)
        except Exception:
            log.debug('Footprint GeoJSON could not be parsed')
            return

        if not len(features) > 0:
            log.debug('GeoJSON parsing created no features')
            return

        return features[0]

    @pyqtSlot()
    def clear_footprint(self):
        if self._footprint:
            self._footprint.reset(QgsWkbTypes.PolygonGeometry)

    @pyqtSlot('PyQt_PyObject')
    def preview_footprint(self, node: PlanetNode):
        if not self._footprint:
            if LOG_VERBOSE:
                log.debug('Footprint is None, skipping footprint preview')
            return

        if node.has_group_footprint():
            geoms = node.geometries()
        else:
            geoms = [node.geometry()]

        self.clear_footprint()

        qgs_geoms = [qgsgeometry_from_geojson(g) for g in geoms]

        for qgs_geom in qgs_geoms:
            self._footprint.addGeometry(
                qgs_geom,
                QgsCoordinateReferenceSystem("EPSG:4326")
            )

        if LOG_VERBOSE:
            log.debug('Footprint sent to canvas')

    @pyqtSlot(list)
    def zoom_to_footprint(self, nodes: [PlanetNode]):
        skip = 'skipping zoom to footprint'
        if not self._footprint:
            log.debug(f'Footprint is None, {skip}')
            return

        if len(nodes) < 1:
            log.debug('No nodes available, skipping zoom to footprint')
            return

        first_node = nodes[0]
        if first_node.has_group_footprint():
            json_geoms = first_node.geometries()
        else:
            json_geoms = [node.geometry() for node in nodes]

        qgs_geoms: [QgsGeometry] = \
            [qgsgeometry_from_geojson(j) for j in json_geoms]

        if len(qgs_geoms) < 1:
            log.debug(f'Geometry collection empty, {skip}')
            return

        rect_geoms: QgsRectangle = qgs_geoms[0].boundingBox()
        for i in range(len(qgs_geoms)):
            if i == 0:
                continue
            r: QgsRectangle = qgs_geoms[i].boundingBox()
            rect_geoms.combineExtentWith(r)

        if rect_geoms.isNull():
            log.debug(f'Footprint geometry is null, {skip}')
            return

        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(),
            QgsProject.instance()
        )
        rect_footprint: QgsRectangle = \
            transform.transformBoundingBox(rect_geoms)

        if not rect_footprint.isEmpty():
            rect_footprint.scale(1.05)
            self._iface.mapCanvas().setExtent(rect_footprint)
            self._iface.mapCanvas().refresh()

    @pyqtSlot('PyQt_PyObject')
    def preview_thumbnail(
            self, node, name=PE_PREVIEW, remove_existing=True):
        item_name_geo = f'{node.item_type_id_key()}{THUMB_GEO}'
        item_geo_path = os.path.join(
            self.search_model().thumbnail_cache().cache_dir(),
            f'{item_name_geo}{THUMB_EXT}')
        if not preview_local_item_raster(
                item_geo_path, name, remove_existing=remove_existing):
            log.warning(f'Item preview {item_name_geo} failed to load')

    @pyqtSlot(dict)
    def update_preview_thumbnails(self, selected_nodes):
        group = temp_preview_group()

        tree_node_names: List[str] = \
            [t_node.name() for t_node in group.children()]
        for name in tree_node_names:
            if name.endswith('_thumb'):
                name_sans = name.replace('_thumb', '')
                if name_sans not in selected_nodes:
                    remove_maplayers_by_name(name, only_first=True)
                if name_sans in selected_nodes:
                    # Keep already loaded layer, skip reloading it
                    selected_nodes[name_sans] = None

        for _, node in selected_nodes.items():
            if node is not None:
                self.preview_thumbnail(node)

        # for node in deselected_nodes:
        #     if f'{node.item_type_id_key()}_thumb'.lower() in node_names:
        #         remove_maplayers_by_name(
        #             f'{node.item_type_id_key()}_thumb'.lower(),
        #             only_first=True)
        #
        # node_names = [t_node.name() for t_node in group.children()]
        # for node in selected_nodes:
        #     if f'{node.item_type_id_key()}_thumb'.lower() not in node_names:
        #         self.preview_thumbnail(node)

    @pyqtSlot(list)
    def add_preview_groups(self, nodes: List[PlanetNode]):
        if len(nodes) < 1 or not isinstance(nodes[0], PlanetNode):
            log.debug('No nodes found to add to preview group')
            return

        if nodes[0].is_group_node():
            log.debug('Adding preview group for group')
            # Grouping tree items are only singularly passed
            self.add_preview_group_for_group(nodes[0])
        else:
            log.debug('Adding preview group for items')
            self.add_preview_groups_for_items(nodes)

    @pyqtSlot(list)
    def add_preview_group_for_group(self, node: PlanetNode):
        name = None
        child_node_type = None
        if node.node_type() == NodeT.DAILY_SCENE:
            child_node_type = NodeT.DAILY_SCENE_IMAGE
            item_type = node.name() or ''
            title = ['Daily', item_type, 'Scene']
            name = f'{" ".join(title)} ' \
                   f'{node.formatted_date_time(node.sort_date())}'
        elif node.node_type() == NodeT.DAILY_SAT_GROUP:
            child_node_type = NodeT.DAILY_SCENE_IMAGE
            item_type = node.parent().name() or ''
            title = ['Daily', item_type, f'Satellite {node.name()} Group']
            name = f'{" ".join(title)} ' \
                   f'{node.formatted_date_time(node.sort_date())}'

        if child_node_type is None:
            log.debug('No node type found for tree group searching')
            return

        item_nodes = node.children_of_node_type(child_node_type)
        if item_nodes:
            create_preview_group(
                name, item_nodes,
                self._search_model.p_client().api_key(),
                tile_service='xyz',
                search_query=self._request,
                sort_order=self._sort_order
            )
        else:
            log.debug(f"No items found for node type '{child_node_type.name}' "
                      f"in tree group '{name}'")

    @pyqtSlot(list)
    def add_preview_groups_for_items(self, nodes: List[PlanetNode]) -> None:
        prev_types = []  # maintain some sort order
        prev_type_nodes = {}
        for node in nodes:
            item_type = node.item_type()
            if item_type not in prev_types:
                prev_types.append(item_type)
                prev_type_nodes[item_type] = []
            prev_type_nodes[item_type].append(node)

        for prev_type in sorted(prev_types):
            prev_nodes: List[PlanetNode] = prev_type_nodes[prev_type]
            # if prev_type in DAILY_ITEM_TYPES_DICT:
            #     # Group imagery by type
            #     item_keys = [n.item_type_id() for n in prev_nodes]
            #     tile_url = self._search_model.p_client().get_tile_url(
            #         item_keys)
            #     create_preview_group(prev_type, prev_nodes, tile_url)
            # else:
            #     # For groups, use any item type listing
            #     for prev_node in prev_nodes:
            #         item_keys = prev_node.item_type_id_list()
            #         if item_keys:
            #             tile_url = \
            #                 self._search_model.p_client().get_tile_url(
            #                     item_keys)
            #             create_preview_group(prev_type, [], tile_url)

            create_preview_group(
                prev_type, prev_nodes,
                self._search_model.p_client().api_key(),
                tile_service='xyz',
                search_query=self._request,
                sort_order=self._sort_order
            )

    @pyqtSlot(list)
    def copy_ids_to_clipboard(self, nodes):
        node_ids = [n.item_type_id() for n in nodes if n.item_id()]
        if node_ids:
            cb = QgsApplication.clipboard()
            cb.setText(','.join(node_ids))

    @pyqtSlot('QPoint')
    def open_menu(self, pos):
        """
        :type pos: QPoint
        :return:
        """
        index = self.indexAt(pos)
        node: PlanetNode = self.model().get_node(index)
        if (node.node_type() == NodeT.LOAD_MORE
                and node.parent() == self.model().root):
            return
        menu = QMenu()

        # Single, current Item's index
        add_menu_section_action('Current item', menu)

        if node.has_footprint() or node.has_group_footprint():
            zoom_fp_act = QAction('Zoom to footprint', menu)
            # noinspection PyUnresolvedReferences
            zoom_fp_act.triggered[bool].connect(
                lambda: self.zoom_to_footprint([node]))
            menu.addAction(zoom_fp_act)

        if node.can_load_preview_layer():
            prev_layer_act = QAction('Add preview layer to map', menu)
            # noinspection PyUnresolvedReferences
            prev_layer_act.triggered[bool].connect(
                lambda: self.add_preview_groups([node]))
            if node.child_images_count() > CHILD_COUNT_THRESHOLD_FOR_PREVIEW:
                prev_layer_act.setEnabled(False)
                prev_layer_act.setToolTip("The node contains too many images to preview")
                menu.setToolTipsVisible(True)

            menu.addAction(prev_layer_act)

        if node.item_id() and node.has_resource():
            copy_id_act = QAction('Copy ID to clipboard', menu)
            # noinspection PyUnresolvedReferences
            copy_id_act.triggered[bool].connect(
                lambda: self.copy_ids_to_clipboard([node]))
            menu.addAction(copy_id_act)

        # Selected Items
        sel_model = self.selectionModel()
        model = self.model()
        # Ensure to grab only first column of indexes (or will get duplicates)
        all_nodes = [model.get_node(i) for i in sel_model.selectedIndexes()
                     if i.column() == 0]
        log.debug(f'Selected items: {len(all_nodes)}')

        if len(all_nodes) == 1 and all_nodes[0] == node:
            menu.exec_(self.viewport().mapToGlobal(pos))
            return

        nodes_have_footprints = \
            [node for node in all_nodes if node.has_footprint()]
        nodes_w_ids = \
            [node for node in all_nodes
             if node.item_id() and node.has_resource()]
        nodes_can_prev = \
            [node for node in all_nodes
             if node.can_load_preview_layer() and node.has_resource()]

        if any([nodes_have_footprints, nodes_w_ids, nodes_can_prev]):
            add_menu_section_action(f'Selected images', menu)

        if nodes_have_footprints:
            zoom_fps_act = QAction(
                f'Zoom to total footprint '
                f'({len(nodes_have_footprints)} items)', menu)
            # noinspection PyUnresolvedReferences
            zoom_fps_act.triggered[bool].connect(
                lambda: self.zoom_to_footprint(nodes_have_footprints))
            menu.addAction(zoom_fps_act)

        if nodes_can_prev:
            prev_layers_act = QAction(
                f'Add preview layer to map '
                f'({len(nodes_can_prev)} items)', menu)
            # noinspection PyUnresolvedReferences
            prev_layers_act.triggered[bool].connect(
                lambda: self.add_preview_groups(nodes_can_prev))
            menu.addAction(prev_layers_act)

        if nodes_w_ids:
            copy_ids_act = QAction(
                f'Copy IDs to clipboard ({len(nodes_w_ids)} items)', menu)
            # noinspection PyUnresolvedReferences
            copy_ids_act.triggered[bool].connect(
                lambda: self.copy_ids_to_clipboard(nodes_w_ids))
            menu.addAction(copy_ids_act)

        menu.exec_(self.viewport().mapToGlobal(pos))

    @pyqtSlot('QModelIndex')
    def item_clicked(self, index):
        node: PlanetNode = self.model().get_node(index)
        log.debug(f'Index clicked: row {index.row()}, col {index.column()}, '
                  f'{node.item_type_id()}')
        if index.column() == 0:
            if (node.node_type() == NodeT.LOAD_MORE
                    and node.parent() == self.model().root):
                self.model().fetch_more_top_items(index)
        elif index.column() == 1:
            self.open_menu(self.viewport().mapFromGlobal(QCursor.pos()))

    @pyqtSlot('QModelIndex')
    def item_expanded(self, index: QModelIndex):
        node: PlanetNode = self.model().get_node(index)
        log.debug(f'Index expanded: row {index.row()}, col {index.column()}, '
                  f'{node.item_type_id()}')
        # log.debug(
        #     f'Node traversed: {node.is_traversed()} {node.item_type_id()}')
        # if index.column() == 0:
        #     if (node.node_type() == NodeT.DAILY_SCENE
        #             and not node.is_traversed()):
        #         log.debug(
        #             f'Traversing node: {node.item_type_id()}')
        #         for sat_grp in node.children():
        #             sat_grp: PlanetNode
        #             self.expand(sat_grp.index())
        #
        #             for image in sat_grp.children():
        #                 if image.has_thumbnail():
        #                     self.search_model().add_to_thumb_queue(
        #                         image.item_type_id_key(), image.index())
        #                     self.search_model().fetch_thumbnail(image)
        #
        #         node.set_is_traversed(True)

    def _update_checked_queue(self,
                              checked_nodes: Set[PlanetNode],
                              unchecked_nodes: Set[PlanetNode]):
        for c_node in checked_nodes:
            it_id = c_node.item_type_id()
            self._checked_queue[it_id] = c_node

        for u_node in unchecked_nodes:
            it_id = u_node.item_type_id()
            if it_id in self._checked_queue:
                del self._checked_queue[it_id]

        self._checked_count = len(self._checked_queue)
        log.debug(f'checked_count: {self._checked_count}')

        if LOG_VERBOSE:
            sorted_item_ids = sorted(self._checked_queue.keys())
            nl = '\n'
            log.debug(f'checked_queue:\n'
                      f'  {"{0}  ".format(nl).join(sorted_item_ids)}')

            # When using with {'item_type': set(nodes)}
            # for it_id in self._checked_queue:
            #     log.debug(f'\n  - {it_id}: '
            #               f'{len(self._checked_queue[it_id])}\n')
            #
            #     # Super verbose output...
            #     nl = '\n'
            #     i_types = \
            #         [n.item_id() for n in self._checked_queue[it_id]]
            #     log.debug(f'\n  - {it_id}: '
            #               f'{len(self._checked_queue[it_id])}\n'
            #               f'    - {"{0}    - ".format(nl).join(i_types)}')

        self.checkedCountChanged.emit(self._checked_count)

    def event(self, event: QEvent) -> bool:
        if event.type() == QEvent.Leave:
            if self._iface:
                self.clear_footprint()
                event.accept()

        return QTreeView.event(self, event)

    def mousePressEvent(self, event: QMouseEvent) -> None:
        index = self.indexAt(event.pos())
        sel_model: QItemSelectionModel = self.selectionModel()
        if (index.column() == 1
                and event.button() == Qt.LeftButton
                and sel_model.isSelected(index)):
            log.debug('Ignoring mouse press')
            return

        return QTreeView.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event: QMouseEvent) -> None:
        index = self.indexAt(event.pos())
        sel_model: QItemSelectionModel = self.selectionModel()
        if (index.column() == 1
                and event.button() == Qt.LeftButton
                and sel_model.isSelected(index)):
            log.debug('Swapping left button for right, on release')
            self.open_menu(event.pos())
            return

        return QTreeView.mouseReleaseEvent(self, event)

    def keyReleaseEvent(self, event: QKeyEvent) -> None:
        index = self.currentIndex()
        if (index.column() == 0
                and event.key() in [Qt.Key_Enter, Qt.Key_Return]):
            node: PlanetNode = self.model().get_node(index)
            if (node.node_type() == NodeT.LOAD_MORE
                    and node.parent() == self.model().root):
                self.model().fetch_more_top_items(index)

        return QTreeView.keyReleaseEvent(self, event)

    def dataChanged(self, t_l: QModelIndex, b_r: QModelIndex,
                    roles: Optional[List[int]] = None) -> None:

        # This may need to go at end of slot
        super().dataChanged(t_l, b_r, roles=roles)

        # Note: empty roles indicates *everything* has changed, not nothing
        if roles is not None and (roles == [] or Qt.CheckStateRole in roles):
            if LOG_VERBOSE:
                log.debug('Node (un)checked')
            checked = set()
            unchecked = set()

            def update_queues(indx):
                node: PlanetNode = self._search_model.get_node(indx)
                if node.is_base_image() and node.can_be_downloaded():
                    if node.checked_state() == Qt.Checked:
                        checked.add(node)
                    elif node.checked_state() == Qt.Unchecked:
                        unchecked.add(node)

            # Note: if parent of t_l and b_r differ, we ignore undefined input
            if t_l == b_r:
                if LOG_VERBOSE:
                    log.debug('Nodes (un)checked is single')
                update_queues(t_l)
            elif t_l.parent() == b_r.parent():
                if LOG_VERBOSE:
                    log.debug('Nodes (un)checked have same parent')
                parent = t_l.parent()
                col = t_l.column()
                for row in range(b_r.row(), t_l.row() + 1):
                    m_indx = self._search_model.index(row, col, parent=parent)
                    update_queues(m_indx)

            if checked or unchecked:
                if LOG_VERBOSE:
                    log.debug(f'Nodes checked: {checked}')
                    log.debug(f'Nodes unchecked: {unchecked}')
                self._update_checked_queue(checked, unchecked)

    # This works, but is disabled until thumbnail georeferencing works OK
    # def currentChanged(self, current: QModelIndex,
    #                    previous: QModelIndex) -> None:
    #     node: PlanetNode = self.model().get_node(current)
    #     log.debug(f'Current item: row {current.row()}, '
    #               f'col {current.column()},'
    #               f' {node.item_id()}')
    #     if current.column() == 0:
    #         if node.has_thumbnail() and node.thumbnail_loaded():
    #             self.preview_thumbnail(node)
    #         else:
    #             clear_local_item_raster_preview()
    #
    #     return QTreeView.currentChanged(self, current, previous)

    # def selectionChanged(self, selected: QItemSelection,
    #                      deselected: QItemSelection) -> None:
    #     selected_nodes = {}
    #     for si in selected.indexes():
    #         if si.column() == 0:
    #             si_node = self._search_model.get_node(si)
    #             selected_nodes[si_node.item_type_id_key()] = si_node
    #
    #     deselected_nodes = {}
    #     for di in deselected.indexes():
    #         if di.column() == 0:
    #             di_node = self._search_model.get_node(di)
    #             deselected_nodes[di_node.item_type_id_key()] = di_node
    #
    #     self.update_preview_thumbnails(selected_nodes)
    #
    #     return QTreeView.selectionChanged(self, selected, deselected)

    @pyqtSlot()
    def clean_up(self):
        self.clear_footprint()
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)
Example #26
0
class DuplicateTool(QgsMapTool):
    """
    Map tool class to duplicate an object
    """
    def __init__(self, iface):
        """
        Constructor
        :param iface: interface
        """
        QgsMapTool.__init__(self, iface.mapCanvas())
        self.__iface = iface
        self.icon_path = ':/plugins/VDLTools/icons/duplicate_icon.png'
        self.text = QCoreApplication.translate("VDLTools",
                                               "Duplicate a feature")
        self.setCursor(Qt.ArrowCursor)
        self.__isEditing = False
        self.__layer = None
        self.__lastFeatureId = None
        self.__selectedFeature = None
        self.__rubberBand = None
        self.__newFeature = None
        self.__dstDlg = None

    def deactivate(self):
        """
        When the action is deselected
        """
        self.__cancel()
        QgsMapTool.deactivate(self)

    def toolName(self):
        """
        To get the tool name
        :return: tool name
        """
        return QCoreApplication.translate("VDLTools", "Duplicate")

    def startEditing(self):
        """
        To set the action as enable, as the layer is editable
        """
        self.action().setEnabled(True)
        Signal.safelyDisconnect(self.__layer.editingStarted, self.startEditing)
        self.__layer.editingStopped.connect(self.stopEditing)

    def stopEditing(self):
        """
        To set the action as disable, as the layer is not editable
        """
        self.action().setEnabled(False)
        Signal.safelyDisconnect(self.__layer.editingStopped, self.stopEditing)
        self.__layer.editingStarted.connect(self.startEditing)
        if self.canvas().mapTool() == self:
            self.__iface.actionPan().trigger()

    def setTool(self):
        """
        To set the current tool as this one
        """
        self.canvas().setMapTool(self)

    def __cancel(self):
        """
        To cancel used variables
        """
        self.__isEditing = False
        if self.__rubberBand is not None:
            self.canvas().scene().removeItem(self.__rubberBand)
            self.__rubberBand.reset()
            self.__rubberBand = None
        self.__dstDlg = None
        self.__newFeature = None
        self.__lastFeatureId = None
        self.__selectedFeature = None
        self.__layer.removeSelection()

    def __removeLayer(self):
        """
        To remove the current working layer
        """
        if self.__layer is not None:
            if self.__layer.isEditable():
                Signal.safelyDisconnect(self.__layer.editingStopped,
                                        self.stopEditing)
            else:
                Signal.safelyDisconnect(self.__layer.editingStarted,
                                        self.startEditing)
            self.__layer = None

    def setEnable(self, layer):
        """
        To check if we can enable the action for the selected layer
        :param layer: selected layer
        """
        types = [QGis.Line, QGis.Polygon]
        if layer is not None and layer.type(
        ) == QgsMapLayer.VectorLayer and layer.geometryType() in types:
            if layer == self.__layer:
                return

            if self.__layer is not None:
                if self.__layer.isEditable():
                    Signal.safelyDisconnect(self.__layer.editingStopped,
                                            self.stopEditing)
                else:
                    Signal.safelyDisconnect(self.__layer.editingStarted,
                                            self.startEditing)
            self.__layer = layer
            if self.__layer.isEditable():
                self.action().setEnabled(True)
                self.__layer.editingStopped.connect(self.stopEditing)
            else:
                self.action().setEnabled(False)
                self.__layer.editingStarted.connect(self.startEditing)
                if self.canvas().mapTool() == self:
                    self.__iface.actionPan().trigger()
            return

        if self.canvas().mapTool() == self:
            self.__iface.actionPan().trigger()
        self.action().setEnabled(False)
        self.__removeLayer()

    def __setDistanceDialog(self, isComplexPolygon):
        """
        To create a Duplicate Distance Dialog
        :param isComplexPolygon: for a polygon, if it has interior ring(s)
        """
        self.__dstDlg = DuplicateDistanceDialog(isComplexPolygon)
        self.__dstDlg.rejected.connect(self.__cancel)
        self.__dstDlg.previewButton().clicked.connect(self.__onDstPreview)
        self.__dstDlg.okButton().clicked.connect(self.__onDstOk)
        self.__dstDlg.cancelButton().clicked.connect(self.__onDstCancel)
        self.__dstDlg.directionCheck().stateChanged.connect(
            self.__onDstPreview)

    def __onDstCancel(self):
        """
        When the Cancel button in Duplicate Distance Dialog is pushed
        """
        self.__dstDlg.reject()

    @staticmethod
    def __newPoint(angle, point, distance):
        """
        To create a new point at a certain distance and certain azimut from another point
        :param angle: the azimut
        :param point: the reference point
        :param distance: the distance
        :return: the new QgsPoint (with same elevation than parameter point)
        """
        x = point.x() + cos(angle) * distance
        y = point.y() + sin(angle) * distance
        pt = QgsPointV2(x, y)
        pt.addZValue(point.z())
        return pt

    def __onDstPreview(self):
        """
        When the Preview button in Duplicate Distance Dialog is pushed
        """
        if self.__rubberBand is not None:
            self.canvas().scene().removeItem(self.__rubberBand)
            self.__rubberBand = None
        if self.__dstDlg.distanceEdit().text() is not None:
            distance = float(self.__dstDlg.distanceEdit().text())
            if self.__dstDlg.directionCheck().checkState():
                distance = -distance
            if self.__layer.geometryType() == QGis.Polygon:
                self.__polygonPreview(distance)
            else:
                self.__linePreview(distance)
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setWidth(2)
            self.__rubberBand.setColor(color)
            self.__rubberBand.setLineStyle(Qt.DotLine)
            self.__rubberBand.show()

    def __linePreview(self, distance):
        """
        To create the preview (rubberBand) of the duplicate line at a certain distance
        :param distance: the given distance
        """
        self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line)
        line_v2, curved = GeometryV2.asLineV2(
            self.__selectedFeature.geometry(), self.__iface)
        if isinstance(curved, (list, tuple)):
            self.__newFeature = QgsCompoundCurveV2()
            for pos in range(line_v2.nCurves()):
                if curved[pos]:
                    curve_v2 = self.__newArc(line_v2.curveAt(pos), distance)
                else:
                    curve_v2 = self.__newLine(line_v2.curveAt(pos), distance)
                self.__newFeature.addCurve(curve_v2)
                if pos == 0:
                    self.__rubberBand.setToGeometry(
                        QgsGeometry(curve_v2.curveToLine()), None)
                else:
                    self.__rubberBand.addGeometry(
                        QgsGeometry(curve_v2.curveToLine()), None)
        else:
            if curved:
                self.__newFeature = self.__newArc(line_v2, distance)
            else:
                self.__newFeature = self.__newLine(line_v2, distance)
            self.__rubberBand.setToGeometry(
                QgsGeometry(self.__newFeature.curveToLine()), None)

    def __newArc(self, arc_v2, distance):
        """
        To duplicate a curve at a given distance
        :param arc_v2: the curve to duplicate
        :param distance: the given distance
        :return: the new curve
        """
        curve_v2 = QgsCircularStringV2()
        points = []
        circle = Circle(arc_v2.pointN(0), arc_v2.pointN(1), arc_v2.pointN(2))
        points.append(
            self.__newPoint(circle.angle1(), arc_v2.pointN(0), distance))
        points.append(
            self.__newPoint(circle.angle2(), arc_v2.pointN(1), distance))
        points.append(
            self.__newPoint(circle.angle3(), arc_v2.pointN(2), distance))
        curve_v2.setPoints(points)
        return curve_v2

    def __newLine(self, line_v2, distance):
        """
        To duplicate a line at a given distance
        :param line_v2: the line to duplicate
        :param distance: the given distance
        :return: the new line
        """
        curve_v2 = QgsLineStringV2()
        points = []
        for pos in range(line_v2.numPoints()):
            if pos == 0:
                angle = Circle.angle(line_v2.pointN(pos),
                                     line_v2.pointN(pos + 1)) + old_div(pi, 2)
                dist = distance
            elif pos == (line_v2.numPoints() - 1):
                angle = Circle.angle(line_v2.pointN(pos - 1),
                                     line_v2.pointN(pos)) + old_div(pi, 2)
                dist = distance
            else:
                angle1 = Circle.angle(line_v2.pointN(pos - 1),
                                      line_v2.pointN(pos))
                angle2 = Circle.angle(line_v2.pointN(pos),
                                      line_v2.pointN(pos + 1))
                angle = old_div(float(pi + angle1 + angle2), 2)
                dist = old_div(float(distance),
                               sin(old_div(float(pi + angle1 - angle2), 2)))
            points.append(self.__newPoint(angle, line_v2.pointN(pos), dist))
        curve_v2.setPoints(points)
        return curve_v2

    def __polygonPreview(self, distance):
        """
        To create the preview (rubberBand) of the duplicate polygon at a certain distance
        :param distance: the given distance
        """
        self.__rubberBand = QgsRubberBand(self.canvas(), QGis.Line)
        polygon_v2, curved = GeometryV2.asPolygonV2(
            self.__selectedFeature.geometry(), self.__iface)
        self.__newFeature = QgsCurvePolygonV2()
        line_v2 = self.__newPolygonCurve(polygon_v2.exteriorRing(), distance,
                                         curved[0])
        self.__newFeature.setExteriorRing(line_v2)
        self.__rubberBand.setToGeometry(QgsGeometry(line_v2.curveToLine()),
                                        None)
        for num in range(polygon_v2.numInteriorRings()):
            if self.__dstDlg.isInverted():
                distance = -distance
            line_v2 = self.__newPolygonCurve(polygon_v2.interiorRing(num),
                                             distance, curved[num + 1])
            self.__newFeature.addInteriorRing(line_v2)
            self.__rubberBand.addGeometry(QgsGeometry(line_v2.curveToLine()),
                                          None)

    def __newPolygonCurve(self, curve_v2, distance, curved):
        """
        To create a duplicate curve for a polygon curves
        :param curve_v2: curve to duplicate
        :param distance: distance where to
        :param curved: if the line is curved
        :return: new duplicate curve
        """
        if curved:
            new_line_v2 = QgsCircularStringV2()
        else:
            new_line_v2 = QgsLineStringV2()
        points = []

        for pos in range(curve_v2.numPoints()):
            if pos == 0:
                pos1 = curve_v2.numPoints() - 2
            else:
                pos1 = pos - 1
            pos2 = pos
            if pos == (curve_v2.numPoints() - 1):
                pos3 = 1
            else:
                pos3 = pos + 1
            angle1 = Circle.angle(curve_v2.pointN(pos1), curve_v2.pointN(pos2))
            angle2 = Circle.angle(curve_v2.pointN(pos), curve_v2.pointN(pos3))
            angle = old_div(float(pi + angle1 + angle2), 2)
            dist = old_div(float(distance),
                           sin(old_div(float(pi + angle1 - angle2), 2)))
            points.append(self.__newPoint(angle, curve_v2.pointN(pos), dist))
        new_line_v2.setPoints(points)
        return new_line_v2

    def __onDstOk(self):
        """
        When the Ok button in Duplicate Distance Dialog is pushed
        """
        self.__onDstPreview()
        self.__dstDlg.accept()
        geometry = QgsGeometry(self.__newFeature)
        if not geometry.isGeosValid():
            self.__iface.messageBar().pushMessage(QCoreApplication.translate(
                "VDLTools", "Geos geometry problem"),
                                                  level=QgsMessageBar.CRITICAL,
                                                  duration=0)
        feature = QgsFeature(self.__layer.pendingFields())
        feature.setGeometry(geometry)
        primaryKey = QgsDataSourceURI(self.__layer.source()).keyColumn()
        for field in self.__selectedFeature.fields():
            if field.name() != primaryKey:
                feature.setAttribute(
                    field.name(),
                    self.__selectedFeature.attribute(field.name()))
        if len(self.__selectedFeature.fields()) > 0 and self.__layer.editFormConfig().suppress() != \
                QgsEditFormConfig.SuppressOn:
            self.__iface.openFeatureForm(self.__layer, feature)
        else:
            self.__layer.addFeature(feature)
        # self.__layer.updateExtents()
        self.__cancel()

    def canvasMoveEvent(self, event):
        """
        When the mouse is moved
        :param event: mouse event
        """
        if not self.__isEditing:
            laySettings = QgsSnappingUtils.LayerConfig(self.__layer,
                                                       QgsPointLocator.All, 10,
                                                       QgsTolerance.Pixels)
            feat = Finder.findClosestFeatureAt(event.mapPoint(), laySettings,
                                               self)
            if feat is not None and self.__lastFeatureId != feat.id():
                self.__lastFeatureId = feat.id()
                self.__layer.setSelectedFeatures([feat.id()])
            if feat is None:
                self.__layer.removeSelection()
                self.__lastFeatureId = None

    def canvasReleaseEvent(self, event):
        """
        When the mouse is clicked
        :param event: mouse event
        """
        found_features = self.__layer.selectedFeatures()
        if len(found_features) > 0:
            if len(found_features) > 1:
                self.__iface.messageBar().pushMessage(
                    QCoreApplication.translate("VDLTools",
                                               "One feature at a time"),
                    level=QgsMessageBar.INFO)
                return
            self.__selectedFeature = found_features[0]
            self.__isEditing = True
            if self.__layer.geometryType() == QGis.Polygon\
                    and len(self.__selectedFeature.geometry().asPolygon()) > 1:
                self.__setDistanceDialog(True)
            else:
                self.__setDistanceDialog(False)
            self.__dstDlg.distanceEdit().setText("5.0")
            self.__dstDlg.distanceEdit().selectAll()
            self.__dstDlg.show()
Example #27
0
class ShLocatorFilter(QgsLocatorFilter):

    HEADERS = {b'User-Agent': b'Mozilla/5.0 QGIS ShLocator Filter'}

    message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget)

    def __init__(self, iface: QgisInterface = None):
        """"
        :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise
        """
        super().__init__()

        self.iface = iface
        self.settings = Settings()

        #  following properties will only be used in main thread
        self.rubber_band = None
        self.map_canvas: QgsMapCanvas = None
        self.transform_ch = None
        self.current_timer = None
        self.result_found = False
        self.nam_fetch_feature = None

        if iface is not None:
            # happens only in main thread
            self.map_canvas = iface.mapCanvas()
            self.map_canvas.destinationCrsChanged.connect(
                self.create_transforms)

            self.rubber_band = QgsRubberBand(
                self.map_canvas, QgsWkbTypes.PolygonGeometry)
            self.rubber_band.setColor(QColor(255, 50, 50, 200))
            self.rubber_band.setFillColor(QColor(255, 255, 50, 160))
            self.rubber_band.setBrushStyle(Qt.SolidPattern)
            self.rubber_band.setLineStyle(Qt.SolidLine)
            self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE)
            self.rubber_band.setIconSize(15)
            self.rubber_band.setWidth(4)
            self.rubber_band.setBrushStyle(Qt.NoBrush)

            self.create_transforms()

    def name(self):
        return 'ShLocator'

    def clone(self):
        return ShLocatorFilter()

    def priority(self):
        return QgsLocatorFilter.Highest

    def displayName(self):
        return 'ShLocator'

    def prefix(self):
        return 'shl'

    def clearPreviousResults(self):
        if self.rubber_band:
            self.rubber_band.reset(QgsWkbTypes.PointGeometry)

        if self.current_timer is not None:
            self.current_timer.stop()
            self.current_timer.deleteLater()
            self.current_timer = None

    def hasConfigWidget(self):
        return True

    def openConfigWidget(self, parent=None):
        dlg = ConfigDialog(parent)
        dlg.exec_()

    def create_transforms(self):
        # this should happen in the main thread
        src_crs_ch = QgsCoordinateReferenceSystem('EPSG:2056')
        assert src_crs_ch.isValid()
        dst_crs = self.map_canvas.mapSettings().destinationCrs()
        self.transform_ch = QgsCoordinateTransform(
            src_crs_ch, dst_crs, QgsProject.instance())

    @staticmethod
    def url_with_param(url, params) -> str:
        url = QUrl(url)
        q = QUrlQuery(url)
        for key, value in params.items():
            q.addQueryItem(key, value)
        url.setQuery(q)
        return url.url()

    def fetchResults(self, search: str, context: QgsLocatorContext, feedback: QgsFeedback):
        try:
            dbg_info("start shlocator search...")

            if len(search) < 3:
                return

            self.result_found = False

            params = {
                'query': str(search),
                'maxfeatures': str(self.settings.value('max_features'))
            }

            nam = NetworkAccessManager()
            feedback.canceled.connect(nam.abort)
            url = self.url_with_param(
                self.settings.value('service_url'), params)
            dbg_info(url)
            try:
                (response, content) = nam.request(
                    url, headers=self.HEADERS, blocking=True)
                self.handle_response(response, search)
            except RequestsExceptionUserAbort:
                pass
            except RequestsException as err:
                info(err, Qgis.Info)

            if not self.result_found:
                result = QgsLocatorResult()
                result.filter = self
                result.displayString = self.tr('No result found.')
                result.userData = NoResult
                self.resultFetched.emit(result)

        except Exception as e:
            info(e, Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            info('{} {} {}'.format(exc_type, filename,
                                   exc_traceback.tb_lineno), Qgis.Critical)
            info(traceback.print_exception(
                exc_type, exc_obj, exc_traceback), Qgis.Critical)

    def data_product_qgsresult(self, data: dict) -> QgsLocatorResult:
        result = QgsLocatorResult()
        result.filter = self
        result.displayString = data['display']
        result.group = 'Karten'
        result.userData = DataProductResult(
            type=data['type'],
            dataproduct_id=data['dataproduct_id'],
            display=data['display'],
            dset_info=data['dset_info'],
            sublayers=data.get('sublayers', None)
        )
        data_product = 'dataproduct'
        data_type = data['type']
        result.icon, result.description = dataproduct2icon_description(
            data_product, data_type)
        return result

    def handle_response(self, response, search_text: str):
        try:
            if response.status_code != 200:
                if not isinstance(response.exception, RequestsExceptionUserAbort):
                    info("Error in main response with status code: "
                         "{} from {}".format(response.status_code, response.url))
                return

            display_name_field = QgsField('display_name', QVariant.String)
            fields = QgsFields()
            fields.append(display_name_field)
            features = QgsJsonUtils.stringToFeatureList(response.content.decode('utf-8'), fields, QTextCodec.codecForName('UTF-8'))
            dbg_info('Found {} features'.format(len(features)))
            dbg_info('Data {}'.format(response.content.decode('utf-8')))

            for feature in features:
                dbg_info('Adding feature {}'.format(feature['display_name']))
                result = QgsLocatorResult()
                result.filter = self
                result.group = 'Objekte'
                result.displayString = feature['display_name']
                result.userData = FeatureResult(feature)
                self.resultFetched.emit(result)

                self.result_found = True

        except Exception as e:
            info(str(e), Qgis.Critical)
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            filename = os.path.split(
                exc_traceback.tb_frame.f_code.co_filename)[1]
            info('{} {} {}'.format(exc_type, filename,
                                   exc_traceback.tb_lineno), Qgis.Critical)
            info(traceback.print_exception(
                exc_type, exc_obj, exc_traceback), Qgis.Critical)

    def triggerResult(self, result: QgsLocatorResult):
        # this is run in the main thread, i.e. map_canvas is not None
        self.clearPreviousResults()

        if type(result.userData) == NoResult:
            pass
        elif type(result.userData) == FeatureResult:
            self.highlight(result.userData.feature.geometry())
        else:
            info('Incorrect result. Please contact support', Qgis.Critical)

    def highlight(self, geometry: QgsGeometry):
        self.rubber_band.reset(geometry.type())
        self.rubber_band.addGeometry(geometry, None)

        rect = geometry.boundingBox()
        if not self.settings.value('keep_scale'):
            if rect.isEmpty():
                current_extent = self.map_canvas.extent()
                rect = current_extent.scaled(self.settings.value(
                    'point_scale')/self.map_canvas.scale(), rect.center())
            else:
                rect.scale(4)
        self.map_canvas.setExtent(rect)
        self.map_canvas.refresh()

#        self.current_timer = QTimer()
#        self.current_timer.timeout.connect(self.clearPreviousResults)
#        self.current_timer.setSingleShot(True)
#        self.current_timer.start(5000)

    def parse_feature_response(self, response):
        if response.status_code != 200:
            if not isinstance(response.exception, RequestsExceptionUserAbort):
                info("Error in feature response with status code: "
                     "{} from {}".format(response.status_code, response.url))
            return

        data = json.loads(response.content.decode('utf-8'))

        geometry_type = data['geometry']['type']
        geometry = QgsGeometry()

        if geometry_type == 'Point':
            geometry = QgsGeometry.fromPointXY(QgsPointXY(data['geometry']['coordinates'][0],
                                                          data['geometry']['coordinates'][1]))

        elif geometry_type == 'Polygon':
            rings = data['geometry']['coordinates']
            for r in range(0, len(rings)):
                for p in range(0, len(rings[r])):
                    rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1])
            geometry = QgsGeometry.fromPolygonXY(rings)

        elif geometry_type == 'MultiPolygon':
            islands = data['geometry']['coordinates']
            for i in range(0, len(islands)):
                for r in range(0, len(islands[i])):
                    for p in range(0, len(islands[i][r])):
                        islands[i][r][p] = QgsPointXY(
                            islands[i][r][p][0], islands[i][r][p][1])
            geometry = QgsGeometry.fromMultiPolygonXY(islands)

        else:
            # ShLocator does not handle {geometry_type} yet. Please contact support
            info('ShLocator unterstützt den Geometrietyp {geometry_type} nicht.'
                 ' Bitte kontaktieren Sie den Support.'.format(geometry_type=geometry_type), Qgis.Warning)

        geometry.transform(self.transform_ch)
        self.highlight(geometry)
class LatLonTools:
    digitizerDialog = None
    toMGRSDialog = None
    MGRStoLayerDialog = None
    geom2FieldDialog = None
    
    def __init__(self, iface):
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.crossRb.setColor(Qt.red)
        self.provider = LatLonToolsProvider()        

    def initGui(self):
        '''Initialize Lot Lon Tools GUI.'''
        # Initialize the Settings Dialog box
        self.settingsDialog = SettingsWidget(self, self.iface, self.iface.mainWindow())
        self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface)
        self.showMapTool = ShowOnMapTool(self.settingsDialog, self.iface)
        
        # Add Interface for Coordinate Capturing
        icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.png")
        self.copyAction = QAction(icon, "Copy Latitude, Longitude", self.iface.mainWindow())
        self.copyAction.setObjectName('latLonToolsCopy')
        self.copyAction.triggered.connect(self.startCapture)
        self.copyAction.setCheckable(True)
        self.iface.addToolBarIcon(self.copyAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction)

        # Add Interface for Zoom to Coordinate
        icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.png")
        self.zoomToAction = QAction(icon, "Zoom To Latitude, Longitude", self.iface.mainWindow())
        self.zoomToAction.setObjectName('latLonToolsZoom')
        self.zoomToAction.triggered.connect(self.showZoomToDialog)
        self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction)

        self.zoomToDialog = ZoomToLatLon(self, self.iface, self.iface.mainWindow())
        self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog)
        self.zoomToDialog.hide()
        
        # Add Interface for External Map
        icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png")
        self.externMapAction = QAction(icon, "Show in External Map", self.iface.mainWindow())
        self.externMapAction.setObjectName('latLonToolsExternalMap')
        self.externMapAction.triggered.connect(self.setShowMapTool)
        self.externMapAction.setCheckable(True)
        self.iface.addToolBarIcon(self.externMapAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction)
        
        # Add Interface for Multi point zoom
        icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.png')
        self.multiZoomToAction = QAction(icon, "Multi-location Zoom", self.iface.mainWindow())
        self.multiZoomToAction.setObjectName('latLonToolsMultiZoom')
        self.multiZoomToAction.triggered.connect(self.multiZoomTo)
        self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction)

        self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog, self.iface.mainWindow())
        self.multiZoomDialog.hide()
        self.multiZoomDialog.setFloating(True)
        
        # Add To MGRS conversion
        menu = QMenu()
        icon = QIcon(os.path.dirname(__file__) + '/images/geom2field.png')
        action = menu.addAction(icon, "Geometry to Field", self.geom2Field)
        action.setObjectName('latLonToolsGeom2Field')
        icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.png')
        action = menu.addAction(icon, "MGRS to Geometry", self.MGRStoLayer)
        action.setObjectName('latLonToolsMGRS2Geom')
        icon = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.png')
        action = menu.addAction(icon, "Geometry to MGRS", self.toMGRS)
        action.setObjectName('latLonToolsGeom2MGRS')
        self.toMGRSAction = QAction(icon, "Conversions", self.iface.mainWindow())
        self.toMGRSAction.setMenu(menu)
        self.iface.addPluginToMenu('Lat Lon Tools', self.toMGRSAction)
        
        # Initialize the Settings Dialog Box
        settingsicon = QIcon(os.path.dirname(__file__) + '/images/settings.png')
        self.settingsAction = QAction(settingsicon, "Settings", self.iface.mainWindow())
        self.settingsAction.setObjectName('latLonToolsSettings')
        self.settingsAction.triggered.connect(self.settings)
        self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction)
        
        # Help
        icon = QIcon(os.path.dirname(__file__) + '/images/help.png')
        self.helpAction = QAction(icon, "Help", self.iface.mainWindow())
        self.helpAction.setObjectName('latLonToolsHelp')
        self.helpAction.triggered.connect(self.help)
        self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction)
        
        # Add to Digitize Toolbar
        icon = QIcon(os.path.dirname(__file__) + '/images/latLonDigitize.png')
        self.digitizeAction = QAction(icon, "Lat Lon Digitize", self.iface.mainWindow())
        self.digitizeAction.setObjectName('latLonToolsDigitize')
        self.digitizeAction.triggered.connect(self.digitizeClicked)
        self.digitizeAction.setEnabled(False)
        self.iface.digitizeToolBar().addAction(self.digitizeAction)
        
        
        self.iface.currentLayerChanged.connect(self.currentLayerChanged)
        self.canvas.mapToolSet.connect(self.unsetTool)
        self.enableDigitizeTool()
        # Add the processing provider
        
        QgsApplication.processingRegistry().addProvider(self.provider)
                
    def unsetTool(self, tool):
        '''Uncheck the Copy Lat Lon tool'''
        try:
            if not isinstance(tool, CopyLatLonTool):
                self.copyAction.setChecked(False)
                self.multiZoomDialog.stopCapture()
                self.mapTool.capture4326 = False
            if not isinstance(tool, ShowOnMapTool):
                self.externMapAction.setChecked(False)
        except:
            pass

    def unload(self):
        '''Unload LatLonTools from the QGIS interface'''
        self.zoomToDialog.removeMarker()
        self.multiZoomDialog.removeMarkers()
        self.canvas.unsetMapTool(self.mapTool)
        self.canvas.unsetMapTool(self.showMapTool)
        self.iface.removePluginMenu('Lat Lon Tools', self.copyAction)
        self.iface.removeToolBarIcon(self.copyAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction)
        self.iface.removeToolBarIcon(self.externMapAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.toMGRSAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.helpAction)
        self.iface.removeDockWidget(self.zoomToDialog)
        self.iface.removeDockWidget(self.multiZoomDialog)
        self.geom2FieldDialog = None
        self.MGRStoLayerDialog = None
        self.toMGRSDialog = None
        self.zoomToDialog = None
        self.multiZoomDialog = None
        self.settingsDialog = None
        self.showMapTool = None
        self.mapTool = None
        self.iface.digitizeToolBar().removeAction(self.digitizeAction)
        self.digitizerDialog = None
        QgsApplication.processingRegistry().removeProvider(self.provider)

    def startCapture(self):
        '''Set the focus of the copy coordinate tool and check it'''
        self.copyAction.setChecked(True)
        self.canvas.setMapTool(self.mapTool)

    def setShowMapTool(self):
        '''Set the focus of the external map tool and check it'''
        self.externMapAction.setChecked(True)
        self.canvas.setMapTool(self.showMapTool)

    def showZoomToDialog(self):
        '''Show the zoom to docked widget.'''
        self.zoomToDialog.show()

    def multiZoomTo(self):
        '''Display the Multi-zoom to dialog box'''
        self.multiZoomDialog.show()

    def geom2Field(self):
        '''Convert layer geometry to a text string'''
        if self.geom2FieldDialog == None:
            from .geom2field import Geom2FieldWidget
            self.geom2FieldDialog = Geom2FieldWidget(self.iface, self.iface.mainWindow())
        self.geom2FieldDialog.show()

    def toMGRS(self):
        '''Display the to MGRS  dialog box'''
        results = processing.execAlgorithmDialog('latlontools:point2mgrs', {})

    def MGRStoLayer(self):
        '''Display the to MGRS  dialog box'''
        results = processing.execAlgorithmDialog('latlontools:mgrs2point', {})
    
    def settings(self):
        '''Show the settings dialog box'''
        self.settingsDialog.show()
        
    def help(self):
        '''Display a help page'''
        url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString()
        webbrowser.open(url, new=2)
        
    def settingsChanged(self):
        # Settings may have changed so we need to make sure the zoomToDialog window is configured properly
        self.zoomToDialog.configure()
        self.multiZoomDialog.settingsChanged()
            
 
    def zoomTo(self, srcCrs, lat, lon):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(srcCrs, canvasCrs, QgsProject.instance())
        x, y = transform.transform(float(lon), float(lat))
            
        rect = QgsRectangle(x,y,x,y)
        self.canvas.setExtent(rect)

        pt = QgsPointXY(x,y)
        self.highlight(pt)
        self.canvas.refresh()
        return pt
        
    def highlight(self, point):
        currExt = self.canvas.extent()
        
        leftPt = QgsPoint(currExt.xMinimum(),point.y())
        rightPt = QgsPoint(currExt.xMaximum(),point.y())
        
        topPt = QgsPoint(point.x(),currExt.yMaximum())
        bottomPt = QgsPoint(point.x(),currExt.yMinimum())
        
        horizLine = QgsGeometry.fromPolyline( [ leftPt , rightPt ] )
        vertLine = QgsGeometry.fromPolyline( [ topPt , bottomPt ] )
        
        self.crossRb.reset(QgsWkbTypes.LineGeometry)
        self.crossRb.addGeometry(horizLine, None)
        self.crossRb.addGeometry(vertLine, None)
        
        QTimer.singleShot(700, self.resetRubberbands)
        
    def resetRubberbands(self):
        self.crossRb.reset()
        
    def digitizeClicked(self):
        if self.digitizerDialog == None:
            from .digitizer import DigitizerWidget
            self.digitizerDialog = DigitizerWidget(self, self.iface, self.iface.mainWindow())
        self.digitizerDialog.show()
        
    def currentLayerChanged(self):
        layer = self.iface.activeLayer()
        if layer != None:
            try:
                layer.editingStarted.disconnect(self.layerEditingChanged)
            except:
                pass
            try:
                layer.editingStopped.disconnect(self.layerEditingChanged)
            except:
                pass
            
            if isinstance(layer, QgsVectorLayer):
                layer.editingStarted.connect(self.layerEditingChanged)
                layer.editingStopped.connect(self.layerEditingChanged)
                
        self.enableDigitizeTool()

    def layerEditingChanged(self):
        self.enableDigitizeTool()

    def enableDigitizeTool(self):
        self.digitizeAction.setEnabled(False)
        layer = self.iface.activeLayer()
        
        if layer != None and isinstance(layer, QgsVectorLayer) and (layer.wkbType() == QgsWkbTypes.Point) and layer.isEditable():
            self.digitizeAction.setEnabled(True)
        else:
            if self.digitizerDialog != None:
                self.digitizerDialog.close()
class PlanetCircleMapTool(QgsMapTool):

    circleSelected = pyqtSignal(object)

    def __init__(self, canvas):
        QgsMapTool.__init__(self, canvas)

        self.canvas = canvas
        self.circle = QgsGeometry
        self.dragging = False
        self.rubber_band = None
        self.center = QPointF()
        self.tangent_point = QPointF()
        self.radius = 0.0

    def canvasPressEvent(self, event):
        self.center = event.pos()
        self.rubber_band = QgsRubberBand(self.canvas,
                                         QgsWkbTypes.PolygonGeometry)
        self.rubber_band.setFillColor(RB_FILL)
        self.rubber_band.setStrokeColor(RB_STROKE)
        self.rubber_band.setWidth(1)

    def canvasMoveEvent(self, event):
        if event.buttons() != Qt.LeftButton:
            return

        if not self.dragging:
            self.dragging = True
            self.center = event.pos()

        self.tangent_point = event.pos()
        self.radius = sqrt(QPointF.dotProduct(self.center, self.tangent_point))
        self._set_rubber_band()

    def canvasReleaseEvent(self, event):
        # If the user simply clicked without dragging ignore this
        if not self.dragging:
            return

        if self.rubber_band:
            self._set_rubber_band()

            self.rubber_band.reset(QgsWkbTypes.PolygonGeometry)
            del self.rubber_band
            self.rubber_band = None

        self.dragging = False

        # noinspection PyUnresolvedReferences
        self.circleSelected.emit(self.circle)

    def _set_rubber_band(self):
        transform = self.canvas.getCoordinateTransform()

        rb_center = transform.toMapCoordinates(self.center)
        rb_tangent = transform.toMapCoordinates(self.tangent_point)
        rb_circle = QgsCircle(
            QgsPoint(rb_center.x(), rb_center.y()),
            rb_center.distance(rb_tangent.x(), rb_tangent.y()))
        circle_geom = QgsGeometry(rb_circle.toPolygon())

        if self.rubber_band:
            self.rubber_band.reset(QgsWkbTypes.PolygonGeometry)
            self.rubber_band.addGeometry(circle_geom)
            self.circle = circle_geom
Example #30
0
class Qgis2threejsDialog(QDialog):

  def __init__(self, iface, objectTypeManager, pluginManager, exportSettings=None, lastTreeItemData=None):
    QDialog.__init__(self, iface.mainWindow())
    self.iface = iface
    self.objectTypeManager = objectTypeManager
    self.pluginManager = pluginManager
    self._settings = exportSettings or {}
    self.lastTreeItemData = lastTreeItemData
    self.localBrowsingMode = True

    self.rb_quads = self.rb_point = None

    self.templateType = None
    self.currentItem = None
    self.currentPage = None

    # Set up the user interface from Designer.
    self.ui = ui = Ui_Qgis2threejsDialog()
    ui.setupUi(self)

    self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint)

    # output html filename
    ui.lineEdit_OutputFilename.setText(self._settings.get("OutputFilename", ""))
    ui.lineEdit_OutputFilename.setPlaceholderText("[Temporary file]")

    # settings button
    icon = QIcon(os.path.join(tools.pluginDir(), "icons", "settings.png"))
    ui.toolButton_Settings.setIcon(icon)

    # popup menu displayed when settings button is pressed
    items = [["Load Settings...", self.loadSettings],
             ["Save Settings As...", self.saveSettings],
             [None, None],
             ["Clear Settings", self.clearSettings],
             [None, None],
             ["Plugin Settings...", self.pluginSettings]]

    self.menu = QMenu()
    self.menu_actions = []
    for text, slot in items:
      if text:
        action = QAction(text, iface.mainWindow())
        action.triggered.connect(slot)
        self.menu.addAction(action)
        self.menu_actions.append(action)
      else:
        self.menu.addSeparator()

    ui.toolButton_Settings.setMenu(self.menu)
    ui.toolButton_Settings.setPopupMode(QToolButton.InstantPopup)

    # progress bar and message label
    ui.progressBar.setVisible(False)
    ui.label_MessageIcon.setVisible(False)

    # buttons
    ui.pushButton_Run.clicked.connect(self.run)
    ui.pushButton_Close.clicked.connect(self.reject)
    ui.pushButton_Help.clicked.connect(self.help)

    # set up map tool
    self.previousMapTool = None
    self.mapTool = RectangleMapTool(iface.mapCanvas())
    #self.mapTool = PointMapTool(iface.mapCanvas())

    # set up the template combo box
    self.initTemplateList()
    self.ui.comboBox_Template.currentIndexChanged.connect(self.currentTemplateChanged)

    # set up the properties pages
    self.pages = {}
    self.pages[ppages.PAGE_WORLD] = ppages.WorldPropertyPage(self)
    self.pages[ppages.PAGE_CONTROLS] = ppages.ControlsPropertyPage(self)
    self.pages[ppages.PAGE_DEM] = ppages.DEMPropertyPage(self)
    self.pages[ppages.PAGE_VECTOR] = ppages.VectorPropertyPage(self)
    container = ui.propertyPagesContainer
    for page in self.pages.itervalues():
      page.hide()
      container.addWidget(page)

    # build object tree
    self.topItemPages = {ObjectTreeItem.ITEM_WORLD: ppages.PAGE_WORLD, ObjectTreeItem.ITEM_CONTROLS: ppages.PAGE_CONTROLS, ObjectTreeItem.ITEM_DEM: ppages.PAGE_DEM}
    self.initObjectTree()
    self.ui.treeWidget.currentItemChanged.connect(self.currentObjectChanged)
    self.ui.treeWidget.itemChanged.connect(self.objectItemChanged)
    self.currentTemplateChanged()   # update item visibility

    ui.toolButton_Browse.clicked.connect(self.browseClicked)

    #iface.mapCanvas().mapToolSet.connect(self.mapToolSet)    # to show button to enable own map tool

  def settings(self, clean=False):
    # save settings of current panel
    item = self.ui.treeWidget.currentItem()
    if item and self.currentPage:
      self.saveProperties(item, self.currentPage)

    # plugin version
    self._settings["PluginVersion"] = plugin_version

    # template and output html file path
    self._settings["Template"] = self.ui.comboBox_Template.currentText()
    self._settings["OutputFilename"] = self.ui.lineEdit_OutputFilename.text()

    if not clean:
      return self._settings

    # clean up settings - remove layers that don't exist in the layer registry
    registry = QgsMapLayerRegistry.instance()
    for itemId in [ObjectTreeItem.ITEM_OPTDEM, ObjectTreeItem.ITEM_POINT, ObjectTreeItem.ITEM_LINE, ObjectTreeItem.ITEM_POLYGON]:
      parent = self._settings.get(itemId, {})
      for layerId in parent.keys():
        if registry.mapLayer(layerId) is None:
          del parent[layerId]

    return self._settings

  def setSettings(self, settings):
    self._settings = settings

    # template and output html file path
    templateName = settings.get("Template")
    if templateName:
      cbox = self.ui.comboBox_Template
      index = cbox.findText(templateName)
      if index != -1:
        cbox.setCurrentIndex(index)

    filename = settings.get("OutputFilename")
    if filename:
      self.ui.lineEdit_OutputFilename.setText(filename)

    # update object tree
    self.ui.treeWidget.blockSignals(True)
    self.initObjectTree()
    self.ui.treeWidget.blockSignals(False)

    # update tree item visibility
    self.templateType = None
    self.currentTemplateChanged()

  def loadSettings(self):
    # file open dialog
    directory = QgsProject.instance().homePath()
    if not directory:
      directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0]
    if not directory:
      directory = QDir.homePath()
    filterString = "Settings files (*.qto3settings);;All files (*.*)"
    filename = QFileDialog.getOpenFileName(self, "Load Export Settings", directory, filterString)
    if not filename:
      return

    # load settings from file (.qto3settings)
    import json
    with open(filename) as f:
      settings = json.load(f)

    self.setSettings(settings)

  def saveSettings(self, filename=None):
    if not filename:
      # file save dialog
      directory = QgsProject.instance().homePath()
      if not directory:
        directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0]
      if not directory:
        directory = QDir.homePath()
      filename = QFileDialog.getSaveFileName(self, "Save Export Settings", directory, "Settings files (*.qto3settings)")
      if not filename:
        return

      # append .qto3settings extension if filename doesn't have
      if os.path.splitext(filename)[1].lower() != ".qto3settings":
        filename += ".qto3settings"

    # save settings to file (.qto3settings)
    import codecs
    import json
    with codecs.open(filename, "w", "UTF-8") as f:
      json.dump(self.settings(True), f, ensure_ascii=False, indent=2, sort_keys=True)

    logMessage(u"Settings saved: {0}".format(filename))

  def clearSettings(self):
    if QMessageBox.question(self, "Qgis2threejs", "Are you sure to clear all export settings?", QMessageBox.Ok | QMessageBox.Cancel) == QMessageBox.Ok:
      self.setSettings({})

  def pluginSettings(self):
    from settingsdialog import SettingsDialog
    dialog = SettingsDialog(self)
    if dialog.exec_():
      self.pluginManager.reloadPlugins()
      self.pages[ppages.PAGE_DEM].initLayerComboBox()

  def showMessageBar(self, text, level=QgsMessageBar.INFO):
    # from src/gui/qgsmessagebaritem.cpp
    if level == QgsMessageBar.CRITICAL:
      msgIcon = "/mIconCritical.png"
      bgColor = "#d65253"
    elif level == QgsMessageBar.WARNING:
      msgIcon = "/mIconWarn.png"
      bgColor = "#ffc800"
    else:
      msgIcon = "/mIconInfo.png"
      bgColor = "#e7f5fe"
    stylesheet = "QLabel {{ background-color:{0}; }}".format(bgColor)

    label = self.ui.label_MessageIcon
    label.setPixmap(QgsApplication.getThemeIcon(msgIcon).pixmap(24))
    label.setStyleSheet(stylesheet)
    label.setVisible(True)

    label = self.ui.label_Status
    label.setText(text)
    label.setStyleSheet(stylesheet)

  def clearMessageBar(self):
    self.ui.label_MessageIcon.setVisible(False)
    self.ui.label_Status.setText("")
    self.ui.label_Status.setStyleSheet("QLabel { background-color: rgba(0, 0, 0, 0); }")

  def initTemplateList(self):
    cbox = self.ui.comboBox_Template
    cbox.clear()
    templateDir = QDir(tools.templateDir())
    for i, entry in enumerate(templateDir.entryList(["*.html", "*.htm"])):
      cbox.addItem(entry)

      config = tools.getTemplateConfig(entry)
      # get template type
      templateType = config.get("type", "plain")
      cbox.setItemData(i, templateType, Qt.UserRole)

      # set tool tip text
      desc = config.get("description", "")
      if desc:
        cbox.setItemData(i, desc, Qt.ToolTipRole)

    # select the template of the settings
    templatePath = self._settings.get("Template")

    # if no template setting, select the last used template
    if not templatePath:
      templatePath = QSettings().value("/Qgis2threejs/lastTemplate", def_vals.template, type=unicode)

    if templatePath:
      index = cbox.findText(templatePath)
      if index != -1:
        cbox.setCurrentIndex(index)
      return index
    return -1

  def initObjectTree(self):
    tree = self.ui.treeWidget
    tree.clear()

    # add vector and raster layers into tree widget
    topItems = {}
    for id, name in zip(ObjectTreeItem.topItemIds, ObjectTreeItem.topItemNames):
      item = QTreeWidgetItem(tree, [name])
      item.setData(0, Qt.UserRole, id)
      topItems[id] = item

    optDEMChecked = False
    for layer in self.iface.legendInterface().layers():
      parentId = ObjectTreeItem.parentIdByLayer(layer)
      if parentId is None:
        continue

      item = QTreeWidgetItem(topItems[parentId], [layer.name()])
      isVisible = self._settings.get(parentId, {}).get(layer.id(), {}).get("visible", False)   #self.iface.legendInterface().isLayerVisible(layer)
      check_state = Qt.Checked if isVisible else Qt.Unchecked
      item.setData(0, Qt.CheckStateRole, check_state)
      item.setData(0, Qt.UserRole, layer.id())
      if parentId == ObjectTreeItem.ITEM_OPTDEM and isVisible:
        optDEMChecked = True

    for id, item in topItems.iteritems():
      if id != ObjectTreeItem.ITEM_OPTDEM or optDEMChecked:
        tree.expandItem(item)

    # disable additional DEM item which is selected as main DEM
    layerId = self._settings.get(ObjectTreeItem.ITEM_DEM, {}).get("comboBox_DEMLayer")
    if layerId:
      self.primaryDEMChanged(layerId)

  def saveProperties(self, item, page):
    properties = page.properties()
    parent = item.parent()
    if parent is None:
      # top level item
      self._settings[item.data(0, Qt.UserRole)] = properties
    else:
      # layer item
      parentId = parent.data(0, Qt.UserRole)
      if parentId not in self._settings:
        self._settings[parentId] = {}
      self._settings[parentId][item.data(0, Qt.UserRole)] = properties

  def setCurrentTreeItemByData(self, data):
    it = QTreeWidgetItemIterator(self.ui.treeWidget)
    while it.value():
      if it.value().data(0, Qt.UserRole) == data:
        self.ui.treeWidget.setCurrentItem(it.value())
        return True
      it += 1
    return False

  def currentTemplateChanged(self, index=None):
    cbox = self.ui.comboBox_Template
    templateType = cbox.itemData(cbox.currentIndex(), Qt.UserRole)
    if templateType == self.templateType:
      return

    # hide items unsupported by template
    tree = self.ui.treeWidget
    for i, id in enumerate(ObjectTreeItem.topItemIds):
      hidden = (templateType == "sphere" and id != ObjectTreeItem.ITEM_CONTROLS)
      tree.topLevelItem(i).setHidden(hidden)

    # set current tree item
    if templateType == "sphere":
      tree.setCurrentItem(tree.topLevelItem(ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_CONTROLS)))
    elif self.lastTreeItemData is None or not self.setCurrentTreeItemByData(self.lastTreeItemData):   # restore selection
      tree.setCurrentItem(tree.topLevelItem(ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_DEM)))   # default selection for plain is DEM

    # display messages
    self.clearMessageBar()
    if templateType != "sphere":
      # show message if crs unit is degrees
      mapSettings = self.iface.mapCanvas().mapSettings() if QGis.QGIS_VERSION_INT >= 20300 else self.iface.mapCanvas().mapRenderer()
      if mapSettings.destinationCrs().mapUnits() in [QGis.Degrees]:
        self.showMessageBar("The unit of current CRS is degrees, so terrain may not appear well.", QgsMessageBar.WARNING)

    self.templateType = templateType

  def currentObjectChanged(self, currentItem, previousItem):
    # save properties of previous item
    if previousItem and self.currentPage:
      self.saveProperties(previousItem, self.currentPage)

    self.currentItem = currentItem
    self.currentPage = None

    # hide text browser and all pages
    self.ui.textBrowser.hide()
    for page in self.pages.itervalues():
      page.hide()

    parent = currentItem.parent()
    if parent is None:
      topItemIndex = currentItem.data(0, Qt.UserRole)
      pageType = self.topItemPages.get(topItemIndex, ppages.PAGE_NONE)
      page = self.pages.get(pageType, None)
      if page is None:
        self.showDescription(topItemIndex)
        return

      page.setup(self._settings.get(topItemIndex))
      page.show()

    else:
      parentId = parent.data(0, Qt.UserRole)
      layerId = currentItem.data(0, Qt.UserRole)
      layer = QgsMapLayerRegistry.instance().mapLayer(unicode(layerId))
      if layer is None:
        return

      layerType = layer.type()
      if layerType == QgsMapLayer.RasterLayer:
        page = self.pages[ppages.PAGE_DEM]
        page.setup(self._settings.get(parentId, {}).get(layerId, None), layer, False)
      elif layerType == QgsMapLayer.VectorLayer:
        page = self.pages[ppages.PAGE_VECTOR]
        page.setup(self._settings.get(parentId, {}).get(layerId, None), layer)
      else:
        return

      page.show()

    self.currentPage = page

  def objectItemChanged(self, item, column):
    parent = item.parent()
    if parent is None:
      return

    # checkbox of optional layer checked/unchecked
    if item == self.currentItem:
      if self.currentPage:
        # update enablement of property widgets
        self.currentPage.itemChanged(item)
    else:
      # select changed item
      self.ui.treeWidget.setCurrentItem(item)

      # set visible property
      #visible = item.data(0, Qt.CheckStateRole) == Qt.Checked
      #parentId = parent.data(0, Qt.UserRole)
      #layerId = item.data(0, Qt.UserRole)
      #self._settings.get(parentId, {}).get(layerId, {})["visible"] = visible

  def primaryDEMChanged(self, layerId):
    tree = self.ui.treeWidget
    parent = tree.topLevelItem(ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_OPTDEM))
    tree.blockSignals(True)
    for i in range(parent.childCount()):
      item = parent.child(i)
      isPrimary = item.data(0, Qt.UserRole) == layerId
      item.setDisabled(isPrimary)
    tree.blockSignals(False)

  def showDescription(self, topItemIndex):
    fragment = {ObjectTreeItem.ITEM_OPTDEM: "additional-dem",
                ObjectTreeItem.ITEM_POINT: "point",
                ObjectTreeItem.ITEM_LINE: "line",
                ObjectTreeItem.ITEM_POLYGON: "polygon"}.get(topItemIndex)

    url = "http://qgis2threejs.readthedocs.org/en/docs-release/ExportSettings.html"
    if fragment:
      url += "#" + fragment

    html = '<a href="{0}">Online Help</a> about this item'.format(url)
    self.ui.textBrowser.setHtml(html)
    self.ui.textBrowser.show()

  def numericFields(self, layer):
    # get attributes of a sample feature and create numeric field name list
    numeric_fields = []
    f = QgsFeature()
    layer.getFeatures().nextFeature(f)
    for field in f.fields():
      isNumeric = False
      try:
        float(f.attribute(field.name()))
        isNumeric = True
      except ValueError:
        pass
      if isNumeric:
        numeric_fields.append(field.name())
    return numeric_fields

  def mapTo3d(self):
    canvas = self.iface.mapCanvas()
    mapSettings = canvas.mapSettings() if QGis.QGIS_VERSION_INT >= 20300 else canvas.mapRenderer()

    world = self._settings.get(ObjectTreeItem.ITEM_WORLD, {})
    bs = float(world.get("lineEdit_BaseSize", def_vals.baseSize))
    ve = float(world.get("lineEdit_zFactor", def_vals.zExaggeration))
    vs = float(world.get("lineEdit_zShift", def_vals.zShift))

    return MapTo3D(mapSettings, bs, ve, vs)

  def progress(self, percentage=None, statusMsg=None):
    ui = self.ui
    if percentage is not None:
      ui.progressBar.setValue(percentage)
      if percentage == 100:
        ui.progressBar.setVisible(False)
        ui.label_Status.setText("")
      else:
        ui.progressBar.setVisible(True)

    if statusMsg is not None:
      ui.label_Status.setText(statusMsg)
      ui.label_Status.repaint()
    QgsApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

  def run(self):
    self.endPointSelection()

    ui = self.ui
    filename = ui.lineEdit_OutputFilename.text()   # ""=Temporary file
    if filename and os.path.exists(filename):
      if QMessageBox.question(self, "Qgis2threejs", "Output file already exists. Overwrite it?", QMessageBox.Ok | QMessageBox.Cancel) != QMessageBox.Ok:
        return

    # export to web (three.js)
    export_settings = ExportSettings(self.pluginManager, self.localBrowsingMode)
    export_settings.loadSettings(self.settings())
    export_settings.setMapCanvas(self.iface.mapCanvas())

    err_msg = export_settings.checkValidity()
    if err_msg is not None:
      QMessageBox.warning(self, "Qgis2threejs", err_msg or "Invalid settings")
      return

    ui.pushButton_Run.setEnabled(False)
    ui.toolButton_Settings.setVisible(False)
    self.clearMessageBar()
    self.progress(0)

    if export_settings.exportMode == ExportSettings.PLAIN_MULTI_RES:
      # update quads and point on map canvas
      self.createRubberBands(export_settings.baseExtent, export_settings.quadtree())

    # export
    ret = exportToThreeJS(export_settings, self.iface.legendInterface(), self.objectTypeManager, self.progress)

    self.progress(100)
    ui.pushButton_Run.setEnabled(True)

    if not ret:
      ui.toolButton_Settings.setVisible(True)
      return

    self.clearRubberBands()

    # store last selections
    settings = QSettings()
    settings.setValue("/Qgis2threejs/lastTemplate", export_settings.templatePath)
    settings.setValue("/Qgis2threejs/lastControls", export_settings.controls)

    # open web browser
    if not tools.openHTMLFile(export_settings.htmlfilename):
      ui.toolButton_Settings.setVisible(True)
      return

    # close dialog
    QDialog.accept(self)

  def reject(self):
    # save properties of current object
    item = self.ui.treeWidget.currentItem()
    if item and self.currentPage:
      self.saveProperties(item, self.currentPage)

    self.endPointSelection()
    self.clearRubberBands()
    QDialog.reject(self)

  def help(self):
    url = "http://qgis2threejs.readthedocs.org/"

    import webbrowser
    webbrowser.open(url, new=2)    # new=2: new tab if possible

  def startPointSelection(self):
    canvas = self.iface.mapCanvas()
    if self.previousMapTool != self.mapTool:
      self.previousMapTool = canvas.mapTool()
    canvas.setMapTool(self.mapTool)
    self.pages[ppages.PAGE_DEM].toolButton_PointTool.setVisible(False)

  def endPointSelection(self):
    self.mapTool.reset()
    if self.previousMapTool is not None:
      self.iface.mapCanvas().setMapTool(self.previousMapTool)

  def mapToolSet(self, mapTool):
    return
    #TODO: unstable
    if mapTool != self.mapTool and self.currentPage is not None:
      if self.currentPage.pageType == ppages.PAGE_DEM and self.currentPage.isPrimary:
        self.currentPage.toolButton_PointTool.setVisible(True)

  def createRubberBands(self, baseExtent, quadtree):
    self.clearRubberBands()
    # create quads with rubber band
    self.rb_quads = QgsRubberBand(self.iface.mapCanvas(), QGis.Line)
    self.rb_quads.setColor(Qt.blue)
    self.rb_quads.setWidth(1)

    quads = quadtree.quads()
    for quad in quads:
      geom = baseExtent.subrectangle(quad.rect).geometry()
      self.rb_quads.addGeometry(geom, None)
    self.log("Quad count: %d" % len(quads))

    if not quadtree.focusRect:
      return

    # create a point with rubber band
    if quadtree.focusRect.width() == 0 or quadtree.focusRect.height() == 0:
      npt = quadtree.focusRect.center()
      self.rb_point = QgsRubberBand(self.iface.mapCanvas(), QGis.Point)
      self.rb_point.setColor(Qt.red)
      self.rb_point.addPoint(baseExtent.point(npt))

  def clearRubberBands(self):
    # clear quads and point
    if self.rb_quads:
      self.iface.mapCanvas().scene().removeItem(self.rb_quads)
      self.rb_quads = None
    if self.rb_point:
      self.iface.mapCanvas().scene().removeItem(self.rb_point)
      self.rb_point = None

  def browseClicked(self):
    directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0]
    if not directory:
      directory = QDir.homePath()
    filename = QFileDialog.getSaveFileName(self, self.tr("Output filename"), directory, "HTML file (*.html *.htm)", options=QFileDialog.DontConfirmOverwrite)
    if not filename:
      return

    # append .html extension if filename doesn't have either .html or .htm
    if filename[-5:].lower() != ".html" and filename[-4:].lower() != ".htm":
      filename += ".html"

    self.ui.lineEdit_OutputFilename.setText(filename)

  def log(self, msg):
    if debug_mode:
      qDebug(msg)
class SimpleIntersectionMapTool(QgsMapTool):
    def __init__(self, iface):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        QgsMapTool.__init__(self, self.mapCanvas)
        self.settings = MySettings()
        self.rubber = QgsRubberBand(self.mapCanvas)

    def deactivate(self):
        self.rubber.reset()
        self.mapCanvas.layersChanged.disconnect(self.updateSnapperList)
        self.mapCanvas.scaleChanged.disconnect(self.updateSnapperList)
        QgsMapTool.deactivate(self)

    def activate(self):
        QgsMapTool.activate(self)
        self.rubber.setWidth(self.settings.value("rubberWidth"))
        self.rubber.setColor(self.settings.value("rubberColor"))
        self.updateSnapperList()
        self.mapCanvas.layersChanged.connect(self.updateSnapperList)
        self.mapCanvas.scaleChanged.connect(self.updateSnapperList)
        self.checkLayer()

    def updateSnapperList(self, dummy=None):
        # make a snapper list of all line and polygons layers
        self.snapperList = []
        scale = self.iface.mapCanvas().mapRenderer().scale()
        for layer in self.mapCanvas.layers():
            if layer.type() == QgsMapLayer.VectorLayer and layer.hasGeometryType():
                if layer.geometryType() in (QGis.Line, QGis.Polygon):
                    if not layer.hasScaleBasedVisibility() or layer.minimumScale() < scale <= layer.maximumScale():
                        snapLayer = QgsSnapper.SnapLayer()
                        snapLayer.mLayer = layer
                        snapLayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment
                        snapLayer.mTolerance = self.settings.value("selectTolerance")
                        if self.settings.value("selectUnits") == "map":
                            snapLayer.mUnitType = QgsTolerance.MapUnits
                        else:
                            snapLayer.mUnitType = QgsTolerance.Pixels
                        self.snapperList.append(snapLayer)

    def canvasMoveEvent(self, mouseEvent):
        # put the observations within tolerance in the rubber band
        self.rubber.reset()
        for f in self.getFeatures(mouseEvent.pos()):
            self.rubber.addGeometry(f.geometry(), None)

    def canvasPressEvent(self, mouseEvent):
        self.rubber.reset()
        pos = mouseEvent.pos()
        features = self.getFeatures(pos)
        nFeat = len(features)
        if nFeat < 2:
            layerNames = " , ".join([feature.layer.name() for feature in features])
            self.iface.messageBar().pushMessage("Intersect It", "You need 2 features to proceed a simple intersection."
                                                " %u given (%s)" % (nFeat, layerNames), QgsMessageBar.WARNING, 3)
            return
        intersectionP = self.intersection(features, pos)
        if intersectionP == QgsPoint(0,0):
            self.iface.messageBar().pushMessage("Intersect It", "Objects do not intersect.", QgsMessageBar.WARNING, 2)
            return

        layer = self.checkLayer()
        if layer is None:
            return
        f = QgsFeature()
        initFields = layer.dataProvider().fields()
        f.setFields(initFields)
        f.initAttributes(initFields.size())
        f.setGeometry(QgsGeometry().fromPoint(intersectionP))
        layer.editBuffer().addFeature(f)
        layer.triggerRepaint()

    def getFeatures(self, pixPoint):
        # do the snapping
        snapper = QgsSnapper(self.mapCanvas.mapRenderer())
        snapper.setSnapLayers(self.snapperList)
        snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances)
        ok, snappingResults = snapper.snapPoint(pixPoint, [])
        # output snapped features
        features = []
        alreadyGot = []
        for result in snappingResults:
            featureId = result.snappedAtGeometry
            f = QgsFeature()
            if (result.layer.id(), featureId) not in alreadyGot:
                if result.layer.getFeatures(QgsFeatureRequest().setFilterFid(featureId)).nextFeature(f) is False:
                    continue
                if not isFeatureRendered(self.mapCanvas, result.layer, f):
                    continue
                features.append(QgsFeature(f))
                features[-1].layer = result.layer
                alreadyGot.append((result.layer.id(), featureId))
        return features

    def intersection(self, features, pos):
        # try all the combinations
        nFeat = len(features)
        intersections = []
        for i in range(nFeat-1):
            for j in range(i+1,nFeat):
                intersection = features[i].geometry().intersection(features[j].geometry())
                intersectionMP = intersection.asMultiPoint()
                intersectionP = intersection.asPoint()
                if len(intersectionMP) == 0:
                    intersectionMP = intersection.asPolyline()
                if len(intersectionMP) == 0 and intersectionP == QgsPoint(0, 0):
                    continue
                if len(intersectionMP) > 1:
                    mousePoint = self.toMapCoordinates(pos)
                    intersectionP = intersectionMP[0]
                    for point in intersectionMP[1:]:
                        if mousePoint.sqrDist(point) < mousePoint.sqrDist(intersectionP):
                            intersectionP = QgsPoint(point.x(), point.y())
                if intersectionP != QgsPoint(0,0):
                    intersections.append(intersectionP)
        if len(intersections) == 0:
            return QgsPoint(0,0)
        intersectionP = intersections[0]
        for point in intersections[1:]:
            if mousePoint.sqrDist(point) < mousePoint.sqrDist(intersectionP):
                intersectionP = QgsPoint(point.x(), point.y())
        return intersectionP

    def checkLayer(self):
        # check output layer is defined
        layerid = self.settings.value("simpleIntersectionLayer")
        layer = QgsMapLayerRegistry.instance().mapLayer(layerid)
        if not self.settings.value("simpleIntersectionWritePoint") or layer is None:
            self.iface.messageBar().pushMessage("Intersect It",
                                                "You must define an output layer for simple intersections",
                                                QgsMessageBar.WARNING, 3)
            self.mapCanvas.unsetMapTool(self)
            return None
        if not layer.isEditable():
            self.iface.messageBar().pushMessage("Intersect It",
                                                "The output layer <b>%s must be editable</b>" % layer.name(),
                                                QgsMessageBar.WARNING, 3)
            self.mapCanvas.unsetMapTool(self)
            return None
        return layer
Example #32
0
class FinderBox(QComboBox):

    running = False
    toFinish = 0

    searchStarted = pyqtSignal()
    searchFinished = pyqtSignal()

    def __init__(self, finders, iface, parent=None):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        self.rubber = QgsRubberBand(self.mapCanvas)
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

        QComboBox.__init__(self, parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.InsertAtTop)
        self.setMinimumHeight(27)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.insertSeparator(0)
        self.lineEdit().returnPressed.connect(self.search)

        self.resultView = QTreeView()
        self.resultView.setHeaderHidden(True)
        self.resultView.setMinimumHeight(300)
        self.resultView.activated.connect(self.itemActivated)
        self.resultView.pressed.connect(self.itemPressed)
        self.setView(self.resultView)

        self.resultModel = ResultModel(self)
        self.setModel(self.resultModel)

        self.finders = finders
        for finder in self.finders.values():
            finder.resultFound.connect(self.resultFound)
            finder.limitReached.connect(self.limitReached)
            finder.finished.connect(self.finished)

        self.clearButton = QPushButton(self)
        self.clearButton.setIcon(
            QIcon(":/plugins/quickfinder/icons/draft.svg"))
        self.clearButton.setText('')
        self.clearButton.setFlat(True)
        self.clearButton.setCursor(QCursor(Qt.ArrowCursor))
        self.clearButton.setStyleSheet('border: 0px; padding: 0px;')
        self.clearButton.clicked.connect(self.clear)

        layout = QHBoxLayout(self)
        self.setLayout(layout)
        layout.addStretch()
        layout.addWidget(self.clearButton)
        layout.addSpacing(20)

        buttonSize = self.clearButton.sizeHint()
        # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
        padding = buttonSize.width()  # + frameWidth + 1
        self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' %
                                      padding)

    def __del__(self):
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def clearSelection(self):
        self.resultModel.setSelected(None, self.resultView.palette())
        self.rubber.reset()

    def clear(self):
        self.clearSelection()
        self.resultModel.clearResults()
        self.lineEdit().setText('')

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.clearSelection()
        QComboBox.keyPressEvent(self, event)

    def search(self):
        if self.running:
            return

        toFind = self.lineEdit().text()
        if not toFind or toFind == '':
            return

        self.running = True
        self.searchStarted.emit()

        self.clearSelection()
        self.resultModel.clearResults()
        self.resultModel.truncateHistory(MySettings().value("historyLength"))
        self.resultModel.setLoading(True)
        self.showPopup()

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        self.findersToStart = []
        for finder in self.finders.values():
            if finder.activated():
                self.findersToStart.append(finder)

        bbox = self.mapCanvas.fullExtent()

        while len(self.findersToStart) > 0:
            finder = self.findersToStart[0]
            self.findersToStart.remove(finder)
            self.resultModel.addResult(finder.name)
            finder.start(toFind, bbox=bbox)

        # For case there is no finder activated
        self.finished(None)

    def stop(self):
        self.findersToStart = []
        for finder in self.finders.values():
            if finder.isRunning():
                finder.stop()
        self.finished(None)

    def resultFound(self, finder, layername, value, geometry, srid):
        self.resultModel.addResult(finder.name, layername, value, geometry,
                                   srid)
        self.resultView.expandAll()

    def limitReached(self, finder, layername):
        self.resultModel.addEllipsys(finder.name, layername)

    def finished(self, finder):
        if len(self.findersToStart) > 0:
            return
        for finder in self.finders.values():
            if finder.isRunning():
                return

        self.running = False
        self.searchFinished.emit()

        self.resultModel.setLoading(False)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def itemActivated(self, index):
        item = self.resultModel.itemFromIndex(index)
        self.showItem(item)

    def itemPressed(self, index):
        item = self.resultModel.itemFromIndex(index)
        if QApplication.mouseButtons() == Qt.LeftButton:
            self.showItem(item)

    def showItem(self, item):
        if isinstance(item, ResultItem):
            self.resultModel.setSelected(item, self.resultView.palette())
            geometry = self.transformGeom(item)
            self.rubber.reset(geometry.type())
            self.rubber.setToGeometry(geometry, None)
            self.zoomToRubberBand()
            return

        if isinstance(item, GroupItem):
            child = item.child(0)
            if isinstance(child, ResultItem):
                self.resultModel.setSelected(item, self.resultView.palette())
                self.rubber.reset(child.geometry.type())
                for i in xrange(0, item.rowCount()):
                    geometry = self.transformGeom(item.child(i))
                    self.rubber.addGeometry(geometry, None)
                self.zoomToRubberBand()
            return

        if item.__class__.__name__ == 'QStandardItem':
            self.clearSelection()

    def transformGeom(self, item):
        src_crs = QgsCoordinateReferenceSystem()
        src_crs.createFromSrid(item.srid)
        dest_crs = self.mapCanvas.mapRenderer().destinationCrs()
        geom = QgsGeometry(item.geometry)
        geom.transform(QgsCoordinateTransform(src_crs, dest_crs))
        return geom

    def zoomToRubberBand(self):
        geom = self.rubber.asGeometry()
        if geom:
            rect = geom.boundingBox()
            rect.scale(1.5)
            self.mapCanvas.setExtent(rect)
            self.mapCanvas.refresh()
Example #33
0
class MoveTool(QgsMapTool):
    def __init__(self, iface):
        """
        Constructor
        :param iface: interface
        """
        QgsMapTool.__init__(self, iface.mapCanvas())
        self.__iface = iface
        self.__canvas = iface.mapCanvas()
        self.__icon_path = ':/plugins/VDLTools/icons/move_icon.png'
        self.__text = QCoreApplication.translate("VDLTools",
                                                 "Move/Copy a feature")
        self.setCursor(Qt.ArrowCursor)
        self.__isEditing = 0
        self.__findVertex = 0
        self.__onMove = 0
        self.__counter = 0
        self.__layer = None
        self.__confDlg = None
        self.__lastFeatureId = None
        self.__selectedFeature = None
        self.__rubberBand = None
        self.__rubberSnap = None
        self.__newFeature = None
        self.__selectedVertex = None
        self.__layerConfig = None

    def icon_path(self):
        """
        To get the icon path
        :return: icon path
        """
        return self.__icon_path

    def text(self):
        """
        To get the menu text
        :return: menu text
        """
        return self.__text

    def toolName(self):
        """
        To get the tool name
        :return: tool name
        """
        return QCoreApplication.translate("VDLTools", "Move/Copy")

    def activate(self):
        """
        When the action is selected
        """
        QgsMapTool.activate(self)
        self.__updateList()
        self.__canvas.layersChanged.connect(self.__updateList)
        QgsProject.instance().snapSettingsChanged.connect(self.__updateList)

    def deactivate(self):
        """
        When the action is deselected
        """
        self.__canvas.layersChanged.disconnect(self.__updateList)
        QgsProject.instance().snapSettingsChanged.disconnect(self.__updateList)
        QgsMapTool.deactivate(self)

    def startEditing(self):
        """
        To set the action as enable, as the layer is editable
        """
        self.action().setEnabled(True)
        self.__layer.editingStarted.disconnect(self.startEditing)
        self.__layer.editingStopped.connect(self.stopEditing)

    def stopEditing(self):
        """
        To set the action as disable, as the layer is not editable
        """
        self.action().setEnabled(False)
        self.__layer.editingStopped.disconnect(self.stopEditing)
        self.__layer.editingStarted.connect(self.startEditing)
        if self.__canvas.mapTool == self:
            self.__iface.actionPan().trigger()

    def setTool(self):
        """
        To set the current tool as this one
        """
        self.__canvas.setMapTool(self)

    def removeLayer(self):
        """
        To remove the current working layer
        """
        if self.__layer is not None:
            if self.__layer.isEditable():
                self.__layer.editingStopped.disconnect(self.stopEditing)
            else:
                self.__layer.editingStarted.disconnect(self.startEditing)
            self.__layer = None

    def setEnable(self, layer):
        """
        To check if we can enable the action for the selected layer
        :param layer: selected layer
        """
        if layer is not None\
                and isinstance(layer, QgsVectorLayer):
            if layer == self.__layer:
                return

            if self.__layer is not None:
                if self.__layer.isEditable():
                    self.__layer.editingStopped.disconnect(self.stopEditing)
                else:
                    self.__layer.editingStarted.disconnect(self.startEditing)
            self.__layer = layer
            if self.__layer.isEditable():
                self.action().setEnabled(True)
                self.__updateList()
                self.__layer.editingStopped.connect(self.stopEditing)
            else:
                self.action().setEnabled(False)
                self.__layer.editingStarted.connect(self.startEditing)
                if self.__canvas.mapTool == self:
                    self.__iface.actionPan().trigger()
            return
        self.action().setEnabled(False)
        self.removeLayer()

    def __pointPreview(self, point):
        """
        To create a point geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        point_v2 = GeometryV2.asPointV2(self.__selectedFeature.geometry())
        self.__newFeature = QgsPointV2(point.x(), point.y())
        self.__newFeature.addZValue(point_v2.z())
        self.__rubberBand = QgsRubberBand(self.__canvas, QGis.Point)
        self.__rubberBand.setToGeometry(QgsGeometry(self.__newFeature.clone()),
                                        None)

    def __linePreview(self, point):
        """
        To create a line geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        line_v2, curved = GeometryV2.asLineV2(
            self.__selectedFeature.geometry())
        print(self.__selectedVertex)
        vertex = QgsPointV2()
        line_v2.pointAt(self.__selectedVertex, vertex)
        self.__rubberBand = QgsRubberBand(self.__canvas, QGis.Line)
        dx = vertex.x() - point.x()
        dy = vertex.y() - point.y()
        if isinstance(curved, (list, tuple)):
            self.__newFeature = QgsCompoundCurveV2()
            for pos in xrange(line_v2.nCurves()):
                curve_v2 = self.__newCurve(curved[pos], line_v2.curveAt(pos),
                                           dx, dy)
                self.__newFeature.addCurve(curve_v2)
                if pos == 0:
                    self.__rubberBand.setToGeometry(
                        QgsGeometry(curve_v2.curveToLine()), None)
                else:
                    self.__rubberBand.addGeometry(
                        QgsGeometry(curve_v2.curveToLine()), None)
        else:
            self.__newFeature = self.__newCurve(curved, line_v2, dx, dy)
            self.__rubberBand.setToGeometry(
                QgsGeometry(self.__newFeature.curveToLine()), None)

    def __newCurve(self, curved, line_v2, dx, dy):
        if curved:
            newCurve = QgsCircularStringV2()
        else:
            newCurve = QgsLineStringV2()
        points = []
        for pos in xrange(line_v2.numPoints()):
            x = line_v2.pointN(pos).x() - dx
            y = line_v2.pointN(pos).y() - dy
            pt = QgsPointV2(x, y)
            pt.addZValue(line_v2.pointN(pos).z())
            points.append(pt)
        newCurve.setPoints(points)
        return newCurve

    def __polygonPreview(self, point):
        """
        To create a polygon geometry preview (rubberBand)
        :param point: new position as mapPoint
        """
        polygon_v2, curved = GeometryV2.asPolygonV2(
            self.__selectedFeature.geometry())
        vertex = polygon_v2.vertexAt(self.__polygonVertexId(polygon_v2))
        dx = vertex.x() - point.x()
        dy = vertex.y() - point.y()
        self.__newFeature = QgsCurvePolygonV2()
        self.__rubberBand = QgsRubberBand(self.__canvas, QGis.Line)
        line_v2 = self.__newLine(polygon_v2.exteriorRing(), dx, dy, curved[0])
        self.__newFeature.setExteriorRing(line_v2)
        self.__rubberBand.setToGeometry(QgsGeometry(line_v2.curveToLine()),
                                        None)
        for num in xrange(polygon_v2.numInteriorRings()):
            line_v2 = self.__newLine(polygon_v2.interiorRing(num), dx, dy,
                                     curved[num + 1])
            self.__newFeature.addInteriorRing(line_v2)
            self.__rubberBand.addGeometry(QgsGeometry(line_v2.curveToLine()),
                                          None)

    def __polygonVertexId(self, polygon_v2):
        """
        To get the id of the selected vertex from a polygon
        :param polygon_v2: the polygon as polygonV2
        :return: id as QgsVertexId
        """
        eR = polygon_v2.exteriorRing()
        if self.__selectedVertex < eR.numPoints():
            return QgsVertexId(0, 0, self.__selectedVertex, 1)
        else:
            sel = self.__selectedVertex - eR.numPoints()
            for num in xrange(polygon_v2.numInteriorRings()):
                iR = polygon_v2.interiorRing(num)
                if sel < iR.numPoints():
                    return QgsVertexId(0, num + 1, sel, 1)
                sel -= iR.numPoints()

    def __newLine(self, curve_v2, dx, dy, curved):
        """
        To create a new moved line for a part of a polygon
        :param curve_v2: the original line
        :param dx: x translation
        :param dy: y translation
        :return: the line as lineV2
        """
        if curved:
            new_line_v2 = QgsCircularStringV2()
        else:
            new_line_v2 = QgsLineStringV2()
        points = []

        for pos in xrange(curve_v2.numPoints()):
            x = curve_v2.pointN(pos).x() - dx
            y = curve_v2.pointN(pos).y() - dy
            pt = QgsPointV2(x, y)
            pt.addZValue(curve_v2.pointN(pos).z())
            points.append(pt)
        new_line_v2.setPoints(points)
        return new_line_v2

    def __onConfirmClose(self):
        """
        When the Cancel button in Move Confirm Dialog is pushed
        """
        self.__confDlg.close()
        self.__rubberBand.reset()
        self.__rubberSnap.reset()
        self.__isEditing = 0
        self.__lastFeatureId = None
        self.__selectedFeature = None
        self.__rubberBand = None
        self.__rubberSnap = None
        self.__newFeature = None
        self.__selectedVertex = None
        self.__layer.removeSelection()

    def __onConfirmMove(self):
        """
        When the Move button in Move Confirm Dialog is pushed
        """
        geometry = QgsGeometry(self.__newFeature)
        if not geometry.isGeosValid():
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Error"),
                QCoreApplication.translate("VDLTools",
                                           "Geos geometry problem"),
                level=QgsMessageBar.CRITICAL)
        self.__layer.changeGeometry(self.__selectedFeature.id(), geometry)
        self.__layer.updateExtents()
        self.__onConfirmClose()

    def __onConfirmCopy(self):
        """
        When the Copy button in Move Confirm Dialog is pushed
        """
        geometry = QgsGeometry(self.__newFeature)
        if not geometry.isGeosValid():
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Error"),
                QCoreApplication.translate("VDLTools",
                                           "Geos geometry problem"),
                level=QgsMessageBar.CRITICAL)
        feature = QgsFeature(self.__layer.pendingFields())
        feature.setGeometry(geometry)
        primaryKey = QgsDataSourceURI(self.__layer.source()).keyColumn()
        for field in self.__selectedFeature.fields():
            if field.name() != primaryKey:
                feature.setAttribute(
                    field.name(),
                    self.__selectedFeature.attribute(field.name()))
        if len(self.__selectedFeature.fields()) > 0 and \
                        self.__layer.editFormConfig().suppress() != QgsEditFormConfig.SuppressOn:
            self.__iface.openFeatureForm(self.__layer, feature)
        else:
            self.__layer.addFeature(feature)
        self.__layer.updateExtents()
        self.__onConfirmClose()

    def __updateList(self):
        """
        To update the snapping options of the layer
        """
        noUse, enabled, snappingType, unitType, tolerance, avoidIntersection = \
            QgsProject.instance().snapSettingsForLayer(self.__layer.id())
        self.__layerConfig = QgsSnappingUtils.LayerConfig(
            self.__layer, QgsPointLocator.Vertex, tolerance, unitType)
        if not enabled or tolerance == 0:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Error"),
                QCoreApplication.translate(
                    "VDLTools", "This layer has no snapping options"),
                level=QgsMessageBar.CRITICAL)

    def canvasMoveEvent(self, event):
        """
        When the mouse is moved
        :param event: mouse event
        """
        if not self.__isEditing and not self.__findVertex and not self.__onMove:
            f = Finder.findClosestFeatureAt(event.mapPoint(),
                                            self.__layerConfig, self)
            if f is not None and self.__lastFeatureId != f.id():
                self.__lastFeatureId = f.id()
                self.__layer.setSelectedFeatures([f.id()])
            if f is None:
                self.__layer.removeSelection()
                self.__lastFeatureId = None
        elif self.__findVertex:
            self.__rubberBand.reset()
            closest = self.__selectedFeature.geometry().closestVertex(
                event.mapPoint())
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            self.__rubberBand.setIcon(4)
            self.__rubberBand.setIconSize(20)
            self.__rubberBand.setToGeometry(
                QgsGeometry().fromPoint(closest[0]), None)
        elif self.__onMove:
            if self.__rubberBand:
                self.__rubberBand.reset()
            if self.__layer.geometryType() == QGis.Polygon:
                self.__polygonPreview(event.mapPoint())
            elif self.__layer.geometryType() == QGis.Line:
                self.__linePreview(event.mapPoint())
            else:
                self.__pointPreview(event.mapPoint())
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            self.__rubberBand.setWidth(2)
            if self.__layer.geometryType() != QGis.Point:
                self.__rubberBand.setLineStyle(Qt.DotLine)
            else:
                self.__rubberBand.setIcon(4)
                self.__rubberBand.setIconSize(20)
            if self.__rubberSnap:
                self.__rubberSnap.reset()
            else:
                self.__rubberSnap = QgsRubberBand(self.__canvas, QGis.Point)
            self.__rubberSnap.setColor(color)
            self.__rubberSnap.setWidth(2)
            self.__rubberSnap.setIconSize(20)
            match = Finder.snap(event.mapPoint(), self.__canvas, True)
            if match.hasVertex():
                if match.layer():
                    self.__rubberSnap.setIcon(4)
                    self.__rubberSnap.setToGeometry(
                        QgsGeometry().fromPoint(match.point()), None)
                else:
                    self.__rubberSnap.setIcon(1)
                    self.__rubberSnap.setToGeometry(
                        QgsGeometry().fromPoint(match.point()), None)
            if match.hasEdge():
                self.__rubberSnap.setIcon(3)
                self.__rubberSnap.setToGeometry(
                    QgsGeometry().fromPoint(match.point()), None)

            # if self.__counter > 2:
            #     if self.__rubberSnap:
            #         self.__rubberSnap.reset()
            #     else:
            #         self.__rubberSnap = QgsRubberBand(self.__canvas, QGis.Point)
            #     self.__rubberSnap.setColor(color)
            #     self.__rubberSnap.setWidth(2)
            #     self.__rubberSnap.setIconSize(20)
            #     snappedIntersection = Finder.snapToIntersection(event.mapPoint(), self, self.__layerList)
            #     if snappedIntersection is None:
            #         snappedPoint = Finder.snapToLayers(event.mapPoint(), self.__snapperList)
            #         if snappedPoint is not None:
            #             self.__rubberSnap.setIcon(4)
            #             self.__rubberSnap.setToGeometry(QgsGeometry().fromPoint(snappedPoint), None)
            #     else:
            #         self.__rubberSnap.setIcon(1)
            #         self.__rubberSnap.setToGeometry(QgsGeometry().fromPoint(snappedIntersection), None)
            #     self.__counter = 0
            # else:
            #     self.__counter += 1

    def canvasReleaseEvent(self, event):
        """
        When the mouse is clicked
        :param event: mouse event
        """
        if not self.__isEditing and not self.__findVertex and not self.__onMove:
            found_features = self.__layer.selectedFeatures()
            if len(found_features) > 0:
                if len(found_features) < 1:
                    self.__iface.messageBar().pushMessage(
                        QCoreApplication.translate("VDLTools",
                                                   "One feature at a time"),
                        level=QgsMessageBar.INFO)
                    return
                self.__selectedFeature = found_features[0]
                if self.__layer.geometryType() != QGis.Point:
                    self.__findVertex = 1
                    self.__rubberBand = QgsRubberBand(self.__canvas,
                                                      QGis.Point)
                else:
                    self.__onMove = 1
                    # self.__snapperList, self.__layerList = Finder.updateSnapperList(self.__iface)
        elif self.__findVertex:
            self.__findVertex = 0
            closest = self.__selectedFeature.geometry().closestVertex(
                event.mapPoint())
            self.__selectedVertex = closest[1]
            self.__onMove = 1
            # self.__snapperList, self.__layerList = Finder.updateSnapperList(self.__iface)
        elif self.__onMove:
            self.__onMove = 0
            mapPoint = event.mapPoint()
            match = Finder.snap(event.mapPoint(), self.__canvas, True)
            if match.hasVertex() or match.hasEdge():
                mapPoint = match.point()
            # snappedIntersection = Finder.snapToIntersection(event.mapPoint(), self, self.__layerList)
            # if snappedIntersection is None:
            #     snappedPoint = Finder.snapToLayers(event.mapPoint(), self.__snapperList)
            #     if snappedPoint is not None:
            #         mapPoint = snappedPoint
            # else:
            #     mapPoint = snappedIntersection
            self.__isEditing = 1
            if self.__rubberBand:
                self.__rubberBand.reset()
            if self.__layer.geometryType() == QGis.Polygon:
                self.__polygonPreview(mapPoint)
            elif self.__layer.geometryType() == QGis.Line:
                self.__linePreview(mapPoint)
            else:
                self.__pointPreview(mapPoint)
            color = QColor("red")
            color.setAlphaF(0.78)
            self.__rubberBand.setColor(color)
            if self.__layer.geometryType() != QGis.Point:
                self.__rubberBand.setWidth(2)
                self.__rubberBand.setLineStyle(Qt.DotLine)
            else:
                self.__rubberBand.setIcon(4)
                self.__rubberBand.setIconSize(20)
            self.__confDlg = MoveConfirmDialog()
            self.__confDlg.moveButton().clicked.connect(self.__onConfirmMove)
            self.__confDlg.copyButton().clicked.connect(self.__onConfirmCopy)
            self.__confDlg.cancelButton().clicked.connect(
                self.__onConfirmClose)
            self.__confDlg.show()
class LatLonTools:
    digitizerDialog = None
    convertCoordinateDialog = None
    mapTool = None
    showMapTool = None
    copyExtentTool = None

    def __init__(self, iface):
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.crossRb.setColor(Qt.red)
        self.provider = LatLonToolsProvider()
        self.toolbar = self.iface.addToolBar('Lat Lon Tools Toolbar')
        self.toolbar.setObjectName('LatLonToolsToolbar')

    def initGui(self):
        '''Initialize Lot Lon Tools GUI.'''
        # Initialize the Settings Dialog box
        self.settingsDialog = SettingsWidget(self, self.iface,
                                             self.iface.mainWindow())

        # Add Interface for Coordinate Capturing
        icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.svg")
        self.copyAction = QAction(icon, "Copy/Display Coordinate",
                                  self.iface.mainWindow())
        self.copyAction.setObjectName('latLonToolsCopy')
        self.copyAction.triggered.connect(self.startCapture)
        self.copyAction.setCheckable(True)
        self.toolbar.addAction(self.copyAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction)

        # Add Interface for External Map
        icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png")
        self.externMapAction = QAction(icon, "Show in External Map",
                                       self.iface.mainWindow())
        self.externMapAction.setObjectName('latLonToolsExternalMap')
        self.externMapAction.triggered.connect(self.setShowMapTool)
        self.externMapAction.setCheckable(True)
        self.toolbar.addAction(self.externMapAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction)

        # Add Interface for Zoom to Coordinate
        icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.svg")
        self.zoomToAction = QAction(icon, "Zoom To Coordinate",
                                    self.iface.mainWindow())
        self.zoomToAction.setObjectName('latLonToolsZoom')
        self.zoomToAction.triggered.connect(self.showZoomToDialog)
        self.toolbar.addAction(self.zoomToAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction)

        self.zoomToDialog = ZoomToLatLon(self, self.iface,
                                         self.iface.mainWindow())
        self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog)
        self.zoomToDialog.hide()

        # Add Interface for Multi point zoom
        icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.svg')
        self.multiZoomToAction = QAction(icon, "Multi-location Zoom",
                                         self.iface.mainWindow())
        self.multiZoomToAction.setObjectName('latLonToolsMultiZoom')
        self.multiZoomToAction.triggered.connect(self.multiZoomTo)
        self.toolbar.addAction(self.multiZoomToAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction)

        self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog,
                                               self.iface.mainWindow())
        self.multiZoomDialog.hide()
        self.multiZoomDialog.setFloating(True)

        menu = QMenu()
        # Add Interface for copying the canvas extent
        icon = QIcon(os.path.dirname(__file__) + "/images/copycanvas.svg")
        self.copyCanvasAction = menu.addAction(icon, 'Copy Canvas Extent',
                                               self.copyCanvas)
        self.copyCanvasAction.setObjectName('latLonToolsCopyCanvasExtent')
        # Add Interface for copying an interactive extent
        icon = QIcon(os.path.dirname(__file__) + "/images/copyextent.svg")
        self.copyExtentAction = menu.addAction(icon,
                                               'Copy Selected Area Extent',
                                               self.copyExtent)
        self.copyExtentAction.setCheckable(True)
        self.copyExtentAction.setObjectName(
            'latLonToolsCopySelectedAreaExtent')
        # Add Interface for copying a layer extent
        icon = QIcon(os.path.dirname(__file__) + "/images/copylayerextent.svg")
        self.copyLayerExtentAction = menu.addAction(icon, 'Copy Layer Extent',
                                                    self.copyLayerExtent)
        self.copyLayerExtentAction.setObjectName('latLonToolsCopyLayerExtent')
        # Add Interface for copying the extent of selected features
        icon = QIcon(
            os.path.dirname(__file__) + "/images/copyselectedlayerextent.svg")
        self.copySelectedFeaturesExtentAction = menu.addAction(
            icon, 'Copy Selected Features Extent',
            self.copySelectedFeaturesExtent)
        self.copySelectedFeaturesExtentAction.setObjectName(
            'latLonToolsCopySelectedFeaturesExtent')

        # Add the copy extent tools to the menu
        icon = QIcon(os.path.dirname(__file__) + '/images/copylayerextent.svg')
        self.copyExtentsAction = QAction(icon, 'Copy Extents to Clipboard',
                                         self.iface.mainWindow())
        self.copyExtentsAction.setMenu(menu)
        self.iface.addPluginToMenu('Lat Lon Tools', self.copyExtentsAction)

        # Add the copy extent tools to the toolbar
        self.copyExtentButton = QToolButton()
        self.copyExtentButton.setMenu(menu)
        self.copyExtentButton.setDefaultAction(self.copyCanvasAction)
        self.copyExtentButton.setPopupMode(QToolButton.MenuButtonPopup)
        self.copyExtentButton.triggered.connect(self.copyExtentTriggered)
        self.copyExtentToolbar = self.toolbar.addWidget(self.copyExtentButton)

        # Create the coordinate converter menu
        icon = QIcon(':/images/themes/default/mIconProjectionEnabled.svg')
        self.convertCoordinatesAction = QAction(icon, "Coordinate Conversion",
                                                self.iface.mainWindow())
        self.convertCoordinatesAction.setObjectName(
            'latLonToolsCoordinateConversion')
        self.convertCoordinatesAction.triggered.connect(
            self.convertCoordinatesTool)
        self.toolbar.addAction(self.convertCoordinatesAction)
        self.iface.addPluginToMenu("Lat Lon Tools",
                                   self.convertCoordinatesAction)

        # Create the conversions menu
        menu = QMenu()
        icon = QIcon(os.path.dirname(__file__) + '/images/field2geom.svg')
        action = menu.addAction(icon, "Fields to point layer", self.field2geom)
        action.setObjectName('latLonToolsField2Geom')
        icon = QIcon(os.path.dirname(__file__) + '/images/geom2field.svg')
        action = menu.addAction(icon, "Point layer to fields", self.geom2Field)
        action.setObjectName('latLonToolsGeom2Field')
        icon = QIcon(os.path.dirname(__file__) + '/images/pluscodes.svg')
        action = menu.addAction(icon, "Plus Codes to point layer",
                                self.PlusCodestoLayer)
        action.setObjectName('latLonToolsPlusCodes2Geom')
        action = menu.addAction(icon, "Point layer to Plus Codes",
                                self.toPlusCodes)
        action.setObjectName('latLonToolsGeom2PlusCodes')
        icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.svg')
        action = menu.addAction(icon, "MGRS to point layer", self.MGRStoLayer)
        action.setObjectName('latLonToolsMGRS2Geom')
        icon = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.svg')
        action = menu.addAction(icon, "Point layer to MGRS", self.toMGRS)
        action.setObjectName('latLonToolsGeom2MGRS')
        self.conversionsAction = QAction(icon, "Conversions",
                                         self.iface.mainWindow())
        self.conversionsAction.setMenu(menu)
        self.iface.addPluginToMenu('Lat Lon Tools', self.conversionsAction)

        # Add to Digitize Toolbar
        icon = QIcon(os.path.dirname(__file__) + '/images/latLonDigitize.svg')
        self.digitizeAction = QAction(icon, "Lat Lon Digitize",
                                      self.iface.mainWindow())
        self.digitizeAction.setObjectName('latLonToolsDigitize')
        self.digitizeAction.triggered.connect(self.digitizeClicked)
        self.digitizeAction.setEnabled(False)
        self.toolbar.addAction(self.digitizeAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.digitizeAction)

        # Initialize the Settings Dialog Box
        settingsicon = QIcon(':/images/themes/default/mActionOptions.svg')
        self.settingsAction = QAction(settingsicon, "Settings",
                                      self.iface.mainWindow())
        self.settingsAction.setObjectName('latLonToolsSettings')
        self.settingsAction.setToolTip('Lat Lon Tools Settings')
        self.settingsAction.triggered.connect(self.settings)
        self.toolbar.addAction(self.settingsAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction)

        # Help
        icon = QIcon(os.path.dirname(__file__) + '/images/help.svg')
        self.helpAction = QAction(icon, "Help", self.iface.mainWindow())
        self.helpAction.setObjectName('latLonToolsHelp')
        self.helpAction.triggered.connect(self.help)
        self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction)

        self.iface.currentLayerChanged.connect(self.currentLayerChanged)
        self.canvas.mapToolSet.connect(self.resetTools)
        self.enableDigitizeTool()

        # Add the processing provider
        QgsApplication.processingRegistry().addProvider(self.provider)
        InitLatLonFunctions()

    def resetTools(self, newtool, oldtool):
        '''Uncheck the Copy Lat Lon tool'''
        try:
            if self.mapTool and (oldtool is self.mapTool):
                self.copyAction.setChecked(False)
            if self.showMapTool and (oldtool is self.showMapTool):
                self.externMapAction.setChecked(False)
            if newtool is self.mapTool:
                self.copyAction.setChecked(True)
            if newtool is self.showMapTool:
                self.externMapAction.setChecked(True)
        except Exception:
            pass

    def unload(self):
        '''Unload LatLonTools from the QGIS interface'''
        self.zoomToDialog.removeMarker()
        self.multiZoomDialog.removeMarkers()
        if self.mapTool:
            self.canvas.unsetMapTool(self.mapTool)
        if self.showMapTool:
            self.canvas.unsetMapTool(self.showMapTool)
        self.iface.removePluginMenu('Lat Lon Tools', self.copyAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.copyExtentsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools',
                                    self.convertCoordinatesAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.conversionsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.helpAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.digitizeAction)
        self.iface.removeDockWidget(self.zoomToDialog)
        self.iface.removeDockWidget(self.multiZoomDialog)
        # Remove Toolbar Icons
        self.iface.removeToolBarIcon(self.copyAction)
        self.iface.removeToolBarIcon(self.copyExtentToolbar)
        self.iface.removeToolBarIcon(self.zoomToAction)
        self.iface.removeToolBarIcon(self.externMapAction)
        self.iface.removeToolBarIcon(self.multiZoomToAction)
        self.iface.removeToolBarIcon(self.convertCoordinatesAction)
        self.iface.removeToolBarIcon(self.digitizeAction)
        del self.toolbar

        if self.convertCoordinateDialog:
            self.iface.removeDockWidget(self.convertCoordinateDialog)
            self.convertCoordinateDialog = None

        self.zoomToDialog = None
        self.multiZoomDialog = None
        self.settingsDialog = None
        self.showMapTool = None
        self.mapTool = None
        self.digitizerDialog = None
        QgsApplication.processingRegistry().removeProvider(self.provider)
        UnloadLatLonFunctions()

    def startCapture(self):
        '''Set the focus of the copy coordinate tool'''
        if self.mapTool is None:
            from .copyLatLonTool import CopyLatLonTool
            self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface)
        self.canvas.setMapTool(self.mapTool)

    def copyExtentTriggered(self, action):
        self.copyExtentButton.setDefaultAction(action)

    def copyExtent(self):
        if self.copyExtentTool is None:
            from .captureExtent import CaptureExtentTool
            self.copyExtentTool = CaptureExtentTool(self.iface, self)
            self.copyExtentTool.setAction(self.copyExtentAction)
        self.canvas.setMapTool(self.copyExtentTool)

    def copyLayerExtent(self):
        layer = self.iface.activeLayer()
        if not layer or not layer.isValid():
            return
        if isinstance(layer, QgsVectorLayer) and (layer.featureCount() == 0):
            self.iface.messageBar().pushMessage(
                "",
                "This layer has no features - A bounding box cannot be calculated.",
                level=Qgis.Warning,
                duration=4)
            return
        src_crs = layer.crs()
        extent = layer.extent()
        if settings.bBoxCrs == 0:
            dst_crs = epsg4326
        else:
            dst_crs = self.canvas.mapSettings().destinationCrs()

        outStr = getExtentString(extent, src_crs, dst_crs)
        clipboard = QApplication.clipboard()
        clipboard.setText(outStr)
        self.iface.messageBar().pushMessage(
            "",
            "'{}' copied to the clipboard".format(outStr),
            level=Qgis.Info,
            duration=4)

    def copySelectedFeaturesExtent(self):
        layer = self.iface.activeLayer()
        if not layer or not layer.isValid():
            return
        if isinstance(layer, QgsVectorLayer) and (layer.featureCount() == 0):
            self.iface.messageBar().pushMessage(
                "",
                "This layer has no features - A bounding box cannot be calculated.",
                level=Qgis.Warning,
                duration=4)
            return
        if isinstance(layer, QgsVectorLayer):
            extent = layer.boundingBoxOfSelected()
            if extent.isNull():
                self.iface.messageBar().pushMessage(
                    "",
                    "No features were selected.",
                    level=Qgis.Warning,
                    duration=4)
                return
        else:
            extent = layer.extent()
        src_crs = layer.crs()
        if settings.bBoxCrs == 0:
            dst_crs = epsg4326
        else:
            dst_crs = self.canvas.mapSettings().destinationCrs()

        outStr = getExtentString(extent, src_crs, dst_crs)
        clipboard = QApplication.clipboard()
        clipboard.setText(outStr)
        self.iface.messageBar().pushMessage(
            "",
            "'{}' copied to the clipboard".format(outStr),
            level=Qgis.Info,
            duration=4)

    def copyCanvas(self):
        extent = self.iface.mapCanvas().extent()
        canvas_crs = self.canvas.mapSettings().destinationCrs()
        if settings.bBoxCrs == 0:
            dst_crs = epsg4326
        else:
            dst_crs = canvas_crs

        outStr = getExtentString(extent, canvas_crs, dst_crs)
        clipboard = QApplication.clipboard()
        clipboard.setText(outStr)
        self.iface.messageBar().pushMessage(
            "",
            "'{}' copied to the clipboard".format(outStr),
            level=Qgis.Info,
            duration=4)

    def setShowMapTool(self):
        '''Set the focus of the external map tool.'''
        if self.showMapTool is None:
            from .showOnMapTool import ShowOnMapTool
            self.showMapTool = ShowOnMapTool(self.iface)
        self.canvas.setMapTool(self.showMapTool)

    def showZoomToDialog(self):
        '''Show the zoom to docked widget.'''
        self.zoomToDialog.show()

    def convertCoordinatesTool(self):
        '''Display the Convert Coordinate Tool Dialog box.'''
        if self.convertCoordinateDialog is None:
            from .coordinateConverter import CoordinateConverterWidget
            self.convertCoordinateDialog = CoordinateConverterWidget(
                self, self.settingsDialog, self.iface, self.iface.mainWindow())
            self.convertCoordinateDialog.setFloating(True)
            self.iface.addDockWidget(Qt.RightDockWidgetArea,
                                     self.convertCoordinateDialog)
        self.convertCoordinateDialog.show()

    def multiZoomTo(self):
        '''Display the Multi-zoom to dialog box'''
        self.multiZoomDialog.show()

    def field2geom(self):
        '''Convert layer containing a point x & y coordinate to a new point layer'''
        processing.execAlgorithmDialog('latlontools:field2geom', {})

    def geom2Field(self):
        '''Convert layer geometry to a text string'''
        processing.execAlgorithmDialog('latlontools:geom2field', {})

    def toMGRS(self):
        '''Display the to MGRS  dialog box'''
        processing.execAlgorithmDialog('latlontools:point2mgrs', {})

    def MGRStoLayer(self):
        '''Display the to MGRS  dialog box'''
        processing.execAlgorithmDialog('latlontools:mgrs2point', {})

    def toPlusCodes(self):
        processing.execAlgorithmDialog('latlontools:point2pluscodes', {})

    def PlusCodestoLayer(self):
        processing.execAlgorithmDialog('latlontools:pluscodes2point', {})

    def settings(self):
        '''Show the settings dialog box'''
        self.settingsDialog.show()

    def help(self):
        '''Display a help page'''
        import webbrowser
        url = QUrl.fromLocalFile(os.path.dirname(__file__) +
                                 "/index.html").toString()
        webbrowser.open(url, new=2)

    def settingsChanged(self):
        # Settings may have changed so we need to make sure the zoomToDialog window is configured properly
        self.zoomToDialog.configure()
        self.multiZoomDialog.settingsChanged()

    def zoomTo(self, src_crs, lat, lon):
        canvas_crs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(src_crs, canvas_crs,
                                           QgsProject.instance())
        x, y = transform.transform(float(lon), float(lat))

        rect = QgsRectangle(x, y, x, y)
        self.canvas.setExtent(rect)

        pt = QgsPointXY(x, y)
        self.highlight(pt)
        self.canvas.refresh()
        return pt

    def highlight(self, point):
        currExt = self.canvas.extent()

        leftPt = QgsPoint(currExt.xMinimum(), point.y())
        rightPt = QgsPoint(currExt.xMaximum(), point.y())

        topPt = QgsPoint(point.x(), currExt.yMaximum())
        bottomPt = QgsPoint(point.x(), currExt.yMinimum())

        horizLine = QgsGeometry.fromPolyline([leftPt, rightPt])
        vertLine = QgsGeometry.fromPolyline([topPt, bottomPt])

        self.crossRb.reset(QgsWkbTypes.LineGeometry)
        self.crossRb.addGeometry(horizLine, None)
        self.crossRb.addGeometry(vertLine, None)

        QTimer.singleShot(700, self.resetRubberbands)

    def resetRubberbands(self):
        self.crossRb.reset()

    def digitizeClicked(self):
        if self.digitizerDialog is None:
            from .digitizer import DigitizerWidget
            self.digitizerDialog = DigitizerWidget(self, self.iface,
                                                   self.iface.mainWindow())
        self.digitizerDialog.show()

    def currentLayerChanged(self):
        layer = self.iface.activeLayer()
        if layer is not None:
            try:
                layer.editingStarted.disconnect(self.layerEditingChanged)
            except Exception:
                pass
            try:
                layer.editingStopped.disconnect(self.layerEditingChanged)
            except Exception:
                pass

            if isinstance(layer, QgsVectorLayer):
                layer.editingStarted.connect(self.layerEditingChanged)
                layer.editingStopped.connect(self.layerEditingChanged)

        self.enableDigitizeTool()

    def layerEditingChanged(self):
        self.enableDigitizeTool()

    def enableDigitizeTool(self):
        self.digitizeAction.setEnabled(False)
        layer = self.iface.activeLayer()

        if layer is not None and isinstance(layer, QgsVectorLayer) and (
                layer.geometryType()
                == QgsWkbTypes.PointGeometry) and layer.isEditable():
            self.digitizeAction.setEnabled(True)
        else:
            if self.digitizerDialog is not None:
                self.digitizerDialog.close()
Example #35
0
class FinderBox(QComboBox):

    running = False
    to_finish = 0

    search_started = pyqtSignal()
    search_finished = pyqtSignal()

    def __init__(self, finders, iface, parent=None):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        self.marker = None
        self.rubber = QgsRubberBand(self.mapCanvas)
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

        QComboBox.__init__(self, parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.InsertAtTop)
        self.setMinimumHeight(27)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.insertSeparator(0)
        self.lineEdit().returnPressed.connect(self.search)

        self.result_view = QTreeView()
        self.result_view.setHeaderHidden(True)
        self.result_view.setMinimumHeight(300)
        self.result_view.activated.connect(self.itemActivated)
        self.result_view.pressed.connect(self.itemPressed)
        self.setView(self.result_view)

        self.result_model = ResultModel(self)
        self.setModel(self.result_model)

        self.finders = finders
        for finder in list(self.finders.values()):
            finder.result_found.connect(self.result_found)
            finder.limit_reached.connect(self.limit_reached)
            finder.finished.connect(self.finished)

        self.clearButton = QPushButton(self)
        self.clearButton.setIcon(
            QIcon(":/plugins/quickfinder/icons/draft.svg"))
        self.clearButton.setText('')
        self.clearButton.setFlat(True)
        self.clearButton.setCursor(QCursor(Qt.ArrowCursor))
        self.clearButton.setStyleSheet('border: 0px; padding: 0px;')
        self.clearButton.clicked.connect(self.clear)

        layout = QHBoxLayout(self)
        self.setLayout(layout)
        layout.addStretch()
        layout.addWidget(self.clearButton)
        layout.addSpacing(20)

        button_size = self.clearButton.sizeHint()
        # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
        padding = button_size.width()  # + frameWidth + 1
        self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' %
                                      padding)

    def __del__(self):
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    #clear marker that the lonlat seted
    def clearMarker(self):
        self.mapCanvas.scene().removeItem(self.marker)
        self.marker = None

    def clearSelection(self):
        self.result_model.setSelected(None, self.result_view.palette())
        self.rubber.reset()
        #self.clearMarker()

    def clear(self):
        self.clearSelection()
        self.result_model.clearResults()
        self.lineEdit().setText('')

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.clearSelection()
            self.clearMarker()
        QComboBox.keyPressEvent(self, event)

    def lnglatFinder(self, to_find):
        import re
        m = re.match(r'(?P<lon>-?\d*(.\d+))\s+(?P<lat>-?\d*(.\d+))', to_find)
        if not m:
            return False
        x = float(m.group('lon'))
        y = float(m.group('lat'))
        return self.zoomLnglat(x, y)

    def zoomLnglat(self, lng, lat):
        x, y = lng, lat

        canvas = self.mapCanvas
        currExt = canvas.extent()
        canvasCenter = currExt.center()
        dx = float(x) - canvasCenter.x()
        dy = float(y) - canvasCenter.y()

        xMin = currExt.xMinimum() + dx
        xMax = currExt.xMaximum() + dx
        yMin = currExt.yMinimum() + dy
        yMax = currExt.yMaximum() + dy

        rect = QgsRectangle(xMin, yMin, xMax, yMax)
        canvas.setExtent(rect)
        pt = QgsPointXY(float(x), float(y))
        self.marker = QgsVertexMarker(canvas)
        self.marker.setCenter(pt)
        self.marker.setIconSize(18)
        self.marker.setPenWidth(2)
        self.marker.setIconType(QgsVertexMarker.ICON_CROSS)
        canvas.refresh()
        return True

#    def geocodeFinder(self, to_finder):
#        print(to_finder[:2])
#        if not to_finder[:2] == 'b:':
#            return False
#
#        address = to_finder[2:]
#        url = MySettings().value("baiduUrl")
#        url = url + parse.quote(address)
#
#        response = request.urlopen(url)
#        content = response.read()
#        data = json.loads(content)
#        print(data)
#        lng, lat = (data['result']['location']['lng'], data['result']['location']['lat'])
#        from .cood_trans import bd09_to_wgs84
#        lng, lat = bd09_to_wgs84(lng, lat)
#        print(f'{lng}-{lat}')
#        return self.zoomLnglat(lng, lat)

    def search(self):
        #  self.geocode()
        if self.running:
            return

        to_find = self.lineEdit().text()
        if not to_find or to_find == '':
            return
#        if not (self.lnglatFinder(to_find) or self.geocodeFinder(to_find)):
        if not self.lnglatFinder(to_find):
            self.showPopup()

        self.running = True
        self.search_started.emit()

        self.clearSelection()
        self.result_model.clearResults()
        self.result_model.truncateHistory(MySettings().value("historyLength"))
        self.result_model.setLoading(True)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        self.finders_to_start = []
        for finder in list(self.finders.values()):
            if finder.activated():
                self.finders_to_start.append(finder)

        bbox = self.mapCanvas.fullExtent()

        while len(self.finders_to_start) > 0:
            finder = self.finders_to_start[0]
            self.finders_to_start.remove(finder)
            self.result_model.addResult(finder.name)
            finder.start(to_find, bbox=bbox)

        # For case there is no finder activated
        self.finished(None)

    def stop(self):
        self.finders_to_start = []
        for finder in list(self.finders.values()):
            if finder.is_running():
                finder.stop()
        self.finished(None)

    def result_found(self, finder, layername, value, geometry, srid):
        self.result_model.addResult(finder.name, layername, value, geometry,
                                    srid)
        self.result_view.expandAll()

    def limit_reached(self, finder, layername):
        self.result_model.addEllipsys(finder.name, layername)

    def finished(self, finder):
        if len(self.finders_to_start) > 0:
            return
        for finder in list(self.finders.values()):
            if finder.is_running():
                return

        self.running = False
        self.search_finished.emit()

        self.result_model.setLoading(False)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def itemActivated(self, index):
        item = self.result_model.itemFromIndex(index)
        self.showItem(item)

    def itemPressed(self, index):
        item = self.result_model.itemFromIndex(index)
        if QApplication.mouseButtons() == Qt.LeftButton:
            self.showItem(item)

    def showItem(self, item):
        if isinstance(item, ResultItem):
            self.result_model.setSelected(item, self.result_view.palette())
            geometry = self.transform_geom(item)
            self.rubber.reset(geometry.type())
            self.rubber.setToGeometry(geometry, None)
            self.zoom_to_rubberband()
            return

        if isinstance(item, GroupItem):
            child = item.child(0)
            if isinstance(child, ResultItem):
                self.result_model.setSelected(item, self.result_view.palette())
                self.rubber.reset(child.geometry.type())
                for i in range(0, item.rowCount()):
                    geometry = self.transform_geom(item.child(i))
                    self.rubber.addGeometry(geometry, None)
                self.zoom_to_rubberband()
            return

        if item.__class__.__name__ == 'QStandardItem':
            self.clearSelection()

    def transform_geom(self, item):
        src_crs = QgsCoordinateReferenceSystem()
        src_crs.createFromSrid(item.srid)
        dest_crs = self.mapCanvas.mapSettings().destinationCrs()
        geom = QgsGeometry(item.geometry)
        geom.transform(
            QgsCoordinateTransform(src_crs, dest_crs, QgsProject.instance()))
        return geom

    def zoom_to_rubberband(self):
        geom = self.rubber.asGeometry()
        if geom:
            rect = geom.boundingBox()
            rect.scale(1.5)
            self.mapCanvas.setExtent(rect)
            self.mapCanvas.refresh()
Example #36
0
class FinderBox(QComboBox):

    running = False
    toFinish = 0

    searchStarted = pyqtSignal()
    searchFinished = pyqtSignal()

    def __init__(self, finders, iface, parent=None):
        self.iface = iface
        self.mapCanvas = iface.mapCanvas()
        self.rubber = QgsRubberBand(self.mapCanvas)
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

        QComboBox.__init__(self, parent)
        self.setEditable(True)
        self.setInsertPolicy(QComboBox.InsertAtTop)
        self.setMinimumHeight(27)
        self.setSizePolicy(QSizePolicy.Expanding,
                           QSizePolicy.Fixed)

        self.insertSeparator(0)
        self.lineEdit().returnPressed.connect(self.search)

        self.resultView = QTreeView()
        self.resultView.setHeaderHidden(True)
        self.resultView.setMinimumHeight(300)
        self.resultView.activated.connect(self.itemActivated)
        self.resultView.pressed.connect(self.itemPressed)
        self.setView(self.resultView)

        self.resultModel = ResultModel(self)
        self.setModel(self.resultModel)

        self.finders = finders
        for finder in self.finders.values():
            finder.resultFound.connect(self.resultFound)
            finder.limitReached.connect(self.limitReached)
            finder.finished.connect(self.finished)

        self.clearButton = QPushButton(self)
        self.clearButton.setIcon(QIcon(":/plugins/quickfinder/icons/draft.svg"))
        self.clearButton.setText('')
        self.clearButton.setFlat(True)
        self.clearButton.setCursor(QCursor(Qt.ArrowCursor))
        self.clearButton.setStyleSheet('border: 0px; padding: 0px;')
        self.clearButton.clicked.connect(self.clear)

        layout = QHBoxLayout(self)
        self.setLayout(layout)
        layout.addStretch()
        layout.addWidget(self.clearButton)
        layout.addSpacing(20)

        buttonSize = self.clearButton.sizeHint()
        # frameWidth = self.lineEdit().style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
        padding = buttonSize.width()  # + frameWidth + 1
        self.lineEdit().setStyleSheet('QLineEdit {padding-right: %dpx; }' % padding)

    def __del__(self):
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def clearSelection(self):
        self.resultModel.setSelected(None, self.resultView.palette())
        self.rubber.reset()

    def clear(self):
        self.clearSelection()
        self.resultModel.clearResults()
        self.lineEdit().setText('')

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.clearSelection()
        QComboBox.keyPressEvent(self, event)

    def search(self):
        if self.running:
            return

        toFind = self.lineEdit().text()
        if not toFind or toFind == '':
            return

        self.running = True
        self.searchStarted.emit()

        self.clearSelection()
        self.resultModel.clearResults()
        self.resultModel.truncateHistory(MySettings().value("historyLength"))
        self.resultModel.setLoading(True)
        self.showPopup()

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        self.findersToStart = []
        for finder in self.finders.values():
            if finder.activated():
                self.findersToStart.append(finder)

        bbox = self.mapCanvas.fullExtent()

        while len(self.findersToStart) > 0:
            finder = self.findersToStart[0]
            self.findersToStart.remove(finder)
            self.resultModel.addResult(finder.name)
            finder.start(toFind, bbox=bbox)

        # For case there is no finder activated
        self.finished(None)

    def stop(self):
        self.findersToStart = []
        for finder in self.finders.values():
            if finder.isRunning():
                finder.stop()
        self.finished(None)

    def resultFound(self, finder, layername, value, geometry, srid):
        self.resultModel.addResult(finder.name, layername, value, geometry, srid)
        self.resultView.expandAll()

    def limitReached(self, finder, layername):
        self.resultModel.addEllipsys(finder.name, layername)

    def finished(self, finder):
        if len(self.findersToStart) > 0:
            return
        for finder in self.finders.values():
            if finder.isRunning():
                return

        self.running = False
        self.searchFinished.emit()

        self.resultModel.setLoading(False)

        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def itemActivated(self, index):
        item = self.resultModel.itemFromIndex(index)
        self.showItem(item)

    def itemPressed(self, index):
        item = self.resultModel.itemFromIndex(index)
        if QApplication.mouseButtons() == Qt.LeftButton:
            self.showItem(item)

    def showItem(self, item):
        if isinstance(item, ResultItem):
            self.resultModel.setSelected(item, self.resultView.palette())
            geometry = self.transformGeom(item)
            self.rubber.reset(geometry.type())
            self.rubber.setToGeometry(geometry, None)
            self.zoomToRubberBand()
            return

        if isinstance(item, GroupItem):
            child = item.child(0)
            if isinstance(child, ResultItem):
                self.resultModel.setSelected(item, self.resultView.palette())
                self.rubber.reset(child.geometry.type())
                for i in xrange(0, item.rowCount()):
                    geometry = self.transformGeom(item.child(i))
                    self.rubber.addGeometry(geometry, None)
                self.zoomToRubberBand()
            return

        if item.__class__.__name__ == 'QStandardItem':
            self.clearSelection()

    def transformGeom(self, item):
        src_crs = QgsCoordinateReferenceSystem()
        src_crs.createFromSrid(item.srid)
        dest_crs = self.mapCanvas.mapRenderer().destinationCrs()
        geom = QgsGeometry(item.geometry)
        geom.transform(QgsCoordinateTransform(src_crs, dest_crs))
        return geom

    def zoomToRubberBand(self):
        geom = self.rubber.asGeometry()
        if geom:
            rect = geom.boundingBox()
            rect.scale(1.5)
            self.mapCanvas.setExtent(rect)
            self.mapCanvas.refresh()
Example #37
0
class CopyTimeZoneTool(QgsMapToolEmitPoint):
    '''Class to interact with the map canvas to capture the coordinate
    when the mouse button is pressed and to display the coordinate in
    in the status bar.'''
    tzf = None
    last_tz = None

    def __init__(self, settings, iface):
        QgsMapToolEmitPoint.__init__(self, iface.mapCanvas())
        self.iface = iface
        self.settings = settings
        self.canvas = iface.mapCanvas()

        # Set up a polygon rubber band
        self.rubber = QgsRubberBand(self.canvas)
        self.rubber.setColor(QColor(255, 70, 0, 200))
        self.rubber.setWidth(3)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def activate(self):
        '''When activated set the cursor to a crosshair.'''
        self.canvas.setCursor(Qt.CrossCursor)
        self.tzf = tzf_instance.getTZF()
        self.settings.show()

    def deactivate(self):
        self.last_tz = None
        self.rubber.reset()

    def formatMessage(self, pt):
        '''Format the coordinate string according to the settings from
        the settings dialog.'''
        # Make sure the coordinate is transformed to EPSG:4326
        canvasCRS = self.canvas.mapSettings().destinationCrs()
        if canvasCRS == epsg4326:
            pt4326 = pt
        else:
            transform = QgsCoordinateTransform(canvasCRS, epsg4326,
                                               QgsProject.instance())
            pt4326 = transform.transform(pt.x(), pt.y())
        try:
            tz_name = self.tzf.timezone_at(lng=pt4326.x(), lat=pt4326.y())
            mode = self.settings.mode()
            if mode == 1:
                msg = tz_name
            else:
                tz = timezone(tz_name)
                date = self.settings.date()
                loc_dt = tz.localize(
                    datetime(date.year(), date.month(), date.day()))
                offset = loc_dt.strftime('%z')
                if mode == 0:
                    msg = '{} ({})'.format(tz_name, offset)
                else:
                    msg = offset
        except Exception:
            # traceback.print_exc()
            msg = ''
            tz_name = ''
        if not msg:
            msg = ''
            tz_name = ''

        return (tz_name, msg)

    def canvasMoveEvent(self, event):
        '''Capture the coordinate as the user moves the mouse over
        the canvas. Show it in the status bar.'''
        pt = event.mapPoint()
        tz_name, msg = self.formatMessage(pt)
        if tz_name != self.last_tz:
            self.rubber.reset()
            self.last_tz = tz_name
            if tz_name != '':
                polygon = self.tzf.get_geometry(tz_name=tz_name)
                qgs_poly = tzf_to_qgis_polygon(polygon)
                canvasCRS = self.canvas.mapSettings().destinationCrs()
                if epsg4326 != canvasCRS:
                    to_canvas_crs = QgsCoordinateTransform(
                        epsg4326, canvasCRS, QgsProject.instance())
                    qgs_poly.transform(to_canvas_crs)
                self.rubber.addGeometry(qgs_poly, None)
                self.rubber.show()
        self.iface.statusBarIface().showMessage(msg, 4000)

    def canvasReleaseEvent(self, event):
        '''Capture the coordinate when the mouse button has been released,
        format it, and copy it to the clipboard. pt is QgsPointXY'''
        pt = event.mapPoint()

        tz_name, msg = self.formatMessage(pt)
        if msg:
            clipboard = QApplication.clipboard()
            clipboard.setText(msg)
            self.iface.messageBar().pushMessage(
                "",
                "'{}' copied to the clipboard".format(msg),
                level=Qgis.Info,
                duration=2)
Example #38
0
class GeodesicMeasureDialog(QDialog, FORM_CLASS):
    def __init__(self, iface, parent):
        super(GeodesicMeasureDialog, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface
        self.canvas = iface.mapCanvas()
        qset = QSettings()

        self.restoreGeometry(
            qset.value("ShapeTools/MeasureDialogGeometry",
                       QByteArray(),
                       type=QByteArray))
        self.closeButton.clicked.connect(self.closeDialog)
        self.newButton.clicked.connect(self.newDialog)
        self.saveToLayerButton.clicked.connect(self.saveToLayer)
        self.saveToLayerButton.setEnabled(False)

        self.unitsComboBox.addItems(DISTANCE_LABELS)

        self.tableWidget.setColumnCount(3)
        self.tableWidget.setSortingEnabled(False)
        self.tableWidget.setHorizontalHeaderLabels(
            [tr('Heading To'),
             tr('Heading From'),
             tr('Distance')])

        self.unitsComboBox.activated.connect(self.unitsChanged)

        self.capturedPoints = []
        self.distances = []
        self.activeMeasuring = True
        self.lastMotionPt = None
        self.unitsChanged()
        self.currentDistance = 0.0

        self.pointRb = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.pointRb.setColor(settings.rubberBandColor)
        self.pointRb.setIconSize(10)
        self.lineRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.lineRb.setColor(settings.rubberBandColor)
        self.lineRb.setWidth(3)
        self.tempRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.tempRb.setColor(settings.rubberBandColor)
        self.tempRb.setWidth(3)

    def ready(self):
        return self.activeMeasuring

    def stop(self):
        self.activeMeasuring = False
        self.lastMotionPt = None

    def closeEvent(self, event):
        self.closeDialog()

    def closeDialog(self):
        self.clear()
        QSettings().setValue("ShapeTools/MeasureDialogGeometry",
                             self.saveGeometry())
        self.close()

    def newDialog(self):
        self.clear()
        self.initGeodLabel()

    def initGeodLabel(self):
        label = tr('Ellipsoid: ') + settings.ellipseDescription
        self.geodLabel.setText(label)

    def keyPressed(self, key):
        index = len(self.capturedPoints)
        if index <= 0:
            return
        if self.motionReady():
            if self.lastMotionPt == None:
                return
            (distance, startAngle,
             endAngle) = self.calcParameters(self.capturedPoints[index - 1],
                                             self.lastMotionPt)
        else:
            if index < 2:
                return
            (distance, startAngle,
             endAngle) = self.calcParameters(self.capturedPoints[index - 2],
                                             self.capturedPoints[index - 1])

        distance = self.unitDistance(distance)
        clipboard = QApplication.clipboard()
        if key == Qt.Key_1 or key == Qt.Key_F:
            s = '{:.{prec}f}'.format(startAngle,
                                     prec=settings.measureSignificantDigits)
            clipboard.setText(s)
            self.iface.messageBar().pushMessage(
                "",
                "Heading to {} copied to the clipboard".format(s),
                level=Qgis.Info,
                duration=3)
        elif key == Qt.Key_2 or key == Qt.Key_T:
            s = '{:.{prec}f}'.format(endAngle,
                                     prec=settings.measureSignificantDigits)
            clipboard.setText(s)
            self.iface.messageBar().pushMessage(
                "",
                "Heading from {} copied to the clipboard".format(s),
                level=Qgis.Info,
                duration=3)
        elif key == Qt.Key_3 or key == Qt.Key_D:
            s = '{:.{prec}f}'.format(distance,
                                     prec=settings.measureSignificantDigits)
            clipboard.setText(s)
            self.iface.messageBar().pushMessage(
                "",
                "Distance {} copied to the clipboard".format(s),
                level=Qgis.Info,
                duration=3)
        elif key == Qt.Key_4 or key == Qt.Key_A:
            total = 0.0
            num = len(self.capturedPoints)
            for i in range(1, num):
                (d, startA,
                 endA) = self.calcParameters(self.capturedPoints[i - 1],
                                             self.capturedPoints[i])
                total += d
            total = self.unitDistance(total)
            # Add in the motion distance
            if self.motionReady():
                total += distance
            s = '{:.{prec}f}'.format(total,
                                     prec=settings.measureSignificantDigits)
            clipboard.setText(s)
            self.iface.messageBar().pushMessage(
                "",
                "Total distance {} copied to the clipboard".format(s),
                level=Qgis.Info,
                duration=3)
        else:
            return

    def unitsChanged(self):
        label = "Distance [{}]".format(
            DISTANCE_LABELS[self.unitsComboBox.currentIndex()])
        item = QTableWidgetItem(label)
        self.tableWidget.setHorizontalHeaderItem(2, item)
        ptcnt = len(self.capturedPoints)
        if ptcnt >= 2:
            i = 0
            while i < ptcnt - 1:
                item = QTableWidgetItem('{:.4f}'.format(
                    self.unitDistance(self.distances[i])))
                self.tableWidget.setItem(i, 2, item)
                i += 1
            self.formatTotal()

    def motionReady(self):
        if len(self.capturedPoints) > 0 and self.activeMeasuring:
            return True
        return False

    def addPoint(self, pt, button):
        self.currentDistance = 0
        index = len(self.capturedPoints)
        if index > 0 and pt == self.capturedPoints[index - 1]:
            # the clicked point is the same as the previous so just ignore it
            return
        self.capturedPoints.append(pt)
        # Add rubber band points
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(epsg4326, canvasCrs,
                                           QgsProject.instance())
        ptCanvas = transform.transform(pt.x(), pt.y())
        self.pointRb.addPoint(ptCanvas, True)
        # If there is more than 1 point add it to the table
        if index > 0:
            self.saveToLayerButton.setEnabled(True)
            (distance, startAngle,
             endAngle) = self.calcParameters(self.capturedPoints[index - 1],
                                             self.capturedPoints[index])
            self.distances.append(distance)
            self.insertParams(index, distance, startAngle, endAngle)
            # Add Rubber Band Line
            linePts = self.getLinePts(distance, self.capturedPoints[index - 1],
                                      self.capturedPoints[index])
            self.lineRb.addGeometry(QgsGeometry.fromPolylineXY(linePts), None)
        self.formatTotal()

    def inMotion(self, pt):
        index = len(self.capturedPoints)
        if index <= 0:
            return
        (self.currentDistance, startAngle,
         endAngle) = self.calcParameters(self.capturedPoints[index - 1], pt)
        self.insertParams(index, self.currentDistance, startAngle, endAngle)
        self.formatTotal()
        linePts = self.getLinePts(self.currentDistance,
                                  self.capturedPoints[index - 1], pt)
        self.lastMotionPt = pt
        self.tempRb.setToGeometry(QgsGeometry.fromPolylineXY(linePts), None)

    def calcParameters(self, pt1, pt2):
        l = geod.Inverse(pt1.y(), pt1.x(), pt2.y(), pt2.x())
        az2 = (l['azi2'] + 180) % 360.0
        if az2 > 180:
            az2 = az2 - 360.0
        az1 = l['azi1']

        # Check to see if the azimuth values should be in the range or 0 to 360
        # The default is -180 to 180
        if settings.mtAzMode:
            if az1 < 0:
                az1 += 360.0
            if az2 < 0:
                az2 += 360
        return (l['s12'], az1, az2)

    def getLinePts(self, distance, pt1, pt2):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(epsg4326, canvasCrs,
                                           QgsProject.instance())
        pt1c = transform.transform(pt1.x(), pt1.y())
        pt2c = transform.transform(pt2.x(), pt2.y())
        if distance < 10000:
            return [pt1c, pt2c]
        l = geod.InverseLine(pt1.y(), pt1.x(), pt2.y(), pt2.x())
        n = int(math.ceil(distance / 10000.0))
        if n > 20:
            n = 20
        seglen = distance / n
        pts = [pt1c]
        for i in range(1, n):
            s = seglen * i
            g = l.Position(
                s,
                Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL)
            ptc = transform.transform(g['lon2'], g['lat2'])
            pts.append(ptc)
        pts.append(pt2c)
        return pts

    def saveToLayer(self):
        units = self.unitDesignator()
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        fields = QgsFields()
        fields.append(QgsField("label", QVariant.String))
        fields.append(QgsField("value", QVariant.Double))
        fields.append(QgsField("units", QVariant.String))
        fields.append(QgsField("heading_to", QVariant.Double))
        fields.append(QgsField("heading_from", QVariant.Double))
        fields.append(QgsField("total_dist", QVariant.Double))

        layer = QgsVectorLayer("LineString?crs={}".format(canvasCrs.authid()),
                               "Measurements", "memory")
        dp = layer.dataProvider()
        dp.addAttributes(fields)
        layer.updateFields()

        num = len(self.capturedPoints)
        total = 0.0
        for i in range(1, num):
            (distance, startA,
             endA) = self.calcParameters(self.capturedPoints[i - 1],
                                         self.capturedPoints[i])
            total += distance
        total = self.unitDistance(total)
        for i in range(1, num):
            (distance, startA,
             endA) = self.calcParameters(self.capturedPoints[i - 1],
                                         self.capturedPoints[i])
            pts = self.getLinePts(distance, self.capturedPoints[i - 1],
                                  self.capturedPoints[i])
            distance = self.unitDistance(distance)
            feat = QgsFeature(layer.fields())
            feat.setAttribute(0, "{:.2f} {}".format(distance, units))
            feat.setAttribute(1, distance)
            feat.setAttribute(2, units)
            feat.setAttribute(3, startA)
            feat.setAttribute(4, endA)
            feat.setAttribute(5, total)
            feat.setGeometry(QgsGeometry.fromPolylineXY(pts))
            dp.addFeatures([feat])

        label = QgsPalLayerSettings()
        label.fieldName = 'label'
        try:
            label.placement = QgsPalLayerSettings.Line
        except:
            label.placement = QgsPalLayerSettings.AboveLine
        format = label.format()
        format.setColor(settings.measureTextColor)
        format.setNamedStyle('Bold')
        label.setFormat(format)
        labeling = QgsVectorLayerSimpleLabeling(label)
        layer.setLabeling(labeling)
        layer.setLabelsEnabled(True)
        renderer = layer.renderer()
        renderer.symbol().setColor(settings.measureLineColor)
        renderer.symbol().setWidth(0.5)

        layer.updateExtents()
        QgsProject.instance().addMapLayer(layer)

    def insertParams(self, position, distance, startAngle, endAngle):
        if position > self.tableWidget.rowCount():
            self.tableWidget.insertRow(position - 1)
        item = QTableWidgetItem('{:.4f}'.format(self.unitDistance(distance)))
        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.tableWidget.setItem(position - 1, 2, item)
        item = QTableWidgetItem('{:.4f}'.format(startAngle))
        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.tableWidget.setItem(position - 1, 0, item)
        item = QTableWidgetItem('{:.4f}'.format(endAngle))
        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
        self.tableWidget.setItem(position - 1, 1, item)

    def formatTotal(self):
        total = self.currentDistance
        ptcnt = len(self.capturedPoints)
        if ptcnt >= 2:
            i = 0
            while i < ptcnt - 1:
                total += self.distances[i]
                i += 1
        self.distanceLineEdit.setText('{:.2f}'.format(
            self.unitDistance(total)))

    def updateRBColor(self):
        self.pointRb.setColor(settings.rubberBandColor)
        self.lineRb.setColor(settings.rubberBandColor)
        self.tempRb.setColor(settings.rubberBandColor)

    def clear(self):
        self.tableWidget.setRowCount(0)
        self.capturedPoints = []
        self.distances = []
        self.activeMeasuring = True
        self.currentDistance = 0.0
        self.distanceLineEdit.setText('')
        self.pointRb.reset(QgsWkbTypes.PointGeometry)
        self.lineRb.reset(QgsWkbTypes.LineGeometry)
        self.tempRb.reset(QgsWkbTypes.LineGeometry)
        self.saveToLayerButton.setEnabled(False)
        self.updateRBColor()

    def unitDistance(self, distance):
        units = self.unitsComboBox.currentIndex()
        if units == 0:  # kilometers
            return distance / 1000.0
        elif units == 1:  # meters
            return distance
        elif units == 2:  # centimeters
            return distance * QgsUnitTypes.fromUnitToUnitFactor(
                QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceCentimeters)
        elif units == 3:  # miles
            return distance * QgsUnitTypes.fromUnitToUnitFactor(
                QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceMiles)
        elif units == 4:  # yards
            return distance * QgsUnitTypes.fromUnitToUnitFactor(
                QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceYards)
        elif units == 5:  # feet
            return distance * QgsUnitTypes.fromUnitToUnitFactor(
                QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceFeet)
        elif units == 6:  # inches
            return distance * QgsUnitTypes.fromUnitToUnitFactor(
                QgsUnitTypes.DistanceMeters, QgsUnitTypes.DistanceFeet) * 12
        elif units == 7:  # nautical miles
            return distance * QgsUnitTypes.fromUnitToUnitFactor(
                QgsUnitTypes.DistanceMeters,
                QgsUnitTypes.DistanceNauticalMiles)

    def unitDesignator(self):
        units = self.unitsComboBox.currentIndex()
        return unitsAbbr[units]
Example #39
0
class Qgis2threejsDialog(QDialog):
  STYLE_MAX_COUNT = 3

  def __init__(self, iface):
    QDialog.__init__(self, iface.mainWindow())
    self.iface = iface
    self.apiChanged22 = False   # not QgsApplication.prefixPath().startswith("C:/OSGeo4W")  # QGis.QGIS_VERSION_INT >= 20200

    # Set up the user interface from Designer.
    self.ui = ui = Ui_Qgis2threejsDialog()
    ui.setupUi(self)

    self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint)
    ui.lineEdit_OutputFilename.setPlaceholderText("[Temporary file]")

    ui.pushButton_Run.clicked.connect(self.run)
    ui.pushButton_Close.clicked.connect(self.reject)

    # DEM tab
    ui.toolButton_switchFocusMode.setVisible(False)
    ui.toolButton_PointTool.setVisible(False)
    ui.progressBar.setVisible(False)
    self.switchFocusMode(True)

    ui.toolButton_Browse.clicked.connect(self.browseClicked)
    ui.radioButton_Simple.toggled.connect(self.samplingModeChanged)
    ui.horizontalSlider_Resolution.valueChanged.connect(self.calculateResolution)
    ui.spinBox_Height.valueChanged.connect(self.updateQuads)
    ui.toolButton_switchFocusMode.clicked.connect(self.switchFocusModeClicked)
    ui.toolButton_PointTool.clicked.connect(self.startPointSelection)

    # Vector tab
    ui.treeWidget_VectorLayers.setHeaderLabel("Vector layers")
    self.initVectorStyleWidgets()

    ui.treeWidget_VectorLayers.currentItemChanged.connect(self.currentVectorLayerChanged)
    ui.treeWidget_VectorLayers.itemChanged.connect(self.vectorLayerItemChanged)
    ui.comboBox_ObjectType.currentIndexChanged.connect(self.objectTypeSelectionChanged)

    self.bar = None
    self.localBrowsingMode = True
    self.rb_quads = self.rb_point = None
    self.currentVectorLayer = None
    self.vectorPropertiesDict = {}
    self.objectTypeManager = ObjectTypeManager()

    # set map tool
    self.previousMapTool = None
    self.mapTool = RectangleMapTool(iface.mapCanvas())
    self.connect(self.mapTool, SIGNAL("rectangleCreated()"), self.rectangleSelected)
#    self.mapTool = PointMapTool(iface.mapCanvas())
#    QObject.connect(self.mapTool, SIGNAL("pointSelected()"), self.pointSelected)
    iface.mapCanvas().mapToolSet.connect(self.mapToolSet)
    self.startPointSelection()

  def exec_(self):
    ui = self.ui
    messages = []
    # show message if crs unit is degrees
    mapSettings = self.iface.mapCanvas().mapSettings() if self.apiChanged22 else self.iface.mapCanvas().mapRenderer()
    if mapSettings.destinationCrs().mapUnits() in [QGis.Degrees]:
      self.showMessageBar("The unit of current CRS is degrees", "Terrain may not appear well.")

    # show message if there are no dem layer
    no_demlayer = ui.comboBox_DEMLayer.count() == 0
    ui.pushButton_Run.setEnabled(not no_demlayer)
    if no_demlayer:
      self.showMessageBar("No DEM layer", "Load 1-band raster layer with GDAL provider.", QgsMessageBar.WARNING)

    return QDialog.exec_(self)

  def showMessageBar(self, title, text, level=QgsMessageBar.INFO):
    if self.bar is None:
      self.bar = QgsMessageBar()
      self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

      ui = self.ui
      margins = ui.gridLayout.getContentsMargins()
      vl = ui.gridLayout.takeAt(0)
      ui.gridLayout.setContentsMargins(0,0,0,0)
      ui.gridLayout.addWidget(self.bar, 0, 0)
      ui.gridLayout.addItem(vl, 1, 0)
      ui.verticalLayout.setContentsMargins(margins[0], margins[1] / 2, margins[2], margins[3])
    self.bar.pushMessage(title, text, level=level)

  def initDEMLayerList(self, layerId=None):
    # list 1 band raster layers
    self.ui.comboBox_DEMLayer.clear()
    for id, layer in QgsMapLayerRegistry().instance().mapLayers().items():
      if layer.type() == QgsMapLayer.RasterLayer and layer.providerType() == "gdal" and layer.bandCount() == 1:
        self.ui.comboBox_DEMLayer.addItem(layer.name(), id)

    # select the last selected layer
    if layerId is not None:
      index = self.ui.comboBox_DEMLayer.findData(layerId)
      if index != -1:
        self.ui.comboBox_DEMLayer.setCurrentIndex(index)
      return index
    return -1

  def initVectorLayerTree(self, vectorPropertiesDict):
    self.vectorPropertiesDict = vectorPropertiesDict
    tree = self.ui.treeWidget_VectorLayers
    tree.clear()
    # add vector layers into tree widget
    self.treeTopItems = topItems = {QGis.Point:QTreeWidgetItem(tree, ["Point"]), QGis.Line:QTreeWidgetItem(tree, ["Line"]), QGis.Polygon:QTreeWidgetItem(tree, ["Polygon"])}
    self.vlItems = {}
    for layer in self.iface.legendInterface().layers():
      if layer.type() != QgsMapLayer.VectorLayer:
        continue
      geometry_type = layer.geometryType()
      if geometry_type in [QGis.Point, QGis.Line, QGis.Polygon]:
        self.vlItems[layer.id()] = item = QTreeWidgetItem(topItems[geometry_type], [layer.name()])
        if layer.id() in self.vectorPropertiesDict:
          isVisible = self.vectorPropertiesDict[layer.id()]["visible"]
        else:
          isVisible = False   #self.iface.legendInterface().isLayerVisible(layer)
        check_state = Qt.Checked if isVisible else Qt.Unchecked
        item.setData(0, Qt.CheckStateRole, check_state)
        item.setData(0, Qt.UserRole, layer.id())
        #item.setDisabled(True)
        #item.setData(0, Qt.CheckStateRole, Qt.Unchecked)

    for item in topItems.values():
      tree.expandItem(item)

    self.setVectorStylesEnabled(False)

  def initVectorStyleWidgets(self):
    self.colorWidget = StyleWidget(StyleWidget.COLOR)
    self.ui.verticalLayout_Styles.addWidget(self.colorWidget)
    self.heightWidget = StyleWidget(StyleWidget.HEIGHT)
    self.ui.verticalLayout_zCoordinate.addWidget(self.heightWidget)

    self.styleWidgets = []
    for i in range(self.STYLE_MAX_COUNT):
      widget = StyleWidget()
      widget.setVisible(False)
      self.ui.verticalLayout_Styles.addWidget(widget)
      self.styleWidgets.append(widget)

  def currentVectorLayerChanged(self, currentItem, previousItem):
    # save properties of previous item
    if previousItem is not None:
      layerid = previousItem.data(0, Qt.UserRole)
      if layerid is not None:
        self.saveVectorProperties(layerid)

    layerid = currentItem.data(0, Qt.UserRole)
    if layerid is None:
      self.currentVectorLayer = None
      return
    self.currentVectorLayer = layer = QgsMapLayerRegistry().instance().mapLayer(layerid)
    if layer is None:
      return

    for i in range(self.STYLE_MAX_COUNT):
      self.styleWidgets[i].hide()

    obj_types = self.objectTypeManager.objectTypeNames(layer.geometryType())
    ui = self.ui
    ui.comboBox_ObjectType.blockSignals(True)
    ui.comboBox_ObjectType.clear()
    ui.comboBox_ObjectType.addItems(obj_types)
    ui.comboBox_ObjectType.blockSignals(False)

    # set up property widgets
    self.objectTypeSelectionChanged()

    if layerid in self.vectorPropertiesDict:
      # restore properties
      self.restoreVectorProperties(layerid)

    self.setVectorStylesEnabled(currentItem.data(0, Qt.CheckStateRole) == Qt.Checked)

  def vectorLayerItemChanged(self, item, column):
    # update style form enablement
    currentItem = self.ui.treeWidget_VectorLayers.currentItem()
    if currentItem:
      self.setVectorStylesEnabled(currentItem.data(0, Qt.CheckStateRole) == Qt.Checked)

  def objectTypeSelectionChanged(self, idx=None):
    layer = self.currentVectorLayer
    try:
      ve = float(ui.lineEdit_zFactor.text())
    except:
      ve = 1
    mapTo3d = MapTo3D(self.iface.mapCanvas(), verticalExaggeration=ve)
    self.objectTypeManager.setupForm(self, mapTo3d, layer, layer.geometryType(), self.ui.comboBox_ObjectType.currentIndex())

  def numericFields(self, layer):
    # get attributes of a sample feature and create numeric field name list
    numeric_fields = []
    f = QgsFeature()
    layer.getFeatures().nextFeature(f)
    for field in f.fields():
      isNumeric = False
      try:
        float(f.attribute(field.name()))
        isNumeric = True
      except:
        pass
      if isNumeric:
        numeric_fields.append(field.name())
    return numeric_fields

  def setVectorStylesEnabled(self, enabled):
    self.ui.comboBox_ObjectType.setEnabled(enabled)
    self.ui.label_ObjectType.setEnabled(enabled)
    self.colorWidget.setEnabled(enabled)
    self.heightWidget.setEnabled(enabled)
    for i in range(self.STYLE_MAX_COUNT):
      self.styleWidgets[i].setEnabled(enabled)

  def saveVectorProperties(self, layerid):
    properties = {}
    layer = QgsMapLayerRegistry().instance().mapLayer(layerid)
    itemIndex = self.ui.comboBox_ObjectType.currentIndex()
    properties["itemindex"] = itemIndex
    properties["typeitem"] = self.objectTypeManager.objectTypeItem(layer.geometryType(), itemIndex)
    properties["visible"] = self.vlItems[self.currentVectorLayer.id()].data(0, Qt.CheckStateRole) == Qt.Checked
    properties["color"] = self.colorWidget.values()
    properties["height"] = self.heightWidget.values()
    for i in range(self.STYLE_MAX_COUNT):
      if self.styleWidgets[i].isVisible():
        properties[i] = self.styleWidgets[i].values()
    self.vectorPropertiesDict[layerid] = properties

  def restoreVectorProperties(self, layerid):
    properties = self.vectorPropertiesDict[layerid]
    self.ui.comboBox_ObjectType.setCurrentIndex(properties["itemindex"])
    self.colorWidget.setValues(properties["color"])
    self.heightWidget.setValues(properties["height"])
    for i in range(self.STYLE_MAX_COUNT):
      if i in properties:
        self.styleWidgets[i].setValues(properties[i])

  def calculateResolution(self, v=None):
    extent = self.iface.mapCanvas().extent()
    renderer = self.iface.mapCanvas().mapRenderer()
    size = 100 * self.ui.horizontalSlider_Resolution.value()
    self.ui.label_Resolution.setText("about {0} x {0} px".format(size))

    # calculate resolution and size
    width, height = renderer.width(), renderer.height()
    s = (size * size / float(width * height)) ** 0.5
    if s < 1:
      width = int(width * s)
      height = int(height * s)

    xres = extent.width() / width
    yres = extent.height() / height
    self.ui.lineEdit_HRes.setText(str(xres))
    self.ui.lineEdit_VRes.setText(str(yres))
    self.ui.lineEdit_Width.setText(str(width + 1))
    self.ui.lineEdit_Height.setText(str(height + 1))

  def progress(self, percentage):
    self.ui.progressBar.setValue(percentage)
    self.ui.progressBar.setVisible(percentage != 100)

  def run(self):
    ui = self.ui
    filename = ui.lineEdit_OutputFilename.text()   # ""=Temporary file
    if filename != "" and QFileInfo(filename).exists() and QMessageBox.question(None, "Qgis2threejs", "Output file already exists. Overwrite it?", QMessageBox.Ok | QMessageBox.Cancel) != QMessageBox.Ok:
      return
    self.endPointSelection()

    item = ui.treeWidget_VectorLayers.currentItem()
    if item:
      self.saveVectorProperties(item.data(0, Qt.UserRole))

    ui.pushButton_Run.setEnabled(False)
    self.progress(0)

    canvas = self.iface.mapCanvas()
    htmlfilename = ui.lineEdit_OutputFilename.text()
    demlayerid = ui.comboBox_DEMLayer.itemData(ui.comboBox_DEMLayer.currentIndex())
    mapTo3d = MapTo3D(canvas, verticalExaggeration=float(ui.lineEdit_zFactor.text()))
    if self.ui.radioButton_Simple.isChecked():
      dem_width = int(ui.lineEdit_Width.text())
      dem_height = int(ui.lineEdit_Height.text())
      context = OutputContext(mapTo3d, canvas, demlayerid, self.vectorPropertiesDict, self.objectTypeManager, self.localBrowsingMode,
                              dem_width, dem_height)
      htmlfilename = runSimple(htmlfilename, context, self.progress)
    else:
      context = OutputContext(mapTo3d, canvas, demlayerid, self.vectorPropertiesDict, self.objectTypeManager, self.localBrowsingMode)
      htmlfilename = runAdvanced(htmlfilename, context, self, self.progress)
    self.progress(100)
    ui.pushButton_Run.setEnabled(True)
    if htmlfilename is None:
      return
    self.clearRubberBands()

    if not tools.openHTMLFile(htmlfilename):
      return
    QDialog.accept(self)

  def reject(self):
    self.endPointSelection()
    self.clearRubberBands()
    QDialog.reject(self)

  def startPointSelection(self):
    canvas = self.iface.mapCanvas()
    self.previousMapTool = canvas.mapTool()
    canvas.setMapTool(self.mapTool)
    self.ui.toolButton_PointTool.setVisible(False)

  def endPointSelection(self):
    self.mapTool.reset()
    self.iface.mapCanvas().setMapTool(self.previousMapTool)

  def rectangleSelected(self):
    ui = self.ui
    ui.radioButton_Advanced.setChecked(True)
    rect = self.mapTool.rectangle()
    toRect = rect.width() and rect.height()
    self.switchFocusMode(toRect)
    ui.lineEdit_xmin.setText(str(rect.xMinimum()))
    ui.lineEdit_ymin.setText(str(rect.yMinimum()))
    ui.lineEdit_xmax.setText(str(rect.xMaximum()))
    ui.lineEdit_ymax.setText(str(rect.yMaximum()))

    quadtree = QuadTree(self.iface.mapCanvas().extent())
    quadtree.buildTreeByRect(rect, self.ui.spinBox_Height.value())
    self.createRubberBands(quadtree.quads(), rect.center())
    self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)

  def pointSelected(self):
    # set values of controls
    self.ui.lineEdit_CenterX.setText(str(self.mapTool.point.x()))
    self.ui.lineEdit_CenterY.setText(str(self.mapTool.point.y()))
    self.ui.radioButton_Advanced.setChecked(True)

    quadtree = QuadTree(self.iface.mapCanvas().extent(), self.mapTool.point, self.ui.spinBox_Height.value())
    self.createRubberBands(quadtree.quads(), self.mapTool.point)
    self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)

  def mapToolSet(self, mapTool):
    if mapTool != self.mapTool:
      self.ui.toolButton_PointTool.setVisible(True)

  def createQuadTree(self):
    ui = self.ui
    try:
      c = map(float, [ui.lineEdit_xmin.text(), ui.lineEdit_ymin.text(), ui.lineEdit_xmax.text(), ui.lineEdit_ymax.text()])
    except:
      return None
    quadtree = QuadTree(self.iface.mapCanvas().extent())
    quadtree.buildTreeByRect(QgsRectangle(c[0], c[1], c[2], c[3]), ui.spinBox_Height.value())
    return quadtree

  def createRubberBands(self, quads, point=None):
    self.clearRubberBands()
    # create quads with rubber band
    self.rb_quads = QgsRubberBand(self.iface.mapCanvas(), QGis.Line)
    self.rb_quads.setColor(Qt.blue)
    self.rb_quads.setWidth(1)

    for quad in quads:
      points = []
      extent = quad.extent
      points.append(QgsPoint(extent.xMinimum(), extent.yMinimum()))
      points.append(QgsPoint(extent.xMinimum(), extent.yMaximum()))
      points.append(QgsPoint(extent.xMaximum(), extent.yMaximum()))
      points.append(QgsPoint(extent.xMaximum(), extent.yMinimum()))
      self.rb_quads.addGeometry(QgsGeometry.fromPolygon([points]), None)
      self.log(extent.toString())
    self.log("Quad count: %d" % len(quads))

    # create a point with rubber band
    if point:
      self.rb_point = QgsRubberBand(self.iface.mapCanvas(), QGis.Point)
      self.rb_point.setColor(Qt.red)
      self.rb_point.addPoint(point)

  def clearRubberBands(self):
    # clear quads and point
    if self.rb_quads:
      self.iface.mapCanvas().scene().removeItem(self.rb_quads)
      self.rb_quads = None
    if self.rb_point:
      self.iface.mapCanvas().scene().removeItem(self.rb_point)
      self.rb_point = None

  def browseClicked(self):
    directory = self.ui.lineEdit_OutputFilename.text()
    if directory == "":
      directory = QDir.homePath()
    filename = QFileDialog.getSaveFileName(self, self.tr("Output filename"), directory, "HTML file (*.html *.htm)", options=QFileDialog.DontConfirmOverwrite)
    if filename != "":
      self.ui.lineEdit_OutputFilename.setText(filename)

  def samplingModeChanged(self):
    ui = self.ui
    isSimpleMode = ui.radioButton_Simple.isChecked()
    simple_widgets = [ui.horizontalSlider_Resolution, ui.lineEdit_Width, ui.lineEdit_Height, ui.lineEdit_HRes, ui.lineEdit_VRes]
    for w in simple_widgets:
      w.setEnabled(isSimpleMode)

    isAdvancedMode = not isSimpleMode
    advanced_widgets = [ui.spinBox_Height, ui.lineEdit_xmin, ui.lineEdit_ymin, ui.lineEdit_xmax, ui.lineEdit_ymax, ui.toolButton_switchFocusMode]
    for w in advanced_widgets:
      w.setEnabled(isAdvancedMode)

  def updateQuads(self, v=None):
    quadtree = self.createQuadTree()
    if quadtree:
      self.createRubberBands(quadtree.quads(), quadtree.focusRect.center())
    else:
      self.clearRubberBands()

  def switchFocusModeClicked(self):
    self.switchFocusMode(not self.ui.label_xmin.isVisible())

  def switchFocusMode(self, toRect):
    ui = self.ui
    toPoint = not toRect
    ui.label_xmin.setVisible(toRect)
    ui.label_ymin.setVisible(toRect)
    ui.lineEdit_xmin.setVisible(toRect)
    ui.lineEdit_ymin.setVisible(toRect)

    suffix = "max" if toRect else ""
    ui.label_xmax.setText("x" + suffix)
    ui.label_ymax.setText("y" + suffix)
    mode = "point" if toRect else "rectangle"
    ui.toolButton_switchFocusMode.setText("To " + mode + " selection")
    selection = "area" if toRect else "point"
    action = "Stroke a rectangle" if toRect else "Click"
    ui.label_Focus.setText("Focus {0} ({1} on map canvas to set values)".format(selection, action))

  def log(self, msg):
    if debug_mode:
      qDebug(msg)
class LatLonTools:
    digitizerDialog = None
    convertCoordinateDialog = None

    def __init__(self, iface):
        self.iface = iface
        self.canvas = iface.mapCanvas()
        self.crossRb = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.crossRb.setColor(Qt.red)
        self.provider = LatLonToolsProvider()
        self.toolbar = self.iface.addToolBar('Lat Lon Tools Toolbar')
        self.toolbar.setObjectName('LatLonToolsToolbar')

    def initGui(self):
        '''Initialize Lot Lon Tools GUI.'''
        # Initialize the Settings Dialog box
        self.settingsDialog = SettingsWidget(self, self.iface, self.iface.mainWindow())
        self.mapTool = CopyLatLonTool(self.settingsDialog, self.iface)
        self.showMapTool = ShowOnMapTool(self.iface)

        # Add Interface for Coordinate Capturing
        icon = QIcon(os.path.dirname(__file__) + "/images/copyicon.png")
        self.copyAction = QAction(icon, "Copy/Display Coordinate", self.iface.mainWindow())
        self.copyAction.setObjectName('latLonToolsCopy')
        self.copyAction.triggered.connect(self.startCapture)
        self.copyAction.setCheckable(True)
        self.toolbar.addAction(self.copyAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.copyAction)

        # Add Interface for External Map
        icon = QIcon(os.path.dirname(__file__) + "/images/mapicon.png")
        self.externMapAction = QAction(icon, "Show in External Map", self.iface.mainWindow())
        self.externMapAction.setObjectName('latLonToolsExternalMap')
        self.externMapAction.triggered.connect(self.setShowMapTool)
        self.externMapAction.setCheckable(True)
        self.toolbar.addAction(self.externMapAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.externMapAction)

        # Add Interface for Zoom to Coordinate
        icon = QIcon(os.path.dirname(__file__) + "/images/zoomicon.png")
        self.zoomToAction = QAction(icon, "Zoom To Coordinate", self.iface.mainWindow())
        self.zoomToAction.setObjectName('latLonToolsZoom')
        self.zoomToAction.triggered.connect(self.showZoomToDialog)
        self.toolbar.addAction(self.zoomToAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.zoomToAction)

        self.zoomToDialog = ZoomToLatLon(self, self.iface, self.iface.mainWindow())
        self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.zoomToDialog)
        self.zoomToDialog.hide()

        # Add Interface for Multi point zoom
        icon = QIcon(os.path.dirname(__file__) + '/images/multizoom.png')
        self.multiZoomToAction = QAction(icon, "Multi-location Zoom", self.iface.mainWindow())
        self.multiZoomToAction.setObjectName('latLonToolsMultiZoom')
        self.multiZoomToAction.triggered.connect(self.multiZoomTo)
        self.toolbar.addAction(self.multiZoomToAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.multiZoomToAction)

        self.multiZoomDialog = MultiZoomWidget(self, self.settingsDialog, self.iface.mainWindow())
        self.multiZoomDialog.hide()
        self.multiZoomDialog.setFloating(True)

        # Create the coordinate converter menu
        icon = QIcon(':/images/themes/default/mIconProjectionEnabled.svg')
        self.convertCoordinatesAction = QAction(icon, "Coordinate Conversion", self.iface.mainWindow())
        self.convertCoordinatesAction.setObjectName('latLonToolsCoordinateConversion')
        self.convertCoordinatesAction.triggered.connect(self.convertCoordinatesTool)
        self.toolbar.addAction(self.convertCoordinatesAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.convertCoordinatesAction)

        # Create the conversions menu
        menu = QMenu()
        icon = QIcon(os.path.dirname(__file__) + '/images/field2geom.png')
        action = menu.addAction(icon, "Fields to point layer", self.field2geom)
        action.setObjectName('latLonToolsField2Geom')
        icon = QIcon(os.path.dirname(__file__) + '/images/geom2field.png')
        action = menu.addAction(icon, "Point layer to fields", self.geom2Field)
        action.setObjectName('latLonToolsGeom2Field')
        icon = QIcon(os.path.dirname(__file__) + '/images/pluscodes.png')
        action = menu.addAction(icon, "Plus Codes to point layer", self.PlusCodestoLayer)
        action.setObjectName('latLonToolsPlusCodes2Geom')
        action = menu.addAction(icon, "Point layer to Plus Codes", self.toPlusCodes)
        action.setObjectName('latLonToolsGeom2PlusCodes')
        icon = QIcon(os.path.dirname(__file__) + '/images/mgrs2point.png')
        action = menu.addAction(icon, "MGRS to point layer", self.MGRStoLayer)
        action.setObjectName('latLonToolsMGRS2Geom')
        icon = QIcon(os.path.dirname(__file__) + '/images/point2mgrs.png')
        action = menu.addAction(icon, "Point layer to MGRS", self.toMGRS)
        action.setObjectName('latLonToolsGeom2MGRS')
        self.conversionsAction = QAction(icon, "Conversions", self.iface.mainWindow())
        self.conversionsAction.setMenu(menu)
        self.iface.addPluginToMenu('Lat Lon Tools', self.conversionsAction)

        # Add to Digitize Toolbar
        icon = QIcon(os.path.dirname(__file__) + '/images/latLonDigitize.png')
        self.digitizeAction = QAction(icon, "Lat Lon Digitize", self.iface.mainWindow())
        self.digitizeAction.setObjectName('latLonToolsDigitize')
        self.digitizeAction.triggered.connect(self.digitizeClicked)
        self.digitizeAction.setEnabled(False)
        self.toolbar.addAction(self.digitizeAction)
        self.iface.addPluginToMenu('Lat Lon Tools', self.digitizeAction)

        # Add Interface for copying the canvas extent
        icon = QIcon(os.path.dirname(__file__) + "/images/copycanvas.png")
        self.copyCanvasAction = QAction(icon, "Copy Canvas Bounding Box", self.iface.mainWindow())
        self.copyCanvasAction.setObjectName('latLonToolsCopyCanvas')
        self.copyCanvasAction.triggered.connect(self.copyCanvas)
        self.toolbar.addAction(self.copyCanvasAction)
        self.iface.addPluginToMenu("Lat Lon Tools", self.copyCanvasAction)

        # Initialize the Settings Dialog Box
        settingsicon = QIcon(os.path.dirname(__file__) + '/images/settings.png')
        self.settingsAction = QAction(settingsicon, "Settings", self.iface.mainWindow())
        self.settingsAction.setObjectName('latLonToolsSettings')
        self.settingsAction.triggered.connect(self.settings)
        self.iface.addPluginToMenu('Lat Lon Tools', self.settingsAction)

        # Help
        icon = QIcon(os.path.dirname(__file__) + '/images/help.png')
        self.helpAction = QAction(icon, "Help", self.iface.mainWindow())
        self.helpAction.setObjectName('latLonToolsHelp')
        self.helpAction.triggered.connect(self.help)
        self.iface.addPluginToMenu('Lat Lon Tools', self.helpAction)

        self.iface.currentLayerChanged.connect(self.currentLayerChanged)
        self.canvas.mapToolSet.connect(self.unsetTool)
        self.enableDigitizeTool()

        # Add the processing provider
        QgsApplication.processingRegistry().addProvider(self.provider)

    def unsetTool(self, tool):
        '''Uncheck the Copy Lat Lon tool'''
        try:
            if not isinstance(tool, CopyLatLonTool):
                self.copyAction.setChecked(False)
                self.multiZoomDialog.stopCapture()
                self.mapTool.capture4326 = False
            if not isinstance(tool, ShowOnMapTool):
                self.externMapAction.setChecked(False)
        except Exception:
            pass

    def unload(self):
        '''Unload LatLonTools from the QGIS interface'''
        self.zoomToDialog.removeMarker()
        self.multiZoomDialog.removeMarkers()
        self.canvas.unsetMapTool(self.mapTool)
        self.canvas.unsetMapTool(self.showMapTool)
        self.iface.removePluginMenu('Lat Lon Tools', self.copyAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.copyCanvasAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.externMapAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.zoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.multiZoomToAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.convertCoordinatesAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.conversionsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.settingsAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.helpAction)
        self.iface.removePluginMenu('Lat Lon Tools', self.digitizeAction)
        self.iface.removeDockWidget(self.zoomToDialog)
        self.iface.removeDockWidget(self.multiZoomDialog)
        # Remove Toolbar Icons
        self.iface.removeToolBarIcon(self.copyAction)
        self.iface.removeToolBarIcon(self.copyCanvasAction)
        self.iface.removeToolBarIcon(self.zoomToAction)
        self.iface.removeToolBarIcon(self.externMapAction)
        self.iface.removeToolBarIcon(self.multiZoomToAction)
        self.iface.removeToolBarIcon(self.convertCoordinatesAction)
        self.iface.removeToolBarIcon(self.digitizeAction)
        del self.toolbar

        self.zoomToDialog = None
        self.multiZoomDialog = None
        self.settingsDialog = None
        self.showMapTool = None
        self.mapTool = None
        self.digitizerDialog = None
        self.convertCoordinateDialog = None
        QgsApplication.processingRegistry().removeProvider(self.provider)

    def startCapture(self):
        '''Set the focus of the copy coordinate tool and check it'''
        self.copyAction.setChecked(True)
        self.canvas.setMapTool(self.mapTool)

    def copyCanvas(self):
        extent = self.iface.mapCanvas().extent()
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        if settings.bBoxCrs == 0 and canvasCrs != epsg4326:
            transform = QgsCoordinateTransform(canvasCrs, epsg4326, QgsProject.instance())
            p1x, p1y = transform.transform(float(extent.xMinimum()), float(extent.yMinimum()))
            p2x, p2y = transform.transform(float(extent.xMaximum()), float(extent.yMaximum()))
            extent.set(p1x, p1y, p2x, p2y)
        delim = settings.bBoxDelimiter
        prefix = settings.bBoxPrefix
        suffix = settings.bBoxSuffix
        precision = settings.bBoxDigits
        outStr = ''
        minX = extent.xMinimum()
        minY = extent.yMinimum()
        maxX = extent.xMaximum()
        maxY = extent.yMaximum()
        if settings.bBoxFormat == 0:  # minX,minY,maxX,maxY - using the delimiter
            outStr = '{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}'.format(
                minX, delim, minY, delim, maxX, delim, maxY, prec=precision)
        elif settings.bBoxFormat == 1:  # minX,maxX,minY,maxY - Using the selected delimiter'
            outStr = '{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}{}{:.{prec}f}'.format(
                minX, delim, maxX, delim, minY, delim, maxY, prec=precision)
        elif settings.bBoxFormat == 2:  # x1 y1,x2 y2,x3 y3,x4 y4,x1 y1 - Polygon format
            outStr = '{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f}'.format(
                minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY, prec=precision)
        elif settings.bBoxFormat == 3:  # x1,y1 x2,y2 x3,y3 x4,y4 x1,y1 - Polygon format
            outStr = '{:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f} {:.{prec}f},{:.{prec}f}'.format(
                minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY, prec=precision)
        elif settings.bBoxFormat == 4:  # WKT Polygon
            outStr = extent.asWktPolygon()
        elif settings.bBoxFormat == 5:  # bbox: [minX, minY, maxX, maxY] - MapProxy
            outStr = 'bbox: [{}, {}, {}, {}]'.format(
                minX, minY, maxX, maxY)
        elif settings.bBoxFormat == 6:  # bbox: [minX, minY, maxX, maxY] - MapProxy
            outStr = 'bbox={},{},{},{}'.format(
                minX, minY, maxX, maxY)
        outStr = '{}{}{}'.format(prefix, outStr, suffix)
        clipboard = QApplication.clipboard()
        clipboard.setText(outStr)
        self.iface.messageBar().pushMessage("", "'{}' copied to the clipboard".format(outStr), level=Qgis.Info, duration=4)

    def setShowMapTool(self):
        '''Set the focus of the external map tool and check it'''
        self.externMapAction.setChecked(True)
        self.canvas.setMapTool(self.showMapTool)

    def showZoomToDialog(self):
        '''Show the zoom to docked widget.'''
        self.zoomToDialog.show()

    def convertCoordinatesTool(self):
        '''Display the Convert Coordinate Tool Dialog box.'''
        if self.convertCoordinateDialog is None:
            from .coordinateConverter import CoordinateConverterWidget
            self.convertCoordinateDialog = CoordinateConverterWidget(self, self.settingsDialog, self.iface, self.iface.mainWindow())
        self.convertCoordinateDialog.show()

    def multiZoomTo(self):
        '''Display the Multi-zoom to dialog box'''
        self.multiZoomDialog.show()

    def field2geom(self):
        '''Convert layer containing a point x & y coordinate to a new point layer'''
        processing.execAlgorithmDialog('latlontools:field2geom', {})

    def geom2Field(self):
        '''Convert layer geometry to a text string'''
        processing.execAlgorithmDialog('latlontools:geom2field', {})

    def toMGRS(self):
        '''Display the to MGRS  dialog box'''
        processing.execAlgorithmDialog('latlontools:point2mgrs', {})

    def MGRStoLayer(self):
        '''Display the to MGRS  dialog box'''
        processing.execAlgorithmDialog('latlontools:mgrs2point', {})

    def toPlusCodes(self):
        processing.execAlgorithmDialog('latlontools:point2pluscodes', {})

    def PlusCodestoLayer(self):
        processing.execAlgorithmDialog('latlontools:pluscodes2point', {})

    def settings(self):
        '''Show the settings dialog box'''
        self.settingsDialog.show()

    def help(self):
        '''Display a help page'''
        url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString()
        webbrowser.open(url, new=2)

    def settingsChanged(self):
        # Settings may have changed so we need to make sure the zoomToDialog window is configured properly
        self.zoomToDialog.configure()
        self.multiZoomDialog.settingsChanged()

    def zoomTo(self, srcCrs, lat, lon):
        canvasCrs = self.canvas.mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(srcCrs, canvasCrs, QgsProject.instance())
        x, y = transform.transform(float(lon), float(lat))

        rect = QgsRectangle(x, y, x, y)
        self.canvas.setExtent(rect)

        pt = QgsPointXY(x, y)
        self.highlight(pt)
        self.canvas.refresh()
        return pt

    def highlight(self, point):
        currExt = self.canvas.extent()

        leftPt = QgsPoint(currExt.xMinimum(), point.y())
        rightPt = QgsPoint(currExt.xMaximum(), point.y())

        topPt = QgsPoint(point.x(), currExt.yMaximum())
        bottomPt = QgsPoint(point.x(), currExt.yMinimum())

        horizLine = QgsGeometry.fromPolyline([leftPt, rightPt])
        vertLine = QgsGeometry.fromPolyline([topPt, bottomPt])

        self.crossRb.reset(QgsWkbTypes.LineGeometry)
        self.crossRb.addGeometry(horizLine, None)
        self.crossRb.addGeometry(vertLine, None)

        QTimer.singleShot(700, self.resetRubberbands)

    def resetRubberbands(self):
        self.crossRb.reset()

    def digitizeClicked(self):
        if self.digitizerDialog is None:
            from .digitizer import DigitizerWidget
            self.digitizerDialog = DigitizerWidget(self, self.iface, self.iface.mainWindow())
        self.digitizerDialog.show()

    def currentLayerChanged(self):
        layer = self.iface.activeLayer()
        if layer is not None:
            try:
                layer.editingStarted.disconnect(self.layerEditingChanged)
            except Exception:
                pass
            try:
                layer.editingStopped.disconnect(self.layerEditingChanged)
            except Exception:
                pass

            if isinstance(layer, QgsVectorLayer):
                layer.editingStarted.connect(self.layerEditingChanged)
                layer.editingStopped.connect(self.layerEditingChanged)

        self.enableDigitizeTool()

    def layerEditingChanged(self):
        self.enableDigitizeTool()

    def enableDigitizeTool(self):
        self.digitizeAction.setEnabled(False)
        layer = self.iface.activeLayer()

        if layer is not None and isinstance(layer, QgsVectorLayer) and (layer.geometryType() == QgsWkbTypes.PointGeometry) and layer.isEditable():
            self.digitizeAction.setEnabled(True)
        else:
            if self.digitizerDialog is not None:
                self.digitizerDialog.close()
Example #41
0
class Qgis2threejsDialog(QDialog):
    def __init__(self,
                 iface,
                 objectTypeManager,
                 pluginManager,
                 exportSettings=None,
                 lastTreeItemData=None):
        QDialog.__init__(self, iface.mainWindow())
        self.iface = iface
        self.objectTypeManager = objectTypeManager
        self.pluginManager = pluginManager
        self._settings = exportSettings or {}
        self.lastTreeItemData = lastTreeItemData
        self.localBrowsingMode = True

        self.rb_quads = self.rb_point = None

        self.templateType = None
        self.currentItem = None
        self.currentPage = None

        # Set up the user interface from Designer.
        self.ui = ui = Ui_Qgis2threejsDialog()
        ui.setupUi(self)

        self.setWindowFlags(self.windowFlags() | Qt.WindowMinimizeButtonHint)

        # output html filename
        ui.lineEdit_OutputFilename.setText(
            self._settings.get("OutputFilename", ""))
        ui.lineEdit_OutputFilename.setPlaceholderText("[Temporary file]")

        # settings button
        icon = QIcon(os.path.join(tools.pluginDir(), "icons", "settings.png"))
        ui.toolButton_Settings.setIcon(icon)

        # popup menu displayed when settings button is pressed
        items = [["Load Settings...", self.loadSettings],
                 ["Save Settings As...", self.saveSettings], [None, None],
                 ["Clear Settings", self.clearSettings], [None, None],
                 ["Plugin Settings...", self.pluginSettings]]

        self.menu = QMenu()
        self.menu_actions = []
        for text, slot in items:
            if text:
                action = QAction(text, iface.mainWindow())
                action.triggered.connect(slot)
                self.menu.addAction(action)
                self.menu_actions.append(action)
            else:
                self.menu.addSeparator()

        ui.toolButton_Settings.setMenu(self.menu)
        ui.toolButton_Settings.setPopupMode(QToolButton.InstantPopup)

        # progress bar and message label
        ui.progressBar.setVisible(False)
        ui.label_MessageIcon.setVisible(False)

        # buttons
        ui.pushButton_Run.clicked.connect(self.run)
        ui.pushButton_Close.clicked.connect(self.reject)
        ui.pushButton_Help.clicked.connect(self.help)

        # set up map tool
        self.previousMapTool = None
        self.mapTool = RectangleMapTool(iface.mapCanvas())
        #self.mapTool = PointMapTool(iface.mapCanvas())

        # set up the template combo box
        self.initTemplateList()
        self.ui.comboBox_Template.currentIndexChanged.connect(
            self.currentTemplateChanged)

        # set up the properties pages
        self.pages = {}
        self.pages[ppages.PAGE_WORLD] = ppages.WorldPropertyPage(self)
        self.pages[ppages.PAGE_CONTROLS] = ppages.ControlsPropertyPage(self)
        self.pages[ppages.PAGE_DEM] = ppages.DEMPropertyPage(self)
        self.pages[ppages.PAGE_VECTOR] = ppages.VectorPropertyPage(self)
        container = ui.propertyPagesContainer
        for page in self.pages.itervalues():
            page.hide()
            container.addWidget(page)

        # build object tree
        self.topItemPages = {
            ObjectTreeItem.ITEM_WORLD: ppages.PAGE_WORLD,
            ObjectTreeItem.ITEM_CONTROLS: ppages.PAGE_CONTROLS,
            ObjectTreeItem.ITEM_DEM: ppages.PAGE_DEM
        }
        self.initObjectTree()
        self.ui.treeWidget.currentItemChanged.connect(
            self.currentObjectChanged)
        self.ui.treeWidget.itemChanged.connect(self.objectItemChanged)
        self.currentTemplateChanged()  # update item visibility

        ui.toolButton_Browse.clicked.connect(self.browseClicked)

        #iface.mapCanvas().mapToolSet.connect(self.mapToolSet)    # to show button to enable own map tool

    def settings(self, clean=False):
        # save settings of current panel
        item = self.ui.treeWidget.currentItem()
        if item and self.currentPage:
            self.saveProperties(item, self.currentPage)

        # plugin version
        self._settings["PluginVersion"] = plugin_version

        # template and output html file path
        self._settings["Template"] = self.ui.comboBox_Template.currentText()
        self._settings[
            "OutputFilename"] = self.ui.lineEdit_OutputFilename.text()

        if not clean:
            return self._settings

        # clean up settings - remove layers that don't exist in the layer registry
        registry = QgsMapLayerRegistry.instance()
        for itemId in [
                ObjectTreeItem.ITEM_OPTDEM, ObjectTreeItem.ITEM_POINT,
                ObjectTreeItem.ITEM_LINE, ObjectTreeItem.ITEM_POLYGON
        ]:
            parent = self._settings.get(itemId, {})
            for layerId in parent.keys():
                if registry.mapLayer(layerId) is None:
                    del parent[layerId]

        return self._settings

    def setSettings(self, settings):
        self._settings = settings

        # template and output html file path
        templateName = settings.get("Template")
        if templateName:
            cbox = self.ui.comboBox_Template
            index = cbox.findText(templateName)
            if index != -1:
                cbox.setCurrentIndex(index)

        filename = settings.get("OutputFilename")
        if filename:
            self.ui.lineEdit_OutputFilename.setText(filename)

        # update object tree
        self.ui.treeWidget.blockSignals(True)
        self.initObjectTree()
        self.ui.treeWidget.blockSignals(False)

        # update tree item visibility
        self.templateType = None
        self.currentTemplateChanged()

    def loadSettings(self):
        # file open dialog
        directory = QgsProject.instance().homePath()
        if not directory:
            directory = os.path.split(
                self.ui.lineEdit_OutputFilename.text())[0]
        if not directory:
            directory = QDir.homePath()
        filterString = "Settings files (*.qto3settings);;All files (*.*)"
        filename = QFileDialog.getOpenFileName(self, "Load Export Settings",
                                               directory, filterString)
        if not filename:
            return

        # load settings from file (.qto3settings)
        import json
        with open(filename) as f:
            settings = json.load(f)

        self.setSettings(settings)

    def saveSettings(self, filename=None):
        if not filename:
            # file save dialog
            directory = QgsProject.instance().homePath()
            if not directory:
                directory = os.path.split(
                    self.ui.lineEdit_OutputFilename.text())[0]
            if not directory:
                directory = QDir.homePath()
            filename = QFileDialog.getSaveFileName(
                self, "Save Export Settings", directory,
                "Settings files (*.qto3settings)")
            if not filename:
                return

            # append .qto3settings extension if filename doesn't have
            if os.path.splitext(filename)[1].lower() != ".qto3settings":
                filename += ".qto3settings"

        # save settings to file (.qto3settings)
        import codecs
        import json
        with codecs.open(filename, "w", "UTF-8") as f:
            json.dump(self.settings(True),
                      f,
                      ensure_ascii=False,
                      indent=2,
                      sort_keys=True)

        logMessage(u"Settings saved: {0}".format(filename))

    def clearSettings(self):
        if QMessageBox.question(self, "Qgis2threejs",
                                "Are you sure to clear all export settings?",
                                QMessageBox.Ok
                                | QMessageBox.Cancel) == QMessageBox.Ok:
            self.setSettings({})

    def pluginSettings(self):
        from settingsdialog import SettingsDialog
        dialog = SettingsDialog(self)
        if dialog.exec_():
            self.pluginManager.reloadPlugins()
            self.pages[ppages.PAGE_DEM].initLayerComboBox()

    def showMessageBar(self, text, level=QgsMessageBar.INFO):
        # from src/gui/qgsmessagebaritem.cpp
        if level == QgsMessageBar.CRITICAL:
            msgIcon = "/mIconCritical.png"
            bgColor = "#d65253"
        elif level == QgsMessageBar.WARNING:
            msgIcon = "/mIconWarn.png"
            bgColor = "#ffc800"
        else:
            msgIcon = "/mIconInfo.png"
            bgColor = "#e7f5fe"
        stylesheet = "QLabel {{ background-color:{0}; }}".format(bgColor)

        label = self.ui.label_MessageIcon
        label.setPixmap(QgsApplication.getThemeIcon(msgIcon).pixmap(24))
        label.setStyleSheet(stylesheet)
        label.setVisible(True)

        label = self.ui.label_Status
        label.setText(text)
        label.setStyleSheet(stylesheet)

    def clearMessageBar(self):
        self.ui.label_MessageIcon.setVisible(False)
        self.ui.label_Status.setText("")
        self.ui.label_Status.setStyleSheet(
            "QLabel { background-color: rgba(0, 0, 0, 0); }")

    def initTemplateList(self):
        cbox = self.ui.comboBox_Template
        cbox.clear()
        templateDir = QDir(tools.templateDir())
        for i, entry in enumerate(templateDir.entryList(["*.html", "*.htm"])):
            cbox.addItem(entry)

            config = tools.getTemplateConfig(entry)
            # get template type
            templateType = config.get("type", "plain")
            cbox.setItemData(i, templateType, Qt.UserRole)

            # set tool tip text
            desc = config.get("description", "")
            if desc:
                cbox.setItemData(i, desc, Qt.ToolTipRole)

        # select the template of the settings
        templatePath = self._settings.get("Template")

        # if no template setting, select the last used template
        if not templatePath:
            templatePath = QSettings().value("/Qgis2threejs/lastTemplate",
                                             def_vals.template,
                                             type=unicode)

        if templatePath:
            index = cbox.findText(templatePath)
            if index != -1:
                cbox.setCurrentIndex(index)
            return index
        return -1

    def initObjectTree(self):
        tree = self.ui.treeWidget
        tree.clear()

        # add vector and raster layers into tree widget
        topItems = {}
        for id, name in zip(ObjectTreeItem.topItemIds,
                            ObjectTreeItem.topItemNames):
            item = QTreeWidgetItem(tree, [name])
            item.setData(0, Qt.UserRole, id)
            topItems[id] = item

        optDEMChecked = False
        for layer in self.iface.legendInterface().layers():
            parentId = ObjectTreeItem.parentIdByLayer(layer)
            if parentId is None:
                continue

            item = QTreeWidgetItem(topItems[parentId], [layer.name()])
            isVisible = self._settings.get(parentId, {}).get(
                layer.id(), {}).get(
                    "visible",
                    False)  #self.iface.legendInterface().isLayerVisible(layer)
            check_state = Qt.Checked if isVisible else Qt.Unchecked
            item.setData(0, Qt.CheckStateRole, check_state)
            item.setData(0, Qt.UserRole, layer.id())
            if parentId == ObjectTreeItem.ITEM_OPTDEM and isVisible:
                optDEMChecked = True

        for id, item in topItems.iteritems():
            if id != ObjectTreeItem.ITEM_OPTDEM or optDEMChecked:
                tree.expandItem(item)

        # disable additional DEM item which is selected as main DEM
        layerId = self._settings.get(ObjectTreeItem.ITEM_DEM,
                                     {}).get("comboBox_DEMLayer")
        if layerId:
            self.primaryDEMChanged(layerId)

    def saveProperties(self, item, page):
        properties = page.properties()
        parent = item.parent()
        if parent is None:
            # top level item
            self._settings[item.data(0, Qt.UserRole)] = properties
        else:
            # layer item
            parentId = parent.data(0, Qt.UserRole)
            if parentId not in self._settings:
                self._settings[parentId] = {}
            self._settings[parentId][item.data(0, Qt.UserRole)] = properties

    def setCurrentTreeItemByData(self, data):
        it = QTreeWidgetItemIterator(self.ui.treeWidget)
        while it.value():
            if it.value().data(0, Qt.UserRole) == data:
                self.ui.treeWidget.setCurrentItem(it.value())
                return True
            it += 1
        return False

    def currentTemplateChanged(self, index=None):
        cbox = self.ui.comboBox_Template
        templateType = cbox.itemData(cbox.currentIndex(), Qt.UserRole)
        if templateType == self.templateType:
            return

        # hide items unsupported by template
        tree = self.ui.treeWidget
        for i, id in enumerate(ObjectTreeItem.topItemIds):
            hidden = (templateType == "sphere"
                      and id != ObjectTreeItem.ITEM_CONTROLS)
            tree.topLevelItem(i).setHidden(hidden)

        # set current tree item
        if templateType == "sphere":
            tree.setCurrentItem(
                tree.topLevelItem(
                    ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_CONTROLS)))
        elif self.lastTreeItemData is None or not self.setCurrentTreeItemByData(
                self.lastTreeItemData):  # restore selection
            tree.setCurrentItem(
                tree.topLevelItem(
                    ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_DEM))
            )  # default selection for plain is DEM

        # display messages
        self.clearMessageBar()
        if templateType != "sphere":
            # show message if crs unit is degrees
            mapSettings = self.iface.mapCanvas().mapSettings(
            ) if QGis.QGIS_VERSION_INT >= 20300 else self.iface.mapCanvas(
            ).mapRenderer()
            if mapSettings.destinationCrs().mapUnits() in [QGis.Degrees]:
                self.showMessageBar(
                    "The unit of current CRS is degrees, so terrain may not appear well.",
                    QgsMessageBar.WARNING)

        self.templateType = templateType

    def currentObjectChanged(self, currentItem, previousItem):
        # save properties of previous item
        if previousItem and self.currentPage:
            self.saveProperties(previousItem, self.currentPage)

        self.currentItem = currentItem
        self.currentPage = None

        # hide text browser and all pages
        self.ui.textBrowser.hide()
        for page in self.pages.itervalues():
            page.hide()

        parent = currentItem.parent()
        if parent is None:
            topItemIndex = currentItem.data(0, Qt.UserRole)
            pageType = self.topItemPages.get(topItemIndex, ppages.PAGE_NONE)
            page = self.pages.get(pageType, None)
            if page is None:
                self.showDescription(topItemIndex)
                return

            page.setup(self._settings.get(topItemIndex))
            page.show()

        else:
            parentId = parent.data(0, Qt.UserRole)
            layerId = currentItem.data(0, Qt.UserRole)
            layer = QgsMapLayerRegistry.instance().mapLayer(unicode(layerId))
            if layer is None:
                return

            layerType = layer.type()
            if layerType == QgsMapLayer.RasterLayer:
                page = self.pages[ppages.PAGE_DEM]
                page.setup(
                    self._settings.get(parentId, {}).get(layerId, None), layer,
                    False)
            elif layerType == QgsMapLayer.VectorLayer:
                page = self.pages[ppages.PAGE_VECTOR]
                page.setup(
                    self._settings.get(parentId, {}).get(layerId, None), layer)
            else:
                return

            page.show()

        self.currentPage = page

    def objectItemChanged(self, item, column):
        parent = item.parent()
        if parent is None:
            return

        # checkbox of optional layer checked/unchecked
        if item == self.currentItem:
            if self.currentPage:
                # update enablement of property widgets
                self.currentPage.itemChanged(item)
        else:
            # select changed item
            self.ui.treeWidget.setCurrentItem(item)

            # set visible property
            #visible = item.data(0, Qt.CheckStateRole) == Qt.Checked
            #parentId = parent.data(0, Qt.UserRole)
            #layerId = item.data(0, Qt.UserRole)
            #self._settings.get(parentId, {}).get(layerId, {})["visible"] = visible

    def primaryDEMChanged(self, layerId):
        tree = self.ui.treeWidget
        parent = tree.topLevelItem(
            ObjectTreeItem.topItemIndex(ObjectTreeItem.ITEM_OPTDEM))
        tree.blockSignals(True)
        for i in range(parent.childCount()):
            item = parent.child(i)
            isPrimary = item.data(0, Qt.UserRole) == layerId
            item.setDisabled(isPrimary)
        tree.blockSignals(False)

    def showDescription(self, topItemIndex):
        fragment = {
            ObjectTreeItem.ITEM_OPTDEM: "additional-dem",
            ObjectTreeItem.ITEM_POINT: "point",
            ObjectTreeItem.ITEM_LINE: "line",
            ObjectTreeItem.ITEM_POLYGON: "polygon"
        }.get(topItemIndex)

        url = "http://qgis2threejs.readthedocs.org/en/docs-release/ExportSettings.html"
        if fragment:
            url += "#" + fragment

        html = '<a href="{0}">Online Help</a> about this item'.format(url)
        self.ui.textBrowser.setHtml(html)
        self.ui.textBrowser.show()

    def numericFields(self, layer):
        # get attributes of a sample feature and create numeric field name list
        numeric_fields = []
        f = QgsFeature()
        layer.getFeatures().nextFeature(f)
        for field in f.fields():
            isNumeric = False
            try:
                float(f.attribute(field.name()))
                isNumeric = True
            except ValueError:
                pass
            if isNumeric:
                numeric_fields.append(field.name())
        return numeric_fields

    def mapTo3d(self):
        canvas = self.iface.mapCanvas()
        mapSettings = canvas.mapSettings(
        ) if QGis.QGIS_VERSION_INT >= 20300 else canvas.mapRenderer()

        world = self._settings.get(ObjectTreeItem.ITEM_WORLD, {})
        bs = float(world.get("lineEdit_BaseSize", def_vals.baseSize))
        ve = float(world.get("lineEdit_zFactor", def_vals.zExaggeration))
        vs = float(world.get("lineEdit_zShift", def_vals.zShift))

        return MapTo3D(mapSettings, bs, ve, vs)

    def progress(self, percentage=None, statusMsg=None):
        ui = self.ui
        if percentage is not None:
            ui.progressBar.setValue(percentage)
            if percentage == 100:
                ui.progressBar.setVisible(False)
                ui.label_Status.setText("")
            else:
                ui.progressBar.setVisible(True)

        if statusMsg is not None:
            ui.label_Status.setText(statusMsg)
            ui.label_Status.repaint()
        QgsApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def run(self):
        self.endPointSelection()

        ui = self.ui
        filename = ui.lineEdit_OutputFilename.text()  # ""=Temporary file
        if filename and os.path.exists(filename):
            if QMessageBox.question(
                    self, "Qgis2threejs",
                    "Output file already exists. Overwrite it?",
                    QMessageBox.Ok | QMessageBox.Cancel) != QMessageBox.Ok:
                return

        # export to web (three.js)
        export_settings = ExportSettings(self.pluginManager,
                                         self.localBrowsingMode)
        export_settings.loadSettings(self.settings())
        export_settings.setMapCanvas(self.iface.mapCanvas())

        err_msg = export_settings.checkValidity()
        if err_msg is not None:
            QMessageBox.warning(self, "Qgis2threejs", err_msg
                                or "Invalid settings")
            return

        ui.pushButton_Run.setEnabled(False)
        ui.toolButton_Settings.setVisible(False)
        self.clearMessageBar()
        self.progress(0)

        if export_settings.exportMode == ExportSettings.PLAIN_MULTI_RES:
            # update quads and point on map canvas
            self.createRubberBands(export_settings.baseExtent,
                                   export_settings.quadtree())

        # export
        ret = exportToThreeJS(export_settings, self.iface.legendInterface(),
                              self.objectTypeManager, self.progress)

        self.progress(100)
        ui.pushButton_Run.setEnabled(True)

        if not ret:
            ui.toolButton_Settings.setVisible(True)
            return

        self.clearRubberBands()

        # store last selections
        settings = QSettings()
        settings.setValue("/Qgis2threejs/lastTemplate",
                          export_settings.templatePath)
        settings.setValue("/Qgis2threejs/lastControls",
                          export_settings.controls)

        # open web browser
        if not tools.openHTMLFile(export_settings.htmlfilename):
            ui.toolButton_Settings.setVisible(True)
            return

        # close dialog
        QDialog.accept(self)

    def reject(self):
        # save properties of current object
        item = self.ui.treeWidget.currentItem()
        if item and self.currentPage:
            self.saveProperties(item, self.currentPage)

        self.endPointSelection()
        self.clearRubberBands()
        QDialog.reject(self)

    def help(self):
        url = "http://qgis2threejs.readthedocs.org/"

        import webbrowser
        webbrowser.open(url, new=2)  # new=2: new tab if possible

    def startPointSelection(self):
        canvas = self.iface.mapCanvas()
        if self.previousMapTool != self.mapTool:
            self.previousMapTool = canvas.mapTool()
        canvas.setMapTool(self.mapTool)
        self.pages[ppages.PAGE_DEM].toolButton_PointTool.setVisible(False)

    def endPointSelection(self):
        self.mapTool.reset()
        if self.previousMapTool is not None:
            self.iface.mapCanvas().setMapTool(self.previousMapTool)

    def mapToolSet(self, mapTool):
        return
        #TODO: unstable
        if mapTool != self.mapTool and self.currentPage is not None:
            if self.currentPage.pageType == ppages.PAGE_DEM and self.currentPage.isPrimary:
                self.currentPage.toolButton_PointTool.setVisible(True)

    def createRubberBands(self, baseExtent, quadtree):
        self.clearRubberBands()
        # create quads with rubber band
        self.rb_quads = QgsRubberBand(self.iface.mapCanvas(), QGis.Line)
        self.rb_quads.setColor(Qt.blue)
        self.rb_quads.setWidth(1)

        quads = quadtree.quads()
        for quad in quads:
            geom = baseExtent.subrectangle(quad.rect).geometry()
            self.rb_quads.addGeometry(geom, None)
        self.log("Quad count: %d" % len(quads))

        if not quadtree.focusRect:
            return

        # create a point with rubber band
        if quadtree.focusRect.width() == 0 or quadtree.focusRect.height() == 0:
            npt = quadtree.focusRect.center()
            self.rb_point = QgsRubberBand(self.iface.mapCanvas(), QGis.Point)
            self.rb_point.setColor(Qt.red)
            self.rb_point.addPoint(baseExtent.point(npt))

    def clearRubberBands(self):
        # clear quads and point
        if self.rb_quads:
            self.iface.mapCanvas().scene().removeItem(self.rb_quads)
            self.rb_quads = None
        if self.rb_point:
            self.iface.mapCanvas().scene().removeItem(self.rb_point)
            self.rb_point = None

    def browseClicked(self):
        directory = os.path.split(self.ui.lineEdit_OutputFilename.text())[0]
        if not directory:
            directory = QDir.homePath()
        filename = QFileDialog.getSaveFileName(
            self,
            self.tr("Output filename"),
            directory,
            "HTML file (*.html *.htm)",
            options=QFileDialog.DontConfirmOverwrite)
        if not filename:
            return

        # append .html extension if filename doesn't have either .html or .htm
        if filename[-5:].lower() != ".html" and filename[-4:].lower(
        ) != ".htm":
            filename += ".html"

        self.ui.lineEdit_OutputFilename.setText(filename)

    def log(self, msg):
        if debug_mode:
            qDebug(msg)
Example #42
0
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)