class TFAnnotatorDockWidget(QtWidgets.QDockWidget, FORM_CLASS): closingPlugin = pyqtSignal() def __init__(self, parent=None): """Constructor.""" super(TFAnnotatorDockWidget, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) # TF Annotation Tool UI Setup self.addLabelButton.clicked.connect(self.addLabelButtonClicked) self.deleteLabelButton.clicked.connect(self.deleteLabelButtonClicked) self.addAnnotationButton.clicked.connect( self.addAnnotationButtonClicked) self.deleteAnnotationButton.clicked.connect( self.deleteAnnotationButtonClicked) self.clearButton.clicked.connect(self.clearButtonClicked) self.exportButton.clicked.connect(self.exportButtonClicked) self.model = QStandardItemModel(self.annotationView) self.model.setHorizontalHeaderLabels([ "Label", "Layer", "Path", "CRS", "Top", "Left", "Bottom", "Right", "C1X", "C1Y", "C2X", "C2Y" ]) self.annotationView.setModel(self.model) self.annotationView.selectionModel().selectionChanged.connect( self.currentRowChanged) self.annotationView.hideColumn(2) self.annotationView.hideColumn(4) self.annotationView.hideColumn(5) self.annotationView.hideColumn(6) self.annotationView.hideColumn(7) self.annotationView.hideColumn(8) self.annotationView.hideColumn(9) self.annotationView.hideColumn(10) self.annotationView.hideColumn(11) self.annotationView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.selectionBand = QgsRubberBand(iface.mapCanvas(), QgsWkbTypes.PolygonGeometry) self.selectionBand.setColor(QColor('green')) self.selectionBand.setWidth(1) def addLabelButtonClicked(self): self.resetSelectionBand() label = self.labelComboBox.currentText().strip() if self.labelComboBox.findText(label, QtCore.Qt.MatchExactly) < 0: self.labelComboBox.addItem(label) else: messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText(label + " already exists.") messageBox.exec_() self.labelComboBox.setCurrentIndex(-1) def deleteLabelButtonClicked(self): self.resetSelectionBand() labelIndex = self.labelComboBox.currentIndex() if labelIndex >= 0: self.labelComboBox.removeItem(labelIndex) else: messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("Label is not selected.") messageBox.exec_() def addAnnotationButtonClicked(self): self.resetSelectionBand() labelIndex = self.labelComboBox.currentIndex() if labelIndex < 0: messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("Label is not selected.") messageBox.exec_() return currentMapTool = iface.mapCanvas().mapTool() if not isinstance(currentMapTool, SelectMapTool): messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("Object is not selected.") messageBox.exec_() return if not currentMapTool.isValid(): messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("Selection is not valid.") messageBox.exec_() return information = currentMapTool.getInformation() if information is None: messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("Information cannot be retrieved.") messageBox.exec_() return crs = information.getCRS() aY, aX, bY, bX = information.getAbsoluteSelection() c1, c2 = information.getCoordinates() file = QFileInfo(information.getDataSource()) row = [ self.labelComboBox.currentText(), file.baseName(), file.filePath(), crs.authid(), str(aY), str(aX), str(bY), str(bX), str(c1.x()), str(c1.y()), str(c2.x()), str(c2.y()) ] self.model.appendRow([QStandardItem(item) for item in row]) currentMapTool.reset() def deleteAnnotationButtonClicked(self): indexes = self.annotationView.selectedIndexes() if len(indexes) == 0: messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("Annotation is not selected.") messageBox.exec_() return self.model.takeRow(indexes[0].row()) self.resetSelectionBand() def currentRowChanged(self, selected, deselected): indexes = self.annotationView.selectedIndexes() if len(indexes) == 0: return rowIndex = indexes[0].row() targetLayer = None for node in QgsProject.instance().layerTreeRoot().findLayers(): layer = node.layer() if isinstance(layer, QgsRasterLayer) and layer.dataProvider( ).dataSourceUri() == self.model.item(rowIndex, 2).text(): targetLayer = layer break root = QgsProject.instance().layerTreeRoot() if targetLayer is None: file = QFileInfo(self.model.item(rowIndex, 2).text()) path = file.filePath() base = file.baseName() targetLayer = QgsRasterLayer(path, base) QgsProject.instance().addMapLayer(targetLayer, False) root.insertLayer(0, targetLayer) else: clonedLayer = targetLayer.clone() QgsProject.instance().removeMapLayer(targetLayer.id()) QgsProject.instance().addMapLayer(clonedLayer) targetLayer = clonedLayer iface.setActiveLayer(targetLayer) crs = QgsCoordinateReferenceSystem(self.model.item(rowIndex, 3).text()) QgsProject.instance().setCrs(crs) iface.mapCanvas().setExtent(targetLayer.extent()) point1 = QgsPointXY(float(self.model.item(rowIndex, 8).text()), float(self.model.item(rowIndex, 9).text())) point2 = QgsPointXY(float(self.model.item(rowIndex, 8).text()), float(self.model.item(rowIndex, 11).text())) point3 = QgsPointXY(float(self.model.item(rowIndex, 10).text()), float(self.model.item(rowIndex, 11).text())) point4 = QgsPointXY(float(self.model.item(rowIndex, 10).text()), float(self.model.item(rowIndex, 9).text())) self.resetSelectionBand() self.selectionBand.addPoint(point1, False) self.selectionBand.addPoint(point2, False) self.selectionBand.addPoint(point3, False) self.selectionBand.addPoint(point4, True) self.selectionBand.setOpacity(0.5) self.selectionBand.show() def clearButtonClicked(self): currentMapTool = iface.mapCanvas().mapTool() if isinstance(currentMapTool, SelectMapTool): currentMapTool.reset() self.model.removeRows(0, self.model.rowCount()) self.resetSelectionBand() def exportButtonClicked(self): self.resetSelectionBand() if self.model.rowCount() == 0: messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("There is no annotation.") messageBox.exec_() return path, _ = QFileDialog.getSaveFileName(self, "TFRecord File", "", "TFRecord File (*.tfrecord)") if path == "": messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("File is not selected.") messageBox.exec_() return self.export(path) def closeEvent(self, event): self.resetSelectionBand() self.closingPlugin.emit() event.accept() def resetSelectionBand(self): self.selectionBand.reset(QgsWkbTypes.PolygonGeometry) def export(self, outputPath): annotations = {} for rowIndex in range(self.model.rowCount()): label = self.model.item(rowIndex, 0).text() # Label must start from 1 since Tensorflow internally use 0. labelIndex = self.labelComboBox.findText(label) + 1 annotation = Annotation( self.model.item(rowIndex, 2).text(), int(self.model.item(rowIndex, 4).text()), int(self.model.item(rowIndex, 5).text()), int(self.model.item(rowIndex, 6).text()), int(self.model.item(rowIndex, 7).text()), int(labelIndex), label) if annotation.path not in annotations: annotations[annotation.path] = [] annotations[annotation.path].append(annotation) writer = tflow.python_io.TFRecordWriter(outputPath) index = 0 for path in annotations: loader = ImageLoader(path) image, width, height = loader.load() basename = os.path.basename(path) xmins = [] xmaxs = [] ymins = [] ymaxs = [] classes_text = [] classes = [] for annotation in annotations[path]: xmins.append(annotation.left / width) xmaxs.append(annotation.right / width) ymins.append(annotation.top / height) ymaxs.append(annotation.bottom / height) classes_text.append(tflow.compat.as_bytes(annotation.label)) classes.append(annotation.index) example = tflow.train.Example(features=tflow.train.Features( feature={ 'image/width': _int64_feature(width), 'image/height': _int64_feature(height), 'image/filename': _bytes_feature(tflow.compat.as_bytes(basename)), 'image/source_id': _bytes_feature(tflow.compat.as_bytes(basename)), 'image/encoded': _bytes_feature(image), 'image/format': _bytes_feature(tflow.compat.as_bytes('jpeg')), 'image/object/bbox/xmin': _float_list_feature(xmins), 'image/object/bbox/xmax': _float_list_feature(xmaxs), 'image/object/bbox/ymin': _float_list_feature(ymins), 'image/object/bbox/ymax': _float_list_feature(ymaxs), 'image/object/class/text': _bytes_list_feature(classes_text), 'image/object/class/label': _int64_list_feature(classes) })) writer.write(example.SerializeToString()) writer.close() directory, file = os.path.split(outputPath) filename = os.path.splitext(file)[0] pbtxtPath = os.path.join(directory, filename + '.pbtxt') writer = PBTXTWriter() writer.open(pbtxtPath) for index in range(self.labelComboBox.count()): # Label must start from 1 since Tensorflow internally use 0. writer.write(index + 1, self.labelComboBox.itemText(index)) writer.close() sys.stdout.flush()
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 SelectMapTool(QgsMapTool): def __init__(self, canvas): super(SelectMapTool, self).__init__(canvas) self.rubberBand = QgsRubberBand(canvas, QgsWkbTypes.PolygonGeometry) self.rubberBand.setColor(QColor('orange')) self.rubberBand.setWidth(1) self.reset() def reset(self): self.start = self.end = None self.startCoordinate = self.endCoordinate = None self.isEmittingPoint = False self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) self.retriever = None def canvasPressEvent(self, event): self.start = event.pos() self.startCoordinate = self.toMapCoordinates(self.start) self.end = self.start self.endCoordinate = self.startCoordinate self.isEmittingPoint = True self.showRect(self.startCoordinate, self.endCoordinate) def canvasReleaseEvent(self, event): self.isEmittingPoint = False if not isinstance(iface.activeLayer(), QgsRasterLayer): messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("The active layer is not raster.") messageBox.exec_() self.reset() return rectangle = self.getRectangle() if rectangle is None: messageBox = QMessageBox() messageBox.setIcon(QMessageBox.Information) messageBox.setText("Object is not selected.") messageBox.exec_() self.reset() return self.retriever = InformationRetriever() self.retriever.retrieve(iface, self.start, self.end) self.retriever.setCoordinates(self.startCoordinate, self.endCoordinate) def canvasMoveEvent(self, event): if not self.isEmittingPoint: return self.end = event.pos() self.endCoordinate = self.toMapCoordinates(self.end) self.showRect(self.startCoordinate, self.endCoordinate) def showRect(self, startCoordinate, endCoordinate): self.rubberBand.reset(QgsWkbTypes.PolygonGeometry) if startCoordinate.x() == endCoordinate.x() or startCoordinate.y( ) == endCoordinate.y(): return point1 = QgsPointXY(startCoordinate.x(), startCoordinate.y()) point2 = QgsPointXY(startCoordinate.x(), endCoordinate.y()) point3 = QgsPointXY(endCoordinate.x(), endCoordinate.y()) point4 = QgsPointXY(endCoordinate.x(), startCoordinate.y()) self.rubberBand.addPoint(point1, False) self.rubberBand.addPoint(point2, False) self.rubberBand.addPoint(point3, False) self.rubberBand.addPoint(point4, True) self.rubberBand.setOpacity(0.5) self.rubberBand.show() def getRectangle(self): if self.startCoordinate is None or self.endCoordinate is None: return None if self.startCoordinate.x() == self.endCoordinate.x( ) or self.startCoordinate.y() == self.endCoordinate.y(): return None return QgsRectangle(self.startCoordinate, self.endCoordinate) def isValid(self): if self.retriever == None: return False if not isinstance(self.retriever.getLayer(), QgsRasterLayer): return False if iface.activeLayer() != self.retriever.getLayer(): return False if not os.path.exists(self.retriever.getDataSource()): return False if self.retriever.getCRS().authid() != super().canvas().mapSettings( ).destinationCrs().authid(): return False return True def getInformation(self): return self.retriever def deactivate(self): self.reset()
class BaseTool(QgsMapToolEmitPoint): __metaclass__ = ABCMeta apply = pyqtSignal(dict) def _apply(self): self.collect_data() if not self._data else None self._data[k.geometry] = { k.line: self.line_band.asGeometry(), k.polygon: self.poly_band.asGeometry() } self.apply.emit(self._data) self.reset() def __init__(self, canvas): self.canvas = canvas QgsMapToolEmitPoint.__init__(self, self.canvas) self.replaced_tool = canvas.mapTool() self.points = [] self.done = True self._line_band = None self._poly_band = None self._markers = [] self._info = None self._line_color = Qt.black self._poly_color = Qt.lightGray self._data = {} @property def n_points(self): return len(self.points) @property def has_points(self): return self.n_points > 1 @property def info(self): if not self._info: self._info = QLabel(parent=self.canvas) font = self._info.font() font.setPointSize(10) font.setFamily('Courier New') self._info.setFont(font) self._info.setStyleSheet('color: black') return self._info @property def line_band(self): if self._line_band is None: self._line_band = QgsRubberBand(self.canvas) self._line_band.setColor(self._line_color) self._line_band.setOpacity(1) self._line_band.setWidth(1) self._line_band.reset(QgsWkbTypes.LineGeometry) return self._line_band @property def poly_band(self): if self._poly_band is None: self._poly_band = QgsRubberBand(self.canvas) self._poly_band.setColor(self._poly_color) self._poly_band.setOpacity(0.1) self._poly_band.setWidth(0.1) self._poly_band.reset(QgsWkbTypes.PolygonGeometry) return self._poly_band @property def markers(self): if not self._markers: line_points = self.line_points line_points = line_points if line_points else self.points for point in line_points: index = line_points.index(point) is_extreme_point = index == 0 or index == len(line_points) - 1 marker = QgsVertexMarker(self.canvas) marker.setColor(self._line_color) marker.setFillColor( self._poly_color if not is_extreme_point else Qt.red) marker.setOpacity(1) marker.setPenWidth(1) marker.setIconSize(5) marker.setIconType(QgsVertexMarker.ICON_CIRCLE) marker.setCenter(point) self._markers.append(marker) return self._markers @property @abstractmethod def line_points(self): """точки для отрисовки линии""" @property @abstractmethod def poly_points(self): """точки для отрисовки фона""" def draw(self): if not self.has_points: return self.reset() line_points = self.line_points for point in line_points: is_last_point = line_points.index(point) == len(line_points) - 1 self.line_band.addPoint(point, doUpdate=is_last_point) poly_points = self.poly_points for point in poly_points: is_last_point = poly_points.index(point) == len(poly_points) - 1 self.poly_band.addPoint(point, doUpdate=is_last_point) self.show() @abstractmethod def collect_data(self): """сбор данных для отправки клиенту""" @abstractmethod def collect_info(self): """ подготовка данных к отображению рядом с курсором мыши""" def show_info(self): self.info.hide() self.info.setText(self.collect_info()) tl = self.canvas.mapToGlobal(self.canvas.rect().topLeft()) self.info.move(QCursor.pos().x() + 15 - tl.x(), QCursor.pos().y() - tl.y()) self.info.show() def reset(self): for marker in self.markers: marker.hide() self.markers.clear() self.line_band.reset(QgsWkbTypes.LineGeometry) self.poly_band.reset(QgsWkbTypes.PolygonGeometry) def show(self): for marker in self.markers: marker.show() self.line_band.show() self.poly_band.show() self.show_info() def set_done(self, clear): if clear: self.reset() else: self._apply() self.done = True self.points = [] self.canvas.setMapTool(self.replaced_tool) self.canvas.refresh() self.info.hide() self._info = None def canvasPressEvent(self, e): if e.button() == Qt.RightButton: self.set_done(clear=True) if e.button() == Qt.LeftButton: self.done = False def canvasReleaseEvent(self, e): if self.done or e.button() != Qt.LeftButton: return p = self.toMapCoordinates(e.pos()) self.points.append(p) self.done = False self.draw() def canvasMoveEvent(self, e): if self.done: return p = self.toMapCoordinates(e.pos()) self.points.pop() if self.has_points else None self.points.append(p) self.done = False self.draw() def canvasDoubleClickEvent(self, e): if not self.done: self.set_done(clear=False) def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.set_done(clear=True) if e.key() in (Qt.Key_Return, Qt.Key_Enter): self.set_done(clear=False) def deactivate(self): if not self.done: self.set_done(clear=True) super(BaseTool, self).deactivate()