Ejemplo n.º 1
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)
Ejemplo n.º 2
0
class PipeSectionDialog(QDialog):
    def __init__(self, parent, iface, params, pipe_ft):

        QDialog.__init__(self, parent)
        main_lay = QVBoxLayout(self)

        self.parent = parent
        self.params = params
        self.pipe_ft = pipe_ft

        self.setMinimumWidth(600)
        self.setMinimumHeight(400)
        self.setWindowTitle('Pipe section editor')  # TODO: softcode
        self.setWindowModality(QtCore.Qt.ApplicationModal)

        curr_dir = os.path.dirname(os.path.abspath(__file__))

        self.fra_toolbar = QFrame(self)
        fra_toolbar_lay = QHBoxLayout(self.fra_toolbar)
        self.btn_zoom = QPushButton('Zoom')
        self.btn_zoom.clicked.connect(self.btn_zoom_clicked)
        set_up_button(self.btn_zoom, os.path.join(curr_dir, 'i_zoom.png'),
                      True, 13, 13, 'Zoom')  # TODO: softcode

        self.btn_pan = QPushButton('Pan')
        self.btn_pan.clicked.connect(self.btn_pan_clicked)
        set_up_button(self.btn_pan, os.path.join(curr_dir, 'i_pan.png'), True,
                      15, 15, 'Pan')  # TODO: softcode

        self.btn_home = QPushButton('Full extent')
        self.btn_home.clicked.connect(self.btn_home_clicked)

        self.btn_back = QPushButton('Back')
        self.btn_back.clicked.connect(self.btn_back_clicked)
        set_up_button(self.btn_back, os.path.join(curr_dir, 'i_back.png'),
                      False, 7, 13, 'Back')  # TODO: softcode

        self.btn_forth = QPushButton('Forth')
        self.btn_forth.clicked.connect(self.btn_forth_clicked)
        set_up_button(self.btn_forth, os.path.join(curr_dir, 'i_forth.png'),
                      False, 7, 13, 'Forward')  # TODO: softcode

        self.btn_edit = QPushButton('Edit')
        self.btn_edit.clicked.connect(self.btn_edit_clicked)

        fra_toolbar_lay.addWidget(self.btn_zoom)
        fra_toolbar_lay.addWidget(self.btn_pan)
        fra_toolbar_lay.addWidget(self.btn_home)
        fra_toolbar_lay.addWidget(self.btn_back)
        fra_toolbar_lay.addWidget(self.btn_forth)
        fra_toolbar_lay.addWidget(self.btn_edit)

        # Graph canvas
        self.fra_graph = QFrame(self)
        self.static_canvas = SectionCanvas(iface, params, self)

        # Toolbar
        self.toolbar = NavigationToolbar2QT(self.static_canvas, self)
        self.toolbar.hide()

        # OK/Cancel buttons
        self.fra_buttons = QFrame(self)
        fra_buttons_lay = QHBoxLayout(self.fra_buttons)
        self.btn_Cancel = QPushButton('Cancel')
        self.btn_Ok = QPushButton('OK')
        fra_buttons_lay.addWidget(self.btn_Ok)
        fra_buttons_lay.addWidget(self.btn_Cancel)

        main_lay.addWidget(self.fra_toolbar)
        main_lay.addWidget(self.static_canvas)
        main_lay.addWidget(self.fra_buttons)

        self.setup()
        self.initialize()

    def setup(self):

        # Buttons
        self.btn_Cancel.clicked.connect(self.btn_cancel_clicked)
        self.btn_Ok.clicked.connect(self.btn_ok_clicked)

    def initialize(self):

        dem_xz = self.find_raster_distz()
        pipe_xz = self.find_line_distz()
        self.static_canvas.draw_pipe_section(dem_xz, pipe_xz)

    def btn_home_clicked(self):
        self.toolbar.home()

    def btn_zoom_clicked(self):
        self.toolbar.zoom()
        self.btn_pan.setChecked(False)

    def btn_pan_clicked(self):
        self.toolbar.pan()
        self.btn_zoom.setChecked(False)

    def btn_back_clicked(self):
        self.toolbar.back()

    def btn_forth_clicked(self):
        self.toolbar.forward()

    def btn_edit_clicked(self):
        # Deactivate tools
        if self.toolbar._active == "PAN":
            self.toolbar.pan()
        elif self.toolbar._active == "ZOOM":
            self.toolbar.zoom()

    def btn_cancel_clicked(self):
        self.setVisible(False)

    def btn_ok_clicked(self):
        new_zs = self.static_canvas.pipe_line.get_ydata()
        pipe_geom_v2 = self.pipe_ft.geometry().get()
        for p in range(pipe_geom_v2.vertexCount(0, 0)):
            vertex_id = QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex)
            vertex = pipe_geom_v2.vertexAt(vertex_id)
            new_pos_pt = QgsPoint(vertex.x(), vertex.y())
            new_pos_pt.addZValue(new_zs[p])

            LinkHandler.move_link_vertex(self.params, self.params.pipes_vlay,
                                         self.pipe_ft, new_pos_pt, p)

        # Update delta z for nodes
        (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes(
            self.params, self.pipe_ft.geometry())
        start_node_elev = start_node_ft.attribute(Junction.field_name_elev)
        # end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z)
        end_node_elev = end_node_ft.attribute(Junction.field_name_elev)

        start_node_new_deltaz = new_zs[0] - start_node_elev
        end_node_new_deltaz = new_zs[-1] - end_node_elev

        start_node_ft.setAttribute(
            start_node_ft.fieldNameIndex(Junction.field_name_delta_z),
            start_node_new_deltaz)
        end_node_ft.setAttribute(
            end_node_ft.fieldNameIndex(Junction.field_name_delta_z),
            end_node_new_deltaz)

        # Update start node elevation attribute
        start_node_lay = NetworkUtils.find_node_layer(self.params,
                                                      start_node_ft.geometry())
        vector_utils.update_attribute(start_node_lay, start_node_ft,
                                      Junction.field_name_delta_z,
                                      float(start_node_new_deltaz))

        # Update end node elevation attribute
        end_node_lay = NetworkUtils.find_node_layer(self.params,
                                                    end_node_ft.geometry())
        vector_utils.update_attribute(end_node_lay, end_node_ft,
                                      Junction.field_name_delta_z,
                                      float(end_node_new_deltaz))

        # Update pipe length
        # Calculate 3D length
        pipe_geom_2 = self.pipe_ft.geometry()
        if self.params.dem_rlay is not None:
            length_3d = LinkHandler.calc_3d_length(self.params, pipe_geom_2)
        else:
            length_3d = pipe_geom_2.length()
        vector_utils.update_attribute(self.params.pipes_vlay, self.pipe_ft,
                                      Pipe.field_name_length, length_3d)

        self.setVisible(False)

    def find_line_distz(self):

        # Get start and end nodes zs and deltazs from table
        pipe_geom = self.pipe_ft.geometry()
        (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes(
            self.params, self.pipe_ft.geometry())
        start_node_z = start_node_ft.attribute(Junction.field_name_elev)
        if start_node_z == NULL:
            start_node_z = 0
        start_node_deltaz = start_node_ft.attribute(
            Junction.field_name_delta_z)
        end_node_z = end_node_ft.attribute(Junction.field_name_elev)
        if end_node_z == NULL:
            end_node_z = 0
        end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z)

        total_dist = 0
        dist_z = OrderedDict()
        pipe_geom_v2 = pipe_geom.get()
        dist_z[0] = start_node_z + start_node_deltaz

        # Interpolate deltaZs for remaining vertices
        for p in range(1, pipe_geom_v2.vertexCount(0, 0)):
            vertex = pipe_geom_v2.vertexAt(
                QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex))
            vertex_prev = pipe_geom_v2.vertexAt(
                QgsVertexId(0, 0, p - 1, QgsVertexId.SegmentVertex))
            total_dist += math.sqrt((vertex.x() - vertex_prev.x())**2 +
                                    (vertex.y() - vertex_prev.y())**2)

            # Interpolate delta z for vertex using distance from nodes and delta z of nodes
            z = (total_dist / self.pipe_ft.geometry().length() *
                 (end_node_z - start_node_z)) + start_node_z

            # z = raster_utils.read_layer_val_from_coord(self.params.dem_rlay, QgsPoint(vertex.x(), vertex.y()))
            delta_z = (
                total_dist / self.pipe_ft.geometry().length() *
                (end_node_deltaz - start_node_deltaz)) + start_node_deltaz
            dist_z[total_dist] = z + delta_z

        return dist_z

    def find_line_distz3D(self):

        pipe_geom_v2 = self.pipe_ft.geometry().get()

        # Find start and end nodes (needed to know delta z)
        (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes(
            self.params, self.pipe_ft.geometry())
        start_node_deltaz = start_node_ft.attribute(
            Junction.field_name_delta_z)
        end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z)

        total_dist = 0
        dist_z = OrderedDict()
        dist_z[0] = pipe_geom_v2.vertexAt(
            QgsVertexId(0, 0, 0,
                        QgsVertexId.SegmentVertex)).z()  #+ start_node_deltaz

        for p in range(1, pipe_geom_v2.vertexCount(0, 0)):
            vertex = pipe_geom_v2.vertexAt(
                QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex))
            vertex_prev = pipe_geom_v2.vertexAt(
                QgsVertexId(0, 0, p - 1, QgsVertexId.SegmentVertex))
            total_dist += math.sqrt((vertex.x() - vertex_prev.x())**2 +
                                    (vertex.y() - vertex_prev.y())**2)

            # Interpolate delta z for vertex using distance from nodes and delta z of nodes
            delta_z = (
                total_dist / self.pipe_ft.geometry().length() *
                (end_node_deltaz - start_node_deltaz)) + start_node_deltaz
            dist_z[total_dist] = vertex.z()  #+ delta_z

        return dist_z

    def find_raster_distz(self):

        dem_extent = self.params.dem_rlay.extent()
        ul_coord = QgsPoint(dem_extent.xMinimum(), dem_extent.yMaximum())
        x_cell_size = self.params.dem_rlay.rasterUnitsPerPixelX()
        y_cell_size = -self.params.dem_rlay.rasterUnitsPerPixelY()

        points = []

        pipe_pts = self.pipe_ft.geometry().asPolyline()
        for p in range(1, len(pipe_pts)):
            start_col_row = raster_utils.get_col_row(pipe_pts[p - 1], ul_coord,
                                                     x_cell_size, y_cell_size)
            end_col_row = raster_utils.get_col_row(pipe_pts[p], ul_coord,
                                                   x_cell_size, y_cell_size)

            points.extend(
                bresenham.get_line((start_col_row.x, start_col_row.y),
                                   (end_col_row.x, end_col_row.y)))

        total_dist = 0
        dist_z = OrderedDict()
        dist_z[total_dist] = raster_utils.read_layer_val_from_coord(
            self.params.dem_rlay,
            raster_utils.get_coords(points[0][0], points[0][1], ul_coord,
                                    x_cell_size, y_cell_size))

        for p in range(1, len(points)):

            total_dist += math.sqrt((
                (points[p][0] - points[p - 1][0]) * x_cell_size)**2 + (
                    (points[p][1] - points[p - 1][1]) * y_cell_size)**2)
            dist_z[total_dist] = raster_utils.read_layer_val_from_coord(
                self.params.dem_rlay,
                raster_utils.get_coords(points[p][0], points[p][1], ul_coord,
                                        x_cell_size, y_cell_size))

        return dist_z
Ejemplo n.º 3
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()
Ejemplo n.º 4
0
    def initialize_widgets(self):
        """Dynamically set up widgets based on detected files."""
        self.widgets_per_file.clear()
        files_widgets = [
            self.widget_general,
            self.widget_terrain_model,
            self.widget_simple_infiltration,
            self.widget_groundwater,
            self.widget_interflow,
        ]
        files_info_collection = [
            self.general_files,
            self.terrain_model_files,
            self.simple_infiltration_files,
            self.groundwater_files,
            self.interflow_files,
        ]
        for widget in files_widgets:
            widget.hide()

        current_main_layout_row = 1
        for widget, files_info in zip(files_widgets, files_info_collection):
            widget_layout = widget.layout()
            for field_name, name in files_info.items():
                try:
                    file_state = self.detected_files[field_name]
                except KeyError:
                    continue
                status = file_state["status"]
                widget.show()
                name_label = QLabel(name)
                name_label.setSizePolicy(QSizePolicy.Preferred,
                                         QSizePolicy.Preferred)
                widget_layout.addWidget(name_label, current_main_layout_row, 0)

                status_label = QLabel(status.value)
                status_label.setSizePolicy(QSizePolicy.Preferred,
                                           QSizePolicy.Preferred)
                widget_layout.addWidget(status_label, current_main_layout_row,
                                        1)

                empty_label = QLabel()
                widget_layout.addWidget(empty_label, current_main_layout_row,
                                        2)

                no_action_pb_name = "Ignore"
                if status == UploadFileStatus.DELETED_LOCALLY:
                    action_pb_name = "Delete online"
                else:
                    action_pb_name = "Upload"
                # Add valid reference widgets
                all_actions_widget = QWidget()
                actions_sublayout = QGridLayout()
                all_actions_widget.setLayout(actions_sublayout)

                valid_ref_widget = QWidget()
                valid_ref_sublayout = QGridLayout()
                valid_ref_widget.setLayout(valid_ref_sublayout)
                no_action_pb = QPushButton(no_action_pb_name)
                no_action_pb.setCheckable(True)
                no_action_pb.setAutoExclusive(True)
                no_action_pb.clicked.connect(
                    partial(self.toggle_action, field_name, False))

                action_pb = QPushButton(action_pb_name)
                action_pb.setCheckable(True)
                action_pb.setAutoExclusive(True)
                action_pb.setChecked(True)
                action_pb.clicked.connect(
                    partial(self.toggle_action, field_name, True))

                valid_ref_sublayout.addWidget(no_action_pb, 0, 0)
                valid_ref_sublayout.addWidget(action_pb, 0, 1)

                # Add invalid reference widgets
                invalid_ref_widget = QWidget()
                invalid_ref_sublayout = QGridLayout()
                invalid_ref_widget.setLayout(invalid_ref_sublayout)

                filepath_sublayout = QGridLayout()
                filepath_line_edit = QLineEdit()
                filepath_line_edit.setSizePolicy(QSizePolicy.Minimum,
                                                 QSizePolicy.Minimum)
                browse_pb = QPushButton("...")
                browse_pb.clicked.connect(
                    partial(self.browse_for_raster, field_name))
                filepath_sublayout.addWidget(filepath_line_edit, 0, 0)
                filepath_sublayout.addWidget(browse_pb, 0, 1)
                invalid_ref_sublayout.addLayout(filepath_sublayout, 0, 0)

                update_ref_pb = QPushButton("Update reference")
                update_ref_pb.clicked.connect(
                    partial(self.update_raster_reference, field_name))
                invalid_ref_sublayout.addWidget(update_ref_pb, 0, 1)

                actions_sublayout.addWidget(valid_ref_widget, 0, 0)
                actions_sublayout.addWidget(invalid_ref_widget, 0, 1)
                # Add all actions widget into the main widget layout
                widget_layout.addWidget(all_actions_widget,
                                        current_main_layout_row, 2)
                # Hide some of the widgets based on files states
                if status == UploadFileStatus.NO_CHANGES_DETECTED:
                    all_actions_widget.hide()
                elif status == UploadFileStatus.INVALID_REFERENCE:
                    valid_ref_widget.hide()
                else:
                    invalid_ref_widget.hide()
                self.widgets_per_file[field_name] = (
                    name_label,
                    status_label,
                    valid_ref_widget,
                    action_pb,
                    invalid_ref_widget,
                    filepath_line_edit,
                )
                current_main_layout_row += 1