Example #1
0
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()
Example #2
0
class DiscoveryPlugin:
    def __init__(self, _iface):
        # Save reference to the QGIS interface
        self.iface = _iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)

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

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

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

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

    def initGui(self):

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

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

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

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

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

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

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

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

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

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

        # Search results
        self.search_results = []

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

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

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

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

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

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

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

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

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

        self.query_text = new_search_text

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.make_enabled(True)

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

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

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

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

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

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

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

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

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

    def hide_rubber_band(self):
        opacity = self.rubber_band.opacity()
        if opacity > 0.:
            # produce a fade out effect
            opacity -= 0.1
            self.rubber_band.setOpacity(opacity)
            QTimer.singleShot(100, self.hide_rubber_band)
        else:
            self.rubber_band.setVisible(False)
            self.rubber_band.hide()
Example #3
0
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()
Example #4
0
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()