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
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()
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
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()
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
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
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()
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
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()
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
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
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
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()
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()
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
class PlanetSearchResultsView(QTreeView): """ """ checkedCountChanged = pyqtSignal(int) def __init__(self, parent, iface=None, api_key=None, request_type=None, request=None, response_timeout=RESPONSE_TIMEOUT, sort_order=None): super().__init__(parent=parent) # noinspection PyTypeChecker self._parent: PlanetSearchResultsWidget = parent self._iface = iface self._api_key = api_key self._request_type = request_type self._request = request self._response_timeout = response_timeout self._sort_order = sort_order self._footprint = None self._setup_footprint() self._thumb_cache_dir: str = pluginSetting( 'thumbCachePath', namespace=SETTINGS_NAMESPACE) self._checked_count = 0 self._checked_queue = {} self._search_model = PlanetSearchResultsModel( parent=self, api_key=api_key, request_type=request_type, request=request, thumb_cache_dir=self._thumb_cache_dir, sort_order=self._sort_order ) # Generic model, as background, until results come in # self._search_model = QStandardItemModel(0, 2, self) self._search_model.thumbnail_cache().set_job_subclass( PlanetQgisRenderJob ) p = self.palette() p.setColor(QPalette.Highlight, ITEM_BACKGROUND_COLOR) self.setPalette(p) self.setModel(self._search_model) self.setIconSize(QSize(48, 48)) self.setAlternatingRowColors(True) self.setHeaderHidden(False) # self.setColumnWidth(0, 250) self.setColumnWidth(1, 26) self.setIndentation(int(self.indentation() * 0.75)) hv = self.header() hv.setStretchLastSection(False) hv.setSectionResizeMode(0, QHeaderView.Stretch) hv.setSectionResizeMode(1, QHeaderView.Fixed) if len(sort_order) > 1: if sort_order[1] == 'asc': sort_indicator = Qt.AscendingOrder else: sort_indicator = Qt.DescendingOrder hv.setSortIndicator(0, sort_indicator) hv.setSortIndicatorShown(True) self.viewport().setAttribute(Qt.WA_Hover) self.setMouseTracking(True) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionBehavior(QAbstractItemView.SelectRows) # self.setWordWrap(True) # self.setTextElideMode(Qt.ElideNone) self.item_delegate = PlanetNodeItemDelegate(parent=self) # noinspection PyUnresolvedReferences self.item_delegate.previewFootprint['PyQt_PyObject'].connect( self.preview_footprint) # noinspection PyUnresolvedReferences self.item_delegate.clearFootprint.connect(self.clear_footprint) self.setItemDelegateForColumn(0, self.item_delegate) self.act_delegate = PlanetNodeActionDelegate(parent=self) self.setItemDelegateForColumn(1, self.act_delegate) self.setContextMenuPolicy(Qt.CustomContextMenu) # noinspection PyUnresolvedReferences self.customContextMenuRequested['QPoint'].connect(self.open_menu) # noinspection PyUnresolvedReferences self.clicked['QModelIndex'].connect(self.item_clicked) # noinspection PyUnresolvedReferences self.expanded['QModelIndex'].connect(self.item_expanded) def search_model(self): return self._search_model def checked_count(self): return self._checked_count def checked_queue(self): return self._checked_queue def _setup_footprint(self): if self._iface: log.debug('iface is available, adding footprint support') self._footprint = QgsRubberBand( self._iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self._footprint.setFillColor(QColor(255, 255, 255, 10)) self._footprint.setStrokeColor(PLANET_COLOR) self._footprint.setWidth(2) else: log.debug('iface is None, skipping footprint support') self._footprint = None # noinspection PyMethodMayBeStatic def qgsfeature_feature_from_node(self, node: PlanetNode): # TODO: Resolve geometry by node_type or do that under node.geometry()? # geom = None # if node.node_type() == NodeT.DAILY_SCENE_IMAGE: # geom = node.geometry() # TODO: Add node # feature_collect = { # 'type': 'FeatureCollection', # 'features': [ # { # 'type': 'Feature', # 'geometry': node.geometry(), # 'properties': { # 'id': node.item_id() # } # } # ] # } feature_collect = { 'type': 'FeatureCollection', 'features': [ node.resource() ] } feature_collect_json = json.dumps(feature_collect) # noinspection PyUnusedLocal features = [] # noinspection PyBroadException try: utf8 = QTextCodec.codecForName('UTF-8') # TODO: Add node id, properties as fields? fields = QgsFields() features = QgsJsonUtils().stringToFeatureList( string=feature_collect_json, fields=fields, encoding=utf8) except Exception: log.debug('Footprint GeoJSON could not be parsed') return if not len(features) > 0: log.debug('GeoJSON parsing created no features') return return features[0] @pyqtSlot() def clear_footprint(self): if self._footprint: self._footprint.reset(QgsWkbTypes.PolygonGeometry) @pyqtSlot('PyQt_PyObject') def preview_footprint(self, node: PlanetNode): if not self._footprint: if LOG_VERBOSE: log.debug('Footprint is None, skipping footprint preview') return if node.has_group_footprint(): geoms = node.geometries() else: geoms = [node.geometry()] self.clear_footprint() qgs_geoms = [qgsgeometry_from_geojson(g) for g in geoms] for qgs_geom in qgs_geoms: self._footprint.addGeometry( qgs_geom, QgsCoordinateReferenceSystem("EPSG:4326") ) if LOG_VERBOSE: log.debug('Footprint sent to canvas') @pyqtSlot(list) def zoom_to_footprint(self, nodes: [PlanetNode]): skip = 'skipping zoom to footprint' if not self._footprint: log.debug(f'Footprint is None, {skip}') return if len(nodes) < 1: log.debug('No nodes available, skipping zoom to footprint') return first_node = nodes[0] if first_node.has_group_footprint(): json_geoms = first_node.geometries() else: json_geoms = [node.geometry() for node in nodes] qgs_geoms: [QgsGeometry] = \ [qgsgeometry_from_geojson(j) for j in json_geoms] if len(qgs_geoms) < 1: log.debug(f'Geometry collection empty, {skip}') return rect_geoms: QgsRectangle = qgs_geoms[0].boundingBox() for i in range(len(qgs_geoms)): if i == 0: continue r: QgsRectangle = qgs_geoms[i].boundingBox() rect_geoms.combineExtentWith(r) if rect_geoms.isNull(): log.debug(f'Footprint geometry is null, {skip}') return # noinspection PyArgumentList transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), QgsProject.instance().crs(), QgsProject.instance() ) rect_footprint: QgsRectangle = \ transform.transformBoundingBox(rect_geoms) if not rect_footprint.isEmpty(): rect_footprint.scale(1.05) self._iface.mapCanvas().setExtent(rect_footprint) self._iface.mapCanvas().refresh() @pyqtSlot('PyQt_PyObject') def preview_thumbnail( self, node, name=PE_PREVIEW, remove_existing=True): item_name_geo = f'{node.item_type_id_key()}{THUMB_GEO}' item_geo_path = os.path.join( self.search_model().thumbnail_cache().cache_dir(), f'{item_name_geo}{THUMB_EXT}') if not preview_local_item_raster( item_geo_path, name, remove_existing=remove_existing): log.warning(f'Item preview {item_name_geo} failed to load') @pyqtSlot(dict) def update_preview_thumbnails(self, selected_nodes): group = temp_preview_group() tree_node_names: List[str] = \ [t_node.name() for t_node in group.children()] for name in tree_node_names: if name.endswith('_thumb'): name_sans = name.replace('_thumb', '') if name_sans not in selected_nodes: remove_maplayers_by_name(name, only_first=True) if name_sans in selected_nodes: # Keep already loaded layer, skip reloading it selected_nodes[name_sans] = None for _, node in selected_nodes.items(): if node is not None: self.preview_thumbnail(node) # for node in deselected_nodes: # if f'{node.item_type_id_key()}_thumb'.lower() in node_names: # remove_maplayers_by_name( # f'{node.item_type_id_key()}_thumb'.lower(), # only_first=True) # # node_names = [t_node.name() for t_node in group.children()] # for node in selected_nodes: # if f'{node.item_type_id_key()}_thumb'.lower() not in node_names: # self.preview_thumbnail(node) @pyqtSlot(list) def add_preview_groups(self, nodes: List[PlanetNode]): if len(nodes) < 1 or not isinstance(nodes[0], PlanetNode): log.debug('No nodes found to add to preview group') return if nodes[0].is_group_node(): log.debug('Adding preview group for group') # Grouping tree items are only singularly passed self.add_preview_group_for_group(nodes[0]) else: log.debug('Adding preview group for items') self.add_preview_groups_for_items(nodes) @pyqtSlot(list) def add_preview_group_for_group(self, node: PlanetNode): name = None child_node_type = None if node.node_type() == NodeT.DAILY_SCENE: child_node_type = NodeT.DAILY_SCENE_IMAGE item_type = node.name() or '' title = ['Daily', item_type, 'Scene'] name = f'{" ".join(title)} ' \ f'{node.formatted_date_time(node.sort_date())}' elif node.node_type() == NodeT.DAILY_SAT_GROUP: child_node_type = NodeT.DAILY_SCENE_IMAGE item_type = node.parent().name() or '' title = ['Daily', item_type, f'Satellite {node.name()} Group'] name = f'{" ".join(title)} ' \ f'{node.formatted_date_time(node.sort_date())}' if child_node_type is None: log.debug('No node type found for tree group searching') return item_nodes = node.children_of_node_type(child_node_type) if item_nodes: create_preview_group( name, item_nodes, self._search_model.p_client().api_key(), tile_service='xyz', search_query=self._request, sort_order=self._sort_order ) else: log.debug(f"No items found for node type '{child_node_type.name}' " f"in tree group '{name}'") @pyqtSlot(list) def add_preview_groups_for_items(self, nodes: List[PlanetNode]) -> None: prev_types = [] # maintain some sort order prev_type_nodes = {} for node in nodes: item_type = node.item_type() if item_type not in prev_types: prev_types.append(item_type) prev_type_nodes[item_type] = [] prev_type_nodes[item_type].append(node) for prev_type in sorted(prev_types): prev_nodes: List[PlanetNode] = prev_type_nodes[prev_type] # if prev_type in DAILY_ITEM_TYPES_DICT: # # Group imagery by type # item_keys = [n.item_type_id() for n in prev_nodes] # tile_url = self._search_model.p_client().get_tile_url( # item_keys) # create_preview_group(prev_type, prev_nodes, tile_url) # else: # # For groups, use any item type listing # for prev_node in prev_nodes: # item_keys = prev_node.item_type_id_list() # if item_keys: # tile_url = \ # self._search_model.p_client().get_tile_url( # item_keys) # create_preview_group(prev_type, [], tile_url) create_preview_group( prev_type, prev_nodes, self._search_model.p_client().api_key(), tile_service='xyz', search_query=self._request, sort_order=self._sort_order ) @pyqtSlot(list) def copy_ids_to_clipboard(self, nodes): node_ids = [n.item_type_id() for n in nodes if n.item_id()] if node_ids: cb = QgsApplication.clipboard() cb.setText(','.join(node_ids)) @pyqtSlot('QPoint') def open_menu(self, pos): """ :type pos: QPoint :return: """ index = self.indexAt(pos) node: PlanetNode = self.model().get_node(index) if (node.node_type() == NodeT.LOAD_MORE and node.parent() == self.model().root): return menu = QMenu() # Single, current Item's index add_menu_section_action('Current item', menu) if node.has_footprint() or node.has_group_footprint(): zoom_fp_act = QAction('Zoom to footprint', menu) # noinspection PyUnresolvedReferences zoom_fp_act.triggered[bool].connect( lambda: self.zoom_to_footprint([node])) menu.addAction(zoom_fp_act) if node.can_load_preview_layer(): prev_layer_act = QAction('Add preview layer to map', menu) # noinspection PyUnresolvedReferences prev_layer_act.triggered[bool].connect( lambda: self.add_preview_groups([node])) if node.child_images_count() > CHILD_COUNT_THRESHOLD_FOR_PREVIEW: prev_layer_act.setEnabled(False) prev_layer_act.setToolTip("The node contains too many images to preview") menu.setToolTipsVisible(True) menu.addAction(prev_layer_act) if node.item_id() and node.has_resource(): copy_id_act = QAction('Copy ID to clipboard', menu) # noinspection PyUnresolvedReferences copy_id_act.triggered[bool].connect( lambda: self.copy_ids_to_clipboard([node])) menu.addAction(copy_id_act) # Selected Items sel_model = self.selectionModel() model = self.model() # Ensure to grab only first column of indexes (or will get duplicates) all_nodes = [model.get_node(i) for i in sel_model.selectedIndexes() if i.column() == 0] log.debug(f'Selected items: {len(all_nodes)}') if len(all_nodes) == 1 and all_nodes[0] == node: menu.exec_(self.viewport().mapToGlobal(pos)) return nodes_have_footprints = \ [node for node in all_nodes if node.has_footprint()] nodes_w_ids = \ [node for node in all_nodes if node.item_id() and node.has_resource()] nodes_can_prev = \ [node for node in all_nodes if node.can_load_preview_layer() and node.has_resource()] if any([nodes_have_footprints, nodes_w_ids, nodes_can_prev]): add_menu_section_action(f'Selected images', menu) if nodes_have_footprints: zoom_fps_act = QAction( f'Zoom to total footprint ' f'({len(nodes_have_footprints)} items)', menu) # noinspection PyUnresolvedReferences zoom_fps_act.triggered[bool].connect( lambda: self.zoom_to_footprint(nodes_have_footprints)) menu.addAction(zoom_fps_act) if nodes_can_prev: prev_layers_act = QAction( f'Add preview layer to map ' f'({len(nodes_can_prev)} items)', menu) # noinspection PyUnresolvedReferences prev_layers_act.triggered[bool].connect( lambda: self.add_preview_groups(nodes_can_prev)) menu.addAction(prev_layers_act) if nodes_w_ids: copy_ids_act = QAction( f'Copy IDs to clipboard ({len(nodes_w_ids)} items)', menu) # noinspection PyUnresolvedReferences copy_ids_act.triggered[bool].connect( lambda: self.copy_ids_to_clipboard(nodes_w_ids)) menu.addAction(copy_ids_act) menu.exec_(self.viewport().mapToGlobal(pos)) @pyqtSlot('QModelIndex') def item_clicked(self, index): node: PlanetNode = self.model().get_node(index) log.debug(f'Index clicked: row {index.row()}, col {index.column()}, ' f'{node.item_type_id()}') if index.column() == 0: if (node.node_type() == NodeT.LOAD_MORE and node.parent() == self.model().root): self.model().fetch_more_top_items(index) elif index.column() == 1: self.open_menu(self.viewport().mapFromGlobal(QCursor.pos())) @pyqtSlot('QModelIndex') def item_expanded(self, index: QModelIndex): node: PlanetNode = self.model().get_node(index) log.debug(f'Index expanded: row {index.row()}, col {index.column()}, ' f'{node.item_type_id()}') # log.debug( # f'Node traversed: {node.is_traversed()} {node.item_type_id()}') # if index.column() == 0: # if (node.node_type() == NodeT.DAILY_SCENE # and not node.is_traversed()): # log.debug( # f'Traversing node: {node.item_type_id()}') # for sat_grp in node.children(): # sat_grp: PlanetNode # self.expand(sat_grp.index()) # # for image in sat_grp.children(): # if image.has_thumbnail(): # self.search_model().add_to_thumb_queue( # image.item_type_id_key(), image.index()) # self.search_model().fetch_thumbnail(image) # # node.set_is_traversed(True) def _update_checked_queue(self, checked_nodes: Set[PlanetNode], unchecked_nodes: Set[PlanetNode]): for c_node in checked_nodes: it_id = c_node.item_type_id() self._checked_queue[it_id] = c_node for u_node in unchecked_nodes: it_id = u_node.item_type_id() if it_id in self._checked_queue: del self._checked_queue[it_id] self._checked_count = len(self._checked_queue) log.debug(f'checked_count: {self._checked_count}') if LOG_VERBOSE: sorted_item_ids = sorted(self._checked_queue.keys()) nl = '\n' log.debug(f'checked_queue:\n' f' {"{0} ".format(nl).join(sorted_item_ids)}') # When using with {'item_type': set(nodes)} # for it_id in self._checked_queue: # log.debug(f'\n - {it_id}: ' # f'{len(self._checked_queue[it_id])}\n') # # # Super verbose output... # nl = '\n' # i_types = \ # [n.item_id() for n in self._checked_queue[it_id]] # log.debug(f'\n - {it_id}: ' # f'{len(self._checked_queue[it_id])}\n' # f' - {"{0} - ".format(nl).join(i_types)}') self.checkedCountChanged.emit(self._checked_count) def event(self, event: QEvent) -> bool: if event.type() == QEvent.Leave: if self._iface: self.clear_footprint() event.accept() return QTreeView.event(self, event) def mousePressEvent(self, event: QMouseEvent) -> None: index = self.indexAt(event.pos()) sel_model: QItemSelectionModel = self.selectionModel() if (index.column() == 1 and event.button() == Qt.LeftButton and sel_model.isSelected(index)): log.debug('Ignoring mouse press') return return QTreeView.mousePressEvent(self, event) def mouseReleaseEvent(self, event: QMouseEvent) -> None: index = self.indexAt(event.pos()) sel_model: QItemSelectionModel = self.selectionModel() if (index.column() == 1 and event.button() == Qt.LeftButton and sel_model.isSelected(index)): log.debug('Swapping left button for right, on release') self.open_menu(event.pos()) return return QTreeView.mouseReleaseEvent(self, event) def keyReleaseEvent(self, event: QKeyEvent) -> None: index = self.currentIndex() if (index.column() == 0 and event.key() in [Qt.Key_Enter, Qt.Key_Return]): node: PlanetNode = self.model().get_node(index) if (node.node_type() == NodeT.LOAD_MORE and node.parent() == self.model().root): self.model().fetch_more_top_items(index) return QTreeView.keyReleaseEvent(self, event) def dataChanged(self, t_l: QModelIndex, b_r: QModelIndex, roles: Optional[List[int]] = None) -> None: # This may need to go at end of slot super().dataChanged(t_l, b_r, roles=roles) # Note: empty roles indicates *everything* has changed, not nothing if roles is not None and (roles == [] or Qt.CheckStateRole in roles): if LOG_VERBOSE: log.debug('Node (un)checked') checked = set() unchecked = set() def update_queues(indx): node: PlanetNode = self._search_model.get_node(indx) if node.is_base_image() and node.can_be_downloaded(): if node.checked_state() == Qt.Checked: checked.add(node) elif node.checked_state() == Qt.Unchecked: unchecked.add(node) # Note: if parent of t_l and b_r differ, we ignore undefined input if t_l == b_r: if LOG_VERBOSE: log.debug('Nodes (un)checked is single') update_queues(t_l) elif t_l.parent() == b_r.parent(): if LOG_VERBOSE: log.debug('Nodes (un)checked have same parent') parent = t_l.parent() col = t_l.column() for row in range(b_r.row(), t_l.row() + 1): m_indx = self._search_model.index(row, col, parent=parent) update_queues(m_indx) if checked or unchecked: if LOG_VERBOSE: log.debug(f'Nodes checked: {checked}') log.debug(f'Nodes unchecked: {unchecked}') self._update_checked_queue(checked, unchecked) # This works, but is disabled until thumbnail georeferencing works OK # def currentChanged(self, current: QModelIndex, # previous: QModelIndex) -> None: # node: PlanetNode = self.model().get_node(current) # log.debug(f'Current item: row {current.row()}, ' # f'col {current.column()},' # f' {node.item_id()}') # if current.column() == 0: # if node.has_thumbnail() and node.thumbnail_loaded(): # self.preview_thumbnail(node) # else: # clear_local_item_raster_preview() # # return QTreeView.currentChanged(self, current, previous) # def selectionChanged(self, selected: QItemSelection, # deselected: QItemSelection) -> None: # selected_nodes = {} # for si in selected.indexes(): # if si.column() == 0: # si_node = self._search_model.get_node(si) # selected_nodes[si_node.item_type_id_key()] = si_node # # deselected_nodes = {} # for di in deselected.indexes(): # if di.column() == 0: # di_node = self._search_model.get_node(di) # deselected_nodes[di_node.item_type_id_key()] = di_node # # self.update_preview_thumbnails(selected_nodes) # # return QTreeView.selectionChanged(self, selected, deselected) @pyqtSlot() def clean_up(self): self.clear_footprint()
class 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 = []
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)