GithubHelp home page GithubHelp logo

nationalsecurityagency / qgis-searchlayers-plugin Goto Github PK

View Code? Open in Web Editor NEW
75.0 20.0 50.0 698 KB

Enhanced textual vector layer searching in QGIS.

License: GNU General Public License v2.0

Makefile 1.23% Python 83.89% HTML 14.48% QMake 0.22% Batchfile 0.18%
fuzzy fuzzy-search levenshtein qgis search soundex

qgis-searchlayers-plugin's Introduction

Search Layers Plugin

The Search Layers plugin features enhanced textual vector layer searching in QGIS. It provides the ability to search across all layers and all fields. It also features a Fuzzy Search algorithm.

Search Layers is located in the QGIS Plugins menu under "Plugins->Search Layers->Search Layers" or by selecting the tool bar icon.

The following dialog box is displayed when "Search Layers" is first launched.

Search Layers Dialog

Search criteria options

Under Search criteria, you can enter up to two search strings. The NOT check box will negate the search results for the applicable string.

Under each search string text box a drop down menu specifies how the search string is to match the contents in the attribute table. The options are:

  • equals - This requires an exact match.
  • contains - This looks for a field that contains the search string.
  • begins with - This search finds any field that begins with the search string.
  • ends with - This search finds any field that ends with the search string.

Case sensitive - When checked the search string must match that of the attribute text case. by default it is unchecked and does a case independent search.

Constrain two search strings to match within an attribute field rather than across attribute fields - This will constrain the search criteria when two search strings are used to match within an attribute field; otherwise, one string may match one attribute field and the other string may match another attribute field. The results are either ANDed or ORed together. Here is an example of a two string search.

Search Layers Dialog

Additional options

Search Layers specifies what layers will be in the search.

  • <All Layers> - All vector layers will be searched whether they are visible or not.

  • <Selected layers> - All the selected layers in the Layers panel will be searched whether they are visible or not.

  • <Visible layers> - All visible layers will be searched.

  • A specific layer - Any of the vector layers in the QGIS project can be selected. When one is selected, then Search Fields will be enabled and by default <All Fields> will be displayed, but any field can be chosen from the layer and the search will only search on that layer and field.

    You can also specify any layer using the project variable searchlayers-plugin to display only the specified layer(s). The Value of the variable is specified as 'layer-name,layer-name,... '. Layer names that do not exist will be ignored.

    Specify by variable

    You can add the project variable by selecting Settings->Options... from the QGIS menu and then selecting the Variables tab on the left. You click on the bottom right green plus sign to add a new variable. Type in searchlayers-plugin in the first column and then the comma separated layer list in the second column.

Constrain search to canvas extent - Checking this box will constrain the search to look for features that are within the current canvas window view. If you are only interested in the features in the canvas view, this will greatly speed up the search.

Report one result per feature - It is possible that a search string could match the contents in one or more attribute fields. By checking this, only the first match per feature will be reported.

Only search selected features - When checked, the features that are selected in QGIS will be the only ones searched. This is one way to limit a search to a particular area. Note that with this checked, the normal interaction of clicking on a found feature in the list will not select the feature for obvious reasons; however, the zoom or pan to actions will still apply.

Zoom action when selecting features below - When matches are found and are clicked on this affects that action that takes place.

  • Do nothing - No action takes place.
  • Zoom to selected features - QGIS will zoom in on the selected features.
  • Pan to selected features - QGIS will pan to the selected features.

The selected feature(s) will be highlighted unless Only search selected features has been checked. Shift->Click will highlight a range of features. Use Ctrl->Click to toggle whether a feature is selected or not.

Results -> Layers - Clicking on this button will export all the found results into new virtual layer. It is advised to install the Memory Saver Plugin to prevent these layers from disappearing after closing the project.

Search - Clicking on this button will begin the search. In the case of a large data set, clicking on Abort will halt the process. The search will stop after finding 2000 matches.

Note that the search is very quick when selecting a single vector layer under Search Layers and a single field under Search Fields. If this is not the case, regular expression searches are used and are slow. In the future this may change.

Open the search results feature's attribute form

If you right-mouse click on a search results entry, a context menu titled Open Record Form will be displayed.

Search Layers Dialog

Clicking on Open Record Form will display the feature's attributes. If the layer is in edit mode, then you can modify the feature's attributes and save them. If it is not in edit mode, you will be able to view them but not edit them.

Search Layers Dialog

Fuzzy search

This QGIS plugin makes use of two fuzzy search algorithms. One is based off of the Levenshtein algorithm and the other is a Soundex algorithm. The soundex is really only useful for matching single words that sound the same. This is a screen shot.

Search Layers Dialog

qgis-searchlayers-plugin's People

Contributors

hamiltoncj avatar nyalldawson avatar rnousia avatar spacemiqote avatar yamamoto-ryuzo avatar zsiki avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

qgis-searchlayers-plugin's Issues

Granularity on Large Feature Counts

First, Thank you for this plugin.
I am using it on 3.4 and 3.6

My use-case is to search for certain partial names in the State Tax Parcel layer.

Selecting that Layer, I want to select a limited amount of Fields to Search (as there are many) but it seems I have to do ALL or ONE.

#1 I Would very much like to select several fields in the layer.
#2 Names can be multipart, So I would like to have the ability to limit the STRING thus:
Right now if I want to search for the Lastname "Ash", not knowing if it is listed before or after the Firstname, I have to use contains... which easily gets me over the cap of 1500 because I get "Wallabash", "Basheeba", etc. I would like a way to say there is a SPACE before, or the String begins the whole field. (I know I can say Begins with. But with this much data, those iterations take a lot of time.

Thanx!

Possibility to use search fields option with "All Layers" option selected

Hi! And thank you for the plugin, this seems to be a very useful tool.

I found this plugin while trying to search for a way to loop through features on multiple layers. This plugin would be quite optimal for our need but there is one caveat: I could not find a way to specify search field name for multiple layers.

Short background: our users have 70+ layers in QGIS and they need to find features with a certain attribute value. All the layers have a common field which needs to be searched. Unfortunately the search value is too common value found in all the fields (like "1" or "a") so currently Search Layers plugin would result 2000+ results and only a minor subset would be the actual features the users are interested in.

Would it be possible to list all the fields in the "Search Fields" combobox even when all the layers are selected? This would of course mean that the list would be quite long but as it is possible to search the list by typing (plus use arrow keys I think?) I think it might not be too much? Or maybe the list could also be in alphabetical order and have a scroll bar?

I tested this a bit by modifying initFieldList-method and it seems to work just with this change (but I just tested with my case if it works):

    def initFieldList(self):
        selectedLayer = self.layerListComboBox.currentIndex()
        self.searchFieldComboBox.clear()
        self.searchFieldComboBox.addItem(tr('<All Fields>'))

        unique_field_names = set()
        layers: List[QgsVectorLayer] = []

        # Following if structure copied from https://github.com/NationalSecurityAgency/qgis-searchlayers-plugin/blob/master/searchDialog.py#L247-L259
        # Could be own method get_search_layers() etc.
        if selectedLayer == 0:
            # Include all vector layers
            layers = QgsProject.instance().mapLayers().values()
        elif selectedLayer == 1:
            # Include all selected vector layers
            layers = self.iface.layerTreeView().selectedLayers()
        elif selectedLayer == 2:
            # This is for visiable layers and content
            layer_trees = QgsProject.instance().layerTreeRoot().findLayers()
            layers = []
            for lt in layer_trees:
                if lt.isVisible():
                    layers.append(lt.layer())
        else:
            layers = [self.searchLayers[selectedLayer]]

        for layer in layers:
            for field in layer.fields():
                unique_field_names.add(field.name())
        
        # Add unique field names to combobox
        self.searchFieldComboBox.setEnabled(True)
        for field_name in unique_field_names:
            self.searchFieldComboBox.addItem(field_name)

Then it would look like something like this (layer1 and layer2 have a common field "common_field" that needs to be searched):
kuva

We are also willing to make a pull request if this is something that would add value to other users and if it helps.

Please explain plugin usefulness

It is apparently very similar to QuickFind. Please consider mergin your plugin to that one, so to avoid plugin duplication and possible confusion for users.

Adding "visible layer"

It would be nice to have the choice of searching only what is been viewed in the map, with an option called (Arcgis has this option...).
This way the engine will search only the visible data thus reducing the time and the final results.

Wrong answer after search

QGIS 3.6
Search Layer 3.0.1

Hi,

I'm trying to use your plugin, because it's really what I need. But I have a problem with it.

When I'm searching something on it (on France map), that find it, and work very goodly. But when I click on the results for go to see it, that send me in the middle of Nigeria (??). Each time...
I don't know why. And as I didn't found any way to configure the plugin, I dont know what to do.

Do you have a solution? Or perhaps it's just a little bug?

Thank you for your future answer.

Best regards

Are there any plans to make it multilingual?

Nice to meet you.
I am a user from Japan.
I have been using your site on a daily basis in my business.
Many of the users around me have a great deal of resistance to the English notation, and I would like to try to convert it to Japanese.
It is OK to change the ui file and each time, but in the long run, I would be glad if you could make a language file.

ObjectName not set

We are implementing a dynamic toolbar plugin and we decided to reference standard qgis tools as well as tools from other plugins by their ObjectName e.g. "mAction...".

Obviously, your tool currently does not set an ObjectName, will you provide it in the future?

Datasecurity NSA

Hello,

in Europe (Belgium) the NSA has a doubtfull image concerning datasecurity. We are in doubt in using this plugin because of possible hacking of our governal data. It is code, after all.

We cannot be sure your plugin is safe for not exploring our data and transferring it to the NSA.

How can we be sure your code and the updates of your codes are not malicious?

Greetings,

Filip Mahieu
Province East-Flanders
Belgium

Realization of custom settings for each project

I apologize for the sudden PULL request the other day.
In addition, I was not sure how to use the system and thought it was two sources to PULL, but they were combined.
Now, here are my suggestions.
Thank you in advance for your consideration.
I have set up a separate repository to check the operation.
Please let me know if there is anything that needs to be fixed.
In addition, I plan to add new features, etc. sequentially as time permits.
https://github.com/yamamoto-ryuzo/yr-qgis-searchlayers-plugin/
I am new to this kind of work, so please let me know how to go about it.
------------ Functional Overview ------------
By specifying a layer name in the variable 'searchlayers-plugin' in the project file, only the specified layer can be searched.

・normal

image

・set 'searchlayers-plugin' "test1,test3,test10'

image

・result:

image

Uncaught NameError: global name 'showErrorMessage' is not defined

Steps to reproduce

  1. First - Enter the search text (Neue Hütte)
  2. Second - contains
  3. Third - klick 'Search' Button
Traceback (most recent call last):
  File "C:/Users/USER/.qgis2/python/plugins\searchlayers\searchDialog.py", line 189, in workerError
    showErrorMessage(exception_string)
NameError: global name 'showErrorMessage' is not defined

searchlayers 0.2, QGIS 2.18.14 on Windows 10

Addition of adding open-form from the results context menu

Hi, thanks for a great plugin!

I've made a modification to searchDialog.py so you can now open the feature's edit form, if you would find this useful I can do a pull-request for this.

import os
import re
import time
import datetime

from qgis.PyQt.uic import loadUiType
from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QTableWidget, QTableWidgetItem,  QMenu, QAction
from qgis.PyQt.QtCore import Qt, QThread, QEvent, QCoreApplication

from qgis.core import QgsVectorLayer, Qgis, QgsProject, QgsWkbTypes, QgsMapLayer, QgsFields, QgsExpressionContextUtils
from .searchWorker import Worker
from .fuzzyWorker import FuzzyWorker

def tr(string):
    return QCoreApplication.translate('@default', string)


FORM_CLASS, _ = loadUiType(os.path.join(
    os.path.dirname(__file__), 'searchlayers.ui'))


class OpenRecordAction(QAction):
    def __init__(self, iface=None, parent=None, results=[]):
        super().__init__("Open Record Form", parent)
        self.iface = iface
        self.results = results
        self.triggered.connect(self.open_record)

    def open_record(self):

        selectedItems = self.parentWidget().selectedItems()
        selectedRow = selectedItems[0].row()
        foundid = self.parentWidget().item(selectedRow, 0).data(Qt.UserRole)
        selectedLayer = self.results[foundid][0]
        selectedFeature = self.results[foundid][1]
        self.iface.openFeatureForm(selectedLayer, selectedFeature)

class LayerSearchDialog(QDialog, FORM_CLASS):
    button_pressed = 1
    def __init__(self, iface, parent):
        '''Initialize the LayerSearch dialog box'''
        super(LayerSearchDialog, self).__init__(parent)
        self.setupUi(self)
        self.iface = iface
        self.canvas = iface.mapCanvas()
        # Notify us when vector items ared added and removed in QGIS
        QgsProject.instance().layersAdded.connect(self.updateLayers)
        QgsProject.instance().layersRemoved.connect(self.updateLayers)

        self.doneButton.clicked.connect(self.closeDialog)
        self.stopButton.clicked.connect(self.killWorker)
        self.searchButton.clicked.connect(self.runSearch)
        self.clearButton.clicked.connect(self.clearResults)
        self.results2LayersButton.clicked.connect(self.exportResults)
        self.layerListComboBox.activated.connect(self.layerSelected)
        self.searchFieldComboBox.addItems([tr('<All Fields>')])
        self.maxResults = 2000
        self.resultsTable.setEditTriggers(QTableWidget.NoEditTriggers)
        self.resultsTable.setColumnCount(4)
        self.resultsTable.setSortingEnabled(True)
        self.resultsTable.setHorizontalHeaderLabels([tr('Layer'),tr('Feature ID'),tr('Field'),tr('Search Results')])
        self.resultsTable.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.resultsTable.itemSelectionChanged.connect(self.select_feature)


        self.resultsTable.setContextMenuPolicy(Qt.CustomContextMenu)
        self.resultsTable.customContextMenuRequested.connect(self.show_context_menu)


        # self.resultsTable.viewport().installEventFilter(self)
        self.results = []
        self.ignore_clear = False
        self.worker = None
        self.time_start = 0
        self.last_search_str = ''
        self.last_search_str2 = ''
        self.layers_need_updating = True

    def show_context_menu(self, pos):

        context_menu = QMenu(self.resultsTable)

        context_menu.addAction(
            OpenRecordAction(
                iface=self.iface,
                parent=self.resultsTable,
                results=self.results
            )
        )

        context_menu.exec_(self.resultsTable.mapToGlobal(pos))

    def closeDialog(self):
        '''Close the dialog box when the Close button is pushed'''
        self.hide()

    def eventFilter(self, source, e):
        if e.type() == QEvent.MouseButtonPress:
            self.button_pressed = e.button()
        return super().eventFilter(source, e)

    def updateLayers(self):
        '''Called when a layer has been added or deleted in QGIS.
        It forces the dialog to reload.'''
        # Stop any existing search
        self.killWorker()
        if self.isVisible() or len(self.results) != 0:
            self.populateLayerListComboBox()
            self.clearResults()
            self.layers_need_updating = False
        else:
            self.layers_need_updating = True

    def select_feature(self):
        '''A feature has been selected from the list so we need to select
        and zoom to it'''
        if self.noSelection:
            # We do not want this event while data is being changed
            return
        zoom_pan = self.zoomPanComboBox.currentIndex()
        if self.searchSelectedCheckBox.isChecked():
            if zoom_pan == 0:
                return
            # We are searching on selected features so we don't want to select them from the list.
            selectedItems = self.resultsTable.selectedItems()
            if len(selectedItems) == 0:
                return
            selectedRow = selectedItems[0].row()
            foundid = self.resultsTable.item(selectedRow, 0).data(Qt.UserRole)
            selectedLayer = self.results[foundid][0]
            selectedFeature = self.results[foundid][1]
            fid = selectedFeature.id()
            if zoom_pan == 1:
                self.canvas.zoomToFeatureIds(selectedLayer, [fid])
            else:
                self.canvas.panToFeatureIds(selectedLayer, [fid])
            return
        # Deselect all selections
        layers = QgsProject.instance().mapLayers().values()
        for layer in layers:
            if layer.type() == QgsMapLayer.VectorLayer:
                layer.removeSelection()
        # Find the layers that are selected and select the features in the layer
        selectedItems = self.resultsTable.selectedItems()
        selectedLayer = None
        for item in selectedItems:
            selectedRow = item.row()
            foundid = self.resultsTable.item(selectedRow, 0).data(Qt.UserRole)
            selectedLayer = self.results[foundid][0]
            selectedFeature = self.results[foundid][1]
            selectedLayer.select(selectedFeature.id())
        # Zoom to the selected feature
        if selectedLayer and zoom_pan:
            if zoom_pan == 1:
                self.canvas.zoomToSelected(selectedLayer)
            else:
                self.canvas.panToSelected(selectedLayer)

    def layerSelected(self):
        '''The user has made a selection so we need to initialize other
        parts of the dialog box'''
        self.initFieldList()

    def showEvent(self, event):
        '''The dialog is being shown. We need to initialize it.'''
        super(LayerSearchDialog, self).showEvent(event)
        if self.layers_need_updating:
            self.populateLayerListComboBox()

    def populateLayerListComboBox(self):
        '''Find all the vector layers and add them to the layer list
        that the user can select. In addition the user can search on all
        layers or all selected layers.'''
        layerlist = [tr('<All Layers>'),tr('<Selected Layers>'),tr('<Visible Layers>')]
        self.searchLayers = [None, None, None] # This is same size as layerlist
        layers = QgsProject.instance().mapLayers().values()
        
        '''If the project variable "searchlayers-plugin" is present, only the specified layer is covered.
        Multiple layers are separated by ",".'''
        ProjectInstance = QgsProject.instance()
        if QgsExpressionContextUtils.projectScope(ProjectInstance).variable('searchlayers-plugin'):
            ProjectVariable = QgsExpressionContextUtils.projectScope(ProjectInstance).variable('searchlayers-plugin').split(',')
            for i,j in enumerate(ProjectVariable):
                for layer in layers:
                    if layer.type() == QgsMapLayer.VectorLayer and not layer.sourceName().startswith('__'):
                        if layer.name() == j:
                            layerlist.append(layer.name())
                            self.searchLayers.append(layer)
        else:
            for layer in layers:
                if layer.type() == QgsMapLayer.VectorLayer and not layer.sourceName().startswith('__'):
                    layerlist.append(layer.name())
                    self.searchLayers.append(layer)

        self.layerListComboBox.clear()
        self.layerListComboBox.addItems(layerlist)
        self.initFieldList()
        self.layers_need_updating = False

    def initFieldList(self):
        selectedLayer = self.layerListComboBox.currentIndex()
        self.searchFieldComboBox.clear()
        self.searchFieldComboBox.addItem(tr('<All Fields>'))
        if selectedLayer > 2:
            self.searchFieldComboBox.setEnabled(True)
            for field in self.searchLayers[selectedLayer].fields():
                self.searchFieldComboBox.addItem(field.name())
        else:
            self.searchFieldComboBox.setCurrentIndex(0)
            self.searchFieldComboBox.setEnabled(False)

    def initSearchResultsTable(self):
        self.clearResults()
        if self.is_single_string or self.two_string_match_single:
            self.resultsTable.setColumnCount(4)
            self.resultsTable.setHorizontalHeaderLabels([tr('Layer'),tr('Feature ID'),tr('Field'),tr('Results')])
        else:
            self.resultsTable.setColumnCount(6)
            self.resultsTable.setHorizontalHeaderLabels([tr('Layer'),tr('Feature ID'),tr('Field 1'),tr('Results 1'),tr('Field 2'),tr('Results 2')])

    def setButtons(self, searching):
        if searching:
            self.searchButton.setEnabled(False)
            self.stopButton.setEnabled(True)
            self.doneButton.setEnabled(False)
            self.clearButton.setEnabled(False)
            self.results2LayersButton.setEnabled(False)
        else:
            self.searchButton.setEnabled(True)
            self.clearButton.setEnabled(True)
            self.stopButton.setEnabled(False)
            self.doneButton.setEnabled(True)
            if len(self.results):
                self.results2LayersButton.setEnabled(True)
            else:
                self.results2LayersButton.setEnabled(False)

    def runSearch(self):
        '''Called when the user pushes the Search button'''
        # Set up general parametrs for all search methods
        selected_tab = self.tabWidget.currentIndex()
        self.noSelection = True
        selectedLayer = self.layerListComboBox.currentIndex()
        self.first_match_only = self.firstMatchCheckBox.isChecked()
        self.search_selected = self.searchSelectedCheckBox.isChecked()
        constrain_to_canvas = self.cannvasConstraintCheckBox.isChecked()

        if selectedLayer == 0:
            # Include all vector layers
            layers = QgsProject.instance().mapLayers().values()
        elif selectedLayer == 1:
            # Include all selected vector layers
            layers = self.iface.layerTreeView().selectedLayers()
        elif selectedLayer == 2:
            # This is for visiable layers and content
            layer_trees = QgsProject.instance().layerTreeRoot().findLayers()
            layers = []
            for lt in layer_trees:
                if lt.isVisible():
                    layers.append(lt.layer())
        else:
            # Only search on the selected vector layer
            layers = [self.searchLayers[selectedLayer]]
        self.vlayers=[]
        # Find the vector layers that are to be searched
        for layer in layers:
            if isinstance(layer, QgsVectorLayer) and not layer.sourceName().startswith('__'):
                self.vlayers.append(layer)
        if len(self.vlayers) == 0:
            self.showErrorMessage(tr('There are no vector layers to search'))
            return

        # vlayers contains the layers that we will search in
        self.setButtons(True)
        self.resultsLabel.setText('')
        infield = self.searchFieldComboBox.currentIndex() >= 1
        if infield is True:
            selectedField = self.searchFieldComboBox.currentText()
        else:
            selectedField = None

        # Because this could take a lot of time, set up a separate thread
        # for a worker function to do the searching.
        self.time_start = time.perf_counter()
        thread = QThread()
        if selected_tab == 0:
            # Get parameters for regular search
            comparisonMode = self.comparisonComboBox.currentIndex()
            comparisonMode2 = self.comparison2ComboBox.currentIndex()
            and_or = self.andOrComboBox.currentIndex()
            case_sensitive = self.caseSensitiveCheckBox.isChecked()
            case_sensitive2 = self.caseSensitive2CheckBox.isChecked()
            self.two_string_match_single = self.twoStringMatchCheckBox.isChecked()
            not_search = self.notCheckBox.isChecked()
            not_search2 = self.not2CheckBox.isChecked()

            try:
                sstr = self.findStringEdit.text()
                self.last_search_str = sstr
                sstr2 = self.findString2Edit.text()
                self.last_search_str2 = sstr2
            except:
                self.showErrorMessage(tr('Invalid Search String'))
                self.setButtons(False)
                return

            if sstr == '':
                self.showErrorMessage(tr('Search string is empty'))
                self.setButtons(False)
                return
            if sstr2 == '':
                self.is_single_string = True
            else:
                self.is_single_string = False
            self.initSearchResultsTable()
            worker = Worker(self.canvas, self.vlayers, infield, sstr, comparisonMode, case_sensitive, not_search, 
                and_or, sstr2, comparisonMode2, case_sensitive2, not_search2,
                selectedField, self.maxResults, self.first_match_only, self.two_string_match_single,
                self.search_selected, constrain_to_canvas)
        else:
            # Get Fuzzy parameters
            if self.levenshteinButton.isChecked():
                algorithm = 0
            else:
                algorithm = 1
            sstr = self.fuzzyTextEdit.toPlainText()
            case_sensitive = self.fuzzyCaseSensitiveCheckBox.isChecked()
            fuzzy_contains = self.fuzzyContainsCheckBox.isChecked()
            match_metric = self.levenshteinMatchSpinBox.value() / 100.0
            self.is_single_string = True
            self.initSearchResultsTable()
            worker = FuzzyWorker(self.canvas, self.vlayers, infield, sstr, algorithm, case_sensitive,
                fuzzy_contains, selectedField, self.maxResults, self.first_match_only,
                self.search_selected, match_metric, constrain_to_canvas)
        worker.moveToThread(thread)
        thread.started.connect(worker.run)
        worker.finished.connect(self.workerFinished)
        worker.foundmatch.connect(self.addFoundItem)
        worker.error.connect(self.workerError)
        self.thread = thread
        self.worker = worker
        self.noSelection = False
        thread.start()

    def workerFinished(self, status):
        '''Clean up the worker and thread'''
        self.worker.deleteLater()
        self.thread.quit()
        self.thread.wait()
        self.thread.deleteLater()
        self.worker = None
        total_time = time.perf_counter() - self.time_start
        self.resultsLabel.setText(tr('Results')+': {} in {:.1f}s'.format(self.found, total_time))

        self.vlayers = []
        self.setButtons(False)

    def workerError(self, exception_string):
        '''An error occurred so display it.'''
        # self.showErrorMessage(exception_string)
        print(exception_string)

    def killWorker(self):
        '''This is initiated when the user presses the Stop button
        and will stop the search process'''
        if self.worker is not None:
            self.worker.kill()

    def clearResults(self):
        '''Clear all the search results.'''
        if self.ignore_clear:
            return
        self.noSelection = True
        self.found = 0
        self.results = []
        self.layer_set = set()
        self.resultsTable.setRowCount(0)        
        self.noSelection = False
        self.results2LayersButton.setEnabled(False)

    def addFoundItem(self, layer, feature, attrname1, results1, attrname2, results2):
        '''We found an item so add it to the found list.'''
        # Don't allow sorting while adding new results
        self.resultsTable.setSortingEnabled(False)
        self.resultsTable.insertRow(self.found)
        self.results.append([layer, feature])
        self.layer_set.add(layer)
        # Save the search found position in the first element of the table. This way
        # we can allow the user to sort the table, but be able to know which entry it is.
        item = QTableWidgetItem(layer.name())
        item.setData(Qt.UserRole, self.found)
        self.resultsTable.setItem(self.found, 0, item)
        self.resultsTable.setItem(self.found, 1, QTableWidgetItem(str(feature.id())))
        if self.is_single_string or self.two_string_match_single:
            self.resultsTable.setItem(self.found, 2, QTableWidgetItem(attrname1))
            self.resultsTable.setItem(self.found, 3, QTableWidgetItem(results1))
        else:
            self.resultsTable.setItem(self.found, 2, QTableWidgetItem(attrname1))
            self.resultsTable.setItem(self.found, 3, QTableWidgetItem(results1))
            self.resultsTable.setItem(self.found, 4, QTableWidgetItem(attrname2))
            self.resultsTable.setItem(self.found, 5, QTableWidgetItem(results2))
        self.found += 1   
        # Restore sorting
        self.resultsTable.setSortingEnabled(True)

    def exportResults(self):
        # No found results, no export
        if len(self.results) == 0:
            return
        self.resultsTable.setDisabled(True)
        self.ignore_clear = True
        layer_map = self.createExportedLayers()
        for layer, feature in self.results:
            # print('{} {}'.format(layer, feature))
            layer_map[layer].dataProvider().addFeatures([feature])

        dt = datetime.datetime.now()
        dt_name = dt.strftime('%Y-%m-%d %H:%M:%S')
        if len(self.last_search_str) > 20:
            sname = self.last_search_str[0:17]+'...'
        else:
            sname = self.last_search_str
        if len(self.last_search_str2) > 20:
            sname2 = self.last_search_str2[0:17]+'...'
        else:
            sname2 = self.last_search_str2
        if sname2.strip() == '':
            fname = '{}_{}'.format(dt_name, sname)
        else:
            fname = '{}_{}_{}'.format(dt_name, sname, sname2)
        self.ignore_clear = True
        layer_tree = QgsProject.instance().layerTreeRoot()
        group = layer_tree.insertGroup(0, fname)
        for layer in layer_map:
            new_layer = layer_map[layer]
            new_layer.updateExtents()
            QgsProject.instance().addMapLayer(new_layer, False)
            group.addLayer(new_layer)
        self.ignore_clear = False
        self.resultsTable.setDisabled(False)

    def createExportedLayers(self):
        layer_mapping = {}
        for layer in self.layer_set:
            new_name = '__'+layer.name()
            wkb_type = layer.wkbType()
            layer_crs = layer.sourceCrs()
            fields = QgsFields(layer.fields())
            new_layer = QgsVectorLayer("{}?crs={}".format(QgsWkbTypes.displayString(wkb_type), layer_crs.authid()), new_name, "memory")
            dp = new_layer.dataProvider()
            dp.addAttributes(fields)
            new_layer.updateFields()
            layer_mapping[layer] = new_layer
        return(layer_mapping)

    def showErrorMessage(self, message):
        '''Display an error message.'''
        self.iface.messageBar().pushMessage("", message, level=Qgis.Warning, duration=2)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.