Example #1
0
class MapTool(QgsMapTool):
    geometry_changed = pyqtSignal(QgsGeometry, bool)
    tool_deactivated = pyqtSignal()

    def __init__(self, canvas, cursorstyle=Qt.CrossCursor):
        self.canvas = canvas
        QgsMapTool.__init__(self, canvas)
        self.caller = self.sender()
        self.cursorStyle = cursorstyle
        self.active = False
        # get selection color
        selcolor = self.canvas.selectionColor()
        mycolor = QColor(selcolor.red(), selcolor.green(), selcolor.blue(), 40)
        self.rb = QgsRubberBand(self.canvas)
        self.rb.setStrokeColor(QColor(255, 0, 0, 40))
        self.rb.setFillColor(mycolor)
        self.rb.setLineStyle(Qt.PenStyle(Qt.SolidLine))
        self.rb.setWidth(2)

    def setCursorStyle(self):
        cursor = QCursor()
        cursor.setShape(self.cursorStyle)
        self.setCursor(cursor)

    def activate(self):
        self.caller.setChecked(True)
        self.setCursorStyle()

    def deactivate(self):
        self.canvas.scene().removeItem(self.rb)
        self.tool_deactivated.emit()
        self.caller.setChecked(False)
        QgsMapTool.deactivate(self)

    def setGeometry(self, geo):
        self.rb.setToGeometry(geo)

    def canvasReleaseEvent(self, mouseEvent):
        if mouseEvent.button() == Qt.LeftButton:
            if not self.active:
                self.active = True
                self.geometry_changed.emit(QgsGeometry(), False)
                self.rb.reset(QgsWkbTypes.LineGeometry)
            self.rb.addPoint(mouseEvent.mapPoint())
            if self.rb.numberOfVertices() > 1:
                self.geometry_changed.emit(self.rb.asGeometry(), False)
        elif mouseEvent.button() == Qt.RightButton:
            if self.rb.numberOfVertices() > 2:
                self.active = False
                self.rb.removeLastPoint()
                geo = self.rb.asGeometry()
                self.geometry_changed.emit(geo, True)
            else:
                self.rb.reset(QgsWkbTypes.LineGeometry)

    def canvasMoveEvent(self, mouseEvent):
        if self.rb.numberOfVertices() > 1 and self.active:
            self.rb.removeLastPoint()
            self.rb.addPoint(mouseEvent.mapPoint())
        pass
class DrawPolygonMapTool(QgsMapTool):

    polygonSelected = pyqtSignal(object)

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

        self.canvas = canvas
        self.extent = None
        self.rubberBand = QgsRubberBand(self.canvas,
                                        QgsWkbTypes.PolygonGeometry)
        self.rubberBand.setFillColor(RB_FILL)
        self.rubberBand.setStrokeColor(RB_STROKE)
        self.rubberBand.setWidth(1)
        self.vertex_count = 1  # two points are dropped initially

    def canvasReleaseEvent(self, event):
        if event.button() == Qt.RightButton:
            if self.rubberBand is None:
                return
            # TODO: validate geom before firing signal
            self.extent.removeDuplicateNodes()
            self.polygonSelected.emit(self.extent)
            self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
            del self.rubberBand
            self.rubberBand = None
            self.vertex_count = 1  # two points are dropped initially
            return
        elif event.button() == Qt.LeftButton:
            if self.rubberBand is None:
                self.rubberBand = QgsRubberBand(self.canvas,
                                                QgsWkbTypes.PolygonGeometry)
                self.rubberBand.setFillColor(RB_FILL)
                self.rubberBand.setStrokeColor(RB_STROKE)
                self.rubberBand.setWidth(1)
            self.rubberBand.addPoint(event.mapPoint())
            self.extent = self.rubberBand.asGeometry()
            self.vertex_count += 1

    def canvasMoveEvent(self, event):
        if self.rubberBand is None:
            pass
        elif not self.rubberBand.numberOfVertices():
            pass
        elif self.rubberBand.numberOfVertices() == self.vertex_count:
            if self.vertex_count == 2:
                mouse_vertex = self.rubberBand.numberOfVertices() - 1
                self.rubberBand.movePoint(mouse_vertex, event.mapPoint())
            else:
                self.rubberBand.addPoint(event.mapPoint())
        else:
            mouse_vertex = self.rubberBand.numberOfVertices() - 1
            self.rubberBand.movePoint(mouse_vertex, event.mapPoint())

    def deactivate(self):
        QgsMapTool.deactivate(self)
        if self.rubberBand is not None:
            self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
        self.deactivated.emit()
class MapTool(QgsMapTool):
    geometry_changed = pyqtSignal(QgsGeometry,bool)
    tool_deactivated =  pyqtSignal()

    def __init__(self, canvas, cursorstyle = Qt.CrossCursor):
        self.canvas = canvas
        QgsMapTool.__init__(self, canvas)
        self.caller = self.sender()
        self.cursorStyle=cursorstyle
        self.active = False
        self.center=None
        # get selection color
        selcolor =self.canvas.selectionColor()
        mycolor = QColor(selcolor.red(), selcolor.green(), selcolor.blue(), 40)
        self.rb = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
        self.rb.setStrokeColor(QColor(255, 0, 0, 40))
        self.rb.setFillColor(mycolor)
        self.rb.setLineStyle(Qt.PenStyle(Qt.SolidLine))
        self.rb.setWidth(2)

    def setCursorStyle(self):
        cursor = QCursor()
        cursor.setShape(self.cursorStyle)
        self.setCursor(cursor)

    def activate(self):
        self.caller.setChecked(True)
        self.setCursorStyle()

    def deactivate(self):
        self.canvas.scene().removeItem(self.rb)
        self.tool_deactivated.emit()
        self.caller.setChecked(False)
        QgsMapTool.deactivate(self)

    def setGeometry(self,geo):
        self.rb.setToGeometry(geo)

    def canvasReleaseEvent(self, mouseevent):
        if mouseevent.button() == Qt.LeftButton:
            self.active = False
            self.geometry_changed.emit(self.rb.asGeometry(),True)
        pass

    def canvasMoveEvent(self, mouseEvent):
        if self.active:
            cp = self.toMapCoordinates(mouseEvent.pos())
            rec = QgsRectangle(self.center, cp)
            self.rb.setToGeometry(QgsGeometry.fromRect(rec))
            self.geometry_changed.emit(self.rb.asGeometry(),True)
        pass

    def canvasPressEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.active = True
            self.center = self.toMapCoordinates(e.pos())
            self.geometry_changed.emit(QgsGeometry(),False)
            self.rb.reset()
        pass
Example #4
0
 def crearNuevoRubberLinea(self):
     nuevoRubber = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
     nuevoRubber.setFillColor(QColor(0,0,0,0))
     nuevoRubber.setStrokeColor(QColor(0,62,240,255))
     nuevoRubber.setWidth(2)
     #penStyle = Qt.PenStyle()
     nuevoRubber.setLineStyle(Qt.PenStyle(3))
     return nuevoRubber
 def addGraphic(self, geom):
     canvas = self.iface.mapCanvas()
     rBand = QgsRubberBand(canvas, True)
     self.graphics.append(rBand)
     rBand.setToGeometry(geom, None)
     rBand.setColor(QColor(0, 0, 255, 70))
     rBand.setStrokeColor(QColor(0, 0, 250, 220))
     rBand.setWidth(3)
class LineTool(QgsMapToolEmitPoint):
    """Line Map tool to capture mapped lines."""
    def __init__(self, canvas):
        """
        :param canvas: current map canvas
        :type canvas: QgsMapCanvas
        """
        self.canvas = canvas
        QgsMapToolEmitPoint.__init__(self, self.canvas)

        self.rubberBand = QgsRubberBand(self.canvas, False)
        self.rubberBand.setStrokeColor(QColor(DEFAULT_COLOR))
        self.rubberBand.setWidth(3)

        self.crsSrc = self.canvas.mapSettings().destinationCrs()
        self.previous_point = None
        self.points = []
        self.reset()

    def reset(self):
        """reset rubber band and captured points."""

        self.points = []
        self.rubberBand.reset(QgsWkbTypes.LineGeometry)

    pointDrawn = pyqtSignal(["QgsPointXY", "int"])

    def canvasReleaseEvent(self, e):
        """Add marker to canvas and shows line."""
        new_point = self.toMapCoordinates(e.pos())
        self.points.append(new_point)

        # noinspection PyUnresolvedReferences
        self.pointDrawn.emit(new_point, self.points.index(new_point))
        self.showLine()

    def showLine(self):
        """Builds rubber band from all points and adds it to the map canvas."""
        self.rubberBand.reset(QgsWkbTypes.LineGeometry)
        for point in self.points:
            if point == self.points[-1]:
                self.rubberBand.addPoint(point, True)
            self.rubberBand.addPoint(point, False)
        self.rubberBand.show()

    doubleClicked = pyqtSignal()

    # noinspection PyUnusedLocal
    def canvasDoubleClickEvent(self, e):
        """Ends line drawing and deletes rubber band and markers from map canvas."""
        # noinspection PyUnresolvedReferences
        self.doubleClicked.emit()
        self.canvas.scene().removeItem(self.rubberBand)

    def deactivate(self):
        super(LineTool, self).deactivate()
        self.deactivated.emit()
Example #7
0
def init_rubberband(color, lineStyle, alphaF, width, bandType, canvas):
    """initiate the rubberbands"""
    rubberBand = QgsRubberBand(canvas, bandType)
    rubberBand.setStrokeColor(color)
    color.setAlpha(alphaF)
    rubberBand.setFillColor(color)
    rubberBand.setLineStyle(lineStyle)
    rubberBand.setWidth(width)
    return rubberBand
def init_rubberband(styleName, canvas, rbType):
    """initiate the rubberbands"""
    rbStyle = RBSTYLES[styleName]
    rubberBand = QgsRubberBand(canvas, getWKBType(rbType))
    rubberBand.setStrokeColor(rbStyle["strokecolor"])
    rbStyle["fillcolor"].setAlpha(rbStyle["alphaF"])
    rubberBand.setFillColor(rbStyle["fillcolor"])
    rubberBand.setLineStyle(rbStyle["linestyle"])
    rubberBand.setWidth(rbStyle["strokewidth"])
    return rubberBand
Example #9
0
        class PolyMapTool(QgsMapToolEmitPoint):
            """
            This class needs to go someplace else and not be some hacky inline crap.
            """
            def __init__(self, canvas):
                self.canvas = canvas
                QgsMapToolEmitPoint.__init__(self, self.canvas)
                self.rubberband = QgsRubberBand(self.canvas, True)
                self.rubberband.setStrokeColor(Qt.red)
                self.rubberband.setWidth(1)
                self.reset()

            def reset(self):
                self.startPoint = self.endPoint = None
                self.isEmittingPoint = False
                self.rubberband.reset(True)

            def canvasPressEvent(self, e):
                self.startPoint = self.toMapCoordinates(e.pos())
                self.endPoint = self.startPoint
                self.isEmittingPoint = True
                self.showRect(self.startPoint, self.endPoint)
            
            def canvasReleaseEvent(self, e):
                self.isEmittingPoint = False
                self.bbox = [(self.startPoint.x(), self.startPoint.y()),
                             (self.startPoint.x(), self.endPoint.y()),
                             (self.endPoint.x(), self.endPoint.y()),
                             (self.endPoint.x(), self.startPoint.y()),
                             (self.startPoint.x(), self.startPoint.y())]

            def canvasMoveEvent(self, e):
                if not self.isEmittingPoint:
                    return
                self.endPoint = self.toMapCoordinates(e.pos())
                self.showRect(self.startPoint, self.endPoint)

            def showRect(self, startPoint, endPoint):
                self.rubberband.reset(QgsWkbTypes.PolygonGeometry)
                if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
                    return
                
                point1 = QgsPointXY(startPoint.x(), startPoint.y())
                point2 = QgsPointXY(startPoint.x(), endPoint.y())
                point3 = QgsPointXY(endPoint.x(), endPoint.y())
                point4 = QgsPointXY(endPoint.x(), startPoint.y())

                self.rubberband.addPoint(point1, False)
                self.rubberband.addPoint(point2, False)
                self.rubberband.addPoint(point3, False)
                self.rubberband.addPoint(point4, True) # Updates the canvas
                self.rubberband.show()
Example #10
0
class MapTool(QgsMapToolIdentify):
    geometry_changed = pyqtSignal(QgsGeometry, bool)
    tool_deactivated = pyqtSignal()

    def __init__(self, canvas, cursorstyle=Qt.CrossCursor):
        self.canvas = canvas
        QgsMapTool.__init__(self, canvas)
        self.caller = self.sender()
        self.cursorStyle = cursorstyle
        self.active = False
        # get selection color
        selcolor = self.canvas.selectionColor()
        mycolor = QColor(selcolor.red(), selcolor.green(), selcolor.blue(), 40)
        self.rb = QgsRubberBand(self.canvas)
        self.rb.setStrokeColor(QColor(255, 0, 0, 40))
        self.rb.setFillColor(mycolor)
        self.rb.setLineStyle(Qt.PenStyle(Qt.SolidLine))
        self.rb.setWidth(2)
        self.center = None
        self.cercle = 120  #circle of 30 segments

    def setCursorStyle(self):
        cursor = QCursor()
        cursor.setShape(self.cursorStyle)
        self.setCursor(cursor)

    def activate(self):
        self.caller.setChecked(True)
        self.setCursorStyle()

    def deactivate(self):
        self.canvas.scene().removeItem(self.rb)
        self.tool_deactivated.emit()
        self.caller.setChecked(False)
        QgsMapTool.deactivate(self)

    def setGeometry(self, geo):
        self.rb.setToGeometry(geo)

    def canvasReleaseEvent(self, mouseEvent):
        self.geometry_changed.emit(QgsGeometry(), False)
        if mouseEvent.button() == Qt.LeftButton:
            results = self.identify(mouseEvent.x(), mouseEvent.y(),
                                    self.ActiveLayer, self.VectorLayer)
            for res in results:
                if res.mFeature and res.mLayer:
                    geo = res.mFeature.geometry()
                    self.rb.setToGeometry(geo)
                    self.geometry_changed.emit(geo, True)
                    break
        pass
Example #11
0
 def crearNuevoRubberPoly(self):
     nuevoRubber = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
     
     r = randint(0, 255)
     g = randint(0, 255)
     b = randint(0, 255)
     
     color = QColor(r,g,b,36)
     colorAg = QColor(r,g,b,87)
     self.pluginM.listaColores.append(colorAg)
     nuevoRubber.setFillColor(color)
     nuevoRubber.setStrokeColor(QColor(r,g,b,255))
     nuevoRubber.setWidth(2)
     return nuevoRubber
Example #12
0
class GeometryDisplayer:
    def __init__(self, canvas):
        self.canvas = canvas

        # main rubber
        self.rubber1 = QgsRubberBand(self.canvas)
        self.rubber1.setWidth(2)
        self.rubber1.setStrokeColor(self.newGeometryColor())
        self.rubber1.setFillColor(self.newGeometryColor())

        # old geometry rubber
        self.rubber2 = QgsRubberBand(self.canvas)
        self.rubber2.setWidth(2)
        self.rubber2.setStrokeColor(self.oldGeometryColor())
        self.rubber2.setFillColor(self.oldGeometryColor())

    def reset(self):
        self.rubber1.reset()
        self.rubber2.reset()

    def oldGeometryColor(self):
        return QColor("#ff5733")

    def newGeometryColor(self):
        return QColor("#00f")

    def display(self, geom1, geom2=None):
        """
        @param geom1 base geometry (old geometry for an update)
        @param geom2 new geometry for an update
        """
        if geom2 is None:
            bbox = geom1.boundingBox()
            self.rubber1.setToGeometry(geom1, None)
        else:
            bbox = geom1.boundingBox()
            bbox.combineExtentWith(geom2.boundingBox())
            self.rubber1.setToGeometry(geom2, None)
            self.rubber2.setToGeometry(geom1, None)
        bbox.scale(1.5)
        self.canvas.setExtent(bbox)
class NavigationPanel(BASE, WIDGET):

    FIXED_WIDTH = 200
    WARNING_DISTANCE = 200

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setStyleSheet("background-color: #333f4f;")
        self.textBrowser.setStyleSheet("background-color: #adb9ca;")
        self.listWaypoints.setStyleSheet("background-color: #adb9ca;")
        self.iface = KadasPluginInterface.cast(iface)
        self.gpsConnection = None
        self.listWaypoints.setSelectionMode(QListWidget.SingleSelection)
        self.listWaypoints.currentItemChanged.connect(
            self.selectedWaypointChanged)
        self.listWaypoints.setSpacing(5)
        self.waypointWidgets = []
        self.optimalRoutesCache = {}

        self.rubberband = QgsRubberBand(iface.mapCanvas(),
                                        QgsWkbTypes.LineGeometry)
        self.rubberband.setStrokeColor(QColor(150, 0, 0))
        self.rubberband.setWidth(2)

        self.chkShowWarnings.setChecked(True)
        self.warningShown = False
        self.iface.messageBar().widgetRemoved.connect(self.setWarningShownOff)
        self.labelConfigureWarnings.linkActivated.connect(
            self.configureWarnings)

    def configureWarnings(self, url):
        threshold = QSettings().value("kadasrouting/warningThreshold",
                                      self.WARNING_DISTANCE,
                                      type=int)
        value, ok = QInputDialog.getInt(
            self.iface.mainWindow(), self.tr("Navigation"),
            self.tr("Set threshold for warnings (meters)"), threshold)
        if ok:
            QSettings().setValue("kadasrouting/warningThreshold", value)

    def setWarningShownOff(self):
        self.warningShown = False

    def show(self):
        super().show()
        self.startNavigation()

    def hide(self):
        super().hide()
        self.stopNavigation()

    def updateNavigationInfo(self, gpsinfo):
        # prevent infinite loop for mocked gps
        self.gpsConnection.statusChanged.disconnect(self.updateNavigationInfo)
        self.currentGpsInformation = gpsinfo
        if gpsinfo is None:
            self.setMessage(self.tr("Cannot connect to GPS"))
            return
        layer = self.iface.activeLayer()
        point = QgsPointXY(gpsinfo.longitude, gpsinfo.latitude)
        origCrs = QgsCoordinateReferenceSystem(4326)
        canvasCrs = self.iface.mapCanvas().mapSettings().destinationCrs()
        transform = QgsCoordinateTransform(origCrs, canvasCrs,
                                           QgsProject.instance())
        canvasPoint = transform.transform(point)
        self.centerPin.setPosition(KadasItemPos(point.x(), point.y()))
        self.iface.mapCanvas().setCenter(canvasPoint)
        self.iface.mapCanvas().setRotation(-gpsinfo.direction)
        self.iface.mapCanvas().refresh()
        self.rubberband.reset(QgsWkbTypes.LineGeometry)

        self.gpsConnection.statusChanged.connect(self.updateNavigationInfo)

        if isinstance(layer, QgsVectorLayer) and layer.geometryType(
        ) == QgsWkbTypes.LineGeometry:
            feature = next(layer.getFeatures(), None)
            if feature:
                geom = feature.geometry()
                layer = self.getOptimalRouteLayerForGeometry(geom)
                if layer is not None:
                    rubbergeom = QgsGeometry(layer.geom)
                    rubbergeom.transform(transform)
                    self.rubberband.setToGeometry(rubbergeom)

        if isinstance(layer, OptimalRouteLayer) and layer.hasRoute():
            try:
                maneuver = layer.maneuverForPoint(point, gpsinfo.speed)
                LOG.debug(maneuver)
            except NotInRouteException:
                self.setMessage(self.tr("You are not in the route"))
                return
            self.setWidgetsVisibility(False)
            html = route_html_template.format(**maneuver)
            self.textBrowser.setHtml(html)
            self.textBrowser.setFixedHeight(
                self.textBrowser.document().size().height())
            self.setWarnings(maneuver["raw_distleft"])
        elif isinstance(layer, KadasItemLayer):
            waypoints = self.waypointsFromLayer(layer)
            if waypoints:
                if self.waypointLayer is None:
                    self.waypointLayer = layer
                    self.populateWaypoints(waypoints)
                else:
                    self.updateWaypoints()
                waypointItem = self.listWaypoints.currentItem(
                ) or self.listWaypoints.item(0)
                waypoint = waypointItem.point
                instructions = getInstructionsToWaypoint(waypoint, gpsinfo)
                self.setCompass(instructions["heading"],
                                instructions["wpangle"])
                html = waypoint_html_template.format(**instructions)
                self.textBrowser.setHtml(html)
                self.textBrowser.setFixedHeight(
                    self.textBrowser.document().size().height())
                self.labelWaypointName.setText(
                    waypoint_name_html_template.format(name=waypointItem.name))
                self.setWidgetsVisibility(True)
                self.setWarnings(instructions["raw_distleft"])
            else:
                self.setMessage(
                    self.tr("Select a route or waypoint layer for navigation"))
        else:
            self.setMessage(
                self.tr("Select a route or waypoint layer for navigation"))

    def setWarnings(self, dist):
        threshold = QSettings().value("kadasrouting/warningThreshold",
                                      self.WARNING_DISTANCE,
                                      type=int)
        if (self.chkShowWarnings.isChecked() and not self.warningShown
                and dist < threshold):
            pushMessage(
                self.tr("In {dist} meters you will arrive at your destination"
                        ).format(dist=int(dist)))
            self.warningShown = True

    def getOptimalRouteLayerForGeometry(self, geom):
        wkt = geom.asWkt()
        if wkt in self.optimalRoutesCache:
            return self.optimalRoutesCache[wkt]

        name = self.iface.activeLayer().name()
        value, ok = QInputDialog.getItem(
            self.iface.mainWindow(), self.tr("Navigation"),
            self.tr("Select Vehicle to use with layer '{name}'").format(
                name=name), vehicles.vehicle_reduced_names())
        if ok:
            profile, costingOptions = vehicles.options_for_vehicle_reduced(
                vehicles.vehicle_reduced_names().index(value))
            layer = OptimalRouteLayer("")
            try:
                if geom.isMultipart():
                    polyline = geom.asMultiPolyline()
                    line = polyline[0]
                else:
                    line = geom.asPolyline()
                layer.updateFromPolyline(line, profile, costingOptions)
                self.optimalRoutesCache[wkt] = layer
                return layer
            except Exception:
                return

    def setCompass(self, heading, wpangle):
        compassPixmap = QPixmap(iconPath("compass.png"))
        compassPixmap = compassPixmap.scaledToWidth(self.FIXED_WIDTH)
        bearingPixmap = QPixmap(iconPath("direction.png"))
        pixmap = QPixmap(self.FIXED_WIDTH, self.FIXED_WIDTH)
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        transform = QTransform()
        transform.translate(self.FIXED_WIDTH / 2, self.FIXED_WIDTH / 2)
        transform.rotate(-heading)
        transform.translate(-self.FIXED_WIDTH / 2, -self.FIXED_WIDTH / 2)
        painter.setTransform(transform)
        painter.drawPixmap(0, 0, self.FIXED_WIDTH, self.FIXED_WIDTH,
                           compassPixmap)
        transform = QTransform()
        transform.translate(self.FIXED_WIDTH / 2, self.FIXED_WIDTH / 2)
        transform.rotate(wpangle - heading)
        transform.translate(-self.FIXED_WIDTH / 2, -self.FIXED_WIDTH / 2)
        painter.setTransform(transform)
        painter.drawPixmap(0, 0, self.FIXED_WIDTH, self.FIXED_WIDTH,
                           bearingPixmap)
        painter.end()
        self.labelCompass.setPixmap(pixmap)
        self.labelCompass.resize(QSize(self.FIXED_WIDTH, self.FIXED_WIDTH))

    def updateWaypoints(self):
        for item, w in self.waypointWidgets:
            w.setWaypointText(self.currentGpsInformation)

    def selectedWaypointChanged(self, current, previous):
        for item, w in self.waypointWidgets:
            w.setIsItemSelected(current == item)
        self.warningShown = False
        self.updateNavigationInfo(self.currentGpsInformation)

    def waypointsFromLayer(self, layer):
        try:
            '''
            center = iface.mapCanvas().center()
            outCrs = QgsCoordinateReferenceSystem(4326)
            canvasCrs = iface.mapCanvas().mapSettings().destinationCrs()
            transform = QgsCoordinateTransform(canvasCrs, outCrs, QgsProject.instance())
            wgspoint = transform.transform(center)
            item = KadasGpxWaypointItem()
            item.addPartFromGeometry(QgsPoint(wgspoint.x() + 10, wgspoint.y() + 10))
            item.setName("Test Waypoint")
            item2 = KadasGpxWaypointItem()
            item2.addPartFromGeometry(QgsPoint(wgspoint.x() + 10, wgspoint.y() + 10))
            item2.setName("Another Waypoint")
            item3 = KadasGpxWaypointItem()
            item3.addPartFromGeometry(QgsPoint(wgspoint.x() + 10, wgspoint.y() + 10))
            item3.setName("My Waypoint")
            return [item, item2, item3]
            '''
            return [
                item for item in layer.items()
                if isinstance(item, KadasGpxWaypointItem)
            ]
        except Exception as e:
            LOG.warning(e)
            return []

    def populateWaypoints(self, waypoints):
        self.listWaypoints.clear()
        self.waypointWidgets = []
        for waypoint in waypoints:
            item = WaypointItem(waypoint)
            widget = WaypointItemWidget(waypoint, self.currentGpsInformation)
            self.listWaypoints.addItem(item)
            item.setSizeHint(widget.sizeHint())
            self.listWaypoints.setItemWidget(item, widget)
            self.waypointWidgets.append((item, widget))
        self.selectedWaypointChanged(self.listWaypoints.item(0), None)

    def setMessage(self, text):
        self.setWidgetsVisibility(False)
        self.textBrowser.setHtml(message_html_template.format(text=text))
        self.textBrowser.setFixedHeight(self.height())

    def setWidgetsVisibility(self, iswaypoints):
        self.labelWaypointName.setVisible(iswaypoints)
        self.labelCompass.setVisible(iswaypoints)
        self.listWaypoints.setVisible(iswaypoints)
        self.labelWaypoints.setVisible(False)  # iswaypoints)
        color = "#adb9ca" if iswaypoints else "#333f4f"
        self.textBrowser.setStyleSheet("background-color: {};".format(color))

    def startNavigation(self):
        self.centerPin = None
        self.waypointLayer = None
        self.currentGpsInformation = None
        self.warningShown = False

        self.setMessage(self.tr("Connecting to GPS..."))
        self.gpsConnection = getMockupGpsConnection()
        if self.gpsConnection is None:
            self.setMessage(self.tr("Cannot connect to GPS"))
        else:
            self.gpsConnection.statusChanged.connect(self.updateNavigationInfo)
            self.centerPin = KadasPinItem(QgsCoordinateReferenceSystem(4326))
            self.centerPin.setup(
                iconPath("navigationcenter.svg"),
                self.centerPin.anchorX(),
                self.centerPin.anchorX(),
                32,
                32,
            )

            KadasMapCanvasItemManager.addItem(self.centerPin)
            self.updateNavigationInfo(
                self.gpsConnection.currentGPSInformation())
        self.iface.layerTreeView().currentLayerChanged.connect(
            self.currentLayerChanged)

    def currentLayerChanged(self, layer):
        self.waypointLayer = None
        self.warningShown = False
        self.updateNavigationInfo(self.currentGpsInformation)

    def stopNavigation(self):
        # Disconnect everything
        if self.gpsConnection is not None:
            try:
                self.gpsConnection.statusChanged.disconnect(
                    self.updateNavigationInfo)
            except TypeError as e:
                LOG.debug(e)
        try:
            if self.centerPin is not None:
                KadasMapCanvasItemManager.removeItem(self.centerPin)
        except Exception:
            # centerPin might have been deleted
            pass
        try:
            self.iface.layerTreeView().currentLayerChanged.disconnect(
                self.currentLayerChanged)
        except TypeError as e:
            LOG.debug(e)
        # Finally, reset everything
        self.rubberband.reset(QgsWkbTypes.LineGeometry)
        self.iface.mapCanvas().setRotation(0)
        self.iface.mapCanvas().refresh()
Example #14
0
class MapTool(QgsMapTool):
    MODE_NONE = 0
    MODE_PAN = 1
    MODE_ROTATE = 2
    MODE_SCALE = 3
    MODE_SCALE_X = 4
    MODE_SCALE_Y = 5
    MODE_PAN_RESULT = 6
    MODE_NODE = 7
    NODE_NAMES = ['A', 'B', 'C', 'D']

    def __init__(self, widget):
        QgsMapTool.__init__(self, widget.canvas)
        self.widget = widget
        self.canvas = widget.canvas
        self.mode = self.MODE_NONE
        self.selected_node = None

        # clicked point
        self.p0 = None

        # centre rectangle
        self.pX = None

        # rectangle vertices (handles)
        self.pA = None  # hg
        self.pB = None  # hd
        self.pC = None  # bd
        self.pD = None  # bg
        self.zoneWidth = None
        self.zoneDepth = None

        # eye (rotation)
        self.pY = None

        # rectangle
        self.rb = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
        self.rb.setStrokeColor(Qt.blue)
        self.rb.setWidth(3)

        self.rbFoc = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.rbFoc.setStrokeColor(Qt.blue)
        self.rbFoc.setWidth(1)

        # SCALE nodes
        self.rbPA = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rbPA.setColor(Qt.red)
        self.rbPA.setWidth(8)
        self.rbPB = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rbPB.setColor(Qt.red)
        self.rbPB.setWidth(8)
        self.rbPC = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rbPC.setColor(Qt.red)
        self.rbPC.setWidth(8)
        self.rbPD = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rbPD.setColor(QColor(255, 50, 150, 255))
        self.rbPD.setWidth(8)
        # scale Y node
        self.rbPH = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rbPH.setColor(Qt.red)
        self.rbPH.setWidth(8)
        # scale X node
        self.rbPL = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rbPL.setColor(Qt.red)
        self.rbPL.setWidth(8)

        # final pan
        self.rbPan = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rbPan.setColor(QColor(0, 200, 50, 255))
        self.rbPan.setWidth(8)

        # ROTATE node
        self.rbPY = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rbPY.setColor(Qt.blue)
        self.rbPY.setWidth(6)
        self.rotation = 0.0

        # cutting lines
        self.rbLines = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        self.rbLines.setColor(QColor(40, 180, 30, 255))
        self.rbLines.setWidth(1.5)

        # plots
        self.rbPlots = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
        self.rbPlots.setStrokeColor(QColor(200, 120, 70, 150))
        self.rbPlots.setWidth(0.8)

        self.rubbers = [
            self.rb,
            self.rbPA,
            self.rbPB,
            self.rbPC,
            self.rbPD,
            self.rbPY,
            self.rbPH,
            self.rbPL,
            self.rbPan,
            self.rbLines,
            self.rbPlots,
            self.rbFoc,
        ]

        self.rowLines = None
        self.columnLines = None
        self.allLines = None

    def hide(self):
        for rb in self.rubbers:
            rb.reset()

    def updateRubberGeom(self):
        if self.pA is None:
            return

        self.zoneWidth = self.pA.distance(self.pB)
        self.zoneDepth = self.pA.distance(self.pD)
        self.pM = QgsPointXY((self.pC.x() + self.pD.x()) / 2,
                             (self.pC.y() + self.pD.y()) / 2)
        self.d0 = self.pM.distance(self.pY)
        # self.widget.updateZ(self.pY)

        self.rb.setToGeometry(
            QgsGeometry.fromPolygonXY(
                [[self.pD, self.pA, self.pB, self.pC, self.pD]]))
        self.rbFoc.setToGeometry(
            QgsGeometry.fromPolylineXY([self.pD, self.pY, self.pC]))

        for p, rb in [
            [self.pA, self.rbPA],
            [self.pB, self.rbPB],
            [self.pC, self.rbPC],
            [self.pD, self.rbPD],
            [self.pY, self.rbPY],
            [self.pH, self.rbPH],
            [self.pL, self.rbPL],
        ]:
            rb.setToGeometry(QgsGeometry.fromPointXY(p))

        leftEdge = (QgsGeometry.fromPolylineXY([
            self.pA, self.pD
        ]).densifyByCount(self.widget.rowCount.value() - 1).asPolyline())
        rightEdge = (QgsGeometry.fromPolylineXY([
            self.pB, self.pC
        ]).densifyByCount(self.widget.rowCount.value() - 1).asPolyline())

        # Plot edges lines
        polyline = list(zip(leftEdge, rightEdge))

        backSide = (QgsGeometry.fromPolylineXY([
            self.pA, self.pB
        ]).densifyByCount(self.widget.columnCount.value() - 1).asPolyline())
        frontSide = (QgsGeometry.fromPolylineXY([
            self.pD, self.pC
        ]).densifyByCount(self.widget.columnCount.value() - 1).asPolyline())
        polylineX = list(zip(frontSide[:], backSide[:]))

        self.finalWidth = self.zoneWidth

        self.rowLines = polyline
        self.columnLines = polylineX
        self.allLines = polyline + polylineX
        self.rbLines.setToGeometry(
            QgsGeometry.fromMultiPolylineXY(
                polylineX + polyline + polyline[::max(1, 1 + len(polyline))]))
        if self.widget.cbReverseRows.isChecked():
            if self.widget.cbReverseColumns.isChecked():
                self.rbPC.setColor(Qt.red)
                self.rbPD.setColor(Qt.red)
                self.rbPA.setColor(Qt.red)
                self.rbPB.setColor(QColor(0, 200, 150, 255))
            else:
                self.rbPC.setColor(Qt.red)
                self.rbPD.setColor(Qt.red)
                self.rbPB.setColor(Qt.red)
                self.rbPA.setColor(QColor(0, 200, 150, 255))
        else:
            if self.widget.cbReverseColumns.isChecked():
                self.rbPA.setColor(Qt.red)
                self.rbPB.setColor(Qt.red)
                self.rbPD.setColor(Qt.red)
                self.rbPC.setColor(QColor(0, 200, 150, 255))
            else:
                self.rbPA.setColor(Qt.red)
                self.rbPB.setColor(Qt.red)
                self.rbPC.setColor(Qt.red)
                self.rbPD.setColor(QColor(0, 200, 150, 255))

        self.widget.alert.setText("Total plots: {}".format(
            self.widget.columnCount.value() * self.widget.rowCount.value()))

    def getLines(self):
        return QgsGeometry.fromMultiPolylineXY(self.rowLines)

    def getSampleLines(self):
        return (
            [self.rowLines[0], self.rowLines[1]] +
            self.rowLines[2:-1][::max(1, 1 +
                                      int((len(self.rowLines) - 3) / 9))] +
            [self.rowLines[-1]])

    def newRubber(self):
        if self.pX is not None:
            self.updateRubberGeom()
            return

        # default parameters
        h = 2 * self.widget.canvas.extent().height() / 3 / 20

        # first bbox, according to current view
        h = self.canvas.extent().height() / 6
        c = self.canvas.extent().center()
        rubberExtent = QgsRectangle(QgsPointXY(c.x() - h,
                                               c.y() - h),
                                    QgsPointXY(c.x() + h,
                                               c.y() + h))
        self.rotation = 0.0
        width = rubberExtent.xMaximum() - rubberExtent.xMinimum()
        height = rubberExtent.yMaximum() - rubberExtent.yMinimum()

        # centre rectangle
        self.pX = QgsPointXY(rubberExtent.xMinimum() + width / 2,
                             rubberExtent.yMinimum() + height / 2)

        self.pA = QgsPointXY(rubberExtent.xMinimum(), rubberExtent.yMaximum())
        self.pB = QgsPointXY(rubberExtent.xMaximum(), rubberExtent.yMaximum())
        self.pC = QgsPointXY(rubberExtent.xMaximum(), rubberExtent.yMinimum())
        self.pD = QgsPointXY(rubberExtent.xMinimum(), rubberExtent.yMinimum())

        # handles H / L
        self.pH = QgsPointXY((self.pA.x() + self.pB.x()) / 2,
                             (self.pA.y() + self.pB.y()) / 2)
        self.pL = QgsPointXY((self.pB.x() + self.pC.x()) / 2,
                             (self.pB.y() + self.pC.y()) / 2)

        # eye (rotation)
        self.pY = QgsPointXY(self.pX.x(), self.pX.y() - 2 * height / 3)

        self.pM = QgsPointXY((self.pC.x() + self.pD.x()) / 2,
                             (self.pC.y() + self.pD.y()) / 2)

        self.rotation_init = self.rotation
        self.pA_init = QgsPointXY(self.pA)
        self.pB_init = QgsPointXY(self.pB)
        self.pC_init = QgsPointXY(self.pC)
        self.pD_init = QgsPointXY(self.pD)
        self.pX_init = QgsPointXY(self.pX)
        self.pY_init = QgsPointXY(self.pY)
        self.pH_init = QgsPointXY(self.pH)
        self.pL_init = QgsPointXY(self.pL)

        self.updateRubberGeom()

    def canvasPressEvent(self, event):
        x = event.pos().x()
        y = event.pos().y()
        self.p0 = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)

        distPA = self.p0.distance(self.pA) / self.canvas.mapUnitsPerPixel()
        distPB = self.p0.distance(self.pB) / self.canvas.mapUnitsPerPixel()
        distPC = self.p0.distance(self.pC) / self.canvas.mapUnitsPerPixel()
        distPD = self.p0.distance(self.pD) / self.canvas.mapUnitsPerPixel()
        distPY = self.p0.distance(self.pY) / self.canvas.mapUnitsPerPixel()
        distPH = self.p0.distance(self.pH) / self.canvas.mapUnitsPerPixel()
        distPL = self.p0.distance(self.pL) / self.canvas.mapUnitsPerPixel()

        edit_individual_node = self.widget.cbIndividualNode.isChecked()

        if distPA < 6 or distPB < 6 or distPC < 6 or distPD < 6:
            if edit_individual_node:
                self.mode = self.MODE_NODE
                val, idx = min(
                    (val, idx)
                    for (idx,
                         val) in enumerate([distPA, distPB, distPC, distPD]))
                self.selected_node = self.NODE_NAMES[idx]
            else:
                self.mode = self.MODE_SCALE
            return

        if distPH < 6:
            self.mode = self.MODE_SCALE_Y
            return

        if distPL < 6:
            self.mode = self.MODE_SCALE_X
            return

        if distPY < 6:
            self.mode = self.MODE_ROTATE
            return

        if self.rb.asGeometry().contains(self.p0):
            self.mode = self.MODE_PAN
            return

    def canvasMoveEvent(self, event):
        if self.mode == self.MODE_NONE:
            return

        x = event.pos().x()
        y = event.pos().y()
        pt = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)
        dx = pt.x() - self.p0.x()
        dy = pt.y() - self.p0.y()

        # node name
        if self.mode == self.MODE_NODE:
            if self.selected_node == 'A':
                self.pA.setX(self.pA_init.x() + dx)
                self.pA.setY(self.pA_init.y() + dy)
            elif self.selected_node == 'B':
                self.pB.setX(self.pB_init.x() + dx)
                self.pB.setY(self.pB_init.y() + dy)
            elif self.selected_node == 'C':
                self.pC.setX(self.pC_init.x() + dx)
                self.pC.setY(self.pC_init.y() + dy)
            elif self.selected_node == 'D':
                self.pD.setX(self.pD_init.x() + dx)
                self.pD.setY(self.pD_init.y() + dy)

        # pan mode
        if self.mode == self.MODE_PAN:
            for p, p_ini in [
                [self.pA, self.pA_init],
                [self.pB, self.pB_init],
                [self.pC, self.pC_init],
                [self.pD, self.pD_init],
                [self.pX, self.pX_init],
                [self.pY, self.pY_init],
                [self.pH, self.pH_init],
                [self.pL, self.pL_init],
            ]:
                p.setX(p_ini.x() + dx)
                p.setY(p_ini.y() + dy)

        # horizontal + vertical sizing
        if self.mode == self.MODE_SCALE:
            d_old = self.pA_init.distance(self.pX_init)
            d_new = pt.distance(self.pX_init)
            dd = d_new / d_old

            for p, p_ini in [
                [self.pA, self.pA_init],
                [self.pB, self.pB_init],
                [self.pC, self.pC_init],
                [self.pD, self.pD_init],
                [self.pY, self.pY_init],
                [self.pH, self.pH_init],
                [self.pL, self.pL_init],
            ]:
                dx = dd * (p_ini.x() - self.pX.x())
                dy = dd * (p_ini.y() - self.pX.y())
                p.setX(self.pX.x() + dx)
                p.setY(self.pX.y() + dy)

        # horizontal sizing
        if self.mode == self.MODE_SCALE_X:
            d_old = self.pL_init.distance(self.pX_init)
            d_new = pt.distance(self.pX_init)
            dd = d_new / d_old
            if dd < 0.001:
                dd = 0.001

            dx = dd * (self.pL_init.x() - self.pX.x())
            dy = dd * (self.pL_init.y() - self.pX.y())
            self.pL.setX(self.pX.x() + dx)
            self.pL.setY(self.pX.y() + dy)

            centre = self.pH
            for p, p_ini in [[self.pA, self.pA_init], [self.pB, self.pB_init]]:
                dx = dd * (p_ini.x() - centre.x())
                dy = dd * (p_ini.y() - centre.y())
                p.setX(centre.x() + dx)
                p.setY(centre.y() + dy)

            centre = self.pM
            for p, p_ini in [[self.pC, self.pC_init], [self.pD, self.pD_init]]:
                dx = dd * (p_ini.x() - centre.x())
                dy = dd * (p_ini.y() - centre.y())
                p.setX(centre.x() + dx)
                p.setY(centre.y() + dy)

        # vertical sizing
        if self.mode == self.MODE_SCALE_Y:
            d_old = self.pH_init.distance(self.pX_init)
            d_new = pt.distance(self.pX_init)
            dd = d_new / d_old
            if dd < 0.001:
                dd = 0.001

            dx = dd * (self.pH_init.x() - self.pX.x())
            dy = dd * (self.pH_init.y() - self.pX.y())
            self.pH.setX(self.pX.x() + dx)
            self.pH.setY(self.pX.y() + dy)

            centre = self.pL
            for p, p_ini in [[self.pB, self.pB_init], [self.pC, self.pC_init]]:
                dx = dd * (p_ini.x() - centre.x())
                dy = dd * (p_ini.y() - centre.y())
                p.setX(centre.x() + dx)
                p.setY(centre.y() + dy)

            centre = QgsPointXY((self.pA.x() + self.pD.x()) / 2,
                                (self.pA.y() + self.pD.y()) / 2)
            for p, p_ini in [[self.pA, self.pA_init], [self.pD, self.pD_init]]:
                dx = dd * (p_ini.x() - centre.x())
                dy = dd * (p_ini.y() - centre.y())
                p.setX(centre.x() + dx)
                p.setY(centre.y() + dy)

        if self.mode == self.MODE_ROTATE:
            self.pY.setX(self.pY_init.x() + dx)
            self.pY.setY(self.pY_init.y() + dy)

            azimuth = self.pX.azimuth(pt)
            theta = azimuth - self.rotation_init + 180
            self.rotation = self.rotation_init + theta

            for a, i in [
                [self.pA, self.pA_init],
                [self.pB, self.pB_init],
                [self.pC, self.pC_init],
                [self.pD, self.pD_init],
                [self.pH, self.pH_init],
                [self.pL, self.pL_init],
            ]:
                A = QgsGeometry.fromPointXY(i)
                A.rotate(theta, self.pX)
                a.setX(A.asPoint().x())
                a.setY(A.asPoint().y())

        self.updateRubberGeom()

    def canvasReleaseEvent(self, event):
        self.pA_init = QgsPointXY(self.pA)
        self.pB_init = QgsPointXY(self.pB)
        self.pC_init = QgsPointXY(self.pC)
        self.pD_init = QgsPointXY(self.pD)
        self.pX_init = QgsPointXY(self.pX)
        self.pY_init = QgsPointXY(self.pY)
        self.pH_init = QgsPointXY(self.pH)
        self.pL_init = QgsPointXY(self.pL)
        self.rotation_init = self.rotation

        self.mode = self.MODE_NONE

    def activate(self):
        pass

    def deactivate(self):
        self.hide()

    def isZoomTool(self):
        return False

    def isTransient(self):
        return False

    def isEditTool(self):
        return True
Example #15
0
class PlanetMainFilters(MAIN_FILTERS_BASE, MAIN_FILTERS_WIDGET,
                        PlanetFilterMixin):

    leAOI: QLineEdit

    filtersChanged = pyqtSignal()
    zoomToAOIRequested = pyqtSignal()

    def __init__(self, iface, parent=None, plugin=None):
        super().__init__(parent=parent)
        self._iface: QgisInterface = iface
        self._plugin = plugin

        self.setupUi(self)

        self._aoi_box = QgsRubberBand(self._iface.mapCanvas(),
                                      QgsWkbTypes.PolygonGeometry)
        self._aoi_box.setFillColor(QColor(0, 0, 0, 0))
        self._aoi_box.setStrokeColor(MAIN_AOI_COLOR)
        self._aoi_box.setWidth(3)
        self._aoi_box.setLineStyle(Qt.DashLine)

        self._canvas: QgsMapCanvas = self._iface.mapCanvas()
        # This may later be a nullptr, if no active tool when queried
        self._cur_maptool = None

        # self._json_exporter = QgsJsonExporter()
        # self._json_exporter.setIncludeAttributes(False)

        # noinspection PyUnresolvedReferences
        self.leAOI.textChanged['QString'].connect(self.filters_changed)
        # noinspection PyUnresolvedReferences
        self.leAOI.textEdited['QString'].connect(self.validate_edited_aoi)

        self._setup_tool_buttons()

        # Extent line edit tools
        self.btnZoomToAOI.clicked.connect(self.zoom_to_aoi)
        self.btnCopyAOI.clicked.connect(self.copy_aoi_to_clipboard)
        self.btnLoadAOI.clicked.connect(self.load_aoi_from_file)

    def reset_aoi_box(self):
        if self._aoi_box:
            self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)

    def filters(self):

        # return and_filter(geom_filter(self.leAOI.text),
        #         date_range('acquired', gte=dateEditStart.text,
        #         lte=dateEditEnd.text))
        filters = []
        if self.leAOI.text():
            # TODO: Validate GeoJSON; try planet.api.utils.probably_geojson()
            # noinspection PyBroadException
            try:
                if qgsgeometry_from_geojson(self.leAOI.text()):
                    aoi = json.loads(self.leAOI.text())
                    filters.append(geom_filter(aoi))
                else:
                    self._show_message("AOI not valid GeoJSON polygon",
                                       level=Qgis.Warning,
                                       duration=10)
            except:
                self._show_message("AOI not valid JSON",
                                   level=Qgis.Warning,
                                   duration=10)
            finally:
                return filters

    def filters_as_json(self):
        filters = []
        if self.leAOI.text():
            filters.append(self.leAOI.text())

        return filters

    def load_filters(self, filter_json):
        pass

    def set_from_request(self, request):
        filters = self._filters_from_request(request, "geometry")
        if filters:
            geom = filters[0]["config"]
            txt = json.dumps(geom)
            self.leAOI.setText(txt)

    @pyqtSlot('QString')
    def filters_changed(self, value):
        # noinspection PyUnresolvedReferences
        self.filtersChanged.emit()

    @pyqtSlot()
    def clean_up(self):
        self.reset_aoi_box()

    def _setup_tool_buttons(self):
        extent_menu = QMenu(self)

        canvas_act = QAction('Current visible extent', extent_menu)
        # noinspection PyUnresolvedReferences
        canvas_act.triggered[bool].connect(self.aoi_from_current_extent)
        extent_menu.addAction(canvas_act)

        active_act = QAction('Active map layer extent', extent_menu)
        # noinspection PyUnresolvedReferences
        active_act.triggered[bool].connect(self.aoi_from_active_layer_extent)
        extent_menu.addAction(active_act)

        full_act = QAction('All map layers extent', extent_menu)
        # noinspection PyUnresolvedReferences
        full_act.triggered[bool].connect(self.aoi_from_full_extent)
        extent_menu.addAction(full_act)

        self.btnExtent.setMenu(extent_menu)

        # Also show menu on click, to keep disclosure triangle visible
        self.btnExtent.clicked.connect(self.btnExtent.showMenu)

        draw_menu = QMenu(self)

        box_act = QAction('Rectangle', draw_menu)
        # noinspection PyUnresolvedReferences
        box_act.triggered[bool].connect(self.aoi_from_box)
        draw_menu.addAction(box_act)

        circle_act = QAction('Circle', draw_menu)
        # noinspection PyUnresolvedReferences
        circle_act.triggered[bool].connect(self.aoi_from_circle)
        draw_menu.addAction(circle_act)

        polygon_act = QAction('Polygon', draw_menu)
        # noinspection PyUnresolvedReferences
        polygon_act.triggered[bool].connect(self.aoi_from_polygon)
        draw_menu.addAction(polygon_act)

        self.btnDraw.setMenu(draw_menu)
        # Also show menu on click, to keep disclosure triangle visible
        self.btnDraw.clicked.connect(self.btnDraw.showMenu)

        selection_menu = QMenu(self)

        self.single_select_act = QAction('Single feature', selection_menu)
        # noinspection PyUnresolvedReferences
        self.single_select_act.triggered[bool].connect(self.aoi_from_feature)
        selection_menu.addAction(self.single_select_act)

        self.bound_select_act = QAction('Multiple features (bounding box)',
                                        selection_menu)
        # noinspection PyUnresolvedReferences
        self.bound_select_act.triggered[bool].connect(self.aoi_from_bound)
        selection_menu.addAction(self.bound_select_act)

        self.btnSelection.setMenu(selection_menu)
        # Also show menu on click, to keep disclosure triangle visible
        self.btnSelection.clicked.connect(self._toggle_selection_tools)
        self.btnSelection.clicked.connect(self.btnSelection.showMenu)

    def _toggle_selection_tools(self):
        active_layer = self._iface.activeLayer()
        is_vector = isinstance(active_layer, QgsVectorLayer)
        if is_vector and active_layer.selectedFeatureCount():
            if active_layer.selectedFeatureCount() > 1:
                self.single_select_act.setEnabled(False)
                self.bound_select_act.setEnabled(True)
            elif active_layer.selectedFeatureCount():
                self.single_select_act.setEnabled(True)
                self.bound_select_act.setEnabled(False)
            else:
                self.single_select_act.setEnabled(False)
                self.bound_select_act.setEnabled(False)
        else:
            self.single_select_act.setEnabled(False)
            self.bound_select_act.setEnabled(False)

    @pyqtSlot()
    # noinspection PyArgumentList
    def aoi_from_current_extent(self):
        """Return current map extent as geojson transformed to EPSG:4326
        """
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        canvas = self._iface.mapCanvas()
        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"), QgsProject.instance())

        canvas_extent: QgsRectangle = canvas.extent()
        transform_extent = transform.transformBoundingBox(canvas_extent)
        # noinspection PyArgumentList
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson()

        # noinspection PyArgumentList
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(canvas.extent()))

        self.leAOI.setText(extent_json)

        log.debug('AOI set to canvas extent')

        self.zoom_to_aoi()

    @pyqtSlot()
    # noinspection PyArgumentList
    def aoi_from_active_layer_extent(self):
        """Return active map layer extent as geojson transformed to EPSG:4326
        """
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        map_layer: QgsMapLayer = self._iface.activeLayer()
        if map_layer is None:
            log.debug('No active layer selected, skipping AOI extent')
            return

        if not map_layer.isValid():
            log.debug('Active map layer invalid, skipping AOI extent')
            return

        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            map_layer.crs(), QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance())

        ml_extent: QgsRectangle = map_layer.extent()
        transform_extent = transform.transformBoundingBox(ml_extent)
        # noinspection PyArgumentList
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson()

        # noinspection PyArgumentList,PyCallByClass
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(ml_extent))

        self.leAOI.setText(extent_json)

        log.debug('AOI set to active layer extent')

        self.zoom_to_aoi()

    @pyqtSlot()
    # noinspection PyArgumentList
    def aoi_from_full_extent(self):
        """Return full data map extent as geojson transformed to EPSG:4326
        """
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        canvas = self._iface.mapCanvas()

        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"), QgsProject.instance())

        canvas_extent: QgsRectangle = canvas.fullExtent()
        transform_extent = transform.transformBoundingBox(canvas_extent)
        # noinspection PyArgumentList
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson()

        # noinspection PyArgumentList,PyCallByClass
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(canvas_extent))

        self.leAOI.setText(extent_json)

        log.debug('AOI set to full data extent')

        self.zoom_to_aoi()

    @pyqtSlot()
    def aoi_from_box(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetExtentMapTool(self._iface.mapCanvas())
        self._iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.extentSelected.connect(self.set_draw_aoi)

    @pyqtSlot()
    def aoi_from_circle(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetCircleMapTool(self._iface.mapCanvas())
        self._iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.circleSelected.connect(self.set_draw_aoi)

    @pyqtSlot()
    def aoi_from_polygon(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetPolyMapTool(self._iface.mapCanvas())
        self._iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.polygonSelected.connect(self.set_draw_aoi)

    @pyqtSlot(object)
    def set_draw_aoi(self, aoi):
        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"), QgsProject.instance())

        aoi_json = None

        if isinstance(aoi, QgsRectangle):
            aoi_geom = QgsGeometry().fromRect(aoi)
            self._aoi_box.setToGeometry(aoi_geom)
            aoi_geom.transform(transform)
            aoi_json = aoi_geom.asJson()

        if isinstance(aoi, QgsGeometry):
            self._aoi_box.setToGeometry(aoi)
            # TODO: validate geom is less than 500 vertices
            aoi.transform(transform)
            aoi_json = aoi.asJson()

        if aoi_json:
            self.leAOI.setText(aoi_json)

            # noinspection PyUnresolvedReferences
            self._show_message('AOI set to drawn figure')
            self.zoom_to_aoi()
            if self._cur_maptool is not None:
                # Restore previously used maptool
                self._canvas.setMapTool(self._cur_maptool)
                self._cur_maptool = None
            else:
                # Fallback to activating pan tool
                self._iface.actionPan().trigger()
        else:
            # noinspection PyUnresolvedReferences
            self._show_message('AOI unable to be set',
                               level=Qgis.Warning,
                               duration=10)

    @pyqtSlot()
    def aoi_from_feature(self):
        layer: QgsVectorLayer = self._iface.activeLayer()

        if layer.selectedFeatureCount() > 1:
            self._show_message('More than 1 feature. Searching by bbox.',
                               level=Qgis.Warning,
                               duration=10)
            self.aoi_from_bound()
            return
        elif layer.selectedFeatureCount() < 1:
            self._show_message('No features selected.',
                               level=Qgis.Warning,
                               duration=10)
            return

        selected: QgsFeature = layer.selectedFeatures()[0]
        if selected.geometry().isMultipart():
            multi_geom = selected.geometry().asGeometryCollection()
            if len(multi_geom) > 1:
                self._show_message('More than 1 geometry. Searching by bbox.',
                                   level=Qgis.Warning,
                                   duration=10)
                self.aoi_from_bound()
                return
            elif len(multi_geom) < 1:
                self._show_message('No geometry selected.',
                                   level=Qgis.Warning,
                                   duration=10)
                return
            else:
                geom: QgsGeometry = multi_geom[0]
        else:
            geom: QgsGeometry = selected.geometry()

        if geom.constGet().vertexCount() > 500:
            self._show_message('More than 500 vertices. Searching by bbox.',
                               level=Qgis.Warning,
                               duration=10)
            self.aoi_from_bound()
            return

        # noinspection PyArgumentList
        trans_layer = QgsCoordinateTransform(
            layer.sourceCrs(), QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance())

        # noinspection PyArgumentList
        trans_canvas = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(), QgsProject.instance())

        # geom.transform(transform)
        geom.transform(trans_layer)
        geom_json = geom.asJson()
        self.leAOI.setText(geom_json)

        geom.transform(trans_canvas)
        self._aoi_box.setToGeometry(geom,
                                    QgsCoordinateReferenceSystem("EPSG:4326"))
        self.zoom_to_aoi()

    @pyqtSlot()
    def aoi_from_bound(self):
        layer: QgsVectorLayer = self._iface.activeLayer()

        if layer.selectedFeatureCount() < 1:
            self._show_message('No features selected.',
                               level=Qgis.Warning,
                               duration=10)
            return

        bbox = layer.boundingBoxOfSelected()

        # noinspection PyArgumentList
        trans_layer = QgsCoordinateTransform(
            layer.sourceCrs(), QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance())

        # noinspection PyArgumentList
        trans_canvas = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(), QgsProject.instance())

        transform_bbox = trans_layer.transformBoundingBox(bbox)
        # noinspection PyArgumentList
        geom_bbox = QgsGeometry.fromRect(transform_bbox)
        bbox_json = geom_bbox.asJson()

        self.leAOI.setText(bbox_json)

        bbox_canvas = trans_canvas.transformBoundingBox(transform_bbox)
        # noinspection PyArgumentList
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(bbox_canvas))

        self.zoom_to_aoi()

    def hide_aoi_if_matches_geom(self, geom):
        color = (QColor(0, 0, 0, 0) if self._aoi_box.asGeometry().equals(geom)
                 else MAIN_AOI_COLOR)
        self._aoi_box.setStrokeColor(color)

    def show_aoi(self):
        if self._aoi_box is not None:
            self._aoi_box.setStrokeColor(MAIN_AOI_COLOR)

    def aoi_geom(self):
        if self._aoi_box is not None:
            return self._aoi_box.asGeometry()

    @pyqtSlot()
    def zoom_to_aoi(self):
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        if not self.leAOI.text():
            log.debug('No AOI defined, skipping zoom to AOI')
            return

        geom: QgsGeometry = qgsgeometry_from_geojson(self.leAOI.text())
        self._aoi_box.setToGeometry(geom,
                                    QgsCoordinateReferenceSystem("EPSG:4326"))

        self.show_aoi()

        zoom_canvas_to_aoi(self.leAOI.text(), iface_obj=self._iface)

        self.zoomToAOIRequested.emit()

    @pyqtSlot()
    def copy_aoi_to_clipboard(self):
        if not self.leAOI.text():
            log.debug('No AOI defined, skipping zoom to AOI')
            return

        json_geom_txt = json.dumps(json.loads(self.leAOI.text()), indent=2)

        cb = QgsApplication.clipboard()
        cb.setText(json_geom_txt)

        # noinspection PyUnresolvedReferences
        self._show_message('AOI copied to clipboard')

    @pyqtSlot()
    def load_aoi_from_file(self):
        path, _ = QFileDialog.getOpenFileName(self, "Open GeoJSON AOI file",
                                              QDir.homePath(),
                                              "JSON (*.json);;All Files (*)")
        file = QFile(path)
        if not file.open(QFile.ReadOnly | QFile.Text):
            return

        inf = QTextStream(file)
        json_txt = inf.readAll()

        try:
            json_obj = json.loads(json_txt)
        except ValueError:
            # noinspection PyUnresolvedReferences
            self._show_message('GeoJSON from file invalid',
                               level=Qgis.Warning,
                               duration=10)
            return

        json_geom = geometry_from_json(json_obj)

        if not json_geom:
            # noinspection PyUnresolvedReferences
            self._show_message('GeoJSON geometry from file invalid',
                               level=Qgis.Warning,
                               duration=10)
            return

        geom: QgsGeometry = qgsgeometry_from_geojson(json_geom)
        self._aoi_box.setToGeometry(geom,
                                    QgsCoordinateReferenceSystem("EPSG:4326"))

        self.leAOI.setText(json.dumps(json_geom))

        self.zoom_to_aoi()

    @pyqtSlot()
    def validate_aoi(self):
        # TODO:gather existing validation logic here
        # TODO: Check for valid json.loads

        # TODO: Check API verticie limit of 500
        pass

    @pyqtSlot()
    def validate_edited_aoi(self):
        json_txt = self.leAOI.text()
        if not json_txt:
            self.reset_aoi_box()
            log.debug('No AOI defined, skipping validation')
            return

        try:
            json_obj = json.loads(json_txt)
        except ValueError:
            # noinspection PyUnresolvedReferences
            self._show_message('AOI GeoJSON is invalid',
                               level=Qgis.Warning,
                               duration=10)
            return

        json_geom = geometry_from_json(json_obj)

        if not json_geom:
            # noinspection PyUnresolvedReferences
            self._show_message('AOI GeoJSON geometry invalid',
                               level=Qgis.Warning,
                               duration=10)
            return

        geom: QgsGeometry = qgsgeometry_from_geojson(json_geom)
        self._aoi_box.setToGeometry(geom,
                                    QgsCoordinateReferenceSystem("EPSG:4326"))

        self.leAOI.blockSignals(True)
        self.leAOI.setText(json.dumps(json_geom))
        self.leAOI.blockSignals(False)

        self.zoom_to_aoi()
Example #16
0
class DiscoveryPlugin:
    def __init__(self, _iface):
        # Save reference to the QGIS interface
        self.iface = _iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)

        # Variables to facilitate delayed queries and database connection management
        self.db_timer = QTimer()
        self.line_edit_timer = QTimer()
        self.line_edit_timer.setSingleShot(True)
        self.line_edit_timer.timeout.connect(self.reset_line_edit_after_move)
        self.next_query_time = None
        self.last_query_time = time.time()
        self.db_conn = None
        self.search_delay = 0.5  # s
        self.query_sql = ''
        self.query_text = ''
        self.query_dict = {}
        self.db_idle_time = 60.0  # s
        self.display_time = 5000  # ms
        self.bar_info_time = 30  # s

        self.search_results = []
        self.tool_bar = None
        self.search_line_edit = None
        self.completer = None
        self.conn_info = {}

        self.marker = QgsVertexMarker(iface.mapCanvas())
        self.marker.setIconSize(15)
        self.marker.setPenWidth(2)
        self.marker.setColor(QColor(226, 27, 28))  #51,160,44))
        self.marker.setZValue(11)
        self.marker.setVisible(False)
        self.marker2 = QgsVertexMarker(iface.mapCanvas())
        self.marker2.setIconSize(16)
        self.marker2.setPenWidth(4)
        self.marker2.setColor(QColor(255, 255, 255, 200))
        self.marker2.setZValue(10)
        self.marker2.setVisible(False)
        self.is_displayed = False

        self.rubber_band = QgsRubberBand(iface.mapCanvas(), False)
        self.rubber_band.setVisible(False)
        self.rubber_band.setWidth(3)
        self.rubber_band.setStrokeColor(QColor(226, 27, 28))
        self.rubber_band.setFillColor(QColor(226, 27, 28, 63))

    def initGui(self):

        # Create a new toolbar
        self.tool_bar = self.iface.addToolBar('Discovery')
        self.tool_bar.setObjectName('Discovery_Plugin')

        # Create action that will start plugin configuration
        self.action_config = QAction(
            QIcon(os.path.join(self.plugin_dir, "discovery_logo.png")),
            u"Configure Discovery", self.tool_bar)
        self.action_config.triggered.connect(self.show_config_dialog)
        self.tool_bar.addAction(self.action_config)

        # Add combobox for configs
        self.config_combo = QComboBox()
        settings = QgsSettings()
        settings.beginGroup("/Discovery")
        config_list = settings.value("config_list")

        if config_list:
            for conf in config_list:
                self.config_combo.addItem(conf)
        elif settings.childGroups():
            # support for prev version
            key = "Config1"
            config_list = []
            config_list.append(key)
            settings.setValue("config_list", config_list)
            self.config_combo.addItem(key)

            settings.setValue(key + "data_type", settings.value("data_type"))
            settings.setValue(key + "file", settings.value("file"))
            settings.setValue(key + "connection", settings.value("connection"))
            settings.setValue(key + "schema", settings.value("schema"))
            settings.setValue(key + "table", settings.value("table"))
            settings.setValue(key + "search_column",
                              settings.value("search_column"))
            settings.setValue(key + "echo_search_column",
                              settings.value("echo_search_column"))
            settings.setValue(key + "display_columns",
                              settings.value("display_columns"))
            settings.setValue(key + "geom_column",
                              settings.value("geom_column"))
            settings.setValue(key + "scale_expr", settings.value("scale_expr"))
            settings.setValue(key + "bbox_expr", settings.value("bbox_expr"))

            delete_config_from_settings("", settings)
        self.tool_bar.addWidget(self.config_combo)

        # Add search edit box
        self.search_line_edit = QgsFilterLineEdit()
        self.search_line_edit.setPlaceholderText('Search for...')
        self.search_line_edit.setMaximumWidth(768)
        self.tool_bar.addWidget(self.search_line_edit)

        self.config_combo.currentIndexChanged.connect(
            self.change_configuration)

        # Set up the completer
        self.completer = QCompleter([])  # Initialise with en empty list
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setMaxVisibleItems(1000)
        self.completer.setModelSorting(
            QCompleter.UnsortedModel)  # Sorting done in PostGIS
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion
                                         )  # Show all fetched possibilities
        self.completer.activated[QModelIndex].connect(self.on_result_selected)
        self.completer.highlighted[QModelIndex].connect(
            self.on_result_highlighted)
        self.search_line_edit.setCompleter(self.completer)

        # Connect any signals
        self.search_line_edit.textEdited.connect(self.on_search_text_changed)

        # Search results
        self.search_results = []

        # Set up a timer to periodically perform db queries as required
        self.db_timer.timeout.connect(self.do_db_operations)
        self.db_timer.start(100)

        # Read config
        self.read_config(config_list[0] if config_list else "")

        self.locator_filter = locator_filter.DiscoveryLocatorFilter(self)
        self.iface.registerLocatorFilter(self.locator_filter)

        # Debug
        # import pydevd; pydevd.settrace('localhost', port=5678)

    def unload(self):
        # Stop timer
        self.db_timer.stop()
        # Disconnect any signals
        self.db_timer.timeout.disconnect(self.do_db_operations)
        self.completer.highlighted[QModelIndex].disconnect(
            self.on_result_highlighted)
        self.completer.activated[QModelIndex].disconnect(
            self.on_result_selected)
        self.search_line_edit.textEdited.disconnect(
            self.on_search_text_changed)
        # Remove the new toolbar
        self.tool_bar.clear()  # Clear all actions
        self.iface.mainWindow().removeToolBar(self.tool_bar)

        self.iface.deregisterLocatorFilter(self.locator_filter)
        self.locator_filter = None

    def clear_suggestions(self):
        model = self.completer.model()
        model.setStringList([])

    def on_search_text_changed(self, new_search_text):
        """
        This function is called whenever the user modified the search text

        1. Open a database connection
        2. Make the query
        3. Update the QStringListModel with these results
        4. Store the other details in self.search_results
        """

        self.query_text = new_search_text

        if len(new_search_text) < 3:
            # Clear any previous suggestions in case the user is 'backspacing'
            self.clear_suggestions()
            return

        if self.data_type == "postgres":
            query_text, query_dict = dbutils.get_search_sql(
                new_search_text, self.postgisgeomcolumn,
                self.postgissearchcolumn, self.echosearchcolumn,
                self.postgisdisplaycolumn, self.extra_expr_columns,
                self.postgisschema, self.postgistable)
            self.schedule_search(query_text, query_dict)

        elif self.data_type == "gpkg":
            query_text = (new_search_text, self.postgissearchcolumn,
                          self.echosearchcolumn,
                          self.postgisdisplaycolumn.split(","),
                          self.extra_expr_columns, self.layer)
            self.schedule_search(query_text, None)

        elif self.data_type == "mssql":
            query_text = mssql_utils.get_search_sql(
                new_search_text, self.postgisgeomcolumn,
                self.postgissearchcolumn, self.echosearchcolumn,
                self.postgisdisplaycolumn, self.extra_expr_columns,
                self.postgisschema, self.postgistable)
            self.schedule_search(query_text, None)

    def do_db_operations(self):
        if self.next_query_time is not None and self.next_query_time < time.time(
        ):
            # It's time to run a query
            self.next_query_time = None  # Prevent this query from being repeated
            self.last_query_time = time.time()
            self.perform_search()
        else:
            # We're not performing a query, close the db connection if it's been open for > 60s
            if time.time() > self.last_query_time + self.db_idle_time:
                self.db_conn = None

    def perform_search(self):
        db = self.get_db()
        self.search_results = []
        suggestions = []
        if self.data_type == "postgres":
            cur = db.cursor()
            try:
                cur.execute(self.query_sql, self.query_dict)
            except psycopg2.Error as e:
                err_info = "Failed to execute the search query. Please, check your settings. Error message:\n\n"
                err_info += f"{e.pgerror}"
                QMessageBox.critical(None, "Discovery", err_info)
                return
            result_set = cur.fetchall()
        elif self.data_type == "mssql":
            result_set = mssql_utils.execute(db, self.query_sql)
        elif self.data_type == "gpkg":
            result_set = gpkg_utils.search_gpkg(*self.query_sql)

        for row in result_set:
            geom, epsg, suggestion_text = row[0], row[1], row[2]
            extra_data = {}
            for idx, extra_col in enumerate(self.extra_expr_columns):
                extra_data[extra_col] = row[3 + idx]
            self.search_results.append(
                (geom, epsg, suggestion_text, extra_data))
            suggestions.append(suggestion_text)
        model = self.completer.model()
        model.setStringList(suggestions)
        self.completer.complete()

    def schedule_search(self, query_text, query_dict):
        # Update the search text and the time after which the query should be executed
        self.query_sql = query_text
        self.query_dict = query_dict
        self.next_query_time = time.time() + self.search_delay

    def show_bar_info(self, info_text):
        """Optional show info bar message with selected result information"""
        self.iface.messageBar().clearWidgets()
        if self.bar_info_time:
            self.iface.messageBar().pushMessage("Discovery",
                                                info_text,
                                                level=Qgis.Info,
                                                duration=self.bar_info_time)

    def on_result_selected(self, result_index):
        # What to do when the user makes a selection
        self.select_result(self.search_results[result_index.row()])

    def select_result(self, result_data):
        geometry_text, src_epsg, suggestion_text, extra_data = result_data
        location_geom = QgsGeometry.fromWkt(geometry_text)
        canvas = self.iface.mapCanvas()
        dst_srid = canvas.mapSettings().destinationCrs().authid()
        transform = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem(src_epsg),
            QgsCoordinateReferenceSystem(dst_srid),
            canvas.mapSettings().transformContext())
        # Ensure the geometry from the DB is reprojected to the same SRID as the map canvas
        location_geom.transform(transform)
        location_centroid = location_geom.centroid().asPoint()

        # show temporary marker
        if location_geom.type() == QgsWkbTypes.PointGeometry:
            self.show_marker(location_centroid)
        elif location_geom.type() == QgsWkbTypes.LineGeometry or \
            location_geom.type() == QgsWkbTypes.PolygonGeometry:
            self.show_line_rubber_band(location_geom)
        else:
            #unsupported geometry type
            pass

        # Adjust map canvas extent
        zoom_method = 'Move and Zoom'
        if zoom_method == 'Move and Zoom':
            # with higher priority try to use exact bounding box to zoom to features (if provided)
            bbox_str = eval_expression(self.bbox_expr, extra_data)
            rect = bbox_str_to_rectangle(bbox_str)
            if rect is not None:
                # transform the rectangle in case of OTF projection
                rect = transform.transformBoundingBox(rect)
            else:
                # bbox is not available - so let's just use defined scale
                # compute target scale. If the result is 2000 this means the target scale is 1:2000
                rect = location_geom.boundingBox()
                if rect.isEmpty():
                    scale_denom = eval_expression(self.scale_expr,
                                                  extra_data,
                                                  default=2000.)
                    rect = canvas.mapSettings().extent()
                    rect.scale(scale_denom / canvas.scale(), location_centroid)
                else:
                    # enlarge geom bbox to have some margin
                    rect.scale(1.2)
            canvas.setExtent(rect)
        elif zoom_method == 'Move':
            current_extent = QgsGeometry.fromRect(
                self.iface.mapCanvas().extent())
            dx = location_centroid.x() - location_centroid.x()
            dy = location_centroid.y() - location_centroid.y()
            current_extent.translate(dx, dy)
            canvas.setExtent(current_extent.boundingBox())
        canvas.refresh()
        self.line_edit_timer.start(0)
        if self.info_to_clipboard:
            QApplication.clipboard().setText(suggestion_text)
            suggestion_text += ' (copied to clipboard)'
        self.show_bar_info(suggestion_text)

    def on_result_highlighted(self, result_idx):
        self.line_edit_timer.start(0)

    def reset_line_edit_after_move(self):
        self.search_line_edit.setText(self.query_text)

    def get_db(self):
        # Create a new new connection if required
        if self.db_conn is None:
            if self.data_type == "postgres":
                self.db_conn = dbutils.get_connection(self.conn_info)
            elif self.data_type == "mssql":
                self.db_conn = mssql_utils.get_mssql_conn(self.conn_info)
        return self.db_conn

    def change_configuration(self):
        self.search_line_edit.setText("")
        self.line_edit_timer.start(0)
        self.read_config(self.config_combo.currentText())

    def read_config(self, key=""):
        # the following code reads the configuration file which setups the plugin to search in the correct database,
        # table and method

        settings = QgsSettings()
        settings.beginGroup("/Discovery")

        connection = settings.value(key + "connection", "", type=str)
        self.data_type = settings.value(key + "data_type", "", type=str)
        self.file = settings.value(key + "file", "", type=str)
        self.postgisschema = settings.value(key + "schema", "", type=str)
        self.postgistable = settings.value(key + "table", "", type=str)
        self.postgissearchcolumn = settings.value(key + "search_column",
                                                  "",
                                                  type=str)
        self.echosearchcolumn = settings.value(key + "echo_search_column",
                                               True,
                                               type=bool)
        self.postgisdisplaycolumn = settings.value(key + "display_columns",
                                                   "",
                                                   type=str)
        self.postgisgeomcolumn = settings.value(key + "geom_column",
                                                "",
                                                type=str)
        if settings.value("marker_time_enabled", True, type=bool):
            self.display_time = settings.value("marker_time", 5000, type=int)
        else:
            self.display_time = -1
        if settings.value("bar_info_time_enabled", True, type=bool):
            self.bar_info_time = settings.value("bar_info_time", 30, type=int)
        else:
            self.bar_info_time = 0
        self.info_to_clipboard = settings.value("info_to_clipboard",
                                                True,
                                                type=bool)

        scale_expr = settings.value(key + "scale_expr", "", type=str)
        bbox_expr = settings.value(key + "bbox_expr", "", type=str)

        if self.is_displayed:
            self.hide_marker()
            self.hide_rubber_band()
            self.is_displayed = False

        self.make_enabled(False)  # assume the config is invalid first

        self.db_conn = None
        if self.data_type == "postgres":
            self.conn_info = dbutils.get_postgres_conn_info(connection)
            self.layer = None

            if len(connection) == 0 or len(self.postgisschema) == 0 or len(self.postgistable) == 0 or \
                    len(self.postgissearchcolumn) == 0 or len(self.postgisgeomcolumn) == 0:
                return

            if len(self.conn_info) == 0:
                iface.messageBar().pushMessage(
                    "Discovery",
                    "The database connection '%s' does not exist!" %
                    connection,
                    level=Qgis.Critical)
                return
        if self.data_type == "mssql":
            self.conn_info = mssql_utils.get_mssql_conn_info(connection)
            self.layer = None

            if len(connection) == 0 or len(self.postgisschema) == 0 or len(self.postgistable) == 0 or \
                    len(self.postgissearchcolumn) == 0 or len(self.postgisgeomcolumn) == 0:
                return

            if len(self.conn_info) == 0:
                iface.messageBar().pushMessage(
                    "Discovery",
                    "The database connection '%s' does not exist!" %
                    connection,
                    level=Qgis.Critical)
                return
        elif self.data_type == "gpkg":
            self.layer = QgsVectorLayer(
                self.file + '|layername=' + self.postgistable,
                self.postgistable, 'ogr')
            self.conn_info = None
        self.extra_expr_columns = []
        self.scale_expr = None
        self.bbox_expr = None

        self.make_enabled(True)

        # optional scale expression when zooming in to results
        if len(scale_expr) != 0:
            expr = QgsExpression(scale_expr)
            if expr.hasParserError():
                iface.messageBar().pushMessage("Discovery",
                                               "Invalid scale expression: " +
                                               expr.parserErrorString(),
                                               level=Qgis.Warning)
            else:
                self.scale_expr = scale_expr
                self.extra_expr_columns += expr.referencedColumns()

        # optional bbox expression when zooming in to results
        if len(bbox_expr) != 0:
            expr = QgsExpression(bbox_expr)
            if expr.hasParserError():
                iface.messageBar().pushMessage("Discovery",
                                               "Invalid bbox expression: " +
                                               expr.parserErrorString(),
                                               level=Qgis.Warning)
            else:
                self.bbox_expr = bbox_expr
                self.extra_expr_columns += expr.referencedColumns()

    def show_config_dialog(self):
        dlg = config_dialog.ConfigDialog()
        if (self.config_combo.currentIndex() >= 0):
            dlg.configOptions.setCurrentIndex(self.config_combo.currentIndex())

        if dlg.exec_():
            dlg.write_config()
            self.config_combo.clear()
            for key in [
                    dlg.configOptions.itemText(i)
                    for i in range(dlg.configOptions.count())
            ]:
                self.config_combo.addItem(key)

            self.config_combo.setCurrentIndex(dlg.configOptions.currentIndex())
            self.change_configuration()

    def make_enabled(self, enabled):
        self.search_line_edit.setEnabled(enabled)
        self.search_line_edit.setPlaceholderText(
            "Search for..."
            if enabled else "Search disabled: check configuration")

    def show_marker(self, point):
        for m in [self.marker, self.marker2]:
            m.setCenter(point)
            m.setOpacity(1.0)
            m.setVisible(True)
        if self.display_time == -1:
            self.is_displayed = True
        else:
            QTimer.singleShot(self.display_time, self.hide_marker)

    def hide_marker(self):
        opacity = self.marker.opacity()
        if opacity > 0.:
            # produce a fade out effect
            opacity -= 0.1
            self.marker.setOpacity(opacity)
            self.marker2.setOpacity(opacity)
            QTimer.singleShot(100, self.hide_marker)
        else:
            self.marker.setVisible(False)
            self.marker2.setVisible(False)

    def show_line_rubber_band(self, geom):
        self.rubber_band.reset(geom.type())
        self.rubber_band.setToGeometry(geom, None)
        self.rubber_band.setVisible(True)
        self.rubber_band.setOpacity(1.0)
        self.rubber_band.show()
        if self.display_time == -1:
            self.is_displayed = True
        else:
            QTimer.singleShot(self.display_time, self.hide_rubber_band)
        pass

    def hide_rubber_band(self):
        opacity = self.rubber_band.opacity()
        if opacity > 0.:
            # produce a fade out effect
            opacity -= 0.1
            self.rubber_band.setOpacity(opacity)
            QTimer.singleShot(100, self.hide_rubber_band)
        else:
            self.rubber_band.setVisible(False)
            self.rubber_band.hide()
class PlanetExtentMapTool(QgsMapTool):

    extentSelected = pyqtSignal(object)

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

        self.canvas = canvas
        self.extent = None
        self.dragging = False
        self.rubber_band = None
        self.select_rect = QRect()

    def canvasPressEvent(self, event):
        self.select_rect.setRect(0, 0, 0, 0)
        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.select_rect.setTopLeft(event.pos())

        self.select_rect.setBottomRight(event.pos())
        self._set_rubber_band()

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

        # Set valid values for rectangle's width and height
        if self.select_rect.width() == 1:
            self.select_rect.setLeft(self.select_rect.left() + 1)
        if self.select_rect.height() == 1:
            self.select_rect.setBottom(self.select_rect.bottom() + 1)

        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.extentSelected.emit(self.extent)

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

        ll = transform.toMapCoordinates(self.select_rect.left(),
                                        self.select_rect.bottom())
        ur = transform.toMapCoordinates(self.select_rect.right(),
                                        self.select_rect.top())

        if self.rubber_band:
            self.rubber_band.reset(QgsWkbTypes.PolygonGeometry)
            self.rubber_band.addPoint(ll, False)
            self.rubber_band.addPoint(QgsPointXY(ur.x(), ll.y()), False)
            self.rubber_band.addPoint(ur, False)
            self.rubber_band.addPoint(QgsPointXY(ll.x(), ur.y()), True)
            self.extent = QgsRectangle(ur, ll)
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 #19
0
class PlanetSearchResultsWidget(RESULTS_BASE, RESULTS_WIDGET):
    """
    """

    zoomToAOIRequested = pyqtSignal()
    setAOIRequested = pyqtSignal(dict)
    setSearchParamsRequested = pyqtSignal(dict, tuple)
    checkedCountChanged = pyqtSignal(int)

    grpBoxQuery: QGroupBox
    teQuery: QPlainTextEdit
    frameSearching: QFrame
    btnCancel: QToolButton
    btnShowQuery: QToolButton
    btnZoomToAOI: QToolButton
    btnSetAOI: QToolButton
    btnSetSearchParams: QToolButton
    lblSearching: QLabel
    frameResults: QFrame

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

        self.setupUi(self)

        self._parent = parent

        self._iface = iface
        # TODO: Grab responseTimeOut from plugin settings and override default
        self._response_timeout = response_timeout
        self._request_type = request_type
        self._request = request
        self.sort_order = sort_order

        self.teQuery.setPlainText(json.dumps(request, indent=2))

        # self.grpBoxQuery.setSaveCollapsedState(False)
        # self.grpBoxQuery.setCollapsed(True)
        self.grpBoxQuery.hide()

        self.btnZoomToAOI.clicked.connect(self._zoom_to_request_aoi)
        self.btnSetAOI.clicked.connect(self._set_aoi_from_request)
        self.btnSetSearchParams.clicked.connect(self._set_search_params_from_request)
        self.btnShowQuery.clicked.connect(self._toggle_query)

        self._aoi_box = None
        self._setup_request_aoi_box()

        self.results_tree = PlanetSearchResultsView(
            parent=self, iface=iface, api_key=api_key,
            request_type=request_type, request=request,
            response_timeout=self._response_timeout,
            sort_order=sort_order
        )

        self.results_tree.checkedCountChanged[int].connect(
            self.checked_count_changed)

        self.frameResults.layout().addWidget(self.results_tree)
        self.results_tree.setHidden(True)

        search_model = self.results_tree.search_model()
        if self.results_tree.search_model():
            search_model.searchFinished.connect(self._search_finished)
            search_model.searchNoResults.connect(self._search_no_results)
            search_model.searchCancelled.connect(self._search_cancelled)
            search_model.searchTimedOut[int].connect(self._search_timed_out)

            self.btnCancel.clicked.connect(search_model.cancel_search)

        self.waiting_spinner = QtWaitingSpinner(
            self.frameResults,
            centerOnParent=True,
            disableParentWhenSpinning=False,
            modality=Qt.NonModal
        )

        self.waiting_spinner.setRoundness(80.0)
        self.waiting_spinner.setMinimumTrailOpacity(15.0)
        self.waiting_spinner.setTrailFadePercentage(75.0)
        self.waiting_spinner.setNumberOfLines(15)
        self.waiting_spinner.setLineLength(14.0)
        self.waiting_spinner.setLineWidth(3.0)
        self.waiting_spinner.setInnerRadius(8.0)
        self.waiting_spinner.setRevolutionsPerSecond(1.0)
        self.waiting_spinner.setColor(PLANET_COLOR)

        self.waiting_spinner.start()
        self.waiting_spinner.show()

        search_model.start_api_search()
        # QTimer.singleShot(0, search_model.start_api_search)

    def _nix_waiting_spinner(self):
        self.waiting_spinner.stop()
        self.waiting_spinner.hide()

    def checked_count(self):
        return self.results_tree.checked_count()

    def checked_queue(self):
        return self.results_tree.checked_queue()

    @pyqtSlot(int)
    def checked_count_changed(self, count):
        if type(self._parent).__name__ == 'QTabWidget':
            # self._parent: QTabWidget
            tab_indx = self._parent.indexOf(self)

            if tab_indx != -1:
                txt = f'{self._request_type.capitalize()}'
                if count > 0:
                    txt += f' ({count})'
                self._parent.setTabText(tab_indx, txt)

        self.checkedCountChanged.emit(count)

    @pyqtSlot()
    def _toggle_query(self):
        if self.grpBoxQuery.isVisible():
            self.grpBoxQuery.hide()
            self.btnShowQuery.setIcon(
                QIcon(':/plugins/planet_explorer/expand-triangle.svg')
            )
        else:
            self.grpBoxQuery.show()
            self.btnShowQuery.setIcon(
                QIcon(':/plugins/planet_explorer/collapse-triangle.svg')
            )

    @pyqtSlot()
    def _search_finished(self):
        self._nix_waiting_spinner()
        self.frameSearching.hide()
        self.results_tree.show()

    @pyqtSlot()
    def _search_no_results(self):
        self._nix_waiting_spinner()
        self.lblSearching.setText('No results found')
        self.btnCancel.hide()
        self.results_tree.hide()

    @pyqtSlot()
    def _search_cancelled(self):
        self._nix_waiting_spinner()
        self.lblSearching.setText('Search cancelled')
        self.btnCancel.hide()
        self.results_tree.hide()

    @pyqtSlot(int)
    def _search_timed_out(self, timeout):
        self._nix_waiting_spinner()
        self.lblSearching.setText(f'Search timed out ({timeout} seconds)')
        self.btnCancel.hide()
        self.results_tree.hide()

    def _setup_request_aoi_box(self):
        if self._iface:
            log.debug('iface is available, adding aoi box support')
            self._aoi_box = QgsRubberBand(
                self._iface.mapCanvas(), QgsWkbTypes.PolygonGeometry)
            self._aoi_box.setFillColor(QColor(0, 0, 0, 0))
            self._aoi_box.setStrokeColor(SEARCH_AOI_COLOR)
            self._aoi_box.setWidth(2)
            self._aoi_box.setLineStyle(Qt.DashLine)
        else:
            log.debug('iface is None, skipping footprint support')
            self._aoi_box = None

    @pyqtSlot()
    def clear_aoi_box(self):
        if self._aoi_box:
            self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)

    @pyqtSlot()
    def _zoom_to_request_aoi(self):
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        aoi_geom = None
        if self._request_type == RESOURCE_DAILY:
            aoi_geom = geometry_from_request(self._request)

        if not aoi_geom:
            log.debug('No AOI geometry defined, skipping zoom to AOI')
            return

        qgs_geom: QgsGeometry = qgsgeometry_from_geojson(aoi_geom)
        self._aoi_box.setToGeometry(
            qgs_geom,
            QgsCoordinateReferenceSystem("EPSG:4326")
        )        

        self.show_aoi()

        zoom_canvas_to_aoi(aoi_geom, iface_obj=self._iface)

        self.zoomToAOIRequested.emit()

    def hide_aoi_if_matches_geom(self,geom):       
        if self._aoi_box is not None:
            color = (QColor(0, 0, 0, 0) if self._aoi_box.asGeometry().equals(geom) 
                    else SEARCH_AOI_COLOR)
            self._aoi_box.setStrokeColor(color)

    def show_aoi(self):
        if self._aoi_box is not None:
            self._aoi_box.setStrokeColor(SEARCH_AOI_COLOR)

    def aoi_geom(self):
        if self._aoi_box is not None:
            return self._aoi_box.asGeometry()

    @pyqtSlot()
    def _set_aoi_from_request(self):
        self.setAOIRequested.emit(self._request)

    @pyqtSlot()
    def _set_search_params_from_request(self):
        self.setSearchParamsRequested.emit(self._request, self.sort_order)

    @pyqtSlot()
    def clean_up(self):
        self.results_tree.clean_up()
        self.clear_aoi_box()

    # noinspection PyPep8Naming
    def closeEvent(self, event):
        self.clean_up()
        super().closeEvent(self, event)

    def request_query(self):
        return self._request
Example #20
0
class NavigationPanel(BASE, WIDGET):

    FIXED_WIDTH = 200
    WARNING_DISTANCE = 200

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setStyleSheet("background-color: #333f4f;")
        self.textBrowser.setStyleSheet("background-color: #adb9ca;")
        self.listWaypoints.setStyleSheet("background-color: #adb9ca;")
        self.iface = KadasPluginInterface.cast(iface)
        self.gpsConnection = None
        self.navLayer = None
        self.listWaypoints.setSelectionMode(QListWidget.SingleSelection)
        self.listWaypoints.currentItemChanged.connect(self.selectedWaypointChanged)
        self.listWaypoints.setSpacing(5)
        self.waypointWidgets = []
        self.optimalRoutesCache = {}

        self.timer = QTimer()

        self.rubberband = QgsRubberBand(iface.mapCanvas(), QgsWkbTypes.LineGeometry)
        self.rubberband.setStrokeColor(QColor(150, 0, 0))
        self.rubberband.setWidth(2)

        self.chkShowWarnings.setChecked(True)
        self.warningShown = False
        self.iface.messageBar().widgetRemoved.connect(self.setWarningShownOff)
        self.labelConfigureWarnings.linkActivated.connect(self.configureWarnings)

    def configureWarnings(self, url):
        threshold = QSettings().value(
            "kadasrouting/warningThreshold", self.WARNING_DISTANCE, type=int
        )
        value, ok = QInputDialog.getInt(
            self.iface.mainWindow(),
            self.tr("Navigation"),
            self.tr("Set threshold for warnings (meters)"),
            threshold,
        )
        if ok:
            QSettings().setValue("kadasrouting/warningThreshold", value)

    def setWarningShownOff(self):
        self.warningShown = False

    def show(self):
        super().show()
        self.startNavigation()

    def hide(self):
        super().hide()
        self.stopNavigation()

    def updateNavigationInfo(self):
        if self.gpsConnection is None:
            self.setMessage(self.tr("Cannot connect to GPS"))
            return
        try:
            gpsinfo = self.gpsConnection.currentGPSInformation()
        except RuntimeError:
            # if the GPS is closed in KADAS main interface, stop the navigation
            self.stopNavigation()
            return
        if gpsinfo is None:
            self.setMessage(self.tr("Cannot connect to GPS"))
            return
        layer = self.iface.activeLayer()
        LOG.debug("Debug: type(layer) = {}".format(type(layer)))
        point = QgsPointXY(gpsinfo.longitude, gpsinfo.latitude)
        if gpsinfo.speed > GPS_MIN_SPEED:
            # if we are moving, it is better for the user experience to
            # project the current point using the speed vector instead
            # of using 'point' directly, otherwise we get to feel of being
            # "behind the current position"
            qgsdistance = QgsDistanceArea()
            qgsdistance.setSourceCrs(
                QgsCoordinateReferenceSystem(4326),
                QgsProject.instance().transformContext(),
            )
            qgsdistance.setEllipsoid(qgsdistance.sourceCrs().ellipsoidAcronym())
            point = qgsdistance.computeSpheroidProject(
                point,
                (gpsinfo.speed / SPEED_DIVIDE_BY) * REFRESH_RATE_S,
                math.radians(gpsinfo.direction),
            )
        origCrs = QgsCoordinateReferenceSystem(4326)
        canvasCrs = self.iface.mapCanvas().mapSettings().destinationCrs()
        self.transform = QgsCoordinateTransform(
            origCrs, canvasCrs, QgsProject.instance()
        )

        if (
            isinstance(layer, QgsVectorLayer)
            and layer.geometryType() == QgsWkbTypes.LineGeometry
        ):
            feature = next(layer.getFeatures(), None)
            if feature:
                geom = feature.geometry()
                layer = self.getOptimalRouteLayerForGeometry(geom)
                if layer is not None:
                    rubbergeom = QgsGeometry(layer.geom)
                    rubbergeom.transform(self.transform)
                    self.rubberband.setToGeometry(rubbergeom)

        if hasattr(layer, "valhalla") and layer.hasRoute():
            try:
                maneuver = layer.maneuverForPoint(point, gpsinfo.speed)
                self.refreshCanvas(maneuver["closest_point"], gpsinfo)
                LOG.debug(maneuver)
            except NotInRouteException:
                self.refreshCanvas(point, gpsinfo)
                self.setMessage(self.tr("You are not on the route"))
                return
            self.setWidgetsVisibility(False)
            html = route_html_template.format(**maneuver)
            self.textBrowser.setHtml(html)
            self.textBrowser.setFixedHeight(self.textBrowser.document().size().height())
            self.setWarnings(maneuver["raw_distleft"])
        # FIXME: we could have some better way of differentiating this...
        elif not isinstance(layer, type(None)):
            if layer.name() != "Routes":
                self.setMessage(
                    self.tr("Select a route or waypoint layer for navigation")
                )
                self.stopNavigation()
                return
            waypoints = self.waypointsFromLayer(self.navLayer)
            if waypoints:
                if self.waypointLayer is None:
                    self.waypointLayer = self.navLayer
                    self.populateWaypoints(waypoints)
                else:
                    self.updateWaypoints()
                waypointItem = (
                    self.listWaypoints.currentItem() or self.listWaypoints.item(0)
                )
                waypoint = waypointItem.point
                instructions = getInstructionsToWaypoint(waypoint, gpsinfo)
                self.setCompass(instructions["heading"], instructions["wpangle"])
                html = waypoint_html_template.format(**instructions)
                self.textBrowser.setHtml(html)
                self.textBrowser.setFixedHeight(
                    self.textBrowser.document().size().height()
                )
                self.labelWaypointName.setText(
                    waypoint_name_html_template.format(name=waypointItem.name)
                )
                self.setWidgetsVisibility(True)
                self.setWarnings(instructions["raw_distleft"])
            else:
                self.setMessage(self.tr("The 'Routes' layer has no waypoints."))
                self.stopNavigation()
                return
        else:
            self.setMessage(self.tr("Select a route or waypoint layer for navigation"))
            self.stopNavigation()
            return

    def refreshCanvas(self, point, gpsinfo):
        canvasPoint = self.transform.transform(point)
        self.centerPin.setPosition(KadasItemPos(point.x(), point.y()))
        self.iface.mapCanvas().setCenter(canvasPoint)
        # stop rotating the map like a crazy when the user is almost still,
        # i.e. rotate only if we move faster than 1m/s
        if gpsinfo.speed > GPS_MIN_SPEED:
            self.iface.mapCanvas().setRotation(-gpsinfo.direction)
            self.centerPin.setAngle(0)
        self.iface.mapCanvas().refresh()
        self.rubberband.reset(QgsWkbTypes.LineGeometry)

    def setWarnings(self, dist):
        threshold = QSettings().value(
            "kadasrouting/warningThreshold", self.WARNING_DISTANCE, type=int
        )
        if (
            self.chkShowWarnings.isChecked()
            and not self.warningShown
            and dist < threshold
        ):
            pushMessage(
                self.tr("In {dist} meters you will arrive at your destination").format(
                    dist=int(dist)
                )
            )
            self.warningShown = True

    def getOptimalRouteLayerForGeometry(self, geom):
        wkt = geom.asWkt()
        if wkt in self.optimalRoutesCache:
            return self.optimalRoutesCache[wkt]

        name = self.iface.activeLayer().name()
        value, ok = QInputDialog.getItem(
            self.iface.mainWindow(),
            self.tr("Navigation"),
            self.tr("Select Vehicle to use with layer '{name}'").format(name=name),
            vehicles.vehicle_reduced_names(),
        )
        if ok:
            profile, costingOptions = vehicles.options_for_vehicle_reduced(
                vehicles.vehicle_reduced_names().index(value)
            )
            layer = OptimalRouteLayer("")
            try:
                if geom.isMultipart():
                    polyline = geom.asMultiPolyline()
                    line = polyline[0]
                else:
                    line = geom.asPolyline()
                layer.updateFromPolyline(line, profile, costingOptions)
                self.optimalRoutesCache[wkt] = layer
                return layer
            except Exception:
                return

    def setCompass(self, heading, wpangle):
        compassPixmap = QPixmap(iconPath("compass.png"))
        compassPixmap = compassPixmap.scaledToWidth(self.FIXED_WIDTH)
        bearingPixmap = QPixmap(iconPath("direction.png"))
        pixmap = QPixmap(self.FIXED_WIDTH, self.FIXED_WIDTH)
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        transform = QTransform()
        transform.translate(self.FIXED_WIDTH / 2, self.FIXED_WIDTH / 2)
        transform.rotate(-heading)
        transform.translate(-self.FIXED_WIDTH / 2, -self.FIXED_WIDTH / 2)
        painter.setTransform(transform)
        painter.drawPixmap(0, 0, self.FIXED_WIDTH, self.FIXED_WIDTH, compassPixmap)
        transform = QTransform()
        transform.translate(self.FIXED_WIDTH / 2, self.FIXED_WIDTH / 2)
        transform.rotate(wpangle - heading)
        transform.translate(-self.FIXED_WIDTH / 2, -self.FIXED_WIDTH / 2)
        painter.setTransform(transform)
        painter.drawPixmap(0, 0, self.FIXED_WIDTH, self.FIXED_WIDTH, bearingPixmap)
        painter.end()
        self.labelCompass.setPixmap(pixmap)
        self.labelCompass.resize(QSize(self.FIXED_WIDTH, self.FIXED_WIDTH))

    def updateWaypoints(self):
        for item, w in self.waypointWidgets:
            w.setWaypointText(self.gpsConnection.currentGPSInformation())

    def selectedWaypointChanged(self, current, previous):
        for item, w in self.waypointWidgets:
            w.setIsItemSelected(current == item)
        self.warningShown = False
        self.updateNavigationInfo()

    def waypointsFromLayer(self, layer):
        try:
            """
            center = iface.mapCanvas().center()
            outCrs = QgsCoordinateReferenceSystem(4326)
            canvasCrs = iface.mapCanvas().mapSettings().destinationCrs()
            transform = QgsCoordinateTransform(canvasCrs, outCrs, QgsProject.instance())
            wgspoint = transform.transform(center)
            item = KadasGpxWaypointItem()
            item.addPartFromGeometry(QgsPoint(wgspoint.x() + 10, wgspoint.y() + 10))
            item.setName("Test Waypoint")
            item2 = KadasGpxWaypointItem()
            item2.addPartFromGeometry(QgsPoint(wgspoint.x() + 10, wgspoint.y() + 10))
            item2.setName("Another Waypoint")
            item3 = KadasGpxWaypointItem()
            item3.addPartFromGeometry(QgsPoint(wgspoint.x() + 10, wgspoint.y() + 10))
            item3.setName("My Waypoint")
            return [item, item2, item3]
            """
            return [
                item for item in layer.items() if isinstance(item, KadasGpxWaypointItem)
            ]
        except Exception as e:
            LOG.warning(e)
            return []

    def populateWaypoints(self, waypoints):
        self.listWaypoints.clear()
        self.waypointWidgets = []
        for waypoint in waypoints:
            item = WaypointItem(waypoint)
            widget = WaypointItemWidget(
                waypoint, self.gpsConnection.currentGPSInformation()
            )
            self.listWaypoints.addItem(item)
            item.setSizeHint(widget.sizeHint())
            self.listWaypoints.setItemWidget(item, widget)
            self.waypointWidgets.append((item, widget))
        self.selectedWaypointChanged(self.listWaypoints.item(0), None)

    def setMessage(self, text):
        self.setWidgetsVisibility(False)
        self.textBrowser.setHtml(message_html_template.format(text=text))
        self.textBrowser.setFixedHeight(self.height())

    def setWidgetsVisibility(self, iswaypoints):
        self.labelWaypointName.setVisible(iswaypoints)
        self.labelCompass.setVisible(iswaypoints)
        self.listWaypoints.setVisible(iswaypoints)
        self.labelWaypoints.setVisible(False)  # iswaypoints)
        color = "#adb9ca" if iswaypoints else "#333f4f"
        self.textBrowser.setStyleSheet("background-color: {};".format(color))

    def startNavigation(self):
        self.centerPin = None
        self.waypointLayer = None
        self.warningShown = False
        self.originalGpsMarker = None

        self.setMessage(self.tr("Connecting to GPS..."))
        self.gpsConnection = getGpsConnection()
        try:
            if iface.activeLayer().name() == "Routes":
                self.navLayer = NavigationFromWaypointsLayer()
        except AttributeError:
            pass
        except FileNotFoundError:
            self.setMessage(
                self.tr(
                    "You must save your project to use waypoint layers for navigation"
                )
            )
            return
        except TypeError:
            self.setMessage(self.tr("There are no waypoints in the 'Routes' layer"))
            return
        if self.gpsConnection is None:
            self.setMessage(self.tr("Cannot connect to GPS"))
        else:
            self.removeOriginalGpsMarker()
            self.centerPin = KadasPinItem(QgsCoordinateReferenceSystem(4326))
            self.centerPin.setup(
                iconPath("navigationcenter.svg"),
                self.centerPin.anchorX(),
                self.centerPin.anchorX(),
                32,
                32,
            )
            # For some reason the first time shown, the direction of the center pin is following the map canvas.
            # The line below is needed to neutralize the direction of the center pin (to make it points up)
            self.centerPin.setAngle(
                -self.gpsConnection.currentGPSInformation().direction
            )
            KadasMapCanvasItemManager.addItem(self.centerPin)
            self.updateNavigationInfo()
            self.timer.start(REFRESH_RATE_S * 1000)
            self.timer.timeout.connect(self.updateNavigationInfo)
        self.iface.layerTreeView().currentLayerChanged.connect(self.currentLayerChanged)

    def currentLayerChanged(self, layer):
        self.waypointLayer = None
        self.warningShown = False
        self.updateNavigationInfo()

    def stopNavigation(self):
        if self.gpsConnection is not None:
            try:
                self.timer.timeout.disconnect(self.updateNavigationInfo)
                self.timer.stop()
            except TypeError as e:
                LOG.debug(e)
        try:
            if self.centerPin is not None:
                KadasMapCanvasItemManager.removeItem(self.centerPin)
        except Exception:
            # centerPin might have been deleted
            pass
        try:
            self.iface.layerTreeView().currentLayerChanged.disconnect(
                self.currentLayerChanged
            )
        except TypeError as e:
            LOG.debug(e)
        # Finally, reset everything
        self.addOriginalGpsMarker()
        self.rubberband.reset(QgsWkbTypes.LineGeometry)
        self.iface.mapCanvas().setRotation(0)
        self.iface.mapCanvas().refresh()

    def removeOriginalGpsMarker(self):
        for item in KadasMapCanvasItemManager.items():
            if item.itemName() == "Symbol":
                item.__class__ = KadasSymbolItem
                try:
                    if item.filePath() == ":/kadas/icons/gpsarrow":
                        self.originalGpsMarker = item
                        KadasMapCanvasItemManager.removeItem(self.originalGpsMarker)
                except AttributeError:
                    pass

    def addOriginalGpsMarker(self):
        try:
            if self.originalGpsMarker:
                KadasMapCanvasItemManager.addItem(self.originalGpsMarker)
        except RuntimeError:
            # if the GPS connection has been aborted, then just pass
            pass
        finally:
            self.originalGpsMarker = None
class DailyImagesSearchResultsWidget(RESULTS_BASE, RESULTS_WIDGET):

    setAOIRequested = pyqtSignal(dict)
    checkedCountChanged = pyqtSignal(int)

    def __init__(self):
        super().__init__()

        self.setupUi(self)

        self._p_client = PlanetClient.getInstance()

        self._has_more = True

        self._metadata_to_show = [
            PlanetNodeMetadata.CLOUD_PERCENTAGE,
            PlanetNodeMetadata.GROUND_SAMPLE_DISTANCE,
        ]

        self._image_count = 0
        self._total_count = 0

        self._request = None
        self._local_filters = None
        self._response_iterator = None

        self.btnSaveSearch.setIcon(SAVE_ICON)
        self.btnSort.setIcon(SORT_ICON)
        self.btnAddPreview.setIcon(ADD_PREVIEW_ICON)
        self.btnAddPreview.setEnabled(False)

        self.btnSaveSearch.clicked.connect(self._save_search)
        self.btnAddPreview.clicked.connect(self._add_preview_clicked)
        self.btnSort.clicked.connect(self._sort_order_changed)
        self.btnSettings.clicked.connect(self._open_settings)
        self.lblImageCount.setOpenExternalLinks(False)
        self.lblImageCount.linkActivated.connect(self.load_more_link_clicked)

        self._aoi_box = None
        self._setup_request_aoi_box()

        self._set_widgets_visibility(False)

    def _set_widgets_visibility(self, search_ok):
        self.tree.setVisible(search_ok)
        self.widgetActions.setVisible(search_ok)
        self.widgetNoResults.setVisible(not search_ok)

    def search_has_been_performed(self):
        return self._request is not None

    def _open_settings(self):
        dlg = ResultsConfigurationDialog(self._metadata_to_show)
        if dlg.exec_():
            self._metadata_to_show = dlg.selection
            self.update_image_items()

    def _add_preview_clicked(self):
        self.add_preview()

    @waitcursor
    def add_preview(self):
        imgs = self.selected_images()
        send_analytics_for_preview(imgs)
        create_preview_group("Selected images", imgs)

    def update_image_items(self):
        it = QTreeWidgetItemIterator(self.tree)
        while it.value():
            item = it.value()
            if isinstance(item, SceneItem):
                w = self.tree.itemWidget(item, 0)
                w.set_metadata_to_show(self._metadata_to_show)
            it += 1

    def _save_search(self):
        dlg = SaveSearchDialog(self._request)
        if dlg.exec_():
            self._p_client.create_search(dlg.request_to_save)
            analytics_track(SAVED_SEARCH_CREATED)

    def sort_order(self):
        order = ["acquired"]
        if self.btnSort.isChecked():
            order.append("asc")
        else:
            order.append("desc")
        return order

    def _sort_order_changed(self):
        self.update_request(self._request, {})

    def load_more_link_clicked(self):
        self.load_more()

    @waitcursor
    def update_request(self, request, local_filters):
        self._image_count = 0
        self._request = request
        self._local_filters = local_filters
        self.tree.clear()
        stats_request = {"interval": "year"}
        stats_request.update(self._request)
        resp = self._p_client.stats(stats_request).get()
        self._total_count = sum([b["count"] for b in resp["buckets"]])
        if self._total_count:
            response = self._p_client.quick_search(
                self._request,
                page_size=TOP_ITEMS_BATCH,
                sort=" ".join(self.sort_order()),
            )
            self._response_iterator = response.iter()
            self.load_more()
            self._set_widgets_visibility(True)
        else:
            self._set_widgets_visibility(False)

    @waitcursor
    def load_more(self):
        page = next(self._response_iterator, None)
        if page is not None:
            for i in range(self.tree.topLevelItemCount()):
                date_item = self.tree.topLevelItem(i)
                date_widget = self.tree.itemWidget(date_item, 0)
                date_widget.has_new = False
                for j in range(date_item.childCount()):
                    satellite_item = date_item.child(j)
                    satellite_widget = self.tree.itemWidget(satellite_item, 0)
                    satellite_widget.has_new = False

            links = page.get()[page.LINKS_KEY]
            next_ = links.get(page.NEXT_KEY, None)
            self._has_more = next_ is not None
            images = page.get().get(page.ITEM_KEY)
            for i, image in enumerate(images):
                if self._passes_area_coverage_filter(image):
                    sort_criteria = "acquired"
                    date_item, satellite_item = self._find_items_for_satellite(
                        image)
                    date_widget = self.tree.itemWidget(date_item, 0)
                    satellite_widget = self.tree.itemWidget(satellite_item, 0)
                    item = SceneItem(image, sort_criteria)
                    widget = SceneItemWidget(
                        image,
                        sort_criteria,
                        self._metadata_to_show,
                        item,
                        self._request,
                    )
                    widget.checkedStateChanged.connect(
                        self.checked_count_changed)
                    widget.thumbnailChanged.connect(
                        satellite_widget.update_thumbnail)
                    item.setSizeHint(0, widget.sizeHint())
                    satellite_item.addChild(item)
                    self.tree.setItemWidget(item, 0, widget)
                    date_widget.update_for_children()
                    self._image_count += 1

            for i in range(self.tree.topLevelItemCount()):
                date_item = self.tree.topLevelItem(i)
                date_widget = self.tree.itemWidget(date_item, 0)
                for j in range(date_item.childCount()):
                    satellite_item = date_item.child(j)
                    satellite_widget = self.tree.itemWidget(satellite_item, 0)
                    satellite_widget.update_for_children()
                    satellite_widget.update_thumbnail()
                    satellite_item.sortChildren(0, Qt.AscendingOrder)
                date_widget.update_for_children()
                date_widget.update_thumbnail()
            self.item_count_changed()
        else:
            self._has_more = False
            self.item_count_changed()

    def _local_filter(self, name):
        for f in self._local_filters:
            if f.get("field_name") == name:
                return f

    def _passes_area_coverage_filter(self, image):
        area_coverage = area_coverage_for_image(image, self._request)
        if area_coverage is None:
            return True  # an ID filter is begin used, so it makes no sense to
            # check for are acoverage
        filt = self._local_filter("area_coverage")
        if filt:
            minvalue = filt["config"].get("gte", 0)
            maxvalue = filt["config"].get("lte", 100)
            return area_coverage > minvalue and area_coverage < maxvalue
        return True

    def _find_item_for_date(self, image):
        sort_criteria = "acquired"
        date = iso8601.parse_date(image[PROPERTIES][sort_criteria]).date()
        itemtype = image[PROPERTIES][ITEM_TYPE]
        count = self.tree.topLevelItemCount()
        for i in range(count):
            child = self.tree.topLevelItem(i)
            if child.date == date and child.itemtype == itemtype:
                return child
        date_item = DateItem(image, sort_criteria)
        widget = DateItemWidget(image, sort_criteria, date_item)
        widget.checkedStateChanged.connect(self.checked_count_changed)
        date_item.setSizeHint(0, widget.sizeHint())
        self.tree.addTopLevelItem(date_item)
        self.tree.setItemWidget(date_item, 0, widget)
        return date_item

    def _find_items_for_satellite(self, image):
        date_item = self._find_item_for_date(image)
        date_widget = self.tree.itemWidget(date_item, 0)
        satellite = image[PROPERTIES][SATELLITE_ID]
        instrument = image[PROPERTIES].get(INSTRUMENT, "")
        count = date_item.childCount()
        for i in range(count):
            child = date_item.child(i)
            if child.satellite == satellite:
                return date_item, child
        satellite_item = SatelliteItem(satellite)
        widget = SatelliteItemWidget(satellite, instrument, satellite_item)
        widget.thumbnailChanged.connect(date_widget.update_thumbnail)
        widget.checkedStateChanged.connect(self.checked_count_changed)
        satellite_item.setSizeHint(0, widget.sizeHint())
        date_item.addChild(satellite_item)
        self.tree.setItemWidget(satellite_item, 0, widget)
        return date_item, satellite_item

    def selected_images(self):
        selected = []
        it = QTreeWidgetItemIterator(self.tree)
        while it.value():
            item = it.value()
            if isinstance(item, SceneItem):
                w = self.tree.itemWidget(item, 0)
                w.set_metadata_to_show(self._metadata_to_show)
                if w.is_selected():
                    selected.append(w.image)
            it += 1
        return selected

    def checked_count_changed(self):
        numimages = len(self.selected_images())
        self.btnAddPreview.setEnabled(numimages)
        self.checkedCountChanged.emit(numimages)

    def item_count_changed(self):
        if self._image_count < self._total_count:
            self.lblImageCount.setText(
                f"{self._image_count} images. <a href='#'>Load more</a>")
        else:
            self.lblImageCount.setText(f"{self._image_count} images")

    def _setup_request_aoi_box(self):
        self._aoi_box = QgsRubberBand(iface.mapCanvas(),
                                      QgsWkbTypes.PolygonGeometry)
        self._aoi_box.setFillColor(QColor(0, 0, 0, 0))
        self._aoi_box.setStrokeColor(SEARCH_AOI_COLOR)
        self._aoi_box.setWidth(2)
        self._aoi_box.setLineStyle(Qt.DashLine)

    @pyqtSlot()
    def clear_aoi_box(self):
        if self._aoi_box:
            self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)

    def clean_up(self):
        self.clear_aoi_box()
        self.tree.clear()
        self.lblImageCount.setText("")
        self._set_widgets_visibility(False)

    def closeEvent(self, event):
        self.clean_up()
        super().closeEvent(self, event)

    def request_query(self):
        return self._request
Example #22
0
class GPS(QObject):
    def __init__(self, gtomain):
        super(GPS, self).__init__()
        self.gtomain = gtomain
        self.debug = self.gtomain.debug
        self.helper = self.gtomain.helper
        self.iface = self.gtomain.iface
        self.info = self.gtomain.info
        self.prj = None
        self.canvas = self.iface.mapCanvas()
        self.gpsLog = Info(self)
        self.gpsLog.panel_name = "GTO-GPS"
        self.mouse = None
        self.LastMapTool = None
        try:
            # settings
            self.settings = None
            self.timer_intervall = 1000
            self.port = None
            self.pdop = 0
            self.wgs_crs = 'EPSG:4326'
            self.gps_streaming_distance = 10

            # refs
            self.gpsCon = None
            self.gpsDetector = None
            self.gps_active = False
            self.dic_gpsinfo = {}

            self.prj_crs = None
            self.src_crs = None
            self.transformation = None
            self.marker = None
            self.prevPointXY = None
            self.prevTime = None
            self.lastGpsInfo = None
            self.center = False
            self.scale = 0
            # actions
            mw = self.iface.mainWindow()
            self.actionGPStoggle = QAction("GTO-GPS", mw)
            self.actionGPStoggle.setObjectName('mActionGTOgpsToggle')
            self.actionGPStoggle.setToolTip('GTO GPS starten')
            self.actionGPStoggle.setIcon(
                self.gtomain.helper.getIcon('mActionGTOgpsToggle.png'))
            self.actionGPStoggle.setCheckable(True)
            self.actionGPStoggle.setChecked(False)
            self.actionGPStoggle.toggled.connect(self.activate)

            self.actionGPSaddPoint = QAction("GPS Punkt hinzufügen", mw)
            self.actionGPSaddPoint.setObjectName('mActionGTOgpsAddPoint')
            self.actionGPSaddPoint.setToolTip('GPS Punkt hinzufügen')
            self.actionGPSaddPoint.setIcon(
                self.gtomain.helper.getIcon('mActionGTOgpsAddPoint.png'))
            self.actionGPSaddPoint.triggered.connect(self.addPoint)
            self.actionGPSaddPoint.setEnabled(False)

            self.actionGPSclick = QAction("GPS Punkt hinzufügen", mw)
            self.actionGPSclick.setObjectName('mActionGTOgpsAddcPoint')
            self.actionGPSclick.setToolTip('GPS Punkt hinzufügen')
            self.actionGPSclick.setIcon(
                self.gtomain.helper.getIcon('mActionGTOgpsAddcPoint.png'))
            self.actionGPSclick.triggered.connect(self.gps_click)
            self.actionGPSclick.setEnabled(False)

            self.actionGPSstreaming = QAction("GPS streaming", mw)
            self.actionGPSstreaming.setObjectName('mActionGTOgpsStream')
            self.actionGPSstreaming.setToolTip('GPS Punkte aufzeichnen')
            self.actionGPSstreaming.setIcon(
                self.gtomain.helper.getIcon('mActionGTOgpsStream.png'))
            self.actionGPSstreaming.setCheckable(True)
            self.actionGPSstreaming.setEnabled(False)
            self.actionGPSstreaming.toggled.connect(self.streaming)

            self.actionGPSsave = QAction("GPS Geometrie speichern", mw)
            self.actionGPSsave.setObjectName('mActionGTOgpsSave')
            self.actionGPSsave.setToolTip('GPS Geometrie speichern')
            self.actionGPSsave.setIcon(
                self.gtomain.helper.getIcon('mActionGTOgpsSave.png'))
            self.actionGPSsave.triggered.connect(self.saveGeometry)
            self.actionGPSsave.setEnabled(False)

            self.actionGPSaddVertexTool = QAction("GPS AddVertexTool", mw)
            self.actionGPSaddVertexTool.setObjectName(
                'mActionGTOgpsAddVertexTool')
            self.actionGPSaddVertexTool.setToolTip('GPS add vertex to stream')
            self.actionGPSaddVertexTool.setIcon(
                self.gtomain.helper.getIcon('mActionGTOgpsAddVertexTool.png'))
            self.actionGPSaddVertexTool.setCheckable(True)
            self.actionGPSaddVertexTool.toggled.connect(
                self.activateVertexTool)
            self.actionGPSaddVertexTool.setEnabled(False)
            # add vertex tool
            self.streamTool = VertexTool(self.iface, self.canvas, True)
            self.streamTool.canvasReleased.connect(self.addVertex)
            self.streamTool.isActive.connect(self.tool_isactive)

            self.actionGPSlog = QAction("GPS Log", mw)
            self.actionGPSlog.setObjectName('mActionGTOgpsLog')
            self.actionGPSlog.setToolTip('GPS events anzeigen')
            self.actionGPSlog.setIcon(
                self.gtomain.helper.getIcon('mActionGTOgpsLog.png'))
            self.actionGPSlog.setCheckable(True)

            self.actionGPScenter = QAction("GPS zentriere Karte", mw)
            self.actionGPScenter.setObjectName('mActionGTOgpsCenter')
            self.actionGPScenter.setToolTip('GPS zentriere Karte')
            self.actionGPScenter.setIcon(
                self.gtomain.helper.getIcon('mActionGTOgpsCenter.png'))
            self.actionGPScenter.setCheckable(True)
            self.actionGPScenter.toggled.connect(self.activateCenter)

            self.actionGPSport = GtoWidgetGpsPort(self, mw)
            self.actionGPSport.portChanged.connect(self.setPort)

            self.canvas.currentLayerChanged.connect(self.layer_changed)
            # streaming
            self.debugXoffset = 0  # debugging
            self.debugYoffset = 0
            # rubberband
            # get selection color
            selcolor = self.canvas.selectionColor()
            mycolor = QColor(selcolor.red(), selcolor.green(), selcolor.blue(),
                             40)
            self.rb = QgsRubberBand(self.canvas)
            self.rb.setStrokeColor(QColor(255, 0, 0, 90))
            self.rb.setFillColor(mycolor)
            self.rb.setLineStyle(Qt.PenStyle(Qt.SolidLine))
            self.rb.setWidth(4)

            # watchdog
            self.watchdog = QTimer()
            self.watchdog.setInterval(2000)
            self.watchdog.timeout.connect(self.watch_dog)
            # layer
            self.streamLayer = None
            self.pointLayer = None
            self.gps_stream_mode = 0  # "0=rubber, 1=click, 2=both
            self.gps_debug = False
        except Exception as e:
            self.info.err(e)

    def watch_dog(self):
        try:
            self.watchdog.stop()
            self.gpsLog.log('Status:',
                            self.gpsCon.status())  # doesnt work properly
            self.marker.setColor(QColor(255, 0, 0))
            if self.actionGPSlog.isChecked(): self.gpsLog.log('No signal!')
            res = self.info.gtoQuestion("GPS deaktivieren? (Empfohlen)",
                                        title='Kein GPS Signal!',
                                        btns=QMessageBox.Yes | QMessageBox.No,
                                        parent=self.iface.mainWindow())
            if res == QMessageBox.Yes:
                self.deactivate()
                self.actionGPStoggle.setChecked(False)
        except Exception as e:
            self.info.err(e)

    def init(self):
        try:
            if self.debug: self.gpsLog.log('gps init')
            self.actionGPStoggle.setChecked(False)
            # qgis
            self.prj = QgsProject().instance()
            self.canvas = self.iface.mapCanvas()
            # settings
            if "GPS" in self.gtomain.settings:  # compatible
                self.settings = self.gtomain.settings["GPS"]
            else:
                self.settings = self.gtomain.settings
            # timer
            self.timer_intervall = self.settings.get("gps_timer_intervall",
                                                     5000)
            # gps
            self.port = self.settings.get("gps_port", None)
            if self.port is None:
                self.setPort(self.helper.getGlobalSetting('GpsPort'))
            self.pdop = self.settings.get("gps_pdop", 0)
            self.actionGPSlog.setChecked(self.settings.get("gps_log", False))
            watchdog_interval = self.settings.get("gps_watchdog_interval",
                                                  3000)
            self.watchdog.setInterval(watchdog_interval)
            # streaming
            self.pointLayer = self.settings.get("gps_point_layer", None)
            try:
                if self.pointLayer is not None:
                    self.pointLayer = self.prj.mapLayersByName(
                        self.pointLayer)[0]
                    geoType = self.pointLayer.geometryType()
                    if geoType != QgsWkbTypes.GeometryType.PointGeometry:
                        self.pointLayer = None
            except Exception as e:
                self.pointLayer = None
                self.info.err(e)
            self.gps_debug = self.settings.get("gps_debug", False)
            self.gps_stream_mode = self.settings.get("gps_stream_mode", 0)
            self.gps_streaming_distance = self.settings.get(
                "gps_streaming_distance", 1)
            # map
            self.center = self.settings.get("gps_center", True)
            self.actionGPScenter.setChecked(self.center)
            self.scale = self.settings.get("gps_scale", 0)
            # transformation
            self.prj_crs = self.prj.crs()
            self.wgs_crs = self.settings.get("gps_wgs_crs", 'EPSG:4326')
            self.init_transformation(self.wgs_crs)
        except Exception as e:
            self.info.err(e)

    def activateCenter(self):
        self.center = self.actionGPScenter.isChecked()

    def activate(self):
        try:
            self.deactivate()
            if self.actionGPStoggle.isChecked():
                if self.port is None:
                    self.info.msg("Kein GPS Port gesetzt!")
                    return
                self.actionGPSport.setEnabled(False)
                if self.actionGPSlog.isChecked():
                    self.gpsLog.log('gps activate')
                self.gpsDetector = QgsGpsDetector(self.port)
                self.gpsDetector.detected[QgsGpsConnection].connect(
                    self.connection_succeed)
                # self.gpsDetector.detected[QgsNmeaConnection].connect(self.connection_succeed)
                self.gpsDetector.detectionFailed.connect(
                    self.connection_failed)
                self.gpsDetector.advance()
                self.init_marker()
        except Exception as e:
            self.info.err(e)

    def setPort(self, port):
        try:
            self.port = port
            self.actionGPStoggle.setToolTip('GPS on/off (Port:{0})'.format(
                self.port))
        except Exception as e:
            self.info.err(e)

    def enableGPSfunctions(self, enabled):
        try:
            if self.iface.mainWindow() is None: return  # dummy
        except:
            # prevent: ERROR:  <class 'RuntimeError'> gto_gps.py | line: 240 | ('wrapped C/C++ object of type QgisInterface has been deleted',)
            # because QGIS is already closing
            return
        try:
            if self.iface.activeLayer() is not None:
                geoType = self.iface.activeLayer().geometryType()
                if geoType == QgsWkbTypes.GeometryType.PointGeometry:
                    self.actionGPSaddPoint.setEnabled(enabled)
                else:
                    self.actionGPSaddPoint.setEnabled(False)
            self.actionGPSclick.setEnabled(enabled)
            self.actionGPSstreaming.setEnabled(enabled)
            self.actionGPSport.setEnabled(not enabled)
        except Exception as e:
            self.info.err(e)

    def connection_succeed(self, connection):
        try:
            self.gps_active = True
            self.gpsCon = connection
            self.gpsCon.stateChanged.connect(self.status_changed)
            self.watchdog.start()
        except Exception as e:
            self.info.err(e)

    def connection_failed(self):
        if not self.gps_active:
            self.actionGPStoggle.setChecked(False)
            self.info.msg("GPS konnte nicht initialisiert werden!")
            self.info.log('GPS connection failed')
            self.enableGPSfunctions(False)

    def deactivate(self):
        try:
            if self.iface.mainWindow() is None: return  # dummy
        except:
            # prevent: ERROR:  <class 'RuntimeError'> gto_gps.py | line: 240 | ('wrapped C/C++ object of type QgisInterface has been deleted',)
            # because QGIS is already closing
            return
        try:
            if self.debug: self.info.log('gps deactivate')
            self.watchdog.stop()
            self.debugXoffset = 0
            self.debugYoffset = 0
            self.prevTime = None
            self.actionGPSstreaming.setChecked(False)
            self.enableGPSfunctions(False)
            self.prevPointXY = None
            if self.gpsCon is not None:
                if self.actionGPSlog.isChecked():
                    self.gpsLog.log('gps deactivate')
                self.gpsCon.close()
            if self.canvas is not None:
                self.canvas.scene().removeItem(self.marker)
            self.gps_active = False
        except Exception as e:
            self.info.err(e)

    def init_marker(self):
        try:
            if self.actionGPSlog.isChecked(): self.info.log('gps init_marker')
            self.marker = QgsVertexMarker(self.canvas)
            self.marker.setColor(QColor(255, 0, 0))  # (R,G,B)
            self.marker.setIconSize(10)
            self.circle = QgsVertexMarker.ICON_CIRCLE
            self.marker.setIconType(self.circle)
            #        ICON_BOX
            #        ICON_CIRCLE
            #        ICON_CROSS
            #        ICON_DOUBLE_TRIANGLE
            #        ICON_NONE
            #        ICON_X
            self.marker.setPenWidth(3)
        except Exception as e:
            self.info.err(e)

    def init_transformation(self, wgs_crs):
        try:
            self.src_crs = QgsCoordinateReferenceSystem(wgs_crs)
            self.transformation = QgsCoordinateTransform(
                self.src_crs, self.prj_crs, self.prj)
        except Exception as e:
            self.info.err(e)

    def status_changed(self, gpsInfo):
        try:
            if self.gpsCon.status() != 3: return  # 3=data received
            self.watchdog.stop()
            self.watchdog.start()
            if gpsInfo.longitude == 0: return
            valid = False
            self.dic_gpsinfo = {
                "direction": gpsInfo.direction,
                "elevation": gpsInfo.elevation,
                "fixMode": gpsInfo.fixMode,
                "fixType": gpsInfo.fixType,
                "hacc": gpsInfo.hacc,
                "satInfoComplete": gpsInfo.satInfoComplete,
                "speed": gpsInfo.speed,
                "utcDateTime": gpsInfo.utcDateTime,
                "vacc": gpsInfo.vacc,
                "status": gpsInfo.status,
                "hdop": gpsInfo.hdop,
                "vdop": gpsInfo.vdop,
                "pdop": gpsInfo.pdop,
                "latitude": gpsInfo.latitude,
                "longitude": gpsInfo.longitude,
                "satellitesInView": gpsInfo.satellitesInView,
                "satellitesUsed": gpsInfo.satellitesUsed,
                "quality": gpsInfo.quality
            }
            if self.actionGPSlog.isChecked():
                self.gpsLog.log('direction:', gpsInfo.direction)
                self.gpsLog.log('elevation:', gpsInfo.elevation)
                self.gpsLog.log('fixMode:', gpsInfo.fixMode)
                self.gpsLog.log('fixType:', gpsInfo.fixType)
                self.gpsLog.log('hacc:', gpsInfo.hacc)
                self.gpsLog.log('satInfoComplete:', gpsInfo.satInfoComplete)
                self.gpsLog.log('speed:', gpsInfo.speed)
                self.gpsLog.log('utcDateTime:', gpsInfo.utcDateTime.toString())
                self.gpsLog.log('vacc:', gpsInfo.vacc)
                self.gpsLog.log('status:', gpsInfo.status)
                self.gpsLog.log('hdop:', gpsInfo.hdop)
                self.gpsLog.log('vdop:', gpsInfo.vdop)
                self.gpsLog.log('pdop:', gpsInfo.pdop)
                self.gpsLog.log('latitude:', gpsInfo.latitude)
                self.gpsLog.log('longitude:', gpsInfo.longitude)
                self.gpsLog.log('satellitesInView:',
                                len(gpsInfo.satellitesInView))
                self.gpsLog.log('satellitesUsed:', gpsInfo.satellitesUsed)
                self.gpsLog.log('quality:', gpsInfo.quality)
                self.gpsLog.log("---------------------")
            if gpsInfo.pdop >= self.pdop:  # gps ok
                valid = True
                self.enableGPSfunctions(True)
                self.marker.setColor(QColor(0, 200, 0))
            else:

                self.enableGPSfunctions(False)
                self.marker.setColor(QColor(255, 0, 0))
            wgs84_pointXY = QgsPointXY(gpsInfo.longitude, gpsInfo.latitude)
            wgs84_point = QgsPoint(wgs84_pointXY)

            wgs84_point.transform(self.transformation)
            if self.gps_debug:
                self.debugXoffset = self.debugXoffset + random.randint(
                    0, int(self.gps_streaming_distance))
                self.debugYoffset = self.debugYoffset + random.randint(
                    0, int(self.gps_streaming_distance))
                x = wgs84_point.x() + self.debugXoffset
                y = wgs84_point.y() + self.debugYoffset
            else:
                x = wgs84_point.x()
                y = wgs84_point.y()
            if self.actionGPSlog.isChecked():
                self.gpsLog.log("x:", x)
                self.gpsLog.log("y:", y)
                self.gpsLog.log("--------------------")
            mapPointXY = QgsPointXY(x, y)
            self.lastGpsInfo = gtoGPSinfo(gpsInfo, mapPointXY, valid)
            # map
            if self.center and valid:
                self.canvas.setCenter(mapPointXY)
                if self.scale > 0: self.canvas.zoomScale(self.scale)
            self.marker.setCenter(mapPointXY)
            self.marker.show()
            # streaming
            gpsDateTime = None
            diff = 0
            if self.pointLayer is not None:
                self.actionGPSaddPoint.setEnabled(valid)
            if self.actionGPSstreaming.isChecked(
            ) and valid and not self.actionGPSaddVertexTool.isChecked():
                if self.prevTime is None:
                    self.prevTime = gpsInfo.utcDateTime.currentDateTime()
                else:
                    gpsDateTime = gpsInfo.utcDateTime.currentDateTime()
                    diff = self.prevTime.msecsTo(gpsDateTime)
                    if diff < self.timer_intervall:
                        return

                if self.prevPointXY is None:
                    self.prevPointXY = mapPointXY
                    self.prevTime = gpsDateTime
                    if self.pointLayer is not None:
                        self.addPoint()
                    else:
                        if self.gps_stream_mode == 1:
                            self.gps_click()
                        elif self.gps_stream_mode == 2:
                            self.gps_click()
                            self.addVertex(mapPointXY)
                        else:
                            self.addVertex(mapPointXY)
                else:
                    qgsDistance = QgsDistanceArea()
                    distance = qgsDistance.measureLine(
                        [self.prevPointXY, mapPointXY])
                    if distance > self.gps_streaming_distance and diff > self.timer_intervall:
                        self.prevTime = gpsDateTime
                        self.prevPointXY = mapPointXY
                        if self.pointLayer is not None:
                            self.addPoint()
                        else:
                            if self.gps_stream_mode == 1:
                                self.gps_click()
                            elif self.gps_stream_mode == 2:
                                self.gps_click()
                                self.addVertex(mapPointXY)
                            else:
                                self.addVertex(mapPointXY)
        except Exception as e:
            self.info.err(e)

    def gps_click(self):
        from pynput.mouse import Button, Controller
        try:
            if self.gps_active:
                if self.debug: self.gpsLog.log('gps_click')
                actionAddFeature = self.iface.mainWindow().findChild(
                    QAction, 'mActionAddFeature')
                gtoGpsInfo = self.lastGpsInfo
                if gtoGpsInfo.isValid and actionAddFeature.isChecked():
                    self.mouse = Controller()
                    mapX = gtoGpsInfo.mapPointXY.x()
                    mapY = gtoGpsInfo.mapPointXY.y()
                    pointXY = self.canvas.getCoordinateTransform().transform(
                        mapX, mapY)  # map coords to device coords
                    x = pointXY.x()
                    y = pointXY.y()
                    p = self.canvas.mapToGlobal(QPoint(
                        x, y))  # device coords to screen coords
                    x = p.x()
                    y = p.y()
                    prevX, prevY = self.mouse.position
                    self.mouse.position = (x, y)
                    if self.debug: self.gpsLog.log('addPoint', x, "/", y)
                    self.mouse.click(Button.left, 1)
                    self.mouse.position = (prevX, prevY)
        except Exception as e:
            self.info.err(e)

    def layer_changed(self):
        try:
            self.actionGPSstreaming.setChecked(False)
            if self.gps_active:
                geoType = self.iface.activeLayer().geometryType()
                if geoType == QgsWkbTypes.GeometryType.PointGeometry or self.pointLayer is not None:
                    self.actionGPSaddPoint.setEnabled(True)
                else:
                    self.actionGPSaddPoint.setEnabled(False)
        except Exception as e:
            self.info.err(e)

    def streaming(self):
        try:
            self.actionGPSsave.setEnabled(False)
            self.actionGPSaddVertexTool.setChecked(False)
            self.activateVertexTool()
            if self.pointLayer is not None:
                return
            if self.actionGPSstreaming.isChecked():
                if self.streamLayer is not None:
                    self.actionGPSstreaming.setChecked(False)
                    res = self.saveGeometry()
                    if res == QMessageBox.Cancel: return
                    self.actionGPSstreaming.setChecked(True)
                geoType = self.iface.activeLayer().geometryType()
                if geoType == QgsWkbTypes.GeometryType.PolygonGeometry or geoType == QgsWkbTypes.GeometryType.LineGeometry:
                    self.streamLayer = self.iface.activeLayer()
                    self.actionGPSaddVertexTool.setEnabled(True)
                    self.rb.reset(geoType)
                else:
                    self.actionGPSstreaming.setChecked(False)
                    self.actionGPSaddVertexTool.setEnabled(False)
                    self.info.msg(
                        "Aktiver Layer muss vom Typ Polgone oder Line sein!")
            else:
                self.actionGPSaddVertexTool.setEnabled(False)
                if self.streamLayer is not None:
                    geoType = self.streamLayer.geometryType()
                    tools = self.settings.get('gps_afterstream_tools', [])
                    if geoType == QgsWkbTypes.GeometryType.PolygonGeometry:
                        if self.rb.numberOfVertices() > 2:
                            self.actionGPSsave.setEnabled(True)
                            self.gtomain.runcmd(tools)
                    if geoType == QgsWkbTypes.GeometryType.LineGeometry:
                        if self.rb.numberOfVertices() > 1:
                            self.actionGPSsave.setEnabled(True)
                            self.gtomain.runcmd(tools)
        except Exception as e:
            self.info.err(e)

    def addPoint(self):
        try:
            if self.gps_active:
                if self.actionGPSlog.isChecked(): self.gpsLog.log('addPoint')
                gtoGpsInfo = self.lastGpsInfo
                if gtoGpsInfo.isValid:
                    if self.pointLayer is not None:
                        layer = self.pointLayer
                    else:
                        layer = self.iface.activeLayer()
                    geoType = layer.geometryType()
                    if geoType != QgsWkbTypes.GeometryType.PointGeometry:
                        self.info.msg("Kein Punktlayer ausgewählt!")
                        return
                    # create feature
                    feat = QgsVectorLayerUtils.createFeature(layer)
                    tr = QgsCoordinateTransform(self.prj_crs, layer.crs(),
                                                self.prj)
                    tr_point = tr.transform(gtoGpsInfo.mapPointXY)
                    geo = QgsGeometry.fromPointXY(tr_point)
                    feat.setGeometry(geo)
                    # set attributes
                    gps_addpoint_attributes = self.settings.get(
                        "gps_addpoint_attributes", {})
                    for k, v in gps_addpoint_attributes.items():
                        feat[k] = layer.fields().field(k).convertCompatible(
                            self.dic_gpsinfo[v])
                    # add to layer
                    if self.pointLayer is not None:
                        # add to provider
                        (res,
                         outFeats) = layer.dataProvider().addFeatures([feat])
                        layer.select(outFeats[0].id())
                    else:
                        if not layer.isEditable(): layer.startEditing()
                        layer.beginEditCommand('Add stream geometry')
                        layer.addFeatures([feat])
                        layer.endEditCommand()
                        self.helper.refreshLayer(layer)
        except Exception as e:
            self.info.err(e)

    def saveGeometry(self, force=False):
        try:
            if self.streamLayer is not None:
                if not force:
                    res = self.info.gtoQuestion(
                        "GPS Stream-Geometry in Layer \n{0}\nspeichern?".
                        format(self.streamLayer.name()),
                        title='GPS Streaming',
                        btns=QMessageBox.Yes | QMessageBox.No
                        | QMessageBox.Cancel,
                        parent=self.iface.mainWindow())
                    if res == QMessageBox.Cancel: return res
                    if res == QMessageBox.No:
                        self.actionGPSsave.setEnabled(False)
                        self.streamLayer = None
                        self.rb.reset()
                        return
                # create feature
                feat = QgsVectorLayerUtils.createFeature(self.streamLayer)
                geo = self.rb.asGeometry()
                feat.setGeometry(geo)
                # add to layer
                if not self.streamLayer.isEditable():
                    self.streamLayer.startEditing()
                self.streamLayer.beginEditCommand('Add stream geometry')
                self.streamLayer.addFeatures([feat])
                self.streamLayer.endEditCommand()
                # add to provider
                # (res, outFeats) = self.streamLayer.dataProvider().addFeatures([feat])
                # self.streamLayer.select(outFeats[0].id())

                # reset streaming
                self.actionGPSsave.setEnabled(False)
                self.streamLayer = None
                self.rb.reset()
        except Exception as e:
            self.streamLayer.destroyEditCommand()
            self.info.err(e)

    def addVertex(self, point):
        try:
            self.rb.addPoint(point)
        except Exception as e:
            self.info.err(e)

    def activateVertexTool(self):
        try:
            if self.actionGPSaddVertexTool.isChecked():
                if self.actionGPSlog.isChecked():
                    self.gpsLog.log('activated VertexTool: stream paused')
                self.LastMapTool = self.canvas.mapTool()
                self.canvas.setMapTool(self.streamTool)
            else:
                if self.actionGPSlog.isChecked():
                    self.gpsLog.log('deactivated VertexTool: stream resumed')
                self.canvas.setMapTool(self.LastMapTool)
        except Exception as e:
            self.info.err(e)

    def tool_isactive(self, active):
        try:
            pass
        except Exception as e:
            self.info.err(e)
class PlanetMainFilters(MAIN_FILTERS_BASE, MAIN_FILTERS_WIDGET,
                        PlanetFilterMixin):

    leAOI: QLineEdit

    filtersChanged = pyqtSignal()
    savedSearchSelected = pyqtSignal(object)
    zoomToAOIRequested = pyqtSignal()

    def __init__(self, iface, parent=None, plugin=None,
                no_saved_search=False, color=MAIN_AOI_COLOR):
        super().__init__(parent=parent)
        self._iface: QgisInterface = iface
        self._plugin = plugin

        self.setupUi(self)

        self.emitFiltersChanged = False

        self.color = color

        self._aoi_box = QgsRubberBand(self._iface.mapCanvas(),
                                      QgsWkbTypes.PolygonGeometry)
        self._aoi_box.setFillColor(QColor(0, 0, 0, 0))
        self._aoi_box.setStrokeColor(color)
        self._aoi_box.setWidth(3)
        self._aoi_box.setLineStyle(Qt.DashLine)

        self._canvas: QgsMapCanvas = self._iface.mapCanvas()
        # This may later be a nullptr, if no active tool when queried
        self._cur_maptool = None

        # noinspection PyUnresolvedReferences
        self.leAOI.textChanged['QString'].connect(self.filters_changed)
        # noinspection PyUnresolvedReferences
        self.leAOI.textEdited['QString'].connect(self.validate_edited_aoi)

        self._setup_tool_buttons()

        # Extent line edit tools
        self.btnZoomToAOI.clicked.connect(self.zoom_to_aoi)
        self.btnCopyAOI.clicked.connect(self.copy_aoi_to_clipboard)

        self.p_client = PlanetClient.getInstance()
        self.p_client.loginChanged.connect(self.populate_saved_searches)

        self.comboSavedSearch.currentIndexChanged.connect(self.saved_search_selected)

        if no_saved_search:
            self.comboSavedSearch.setVisible(False)

    def populate_saved_searches(self, is_logged):
        if is_logged:
            self.comboSavedSearch.clear()
            self.comboSavedSearch.blockSignals(True)
            self.comboSavedSearch.addItem("[Select a Saved Search]")
            res = self.p_client.get_searches().get()
            for search in res["searches"]:
                self.comboSavedSearch.addItem(search["name"], search)
            self.comboSavedSearch.blockSignals(False)

    def add_saved_search(self, request):
        self.comboSavedSearch.blockSignals(True)
        self.comboSavedSearch.addItem(request["name"], request)
        self.comboSavedSearch.setCurrentIndex(self.comboSavedSearch.count() - 1)
        self.comboSavedSearch.blockSignals(False)

    def saved_search_selected(self, idx):
        if idx == 0:
            return
        request = self.comboSavedSearch.currentData()
        analytics_track("saved_search_accessed")
        self.savedSearchSelected.emit(request)

    def null_out_saved_search(self):
        self.comboSavedSearch.blockSignals(True)
        self.comboSavedSearch.setCurrentIndex(0)
        self.comboSavedSearch.blockSignals(False)

    def reset_aoi_box(self):
        self.leAOI.setText("")
        if self._aoi_box:
            self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)

    def filters(self):
        filters = []
        if self.leAOI.text():
            # TODO: Validate GeoJSON; try planet.api.utils.probably_geojson()
            # noinspection PyBroadException
            try:
                qgsgeom = qgsgeometry_from_geojson(self.leAOI.text())
                if not qgsgeom.isEmpty():
                    geom_json = json.loads(qgsgeom.asJson())
                    filters.append(geom_filter(geom_json))
                else:
                    self._show_message("AOI not valid GeoJSON polygon",
                                       level=Qgis.Warning,
                                       duration=10)
            except Exception:
                self._show_message("AOI not valid JSON",
                                   level=Qgis.Warning,
                                   duration=10)
            finally:
                return filters

    def filters_as_json(self):
        filters = []
        if self.leAOI.text():
            filters.append(self.leAOI.text())

        return filters

    def set_from_request(self, request):
        self.emitFiltersChanged = False
        filters = filters_from_request(request, "geometry")
        if filters:
            geom = filters[0]["config"]
            txt = json.dumps(geom)
            self.leAOI.setText(txt)
        else:
            self.leAOI.setText("")
        self.emitFiltersChanged = True

    @pyqtSlot('QString')
    def filters_changed(self, value):
        if self.emitFiltersChanged:# noinspection PyUnresolvedReferences
            self.filtersChanged.emit()

    @pyqtSlot()
    def clean_up(self):
        self.reset_aoi_box()

    def _setup_tool_buttons(self):
        extent_menu = QMenu(self)

        canvas_act = QAction('Current visible extent', extent_menu)
        # noinspection PyUnresolvedReferences
        canvas_act.triggered[bool].connect(self.aoi_from_current_extent)
        extent_menu.addAction(canvas_act)

        active_act = QAction('Active map layer extent', extent_menu)
        # noinspection PyUnresolvedReferences
        active_act.triggered[bool].connect(self.aoi_from_active_layer_extent)
        extent_menu.addAction(active_act)

        full_act = QAction('All map layers extent', extent_menu)
        # noinspection PyUnresolvedReferences
        full_act.triggered[bool].connect(self.aoi_from_full_extent)
        extent_menu.addAction(full_act)

        self.btnExtent.setMenu(extent_menu)

        # Also show menu on click, to keep disclosure triangle visible
        self.btnExtent.clicked.connect(self.btnExtent.showMenu)

        draw_menu = QMenu(self)

        box_act = QAction('Rectangle', draw_menu)
        # noinspection PyUnresolvedReferences
        box_act.triggered[bool].connect(self.aoi_from_box)
        draw_menu.addAction(box_act)

        circle_act = QAction('Circle', draw_menu)
        # noinspection PyUnresolvedReferences
        circle_act.triggered[bool].connect(self.aoi_from_circle)
        draw_menu.addAction(circle_act)

        polygon_act = QAction('Polygon', draw_menu)
        # noinspection PyUnresolvedReferences
        polygon_act.triggered[bool].connect(self.aoi_from_polygon)
        draw_menu.addAction(polygon_act)

        self.btnDraw.setMenu(draw_menu)
        # Also show menu on click, to keep disclosure triangle visible
        self.btnDraw.clicked.connect(self.btnDraw.showMenu)

        selection_menu = QMenu(self)

        self.single_select_act = QAction('Single feature', selection_menu)
        # noinspection PyUnresolvedReferences
        self.single_select_act.triggered[bool].connect(self.aoi_from_feature)
        selection_menu.addAction(self.single_select_act)

        self.bound_select_act = QAction('Multiple features (bounding box)',
                                        selection_menu)
        # noinspection PyUnresolvedReferences
        self.bound_select_act.triggered[bool].connect(self.aoi_from_bound)
        selection_menu.addAction(self.bound_select_act)

        self.btnSelection.setMenu(selection_menu)
        # Also show menu on click, to keep disclosure triangle visible
        self.btnSelection.clicked.connect(self._toggle_selection_tools)
        self.btnSelection.clicked.connect(self.btnSelection.showMenu)

        upload_menu = QMenu(self)

        upload_act = QAction('Upload vector layer file', upload_menu)
        upload_act.triggered[bool].connect(self.upload_file)
        upload_menu.addAction(upload_act)


        self.btnUpload.setMenu(upload_menu)
        self.btnUpload.clicked.connect(self.btnUpload.showMenu)

    def upload_file(self):
        filename, _ = QFileDialog.getOpenFileName(self, "Select AOI file", "",
                                                  "All files(*.*)")
        if filename:
            layer = QgsVectorLayer(filename, "")
            self.aoi_from_layer(layer)

    def aoi_from_layer(self, layer):
        if not layer.isValid():
            self._show_message("Invalid layer",
                               level=Qgis.Warning,
                               duration=10)
        else:
            feature = next(layer.getFeatures(), None)
            if feature is None:
                self._show_message("Layer contains no features",
                                   level=Qgis.Warning,
                                   duration=10)
            else:
                geom = feature.geometry()

                transform = QgsCoordinateTransform(
                    layer.crs(),
                    QgsCoordinateReferenceSystem("EPSG:4326"),
                    QgsProject.instance())

                try:
                    geom.transform(transform)
                except QgsCsException as e:
                    self._show_message("Could not convert AOI to EPSG:4326",
                           level=Qgis.Warning,
                           duration=10)
                    return

                geom_json = geom.asJson(precision=6)

                self._aoi_box.setToGeometry(geom)

                self.leAOI.setText(geom_json)

                log.debug('AOI set to layer')

                self.zoom_to_aoi()

    def _toggle_selection_tools(self):
        active_layer = self._iface.activeLayer()
        is_vector = isinstance(active_layer, QgsVectorLayer)
        if is_vector and active_layer.selectedFeatureCount():
            if active_layer.selectedFeatureCount() > 1:
                self.single_select_act.setEnabled(False)
                self.bound_select_act.setEnabled(True)
            elif active_layer.selectedFeatureCount():
                self.single_select_act.setEnabled(True)
                self.bound_select_act.setEnabled(False)
            else:
                self.single_select_act.setEnabled(False)
                self.bound_select_act.setEnabled(False)
        else:
            self.single_select_act.setEnabled(False)
            self.bound_select_act.setEnabled(False)

    @pyqtSlot()
    # noinspection PyArgumentList
    def aoi_from_current_extent(self):
        """Return current map extent as geojson transformed to EPSG:4326
        """
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        canvas = self._iface.mapCanvas()
        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance()
        )

        canvas_extent: QgsRectangle = canvas.extent()
        try:
            transform_extent = transform.transformBoundingBox(canvas_extent)
        except QgsCsException as e:
            self._show_message("Could not convert AOI to EPSG:4326",
                   level=Qgis.Warning,
                   duration=10)
            return
        # noinspection PyArgumentList
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson(precision=6)

        # noinspection PyArgumentList
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(canvas.extent()))

        self.leAOI.setText(extent_json)

        log.debug('AOI set to canvas extent')

        self.zoom_to_aoi()

    @pyqtSlot()
    # noinspection PyArgumentList
    def aoi_from_active_layer_extent(self):
        """Return active map layer extent as geojson transformed to EPSG:4326
        """
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        map_layer: QgsMapLayer = self._iface.activeLayer()
        if map_layer is None:
            log.debug('No active layer selected, skipping AOI extent')
            return

        if not map_layer.isValid():
            log.debug('Active map layer invalid, skipping AOI extent')
            return

        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            map_layer.crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance())

        ml_extent: QgsRectangle = map_layer.extent()
        try:
            transform_extent = transform.transformBoundingBox(ml_extent)
        except QgsCsException as e:
            self._show_message("Could not convert AOI to EPSG:4326",
                   level=Qgis.Warning,
                   duration=10)
            return
        # noinspection PyArgumentList
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson(precision=6)

        # noinspection PyArgumentList,PyCallByClass
        #self._aoi_box.setToGeometry(QgsGeometry.fromRect(ml_extent))

        self.leAOI.setText(extent_json)

        log.debug('AOI set to active layer extent')

        self.zoom_to_aoi()

    @pyqtSlot()
    # noinspection PyArgumentList
    def aoi_from_full_extent(self):
        """Return full data map extent as geojson transformed to EPSG:4326
        """
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        canvas = self._iface.mapCanvas()

        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance())

        canvas_extent: QgsRectangle = canvas.fullExtent()
        if canvas_extent.isNull(): # Canvas not yet initialized
            return
        try:
            transform_extent = transform.transformBoundingBox(canvas_extent)
        except QgsCsException as e:
            self._show_message("Could not convert AOI to EPSG:4326",
                   level=Qgis.Warning,
                   duration=10)
            return
        # noinspection PyArgumentList
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson(precision=6)

        # noinspection PyArgumentList,PyCallByClass
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(canvas_extent))

        self.leAOI.setText(extent_json)

        log.debug('AOI set to full data extent')

        self.zoom_to_aoi()

    @pyqtSlot()
    def aoi_from_box(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetExtentMapTool(self._iface.mapCanvas())
        self._iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.extentSelected.connect(self.set_draw_aoi)

    @pyqtSlot()
    def aoi_from_circle(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetCircleMapTool(self._iface.mapCanvas())
        self._iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.circleSelected.connect(self.set_draw_aoi)

    @pyqtSlot()
    def aoi_from_polygon(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetPolyMapTool(self._iface.mapCanvas())
        self._iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.polygonSelected.connect(self.set_draw_aoi)

    @pyqtSlot(object)
    def set_draw_aoi(self, aoi):
        # noinspection PyArgumentList
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance())

        aoi_json = None

        if isinstance(aoi, QgsRectangle):
            aoi_geom = QgsGeometry().fromRect(aoi)
            self._aoi_box.setToGeometry(aoi_geom)
            aoi_geom.transform(transform)
            aoi_json = aoi_geom.asJson(precision=6)

        if isinstance(aoi, QgsGeometry):
            self._aoi_box.setToGeometry(aoi)
            # TODO: validate geom is less than 500 vertices
            aoi.transform(transform)
            aoi_json = aoi.asJson(precision=6)

        if aoi_json:
            self.leAOI.setText(aoi_json)

            # noinspection PyUnresolvedReferences
            self._show_message('AOI set to drawn figure')
            self.zoom_to_aoi()
            if self._cur_maptool is not None:
                # Restore previously used maptool
                self._canvas.setMapTool(self._cur_maptool)
                self._cur_maptool = None
            else:
                # Fallback to activating pan tool
                self._iface.actionPan().trigger()
        else:
            # noinspection PyUnresolvedReferences
            self._show_message('AOI unable to be set',
                               level=Qgis.Warning,
                               duration=10)

    @pyqtSlot()
    def aoi_from_feature(self):
        layer = self._iface.activeLayer()
        if not isinstance(layer, QgsVectorLayer):
            self._show_message('Active layer must be a vector layer.',
                               level=Qgis.Warning,
                               duration=10)
            return

        if layer.selectedFeatureCount() > 1:
            self._show_message('More than 1 feature. Searching by bbox.',
                               level=Qgis.Warning,
                               duration=10)
            self.aoi_from_bound()
            return
        elif layer.selectedFeatureCount() < 1:
            self._show_message('No features selected.',
                               level=Qgis.Warning,
                               duration=10)
            return

        selected: QgsFeature = layer.selectedFeatures()[0]
        geom: QgsGeometry = selected.geometry()

        if geom.constGet().vertexCount() > 500:
            self._show_message(
                               'More than 500 vertices. Searching by bbox.',
                               level=Qgis.Warning,
                               duration=10
            )
            self.aoi_from_bound()
            return

        # noinspection PyArgumentList
        trans_layer = QgsCoordinateTransform(
            layer.sourceCrs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance()
        )

        # noinspection PyArgumentList
        trans_canvas = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(),
            QgsProject.instance()
        )

        # geom.transform(transform)
        geom.transform(trans_layer)
        geom_json = geom.asJson(precision=6)
        self.leAOI.setText(geom_json)

        geom.transform(trans_canvas)
        self._aoi_box.setToGeometry(
            geom,
            QgsCoordinateReferenceSystem("EPSG:4326")
        )
        self.zoom_to_aoi()

    @pyqtSlot()
    def aoi_from_bound(self):
        layer = self._iface.activeLayer()
        if not isinstance(layer, QgsVectorLayer):
            self._show_message('Active layer must be a vector layer.',
                               level=Qgis.Warning,
                               duration=10)
            return

        if layer.selectedFeatureCount() < 1:
            self._show_message('No features selected.',
                               level=Qgis.Warning,
                               duration=10)
            return

        bbox = layer.boundingBoxOfSelected()

        # noinspection PyArgumentList
        trans_layer = QgsCoordinateTransform(
            layer.sourceCrs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance()
        )

        # noinspection PyArgumentList
        trans_canvas = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(),
            QgsProject.instance()
        )

        transform_bbox = trans_layer.transformBoundingBox(bbox)
        # noinspection PyArgumentList
        geom_bbox = QgsGeometry.fromRect(transform_bbox)
        bbox_json = geom_bbox.asJson(precision=6)

        self.leAOI.setText(bbox_json)

        bbox_canvas = trans_canvas.transformBoundingBox(transform_bbox)
        # noinspection PyArgumentList
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(bbox_canvas))

        self.zoom_to_aoi()

    def hide_aoi_if_matches_geom(self, geom):
            color = (QColor(0, 0, 0, 0) if self._aoi_box.asGeometry().equals(geom)
                    else self.color)
            self._aoi_box.setStrokeColor(color)

    def show_aoi(self):
        if self._aoi_box is not None:
            self._aoi_box.setStrokeColor(self.color)

    def aoi_geom(self):
        if self._aoi_box is not None:
            return self._aoi_box.asGeometry()

    def aoi_as_4326_geom(self):
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance()
        )
        geom = self.aoi_geom()
        if geom is not None:
            geom.transform(transform)
        return geom


    @pyqtSlot()
    def zoom_to_aoi(self):
        if not self._iface:
            log.debug('No iface object, skipping AOI extent')
            return

        if not self.leAOI.text():
            log.debug('No AOI defined, skipping zoom to AOI')
            return

        geom: QgsGeometry = qgsgeometry_from_geojson(self.leAOI.text())
        if geom.isEmpty():
            self._show_message('AOI GeoJSON geometry invalid',
                   level=Qgis.Warning,
                   duration=10)
            return

        self._aoi_box.setToGeometry(
            geom,
            QgsCoordinateReferenceSystem("EPSG:4326")
        )

        self.show_aoi()

        zoom_canvas_to_aoi(self.leAOI.text())

        self.zoomToAOIRequested.emit()

    @pyqtSlot()
    def copy_aoi_to_clipboard(self):
        if not self.leAOI.text():
            log.debug('No AOI defined, skipping zoom to AOI')
            return

        try:
            json_obj = json.loads(self.leAOI.text())
        except ValueError:
            return

        json_geom_txt = json.dumps(json_obj, indent=2)

        cb = QgsApplication.clipboard()
        cb.setText(json_geom_txt)

        # noinspection PyUnresolvedReferences
        self._show_message('AOI copied to clipboard')

    @pyqtSlot()
    def validate_aoi(self):
        # TODO:gather existing validation logic here
        # TODO: Check for valid json.loads

        # TODO: Check API verticie limit of 500
        pass

    @pyqtSlot()
    def validate_edited_aoi(self):
        json_txt = self.leAOI.text()
        if not json_txt:
            self.reset_aoi_box()
            log.debug('No AOI defined, skipping validation')
            return

        try:
            json_obj = json.loads(json_txt)
        except ValueError:
            # noinspection PyUnresolvedReferences
            self._show_message('AOI GeoJSON is invalid',
                               level=Qgis.Warning,
                               duration=10)
            return

        try:
            json_geom = geometry_from_json(json_obj)
        except:
            json_geom = None

        if not json_geom:
            # noinspection PyUnresolvedReferences
            self._show_message('AOI GeoJSON geometry invalid',
                               level=Qgis.Warning,
                               duration=10)
            return

        geom: QgsGeometry = qgsgeometry_from_geojson(json_geom)
        self._aoi_box.setToGeometry(
            geom,
            QgsCoordinateReferenceSystem("EPSG:4326")
        )

        self.leAOI.blockSignals(True)
        self.leAOI.setText(json.dumps(json_geom))
        self.leAOI.blockSignals(False)

        self.zoom_to_aoi()
Example #24
0
class PlanetAOIFilter(AOI_FILTER_BASE, AOI_FILTER_WIDGET, PlanetFilterMixin):

    filtersChanged = pyqtSignal()
    savedSearchSelected = pyqtSignal(object)
    zoomToAOIRequested = pyqtSignal()

    def __init__(
        self,
        parent=None,
        plugin=None,
        color=MAIN_AOI_COLOR,
    ):
        super().__init__(parent=parent)
        self._plugin = plugin

        self.setupUi(self)

        self.emitFiltersChanged = False

        self.color = color

        self._aoi_box = QgsRubberBand(iface.mapCanvas(),
                                      QgsWkbTypes.PolygonGeometry)
        self._aoi_box.setFillColor(QColor(0, 0, 0, 0))
        self._aoi_box.setStrokeColor(color)
        self._aoi_box.setWidth(3)
        self._aoi_box.setLineStyle(Qt.DashLine)

        self._canvas: QgsMapCanvas = iface.mapCanvas()
        # This may later be a nullptr, if no active tool when queried
        self._cur_maptool = None

        self.leAOI.textChanged["QString"].connect(self.filters_changed)
        self.leAOI.textEdited["QString"].connect(self.validate_edited_aoi)

        self._setup_tool_buttons()

        # Extent line edit tools
        self.btnZoomToAOI.clicked.connect(self.zoom_to_aoi)
        self.btnCopyAOI.clicked.connect(self.copy_aoi_to_clipboard)

        self.p_client = PlanetClient.getInstance()

    def reset_aoi_box(self):
        self.leAOI.setText("")
        if self._aoi_box:
            self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)

    def filters(self):
        filters = []
        if self.leAOI.text():
            try:
                qgsgeom = qgsgeometry_from_geojson(self.leAOI.text())
                if not qgsgeom.isEmpty():
                    geom_json = json.loads(qgsgeom.asJson())
                    filters.append(geom_filter(geom_json))
                else:
                    self._show_message("AOI not valid GeoJSON polygon",
                                       level=Qgis.Warning,
                                       duration=10)
            except Exception:
                self._show_message("AOI not valid JSON",
                                   level=Qgis.Warning,
                                   duration=10)
            finally:
                return filters

    def filters_as_json(self):
        filters = []
        if self.leAOI.text():
            filters.append(self.leAOI.text())

        return filters

    def set_from_request(self, request):
        self.emitFiltersChanged = False
        filters = filters_from_request(request, "geometry")
        if filters:
            geom = filters[0]["config"]
            txt = json.dumps(geom)
            self.leAOI.setText(txt)
        else:
            self.leAOI.setText("")
        self.emitFiltersChanged = True

    @pyqtSlot("QString")
    def filters_changed(self, value):
        if self.emitFiltersChanged:
            self.filtersChanged.emit()

    @pyqtSlot()
    def clean_up(self):
        self.reset_aoi_box()

    def _setup_tool_buttons(self):
        extent_menu = QMenu(self)

        canvas_act = QAction("Current visible extent", extent_menu)
        canvas_act.triggered[bool].connect(self.aoi_from_current_extent)
        extent_menu.addAction(canvas_act)

        active_act = QAction("Active map layer extent", extent_menu)
        active_act.triggered[bool].connect(self.aoi_from_active_layer_extent)
        extent_menu.addAction(active_act)

        full_act = QAction("All map layers extent", extent_menu)
        full_act.triggered[bool].connect(self.aoi_from_full_extent)
        extent_menu.addAction(full_act)

        self.btnExtent.setMenu(extent_menu)

        # Also show menu on click, to keep disclosure triangle visible
        self.btnExtent.clicked.connect(self.btnExtent.showMenu)

        draw_menu = QMenu(self)

        box_act = QAction("Rectangle", draw_menu)
        box_act.triggered[bool].connect(self.aoi_from_box)
        draw_menu.addAction(box_act)

        circle_act = QAction("Circle", draw_menu)
        circle_act.triggered[bool].connect(self.aoi_from_circle)
        draw_menu.addAction(circle_act)

        polygon_act = QAction("Polygon", draw_menu)
        polygon_act.triggered[bool].connect(self.aoi_from_polygon)
        draw_menu.addAction(polygon_act)

        self.btnDraw.setMenu(draw_menu)
        # Also show menu on click, to keep disclosure triangle visible
        self.btnDraw.clicked.connect(self.btnDraw.showMenu)

        selection_menu = QMenu(self)

        self.single_select_act = QAction("Single feature", selection_menu)
        self.single_select_act.triggered[bool].connect(self.aoi_from_feature)
        selection_menu.addAction(self.single_select_act)

        self.bound_select_act = QAction("Multiple features (bounding box)",
                                        selection_menu)
        self.bound_select_act.triggered[bool].connect(self.aoi_from_bound)
        selection_menu.addAction(self.bound_select_act)

        self.btnSelection.setMenu(selection_menu)
        # Also show menu on click, to keep disclosure triangle visible
        self.btnSelection.clicked.connect(self._toggle_selection_tools)
        self.btnSelection.clicked.connect(self.btnSelection.showMenu)

        upload_menu = QMenu(self)

        upload_act = QAction("Upload vector layer file", upload_menu)
        upload_act.triggered[bool].connect(self.upload_file)
        upload_menu.addAction(upload_act)

        self.btnUpload.setMenu(upload_menu)
        self.btnUpload.clicked.connect(self.btnUpload.showMenu)

    def upload_file(self):
        filename, _ = QFileDialog.getOpenFileName(self, "Select AOI file", "",
                                                  "All files(*.*)")
        if filename:
            layer = QgsVectorLayer(filename, "")
            self.aoi_from_layer(layer)

    def aoi_from_layer(self, layer):
        if not layer.isValid():
            self._show_message("Invalid layer",
                               level=Qgis.Warning,
                               duration=10)
        else:
            feature = next(layer.getFeatures(), None)
            if feature is None:
                self._show_message("Layer contains no features",
                                   level=Qgis.Warning,
                                   duration=10)
            else:
                geom = feature.geometry()

                transform = QgsCoordinateTransform(
                    layer.crs(),
                    QgsCoordinateReferenceSystem("EPSG:4326"),
                    QgsProject.instance(),
                )

                try:
                    geom.transform(transform)
                except QgsCsException:
                    self._show_message(
                        "Could not convert AOI to EPSG:4326",
                        level=Qgis.Warning,
                        duration=10,
                    )
                    return

                geom_json = geom.asJson(precision=6)

                self._aoi_box.setToGeometry(geom)

                self.leAOI.setText(geom_json)

                log.debug("AOI set to layer")

                self.zoom_to_aoi()

    def _toggle_selection_tools(self):
        active_layer = iface.activeLayer()
        is_vector = isinstance(active_layer, QgsVectorLayer)
        if is_vector and active_layer.selectedFeatureCount():
            if active_layer.selectedFeatureCount() > 1:
                self.single_select_act.setEnabled(False)
                self.bound_select_act.setEnabled(True)
            elif active_layer.selectedFeatureCount():
                self.single_select_act.setEnabled(True)
                self.bound_select_act.setEnabled(False)
            else:
                self.single_select_act.setEnabled(False)
                self.bound_select_act.setEnabled(False)
        else:
            self.single_select_act.setEnabled(False)
            self.bound_select_act.setEnabled(False)

    @pyqtSlot()
    def aoi_from_current_extent(self):
        """Return current map extent as geojson transformed to EPSG:4326"""
        canvas = iface.mapCanvas()
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance(),
        )

        canvas_extent: QgsRectangle = canvas.extent()
        try:
            transform_extent = transform.transformBoundingBox(canvas_extent)
        except QgsCsException:
            self._show_message("Could not convert AOI to EPSG:4326",
                               level=Qgis.Warning,
                               duration=10)
            return
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson(precision=6)

        self._aoi_box.setToGeometry(QgsGeometry.fromRect(canvas.extent()))

        self.leAOI.setText(extent_json)

        log.debug("AOI set to canvas extent")

        self.zoom_to_aoi()

    @pyqtSlot()
    def aoi_from_active_layer_extent(self):
        """Return active map layer extent as geojson transformed to EPSG:4326"""
        map_layer: QgsMapLayer = iface.activeLayer()
        if map_layer is None:
            log.debug("No active layer selected, skipping AOI extent")
            return

        if not map_layer.isValid():
            log.debug("Active map layer invalid, skipping AOI extent")
            return

        transform = QgsCoordinateTransform(
            map_layer.crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance(),
        )

        ml_extent: QgsRectangle = map_layer.extent()
        try:
            transform_extent = transform.transformBoundingBox(ml_extent)
        except QgsCsException:
            self._show_message("Could not convert AOI to EPSG:4326",
                               level=Qgis.Warning,
                               duration=10)
            return
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson(precision=6)

        self.leAOI.setText(extent_json)

        log.debug("AOI set to active layer extent")

        self.zoom_to_aoi()

    @pyqtSlot()
    def aoi_from_full_extent(self):
        """Return full data map extent as geojson transformed to EPSG:4326"""
        canvas = iface.mapCanvas()

        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance(),
        )

        canvas_extent: QgsRectangle = canvas.fullExtent()
        if canvas_extent.isNull():  # Canvas not yet initialized
            return
        try:
            transform_extent = transform.transformBoundingBox(canvas_extent)
        except QgsCsException:
            self._show_message("Could not convert AOI to EPSG:4326",
                               level=Qgis.Warning,
                               duration=10)
            return
        geom_extent = QgsGeometry.fromRect(transform_extent)
        extent_json = geom_extent.asJson(precision=6)
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(canvas_extent))

        self.leAOI.setText(extent_json)

        log.debug("AOI set to full data extent")

        self.zoom_to_aoi()

    @pyqtSlot()
    def aoi_from_box(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetExtentMapTool(iface.mapCanvas())
        iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.extentSelected.connect(self.set_draw_aoi)

    @pyqtSlot()
    def aoi_from_circle(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetCircleMapTool(iface.mapCanvas())
        iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.circleSelected.connect(self.set_draw_aoi)

    @pyqtSlot()
    def aoi_from_polygon(self):
        self._cur_maptool: QgsMapTool = self._canvas.mapTool()
        self._aoi_box.reset(QgsWkbTypes.PolygonGeometry)
        aoi_draw = PlanetPolyMapTool(iface.mapCanvas())
        iface.mapCanvas().setMapTool(aoi_draw)
        aoi_draw.polygonSelected.connect(self.set_draw_aoi)

    @pyqtSlot(object)
    def set_draw_aoi(self, aoi):
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance(),
        )

        aoi_json = None

        if isinstance(aoi, QgsRectangle):
            aoi_geom = QgsGeometry().fromRect(aoi)
            self._aoi_box.setToGeometry(aoi_geom)
            aoi_geom.transform(transform)
            aoi_json = aoi_geom.asJson(precision=6)

        if isinstance(aoi, QgsGeometry):
            self._aoi_box.setToGeometry(aoi)
            # TODO: validate geom is less than 500 vertices
            aoi.transform(transform)
            aoi_json = aoi.asJson(precision=6)

        if aoi_json:
            self.leAOI.setText(aoi_json)

            self._show_message("AOI set to drawn figure")
            self.zoom_to_aoi()
            if self._cur_maptool is not None:
                # Restore previously used maptool
                self._canvas.setMapTool(self._cur_maptool)
                self._cur_maptool = None
            else:
                # Fallback to activating pan tool
                iface.actionPan().trigger()
        else:
            self._show_message("AOI unable to be set",
                               level=Qgis.Warning,
                               duration=10)

    @pyqtSlot()
    def aoi_from_feature(self):
        layer = iface.activeLayer()
        if not isinstance(layer, QgsVectorLayer):
            self._show_message("Active layer must be a vector layer.",
                               level=Qgis.Warning,
                               duration=10)
            return

        if layer.selectedFeatureCount() > 1:
            self._show_message(
                "More than 1 feature. Searching by bbox.",
                level=Qgis.Warning,
                duration=10,
            )
            self.aoi_from_bound()
            return
        elif layer.selectedFeatureCount() < 1:
            self._show_message("No features selected.",
                               level=Qgis.Warning,
                               duration=10)
            return

        selected: QgsFeature = layer.selectedFeatures()[0]
        geom: QgsGeometry = selected.geometry()

        if geom.constGet().vertexCount() > 500:
            self._show_message(
                "More than 500 vertices. Searching by bbox.",
                level=Qgis.Warning,
                duration=10,
            )
            self.aoi_from_bound()
            return

        trans_layer = QgsCoordinateTransform(
            layer.sourceCrs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance(),
        )

        trans_canvas = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(),
            QgsProject.instance(),
        )

        # geom.transform(transform)
        geom.transform(trans_layer)
        geom_json = geom.asJson(precision=6)
        self.leAOI.setText(geom_json)

        geom.transform(trans_canvas)
        self._aoi_box.setToGeometry(geom,
                                    QgsCoordinateReferenceSystem("EPSG:4326"))
        self.zoom_to_aoi()

    @pyqtSlot()
    def aoi_from_bound(self):
        layer = iface.activeLayer()
        if not isinstance(layer, QgsVectorLayer):
            self._show_message("Active layer must be a vector layer.",
                               level=Qgis.Warning,
                               duration=10)
            return

        if layer.selectedFeatureCount() < 1:
            self._show_message("No features selected.",
                               level=Qgis.Warning,
                               duration=10)
            return

        bbox = layer.boundingBoxOfSelected()

        trans_layer = QgsCoordinateTransform(
            layer.sourceCrs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance(),
        )

        trans_canvas = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(),
            QgsProject.instance(),
        )

        transform_bbox = trans_layer.transformBoundingBox(bbox)
        geom_bbox = QgsGeometry.fromRect(transform_bbox)
        bbox_json = geom_bbox.asJson(precision=6)

        self.leAOI.setText(bbox_json)

        bbox_canvas = trans_canvas.transformBoundingBox(transform_bbox)
        self._aoi_box.setToGeometry(QgsGeometry.fromRect(bbox_canvas))

        self.zoom_to_aoi()

    def hide_aoi_if_matches_geom(self, geom):
        color = (QColor(0, 0, 0, 0)
                 if self._aoi_box.asGeometry().equals(geom) else self.color)
        self._aoi_box.setStrokeColor(color)

    def show_aoi(self):
        if self._aoi_box is not None:
            self._aoi_box.setStrokeColor(self.color)

    def aoi_geom(self):
        if self._aoi_box is not None:
            return self._aoi_box.asGeometry()

    def aoi_as_4326_geom(self):
        transform = QgsCoordinateTransform(
            QgsProject.instance().crs(),
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance(),
        )
        geom = self.aoi_geom()
        if geom is not None:
            geom.transform(transform)
        return geom

    @pyqtSlot()
    def zoom_to_aoi(self):
        if not self.leAOI.text():
            log.debug("No AOI defined, skipping zoom to AOI")
            return

        geom: QgsGeometry = qgsgeometry_from_geojson(self.leAOI.text())
        if geom.isEmpty():
            self._show_message("AOI GeoJSON geometry invalid",
                               level=Qgis.Warning,
                               duration=10)
            return

        self._aoi_box.setToGeometry(geom,
                                    QgsCoordinateReferenceSystem("EPSG:4326"))

        self.show_aoi()

        zoom_canvas_to_aoi(self.leAOI.text())

        self.zoomToAOIRequested.emit()

    @pyqtSlot()
    def copy_aoi_to_clipboard(self):
        if not self.leAOI.text():
            log.debug("No AOI defined, skipping zoom to AOI")
            return

        try:
            json_obj = json.loads(self.leAOI.text())
        except ValueError:
            return

        json_geom_txt = json.dumps(json_obj, indent=2)

        cb = QgsApplication.clipboard()
        cb.setText(json_geom_txt)

        self._show_message("AOI copied to clipboard")

    @pyqtSlot()
    def validate_edited_aoi(self):
        json_txt = self.leAOI.text()
        if not json_txt:
            self.reset_aoi_box()
            log.debug("No AOI defined, skipping validation")
            return

        try:
            json_obj = json.loads(json_txt)
        except ValueError:
            self._show_message("AOI GeoJSON is invalid",
                               level=Qgis.Warning,
                               duration=10)
            return

        try:
            json_geom = geometry_from_json(json_obj)
        except Exception:
            json_geom = None

        if not json_geom:
            self._show_message("AOI GeoJSON geometry invalid",
                               level=Qgis.Warning,
                               duration=10)
            return

        geom: QgsGeometry = qgsgeometry_from_geojson(json_geom)
        self._aoi_box.setToGeometry(geom,
                                    QgsCoordinateReferenceSystem("EPSG:4326"))

        self.leAOI.blockSignals(True)
        self.leAOI.setText(json.dumps(json_geom))
        self.leAOI.blockSignals(False)

        self.zoom_to_aoi()
Example #25
0
class MapToolSelect(QgsMapTool):
    ''' Based on the QGIS select tool
    '''
    featuresSelected = pyqtSignal()

    def __init__(self, canvas):
        super(MapToolSelect, self).__init__(canvas)

        self.canvas = canvas
        self.cursor = QgsApplication.getThemeCursor(QgsApplication.Select)

        self.rubberBand = None
        self.dragStart = None
        self.selectionActive = False

    def activate(self):
        super(MapToolSelect, self).activate()
        self.canvas.setCursor(self.cursor)

    def deactivate(self):
        super(MapToolSelect, self).deactivate()
        self.canvas.unsetCursor()

    def canvasPressEvent(self, event):
        if self.rubberBand is None:
            self.initRubberBand()

        self.dragStart = event.pos()

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

        r = QRect()
        if not self.selectionActive:
            self.selectionActive = True
            rect = QRect(event.pos(), event.pos())
        else:
            rect = QRect(event.pos(), self.dragStart)

        if self.rubberBand is not None:
            self.rubberBand.setToCanvasRectangle(rect)

    def canvasReleaseEvent(self, event):
        point = event.pos() - self.dragStart
        if not self.selectionActive or (point.manhattanLength() < QApplication.startDragDistance()):
            self.selectionActive = False
            self.selectFeatures(QgsGeometry.fromPointXY(self.toMapCoordinates(event.pos())), event.modifiers())

        if self.rubberBand is not None and self.selectionActive:
            self.selectFeatures(self.rubberBand.asGeometry(), event.modifiers())
            self.rubberBand.reset()

        self.selectionActive = False

        self.featuresSelected.emit()

    def initRubberBand(self):
        self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
        self.rubberBand.setFillColor(FILL_COLOR)
        self.rubberBand.setStrokeColor(STROKE_COLOR)

    def selectFeatures(self, geometry, modifiers):
        if geometry.type() == QgsWkbTypes.PointGeometry:
            layer = self.canvas.currentLayer()
            # TODO: check layer validity
            rect = self.expandSelectRectangle(geometry.asPoint(), layer)
            self.selectSingleFeature(QgsGeometry.fromRect(rect), modifiers)
        else:
            self.selectMultipleFeatures(geometry, modifiers)

    def expandSelectRectangle(self, mapPoint, layer):
        boxSize = 0
        if layer is None or layer.geometryType() != QgsWkbTypes.PolygonGeometry:
            # for lines and points make rectangle to simplify selection
            boxSize = 5
        else:
            boxSize = 1

        transform = self.canvas.getCoordinateTransform()
        point = transform.transform(mapPoint)
        ll = transform.toMapCoordinates(point.x() - boxSize, point.y() + boxSize)
        ur = transform.toMapCoordinates(point.x() + boxSize, point.y() - boxSize)
        return QgsRectangle(ll, ur)

    def selectSingleFeature(self, geometry, modifiers):
        layer = self.canvas.currentLayer()
        # TODO: check layer validity

        QApplication.setOverrideCursor(Qt.WaitCursor)
        selectedFeatures = self.getMatchingFeatures(geometry, False, True)
        if len(selectedFeatures) == 0:
            if not (modifiers & Qt.ShiftModifier or modifiers & Qt.ControlModifier):
                layer.removeSelection()

            QApplication.restoreOverrideCursor()
            return

        behavior = QgsVectorLayer.SetSelection
        if modifiers & Qt.ShiftModifier or modifiers & Qt.ControlModifier:
            # either shift or control modifier switches to "toggle" selection mode
            selectId = selectedFeatures[0]
            layerSelectedFeatures = layer.selectedFeatureIds()
            if selectId in layerSelectedFeatures:
                behavior = QgsVectorLayer.RemoveFromSelection
            else:
                behavior = QgsVectorLayer.AddToSelection

        layer.selectByIds(selectedFeatures, behavior)
        QApplication.restoreOverrideCursor()

    def selectMultipleFeatures(self, geometry, modifiers):
        behavior = QgsVectorLayer.SetSelection

        if modifiers & Qt.ShiftModifier and modifiers & Qt.ControlModifier:
            behavior = QgsVectorLayer.IntersectSelection
        elif modifiers & Qt.ShiftModifier:
            behavior = QgsVectorLayer.AddToSelection
        elif modifiers & Qt.ControlModifier:
            behavior = QgsVectorLayer.RemoveFromSelection

        contains = modifiers & Qt.AltModifier
        self.setSelectedFeatures(geometry, behavior, contains)

    def setSelectedFeatures(self, geometry, behavior=QgsVectorLayer.SetSelection, contains=True, singleSelect=False):
        layer = self.canvas.currentLayer()
        #TODO: check layer validity

        QApplication.setOverrideCursor(Qt.WaitCursor)

        selectedFeatures = self.getMatchingFeatures(geometry, contains, singleSelect)
        layer.selectByIds(selectedFeatures, behavior)

        QApplication.restoreOverrideCursor()

    def getMatchingFeatures(self, geometry, contains, singleSelect):
        newFeatures = []
        if geometry.type() != QgsWkbTypes.PolygonGeometry:
            return newFeatures

        layer = self.canvas.currentLayer()
        if layer is None:
            return newFeatures

        selectGeomTrans = QgsGeometry(geometry)
        try:
            ct = QgsCoordinateTransform(self.canvas.mapSettings().destinationCrs(), layer.crs(), QgsProject.instance())
            if not ct.isShortCircuited() and selectGeomTrans.type() == QgsWkbTypes.PolygonGeometry:
                poly = selectGeomTrans.asPolygon()
                if len(poly) == 1 and len(poly[0]) == 5:
                    ringIn = poly[0]
                    ringOut = []
                    ringOut.append(ringIn[0])
                    i = 1
                    for j in range(1, 5):
                        v = QgsVector((ringIn[j] - ringIn[j - 1]) / 10.0)
                        for k in range(9):
                            ringOut.append(ringOut[i - 1] + v)
                            i += 1
                        ringOut.append(ringIn[j])
                        i += 1
                    selectGeomTrans = QgsGeometry.fromPolygonXY([ringOut])

            selectGeomTrans.transform(ct)
        except QgsCsException as e:
            QgsMessageLog.logMessage("Selection extends beyond layer's coordinate system")
            return newFeatures

        context = QgsRenderContext.fromMapSettings(self.canvas.mapSettings())
        context.expressionContext().appendScope(QgsExpressionContextUtils.layerScope(layer))

        r = None
        if layer.renderer():
            r = layer.renderer().clone()
            r.startRender(context, layer.fields())

        request = QgsFeatureRequest()
        request.setFilterRect(selectGeomTrans.boundingBox())
        request.setFlags(QgsFeatureRequest.ExactIntersect)

        if r:
            request.setSubsetOfAttributes(r.usedAttributes(context), layer.fields())
        else:
            request.setSubsetOfAttributes([])

        closestFeatureId = 0
        foundSingleFeature = False
        closestFeatureDist = sys.float_info.max
        for f in layer.getFeatures(request):
            context.expressionContext().setFeature(f)
            if r and  not r.willRenderFeature(f, context):
                continue

            g = f.geometry()
            if contains:
                if not selectGeomTrans.contains(g):
                    continue

            else:
                if not selectGeomTrans.intersects(g):
                    continue

            if singleSelect:
                foundSingleFeature = True
                distance = g.distance(selectGeomTrans)
                if distance <= closestFeatureDist:
                    closestFeatureDist = distance
                    closestFeatureId = f.id()
            else:
                newFeatures.append(f.id())

        if singleSelect and foundSingleFeature:
            newFeatures.append(closestFeatureId)

        if r:
            r.stopRender(context)

        return newFeatures
Example #26
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()
Example #27
0
class addPolygon(QgsMapTool):
    cut = pyqtSignal()
    deact = pyqtSignal()

    def __init__(self, iface, action, geometryClass):
        self.canvas = iface.mapCanvas()
        self.iface = iface
        self.action = action
        self.geometryClass = geometryClass

        obj_color = QColor(254, 0, 0)
        obj_color_alpha = QColor(254, 0, 0)
        obj_color_alpha.setAlpha(60)
        vert_color = QColor(0, 0, 255)

        QgsMapTool.__init__(self, self.canvas)

        self.rubberBand = QgsRubberBand(self.canvas,
                                        QgsWkbTypes.GeometryType(3))
        self.rubberBand.setWidth(1)
        self.rubberBand.setStrokeColor(obj_color)
        self.rubberBand.setFillColor(obj_color_alpha)

        self.rubberBand_click = QgsRubberBand(self.canvas,
                                              QgsWkbTypes.GeometryType(3))
        self.rubberBand_click.setWidth(0)
        self.rubberBand_click.setFillColor(obj_color_alpha)

        # snap marker
        self.snap_mark = QgsVertexMarker(self.canvas)
        self.snap_mark.setColor(vert_color)
        self.snap_mark.setPenWidth(2)
        self.snap_mark.setIconType(QgsVertexMarker.ICON_BOX)
        self.snap_mark.setIconSize(10)

        self.points = []

    def activate(self):
        self.action.setChecked(True)
        self.setCursor(Qt.CrossCursor)

    def canvasMoveEvent(self, e):
        self.snap_mark.hide()
        self.snapPoint = False
        self.snapPoint = self.checkSnapToPoint(e.pos())

        if self.snapPoint[0]:
            self.snap_mark.setCenter(self.snapPoint[1])
            self.snap_mark.show()

        if len(self.points) > 0:
            self.rubberBand.reset(QgsWkbTypes.GeometryType(3))
            point = self.toMapCoordinates(self.canvas.mouseLastXY())
            temp_points = self.points[:]
            temp_points.append(point)
            polygon = QgsGeometry.fromPolygonXY([temp_points])
            self.rubberBand.setToGeometry(polygon, None)
            self.rubberBand.show()

    def canvasPressEvent(self, e):
        # Left mouse button
        if e.button() == Qt.LeftButton:
            if self.snapPoint[0]:
                point = self.snapPoint[1]
            else:
                point = self.toMapCoordinates(self.canvas.mouseLastXY())

            self.points.append(point)
            polygon = QgsGeometry.fromPolygonXY([self.points])
            self.rubberBand_click.reset(QgsWkbTypes.GeometryType(3))
            self.rubberBand_click.setToGeometry(polygon, None)
            self.rubberBand_click.show()

        # Right mouse button
        if e.button() == Qt.RightButton:
            geometry = QgsGeometry.fromPolygonXY([self.points])
            self.geometryClass.geometry = geometry
            self.cut.emit()
            self.reset()

    def checkSnapToPoint(self, point):
        snapped = False
        snap_point = self.toMapCoordinates(point)
        snapper = self.canvas.snappingUtils()
        snapMatch = snapper.snapToMap(point)
        if snapMatch.hasVertex():
            snap_point = snapMatch.point()
            snapped = True
        return snapped, snap_point

    def deactivate(self):
        self.action.setChecked(False)
        self.reset()
        self.deact.emit()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape:
            self.reset()

    def reset(self):
        self.rubberBand_click.reset(QgsWkbTypes.GeometryType(3))
        self.rubberBand.reset(QgsWkbTypes.GeometryType(3))
        self.snap_mark.hide()
        self.points = []
Example #28
0
class EventoDivision(QgsMapTool):   
    def __init__(self, canvas, pluginM):
        QgsMapTool.__init__(self, canvas)
        #Asignacion inicial
        self.canvas = canvas
        self.pluginM = pluginM
        self.modoDividir = False
        self.modoEliminar = False
        self.modoEditar = False
        self.moviendoVertice = False

        self.relaciones = {}
        self.punteroRelaciones = 0

        self.relaciones[self.punteroRelaciones] = RelacionRubberGeom(self.crearNuevoRubberLinea(), None)
        
        self.rubberPunto = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry)
        self.rubberPunto.setFillColor(QColor(0,0,0,0))
        self.rubberPunto.setStrokeColor(QColor(255,0,0,255))
        self.rubberPunto.setWidth(6)

        self.primerClick = False
        self.snapper = self.canvas.snappingUtils()
        self.listaPuntosLineaTemp = []
        self.cuentaClickLinea = 0
        self.relacionEnEdicion = -1
        

#--------------------------------------------------------------------------------------------

    def canvasPressEvent(self, event):
        x = event.pos().x()
        y = event.pos().y()
        startingPoint = QtCore.QPoint(x,y)
        trans = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)
        posTemp = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)
        if self.modoDividir: # ----- Modo Dividir ---- #
            if event.buttons() == Qt.LeftButton: 
                
                self.puntoLineaTemp = self.toMapCoordinates(event.pos())
                geoTemp = QgsPoint(trans.x(),trans.y())
                self.cuentaClickLinea += 1

                puntoSnap = self.snapCompleto(startingPoint)
                if puntoSnap != None: #Cuando tenemos snap ------------#
                    
                    self.listaPuntosLineaTemp.append(puntoSnap)
                    self.isEmittingPoint = True
                    marcador = self.crearNuevoMarcaVert()
                    self.relaciones[self.punteroRelaciones].marcadores.append(marcador)
                    marcador.setCenter(puntoSnap)
                else: #Cuando no tenemos snap ------------- #

                    self.listaPuntosLineaTemp.append(self.puntoLineaTemp)
                    self.isEmittingPoint = True
                    marcador = self.crearNuevoMarcaVert()
                    self.relaciones[self.punteroRelaciones].marcadores.append(marcador)
                    marcador.setCenter(self.puntoLineaTemp)

                if not self.primerClick:
                    self.primerClick = True

            if event.buttons() == Qt.RightButton: #Click derecho
                self.primerClick = False

                if self.cuentaClickLinea >= 2: #Cuando son mas de dos puntos
                    
                    self.isEmittingPoint = False
                    self.cuentaClickLinea = 0
                    self.primerClick = False

                    self.relaciones[self.punteroRelaciones].rubber.reset(QgsWkbTypes.LineGeometry) #Vaciamos el rubber

                    rango = len(self.listaPuntosLineaTemp) - 1 #Agregamos todos los puntos al rubber excepto el ultimo
                    for x in range(0, rango):
                        punto = self.listaPuntosLineaTemp[x]
                        self.relaciones[self.punteroRelaciones].rubber.addPoint(punto, True)
                    
                    geometriaR = QgsGeometry( self.relaciones[self.punteroRelaciones].rubber.asGeometry()) #Ponemos la geometria en la relacion
                    self.relaciones[self.punteroRelaciones].geom = geometriaR
                    
                    vertices = self.obtenerVertices(geometriaR) #Obtenemos los vertices de la geometria
                    
                    self.relaciones[self.punteroRelaciones].rubber.reset(QgsWkbTypes.LineGeometry) #Vaciamos el rubber

                    for vertice in vertices: #Ponemos los vertices en el rubber
                        self.relaciones[self.punteroRelaciones].rubber.addPoint(QgsPointXY(vertice.x(), vertice.y()), True)
                        

                    self.relaciones[self.punteroRelaciones].rubber.show()
                    self.listaPuntosLineaTemp = []
                    
                    self.punteroRelaciones += 1 #Agregamos otro Rubber
                    self.relaciones[self.punteroRelaciones] = RelacionRubberGeom(self.crearNuevoRubberLinea(), None)

                else:
                    self.relaciones[self.punteroRelaciones].rubber.reset(QgsWkbTypes.LineGeometry)
                    self.listaPuntosLineaTemp = []
                    self.relaciones[self.punteroRelaciones].geom = None


        elif self.modoEliminar: #-------Modo Elimina-------------#
            if event.buttons() == Qt.LeftButton: 
                geomClick = QgsGeometry(QgsGeometry.fromPointXY(posTemp))
                bufferClick = geomClick.buffer(0.25, 1)
                
                relacion = self.obtenerRelacionCercana(bufferClick)
                if relacion != None:
                    relacion.rubber.reset(QgsWkbTypes.LineGeometry)
                    relacion.geom = None
                    relacion.vaciarMarcadores()

        elif self.modoEditar: #--------------Modo Editar---------#
            geomClick = QgsGeometry(QgsGeometry.fromPointXY(posTemp))
            bufferClick = geomClick.buffer(0.25, 1)
            relacion = self.obtenerRelacionCercana(bufferClick)

            if event.buttons() == Qt.LeftButton: #---------Click Izquierdo ------#
                
                if not self.moviendoVertice: #Cuando NO estamos moviendo un vertice y buscamos mover uno
                    
                    if relacion != None:

                        relacion.rubber.setStrokeColor(QColor(255,170,0,255))
                        iface.mapCanvas().refresh()
                        vertices = self.obtenerVertices(relacion.geom)
                        verticeSeleccionado = None
                        for vertice in vertices: #Aqui buscamos el vertice cercano al click
                            puntoXY = QgsPointXY(vertice.x(), vertice.y())
                            geomVertice = QgsGeometry(QgsGeometry.fromPointXY(puntoXY)).buffer(2.25, 1)
                            if geomVertice.intersects(bufferClick):
                                verticeSeleccionado = vertice
                                break

                        if verticeSeleccionado != None: #aqui tenemos ya un vertice jalando para arrastrase
                            self.listaPuntosLineaTemp = []
                            self.indiceSeleccionado = vertices.index(vertice)
                            self.moviendoVertice = True
                            for vertice in vertices:
                                puntoXY = QgsPointXY(vertice.x(), vertice.y())
                                self.listaPuntosLineaTemp.append(puntoXY)
                                
                            print(self.indiceSeleccionado)
                else: #Cuando estamos moviendo un vertice y queremos soltarlo
                    self.moviendoVertice = False
                    rel = self.relaciones[self.relacionEnEdicion]
                    rel.geom = rel.rubber.asGeometry()
                    rel.rubber.setStrokeColor(QColor(0,62,240,255))
                    self.punteroRelaciones = len(self.relaciones)
                    self.relaciones[self.punteroRelaciones] = RelacionRubberGeom(self.crearNuevoRubberLinea(), None)
                    self.listaPuntosLineaTemp = []
                    self.pluginM.VentanaAreas.close()
                    iface.mapCanvas().refresh()

            elif event.buttons() == Qt.RightButton: #--------Click Derecho -----# Para agregar vertices personales
                
                if relacion != None:
                    inter = bufferClick.intersection(relacion.geom.buffer(0.000004, 1)) #Checamos la interseccion con la linea a editar
                    c = inter.centroid().asPoint() #Obtenemos el centroide, aqui se pintara el vertice

                    vertices1 = self.obtenerVertices(relacion.geom) #Obtenemos todos los vertices acutales

                    rango = len(vertices1)

                    posiblesX = []
                    for x in range(0, rango-1): #Recorremos todos los vertices
                        v1 = vertices1[x] #Punto izqueirdo
                        v2 = vertices1[x+1] #punto derecho
                        par = (v1, v2)
                        
                        if v1.x() <= v2.x(): #Cuando el primer punto esta a la izquierda
                            if c.x() >= v1.x() and c.x() <= v2.x():
                                posiblesX.append(par)
                        else: #Cuando el primer punto esta a la derecha
                            if c.x() <= v1.x() and c.x() >= v2.x():
                                posiblesX.append(par)

                    
                    for pa in posiblesX: #Checamos todos los posibles que se encuentren en el eje X
                        v1 = pa[0]
                        v2 = pa[1]
                        if v1.y() <= v2.y(): #Cuando el primer punto esta abajo
                            if c.y() >= v1.y() and c.y() <= v2.y():
                                salidaY = pa
                                break
                        else: #Cuando esta arriba
                            if c.y() <= v1.y() and c.y() >= v2.y():
                                salidaY = pa
                                break

                    indiceI = vertices1.index(salidaY[0])
                    indiceD = vertices1.index(salidaY[1])

                    vertices2 = []
                    relacion.vaciarMarcadores()
                    for x in range(0, rango): #Generamos lista d epuntos que incluyan el nuevo
                        if x == indiceD:

                            vertices2.append(c)
                            nuevo = self.crearNuevoMarcaVert()
                            centroM = QgsPointXY(c.x(),c.y())
                            nuevo.setCenter(centroM)
                            relacion.marcadores.append(nuevo)
                            
                            vertices2.append(vertices1[x])
                            nuevo = self.crearNuevoMarcaVert()
                            centroM = QgsPointXY(vertices1[x].x(),vertices1[x].y())
                            nuevo.setCenter(centroM)
                            relacion.marcadores.append(nuevo)

                        else: 
                            vertices2.append(vertices1[x])
                            nuevo = self.crearNuevoMarcaVert()
                            centroM = QgsPointXY(vertices1[x].x(),vertices1[x].y())
                            nuevo.setCenter(centroM)
                            relacion.marcadores.append(nuevo)

                    relacion.rubber.reset(QgsWkbTypes.LineGeometry)

                    for punto in vertices2: #Regeneramos el rubber
                        puntoXY = QgsPointXY(punto.x(),punto.y())
                        relacion.rubber.addPoint(puntoXY, True)

                    relacion.geom = relacion.rubber.asGeometry()

#-----------------------------------------------------------------------


    def canvasMoveEvent(self, event):

        x = event.pos().x()
        y = event.pos().y()
        startingPoint = QtCore.QPoint(x,y)
        posTemp = self.canvas.getCoordinateTransform().toMapCoordinates(x, y)
        
        if self.modoDividir:
            self.relaciones[self.punteroRelaciones].rubber.reset(QgsWkbTypes.LineGeometry)
            self.rubberPunto.reset(QgsWkbTypes.PointGeometry)
            puntoSnap = self.snapCompleto(startingPoint)

            if puntoSnap != None:

                puntoSnapXY = QgsPointXY(puntoSnap.x(),puntoSnap.y())
                self.rubberPunto.addPoint(puntoSnapXY, True)
                self.rubberPunto.show()

            if self.primerClick:
                if (len(self.listaPuntosLineaTemp) > 1):

                    if puntoSnap != None:
                        self.listaPuntosLineaTemp[-1] = puntoSnapXY
                    else:
                        self.listaPuntosLineaTemp[-1] = posTemp

                    for punto in self.listaPuntosLineaTemp[:-1]:
                        self.relaciones[self.punteroRelaciones].rubber.addPoint(punto, False)
                    self.relaciones[self.punteroRelaciones].rubber.addPoint(self.listaPuntosLineaTemp[-1], True)
                    self.relaciones[self.punteroRelaciones].rubber.show()
                else:
                    if puntoSnap != None:
                        self.listaPuntosLineaTemp.append(puntoSnapXY)
                    else:
                        self.listaPuntosLineaTemp.append(posTemp)
                    self.relaciones[self.punteroRelaciones].rubber.addPoint(self.listaPuntosLineaTemp[0], True)
                    self.relaciones[self.punteroRelaciones].rubber.show()
        
        elif self.modoEditar: #-modo editar

            if self.moviendoVertice: #Arrastrando un vertice

                self.rubberPunto.reset(QgsWkbTypes.PointGeometry)
                puntoSnap = self.snapCompleto(startingPoint)

                if puntoSnap != None:

                    puntoSnapXY = QgsPointXY(puntoSnap.x(),puntoSnap.y())
                    self.rubberPunto.addPoint(puntoSnapXY, True)
                    self.rubberPunto.show()
                    self.listaPuntosLineaTemp[self.indiceSeleccionado] = puntoSnapXY
                    self.relaciones[self.relacionEnEdicion].marcadores[self.indiceSeleccionado].setCenter(puntoSnapXY)

                else:
                    self.listaPuntosLineaTemp[self.indiceSeleccionado] = posTemp
                    self.relaciones[self.relacionEnEdicion].marcadores[self.indiceSeleccionado].setCenter(posTemp)
                
                self.relaciones[self.relacionEnEdicion].rubber.reset(QgsWkbTypes.LineGeometry)
                
                for punto in self.listaPuntosLineaTemp:
                    self.relaciones[self.relacionEnEdicion].rubber.addPoint(punto, True)
                
                
                self.relaciones[self.relacionEnEdicion].rubber.show()

    
#-------------------------------------------------------------------------------------#

    def snapVertice(self, startingPoint, nombreCapa):

        layer = QgsProject.instance().mapLayer(self.pluginM.ACA.obtenerIdCapa(nombreCapa))
        
        self.snapper.setCurrentLayer(layer)
        snapMatch = self.snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Vertex)

        if snapMatch.hasVertex():
            return snapMatch.point()

#------------------------------------------------------------------------------------#

    def snapArista(self, startingPoint, nombreCapa):

        layer = QgsProject.instance().mapLayer(self.pluginM.ACA.obtenerIdCapa(nombreCapa))
        
        self.snapper.setCurrentLayer(layer)
        snapMatch = self.snapper.snapToCurrentLayer(startingPoint, QgsPointLocator.Edge)

        if snapMatch.hasEdge():
            return snapMatch.point()

#-----------------------------------------------------------------------------------

    def snapCompleto(self, startingPoint):
        snap = self.snapVertice(startingPoint, 'predios.geom') #----vertices -----#
        if snap != None:
            return snap
        else:
            snap = self.snapVertice(startingPoint, 'construcciones')
            if snap != None:
                return snap
            else:
                snap = self.snapVertice(startingPoint, 'horizontales.geom')
                if snap != None:
                    return snap
                else:
                    snap = self.snapVertice(startingPoint, 'verticales') #-----Hasta aqui son vertices -----#
                    if snap != None:
                        return snap
                    else:
                        snap = self.snapArista(startingPoint, 'predios.geom')
                        if snap != None:
                            return snap
                        else:
                            snap = self.snapArista(startingPoint, 'construcciones')
                            if snap != None:
                                return snap
                            else:
                                snap = self.snapArista(startingPoint, 'horizontales.geom')
                                if snap != None:
                                    return snap
                                else:
                                    snap = self.snapArista(startingPoint, 'verticales') #-------- hasta aqui aristas-----------#
                                    if snap != None:
                                        return snap
                                    else:
                                        return None

#----------------------------------------------------------------------------------------------

    def crearNuevoRubberLinea(self):
        nuevoRubber = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry)
        nuevoRubber.setFillColor(QColor(0,0,0,0))
        nuevoRubber.setStrokeColor(QColor(0,62,240,255))
        nuevoRubber.setWidth(2)
        #penStyle = Qt.PenStyle()
        nuevoRubber.setLineStyle(Qt.PenStyle(3))
        return nuevoRubber

#------------------------------------------------------------------------------------------

    def crearNuevoRubberPoly(self):
        nuevoRubber = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
        
        r = randint(0, 255)
        g = randint(0, 255)
        b = randint(0, 255)
        
        color = QColor(r,g,b,36)
        colorAg = QColor(r,g,b,87)
        self.pluginM.listaColores.append(colorAg)
        nuevoRubber.setFillColor(color)
        nuevoRubber.setStrokeColor(QColor(r,g,b,255))
        nuevoRubber.setWidth(2)
        return nuevoRubber

#-------------------------------------------------------------------------------------

    def recargarRelaciones(self):
        self.relaciones = {}
        self.punteroRelaciones = 0

        self.relaciones[self.punteroRelaciones] = RelacionRubberGeom(self.crearNuevoRubberLinea(), None)

#-----------------------------------------------------------------------------------------

    def prueba(self, e):
        print(e)

#----------------------------------------------------------------------------------

    def obtenerRelacionCercana(self, punto):
        rango = len(self.relaciones) - 1
        for i in range(0, rango):
            relacion = self.relaciones[i]
            geom = relacion.geom
            if geom != None:
                if geom.buffer(0.000004,1).intersects(punto):
                    self.relacionEnEdicion = i
                    return relacion
                    

#-----------------------------------------------------------------------------------

    def obtenerVertices(self, geom):
        n  = 0
        ver = geom.vertexAt(0)
        vertices=[]

        while(ver != QgsPoint(0,0)):
            n +=1
            vertices.append(ver)
            ver=geom.vertexAt(n)

        return vertices

    def crearNuevoMarcaVert(self):
        marcador = QgsVertexMarker(iface.mapCanvas())
        marcador.setColor(QColor(0,255,0))
        marcador.setIconSize(5)
        marcador.setIconType(QgsVertexMarker.ICON_BOX)
        marcador.setPenWidth(3)

        return marcador
class ItemWidgetBase(QFrame):

    checkedStateChanged = pyqtSignal()
    thumbnailChanged = pyqtSignal()

    def __init__(self, item):
        QFrame.__init__(self)
        self.item = item
        self.is_updating_checkbox = False
        self.setMouseTracking(True)
        self.setStyleSheet("ItemWidgetBase{border: 2px solid transparent;}")

    def _setup_ui(self, text, thumbnailurl):

        self.lockLabel = QLabel()
        iconSize = QSize(16, 16)
        self.lockLabel.setPixmap(LOCK_ICON.pixmap(iconSize))
        self.checkBox = QCheckBox("")
        self.checkBox.clicked.connect(self.check_box_state_changed)
        self.nameLabel = QLabel(text)
        self.iconLabel = QLabel()
        self.labelZoomTo = QLabel()
        self.labelZoomTo.setPixmap(ZOOMTO_ICON.pixmap(QSize(18, 18)))
        self.labelZoomTo.setToolTip("Zoom to extent")
        self.labelZoomTo.mousePressEvent = self.zoom_to_extent
        self.labelAddPreview = QLabel()
        self.labelAddPreview.setPixmap(ADD_PREVIEW_ICON.pixmap(QSize(18, 18)))
        self.labelAddPreview.setToolTip("Add preview layer to map")
        self.labelAddPreview.mousePressEvent = self._add_preview_clicked

        layout = QHBoxLayout()
        layout.setMargin(0)
        layout.addWidget(self.checkBox)
        layout.addWidget(self.lockLabel)
        pixmap = QPixmap(PLACEHOLDER_THUMB, "SVG")
        self.thumbnail = None
        thumb = pixmap.scaled(48, 48, Qt.KeepAspectRatio,
                              Qt.SmoothTransformation)
        self.iconLabel.setPixmap(thumb)
        self.iconLabel.setFixedSize(48, 48)
        layout.addWidget(self.iconLabel)
        if thumbnailurl is not None:
            download_thumbnail(thumbnailurl, self)
        layout.addWidget(self.nameLabel)
        layout.addStretch()
        layout.addWidget(self.labelZoomTo)
        layout.addWidget(self.labelAddPreview)
        layout.addSpacing(10)
        self.setLayout(layout)

        self.footprint = QgsRubberBand(iface.mapCanvas(),
                                       QgsWkbTypes.PolygonGeometry)
        self.footprint.setStrokeColor(PLANET_COLOR)
        self.footprint.setWidth(2)

    def set_thumbnail(self, img):
        self.thumbnail = QPixmap(img)
        thumb = self.thumbnail.scaled(48, 48, Qt.KeepAspectRatio,
                                      Qt.SmoothTransformation)
        self.iconLabel.setPixmap(thumb)
        self.thumbnailChanged.emit()

    def is_selected(self):
        return self.checkBox.checkState() == Qt.Checked

    def _geom_bbox_in_project_crs(self):
        transform = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(),
            QgsProject.instance(),
        )
        return transform.transformBoundingBox(self.geom.boundingBox())

    def _geom_in_project_crs(self):
        transform = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:4326"),
            QgsProject.instance().crs(),
            QgsProject.instance(),
        )
        geom = QgsGeometry(self.geom)
        geom.transform(transform)
        return geom

    def show_footprint(self):
        self.footprint.setToGeometry(self._geom_in_project_crs())

    def hide_footprint(self):
        self.footprint.reset(QgsWkbTypes.PolygonGeometry)

    def enterEvent(self, event):
        self.setStyleSheet(
            "ItemWidgetBase{border: 2px solid rgb(0, 157, 165);}")
        self.show_footprint()

    def leaveEvent(self, event):
        self.setStyleSheet("ItemWidgetBase{border: 2px solid transparent;}")
        self.hide_footprint()

    def zoom_to_extent(self, evt):
        rect = QgsRectangle(self._geom_bbox_in_project_crs())
        rect.scale(1.05)
        iface.mapCanvas().setExtent(rect)
        iface.mapCanvas().refresh()

    def _add_preview_clicked(self, evt):
        self.add_preview()

    @waitcursor
    def add_preview(self):
        send_analytics_for_preview(self.item.images())
        create_preview_group(self.name(), self.item.images())

    def check_box_state_changed(self):
        self.update_children_items()
        self.update_parent_item()
        self.checkedStateChanged.emit()

    def update_parent_item(self):
        parent = self.item.parent()
        if parent is not None:
            w = parent.treeWidget().itemWidget(parent, 0)
            w.update_checkbox()

    def update_children_items(self):
        total = self.item.childCount()
        if self.checkBox.isTristate():
            self.checkBox.setTristate(False)
            self.checkBox.setChecked(False)
        for i in range(total):
            w = self.item.treeWidget().itemWidget(self.item.child(i), 0)
            w.set_checked(self.checkBox.isChecked())

    def update_checkbox(self):
        selected = 0
        total = self.item.childCount()
        for i in range(total):
            w = self.item.treeWidget().itemWidget(self.item.child(i), 0)
            if w.is_selected():
                selected += 1
        if selected == total:
            self.checkBox.setTristate(False)
            self.checkBox.setCheckState(Qt.Checked)
        elif selected == 0:
            self.checkBox.setTristate(False)
            self.checkBox.setCheckState(Qt.Unchecked)
        else:
            self.checkBox.setTristate(True)
            self.checkBox.setCheckState(Qt.PartiallyChecked)

    def set_checked(self, checked):
        self.checkBox.setChecked(checked)
        self.update_children_items()

    def update_thumbnail(self):
        thumbnails = self.scene_thumbnails()
        if thumbnails and None not in thumbnails:
            bboxes = [img[GEOMETRY] for img in self.item.images()]
            pixmap = createCompoundThumbnail(bboxes, thumbnails)
            thumb = pixmap.scaled(48, 48, Qt.KeepAspectRatio,
                                  Qt.SmoothTransformation)
            self.iconLabel.setPixmap(thumb)
            self.thumbnailChanged.emit()

    def scene_thumbnails(self):
        thumbnails = []
        try:
            for i in range(self.item.childCount()):
                w = self.item.treeWidget().itemWidget(self.item.child(i), 0)
                thumbnails.extend(w.scene_thumbnails())
        except RuntimeError:
            # item might not exist anymore. In this case, we just return
            # an empty list
            pass
        return thumbnails
class OptimalRouteBottomBar(KadasBottomBar, WIDGET):
    def __init__(self, canvas, action, plugin):
        KadasBottomBar.__init__(self, canvas, "orange")
        self.setupUi(self)
        self.setStyleSheet("QFrame { background-color: orange; }")
        self.action = action
        self.plugin = plugin
        self.canvas = canvas
        self.waypoints = []
        self.waypointPins = []
        self.areasToAvoid = None

        self.btnAddWaypoints.setIcon(QIcon(":/kadas/icons/add"))
        self.btnClose.setIcon(QIcon(":/kadas/icons/close"))
        self.btnAddWaypoints.setToolTip(self.tr("Add waypoint"))
        self.btnClose.setToolTip(self.tr("Close routing dialog"))

        self.action.toggled.connect(self.actionToggled)
        self.btnClose.clicked.connect(self.action.toggle)

        self.btnCalculate.clicked.connect(self.calculate)

        self.layerSelector = KadasLayerSelectionWidget(
            canvas,
            iface.layerTreeView(),
            lambda x: isinstance(x, OptimalRouteLayer),
            self.createLayer,
        )
        self.layerSelector.createLayerIfEmpty(self.tr("Route"))
        self.layerSelector.selectedLayerChanged.connect(
            self.selectedLayerChanged)
        self.layout().addWidget(self.layerSelector, 0, 0, 1, 2)
        layer = self.layerSelector.getSelectedLayer()
        self.btnNavigate.setEnabled(layer is not None and layer.hasRoute())

        self.originSearchBox = LocationInputWidget(
            canvas, locationSymbolPath=iconPath("pin_origin.svg"))
        self.layout().addWidget(self.originSearchBox, 2, 1)

        self.destinationSearchBox = LocationInputWidget(
            canvas, locationSymbolPath=iconPath("pin_destination.svg"))
        self.layout().addWidget(self.destinationSearchBox, 3, 1)

        self.waypointsSearchBox = LocationInputWidget(
            canvas, locationSymbolPath=iconPath("pin_bluegray.svg"))
        self.groupBox.layout().addWidget(self.waypointsSearchBox, 0, 0)

        self.comboBoxVehicles.addItems(vehicles.vehicle_names())

        self.btnPointsClear.clicked.connect(self.clearPoints)
        self.btnReverse.clicked.connect(self.reverse)
        self.btnAddWaypoints.clicked.connect(self.addWaypoints)
        self.btnNavigate.clicked.connect(self.navigate)
        self.btnAreasToAvoidClear.clicked.connect(self.clearAreasToAvoid)
        self.btnAreasToAvoidFromCanvas.toggled.connect(
            self.setPolygonDrawingMapTool)

        iface.mapCanvas().mapToolSet.connect(self.mapToolSet)

        self.areasToAvoidFootprint = QgsRubberBand(iface.mapCanvas(),
                                                   QgsWkbTypes.PolygonGeometry)
        self.areasToAvoidFootprint.setStrokeColor(AVOID_AREA_COLOR)
        self.areasToAvoidFootprint.setWidth(2)

        self.populateLayerSelector()
        self.radioAreasToAvoidPolygon.toggled.connect(
            self._radioButtonsChanged)
        self.radioAreasToAvoidLayer.toggled.connect(self._radioButtonsChanged)
        self.radioAreasToAvoidNone.toggled.connect(self._radioButtonsChanged)
        self.radioAreasToAvoidNone.setChecked(True)

        # Handling HiDPI screen, perhaps we can make a ratio of the screen size
        size = QDesktopWidget().screenGeometry()
        if size.width() >= 3200 or size.height() >= 1800:
            self.setFixedSize(self.size() * 1.5)

    def populateLayerSelector(self):
        self.comboAreasToAvoidLayers.clear()
        for layer in QgsProject.instance().mapLayers().values():
            if isinstance(layer, QgsVectorLayer) and layer.geometryType(
            ) == QgsWkbTypes.PolygonGeometry:
                self.comboAreasToAvoidLayers.addItem(layer.name(), layer)

    def _radioButtonsChanged(self):
        self.populateLayerSelector()
        self.comboAreasToAvoidLayers.setEnabled(
            self.radioAreasToAvoidLayer.isChecked())
        self.btnAreasToAvoidFromCanvas.setEnabled(
            self.radioAreasToAvoidPolygon.isChecked())
        self.btnAreasToAvoidClear.setEnabled(
            self.radioAreasToAvoidPolygon.isChecked())
        if self.radioAreasToAvoidPolygon.isChecked():
            if self.areasToAvoid is not None:
                self.areasToAvoidFootprint.setToGeometry(self.areasToAvoid)
        else:
            self.areasToAvoidFootprint.reset(QgsWkbTypes.PolygonGeometry)

    def setPolygonDrawingMapTool(self, checked):
        if checked:
            self.prevMapTool = iface.mapCanvas().mapTool()
            self.mapToolDrawPolygon = DrawPolygonMapTool(iface.mapCanvas())
            self.mapToolDrawPolygon.polygonSelected.connect(
                self.setAreasToAvoidFromPolygon)
            iface.mapCanvas().setMapTool(self.mapToolDrawPolygon)
        else:
            try:
                iface.mapCanvas().setMapTool(self.prevMapTool)
            except Exception as e:
                logging.error(e)
                iface.mapCanvas().setMapTool(QgsMapToolPan(iface.mapCanvas()))

    def mapToolSet(self, new, old):
        if not isinstance(new, DrawPolygonMapTool):
            self.btnAreasToAvoidFromCanvas.blockSignals(True)
            self.btnAreasToAvoidFromCanvas.setChecked(False)
            self.btnAreasToAvoidFromCanvas.blockSignals(False)

    def clearAreasToAvoid(self):
        self.areasToAvoid = None
        self.areasToAvoidFootprint.reset(QgsWkbTypes.PolygonGeometry)

    def setAreasToAvoidFromPolygon(self, polygon):
        self.areasToAvoid = polygon
        self.areasToAvoidFootprint.setToGeometry(polygon)

    def createLayer(self, name):
        layer = OptimalRouteLayer(name)
        return layer

    def selectedLayerChanged(self, layer):
        self.btnNavigate.setEnabled(layer is not None and layer.hasRoute())

    def calculate(self):
        layer = self.layerSelector.getSelectedLayer()
        if layer is None:
            pushWarning(self.tr("Please, select a valid destination layer"))
            return
        try:
            points = [self.originSearchBox.point]
            points.extend(self.waypoints)
            points.append(self.destinationSearchBox.point)
        except WrongLocationException as e:
            pushWarning(self.tr("Invalid location:") + str(e))
            return

        if None in points:
            pushWarning(
                self.tr("Both origin and destination points are required"))
            return

        shortest = self.radioButtonShortest.isChecked()

        vehicle = self.comboBoxVehicles.currentIndex()
        profile, costingOptions = vehicles.options_for_vehicle(vehicle)
        '''
        areasToAvoid = None
        if self.radioAreasToAvoidPolygon.isChecked():
            areasToAvoid = self.areasToAvoid
        elif self.radioAreasToAvoidLayer.isChecked():
            avoidLayer = self.comboAreasToAvoidLayers.currentData()
            if avoidLayer is not None:
                geoms = [f.geometry() for f in avoidLayer.getFeatures()]
                areasToAvoid = QgsGeometry.collectGeometry(geoms)
        # TODO: use areas to avoid
        '''

        if shortest:
            costingOptions["shortest"] = True

        try:
            layer.updateRoute(points, profile, costingOptions)
            self.btnNavigate.setEnabled(True)
        except Exception as e:
            raise
            logging.error(e, exc_info=True)
            # TODO more fine-grained error control
            pushWarning(self.tr("Could not compute route"))
            logging.error("Could not compute route")

    def clearPoints(self):
        self.originSearchBox.clearSearchBox()
        self.destinationSearchBox.clearSearchBox()
        self.waypointsSearchBox.clearSearchBox()
        self.waypoints = []
        self.lineEditWaypoints.clear()
        for waypointPin in self.waypointPins:
            KadasMapCanvasItemManager.removeItem(waypointPin)
        self.waypointPins = []
        KadasMapCanvasItemManager.removeItem(self.originSearchBox.pin)
        KadasMapCanvasItemManager.removeItem(self.destinationSearchBox.pin)

    def addWaypoints(self):
        """Add way point to the list of way points"""
        if self.waypointsSearchBox.text() == "":
            return
        waypoint = self.waypointsSearchBox.point
        self.waypoints.append(waypoint)
        if self.lineEditWaypoints.text() == "":
            self.lineEditWaypoints.setText(self.waypointsSearchBox.text())
        else:
            self.lineEditWaypoints.setText(self.lineEditWaypoints.text() +
                                           ";" +
                                           self.waypointsSearchBox.text())
        self.waypointsSearchBox.clearSearchBox()
        # Remove way point pin from the location input widget
        self.waypointsSearchBox.removePin()
        # Create/add new waypoint pin for the waypoint
        self.addWaypointPin(waypoint)

    def reverse(self):
        """Reverse route"""
        originLocation = self.originSearchBox.text()
        self.originSearchBox.setText(self.destinationSearchBox.text())
        self.destinationSearchBox.setText(originLocation)
        self.waypoints.reverse()
        self.waypointPins.reverse()
        waypointsCoordinates = []
        for waypoint in self.waypoints:
            waypointsCoordinates.append("%f, %f" %
                                        (waypoint.x(), waypoint.y()))
        self.lineEditWaypoints.setText(";".join(waypointsCoordinates))
        self.clearPins()
        self.addPins()

    def addWaypointPin(self, waypoint):
        """Create a new pin for a waypoint with its symbology"""
        # Create pin with waypoint symbology
        canvasCrs = QgsCoordinateReferenceSystem(4326)
        waypointPin = KadasPinItem(canvasCrs)
        waypointPin.setPosition(KadasItemPos(waypoint.x(), waypoint.y()))
        waypointPin.setup(
            ":/kadas/icons/waypoint",
            waypointPin.anchorX(),
            waypointPin.anchorX(),
            32,
            32,
        )
        self.waypointPins.append(waypointPin)
        KadasMapCanvasItemManager.addItem(waypointPin)

    def clearPins(self):
        """Remove all pins from the map
        Not removing the point stored.
        """
        # remove origin pin
        self.originSearchBox.removePin()
        # remove destination poin
        self.destinationSearchBox.removePin()
        # remove waypoint pins
        for waypointPin in self.waypointPins:
            KadasMapCanvasItemManager.removeItem(waypointPin)

    def addPins(self):
        """Add pins for all stored points."""
        self.originSearchBox.addPin()
        self.destinationSearchBox.addPin()
        for waypoint in self.waypoints:
            self.addWaypointPin(waypoint)

    def navigate(self):
        self.action.toggle()
        iface.setActiveLayer(self.layerSelector.getSelectedLayer())
        self.plugin.navigationAction.toggle()

    def actionToggled(self, toggled):
        if toggled:
            self.addPins()
        else:
            self.clearPins()
            self.clearAreasToAvoid()
            self.setPolygonDrawingMapTool(False)