Exemplo n.º 1
0
    def onResultTableSelChanged(self):
        cur_row_index = self.dockWidget.tableResult.currentRow()
        if cur_row_index > -1:
            self.clearHighlight(self.intersected_h_list)
            self.clearHighlight(self.intersection_h_list)

            f_geometry = QgsGeometry()
            f_geometry = QgsGeometry.fromWkt(
                self.dockWidget.tableResult.item(cur_row_index, 1).text())
            h = QgsHighlight(self.iface.mapCanvas(), f_geometry,
                             self.inters_layer)
            h.setColor(QColor(26, 200, 0, 220))
            h.setWidth(6)
            h.setFillColor(QColor(26, 200, 0, 150))
            self.intersected_h_list.append(h)

            if_geometry = QgsGeometry()
            if_geometry = QgsGeometry.fromWkt(
                self.dockWidget.tableResult.item(cur_row_index, 2).text())
            ih = QgsHighlight(self.iface.mapCanvas(), if_geometry,
                              self.inters_layer)
            ih.setColor(QColor(230, 0, 0, 220))
            ih.setWidth(6)
            ih.setFillColor(QColor(230, 0, 0, 150))
            self.intersection_h_list.append(ih)
Exemplo n.º 2
0
class DigitizingToolsChooseRemaining(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, iface, editLayer, pkValues, featDict, title):
        QtWidgets.QDialog.__init__(self)
        self.setupUi(self)
        self.iface = iface
        self.editLayer = editLayer
        self.pkValues = pkValues
        self.featDict = featDict
        self.chooseId.addItems(list(self.pkValues.keys()))
        self.setWindowTitle(title)
        self.label.setText(QtWidgets.QApplication.translate(
            "digitizingtools", "Choose which already existing feature should remain"))
        self.buttonBox.rejected.connect(self.reject)
        self.buttonBox.accepted.connect(self.accept)

    @QtCore.pyqtSlot(int)
    def on_chooseId_currentIndexChanged(self, thisIndex):
        aPkValue = self.chooseId.currentText()
        aGeom = self.featDict[self.pkValues[aPkValue]].geometry()
        hlColor, hlFillColor, hlBuffer,  hlMinWidth = dtGetHighlightSettings()
        self.hl = QgsHighlight(self.iface.mapCanvas(), aGeom, self.editLayer)
        self.hl.setColor(hlColor)
        self.hl.setFillColor(hlFillColor)
        self.hl.setBuffer(hlBuffer)
        self.hl.setWidth(hlMinWidth)

    @QtCore.pyqtSlot()
    def reject(self):
        self.done(0)

    @QtCore.pyqtSlot()
    def accept(self):
        self.pkValueToKeep = self.chooseId.currentText()
        self.done(1)
Exemplo n.º 3
0
 def highlight_features(self):
     for item in self.highlight:
         self.canvas.scene().removeItem(item)
     del self.highlight[:]
     del self.highlight_rows[:]
     index = self.tabWidget.currentIndex()
     tab = self.tabWidget.widget(index)
     if self.tabWidget.count() != 0:
         table = self.tabWidget.widget(index).findChildren(QTableWidget)[0]
         nb = 0
         area = 0
         length = 0
         items = table.selectedItems()
         for item in items:
             if item.row() not in self.highlight_rows:
                 if self.selectGeom:
                     highlight = QgsHighlight(self.canvas, item.feature.geometry(), self.tabWidget.widget(index).layer)
                 else:
                     highlight = QgsHighlight(self.canvas, item.feature.geometry().centroid(), self.tabWidget.widget(index).layer)
                 highlight.setColor(QColor(255,0,0))
                 self.highlight.append(highlight)
                 self.highlight_rows.append(item.row())
                 g = QgsGeometry(item.feature.geometry())
                 g.transform(QgsCoordinateTransform(tab.layer.crs(), QgsCoordinateReferenceSystem(2154), QgsProject.instance())) # geometry reprojection to get meters
                 nb += 1
                 area += g.area()
                 length += g.length()
         if tab.layer.geometryType()==QgsWkbTypes.PolygonGeometry:
             tab.sb.showMessage(self.tr('Selected features')+': '+str(nb)+'  '+self.tr('Area')+': '+"%.2f"%area+' m'+u'²')
         elif tab.layer.geometryType()==QgsWkbTypes.LineGeometry:
             tab.sb.showMessage(self.tr('Selected features')+': '+str(nb)+'  '+self.tr('Length')+': '+"%.2f"%length+' m')
         else:
             tab.sb.showMessage(self.tr('Selected features')+': '+str(nb))
Exemplo n.º 4
0
    def runTestForLayer(self, layer, testname):
        tempdir = tempfile.mkdtemp()

        layer = QgsVectorLayer(layer, 'Layer', 'ogr')
        QgsProject.instance().addMapLayer(layer)
        self.iface.mapCanvas().setExtent(layer.extent())

        geom = next(layer.getFeatures()).geometry()

        highlight = QgsHighlight(self.iface.mapCanvas(), geom, layer)
        color = QColor(Qt.red)
        highlight.setColor(color)
        highlight.setWidth(2)
        color.setAlpha(50)
        highlight.setFillColor(color)
        highlight.show()

        image = QImage(QSize(400, 400), QImage.Format_ARGB32)
        image.fill(Qt.white)
        painter = QPainter()
        painter.begin(image)
        self.iface.mapCanvas().render(painter)
        painter.end()
        control_image = os.path.join(tempdir, 'highlight_{}.png'.format(testname))
        image.save(control_image)
        checker = QgsRenderChecker()
        checker.setControlPathPrefix("highlight")
        checker.setControlName("expected_highlight_{}".format(testname))
        checker.setRenderedImage(control_image)
        self.assertTrue(checker.compareImages("highlight_{}".format(testname)))
        shutil.rmtree(tempdir)
Exemplo n.º 5
0
    def runTestForLayer(self, layer, testname):
        tempdir = tempfile.mkdtemp()

        layer = QgsVectorLayer(layer, 'Layer', 'ogr')
        QgsProject.instance().addMapLayer(layer)
        self.iface.mapCanvas().setExtent(layer.extent())

        geom = next(layer.getFeatures()).geometry()

        highlight = QgsHighlight(self.iface.mapCanvas(), geom, layer)
        color = QColor(Qt.red)
        highlight.setColor(color)
        highlight.setWidth(2)
        color.setAlpha(50)
        highlight.setFillColor(color)
        highlight.show()

        image = QImage(QSize(400, 400), QImage.Format_ARGB32)
        image.fill(Qt.white)
        painter = QPainter()
        painter.begin(image)
        self.iface.mapCanvas().render(painter)
        painter.end()
        control_image = os.path.join(tempdir, 'highlight_{}.png'.format(testname))
        image.save(control_image)
        checker = QgsRenderChecker()
        checker.setControlPathPrefix("highlight")
        checker.setControlName("expected_highlight_{}".format(testname))
        checker.setRenderedImage(control_image)
        self.assertTrue(checker.compareImages("highlight_{}".format(testname)))
        shutil.rmtree(tempdir)
Exemplo n.º 6
0
 def highlightFeature(self, theFeature):
     highlight = QgsHighlight(self.mTheCanvas, theFeature.geometry(), self.mTheLayer)
     highlight.setColor(QColor(255,0,0,128))
     highlight.setFillColor(QColor(255,0,0,128))
     highlight.setBuffer(0.5)
     highlight.setMinWidth(6)
     highlight.setWidth(6)
     highlight.show()
     self.highlightList.append(highlight)
     return
Exemplo n.º 7
0
 def highlightByGeometry(self, geometry, color=QColor(255, 0, 0, 128)):
     highlight = QgsHighlight(self.mTheCanvas, geometry, self.mTheLayer)
     highlight.setColor(color)
     highlight.setFillColor(color)
     highlight.setBuffer(0.5)
     highlight.setMinWidth(6)
     highlight.setWidth(6)
     highlight.show()
     self.highlightList.append(highlight)
     return
Exemplo n.º 8
0
 def markFeature(self, lay, feat):
     try:
         color = QColor(Qt.red)
         highlight = QgsHighlight(self.iface.mapCanvas(), feat, lay)
         highlight.setColor(color)
         color.setAlpha(50)
         highlight.setFillColor(color)
         highlight.show()
         return highlight
     except Exception as e:
         self.info.err(e)
 def _addHighlight(self, canvas, geometry, layer):
     hl = QgsHighlight(canvas, geometry, layer)
     color = QColor(QSettings().value('/Map/highlight/color', QGis.DEFAULT_HIGHLIGHT_COLOR.name(), str))
     alpha = QSettings().value('/Map/highlight/colorAlpha', QGis.DEFAULT_HIGHLIGHT_COLOR.alpha(), int)
     buff = QSettings().value('/Map/highlight/buffer', QGis.DEFAULT_HIGHLIGHT_BUFFER_MM, float)
     minWidth = QSettings().value('/Map/highlight/minWidth', QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM, float)
     hl.setColor(color)
     color.setAlpha(alpha)
     hl.setFillColor(color)
     hl.setBuffer(buff)
     hl.setMinWidth(minWidth)
     self._highlights.append(hl)
Exemplo n.º 10
0
    def test_feature_transformation(self):
        poly_shp = os.path.join(TEST_DATA_DIR, 'polys.shp')
        layer = QgsVectorLayer(poly_shp, 'Layer', 'ogr')

        sub_symbol = QgsFillSymbol.createSimple({
            'color': '#8888ff',
            'outline_style': 'no'
        })

        sym = QgsFillSymbol()
        buffer_layer = QgsGeometryGeneratorSymbolLayer.create(
            {'geometryModifier': 'buffer($geometry, -0.4)'})
        buffer_layer.setSymbolType(QgsSymbol.Fill)
        buffer_layer.setSubSymbol(sub_symbol)
        sym.changeSymbolLayer(0, buffer_layer)
        layer.setRenderer(QgsSingleSymbolRenderer(sym))

        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(-11960254, 4247568, -11072454, 4983088))
        canvas.show()

        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()

        feature = layer.getFeature(1)
        self.assertTrue(feature.isValid())

        highlight = QgsHighlight(canvas, feature, layer)
        color = QColor(Qt.red)
        highlight.setColor(color)
        color.setAlpha(50)
        highlight.setFillColor(color)
        highlight.show()
        highlight.show()

        self.assertTrue(
            self.canvasImageCheck('highlight_transform', 'highlight_transform',
                                  canvas))
Exemplo n.º 11
0
class DigitizingToolsChooseRemaining(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, iface, editLayer, pkValues, featDict, title):
        QtWidgets.QDialog.__init__(self)
        self.setupUi(self)
        self.iface = iface
        self.editLayer = editLayer
        self.pkValues = pkValues
        self.featDict = featDict
        self.chooseId.addItems(list(self.pkValues.keys()))
        self.setWindowTitle(title)
        self.label.setText(
            QtWidgets.QApplication.translate(
                "digitizingtools",
                "Choose which already existing feature should remain"))
        self.buttonBox.rejected.connect(self.reject)
        self.buttonBox.accepted.connect(self.accept)

    def clearHighlight(self):
        try:
            self.hl.hide()
        except:
            pass

    @QtCore.pyqtSlot(int)
    def on_chooseId_currentIndexChanged(self, thisIndex):
        self.clearHighlight()
        aPkValue = self.chooseId.currentText()
        aGeom = self.featDict[self.pkValues[aPkValue]].geometry()
        hlColor, hlFillColor, hlBuffer, hlMinWidth = dtGetHighlightSettings()
        self.hl = QgsHighlight(self.iface.mapCanvas(), aGeom, self.editLayer)
        self.hl.setColor(hlColor)
        self.hl.setFillColor(hlFillColor)
        self.hl.setBuffer(hlBuffer)
        self.hl.setWidth(hlMinWidth)
        self.hl.show()

    @QtCore.pyqtSlot()
    def reject(self):
        self.clearHighlight()
        self.done(0)

    @QtCore.pyqtSlot()
    def accept(self):
        self.clearHighlight()
        self.pkValueToKeep = self.chooseId.currentText()
        self.done(1)
    def highlight_courant(self):
        """highlight animated flowline layer where Courant number is higher than a given value (use velocity variable as flowline-results)"""

        if len(QgsProject.instance().mapLayersByName("line_results")) == 0:
            self.iface.messageBar().pushMessage(
                "Warning",
                'Couldn\'t find line_results layer, click "Animation on" button',
                level=Qgis.Warning,
            )
            return

        # layer found
        canvas = self.iface.mapCanvas()

        line_results = QgsProject.instance().mapLayersByName("line_results")[0]
        global_settings_layer = QgsProject.instance().mapLayersByName(
            "v2_global_settings"
        )[0]
        timestep = list(global_settings_layer.getFeatures())[0][
            "sim_time_step"
        ]  # [0] -> [self.selected_scenario_index]
        d = QgsDistanceArea()
        d.setEllipsoid("WGS84")

        features = line_results.getFeatures()
        for feature in features:
            kcu = feature["kcu"]
            if kcu in [0, 1, 2, 3, 5, 100, 101]:
                geometry = feature.geometry()
                length = d.measureLength(geometry)

                velocity = abs(feature["result"])

                courant = velocity * timestep / length

                if courant > self.courantThreshold.value():
                    color = QtGui.QColor(Qt.red)
                    highlight = QgsHighlight(canvas, feature, line_results)
                    highlight.setColor(color)
                    highlight.setMinWidth(courant / 2)
                    # highlight.setBuffer()
                    color.setAlpha(50)
                    highlight.setFillColor(color)
                    highlight.show()
                    self.highlights.append(highlight)
Exemplo n.º 13
0
class LrsErrorVisualizer(object):
    def __init__(self, mapCanvas):
        self.errorHighlight = None
        self.mapCanvas = mapCanvas

    def __del__(self):
        if self.errorHighlight:
            del self.errorHighlight

    def clearHighlight(self):
        if self.errorHighlight:
            del self.errorHighlight
            self.errorHighlight = None

    def highlight(self, error, crs):
        self.clearHighlight()
        if not error: return

        # QgsHighlight does reprojection from layer CRS
        layer = QgsVectorLayer('Point?crs=' + crsString(crs), 'LRS error highlight', 'memory')
        self.errorHighlight = QgsHighlight(self.mapCanvas, error.geo, layer)
        # highlight point size is hardcoded in QgsHighlight
        self.errorHighlight.setWidth(2)
        self.errorHighlight.setColor(Qt.yellow)
        self.errorHighlight.show()

    def zoom(self, error, crs):
        if not error: return
        geo = error.geo
        mapSettings = self.mapCanvas.mapSettings()
        if isProjectCrsEnabled() and getProjectCrs() != crs:
            geo = QgsGeometry(error.geo)
            transform = QgsCoordinateTransform(crs, QgsProject().instance().crs())
            geo.transform(transform)

        if geo.type() == QgsWkbTypes.PointGeometry:
            p = geo.asPoint()
            bufferCrs = getProjectCrs() if isProjectCrsEnabled() else crs
            b = 2000 if not bufferCrs.isGeographic() else 2000 / 100000  # buffer
            extent = QgsRectangle(p.x() - b, p.y() - b, p.x() + b, p.y() + b)
        else:  # line
            extent = geo.boundingBox()
            extent.scale(2)
        self.mapCanvas.setExtent(extent)
        self.mapCanvas.refresh();
Exemplo n.º 14
0
    def onMapClickedTableSelChanged(self):
        cur_row_index = self.map_clicked_dlg.tableClickedWays.currentRow()
        if cur_row_index > -1:
            self.clearAllHighlights()

            f_geometry = QgsGeometry()
            f_geometry = QgsGeometry.fromWkt(
                self.map_clicked_dlg.tableClickedWays.item(cur_row_index,
                                                           1).text())

            h = QgsHighlight(self.iface.mapCanvas(), f_geometry,
                             self.current_layer)
            h.setColor(QColor(0, 15, 183, 220))
            h.setWidth(6)
            h.setFillColor(QColor(0, 15, 183, 150))
            self.mapclicked_h_list.append(h)

        self.setButtonOkStatus()
Exemplo n.º 15
0
 def _addHighlight(self, canvas, geometry, layer):
     hl = QgsHighlight(canvas, geometry, layer)
     color = QColor(QSettings().value('/Map/highlight/color',
                                      QGis.DEFAULT_HIGHLIGHT_COLOR.name(),
                                      str))
     alpha = QSettings().value('/Map/highlight/colorAlpha',
                               QGis.DEFAULT_HIGHLIGHT_COLOR.alpha(), int)
     buff = QSettings().value('/Map/highlight/buffer',
                              QGis.DEFAULT_HIGHLIGHT_BUFFER_MM, float)
     minWidth = QSettings().value('/Map/highlight/minWidth',
                                  QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM,
                                  float)
     hl.setColor(color)
     color.setAlpha(alpha)
     hl.setFillColor(color)
     hl.setBuffer(buff)
     hl.setMinWidth(minWidth)
     self._highlights.append(hl)
Exemplo n.º 16
0
    def pointPicked(self, event):
        for b in self.rubberBands:
            del b
        self.rubberBands[:] = []

        fieldX = self.cmbXField.currentField()
        fieldY = self.cmbYField.currentField()

        artist = event.artist
        indices = event.ind
        for i in indices:
            x = self.xData[artist.name][i]
            y = self.yData[artist.name][i]

            if isinstance(x, int):
                expr = '"{}" = {} AND '.format(fieldX, x)
            elif isinstance(x, float):
                expr = 'abs("{}" - {}) <= 0.0000001 AND '.format(fieldX, x)
            elif isinstance(x, (str, unicode)):
                expr = """"{}" = '{}' AND """.format(fieldX, x)
            else:
                expr = """"{}" = '{}' AND """.format(fieldX, x.toString('yyyy-MM-dd'))

            if isinstance(y, float):
                expr += 'abs("{}" - {}) <= 0.0000001'.format(fieldY, y)
            elif isinstance(y, (str, unicode)):
                expr += """"{}" = '{}'""".format(fieldY, y)
            else:
                expr += '"{}" = {}'.format(fieldY, y)

            layer = self.cmbLayer.currentLayer()
            expression = QgsExpression(expr)
            context = QgsExpressionContext()
            context.appendScope(QgsExpressionContextUtils.globalScope())
            context.appendScope(QgsExpressionContextUtils.projectScope())
            context.appendScope(QgsExpressionContextUtils.mapSettingsScope(self.canvas.mapSettings()))
            context.appendScope(QgsExpressionContextUtils.layerScope(layer))

            request = QgsFeatureRequest(expression, context)
            for f in layer.getFeatures(request):
                hl = QgsHighlight(self.canvas, f.geometry(), layer)
                hl.setColor(QColor(255, 0, 0))
                hl.setWidth(2)
                self.rubberBands.append(hl)
    def handle(self, message):
        if message.username != self.settings.get_user_name_prefix():
            return

        for highlight in self.highlightFeatures:
            self.iface.mapCanvas().scene().removeItem(highlight)

        self.highlightFeatures = []

        if len(message.identifiedFeatureMrids) == 0:
            return

        layer = None
        if message.featureType == self.settings.get_types_route_segment():
            layer = QgsProject.instance().mapLayersByName(
                self.settings.get_layers_route_segment_name())[0]
        elif message.featureType == self.settings.get_types_route_node():
            layer = QgsProject.instance().mapLayersByName(
                self.settings.get_layers_route_node_name())[0]

        filterExpression = ""
        for i in range(len(message.identifiedFeatureMrids)):
            mrid = message.identifiedFeatureMrids[i]
            if i == len(message.identifiedFeatureMrids) - 1:
                filterExpression += f'"mrid" = \'{mrid}\''
            else:
                filterExpression += f'"mrid" = \'{mrid}\' OR '

        features = layer.getFeatures(
            QgsFeatureRequest().setFilterExpression(filterExpression))

        color = QColor(64, 224, 208)
        for feature in features:
            identifyHighlight = QgsHighlight(self.iface.mapCanvas(),
                                             feature.geometry(), layer)
            identifyHighlight.setWidth(5)
            identifyHighlight.setColor(color)
            self.highlightFeatures.append(identifyHighlight)

        for highlight in self.highlightFeatures:
            highlight.show()

        layer.triggerRepaint()
Exemplo n.º 18
0
class TidalPredictionWidget(QtWidgets.QDockWidget, FORM_CLASS):
    TEMPORAL_HACK_SECS = 1
    AUTOLOAD_TIMER_MSECS = 300

    def __init__(self, parent, canvas):
        """Constructor."""
        super(TidalPredictionWidget, self).__init__(parent)
        self.canvas = canvas
        self.temporal = canvas.temporalController()
        self.setupUi(self)

        self.tableWidget.setColumnCount(3)
        self.tableWidget.setSortingEnabled(False)
        self.tableWidget.setHorizontalHeaderLabels(
            [tr('Time'), tr('Direction'),
             tr('Speed')])

        self.dateEdit.dateChanged.connect(self.updateDate)
        self.dateEdit.dateChanged.connect(self.loadStationPredictions)
        self.timeEdit.timeChanged.connect(self.updateTime)

        self.nextDay.clicked.connect(lambda: self.adjustDay(1))
        self.prevDay.clicked.connect(lambda: self.adjustDay(-1))
        self.nextStep.clicked.connect(lambda: self.adjustStep(1))
        self.prevStep.clicked.connect(lambda: self.adjustStep(-1))

        self.annotationButton.clicked.connect(self.annotatePredictions)

        self.predictionManager = None
        self.stationFeature = None
        self.stationZone = None
        self.stationHighlight = None

        self.active = False

        self.predictionCanvas = None

        self.includeCurrentsInTable = False

        self.autoLoadTimer = QTimer()
        self.autoLoadTimer.setSingleShot(True)

        self.progressBar.hide()

    def activate(self):
        if currentStationsLayer() is None or currentPredictionsLayer() is None:
            QMessageBox.critical(
                None, None,
                tr('You must add current station layers before this tool can be used.'
                   ))
            return

        self.show()

        if not self.active:
            self.active = True
            self.currentStationsLayer = currentStationsLayer()
            self.currentPredictionsLayer = currentPredictionsLayer()
            self.predictionManager = PredictionManager(
                self.currentStationsLayer, self.currentPredictionsLayer)
            self.predictionManager.progressChanged.connect(
                self.predictionProgress)
            self.setTemporalRange()
            self.loadMapExtentPredictions()

            self.autoLoadTimer.timeout.connect(self.loadMapExtentPredictions)
            self.canvas.extentsChanged.connect(self.triggerAutoLoad)
            QgsProject.instance().layerWillBeRemoved.connect(self.removalCheck)

    def deactivate(self):
        self.hide()

        if self.active:
            self.canvas.extentsChanged.disconnect(self.triggerAutoLoad)
            self.autoLoadTimer.timeout.disconnect(
                self.loadMapExtentPredictions)
            self.autoLoadTimer.stop()
            QgsProject.instance().layerWillBeRemoved.disconnect(
                self.removalCheck)

            self.tableWidget.clearContents()
            if self.predictionCanvas is not None:
                self.predictionCanvas.hide()
            if self.stationHighlight is not None:
                self.stationHighlight.hide()

            self.predictionManager.progressChanged.disconnect(
                self.predictionProgress)
            self.predictionManager = None

            self.stationFeature = None
            self.active = False

    def predictionProgress(self, progress):
        if progress == 100:
            self.progressBar.hide()
        else:
            self.progressBar.show()
            self.progressBar.setValue(progress)

    def maxAutoLoadCount(self):
        return 100
        # TODO: have a widget for this

    def removalCheck(self, layerId):
        if layerId == self.currentStationsLayer.id(
        ) or layerId == self.currentPredictionsLayer.id():
            self.deactivate()

    def triggerAutoLoad(self):
        self.autoLoadTimer.start(self.AUTOLOAD_TIMER_MSECS)

    def loadMapExtentPredictions(self):
        self.autoLoadTimer.stop()
        """ ensure all stations in visible extent of the map are loaded
        """
        if self.active and self.predictionManager is not None:
            xform = QgsCoordinateTransform(
                self.canvas.mapSettings().destinationCrs(), epsg4326,
                QgsProject.instance())
            rect = xform.transform(self.canvas.extent())
            mapFeatures = self.predictionManager.getExtentStations(rect)
            print('autoloading ', len(mapFeatures), ' in ', rect)
            if len(mapFeatures) <= self.maxAutoLoadCount():
                for f in mapFeatures:
                    self.predictionManager.getDataPromise(
                        f, self.dateEdit.date()).start()
            if self.stationZone is None and len(mapFeatures) > 0:
                self.stationZone = stationTimeZone(mapFeatures[0])

    def loadStationPredictions(self):
        """ load predictions for the selected station
        """
        if self.stationFeature is None:
            return

        self.stationData = self.predictionManager.getDataPromise(
            self.stationFeature, self.dateEdit.date())
        self.tableWidget.clearContents()
        self.stationData.resolved(self.predictionsResolved)
        self.stationData.start()

    def setTemporalRange(self):
        """ Set up the temporal range of either based on the current time, or on the temporal
            extents in the map canvas if those are defined.
        """
        if self.temporal.navigationMode(
        ) == QgsTemporalNavigationObject.NavigationMode.NavigationOff:
            startTime = QDateTime.currentDateTime().toUTC()
        else:
            startTime = self.temporal.temporalExtents().begin().addSecs(
                self.TEMPORAL_HACK_SECS)

        self.setDateTime(startTime)

    def setDateTime(self, datetime):
        """ Set our date and time choosers appropriately based on the given UTC time
            as interpreted for the current station if there is one, else base on local time.
        """
        if self.stationFeature:
            localtime = datetime.toTimeZone(
                stationTimeZone(self.stationFeature))
        else:
            localtime = datetime.toLocalTime()
        self.dateEdit.setDate(localtime.date())

        # round off the time to the nearest step
        displayTime = localtime.time()
        displayTime.setHMS(
            displayTime.hour(),
            PredictionManager.STEP_MINUTES *
            (displayTime.minute() // PredictionManager.STEP_MINUTES), 0)
        self.timeEdit.setTime(displayTime)

        self.updateTime()
        self.loadStationPredictions()

    def setCurrentStation(self, feature):
        """ set the panel's current prediction station to the one described by the given feature
        """
        self.stationFeature = feature
        self.stationZone = stationTimeZone(feature)
        self.stationLabel.setText(feature['name'])

        self.updateTime()
        self.updateStationLink()
        self.loadStationPredictions()

        if self.stationHighlight is not None:
            self.stationHighlight.hide()

        self.stationHighlight = QgsHighlight(self.canvas, self.stationFeature,
                                             self.currentStationsLayer)
        self.stationHighlight.setColor(QColor(Qt.red))
        self.stationHighlight.setFillColor(QColor(Qt.red))
        self.stationHighlight.show()

    def adjustDay(self, delta):
        self.dateEdit.setDate(self.dateEdit.date().addDays(delta))

    def adjustStep(self, delta):
        step = 60 * PredictionManager.STEP_MINUTES * delta
        curTime = self.timeEdit.time()
        if step < 0 and curTime.hour() == 0 and curTime.minute() == 0:
            self.adjustDay(-1)
        curTime = curTime.addSecs(step)
        self.timeEdit.setTime(curTime)
        if step > 0 and curTime.hour() == 0 and curTime.minute() == 0:
            self.adjustDay(1)

    def updateDate(self):
        self.loadMapExtentPredictions()
        self.updateStationLink()
        self.updateTime()

    def updateTime(self):
        self.temporal.setNavigationMode(
            QgsTemporalNavigationObject.NavigationMode.FixedRange)

        if self.stationZone is not None:
            self.datetime = QDateTime(self.dateEdit.date(),
                                      self.timeEdit.time(),
                                      self.stationZone).toUTC()
        else:
            self.datetime = QDateTime(self.dateEdit.date(),
                                      self.timeEdit.time()).toUTC()
        # Note: we hack around a memory provider range bug here by offsetting the window by 1 minute
        self.temporal.setTemporalExtents(
            QgsDateTimeRange(
                self.datetime.addSecs(-self.TEMPORAL_HACK_SECS),
                self.datetime.addSecs((60 * PredictionManager.STEP_MINUTES) -
                                      self.TEMPORAL_HACK_SECS), True, False))
        self.updatePlotXLine()

    def updateStationLink(self):
        if self.stationFeature is not None:
            linkUrl = 'https://tidesandcurrents.noaa.gov/noaacurrents/Predictions?id={}&d={}&r=1&tz=LST%2FLDT'
            linkUrl = linkUrl.format(
                self.stationFeature['station'],
                self.dateEdit.date().toString('yyyy-MM-dd'))
            self.linkLabel.setText('<a href="{}">{} Station Page</a>'.format(
                linkUrl, self.stationFeature['id']))
        else:
            self.linkLabel.setText('')

    def handlePlotClick(self, event):
        minutes = PredictionManager.STEP_MINUTES * (
            event.xdata * 60 // PredictionManager.STEP_MINUTES)
        self.timeEdit.setTime(QTime(minutes // 60, minutes % 60))

    def updatePlotXLine(self):
        if self.predictionCanvas is not None:
            x = QTime(0, 0).secsTo(self.timeEdit.time()) / 3600.0
            self.plotXLine.set_xdata([x, x])
            self.plotAxes.figure.canvas.draw()

    def predictionsResolved(self):
        # Check to see if the resolved signal is for data we currently care about.
        # if not, then just bail
        if self.stationData is None or self.stationData.state != PredictionPromise.ResolvedState:
            return
        """ when we have predictions for the current station, show them in the
            plot and table widget.
        """
        if self.predictionCanvas is not None:
            self.predictionCanvas.mpl_disconnect(self.plotCallbackId)
            self.plotLayout.removeWidget(self.predictionCanvas)
            self.predictionCanvas.hide()

        self.predictionCanvas = FigureCanvas(Figure(figsize=(5, 3)))
        self.plotLayout.addWidget(self.predictionCanvas)

        self.plotAxes = self.predictionCanvas.figure.subplots()
        # zero time in this plot = 00:00 local time on the date of interest
        t0 = QDateTime(self.dateEdit.date(), QTime(0, 0),
                       stationTimeZone(self.stationFeature)).toUTC()
        t = []
        val = []
        for f in self.stationData.predictions:
            if f['type'] == 'current':
                utcTime = f['time']
                utcTime.setTimeSpec(Qt.TimeSpec.UTC)
                t.append(t0.secsTo(utcTime) / 3600)
                val.append(f['value'])

        self.plotAxes.set_xlim(left=0, right=24)
        self.plotAxes.set_xticks([0, 3, 6, 9, 12, 15, 18, 21, 24])
        self.plotAxes.grid(linewidth=0.5)

        y0line = self.plotAxes.axhline(y=0)
        y0line.set_linestyle(':')
        y0line.set_linewidth(1)

        self.plotXLine = self.plotAxes.axvline(x=0)
        self.plotXLine.set_linestyle(':')
        self.plotXLine.set_linewidth(1)
        self.updatePlotXLine()

        self.plotAxes.plot(t, val)

        self.plotCallbackId = self.predictionCanvas.mpl_connect(
            'button_release_event', self.handlePlotClick)

        QgsProject.instance()._ax = self.plotAxes

        self.tableWidget.setRowCount(len(self.stationData.predictions))
        i = 0
        for p in self.stationData.predictions:
            dt = p['time']
            dt.setTimeSpec(Qt.TimeSpec.UTC)
            if self.includeCurrentsInTable and p[
                    'type'] == 'current' and p['dir'] != NULL:
                self.tableWidget.setItem(
                    i, 0,
                    QTableWidgetItem(
                        dt.toTimeZone(self.stationZone).toString('h:mm AP')))
                self.tableWidget.setItem(
                    i, 1, QTableWidgetItem(str(round(p['dir'])) + 'º'))
                self.tableWidget.setItem(
                    i, 2, QTableWidgetItem("{:.2f}".format(p['magnitude'])))
                i += 1
            elif p['type'] != 'current':
                self.tableWidget.setItem(
                    i, 0,
                    QTableWidgetItem(
                        dt.toTimeZone(self.stationZone).toString('h:mm AP')))
                self.tableWidget.setItem(i, 1, QTableWidgetItem(p['type']))
                self.tableWidget.setItem(
                    i, 2, QTableWidgetItem("{:.2f}".format(p['value'])))
                self.tableWidget.setRowHeight(i, 20)
                i += 1
        self.tableWidget.setRowCount(i)

    def annotatePredictions(self):
        if self.stationFeature is None:
            return

        a = QgsTextAnnotation()
        a.setMapLayer(self.predictionManager.stationsLayer)

        document = a.document()

        columnWidth = [80, 100, 60]
        columnAlign = ['left', 'left', 'right']

        html = '<font size="+2"><b>'
        html += self.stationFeature['name'] + '<br>' + self.dateEdit.date(
        ).toString() + '<br>'
        html += '</b></font>'
        html += '<font size="+1"><table cellpadding="0" cellspacing="0">'
        html += '<tr>'
        for j in range(0, self.tableWidget.columnCount()):
            html += '<td width="{}"><b>{}</b></td>'.format(
                columnWidth[j],
                self.tableWidget.horizontalHeaderItem(j).text())
        html += '</tr>'

        for i in range(0, self.tableWidget.rowCount()):
            html += '<tr bgcolor="{}">'.format('#FFFFFF' if i %
                                               2 else '#EEEEEE')
            for j in range(0, self.tableWidget.columnCount()):
                html += '<td align="{}" width="{}">{}</td>'.format(
                    columnAlign[j], columnWidth[j],
                    self.tableWidget.item(i, j).text())
            html += '</tr>'

        html += '</table></font>'
        document.setHtml(html)

        # TODO: this size and offset are wack. Can we dynamically calculate from the content somehow?
        a.setFrameSize(QSizeF(270, 300))
        a.setFrameOffsetFromReferencePoint(QPointF(-300, -200))
        a.setMapPosition(self.stationFeature.geometry().asPoint())
        a.setMapPositionCrs(
            QgsCoordinateReferenceSystem(
                self.predictionManager.stationsLayer.crs()))

        # disable its symbol
        for symbol in a.markerSymbol().symbolLayers():
            symbol.setEnabled(False)

        QgsProject.instance().annotationManager().addAnnotation(a)
Exemplo n.º 19
0
class FeatureSelectorWidget(QWidget):
    feature_identified = pyqtSignal(QgsFeature)

    def __init__(self, parent):
        QWidget.__init__(self, parent)

        edit_layout = QHBoxLayout()
        edit_layout.setContentsMargins(0, 0, 0, 0)
        edit_layout.setSpacing(2)
        self.setLayout(edit_layout)

        self.line_edit = QLineEdit(self)
        self.line_edit.setReadOnly(True)
        edit_layout.addWidget(self.line_edit)

        self.highlight_feature_button = QToolButton(self)
        self.highlight_feature_button.setPopupMode(QToolButton.MenuButtonPopup)
        self.highlight_feature_action = QAction(
            QgsApplication.getThemeIcon("/mActionHighlightFeature.svg"),
            "Highlight feature", self)
        self.scale_highlight_feature_action = QAction(
            QgsApplication.getThemeIcon("/mActionScaleHighlightFeature.svg"),
            "Scale and highlight feature", self)
        self.pan_highlight_feature_action = QAction(
            QgsApplication.getThemeIcon("/mActionPanHighlightFeature.svg"),
            "Pan and highlight feature", self)
        self.highlight_feature_button.addAction(self.highlight_feature_action)
        self.highlight_feature_button.addAction(
            self.scale_highlight_feature_action)
        self.highlight_feature_button.addAction(
            self.pan_highlight_feature_action)
        self.highlight_feature_button.setDefaultAction(
            self.highlight_feature_action)
        edit_layout.addWidget(self.highlight_feature_button)

        self.map_identification_button = QToolButton(self)
        self.map_identification_button.setIcon(
            QgsApplication.getThemeIcon("/mActionMapIdentification.svg"))
        self.map_identification_button.setText("Select on map")
        self.map_identification_button.setCheckable(True)
        edit_layout.addWidget(self.map_identification_button)

        self.map_identification_button.clicked.connect(self.map_identification)
        self.highlight_feature_button.triggered.connect(
            self.highlight_action_triggered)

        self.layer = None
        self.map_tool = None
        self.canvas = None
        self.window_widget = None
        self.highlight = None
        self.feature = QgsFeature()

    def set_canvas(self, map_canvas):
        self.map_tool = QgsMapToolIdentifyFeature(map_canvas)
        self.map_tool.setButton(self.map_identification_button)
        self.canvas = map_canvas

    def set_layer(self, layer):
        self.layer = layer

    def set_feature(self, feature, canvas_extent=CanvasExtent.Fixed):
        self.line_edit.clear()
        self.feature = feature

        if self.feature is None or not self.feature.isValid(
        ) or self.layer is None:
            return

        expression = QgsExpression(self.layer.displayExpression())
        context = QgsExpressionContext()
        scope = QgsExpressionContextScope()
        context.appendScope(scope)
        scope.setFeature(feature)
        feature_title = expression.evaluate(context)
        if feature_title == "":
            feature_title = feature.id()
        self.line_edit.setText(str(feature_title))
        self.highlight_feature(canvas_extent)

    def clear(self):
        self.feature = QgsFeature()
        self.line_edit.clear()

    @pyqtSlot()
    def map_identification(self):
        if self.layer is None or self.map_tool is None or self.canvas is None:
            return

        self.map_tool.setLayer(self.layer)
        self.canvas.setMapTool(self.map_tool)

        self.window_widget = QWidget.window(self)
        self.canvas.window().raise_()
        self.canvas.activateWindow()
        self.canvas.setFocus()

        self.map_tool.featureIdentified.connect(
            self.map_tool_feature_identified)
        self.map_tool.deactivated.connect(self.map_tool_deactivated)

    def map_tool_feature_identified(self, feature):
        feature = QgsFeature(feature)
        self.feature_identified.emit(feature)
        self.unset_map_tool()
        self.set_feature(feature)

    def map_tool_deactivated(self):
        if self.window_widget is not None:
            self.window_widget.raise_()
            self.window_widget.activateWindow()

    def highlight_feature(self, canvas_extent=CanvasExtent.Fixed):
        if self.canvas is None or not self.feature.isValid():
            return

        geom = self.feature.geometry()

        if geom is None:
            return

        if canvas_extent == CanvasExtent.Scale:
            feature_bounding_box = geom.boundingBox()
            feature_bounding_box = self.canvas.mapSettings(
            ).layerToMapCoordinates(self.layer, feature_bounding_box)
            extent = self.canvas.extent()
            if not extent.contains(feature_bounding_box):
                extent.combineExtentWith(feature_bounding_box)
                extent.scale(1.1)
                self.canvas.setExtent(extent)
                self.canvas.refresh()

        elif canvas_extent == CanvasExtent.Pan:
            centroid = geom.centroid()
            center = centroid.asPoint()

            center = self.canvas.mapSettings().layerToMapCoordinates(
                self.layer, center)
            self.canvas.zoomByFactor(1.0,
                                     center)  # refresh is done in this method

        # highlight
        self.delete_highlight()
        self.highlight = QgsHighlight(self.canvas, geom, self.layer)

        settings = QSettings()
        color = QColor(
            settings.value("/Map/highlight/color",
                           Qgis.DEFAULT_HIGHLIGHT_COLOR.name()))
        alpha = int(
            settings.value("/Map/highlight/colorAlpha",
                           Qgis.DEFAULT_HIGHLIGHT_COLOR.alpha()))
        buffer = 2 * float(
            settings.value("/Map/highlight/buffer",
                           Qgis.DEFAULT_HIGHLIGHT_BUFFER_MM))
        min_width = 2 * float(
            settings.value("/Map/highlight/min_width",
                           Qgis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM))

        self.highlight.setColor(color)  # sets also fill with default alpha
        color.setAlpha(alpha)
        self.highlight.setFillColor(color)  # sets fill with alpha
        self.highlight.setBuffer(buffer)
        self.highlight.setMinWidth(min_width)
        self.highlight.setWidth(4.0)
        self.highlight.show()

        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.delete_highlight)
        self.timer.start(3000)

    def delete_highlight(self):
        if self.highlight is not None:
            self.highlight.hide()
            del self.highlight
            self.highlight = None

    def unset_map_tool(self):
        if self.canvas is not None and self.map_tool is not None:
            # this will call mapTool.deactivated
            self.canvas.unsetMapTool(self.map_tool)

    def highlight_action_triggered(self, action):
        self.highlight_feature_button.setDefaultAction(action)

        if action == self.highlight_feature_action:
            self.highlight_feature()

        elif action == self.scale_highlight_feature_action:
            self.highlight_feature(CanvasExtent.Scale)

        elif action == self.pan_highlight_feature_action:
            self.highlight_feature(CanvasExtent.Pan)
Exemplo n.º 20
0
class LrsDockWidget(QDockWidget, Ui_LrsDockWidget):
    def __init__(self, parent, iface):
        # #debug( "LrsDockWidget.__init__")
        self.iface = iface
        self.lrs = None  # Lrs object
        self.lrsLayer = None  # Common input LrsLayer for locate/events/measure
        self.genSelectionDialog = None
        self.locatePoint = None  # QgsPointXY
        self.locateHighlight = None  # QgsHighlight
        self.errorPointLayer = None
        self.errorPointLayerManager = None
        self.errorLineLayer = None
        self.errorLineLayerManager = None
        self.qualityLayer = None
        self.qualityLayerManager = None

        self.pluginDir = QFileInfo(QgsApplication.qgisUserDatabaseFilePath()
                                   ).path() + "/python/plugins/lrs"

        super(LrsDockWidget, self).__init__(parent)

        # Set up the user interface from Designer.
        self.setupUi(self)

        # keep progress frame height
        self.genProgressFrame.setMinimumHeight(self.genProgressFrame.height())
        self.hideGenProgress()

        self.tabWidget.currentChanged.connect(self.tabChanged)

        # ------------- locate, events, measure have synchronized lrs layer and route field --------
        lrsLayerComboList = [
            self.locateLrsLayerCombo, self.eventsLrsLayerCombo,
            self.measureLrsLayerCombo
        ]

        self.lrsLayerCM = LrsLayerComboManager(
            lrsLayerComboList,
            geometryType=QgsWkbTypes.LineGeometry,
            geometryHasM=True,
            settingsName='lrsLayerId')

        self.lrsLayerCM.layerChanged.connect(self.lrsLayerChanged)

        lrsRouteFieldComboList = [
            self.locateLrsRouteFieldCombo, self.eventsLrsRouteFieldCombo,
            self.measureLrsRouteFieldCombo
        ]
        self.lrsRouteFieldCM = LrsFieldComboManager(
            lrsRouteFieldComboList,
            self.lrsLayerCM,
            settingsName='lrsRouteField',
            allowNone=True)

        # using activated() which is called on user interaction to avoid be called 3 times from each combo
        # self.lrsRouteFieldCM.fieldNameChanged.connect(self.lrsRouteFieldNameChanged)
        self.lrsRouteFieldCM.fieldNameActivated.connect(
            self.lrsRouteFieldNameActivated)

        # ----------------------- locateTab ---------------------------
        self.locateRouteCM = LrsComboManager(self.locateRouteCombo)
        self.locateHighlightWM = LrsWidgetManager(
            self.locateHighlightCheckBox,
            settingsName='locateHighlight',
            defaultValue=True)
        self.locateBufferWM = LrsWidgetManager(self.locateBufferSpin,
                                               settingsName='locateBuffer',
                                               defaultValue=200.0)

        self.locateRouteCombo.currentIndexChanged.connect(
            self.locateRouteChanged)
        self.locateMeasureSpin.valueChanged.connect(self.resetLocateEvent)
        self.locateBufferSpin.valueChanged.connect(self.locateBufferChanged)
        self.locateCenterButton.clicked.connect(self.locateCenter)
        self.locateHighlightCheckBox.stateChanged.connect(
            self.locateHighlightChanged)
        self.locateZoomButton.clicked.connect(self.locateZoom)
        self.locateHelpButton.clicked.connect(lambda: self.showHelp('locate'))
        self.resetLocateRoutes()
        self.locateProgressBar.hide()

        # ----------------------- eventsTab ---------------------------
        self.eventsLayerCM = LrsLayerComboManager(self.eventsLayerCombo,
                                                  settingsName='eventsLayerId')
        self.eventsRouteFieldCM = LrsFieldComboManager(
            self.eventsRouteFieldCombo,
            self.eventsLayerCM,
            settingsName='eventsRouteField')
        self.eventsMeasureStartFieldCM = LrsFieldComboManager(
            self.eventsMeasureStartFieldCombo,
            self.eventsLayerCM,
            types=QVARIANT_NUMBER_TYPE_LIST,
            settingsName='eventsMeasureStartField')
        self.eventsMeasureEndFieldCM = LrsFieldComboManager(
            self.eventsMeasureEndFieldCombo,
            self.eventsLayerCM,
            types=QVARIANT_NUMBER_TYPE_LIST,
            allowNone=True,
            settingsName='eventsMeasureEndField')

        self.eventsFeaturesSelectCM = LrsComboManager(
            self.eventsFeaturesSelectCombo,
            options=((ALL_FEATURES, self.tr('All features')),
                     (SELECTED_FEATURES, self.tr('Selected features'))),
            defaultValue=ALL_FEATURES,
            settingsName='eventsFeaturesSelect')

        self.eventsOutputNameWM = LrsWidgetManager(
            self.eventsOutputNameLineEdit,
            settingsName='eventsOutputName',
            defaultValue='LRS events')
        self.eventsErrorFieldWM = LrsWidgetManager(
            self.eventsErrorFieldLineEdit,
            settingsName='eventsErrorField',
            defaultValue='lrs_err')
        validator = QRegExpValidator(QRegExp('[A-Za-z_][A-Za-z0-9_]+'), None)
        self.eventsErrorFieldLineEdit.setValidator(validator)

        self.eventsButtonBox.button(QDialogButtonBox.Ok).clicked.connect(
            self.createEvents)
        self.eventsButtonBox.button(QDialogButtonBox.Reset).clicked.connect(
            self.resetEventsOptionsAndWrite)
        self.eventsButtonBox.button(QDialogButtonBox.Help).clicked.connect(
            lambda: self.showHelp('events'))
        self.eventsLayerCombo.currentIndexChanged.connect(
            self.resetEventsButtons)
        self.eventsRouteFieldCombo.currentIndexChanged.connect(
            self.resetEventsButtons)
        self.eventsMeasureStartFieldCombo.currentIndexChanged.connect(
            self.resetEventsButtons)
        self.eventsMeasureEndFieldCombo.currentIndexChanged.connect(
            self.resetEventsButtons)
        self.eventsOutputNameLineEdit.textEdited.connect(
            self.resetEventsButtons)
        self.resetEventsOptions()
        self.resetEventsButtons()
        self.eventsProgressBar.hide()

        self.eventsLayerCM.reload()

        # ----------------------- measureTab ---------------------------
        self.measureLayerCM = LrsLayerComboManager(
            self.measureLayerCombo,
            geometryType=QgsWkbTypes.PointGeometry,
            settingsName='measureLayerId')
        self.measureRouteFieldCM = LrsFieldComboManager(
            self.measureRouteFieldCombo,
            self.measureLayerCM,
            allowNone=True,
            settingsName='measureRouteField')
        self.measureThresholdWM = LrsWidgetManager(
            self.measureThresholdSpin,
            settingsName='measureThreshold',
            defaultValue=100.0)
        self.measureOutputNameWM = LrsWidgetManager(
            self.measureOutputNameLineEdit,
            settingsName='measureOutputName',
            defaultValue='LRS measure')

        self.measureOutputRouteFieldWM = LrsWidgetManager(
            self.measureOutputRouteFieldLineEdit,
            settingsName='measureOutputRouteField',
            defaultValue='route')
        validator = QRegExpValidator(QRegExp('[A-Za-z_][A-Za-z0-9_]+'), None)
        self.measureOutputRouteFieldLineEdit.setValidator(validator)

        self.measureMeasureFieldWM = LrsWidgetManager(
            self.measureMeasureFieldLineEdit,
            settingsName='measureMeasureField',
            defaultValue='measure')
        self.measureMeasureFieldLineEdit.setValidator(validator)

        self.measureButtonBox.button(QDialogButtonBox.Ok).clicked.connect(
            self.calculateMeasures)
        self.measureButtonBox.button(QDialogButtonBox.Reset).clicked.connect(
            self.resetMeasureOptionsAndWrite)
        self.measureButtonBox.button(QDialogButtonBox.Help).clicked.connect(
            lambda: self.showHelp('measures'))
        self.measureLayerCombo.currentIndexChanged.connect(
            self.resetMeasureButtons)
        self.measureOutputNameLineEdit.textEdited.connect(
            self.resetMeasureButtons)
        self.measureOutputRouteFieldLineEdit.textEdited.connect(
            self.resetMeasureButtons)
        self.measureMeasureFieldLineEdit.textEdited.connect(
            self.resetMeasureButtons)
        self.resetMeasureOptions()
        self.resetMeasureButtons()
        self.measureProgressBar.hide()

        self.measureLayerCM.reload()

        # ------------- genTab -----------------------
        self.genLineLayerCM = LrsLayerComboManager(
            self.genLineLayerCombo,
            geometryType=QgsWkbTypes.LineGeometry,
            settingsName='lineLayerId')
        self.genLineRouteFieldCM = LrsFieldComboManager(
            self.genLineRouteFieldCombo,
            self.genLineLayerCM,
            settingsName='lineRouteField')
        self.genPointLayerCM = LrsLayerComboManager(
            self.genPointLayerCombo,
            geometryType=QgsWkbTypes.PointGeometry,
            settingsName='pointLayerId')
        self.genPointRouteFieldCM = LrsFieldComboManager(
            self.genPointRouteFieldCombo,
            self.genPointLayerCM,
            settingsName='pointRouteField')
        self.genPointMeasureFieldCM = LrsFieldComboManager(
            self.genPointMeasureFieldCombo,
            self.genPointLayerCM,
            types=QVARIANT_NUMBER_TYPE_LIST,
            settingsName='pointMeasureField')

        self.genMeasureUnitCM = LrsUnitComboManager(
            self.genMeasureUnitCombo,
            settingsName='measureUnit',
            defaultValue=LrsUnits.KILOMETER)

        self.genSelectionModeCM = LrsComboManager(
            self.genSelectionModeCombo,
            options=(('all', self.tr('All routes')),
                     ('include', self.tr('Include routes')),
                     ('exclude', self.tr('Exclude routes'))),
            defaultValue='all',
            settingsName='selectionMode')
        self.genSelectionWM = LrsWidgetManager(self.genSelectionLineEdit,
                                               settingsName='selection')

        self.genThresholdWM = LrsWidgetManager(self.genThresholdSpin,
                                               settingsName='threshold',
                                               defaultValue=100.0)
        self.genSnapWM = LrsWidgetManager(self.genSnapSpin,
                                          settingsName='snap',
                                          defaultValue=0.0)
        self.genParallelModeCM = LrsComboManager(
            self.genParallelModeCombo,
            options=(('error', self.tr('Mark as errors')),
                     ('span', self.tr('Span by straight line')),
                     ('exclude', self.tr('Exclude'))),
            defaultValue='error',
            settingsName='parallelMode')
        self.genExtrapolateWM = LrsWidgetManager(self.genExtrapolateCheckBox,
                                                 settingsName='extrapolate',
                                                 defaultValue=False)

        self.genOutputNameWM = LrsWidgetManager(self.genOutputNameLineEdit,
                                                settingsName='lrsOutputName',
                                                defaultValue='LRS')

        self.genLineLayerCombo.currentIndexChanged.connect(
            self.resetGenerateButtons)
        self.genLineLayerCombo.currentIndexChanged.connect(
            self.updateLabelsUnits)
        self.genLineRouteFieldCombo.currentIndexChanged.connect(
            self.resetGenerateButtons)
        self.genPointLayerCombo.currentIndexChanged.connect(
            self.resetGenerateButtons)
        self.genPointRouteFieldCombo.currentIndexChanged.connect(
            self.resetGenerateButtons)
        self.genPointMeasureFieldCombo.currentIndexChanged.connect(
            self.resetGenerateButtons)

        self.genSelectionModeCombo.currentIndexChanged.connect(
            self.enableGenerateSelection)
        self.genSelectionButton.clicked.connect(
            self.openGenerateSelectionDialog)

        self.genButtonBox.button(QDialogButtonBox.Ok).clicked.connect(
            self.generateLrs)
        self.genButtonBox.button(QDialogButtonBox.Reset).clicked.connect(
            self.resetGenerateOptionsAndWrite)
        self.genButtonBox.button(QDialogButtonBox.Help).clicked.connect(
            lambda: self.showHelp('calibration'))

        self.genOutputNameLineEdit.textChanged.connect(self.resetGenButtons)
        self.genCreateOutputButton.setEnabled(False)
        self.genCreateOutputButton.clicked.connect(self.createLrsOutput)

        # load layers after other combos were connected
        self.genLineLayerCM.reload()
        self.genPointLayerCM.reload()

        self.enableGenerateSelection()

        # ------------- errorTab -----------------------
        self.errorVisualizer = LrsErrorVisualizer(self.iface.mapCanvas())
        self.errorModel = None
        self.errorView.horizontalHeader().setStretchLastSection(True)
        self.errorZoomButton.setEnabled(False)
        self.errorZoomButton.setIcon(
            QgsApplication.getThemeIcon('/mActionZoomIn.svg'))
        self.errorZoomButton.setText('Zoom')
        self.errorZoomButton.clicked.connect(self.errorZoom)
        self.errorFilterLineEdit.textChanged.connect(self.errorFilterChanged)

        self.addErrorLayersButton.clicked.connect(self.addErrorLayers)
        self.addQualityLayerButton.clicked.connect(self.addQualityLayer)
        self.errorButtonBox.button(QDialogButtonBox.Help).clicked.connect(
            lambda: self.showHelp('errors'))

        # ---------------------------- statistics tab ----------------------------
        # currently not used (did not correspond well to errors)
        # self.tabWidget.removeTab( self.tabWidget.indexOf(self.statsTab) )

        # ------------------ help tab -------------------------
        # Importing QWebEngineView gives (Qt 5.8.0, PyQt5 5.8.2):
        # ImportError: QtWebEngineWidgets must be imported before a QCoreApplication instance is created
        # from PyQt5.QtWebEngineWidgets import QWebEngineView
        # self.helpWebEngineView = QWebEngineView(self.helpTab)

        # QTextBrowser does not render perfectly all html created by Sphinx -> see help/source/conf.py
        # http://doc.qt.io/qt-5/richtext-html-subset.html
        # QTextBrowser would not render external web sites -> open in browser
        self.helpTextBrowser.setOpenExternalLinks(True)
        self.helpTextBrowser.setSource(QUrl(self.getHelpUrl()))
        # self.helpTextBrowser.setSource(QUrl("http://www.mpasolutions.it/"))
        # self.helpTextBrowser.setSource(QUrl("http://www.google.com/"))

        # -----------------------------------------------------
        # after all combos were created and connected
        self.lrsLayerCM.reload()  # -> lrsRouteFieldCM -> ....

        # -----------------------------------------------------
        self.enableTabs()

        QgsProject.instance().layersWillBeRemoved.connect(
            self.layersWillBeRemoved)

        QgsProject.instance().readProject.connect(self.projectRead)

        QgsProject().instance().crsChanged.connect(self.mapSettingsCrsChanged)

        self.updateLabelsUnits()

        # newProject is currently missing in sip
        # QgsProject.instance().newProject.connect( self.projectNew )

        # read project if plugin was reloaded
        self.projectRead()

    def lrsLayerChanged(self, layer):
        # debug("lrsLayerChanged layer: %s" % (layer.name() if layer else None))
        self.lrsLayer = None
        if layer is not None:
            self.lrsLayer = LrsLayer(layer)
        self.lrsRouteFieldCM.reset()
        self.resetLocateRoutes()
        self.updateMeasureUnits()
        # don't write here, the layer is changing also when loading plugin ->
        # written when route is selected
        # self.lrsLayerCM.writeToProject()

    def lrsRouteFieldNameActivated(self, fieldName):
        # debug("lrsRouteFieldNameActivated fieldName = " + fieldName)
        self.loadLrsLayer()
        self.lrsLayerCM.writeToProject()
        self.lrsRouteFieldCM.writeToProject()

    def loadLrsLayer(self):
        fieldName = self.lrsRouteFieldCM.value()
        if self.lrsLayer:
            self.lrsLayer.setRouteFieldName(fieldName)
            if fieldName:
                self.showLrsLayerProgressBar()
                self.lrsLayer.load(self.loadLrsLayerProgress)
                self.hideLrsLayerProgressBar()
            else:
                self.lrsLayer.reset()
        self.resetLocateRoutes()

    def errorFilterChanged(self, text):
        if not self.sortErrorModel: return
        self.sortErrorModel.setFilterWildcard(text)

    def projectRead(self):
        # debug("projectRead")
        if not QgsProject:
            return

        project = QgsProject.instance()
        if not project:
            return

        self.lrsLayerCM.readFromProject()
        self.lrsRouteFieldCM.readFromProject()
        self.loadLrsLayer()  # load if layer + route field were selected
        self.readGenerateOptions()
        self.readLocateOptions()
        self.readEventsOptions()
        self.readMeasureOptions()

        # --------------------- set error layers if stored in project -------------------
        errorLineLayerId = project.readEntry(PROJECT_PLUGIN_NAME,
                                             "errorLineLayerId")[0]
        # debug("projectRead errorLineLayerId = %s" % errorLineLayerId)
        self.errorLineLayer = project.mapLayer(errorLineLayerId)
        # debug("projectRead errorLineLayer = %s" % self.errorLineLayer)
        # layers must be tested 'is not None' (because layers have __len__(), some strange len)
        if self.errorLineLayer is not None:
            self.errorLineLayerManager = LrsErrorLayerManager(
                self.errorLineLayer)

        errorPointLayerId = project.readEntry(PROJECT_PLUGIN_NAME,
                                              "errorPointLayerId")[0]
        self.errorPointLayer = project.mapLayer(errorPointLayerId)
        if self.errorPointLayer is not None:
            self.errorPointLayerManager = LrsErrorLayerManager(
                self.errorPointLayer)

        qualityLayerId = project.readEntry(PROJECT_PLUGIN_NAME,
                                           "qualityLayerId")[0]
        self.qualityLayer = project.mapLayer(qualityLayerId)
        if self.qualityLayer is not None:
            self.qualityLayerManager = LrsQualityLayerManager(
                self.qualityLayer)

        self.resetGenerateButtons()

        # #debug
        # if self.genLineLayerCM.getLayer():
        #    self.generateLrs() # only when reloading!

    def projectNew(self):
        self.deleteLrs()
        self.resetGenerateOptions()
        self.resetEventsOptions()
        self.resetMeasureOptions()
        self.enableTabs()

    def deleteLrs(self):
        if self.lrs is not None:
            self.lrs.disconnect()
            del self.lrs
        self.lrs = None

    def close(self):
        # #debug( "LrsDockWidget.close")
        self.deleteLrs()
        QgsProject.instance().layersWillBeRemoved.disconnect(
            self.layersWillBeRemoved)
        QgsProject.instance().readProject.disconnect(self.projectRead)
        QgsProject().instance().crsChanged.disconnect(
            self.mapSettingsCrsChanged)

        # Must delete combo managers to disconnect!
        del self.genLineLayerCM
        del self.genLineRouteFieldCM
        del self.genPointLayerCM
        del self.genPointRouteFieldCM
        del self.genPointMeasureFieldCM
        del self.errorVisualizer

        del self.eventsLayerCM
        del self.eventsRouteFieldCM
        del self.eventsMeasureStartFieldCM
        del self.eventsMeasureEndFieldCM
        del self.eventsFeaturesSelectCM

        self.clearLocateHighlight()

        super(LrsDockWidget, self).close()

    def layersWillBeRemoved(self, layerIdList):
        # debug("layersWillBeRemoved layerIdList = %s" % layerIdList)
        project = QgsProject.instance()
        # layers must be tested 'is not None' (because layers have __len__(), some strange len)
        for id in layerIdList:
            if self.errorPointLayer is not None and self.errorPointLayer.id(
            ) == id:
                # debug("layersWillBeRemoved errorPointLayer.id = %s -> unset" % self.errorPointLayer.id())
                self.errorPointLayerManager = None
                self.errorPointLayer = None
                project.removeEntry(PROJECT_PLUGIN_NAME, "errorPointLayerId")
            if self.errorLineLayer is not None and self.errorLineLayer.id(
            ) == id:
                self.errorLineLayerManager = None
                self.errorLineLayer = None
                project.removeEntry(PROJECT_PLUGIN_NAME, "errorLineLayerId")
            if self.qualityLayer is not None and self.qualityLayer.id() == id:
                self.qualityLayerManager = None
                self.qualityLayer = None
                project.removeEntry(PROJECT_PLUGIN_NAME, "qualityLayerId")

    def enableTabs(self):
        enable = bool(self.lrs)
        # self.errorTab.setEnabled(enable)
        # self.locateTab.setEnabled(enable)
        # self.eventsTab.setEnabled(enable)
        # self.measureTab.setEnabled(enable)
        # self.statsTab.setEnabled(enable)

    def tabChanged(self, index):
        # #debug("tabChanged index = %s" % index )
        pass

    def mapSettingsCrsChanged(self):
        # #debug("mapSettingsCrsChanged")
        self.updateLabelsUnits()

    @staticmethod
    def getUnitsLabel(crs):
        if crs:
            return " (%s)" % QgsUnitTypes.encodeUnit(crs.mapUnits())
        else:
            return ""

    def getThresholdLabel(self, crs):
        label = "Max point distance"
        if crs is not None:
            label += self.getUnitsLabel(crs)
        return label

    def getHelpUrl(self):
        return "file:///" + self.pluginDir + "/help/index.html"

    def showHelp(self, anchor=None):
        # debug("showHelp anchor = %s" % anchor)
        url = self.getHelpUrl()
        if anchor:
            url += "#" + anchor
            # debug("showHelp url = %s" % url)
            self.helpTextBrowser.setSource(QUrl(url))
        # QDesktopServices.openUrl(QUrl(url)) # open in default browser
        self.tabWidget.setCurrentIndex(self.tabWidget.indexOf(self.helpTab))

    def lrsEdited(self):
        self.resetStats()

    # ------------------- GENERATE (CALIBRATE) -------------------

    def resetGenerateButtons(self):
        enabled = self.genLineLayerCombo.currentIndex(
        ) != -1 and self.genLineRouteFieldCombo.currentIndex(
        ) != -1 and self.genPointLayerCombo.currentIndex(
        ) != -1 and self.genPointRouteFieldCombo.currentIndex(
        ) != -1 and self.genPointMeasureFieldCombo.currentIndex() != -1

        self.genButtonBox.button(QDialogButtonBox.Ok).setEnabled(enabled)

    def resetGenerateOptions(self):
        self.genLineLayerCM.reset()
        self.genLineRouteFieldCM.reset()
        self.genPointLayerCM.reset()
        self.genPointRouteFieldCM.reset()
        self.genPointMeasureFieldCM.reset()
        self.genMeasureUnitCM.reset()
        self.genSelectionModeCM.reset()
        self.genSelectionWM.reset()
        self.genThresholdWM.reset()
        self.genSnapWM.reset()
        self.genParallelModeCM.reset()
        self.genExtrapolateWM.reset()
        self.genOutputNameWM.reset()

        self.resetGenerateButtons()

    def resetGenerateOptionsAndWrite(self):
        self.resetGenerateOptions()
        self.writeGenerateOptions()

    def enableGenerateSelection(self):
        enable = self.genSelectionModeCM.value() != 'all'
        self.genSelectionLineEdit.setEnabled(enable)
        self.genSelectionButton.setEnabled(enable)

    # save settings in project
    def writeGenerateOptions(self):
        self.genLineLayerCM.writeToProject()
        self.genLineRouteFieldCM.writeToProject()
        self.genPointLayerCM.writeToProject()
        self.genPointRouteFieldCM.writeToProject()
        self.genPointMeasureFieldCM.writeToProject()
        self.genMeasureUnitCM.writeToProject()
        self.genSelectionModeCM.writeToProject()
        self.genSelectionWM.writeToProject()
        self.genThresholdWM.writeToProject()
        self.genSnapWM.writeToProject()
        self.genParallelModeCM.writeToProject()
        self.genExtrapolateWM.writeToProject()
        self.genOutputNameWM.writeToProject()

    def readGenerateOptions(self):
        self.genLineLayerCM.readFromProject()
        self.genLineRouteFieldCM.readFromProject()
        self.genPointLayerCM.readFromProject()
        self.genPointRouteFieldCM.readFromProject()
        self.genPointMeasureFieldCM.readFromProject()
        self.genMeasureUnitCM.readFromProject()
        self.genSelectionModeCM.readFromProject()
        self.genSelectionWM.readFromProject()
        self.genThresholdWM.readFromProject()
        self.genSnapWM.readFromProject()
        self.genParallelModeCM.readFromProject()
        self.genExtrapolateWM.readFromProject()
        self.genOutputNameWM.readFromProject()

    def getGenerateSelection(self):
        return map(str.strip, self.genSelectionLineEdit.text().split(','))

    def openGenerateSelectionDialog(self):
        if not self.genSelectionDialog:
            self.genSelectionDialog = LrsSelectionDialog()
            self.genSelectionDialog.accepted.connect(
                self.generateSelectionDialogAccepted)

        layer = self.genLineLayerCM.getLayer()
        fieldName = self.genLineRouteFieldCM.getFieldName()
        select = self.getGenerateSelection()
        self.genSelectionDialog.load(layer, fieldName, select)

        self.genSelectionDialog.show()

    def generateSelectionDialogAccepted(self):
        selection = self.genSelectionDialog.selected()
        selection = ",".join(map(str, selection))
        self.genSelectionLineEdit.setText(selection)

    def getGenerateCrs(self):
        crs = None
        # debug ( "genLineLayerCM = %s" % self.genLineLayerCM )
        lineLayer = self.genLineLayerCM.getLayer()
        if lineLayer:
            # debug('lineLayer.crs().authid() = %s' % lineLayer.crs().authid())
            crs = lineLayer.crs()

        if isProjectCrsEnabled():
            # #debug ('enabled mapCanvas crs = %s' % self.iface.mapCanvas().mapSettings().destinationCrs().authid() )
            crs = getProjectCrs()
        return crs

    # set threshold units according to current crs
    def updateLabelsUnits(self):
        crs = self.getGenerateCrs()
        label = self.getThresholdLabel(crs)
        self.genThresholdLabel.setText(label)
        label = "Max lines snap" + self.getUnitsLabel(crs)
        self.genSnapLabel.setText(label)

    def generateLrs(self):
        # debug ( 'generateLrs')
        self.deleteLrs()

        self.errorVisualizer.clearHighlight()

        self.writeGenerateOptions()

        crs = self.getGenerateCrs()

        selection = self.getGenerateSelection()
        snap = self.genSnapSpin.value()
        threshold = self.genThresholdSpin.value()
        parallelMode = self.genParallelModeCM.value()
        extrapolate = self.genExtrapolateCheckBox.isChecked()

        # self.mapUnitsPerMeasureUnit = self.genMapUnitsPerMeasureUnitSpin.value()
        measureUnit = self.genMeasureUnitCM.unit()

        self.lrs = LrsCalib(self.genLineLayerCM.getLayer(),
                            self.genLineRouteFieldCM.getFieldName(),
                            self.genPointLayerCM.getLayer(),
                            self.genPointRouteFieldCM.getFieldName(),
                            self.genPointMeasureFieldCM.getFieldName(),
                            selectionMode=self.genSelectionModeCM.value(),
                            selection=selection,
                            crs=crs,
                            snap=snap,
                            threshold=threshold,
                            parallelMode=parallelMode,
                            extrapolate=extrapolate,
                            measureUnit=measureUnit)

        self.genProgressLabel.setText("Writing features")
        self.lrs.progressChanged.connect(self.showGenProgress)
        self.lrs.calibrate()

        self.hideGenProgress()
        self.resetStats()

        # ------------------- errors -------------------
        self.errorZoomButton.setEnabled(False)
        self.errorModel = LrsErrorModel()
        self.errorModel.addErrors(self.lrs.getErrors())

        self.sortErrorModel = QSortFilterProxyModel()
        self.sortErrorModel.setFilterKeyColumn(-1)  # all columns
        self.sortErrorModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.sortErrorModel.setDynamicSortFilter(True)
        self.sortErrorModel.setSourceModel(self.errorModel)

        self.errorView.setModel(self.sortErrorModel)
        self.sortErrorModel.sort(0)
        self.errorView.resizeColumnsToContents()
        self.errorView.setSelectionBehavior(QAbstractItemView.SelectRows)
        # Attention, if selectionMode is QTableView.SingleSelection, selection is not
        # cleared if deleted row was selected (at least one row is always selected)
        self.errorView.setSelectionMode(QTableView.SingleSelection)
        self.errorView.selectionModel().selectionChanged.connect(
            self.errorSelectionChanged)

        self.lrs.updateErrors.connect(self.updateErrors)

        self.resetErrorLayers()
        self.resetQualityLayer()

        # layers must be tested 'is not None' (because layers have __len__(), some strange len)
        if self.errorPointLayer is not None or self.errorLineLayer is not None or self.qualityLayer is not None:
            self.iface.mapCanvas().refresh()

        self.lrs.edited.connect(self.lrsEdited)

        self.resetGenButtons()
        self.resetLocateRoutes()
        self.resetEventsButtons()
        self.resetMeasureButtons()
        self.updateMeasureUnits()
        self.enableTabs()

    def isCalibrated(self):
        return self.lrs is not None and self.lrs.isCalibrated()

    def resetGenButtons(self):
        self.genCreateOutputButton.setEnabled(
            self.isCalibrated()
            and len(self.genOutputNameLineEdit.text().strip()) > 0)

    def createLrsOutput(self):
        output = LrsOutput(self.iface, self.lrs, self.showGenProgress)
        output.output(self.genOutputNameLineEdit.text().strip())
        self.hideGenProgress()

    def showGenProgress(self, label, percent):
        self.genProgressFrame.show()
        self.genProgressLabel.setText(label)
        self.genProgressBar.setValue(percent)

    def hideGenProgress(self):
        self.genProgressFrame.hide()

    # ------------------------------- ERRORS -------------------------------
    def updateErrors(self, errorUpdates):
        # #debug ( "updateErrors" )
        # because SingleSelection does not allow to deselect row, we have to clear selection manually
        index = self.getSelectedErrorIndex()
        if index:
            rows = self.errorModel.rowsToBeRemoved(errorUpdates)
            selected = index.row()
            if selected in rows:
                self.errorView.selectionModel().clear()
        self.errorModel.updateErrors(errorUpdates)
        self.errorSelectionChanged()
        self.updateErrorLayers(errorUpdates)
        self.updateQualityLayer(errorUpdates)

    # def errorSelectionChanged(self, selected, deselected ):
    def errorSelectionChanged(self):
        error = self.getSelectedError()
        self.errorVisualizer.highlight(error, self.lrs.crs)
        self.errorZoomButton.setEnabled(error is not None)

    def getSelectedErrorIndex(self):
        sm = self.errorView.selectionModel()
        if not sm.hasSelection():
            return None
        index = sm.selection().indexes()[0]
        index = self.sortErrorModel.mapToSource(index)
        return index

    def getSelectedError(self):
        index = self.getSelectedErrorIndex()
        if not index:
            return None
        return self.errorModel.getError(index)

    def errorZoom(self):
        error = self.getSelectedError()
        if not error:
            return
        self.errorVisualizer.zoom(error, self.lrs.crs)

        # add new error layers to map

    def addErrorLayers(self):
        project = QgsProject.instance()

        if self.errorLineLayer is None:
            self.errorLineLayer = LrsErrorLineLayer(self.lrs.crs)
            self.errorLineLayerManager = LrsErrorLayerManager(
                self.errorLineLayer)
            self.errorLineLayer.renderer().symbol().setColor(QColor(Qt.red))
            self.resetErrorLineLayer()
            QgsProject.instance().addMapLayers([
                self.errorLineLayer,
            ])
            project.writeEntry(PROJECT_PLUGIN_NAME, "errorLineLayerId",
                               self.errorLineLayer.id())

        if self.errorPointLayer is None:
            self.errorPointLayer = LrsErrorPointLayer(self.lrs.crs)
            self.errorPointLayerManager = LrsErrorLayerManager(
                self.errorPointLayer)
            self.errorPointLayer.renderer().symbol().setColor(QColor(Qt.red))
            self.resetErrorPointLayer()
            QgsProject.instance().addMapLayers([
                self.errorPointLayer,
            ])
            project.writeEntry(PROJECT_PLUGIN_NAME, "errorPointLayerId",
                               self.errorPointLayer.id())

    # reset error layers content (features)
    def resetErrorLayers(self):
        # #debug ( "resetErrorLayers" )
        self.resetErrorPointLayer()
        self.resetErrorLineLayer()

    def updateErrorLayers(self, errorUpdates):
        if self.errorPointLayerManager:
            self.errorPointLayerManager.updateErrors(errorUpdates)
        if self.errorLineLayerManager:
            self.errorLineLayerManager.updateErrors(errorUpdates)

    def updateQualityLayer(self, errorUpdates):
        if self.qualityLayerManager:
            self.qualityLayerManager.update(errorUpdates)

    def resetErrorPointLayer(self):
        # debug("resetErrorPointLayer %s" % self.errorPointLayer)
        if self.errorPointLayerManager is None:
            return
        self.errorPointLayerManager.clear()
        errors = self.lrs.getErrors()
        self.errorPointLayerManager.addErrors(errors, self.lrs.crs)

    def resetErrorLineLayer(self):
        if self.errorLineLayerManager is None:
            return
        self.errorLineLayerManager.clear()
        errors = self.lrs.getErrors()
        self.errorLineLayerManager.addErrors(errors, self.lrs.crs)

    def addQualityLayer(self):
        if not self.qualityLayer:
            self.qualityLayer = LrsQualityLayer(self.lrs.crs)
            self.qualityLayerManager = LrsQualityLayerManager(
                self.qualityLayer)

            self.resetQualityLayer()
            QgsProject.instance().addMapLayers([
                self.qualityLayer,
            ])
            project = QgsProject.instance()
            project.writeEntry(PROJECT_PLUGIN_NAME, "qualityLayerId",
                               self.qualityLayer.id())

    def resetQualityLayer(self):
        # #debug ( "resetQualityLayer %s" % self.qualityLayer )
        if not self.qualityLayerManager: return
        self.qualityLayerManager.clear()
        features = self.lrs.getQualityFeatures()
        self.qualityLayerManager.addFeatures(features, self.lrs.crs)

    # ------------------------ common layer for LOCATE, EVENTS, MEASURES -------------
    def showLrsLayerProgressBar(self):
        self.locateProgressBar.show()
        self.eventsProgressBar.show()
        self.measureProgressBar.show()

    def hideLrsLayerProgressBar(self):
        self.locateProgressBar.hide()
        self.eventsProgressBar.hide()
        self.measureProgressBar.hide()

    def loadLrsLayerProgress(self, percent):
        self.locateProgressBar.setValue(percent)
        self.eventsProgressBar.setValue(percent)
        self.measureProgressBar.setValue(percent)

    # ------------------------------------ LOCATE ------------------------------------
    def resetLocateOptions(self):
        self.locateHighlightWM.reset()
        self.locateBufferWM.reset()

    def readLocateOptions(self):
        self.locateHighlightWM.readFromProject()
        self.locateBufferWM.readFromProject()

    def resetLocateRoutes(self):
        # debug("resetLocateRoutes lrsLayer: %s" % (self.lrsLayer if self.lrsLayer else None))
        options = [(None, '')]
        if self.lrsLayer:
            options.extend([(id, "%s" % id)
                            for id in self.lrsLayer.getRouteIds()])
        # debug("resetLocateRoutes options: %s" % options)
        self.locateRouteCM.setOptions(options)

    def locateRouteChanged(self):
        # #debug ('locateRouteChanged')
        rangesText = ''
        routeId = self.locateRouteCM.value()
        if self.lrsLayer and routeId is not None:
            ranges = []
            for r in self.lrsLayer.getRouteMeasureRanges(routeId):
                rang = "%s-%s" % (
                    formatMeasure(r[0], self.lrsLayer.measureUnit),
                    formatMeasure(r[1], self.lrsLayer.measureUnit))
                ranges.append(rang)
            rangesText = ", ".join(ranges)
        # #debug ('ranges: %s' % rangesText )
        self.locateRanges.setText(rangesText)

        self.resetLocateEvent()

    def locateBufferChanged(self):
        self.locateBufferWM.writeToProject()

    def resetLocateEvent(self):
        self.clearLocateHighlight()
        routeId = self.locateRouteCM.value()
        measure = self.locateMeasureSpin.value()
        coordinates = ''
        point = None
        if routeId is not None:
            point, error = self.lrsLayer.eventPointXY(routeId, measure)

            if point:
                mapSettings = self.iface.mapCanvas().mapSettings()
                if isProjectCrsEnabled(
                ) and getProjectCrs() != self.lrsLayer.crs:
                    transform = QgsCoordinateTransform(
                        self.lrsLayer.crs, mapSettings.destinationCrs(),
                        QgsProject.instance())
                    point = transform.transform(point)
                coordinates = "%.3f,%.3f" % (point.x(), point.y())
            else:
                coordinates = error

        self.locatePoint = point  # QgsPointXY
        self.highlightLocatePoint()

        self.locateCoordinates.setText(coordinates)

        self.locateCenterButton.setEnabled(bool(point))
        self.locateZoomButton.setEnabled(bool(point))

    def locateHighlightChanged(self):
        # #debug ('locateHighlightChanged')
        self.clearLocateHighlight()
        self.locateHighlightWM.writeToProject()
        self.highlightLocatePoint()

    def highlightLocatePoint(self):
        # #debug ('highlightLocatePoint')
        self.clearLocateHighlight()
        if not self.locatePoint: return
        if not self.locateHighlightCheckBox.isChecked(): return

        mapCanvas = self.iface.mapCanvas()
        mapSettings = mapCanvas.mapSettings()
        # QgsHighlight does reprojection from layer CRS
        crs = getProjectCrs() if isProjectCrsEnabled() else self.lrsLayer.crs
        layer = QgsVectorLayer('Point?crs=' + crsString(crs),
                               'LRS locate highlight', 'memory')
        #self.locateHighlight = QgsHighlight(mapCanvas, QgsGeometry.fromPoint(self.locatePoint), layer)
        # QgsGeometry(QgsPoint) takes ownership!
        self.locateHighlight = QgsHighlight(
            mapCanvas, QgsGeometry(QgsPoint(self.locatePoint)), layer)
        # highlight point size is hardcoded in QgsHighlight
        self.locateHighlight.setWidth(2)
        self.locateHighlight.setColor(Qt.yellow)
        self.locateHighlight.show()

    def clearLocateHighlight(self):
        # #debug ('clearLocateHighlight')
        if self.locateHighlight:
            del self.locateHighlight
            self.locateHighlight = None

    def locateCenter(self):
        if not self.locatePoint: return
        mapCanvas = self.iface.mapCanvas()
        extent = mapCanvas.extent()
        extent.scale(1.0, self.locatePoint.x(), self.locatePoint.y())

        self.iface.mapCanvas().setExtent(extent)
        self.iface.mapCanvas().refresh()

    def locateZoom(self):
        if not self.locatePoint: return
        p = self.locatePoint
        b = self.locateBufferSpin.value()
        extent = QgsRectangle(p.x() - b, p.y() - b, p.x() + b, p.y() + b)

        self.iface.mapCanvas().setExtent(extent)
        self.iface.mapCanvas().refresh()

    # ---------------------------------- EVENTS ----------------------------------

    def resetEventsOptions(self):
        self.eventsLayerCM.reset()
        self.eventsRouteFieldCM.reset()
        self.eventsMeasureStartFieldCM.reset()
        self.eventsMeasureEndFieldCM.reset()
        self.eventsFeaturesSelectCM.reset()
        self.eventsOutputNameWM.reset()
        self.eventsErrorFieldWM.reset()

        self.resetEventsButtons()

    def resetEventsOptionsAndWrite(self):
        self.resetEventsOptions()
        self.writeEventsOptions()

    def resetEventsButtons(self):
        enabled = bool(self.lrsLayer) and self.eventsLayerCombo.currentIndex(
        ) != -1 and self.eventsRouteFieldCombo.currentIndex(
        ) != -1 and self.eventsMeasureStartFieldCombo.currentIndex(
        ) != -1 and bool(self.eventsOutputNameLineEdit.text())

        self.eventsButtonBox.button(QDialogButtonBox.Ok).setEnabled(enabled)

    # save settings in project
    def writeEventsOptions(self):
        self.eventsLayerCM.writeToProject()
        self.eventsRouteFieldCM.writeToProject()
        self.eventsMeasureStartFieldCM.writeToProject()
        self.eventsMeasureEndFieldCM.writeToProject()
        self.eventsFeaturesSelectCM.writeToProject()
        self.eventsOutputNameWM.writeToProject()
        self.eventsErrorFieldWM.writeToProject()

    def readEventsOptions(self):
        self.eventsLayerCM.readFromProject()
        self.eventsRouteFieldCM.readFromProject()
        self.eventsMeasureStartFieldCM.readFromProject()
        self.eventsMeasureEndFieldCM.readFromProject()
        self.eventsFeaturesSelectCM.readFromProject()
        self.eventsOutputNameWM.readFromProject()
        self.eventsErrorFieldWM.readFromProject()

    def createEvents(self):
        self.writeEventsOptions()
        self.eventsProgressBar.show()

        layer = self.eventsLayerCM.getLayer()
        routeFieldName = self.eventsRouteFieldCM.getFieldName()
        startFieldName = self.eventsMeasureStartFieldCM.getFieldName()
        endFieldName = self.eventsMeasureEndFieldCM.getFieldName()
        featuresSelect = self.eventsFeaturesSelectCM.value()
        outputName = self.eventsOutputNameLineEdit.text()
        if not outputName: outputName = self.eventsOutputNameWM.defaultValue()
        errorFieldName = self.eventsErrorFieldLineEdit.text()

        events = LrsEvents(self.lrsLayer, self.eventsProgressBar)
        events.create(layer, featuresSelect, routeFieldName, startFieldName,
                      endFieldName, errorFieldName, outputName)

    # ------------------- MEASURE -------------------

    def resetMeasureOptions(self):
        # #debug('resetMeasureOptions')
        self.measureLayerCM.reset()
        self.measureRouteFieldCM.reset()
        self.measureThresholdWM.reset()
        self.measureOutputNameWM.reset()
        self.measureOutputRouteFieldWM.reset()
        self.measureMeasureFieldWM.reset()

        self.resetMeasureButtons()

    def resetMeasureOptionsAndWrite(self):
        self.resetMeasureOptions()
        self.writeMeasureOptions()

    def resetMeasureButtons(self):
        # #debug('resetMeasureButtons')
        enabled = bool(self.lrsLayer) and self.measureLayerCombo.currentIndex(
        ) != -1 and bool(self.measureOutputNameLineEdit.text()) and bool(
            self.measureOutputRouteFieldLineEdit.text()) and bool(
                self.measureMeasureFieldLineEdit.text())

        self.measureButtonBox.button(QDialogButtonBox.Ok).setEnabled(enabled)

    # save settings in project
    def writeMeasureOptions(self):
        self.measureLayerCM.writeToProject()
        self.measureRouteFieldCM.writeToProject()
        self.measureThresholdWM.writeToProject()
        self.measureOutputNameWM.writeToProject()
        self.measureOutputRouteFieldWM.writeToProject()
        self.measureMeasureFieldWM.writeToProject()

    def readMeasureOptions(self):
        self.measureLayerCM.readFromProject()
        self.measureRouteFieldCM.readFromProject()
        self.measureThresholdWM.readFromProject()
        self.measureOutputNameWM.readFromProject()
        self.measureOutputRouteFieldWM.readFromProject()
        self.measureMeasureFieldWM.readFromProject()

    # set threshold units according to current crs
    def updateMeasureUnits(self):
        crs = self.lrsLayer.crs if self.lrsLayer else None
        label = self.getThresholdLabel(crs)
        # debug('updateMeasureUnits label = %s' % label)
        self.measureThresholdLabel.setText(label)

    def calculateMeasures(self):
        # #debug('calculateMeasures')
        self.writeMeasureOptions()

        self.measureProgressBar.show()

        layer = self.measureLayerCM.getLayer()
        routeFieldName = self.measureRouteFieldCM.getFieldName()
        threshold = self.measureThresholdSpin.value()
        outputName = self.measureOutputNameLineEdit.text()
        if not outputName: outputName = self.measureOutputNameWM.defaultValue()
        outputRouteFieldName = self.measureOutputRouteFieldLineEdit.text()
        measureFieldName = self.measureMeasureFieldLineEdit.text()

        measures = LrsMeasures(self.iface, self.lrsLayer,
                               self.measureProgressBar)
        measures.calculate(layer, routeFieldName, outputRouteFieldName,
                           measureFieldName, threshold, outputName)

    # ------------------- STATS -------------------

    def resetStats(self):
        # debug ( 'setStats' )
        # html = ''
        if self.lrs:
            #     if self.lrs.getEdited():
            #         html = 'Statistics are not available if an input layer has been edited after calibration. Run calibration again to get fresh statistics.'
            #     else:
            #         html = self.lrs.getStatsHtml()
            # self.statsTextEdit.setHtml(html)

            self.errorTotalLength.setText("%.3f" % self.lrs.getStat('length'))
            self.errorIncludedLength.setText(
                "%.3f" % self.lrs.getStat('lengthIncluded'))
            self.errorSuccessLength.setText("%.3f" %
                                            self.lrs.getStat('lengthOk'))

    # -------------------- widget ----------------------
    def saveWidgetGeometry(self):
        # debug("LrsDockWidget.saveWidgetGeometry")
        settings = QgsSettings()
        settings.setValue("/Windows/lrs/geometry", self.saveGeometry())

    def restoreWidgetGeometry(self):
        # debug("LrsDockWidget.restoreWidgetGeometry")
        settings = QgsSettings()
        self.restoreGeometry(
            settings.value("/Windows/lrs/geometry", QByteArray()))
Exemplo n.º 21
0
class TinTools:
    def __init__(self, iface):
        self.iface = iface
        self.toolbar = None
        self.highlight = None
        self.msgBar = iface.messageBar()
        self.plugin_dir = os.path.dirname(__file__)
        print('plugin init en ' + self.plugin_dir)

    def initGui(self):
        self.toolbar = self.iface.addToolBar(u'TIN Tools')
        self.toolbar.setObjectName(u'tintools')
        label = QLabel('Capa TIN:')
        self.toolbar.addWidget(label)
        self.tin_cb = QgsMapLayerComboBox()
        self.tin_cb.setMinimumWidth(200)
        self.tin_cb.setFilters(QgsMapLayerProxyModel.VectorLayer)
        self.tin_cb.layerChanged.connect(self.tinLayerChanged)
        self.toolbar.addWidget(self.tin_cb)

        #        icon_path = self.plugin_dir +'/icon.png'
        #        icon = QIcon(icon_path)
        #'\u0394 \u2B16  \u20E4  \u2350 \u21C8 \u2963 \u2B7F \u2b85 \u23C4  \u25e9'
        self.calc_plane_action = QAction('\u0394', self.iface.mainWindow())
        self.calc_plane_action.triggered.connect(self.calcPlaneEquation)
        self.toolbar.addAction(self.calc_plane_action)

        self.calc_z = QAction('\u21C8', self.iface.mainWindow())
        self.calc_z.triggered.connect(self.calcZ)
        self.toolbar.addAction(self.calc_z)

        self.adjust_to_tin = QAction('\u25e9', self.iface.mainWindow())
        self.adjust_to_tin.triggered.connect(self.adjustToTin)
        self.toolbar.addAction(self.adjust_to_tin)

        self.tinLayerChanged(self.tin_cb.currentLayer())

    def unload(self):
        self.planeCalc = None
        self.setHighlight(None, None)
        self.iface.removeToolBarIcon(self.calc_plane_action)
        self.iface.removeToolBarIcon(self.calc_z)
        self.iface.removeToolBarIcon(self.adjust_to_tin)
        del self.toolbar
        print('unload toolbar5')

    def msg(self, text):
        self.msgBar.pushWarning('TIN tool', text)

    def info(self, text):
        self.msgBar.pushInfo('TIN tool', text)

    def setHighlight(self, ly, vertex):
        if self.highlight:
            self.highlight.hide()
            self.highlight = None
        if vertex:
            color = QColor(255, 0, 100, 255)
            lv = vertex + [vertex[0]]
            g = QgsGeometry.fromPolyline(lv).convertToType(2)

            print(g.asWkt(3))
            self.highlight = QgsHighlight(self.iface.mapCanvas(), g, ly)
            self.highlight.setColor(color)
            self.highlight.setWidth(5)
            color.setAlpha(50)
            self.highlight.setFillColor(color)
            self.highlight.show()

    def tinLayerChanged(self, layer):
        self.calc_plane_action.setEnabled(layer is not None)
        self.adjust_to_tin.setEnabled(layer is not None)
        self.calc_z.setEnabled(False)
        self.planeCalc = None
        self.setHighlight(None, None)

    def calcPlaneEquation(self):
        ly = self.tin_cb.currentLayer()
        self.setHighlight(None, None)
        if not ly:
            self.msg('No hay ninguna capa seleccionada')
            return
        ver = None
        feat = ly.selectedFeatures()
        geometry_type = ly.geometryType()
        if geometry_type == 0:  # point
            if len(feat) == 3:
                ver = [f.geometry().vertexAt(0) for f in feat]
            else:
                self.msg('Hay que seleccionar 3 puntos')
        elif geometry_type == 2:  #polygon
            if len(feat) == 1:
                geom = feat[0].geometry()
                ver = list(geom.vertices())
                if len(ver) == 4:
                    ver = ver[:3]
                else:
                    self.msg('El polígono tiene que ser un triángulo')
            else:
                self.msg('Seleccionar solo un triángulo')
        else:
            self.msg('seleccionar tres puntos o un triángulo')
        if ver:
            self.planeCalc = PlaneCalc(ver)
            self.info(
                'Selecciona los elementos de una capa para calcular su Z en el plano'
            )
            self.calc_z.setEnabled(True)
            self.setHighlight(ly, ver)
        else:
            self.planeCalc = None
            self.calc_z.setEnabled(False)

    def calcZ(self):

        layer = self.iface.activeLayer()
        if not layer:
            QMessageBox.warning(None, 'TIN Tools',
                                'Selecciona elemenos de una capa')
            return
        if not layer.isEditable():
            QMessageBox.information(None, 'TIN cal',
                                    'La capa no está en modo edición')
            return
        for f in layer.getSelectedFeatures():
            geom = f.geometry()
            n = 0
            v = geom.vertexAt(0)
            while (v != QgsPoint(0, 0)):
                z = self.planeCalc.cal_z(v.x(), v.y())
                v.setZ(z)
                geom.moveVertex(v, n)
                n += 1
                v = geom.vertexAt(n)
            layer.changeGeometry(f.id(), geom)

    def adjustToTin(self):
        print('calc tin')
Exemplo n.º 22
0
class LinkerDock(QDockWidget, Ui_linker, SettingDialog):
    def __init__(self, iface):
        # QGIS
        self.iface = iface
        self.settings = MySettings()
        self.linkRubber = QgsRubberBand(self.iface.mapCanvas())
        self.featureHighlight = None
        # Relation management
        self.relationManager = QgsProject.instance().relationManager()
        self.relationManager.changed.connect(self.loadRelations)
        self.relation = QgsRelation()
        self.referencingFeature = QgsFeature()
        self.relationWidgetWrapper = None
        self.editorContext = QgsAttributeEditorContext()
        self.editorContext.setVectorLayerTools(self.iface.vectorLayerTools())

        # GUI
        QDockWidget.__init__(self)
        self.setupUi(self)
        SettingDialog.__init__(self, MySettings(), False, True)
        self.drawButton.setChecked(self.settings.value("drawEnabled"))

        self.relationReferenceWidget.setAllowMapIdentification(True)
        self.relationReferenceWidget.setEmbedForm(False)

        self.mapTool = QgsMapToolIdentifyFeature(self.iface.mapCanvas())
        self.mapTool.setButton(self.identifyReferencingFeatureButton)

        # Connect signal/slot
        self.relationComboBox.currentIndexChanged.connect(
            self.currentRelationChanged)
        self.mapTool.featureIdentified.connect(self.setReferencingFeature)

        # load relations at start
        self.loadRelations()

    def showEvent(self, QShowEvent):
        self.drawLink()

    def closeEvent(self, e):
        self.iface.mapCanvas().unsetMapTool(self.mapTool)
        self.linkRubber.reset()
        self.deleteHighlight()
        self.deleteWrapper()
        self.disconnectLayer()

    def disconnectLayer(self):
        if self.relation.isValid():
            self.relation.referencingLayer().editingStarted.disconnect(
                self.relationEditableChanged)
            self.relation.referencingLayer().editingStopped.disconnect(
                self.relationEditableChanged)
            self.relation.referencingLayer().attributeValueChanged.disconnect(
                self.layerValueChangedOutside)

    def runForFeature(self, relationId, layer, feature):
        index = self.relationComboBox.findData(relationId)
        self.relationComboBox.setCurrentIndex(index)
        self.setReferencingFeature(feature)
        self.show()
        if not layer.isEditable():
            self.iface.messageBar().pushMessage(
                "Link It",
                "Cannot set a new related feature since %s is not editable" %
                layer.name(), QgsMessageBar.WARNING, 4)
        else:
            self.relationReferenceWidget.mapIdentification()

    @pyqtSlot(name="on_identifyReferencingFeatureButton_clicked")
    def activateMapTool(self):
        self.iface.mapCanvas().setMapTool(self.mapTool)

    def deactivateMapTool(self):
        self.iface.mapCanvas().unsetMapTool(self.mapTool)

    def loadRelations(self):
        self.deleteWrapper()
        self.disconnectLayer()
        self.relation = QgsRelation()
        self.referencingFeature = QgsFeature()
        self.relationComboBox.currentIndexChanged.disconnect(
            self.currentRelationChanged)
        self.relationComboBox.clear()
        for relation in self.relationManager.referencedRelations():
            if relation.referencingLayer().hasGeometryType():
                self.relationComboBox.addItem(relation.name(), relation.id())
        self.relationComboBox.setCurrentIndex(-1)
        self.relationComboBox.currentIndexChanged.connect(
            self.currentRelationChanged)
        self.currentRelationChanged(-1)

    def currentRelationChanged(self, index):
        # disconnect previous relation
        if self.relation.isValid():
            try:
                self.relation.referencingLayer().editingStarted.disconnect(
                    self.relationEditableChanged)
                self.relation.referencingLayer().editingStopped.disconnect(
                    self.relationEditableChanged)
                self.relation.referencingLayer(
                ).attributeValueChanged.disconnect(
                    self.layerValueChangedOutside)
            except TypeError:
                pass

        self.referencingFeatureLayout.setEnabled(index >= 0)
        relationId = self.relationComboBox.itemData(index)
        self.relation = self.relationManager.relation(relationId)
        self.mapTool.setLayer(self.relation.referencingLayer())
        self.setReferencingFeature()
        # connect
        if self.relation.isValid():
            self.relation.referencingLayer().editingStarted.connect(
                self.relationEditableChanged)
            self.relation.referencingLayer().editingStopped.connect(
                self.relationEditableChanged)
            self.relation.referencingLayer().attributeValueChanged.connect(
                self.layerValueChangedOutside)

    def setReferencingFeature(self, feature=QgsFeature()):
        self.deactivateMapTool()
        self.referencingFeature = QgsFeature(feature)
        self.deleteWrapper()

        # disable relation reference widget if no referencing feature
        self.referencedFeatureLayout.setEnabled(feature.isValid())

        # set line edit
        if not self.relation.isValid() or not feature.isValid():
            self.referencingFeatureLineEdit.clear()
            return
        self.referencingFeatureLineEdit.setText("%s" % feature.id())

        fieldIdx = self.referencingFieldIndex()
        widgetConfig = self.relation.referencingLayer().editorWidgetV2Config(
            fieldIdx)
        self.relationWidgetWrapper = QgsEditorWidgetRegistry.instance().create(
            "RelationReference", self.relation.referencingLayer(), fieldIdx,
            widgetConfig, self.relationReferenceWidget, self,
            self.editorContext)

        self.relationWidgetWrapper.setEnabled(
            self.relation.referencingLayer().isEditable())
        self.relationWidgetWrapper.setValue(feature[fieldIdx])
        self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged)
        # override field definition to allow map identification
        self.relationReferenceWidget.setAllowMapIdentification(True)
        self.relationReferenceWidget.setEmbedForm(False)

        # update drawn link
        self.highlightReferencingFeature()
        self.drawLink()

    def deleteWrapper(self):
        if self.relationWidgetWrapper is not None:
            self.relationWidgetWrapper.valueChanged.disconnect(
                self.foreignKeyChanged)
            self.relationWidgetWrapper.setValue(None)
            del self.relationWidgetWrapper
            self.relationWidgetWrapper = None

    def foreignKeyChanged(self, newKey):
        if not self.relation.isValid() or not self.relation.referencingLayer(
        ).isEditable() or not self.referencingFeature.isValid():
            self.drawLink()
            return
        if not self.relation.referencingLayer().editBuffer(
        ).changeAttributeValue(self.referencingFeature.id(),
                               self.referencingFieldIndex(), newKey):
            self.iface.messageBar().pushMessage(
                "Link It", "Cannot change attribute value.",
                QgsMessageBar.CRITICAL)
        self.drawLink()

    def relationEditableChanged(self):
        if self.relationWidgetWrapper is not None:
            self.relationWidgetWrapper.setEnabled(
                self.relation.isValid()
                and self.relation.referencingLayer().isEditable())

    def layerValueChangedOutside(self, fid, fieldIdx, value):
        if not self.relation.isValid() or not self.referencingFeature.isValid(
        ) or self.relationWidgetWrapper is None:
            return
        # not the correct feature
        if fid != self.referencingFeature.id():
            return
        # not the correct field
        if fieldIdx != self.referencingFieldIndex():
            return
        # widget already has this value
        if value == self.relationWidgetWrapper.value():
            return
        self.relationWidgetWrapper.valueChanged.disconnect(
            self.foreignKeyChanged)
        self.relationWidgetWrapper.setValue(value)
        self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged)

    def referencingFieldIndex(self):
        if not self.relation.isValid():
            return -1
        fieldName = self.relation.fieldPairs().keys()[0]
        fieldIdx = self.relation.referencingLayer().fieldNameIndex(fieldName)
        return fieldIdx

    @pyqtSlot(bool, name="on_drawButton_toggled")
    def drawLink(self):
        self.settings.setValue("drawEnabled", self.drawButton.isChecked())
        self.linkRubber.reset()
        if not self.drawButton.isChecked(
        ) or not self.referencingFeature.isValid(
        ) or not self.relation.isValid():
            return

        referencedFeature = self.relationReferenceWidget.referencedFeature()
        if not referencedFeature.isValid():
            return

        p1 = self.centroid(self.relation.referencedLayer(), referencedFeature)
        p2 = self.centroid(self.relation.referencingLayer(),
                           self.referencingFeature)
        geom = arc(p1, p2)

        self.linkRubber.setToGeometry(geom, None)
        self.linkRubber.setWidth(self.settings.value("rubberWidth"))
        self.linkRubber.setColor(self.settings.value("rubberColor"))
        self.linkRubber.setLineStyle(Qt.DashLine)

    def centroid(self, layer, feature):
        geom = feature.geometry()
        if geom.type() == QGis.Line:
            geom = geom.interpolate(geom.length() / 2)
        else:
            geom = geom.centroid()
        return self.iface.mapCanvas().mapSettings().layerToMapCoordinates(
            layer, geom.asPoint())

    @pyqtSlot(name="on_highlightReferencingFeatureButton_clicked")
    def highlightReferencingFeature(self):
        self.deleteHighlight()
        if not self.relation.isValid() or not self.referencingFeature.isValid(
        ):
            return

        self.featureHighlight = QgsHighlight(
            self.iface.mapCanvas(), self.referencingFeature.geometry(),
            self.relation.referencingLayer())
        settings = QSettings()
        color = QColor(
            settings.value("/Map/highlight/color",
                           QGis.DEFAULT_HIGHLIGHT_COLOR.name()))
        alpha = int(
            settings.value("/Map/highlight/colorAlpha",
                           QGis.DEFAULT_HIGHLIGHT_COLOR.alpha()))
        bbuffer = float(
            settings.value("/Map/highlight/buffer",
                           QGis.DEFAULT_HIGHLIGHT_BUFFER_MM))
        minWidth = float(
            settings.value("/Map/highlight/minWidth",
                           QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM))

        self.featureHighlight.setColor(color)
        color.setAlpha(alpha)
        self.featureHighlight.setFillColor(color)
        self.featureHighlight.setBuffer(bbuffer)
        self.featureHighlight.setMinWidth(minWidth)
        self.featureHighlight.show()

        timer = QTimer(self)
        timer.setSingleShot(True)
        timer.timeout.connect(self.deleteHighlight)
        timer.start(3000)

    def deleteHighlight(self):
        if self.featureHighlight:
            del self.featureHighlight
        self.featureHighlight = None
 def getFlash():
     h = QgsHighlight( self.canvas, geometry, layer )
     h.setColor(     QColor( 255, 0, 0, 255 ) )
     h.setFillColor( QColor( 255, 0, 0, 100 ) )
     h.setWidth( 2 )
     return h
pr.addAttributes([
    QgsField("name", QVariant.String),
    QgsField("age", QVariant.Int),
    QgsField("size", QVariant.Double)
])
vl.updateFields()  # tell the vector layer to fetch changes from the provider

infos = [[10, 10, "John", 24, 1.73], [40, -60, "Paul", 29, 1.86],
         [60, 5, "George", 34, 1.69], [0, 45, "Ringo", 73, 1.75]]

# add features
for i in infos:
    fet = QgsFeature()
    fet.setGeometry(QgsGeometry.fromPoint(QgsPoint(i[0], i[1])))
    fet.setAttributes(i[2:5])
    pr.addFeatures([fet])

# update layer's extent when new features have been added
# because change of extent in provider is not propagated to the layer
vl.updateExtents()

QgsMapLayerRegistry.instance().addMapLayer(vl)

highlight = QgsHighlight(iface.mapCanvas(),
                         QgsGeometry.fromPoint(QgsPoint(0, 47)), vl)
highlight.setBuffer(1.5)
highlight.setColor(QColor('black'))
highlight.setFillColor(QColor('blue'))
highlight.setWidth(0.5)
iface.mapCanvas().refresh()
Exemplo n.º 25
0
class LinkerDock(QDockWidget, Ui_linker, SettingDialog):
    def __init__(self, iface):
        # QGIS
        self.iface = iface
        self.settings = MySettings()
        self.linkRubber = QgsRubberBand(self.iface.mapCanvas())
        self.featureHighlight = None
        # Relation management
        self.relationManager = QgsProject.instance().relationManager()
        self.relationManager.changed.connect(self.loadRelations)
        self.relation = QgsRelation()
        self.referencingFeature = QgsFeature()
        self.relationWidgetWrapper = None
        self.editorContext = QgsAttributeEditorContext()
        self.editorContext.setVectorLayerTools(self.iface.vectorLayerTools())

        # GUI
        QDockWidget.__init__(self)
        self.setupUi(self)
        SettingDialog.__init__(self, MySettings(), False, True)
        self.drawButton.setChecked(self.settings.value("drawEnabled"))

        self.relationReferenceWidget.setAllowMapIdentification(True)
        self.relationReferenceWidget.setEmbedForm(False)

        self.mapTool = QgsMapToolIdentifyFeature(self.iface.mapCanvas())
        self.mapTool.setButton(self.identifyReferencingFeatureButton)

        # Connect signal/slot
        self.relationComboBox.currentIndexChanged.connect(self.currentRelationChanged)
        self.mapTool.featureIdentified.connect(self.setReferencingFeature)

        # load relations at start
        self.loadRelations()

    def showEvent(self, QShowEvent):
        self.drawLink()

    def closeEvent(self, e):
        self.iface.mapCanvas().unsetMapTool(self.mapTool)
        self.linkRubber.reset()
        self.deleteHighlight()
        self.deleteWrapper()
        self.disconnectLayer()

    def disconnectLayer(self):
        if self.relation.isValid():
            self.relation.referencingLayer().editingStarted.disconnect(self.relationEditableChanged)
            self.relation.referencingLayer().editingStopped.disconnect(self.relationEditableChanged)
            self.relation.referencingLayer().attributeValueChanged.disconnect(self.layerValueChangedOutside)

    def runForFeature(self, relationId, layer, feature):
        index = self.relationComboBox.findData(relationId)
        self.relationComboBox.setCurrentIndex(index)
        self.setReferencingFeature(feature)
        self.show()
        if not layer.isEditable():
            self.iface.messageBar().pushMessage("Link It", "Cannot set a new related feature since %s is not editable" % layer.name(), QgsMessageBar.WARNING, 4)
        else:
            self.relationReferenceWidget.mapIdentification()

    @pyqtSlot(name="on_identifyReferencingFeatureButton_clicked")
    def activateMapTool(self):
        self.iface.mapCanvas().setMapTool(self.mapTool)

    def deactivateMapTool(self):
        self.iface.mapCanvas().unsetMapTool(self.mapTool)

    def loadRelations(self):
        self.deleteWrapper()
        self.disconnectLayer()
        self.relation = QgsRelation()
        self.referencingFeature = QgsFeature()
        self.relationComboBox.currentIndexChanged.disconnect(self.currentRelationChanged)
        self.relationComboBox.clear()
        for relation in self.relationManager.referencedRelations():
            if relation.referencingLayer().hasGeometryType():
                self.relationComboBox.addItem(relation.name(), relation.id())
        self.relationComboBox.setCurrentIndex(-1)
        self.relationComboBox.currentIndexChanged.connect(self.currentRelationChanged)
        self.currentRelationChanged(-1)

    def currentRelationChanged(self, index):
        # disconnect previous relation
        if self.relation.isValid():
            try:
                self.relation.referencingLayer().editingStarted.disconnect(self.relationEditableChanged)
                self.relation.referencingLayer().editingStopped.disconnect(self.relationEditableChanged)
                self.relation.referencingLayer().attributeValueChanged.disconnect(self.layerValueChangedOutside)
            except TypeError:
                pass

        self.referencingFeatureLayout.setEnabled(index >= 0)
        relationId = self.relationComboBox.itemData(index)
        self.relation = self.relationManager.relation(relationId)
        self.mapTool.setLayer(self.relation.referencingLayer())
        self.setReferencingFeature()
        # connect
        if self.relation.isValid():
            self.relation.referencingLayer().editingStarted.connect(self.relationEditableChanged)
            self.relation.referencingLayer().editingStopped.connect(self.relationEditableChanged)
            self.relation.referencingLayer().attributeValueChanged.connect(self.layerValueChangedOutside)

    def setReferencingFeature(self, feature=QgsFeature()):
        self.deactivateMapTool()
        self.referencingFeature = QgsFeature(feature)
        self.deleteWrapper()

        # disable relation reference widget if no referencing feature
        self.referencedFeatureLayout.setEnabled(feature.isValid())

        # set line edit
        if not self.relation.isValid() or not feature.isValid():
            self.referencingFeatureLineEdit.clear()
            return
        self.referencingFeatureLineEdit.setText("%s" % feature.id())

        fieldIdx = self.referencingFieldIndex()
        widgetConfig = self.relation.referencingLayer().editorWidgetV2Config(fieldIdx)
        self.relationWidgetWrapper = QgsEditorWidgetRegistry.instance().create("RelationReference",
                                                                               self.relation.referencingLayer(),
                                                                               fieldIdx,
                                                                               widgetConfig,
                                                                               self.relationReferenceWidget,
                                                                               self,
                                                                               self.editorContext)

        self.relationWidgetWrapper.setEnabled(self.relation.referencingLayer().isEditable())
        self.relationWidgetWrapper.setValue(feature[fieldIdx])
        self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged)
        # override field definition to allow map identification
        self.relationReferenceWidget.setAllowMapIdentification(True)
        self.relationReferenceWidget.setEmbedForm(False)

        # update drawn link
        self.highlightReferencingFeature()
        self.drawLink()

    def deleteWrapper(self):
        if self.relationWidgetWrapper is not None:
            self.relationWidgetWrapper.valueChanged.disconnect(self.foreignKeyChanged)
            self.relationWidgetWrapper.setValue(None)
            del self.relationWidgetWrapper
            self.relationWidgetWrapper = None

    def foreignKeyChanged(self, newKey):
        if not self.relation.isValid() or not self.relation.referencingLayer().isEditable() or not self.referencingFeature.isValid():
            self.drawLink()
            return
        if not self.relation.referencingLayer().editBuffer().changeAttributeValue(self.referencingFeature.id(), self.referencingFieldIndex(), newKey):
            self.iface.messageBar().pushMessage("Link It", "Cannot change attribute value.", QgsMessageBar.CRITICAL)
        self.drawLink()

    def relationEditableChanged(self):
        if self.relationWidgetWrapper is not None:
            self.relationWidgetWrapper.setEnabled(self.relation.isValid() and self.relation.referencingLayer().isEditable())

    def layerValueChangedOutside(self, fid, fieldIdx, value):
        if not self.relation.isValid() or not self.referencingFeature.isValid() or self.relationWidgetWrapper is None:
            return
        # not the correct feature
        if fid != self.referencingFeature.id():
            return
        # not the correct field
        if fieldIdx != self.referencingFieldIndex():
            return
        # widget already has this value
        if value == self.relationWidgetWrapper.value():
            return
        self.relationWidgetWrapper.valueChanged.disconnect(self.foreignKeyChanged)
        self.relationWidgetWrapper.setValue(value)
        self.relationWidgetWrapper.valueChanged.connect(self.foreignKeyChanged)

    def referencingFieldIndex(self):
        if not self.relation.isValid():
            return -1
        fieldName = self.relation.fieldPairs().keys()[0]
        fieldIdx = self.relation.referencingLayer().fieldNameIndex(fieldName)
        return fieldIdx

    @pyqtSlot(bool, name="on_drawButton_toggled")
    def drawLink(self):
        self.settings.setValue("drawEnabled", self.drawButton.isChecked())
        self.linkRubber.reset()
        if not self.drawButton.isChecked() or not self.referencingFeature.isValid() or not self.relation.isValid():
            return

        referencedFeature = self.relationReferenceWidget.referencedFeature()
        if not referencedFeature.isValid():
            return

        p1 = self.centroid(self.relation.referencedLayer(), referencedFeature)
        p2 = self.centroid(self.relation.referencingLayer(), self.referencingFeature)
        geom = arc(p1, p2)

        self.linkRubber.setToGeometry(geom, None)
        self.linkRubber.setWidth(self.settings.value("rubberWidth"))
        self.linkRubber.setColor(self.settings.value("rubberColor"))
        self.linkRubber.setLineStyle(Qt.DashLine)

    def centroid(self, layer, feature):
        geom = feature.geometry()
        if geom.type() == QGis.Line:
            geom = geom.interpolate(geom.length()/2)
        else:
            geom = geom.centroid()
        return self.iface.mapCanvas().mapSettings().layerToMapCoordinates(layer, geom.asPoint())

    @pyqtSlot(name="on_highlightReferencingFeatureButton_clicked")
    def highlightReferencingFeature(self):
        self.deleteHighlight()
        if not self.relation.isValid() or not self.referencingFeature.isValid():
            return
        
        self.featureHighlight = QgsHighlight(self.iface.mapCanvas(), self.referencingFeature.geometry(), self.relation.referencingLayer())
        settings = QSettings()
        color = QColor( settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name()))
        alpha = int(settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha()))
        bbuffer = float(settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM))
        minWidth = float(settings.value("/Map/highlight/minWidth", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM))
        
        self.featureHighlight.setColor(color)
        color.setAlpha(alpha)
        self.featureHighlight.setFillColor(color)
        self.featureHighlight.setBuffer(bbuffer)
        self.featureHighlight.setMinWidth(minWidth)
        self.featureHighlight.show()

        timer = QTimer(self)
        timer.setSingleShot(True)
        timer.timeout.connect(self.deleteHighlight)
        timer.start(3000)

    def deleteHighlight(self):
        if self.featureHighlight:
            del self.featureHighlight
        self.featureHighlight = None
Exemplo n.º 26
0
class Start:
    def __init__(self, iface):
        self.name = "OPEN_FTTH"
        self.iface = iface
        self.autosave_enabled = False
        self.route_segment_layer = None
        self.route_node_layer = None
        self.websocket = BridgeWebsocket(self.iface)

        self.identifyHighlight = None
        self.last_identified_feature_mrid = None
        self.last_identified_feature_type = None

        self.event_handler = EventHandler(self.iface, self.websocket.websocket,
                                          self)
        self.websocket.messageReceived.connect(self.event_handler.handle)

        self.identifyNetworkElementHandler = IdentifyNetworkElementHandler(
            self.websocket)
        self.retrieve_selected_handler = RetrieveSelectedHandler(
            self.iface, self.websocket)
        self.application_settings = ApplicationSettings()
        self.layers_loaded = False

    def initGui(self):
        self.setupActions()
        self.iface.layerTreeView().currentLayerChanged.connect(
            self.layersLoaded)
        self.iface.layerTreeView().currentLayerChanged.connect(
            self.layerSelectionChange)

    def setupActions(self):
        self.actions = []

        icon_auto_save = ":/plugins/open_ftth/auto_save.svg"
        self.autosave_action = QAction(QtGui.QIcon(icon_auto_save), "Autosave",
                                       self.iface.mainWindow())
        self.autosave_action.setCheckable(True)
        self.autosave_action.triggered.connect(self.setupAutoSave)

        auto_identify = ":/plugins/open_ftth/auto_identify.svg"
        self.select_action = QAction(QtGui.QIcon(auto_identify), "Select",
                                     self.iface.mainWindow())
        self.select_action.setCheckable(True)
        self.select_action.triggered.connect(self.setupSelectTool)

        web_browser = ":/plugins/open_ftth/browser_icon.svg"
        self.web_browser_action = QAction(QtGui.QIcon(web_browser),
                                          "Web-browser",
                                          self.iface.mainWindow())
        self.web_browser_action.setCheckable(False)
        self.web_browser_action.triggered.connect(self.connectWebBrowser)

        paste_geometry = ":/plugins/open_ftth/paste_geometry.svg"
        self.paste_geometry_action = QAction(QtGui.QIcon(paste_geometry),
                                             "Paste geometry",
                                             self.iface.mainWindow())
        self.paste_geometry_action.setCheckable(False)
        self.paste_geometry_action.triggered.connect(self.pasteGeometry)

        self.iface.addPluginToMenu("&OPEN FTTH", self.select_action)
        self.iface.addToolBarIcon(self.autosave_action)
        self.iface.addToolBarIcon(self.select_action)
        self.iface.addToolBarIcon(self.web_browser_action)
        self.iface.addToolBarIcon(self.paste_geometry_action)

        self.identify_tool = IdentifySelect(self.iface.mapCanvas())
        self.identify_tool.identified.connect(self.onIdentified)
        self.identify_tool.identifiedNone.connect(self.onIdentifiedNone)
        self.buildActionListIdentifyTool()

    def buildActionListIdentifyTool(self):
        actionList = self.iface.mapNavToolToolBar().actions()

        # Add actions from QGIS attributes toolbar (handling QWidgetActions)
        tmpActionList = self.iface.attributesToolBar().actions()
        for action in tmpActionList:
            if isinstance(action, QWidgetAction):
                actionList.extend(action.defaultWidget().actions())
            else:
                actionList.append(action)

        tmpActionList = self.iface.digitizeToolBar().actions()
        for action in tmpActionList:
            if isinstance(action, QWidgetAction):
                actionList.extend(action.defaultWidget().actions())
            else:
                actionList.append(action)

        tmpActionList = self.iface.selectionToolBar().actions()
        for action in tmpActionList:
            if isinstance(action, QWidgetAction):
                actionList.extend(action.defaultWidget().actions())
            else:
                actionList.append(action)

        # Build a group with actions from actionList and add your own action
        group = QActionGroup(self.iface.mainWindow())
        group.setExclusive(True)
        for action in actionList:
            group.addAction(action)
        group.addAction(self.select_action)

    def unload(self):
        for action in self.actions:
            self.iface.removeToolBarIcon(action)

        self.autosave_action.setChecked(False)
        self.select_action.setChecked(False)

        self.autosave_enabled = False
        self.select_tool_enabled = False

        try:
            self.disconnectSelectedFeatures()
            self.disconnectSelectTool()
            self.websocket.close()
            self.route_segment_layer.beforeCommitChanges.disconnect(
                self.preCommitDeleteHandlerRouteSegment)
            self.route_node_layer.beforeCommitChanges.disconnect(
                self.preCommitDeleteHandlerRouteNode)
            self.route_node_layer.featuresDeleted.disconnect(
                self.checkFeaturesDeleted)
            self.route_segment_layer.featuresDeleted.disconnect(
                self.checkFeaturesDeleted)
        except Exception:
            pass

    def setupSelectTool(self):
        self.iface.mapCanvas().setMapTool(self.identify_tool)

    def sendSelectedFeatures(self):
        self.getSelectedFeaturesHandler.handle()

    def setupAutoSave(self):
        if self.autosave_enabled is False:
            self.connectAutosave()
            self.saveActiveLayerEdits()
        else:
            self.disconnectAutosave()

    def layersLoaded(self):
        if not self.hasCorrectLayers():
            self.onIdentifiedNone()
            return

        if self.layers_loaded is False:
            self.websocket.start()
            self.layers_loaded = True
            self.route_segment_layer = QgsProject.instance().mapLayersByName(
                ApplicationSettings().get_layers_route_segment_name())[0]
            self.route_node_layer = QgsProject.instance().mapLayersByName(
                ApplicationSettings().get_layers_route_node_name())[0]

            try:
                self.route_node_layer.featuresDeleted.connect(
                    self.checkFeaturesDeleted)
                self.route_segment_layer.featuresDeleted.connect(
                    self.checkFeaturesDeleted)
            except TypeError:
                pass

            try:
                self.route_segment_layer.beforeCommitChanges.connect(
                    self.preCommitDeleteHandlerRouteSegment)
                self.route_node_layer.beforeCommitChanges.connect(
                    self.preCommitDeleteHandlerRouteNode)
            except TypeError:
                pass

    def layerSelectionChange(self):
        if not self.hasCorrectLayers():
            self.onIdentifiedNone()
            return

        self.route_segment_layer = QgsProject.instance().mapLayersByName(
            ApplicationSettings().get_layers_route_segment_name())[0]
        try:
            self.route_segment_layer.selectionChanged.disconnect(
                self.onSelectedSegment)
        except TypeError:
            pass

        self.route_segment_layer.selectionChanged.connect(
            self.onSelectedSegment)

    def hasCorrectLayers(self):
        route_segment_layers = QgsProject.instance().mapLayersByName(
            ApplicationSettings().get_layers_route_segment_name())
        route_node_layers = QgsProject.instance().mapLayersByName(
            ApplicationSettings().get_layers_route_node_name())
        return len(route_segment_layers) > 0 and len(route_node_layers) > 0

    def connectAutosave(self):
        # We do this to avoid plugin crash in case that connects come in an invalid state.
        try:
            self.route_segment_layer = QgsProject.instance().mapLayersByName(
                ApplicationSettings().get_layers_route_segment_name())[0]
            self.route_segment_layer.layerModified.connect(
                self.saveActiveLayerEdits)
        except TypeError:
            pass

        # We do this to avoid plugin crash in case that connects come in an invalid state.
        try:
            self.route_node_layer = QgsProject.instance().mapLayersByName(
                ApplicationSettings().get_layers_route_node_name())[0]
            self.route_node_layer.layerModified.connect(
                self.saveActiveLayerEdits)
        except TypeError:
            pass

        self.autosave_enabled = True

    def disconnectAutosave(self):
        # We do this to avoid plugin crash in case that connects come in an invalid state.
        try:
            self.route_segment_layer.layerModified.disconnect(
                self.saveActiveLayerEdits)
        except TypeError:
            pass

        # We do this to avoid plugin crash in case that connects come in an invalid state.
        try:
            self.route_node_layer.layerModified.disconnect(
                self.saveActiveLayerEdits)
        except TypeError:
            pass

        self.autosave_action.setChecked(False)
        self.autosave_enabled = False

    def connectWebBrowser(self):
        webbrowser.open(self.application_settings.get_website_url(), new=2)

    def saveActiveLayerEdits(self):
        self.iface.actionSaveActiveLayerEdits().trigger()

    def onSelectedSegment(self):
        message = type(
            'Expando', (object, ),
            {'username': self.application_settings.get_user_name_prefix()})()
        self.retrieve_selected_handler.handle(message)

    def checkFeaturesDeleted(self, fids):
        if self.last_identified_feature_mrid is None:
            return

        features = None
        filterExpression = f'"mrid" = \'{self.last_identified_feature_mrid}\''
        if self.last_identified_feature_type == self.application_settings.get_types_route_node(
        ):
            features = self.route_node_layer.getFeatures(
                QgsFeatureRequest().setFilterExpression(filterExpression))
        elif self.last_identified_feature_type == self.application_settings.get_types_route_segment(
        ):
            features = self.route_segment_layer.getFeatures(
                QgsFeatureRequest().setFilterExpression(filterExpression))
        else:
            return

        # hack because the deleted signal is triggered on both create and delete
        stillExists = False
        for feature in features:
            stillExists = True

        if not stillExists:
            self.onIdentifiedNone()
            self.identifyNetworkElementHandler.handle(None, None)
        # end of hack

    def onIdentified(self, selected_layer, selected_feature):
        selected_type = ""
        if self.application_settings.get_layers_route_node_name(
        ) == selected_layer.sourceName():
            selected_type = self.application_settings.get_types_route_node()
        elif self.application_settings.get_layers_route_segment_name(
        ) == selected_layer.sourceName():
            selected_type = self.application_settings.get_types_route_segment()
        else:
            self.identifyHighlight.hide()
            return

        if self.identifyHighlight is not None:
            self.identifyHighlight.hide()

        color = QColor(0, 255, 0)
        self.identifyHighlight = QgsHighlight(self.iface.mapCanvas(),
                                              selected_feature.geometry(),
                                              selected_layer)
        self.identifyHighlight.setWidth(3)
        self.identifyHighlight.setColor(color)
        self.identifyHighlight.show()

        mrid = selected_feature.attribute("mrid")

        self.last_identified_feature_mrid = mrid
        self.last_identified_feature_type = selected_type

        if selected_type != "":
            self.identifyNetworkElementHandler.handle(mrid, selected_type)

    def onIdentifiedNone(self):
        if self.identifyHighlight is not None:
            self.last_identified_feature_mrid = None
            self.last_identified_feature_type = None
            self.identifyHighlight.hide()

    def preCommitDeleteHandlerRouteSegment(self):
        self.undoDeleteSetMarkedToBeDeleted(
            ApplicationSettings().get_layers_route_segment_name())

    def preCommitDeleteHandlerRouteNode(self):
        self.undoDeleteSetMarkedToBeDeleted(
            ApplicationSettings().get_layers_route_node_name())

    def undoDeleteSetMarkedToBeDeleted(self, layerName):
        layers = QgsProject.instance().mapLayersByName(layerName)
        if len(layers) != 1:
            return
        layer = layers[0]

        deleted_features_ids = layer.editBuffer().deletedFeatureIds()
        for feature_id in deleted_features_ids:
            QgsVectorLayerUndoCommandDeleteFeature(layer.editBuffer(),
                                                   feature_id).undo()

        marked_to_be_deleted_idx = layer.fields().indexOf(
            'marked_to_be_deleted')
        user_name_idx = layer.fields().indexOf('user_name')
        user_name = self.application_settings.get_user_name()
        for feature in layer.dataProvider().getFeatures(
                QgsFeatureRequest().setFilterFids(deleted_features_ids)):
            layer.changeAttributeValue(feature.id(), marked_to_be_deleted_idx,
                                       True)
            layer.changeAttributeValue(feature.id(), user_name_idx, user_name)

    def pasteGeometry(self):
        layer = self.iface.activeLayer()

        route_segment_layer_name = self.application_settings.get_layers_route_segment_name(
        )
        if layer.sourceName() != route_segment_layer_name:
            self.showBarMessage(
                "You can only paste a geometry when layer %s is selected." %
                route_segment_layer_name, Qgis.Warning)
            return

        if not layer.isEditable():
            self.showBarMessage(
                "You need to be in edit mode to paste the geometry.",
                Qgis.Warning)
            return

        geoms = self.tryGetFeaturesGeomsFromClipBoard()
        if len(geoms) > 1:
            self.showBarMessage(
                "Can't paste geometry multiple features in clipboard.",
                Qgis.Warning)
            return

        if len(geoms) == 0:
            self.showBarMessage(
                "Can't paste geometry. No features in clipboard.",
                Qgis.Warning)
            return

        selected_features_iter = layer.getSelectedFeatures()
        selected_features = []
        for selected_feature in selected_features_iter:
            selected_features.append(selected_feature)

        if len(selected_features) == 0:
            self.showBarMessage("Can't paste. No target feature to paste to.",
                                Qgis.Warning)
            return

        # Paste is the comment we want to paste to
        paste_feature = selected_features[0]
        paste_geom = paste_feature.geometry()

        # Copy is the geom from the clipboard that we want "paste" to resemble.
        copy_geom = geoms[0]

        if paste_geom.type() != copy_geom.type():
            self.showBarMessage(
                "Not the same geometry type. From %s to %s" %
                (copy_geom.type(), paste_geom.type()), Qgis.Warning)
            return

        paste_geom_start = paste_geom.asPolyline()[0]
        paste_geom_end = paste_geom.asPolyline()[len(paste_geom.asPolyline()) -
                                                 1]
        copy_geom_start = copy_geom.asPolyline()[0]
        copy_geom_end = copy_geom.asPolyline()[len(copy_geom.asPolyline()) - 1]

        start_to_start_distance = paste_geom_start.distance(copy_geom_start)
        start_to_end_distance = paste_geom_start.distance(copy_geom_end)

        new_copy_polyline = copy_geom.asPolyline()

        if start_to_start_distance > start_to_end_distance:
            QgsMessageLog.logMessage(
                "The geometries are flipped, we reverse them for the copy.",
                self.name, Qgis.Info)
            new_copy_polyline.reverse()

        # Its important that we do this after, in case that the geometry is reversed.
        new_copy_geom_start = new_copy_polyline[0]
        new_copy_geom_end = new_copy_polyline[len(new_copy_polyline) - 1]

        new_start_to_start_distance = paste_geom_start.distance(
            new_copy_geom_start)
        if self.application_settings.get_tolerance(
        ) < new_start_to_start_distance:
            self.showBarMessage(
                "Start point distance is bigger than tolerance.",
                Qgis.Critical)
            return

        new_start_to_end_distance = paste_geom_end.distance(new_copy_geom_end)
        if self.application_settings.get_tolerance(
        ) < new_start_to_end_distance:
            self.showBarMessage(
                "End points distance is bigger than tolerance.", Qgis.Critical)
            return

        # We update the geometry.
        result = layer.changeGeometry(
            paste_feature.id(), QgsGeometry.fromPolylineXY(new_copy_polyline))

        # We change the mapping method to LandSurveying to identify that the polyline now matches the landsurvey polyline.
        mapping_method_idx = layer.fields().indexOf('mapping_method')
        layer.changeAttributeValue(paste_feature.id(), mapping_method_idx,
                                   "LandSurveying")

        if not result:
            self.showBarMessage("Can't paste geometry, something went wrong.",
                                Qgis.Critical)
            return

        self.iface.mapCanvas().refresh()

    def tryGetFeaturesGeomsFromClipBoard(self):
        cb = QApplication.clipboard()
        clipboard_text = cb.text()
        if sys.version_info[0] == 2:
            clipboard_text = clipboard_text.encode('utf-8')

        reader = csv.DictReader(StringIO(clipboard_text), delimiter='\t')

        geoms = []
        for row in reader:
            wkt_geom = row.get('wkt_geom')
            geom = QgsGeometry.fromWkt(wkt_geom)

            if not geom:
                self.showBarMessage(
                    'Can\'t create geometry from wkt: %s' % wkt_geom,
                    Qgis.Critical)
                return []

            geoms.append(geom)
        return geoms

    def showBarMessage(self, message, level=Qgis.Info, duration=-1):
        self.iface.messageBar().pushMessage("Error",
                                            message,
                                            level=level,
                                            duration=duration)
Exemplo n.º 27
0
class DetailsTreeView(DetailsDBHandler, DetailsDockWidget):
    def __init__(self, iface, spatial_unit_dock):

        """
        The method initializes the dockwidget.
        :param iface: QGIS user interface class
        :type class qgis.utils.iface
        :param plugin: The STDM plugin
        :type class
        :return: None
        """
        from stdm.ui.entity_browser import _EntityDocumentViewerHandler
        DetailsDockWidget.__init__(self, iface, spatial_unit_dock)

        DetailsDBHandler.__init__(self)

        self.spatial_unit_dock = spatial_unit_dock

        self.view = QTreeView()
        self.view.setSelectionBehavior(
            QAbstractItemView.SelectRows
        )
        #self.feature_ids = []
        self.layer_table = None
        self.entity = None
        self.feature_models = {}
        self.party_models = {}
        self.STR_models = {}
        self.feature_STR_model = {}
        self.removed_feature = None
        self.selected_root = None
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.view.setUniformRowHeights(True)
        self.view.setRootIsDecorated(True)
        self.view.setAlternatingRowColors(True)
        self.view.setWordWrap(True)
        self.view.setHeaderHidden(True)
        self.view.setEditTriggers(
            QAbstractItemView.NoEditTriggers
        )
        self.current_profile = current_profile()
        self.social_tenure = self.current_profile.social_tenure
        self.spatial_unit = self.social_tenure.spatial_unit
        self.party = self.social_tenure.party
        self.view.setMinimumWidth(250)
        self.doc_viewer_title = QApplication.translate(
            'EntityBrowser',
            'Document Viewer'
        )
        self.doc_viewer = _EntityDocumentViewerHandler(
            self.doc_viewer_title, self.iface.mainWindow()
        )

    def set_layer_entity(self):
        self.layer_table = self.get_layer_source(
            self.iface.activeLayer()
        )
        if self.layer_table in spatial_tables():
            self.entity = self.current_profile.entity_by_name(
                self.layer_table
            )

        else:
            self.treeview_error('The layer is not a spatial entity layer. ')

    def activate_feature_details(self, button_clicked=True):
        """
        Action for showing feature details.
        :return:
        """
        # Get the active layer.
        active_layer = self.iface.activeLayer()
        # TODO fix feature_details_btn is deleted error.
        if active_layer is not None and \
                self.spatial_unit_dock.feature_details_btn.isChecked():
            # if feature detail dock is not defined or hidden, create empty dock.
            if self is None or self.isHidden():
                # if the selected layer is not a feature layer, show not
                # feature layer. (implicitly included in the if statement).
                if not self.feature_layer(active_layer):
                    self.spatial_unit_dock.feature_details_btn.setChecked(False)
                    return
                # If the selected layer is feature layer, get data and
                # display treeview in a dock widget
                else:
                    select_feature = QApplication.translate(
                        "STDMQGISLoader",
                        "Please select a feature to view their details."
                    )
                    self.init_dock()
                    self.add_tree_view()
                    self.model.clear()
                    self.treeview_error(select_feature)

                    # enable the select tool
                    self.activate_select_tool()
                    # set entity from active layer in the child class
                    self.set_layer_entity()
                    # set entity for the super class DetailModel
                    self.set_entity(self.entity)
                    # Registery column widget
                    self.set_formatter()
                    #set formatter for social tenure relationship.
                    self.set_formatter(self.social_tenure)
                    self.set_formatter(self.party)
                    # pull data, show treeview
                    active_layer.selectionChanged.connect(
                        self.show_tree
                    )
                    self.steam_signals(self.entity)

            # if feature_detail dock is open, toggle close
            else:
                self.close_dock(
                    self.spatial_unit_dock.feature_details_btn
                )
                self.feature_details = None
        # if no active layer, show error message and uncheck the feature tool
        else:
            if button_clicked:
                self.active_layer_check()
            self.spatial_unit_dock.feature_details_btn.setChecked(False)

    def add_tree_view(self):
        """
        Adds tree view to the dock widget and sets style.
        :return: None
        """
        self.tree_scrollArea.setWidget(self.view)

    def clear_feature_models(self):
        self.feature_models.clear()

    def reset_tree_view(self, no_feature=False):
        #clear feature_ids list, model and highlight
        self.model.clear()

        self.clear_sel_highlight() # remove sel_highlight
        self.disable_buttons(no_feature)
        if self.removed_feature is None:
            self.STR_models.clear()
            self.feature_models.clear()
        else:
            self.removed_feature = None
        features = self.selected_features()
        # if the selected feature is over 1,
        # activate multi_select_highlight
        if not features is None:
            self.view.clicked.connect(
                self.multi_select_highlight
            )
        # if there is at least one selected feature
        if len(features) > 0:
            self.add_tree_view()
            #self.feature_ids = features

    def disable_buttons(self, bool):
        self.edit_btn.setDisabled(bool)
        self.delete_btn.setDisabled(bool)

    def show_tree(self):
        selected_features = self.selected_features()
        if len(selected_features) < 1:
            self.reset_tree_view(True)
            return
        if not self.entity is None:
            self.reset_tree_view()
            if len(selected_features) < 1:
                self.disable_buttons(True)
                return

            layer_icon = QIcon(':/plugins/stdm/images/icons/layer.gif')
            roots = self.add_parent_tree(
                layer_icon, format_name(self.entity.short_name)
            )
            if roots is None:
                return

            for id, root in roots.iteritems():
                db_model = entity_id_to_model(self.entity, id)

                self.add_roots(db_model, root, id)

    def add_parent_tree(self, icon, title):
        roots = OrderedDict()
        for feature_id in self.selected_features():
            root = QStandardItem(icon, title)
            root.setData(feature_id)
            self.set_bold(root)
            self.model.appendRow(root)
            roots[feature_id] = root
        return roots

    def add_roots(self, model, parent, feature_id):
        self.feature_models[feature_id] = model
        if model is None:
            return
        self.column_widget_registry(model, self.entity)

        for i, (col, row) in enumerate(self.formatted_record.iteritems()):
            child = QStandardItem('{}: {}'.format(col, row))
            child.setSelectable(False)
            parent.appendRow([child])

            # Add Social Tenure Relationship steam as a last child
            if i == len(self.formatted_record)-1:
                self.add_STR_child(parent, feature_id)
        self.expand_node(parent)

    def add_STR_steam(self, parent, STR_id):
        str_icon = QIcon(
            ':/plugins/stdm/images/icons/social_tenure.png'
        )
        title = 'Social Tenure Relationship'
        str_root = QStandardItem(str_icon, title)
        str_root.setData(STR_id)
        self.set_bold(str_root)
        parent.appendRow([str_root])
        return str_root
    def add_no_STR_steam(self, parent):
        if self.entity.name == self.spatial_unit.name:
            no_str_icon = QIcon(
                ':/plugins/stdm/images/icons/remove.png'
            )
            title = 'No STR Defined'
            no_str_root = QStandardItem(no_str_icon, title)
            self.set_bold(no_str_root)
            parent.appendRow([no_str_root])

    def add_STR_child(self, parent, feature_id):
        if len(self.feature_STR_link(feature_id)) < 1:
            self.add_no_STR_steam(parent)
            return
        for record in self.feature_STR_link(feature_id):
            self.STR_models[record.id] = record
            str_root = self.add_STR_steam(parent, record.id)
            # add STR children
            self.column_widget_registry(record, self.social_tenure)
            for i, (col, row) in enumerate(
                    self.formatted_record.iteritems()
            ):
                STR_child = QStandardItem(
                    '{}: {}'.format(col, row)
                )
                STR_child.setSelectable(False)
                str_root.appendRow([STR_child])
                if i == len(self.formatted_record)-1:
                    self.add_party_child(
                        str_root, record.party_id
                    )
        self.feature_STR_model[feature_id] = self.STR_models.keys()

    def add_party_steam(self, parent, party_id):
        party_icon = QIcon(
            ':/plugins/stdm/images/icons/table.png'
        )
        title = format_name(self.party.short_name)
        party_root = QStandardItem(party_icon, title)
        party_root.setData(party_id)
        self.set_bold(party_root)

        parent.appendRow([party_root])
        party_root.setEditable(False)
        return party_root

    def add_party_child(self, parent, party_id):

        db_model = entity_id_to_model(self.party, party_id)
        self.party_models[party_id] = db_model
        party_root = self.add_party_steam(parent, party_id)
        # add STR children
        self.column_widget_registry(db_model, self.party)
        for col, row in self.formatted_record.iteritems():
            party_child = QStandardItem('{}: {}'.format(col, row))
            party_child.setSelectable(False)
            party_root.appendRow([party_child])

    def set_bold(self, standard_item):
        """
        Make a text of Qstandaritem to bold.
        :param standard_item: Qstandaritem
        :type: Qstandaritem
        :return: None
        """
        font = standard_item.font()
        font.setBold(True)
        standard_item.setFont(font)

    def treeview_error(self, message, icon=None):
        """
        Displays error message in feature details treeview.
        :param title: the title of the treeview.
        :type: String
        :param message: The message to be displayed.
        :type: String
        :param icon: The icon of the item.
        :type: Resource string
        :return: None
        """
        not_feature_ft_msg = QApplication.translate(
            'FeatureDetails', message
        )
        if icon== None:
            root = QStandardItem(not_feature_ft_msg)
        else:
            root = QStandardItem(icon, not_feature_ft_msg)

        self.view.setRootIsDecorated(False)
        self.model.appendRow(root)
        self.view.setRootIsDecorated(True)

    def expand_node(self, parent):
        """
        Make the last tree node expand.
        :param parent: The parent to expand
        :type QStandardItem
        :return:None
        """
        index = self.model.indexFromItem(parent)
        self.view.expand(index)


    def multi_select_highlight(self, index):
        """
        Highlights a feature with rubberBald class when selecting
        features are more than one.
        :param index: Selected QTreeView item index
        :type Integer
        :return: None
        """
        map = self.iface.mapCanvas()
        try:

            # Get the selected item text using the index
            selected_item = self.model.itemFromIndex(index)
            # Use mutli-select only when more than 1 items are selected.
            if self.layer.selectedFeatures() < 2:
                return
            self.selected_root = selected_item
            # Split the text to get the key and value.
            selected_item_text = selected_item.text()

            selected_value = selected_item.data()
            # If the first word is feature, expand & highlight.
            if selected_item_text == format_name(self.spatial_unit.short_name):
                self.view.expand(index)  # expand the item
                # Clear any existing highlight
                self.clear_sel_highlight()
                # Insert highlight
                # Create expression to target the selected feature
                expression = QgsExpression(
                    "\"id\"='" + str(selected_value) + "'"
                )
                # Get feature iteration based on the expression
                ft_iteration = self.layer.getFeatures(
                    QgsFeatureRequest(expression)
                )

                # Retrieve geometry and attributes
                for feature in ft_iteration:
                    # Fetch geometry
                    geom = feature.geometry()
                    self.sel_highlight = QgsHighlight(map, geom, self.layer)

                    self.sel_highlight.setFillColor(selection_color())
                    self.sel_highlight.setWidth(4)
                    self.sel_highlight.setColor(QColor(212,95,0, 255))
                    self.sel_highlight.show()
                    break
        except AttributeError:
            # pass attribute error on child items such as party
            pass
        except IndexError:
            pass

    def steam_signals(self, entity):
        self.edit_btn.clicked.connect(
            lambda : self.edit_selected_steam(
                entity
            )
        )
        self.delete_btn.clicked.connect(
            self.delete_selected_item
        )
        self.view_document_btn.clicked.connect(
            lambda : self.view_steam_document(
                entity
            )
        )

    def steam_data(self, mode):
        item = None
        # if self.view.currentIndex().text() == format_name(self.party):
        #     return None, None
        # One item is selected and number of feature is also 1
        if len(self.layer.selectedFeatures()) == 1 and \
                        len(self.view.selectedIndexes()) == 1:
            index = self.view.selectedIndexes()[0]
            item = self.model.itemFromIndex(index)
            result = item.data()

        # One item is selected on the map but not on the treeview
        elif len(self.layer.selectedFeatures()) == 1 and \
                        len(self.view.selectedIndexes()) == 0:
            item = self.model.item(0, 0)
            result = item.data()

        # multiple features are selected but one treeview item is selected
        elif len(self.layer.selectedFeatures()) > 1 and \
                        len(self.view.selectedIndexes()) == 1:
            item = self.selected_root
            result = self.selected_root.data()
        # multiple features are selected but no treeview item is selected
        elif len(self.layer.selectedFeatures()) > 1 and \
             len(self.view.selectedIndexes()) == 0:
            result = 'Please, select an item to {}.'.format(mode)
        else:
            result = 'Please, select at least one feature to {}.'.format(mode)
        if result is None:

            if item is None:
                item = self.model.item(0, 0)
                result = item.data()
            else:
                result = item.parent().data()
        return result, item

    def edit_selected_steam(self, entity):
        id, item = self.steam_data('edit')

        feature_edit = True
        if id is None:
            return
        if isinstance(id, str):
            data_error = QApplication.translate('DetailsTreeView', id)
            QMessageBox.warning(
                self.iface.mainWindow(), "Edit Error", data_error
            )
            return

        if item.text() == 'Social Tenure Relationship':
            model = self.STR_models[id]

            feature_edit = False
            ##TODO add STR wizard edit mode here.
        elif item.text() == format_name(self.party.short_name):
            feature_edit = False

            model = self.party_models[id]
            editor = EntityEditorDialog(
                self.party, model, self.iface.mainWindow()
            )
            editor.exec_()
        else:
            model = self.feature_models[id]

            editor = EntityEditorDialog(
                entity, model, self.iface.mainWindow()
            )
            editor.exec_()
        #root = self.find_root(entity, id)
        self.view.expand(item.index())
        if feature_edit:
            self.update_edited_steam(entity, id)
        else:
            self.update_edited_steam(self.social_tenure, id)

    def delete_selected_item(self):
        str_edit = False
        id, item = self.steam_data('delete')

        if isinstance(id, str):
            data_error = QApplication.translate(
                'DetailsTreeView', id
            )
            QMessageBox.warning(
                self.iface.mainWindow(),
                'Delete Error',
                data_error
            )
            return
        if item.text() == 'Social Tenure Relationship':
            str_edit = True
            db_model = self.STR_models[id]

        elif item.text() == format_name(self.spatial_unit.short_name) and \
            id not in self.feature_STR_model.keys():
            db_model = self.feature_models[id]

        # if spatial unit is linked to STR, don't allow delete
        elif item.text() == format_name(self.spatial_unit.short_name) and \
                        id in self.feature_STR_model.keys():


            delete_warning = QApplication.translate(
                'DetailsTreeView',
                'You have to first delete the social tenure \n'
                'relationship to delete the {} record.'.format(
                    item.text()
                )

            )
            QMessageBox.warning(
                self.iface.mainWindow(),
                'Delete Error',
                delete_warning
            )
            return
        # If it is party node, STR exists and don't allow delete.
        elif item.text() == format_name(self.party.short_name):
            delete_warning = QApplication.translate(
                'DetailsTreeView',
                'You have to first delete the social tenure \n'
                'relationship to delete the {} record.'.format(
                    item.text()
                )
            )
            QMessageBox.warning(
                self.iface.mainWindow(),
                'Delete Error',
                delete_warning
            )
            return
        else:
            return
        delete_warning = QApplication.translate(
            'DetailsTreeView',
            'Are you sure you want to delete '
            'the selected record(s)?\n'
            'This action cannot be undone.'
        )

        delete_question = QMessageBox.warning(
            self.iface.mainWindow(),
            "Delete Warning",
            delete_warning,
            QMessageBox.Yes | QMessageBox.No
        )
        if delete_question == QMessageBox.Yes:
            db_model.delete()

            if str_edit:
                del self.STR_models[id]
            else:
                self.removed_feature = id
                del self.feature_models[id]

            self.updated_removed_steam(str_edit, item)
        else:
            return

    def update_edited_steam(self, entity, feature_id):

        # remove rows before adding the updated ones.
        self.layer.setSelectedFeatures(
            self.feature_models.keys()
        )
        root = self.find_root(entity, feature_id)
        if root is None:
            return
        self.view.selectionModel().select(
            root.index(), self.view.selectionModel().Select
        )
        self.expand_node(root)
        self.multi_select_highlight(root.index())

    def find_root(self, entity, feature_id):
        all_roots = self.model.findItems(
            format_name(entity.short_name)
        )
        root = None
        for item in all_roots:
            if item.data() == feature_id:
                root = item
                break
        return root

    def updated_removed_steam(self, STR_edit, item):
        if not STR_edit:
            if len(self.feature_models) > 1:
                self.refresh_layers()
            feature_ids = self.feature_models.keys()
            self.layer.setSelectedFeatures(
                feature_ids
            )
        else:
            item.removeRows(0, 2)
            item.setText('No STR Definded')
            no_str_icon = QIcon(
                ':/plugins/stdm/images/icons/remove.png'
            )
            item.setIcon(no_str_icon)

    def view_steam_document(self, entity):
        # Slot raised to show the document viewer for the selected entity

        id, item = self.steam_data('edit')

        if id is None:
            return
        if isinstance(id, str):
            data_error = QApplication.translate('DetailsTreeView', id)
            QMessageBox.warning(
                self.iface.mainWindow(), "Edit Error", data_error
            )
            return
        if item.text() == 'Social Tenure Relationship':
            db_model = self.STR_models[id]
        else:
            db_model = self.feature_models[id]

        if not db_model is None:
            docs = db_model.documents
            # Notify there are no documents for the selected doc
            if len(docs) == 0:
                msg = QApplication.translate(
                    'EntityBrowser',
                    'There are no supporting documents '
                    'for the selected record.'
                )

                QMessageBox.warning(
                    self,
                    self.doc_viewer_title,
                    msg
                )
            else:
                self.doc_viewer.load(docs)
Exemplo n.º 28
0
class FeatureSelectorWidget(QWidget):
    featureIdentified = pyqtSignal(QgsFeature)

    def __init__(self, parent):
        QWidget.__init__(self, parent)

        editLayout = QHBoxLayout()
        editLayout.setContentsMargins(0, 0, 0, 0)
        editLayout.setSpacing(2)
        self.setLayout(editLayout)

        self.lineEdit = QLineEdit(self)
        self.lineEdit.setReadOnly(True)
        editLayout.addWidget(self.lineEdit)
        
        self.highlightFeatureButton = QToolButton(self)
        self.highlightFeatureButton.setPopupMode(QToolButton.MenuButtonPopup)
        self.highlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionHighlightFeature.svg"), "Highlight feature", self)
        self.scaleHighlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionScaleHighlightFeature.svg"), "Scale and highlight feature", self)
        self.panHighlightFeatureAction = QAction(QgsApplication.getThemeIcon("/mActionPanHighlightFeature.svg"), "Pan and highlight feature", self)
        self.highlightFeatureButton.addAction(self.highlightFeatureAction)
        self.highlightFeatureButton.addAction(self.scaleHighlightFeatureAction)
        self.highlightFeatureButton.addAction(self.panHighlightFeatureAction)
        self.highlightFeatureButton.setDefaultAction(self.highlightFeatureAction)
        editLayout.addWidget(self.highlightFeatureButton)

        self.mapIdentificationButton = QToolButton(self)
        self.mapIdentificationButton.setIcon(QgsApplication.getThemeIcon("/mActionMapIdentification.svg"))
        self.mapIdentificationButton.setText("Select on map")
        self.mapIdentificationButton.setCheckable(True)
        editLayout.addWidget(self.mapIdentificationButton)

        self.mapIdentificationButton.clicked.connect(self.mapIdentification)
        self.highlightFeatureButton.triggered.connect(self.highlightActionTriggered)

        self.layer = None
        self.mapTool = None
        self.canvas = None
        self.windowWidget = None
        self.highlight = None
        self.feature = QgsFeature()

    def setCanvas(self, mapCanvas):
        self.mapTool = QgsMapToolIdentifyFeature(mapCanvas)
        self.mapTool.setButton(self.mapIdentificationButton)
        self.canvas = mapCanvas

    def setLayer(self, layer):
        self.layer = layer

    def setFeature(self, feature, canvasExtent = CanvasExtent.Fixed):
        self.lineEdit.clear()
        self.feature = feature

        if not self.feature.isValid() or self.layer is None:
            return

        featureTitle = feature.attribute(self.layer.displayField())
        if featureTitle == '':
            featureTitle = feature.id()
        self.lineEdit.setText(str(featureTitle))
        self.highlightFeature(canvasExtent)


    def clear(self):
        self.feature = QgsFeature()
        self.lineEdit.clear()

    def mapIdentification(self):
        if self.layer is None or self.mapTool is None or self.canvas is None:
            return

        self.mapTool.setLayer(self.layer)
        self.canvas.setMapTool(self.mapTool)

        self.windowWidget = QWidget.window(self)
        self.canvas.window().raise_()
        self.canvas.activateWindow()
        self.canvas.setFocus()

        self.mapTool.featureIdentified.connect(self.mapToolFeatureIdentified)
        self.mapTool.deactivated.connect(self.mapToolDeactivated)

    def mapToolFeatureIdentified(self, feature):
        feature = QgsFeature(feature)
        self.featureIdentified.emit(feature)
        self.unsetMapTool()
        self.setFeature(feature)

    def mapToolDeactivated(self):
        if self.windowWidget is not None:
            self.windowWidget.raise_()
            self.windowWidget.activateWindow()

    def highlightFeature(self, canvasExtent = CanvasExtent.Fixed):
        if self.canvas is None or not self.feature.isValid():
            return

        geom = self.feature.geometry()

        if geom is None:
            return
  
        if canvasExtent == CanvasExtent.Scale:
            featBBox = geom.boundingBox()
            featBBox = self.canvas.mapSettings().layerToMapCoordinates(self.layer, featBBox)
            extent = self.canvas.extent()
            if not extent.contains(featBBox):
                extent.combineExtentWith(featBBox)
                extent.scale(1.1)
                self.canvas.setExtent(extent)
                self.canvas.refresh()
            
        elif canvasExtent == CanvasExtent.Pan:
            centroid = geom.centroid()
            center = centroid.asPoint()

            center = self.canvas.mapSettings().layerToMapCoordinates(self.layer, center)
            self.canvas.zoomByFactor(1.0, center)  # refresh is done in this method

        # highlight
        self.delete_highlight()
        self.highlight = QgsHighlight(self.canvas, geom, self.layer)

        settings = QSettings()
        color = QColor(settings.value("/Map/highlight/color", QGis.DEFAULT_HIGHLIGHT_COLOR.name()))
        alpha = int(settings.value("/Map/highlight/colorAlpha", QGis.DEFAULT_HIGHLIGHT_COLOR.alpha()))
        buffer = 2*float(settings.value("/Map/highlight/buffer", QGis.DEFAULT_HIGHLIGHT_BUFFER_MM))
        min_width = 2*float(settings.value("/Map/highlight/min_width", QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM))

        self.highlight.setColor(color)  # sets also fill with default alpha
        color.setAlpha(alpha)
        self.highlight.setFillColor(color)  # sets fill with alpha
        self.highlight.setBuffer(buffer)
        self.highlight.setMinWidth(min_width)
        self.highlight.setWidth(4.0)
        self.highlight.show()
        
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.delete_highlight)
        self.timer.start(3000)

    def delete_highlight(self):
        if self.highlight is not None:
            self.highlight.hide()
            del self.highlight
            self.highlight = None

    def unsetMapTool(self):
        if self.canvas is not None and self.mapTool is not None:
            # this will call mapToolDeactivated
            self.canvas.unsetMapTool(self.mapTool)

    def highlightActionTriggered(self, action):
        self.highlightFeatureButton.setDefaultAction(action)

        if action == self.highlightFeatureAction:
            self.highlightFeature()

        elif action == self.scaleHighlightFeatureAction:
            self.highlightFeature(CanvasExtent.Scale)

        elif action == self.panHighlightFeatureAction:
            self.highlightFeature(CanvasExtent.Pan)