Exemplo n.º 1
0
class Slider(InputType):
    '''
    slider input, displays a slider and a number input next to it, both
    connected to each other
    '''

    def __init__(self, minimum: int = 0, maximum: int = 100000000,
                 step: int = 1, width: int = 300,
                 lockable: bool = False, locked: bool = False, **kwargs):
        '''
        Parameters
        ----------
        width : int, optional
            width of slider in pixels, defaults to 300 pixels
        minimum : int, optional
            minimum value that the user can set
        maximum : int, optional
            maximum value that the user can set
        step : int, optional
            the tick intervall of the slider and single step of the number
            input, defaults to 1
        lockable : bool, optional
            the slider and number input can be locked by a checkbox that will
            be displayed next to them if True, defaults to not lockable
        locked : bool, optional
            initial lock-state of inputs, only applied if lockable is True,
            defaults to inputs being not locked
        '''
        super().__init__(**kwargs)
        self.minimum = minimum
        self.maximum = maximum
        self.lockable = lockable
        self.step = step
        self.slider = QSlider(Qt.Horizontal)
        self.slider.setMinimum(minimum)
        self.slider.setMaximum(maximum)
        self.slider.setTickInterval(step)
        self.slider.setFixedWidth(width)
        self.spinbox = QSpinBox()
        self.spinbox.setMinimum(minimum)
        self.spinbox.setMaximum(maximum)
        self.spinbox.setSingleStep(step)
        self.registerFocusEvent(self.spinbox)
        self.registerFocusEvent(self.slider)

        if lockable:
            self.lock_button = QPushButton()
            self.lock_button.setCheckable(True)
            self.lock_button.setChecked(locked)
            self.lock_button.setSizePolicy(
                QSizePolicy.Fixed, QSizePolicy.Fixed)

            def toggle_icon(emit=True):
                is_locked = self.lock_button.isChecked()
                fn = '20190619_iconset_mob_lock_locked_02.png' if is_locked \
                    else '20190619_iconset_mob_lock_unlocked_03.png'
                self.slider.setEnabled(not is_locked)
                self.spinbox.setEnabled(not is_locked)
                icon_path = os.path.join(settings.IMAGE_PATH, 'iconset_mob', fn)
                icon = QIcon(icon_path)
                self.lock_button.setIcon(icon)
                self.locked.emit(is_locked)
            toggle_icon(emit=False)
            self.lock_button.clicked.connect(lambda: toggle_icon(emit=True))

        self.slider.valueChanged.connect(
            lambda: self.set_value(self.slider.value()))
        self.spinbox.valueChanged.connect(
            lambda: self.set_value(self.spinbox.value()))
        self.slider.valueChanged.connect(
            lambda: self.changed.emit(self.get_value()))
        self.spinbox.valueChanged.connect(
            lambda: self.changed.emit(self.get_value())
        )

    def set_value(self, value: int):
        '''
        set a number to both the slider and the number input

        Parameters
        ----------
        checked : int
            check-state
        '''
        for element in [self.slider, self.spinbox]:
            # avoid infinite recursion
            element.blockSignals(True)
            element.setValue(value or 0)
            element.blockSignals(False)

    @property
    def is_locked(self) -> bool:
        '''
        Returns
        -------
        bool
            current lock-state of slider and number input
        '''
        if not self.lockable:
            return False
        return self.lock_button.isChecked()

    def draw(self, layout: QLayout, unit: str = ''):
        '''
        add slider, the connected number and the lock (if lockable) input
        to the layout

        Parameters
        ----------
        layout : QLayout
            layout to add the inputs to
        unit : str, optional
            the unit shown after the value, defaults to no unit
        '''
        l = QHBoxLayout()
        l.addWidget(self.slider)
        l.addWidget(self.spinbox)
        if unit:
            l.addWidget(QLabel(unit))
        if self.lockable:
            l.addWidget(self.lock_button)
        layout.addLayout(l)

    def get_value(self) -> int:
        '''
        get the currently set number

        Returns
        -------
        int
            currently set number
        '''
        return self.slider.value()
Exemplo n.º 2
0
class SpinBox(InputType):
    '''
    spinbox integer number input
    '''
    InputClass = QSpinBox
    def __init__(self, minimum=0, maximum=100000000, step=1,
                 lockable=False, locked=False, reversed_lock=False, **kwargs):
        '''
        Parameters
        ----------
        minimum : int, optional
            minimum value that the user can set
        maximum : int, optional
            maximum value that the user can set
        step : int, optional
            the single step of the number input when changing values,
            defaults to 1
        lockable : bool, optional
            the number input can be locked by a checkbox that will
            be displayed next to it if True, defaults to not lockable
        locked : bool, optional
            initial lock-state of input, only applied if lockable is True,
            defaults to input being not locked
        reversed_lock : bool, optional
            reverses the locking logic, if True checking the lock will enable
            the inputs instead of disabling them, defaults to normal lock
            behaviour (disabling inputs when setting lock-state to True)
        '''
        super().__init__(**kwargs)
        self.minimum = minimum
        self.maximum = maximum
        self.input = self.InputClass()
        self.input.setMinimum(minimum)
        self.input.setMaximum(maximum)
        self.input.setSingleStep(step)
        self.input.valueChanged.connect(self.changed.emit)
        self.registerFocusEvent(self.input)
        self.lockable = lockable

        # ToDo: almost the same as in Slider, outsource into common function
        if lockable:
            self.lock_button = QPushButton()
            self.lock_button.setCheckable(True)
            self.lock_button.setChecked(locked)
            self.lock_button.setSizePolicy(
                QSizePolicy.Fixed, QSizePolicy.Fixed)

            def toggle_icon(emit=True):
                is_locked = self.lock_button.isChecked()
                fn = '20190619_iconset_mob_lock_locked_02.png' if is_locked \
                    else '20190619_iconset_mob_lock_unlocked_03.png'
                self.input.setEnabled(is_locked if reversed_lock else
                                      not is_locked)
                icon_path = os.path.join(settings.IMAGE_PATH, 'iconset_mob', fn)
                icon = QIcon(icon_path)
                self.lock_button.setIcon(icon)
                self.locked.emit(is_locked)
            toggle_icon(emit=False)
            self.lock_button.clicked.connect(lambda: toggle_icon(emit=True))

    def set_value(self, value: int):
        '''
        set the value of the input

        Parameters
        ----------
        value : int
            value to set
        '''
        self.input.setValue(value or 0)

    def get_value(self) -> int:
        '''
        get the current value of the input

        Returns
        -------
        value : int
            current value of input
        '''
        return self.input.value()

    @property
    def is_locked(self) -> bool:
        '''
        Returns
        -------
        bool
            current lock-state of number input
        '''
        if not self.lockable:
            return False
        return self.lock_button.isChecked()

    def draw(self, layout: QLayout, unit: str = ''):
        '''
        add number input and the lock (if lockable) to the layout

        Parameters
        ----------
        layout : QLayout
            layout to add the inputs to
        unit : str, optional
            the unit shown after the value, defaults to no unit
        '''
        l = QHBoxLayout()
        l.addWidget(self.input)
        if unit:
            l.addWidget(QLabel(unit))
        if self.lockable:
            l.addWidget(self.lock_button)
        layout.addLayout(l)
Exemplo n.º 3
0
class RiverMetricsDockWidget(QDockWidget, FORM_CLASS):

    closingPlugin = pyqtSignal()

    def __init__(self, parent=None):
        """Constructor."""
        super(RiverMetricsDockWidget, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

        # fill the layer combobox with vector layers
        self.vectorCombo.setFilters(QgsMapLayerProxyModel.LineLayer)
        self.validate.clicked.connect(self.validateLayer)
        self.graph.clicked.connect(self.graph_data)

        self.clear_graph.clicked.connect(self.do_clear_graph)
        #self.vectorCombo.currentIndexChanged.connect(self.setup_gui)

        #global variables
        self.validate_test = None
        self.graphicState = False
        self.canvas = None
        self.figure = None
        self.breaks = []  # breaks of river axes
        self.line = None  # line geometry
        self.breakButton = QPushButton('Add breaks')
        self.breakButton.setCheckable(True)
        self.Xcsv = None
        self.Ycsv = None
        self.breakButton.clicked.connect(self.addBreaks)
        self.browseBtn.clicked.connect(self.writeFile)

        self.filecsvtemp = tempfile.NamedTemporaryFile(suffix='.csv')

        self.filecsvpath = os.path.splitext(str(
            self.filecsvtemp.name))[0] + '.csv'
        self.lineOutput.setText(self.filecsvpath)

        #create container plot
        self.setup_gui()

    def closeEvent(self, event):
        self.closingPlugin.emit()
        event.accept()

    def message(self, text, col):
        self.validator.setText(text)
        self.validator.setStyleSheet('background-color: ' + col)

    def setup_gui(self):

        #PLOT container
        # a figure instance to plot on
        self.figure = plt.figure()

        # this is the Canvas Widget that displays the `figure`
        # it takes the `figure` instance as a parameter to __init__
        self.canvas = FigureCanvas(self.figure)

        # this is the Navigation widget
        # it takes the Canvas widget and a parent
        self.toolbar = NavigationToolbar(self.canvas, self)

        #  create a new empty QVboxLayout
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.toolbar)
        self.layout.addWidget(self.canvas)
        self.layout.addWidget(self.breakButton)

        # set the Qframe layout
        self.frame_for_plot.setLayout(self.layout)  # si alla fine

    def validateLayer(self):
        self.validator.clear()
        self.validator.setStyleSheet('background-color: None')
        vlayer = self.vectorCombo.currentLayer()

        if not vlayer.isValid():
            self.message(vlayer.name() + ' is not valid', 'red')
            self.validate_test = False
        elif vlayer.geometryType() != QgsWkbTypes.LineGeometry:
            self.message('your vector layer is not a Line', 'red')
            self.validate_test = False
        else:
            self.validate_test = True
            self.message(vlayer.name() + ' is valid', 'white')

        if vlayer.featureCount() > 1:
            self.message('You have more then one feature on your vector layer',
                         'yellow')
            self.validate_test = False
        else:
            for feat in vlayer.getFeatures():
                the_geom = feat.geometry()
                len = the_geom.length()
                self.validate_test = True
                if self.stepSpin.value() >= the_geom.length():
                    self.message(
                        'Use a smaller value for step less than ' +
                        str(round(len / 1000, 2)) + ' km', 'yellow')
                    self.validate_test = False

        #TODO -- add validate CRS layer to be not a geographic
        crslayer = vlayer.crs().toProj4()
        if 'proj=longlat' in crslayer:
            self.validate_test = False
            self.message('The layer crs is not projected', 'yellow')
        if crslayer == '':
            self.validate_test = True
            self.message('warning:the crs seems missing', 'yellow')

    def clearLayout(self, layout):
        '''
        clear layout function
        :return:
        '''
        while layout.count():
            child = layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()

    def graph_data(self):

        step = self.stepSpin.value()
        shif = self.shiftSpin.value()
        if self.validate_test == None:
            self.message('You have to validate your layers first', 'yellow')
        if self.validate_test == True:
            vlayer = self.vectorCombo.currentLayer()
            for feat in vlayer.getFeatures():
                the_geom = feat.geometry()
                x, y = sinuosity(the_geom, step, shif)
                self.line = the_geom

            self.figure.clear()
            # create an axis
            ax = self.figure.add_subplot(111)
            ax.plot(x, y, 'bo', x, y, 'k')
            self.Xcsv = x
            self.Ycsv = y

            #def addVline():
            #if breakButton.isChecked():
            #       figure.canvas.mpl_connect('button_press_event', OnClick)

            #TODO: debug
            #self.canvas.show()
            self.canvas.draw()
            #set variable graphState to true to remember graph is plotted
            self.graphicState = True

            self.writecsv()

            #self.graph.hide()

    def addBreaks(self):
        def OnClick(event):
            ax = self.figure.add_subplot(111)
            ax.axvline(event.xdata, linewidth=4, color='r')
            self.canvas.draw()
            self.breaks.append(float(event.xdata))

        if self.graphicState is True:
            if self.breakButton.isChecked():
                self.breakButton.setText('stop-break')
                self.cid = self.figure.canvas.mpl_connect(
                    'button_press_event', OnClick)
            #TODO: create memory layer splitted with breaks
            else:
                self.breakButton.setText('Add Break')
                self.figure.canvas.mpl_disconnect(self.cid)
                self.final()
                #print self.breaks
                # ll1 = createMemLayer(self.line, self.breaks)
                # QgsMapLayerRegistry.instance().addMapLayers([ll1])
        else:
            self.message('You have to graph your data first', 'yellow')

    def final(self):

        vlayer = self.vectorCombo.currentLayer()
        for feat in vlayer.getFeatures():
            the_geom = feat.geometry()
        #TODO: remove the line for debigging
        #self.message(str(vlayer.name())+'|'+str(the_geom.length())+'|'+str(self.breaks), 'red')

        ll1 = createMemLayer(the_geom, self.breaks)
        QgsProject.instance().addMapLayer(ll1)

    def writeFile(self):
        fileName, __ = QFileDialog.getSaveFileName(
            self, 'Save CSV file', "", "CSV (*.csv);;All files (*)")
        fileName = os.path.splitext(str(fileName))[0] + '.csv'
        self.lineOutput.setText(fileName)

    def writecsv(self):
        filecsv = open(self.lineOutput.text(), 'w')
        filecsv.write('length,sinuosity\n')
        for row in range(len(self.Xcsv)):
            filecsv.write(
                str(round(self.Xcsv[row], 4)) + ',' +
                str(round(self.Ycsv[row], 4)) + '\n')
        filecsv.close()

    def do_clear_graph(self):
        self.figure.clear()
        self.canvas.draw()