Esempio n. 1
0
    def toSearchRect(self, point):
        size = 20
        rect = QRectF()
        rect.setLeft(point.x() - size)
        rect.setRight(point.x() + size)
        rect.setTop(point.y() - size)
        rect.setBottom(point.y() + size)

        transform = self.canvas.getCoordinateTransform()
        ll = transform.toMapCoordinates(rect.left(), rect.bottom())
        ur = transform.toMapCoordinates(rect.right(), rect.top())

        rect = QgsRectangle(ur, ll)
        return rect
Esempio n. 2
0
class PlotItem(LogItem):

    # emitted when the style is updated
    style_updated = pyqtSignal()

    def __init__(self,
                 size=QSizeF(400, 200),
                 render_type=POINT_RENDERER,
                 x_orientation=ORIENTATION_LEFT_TO_RIGHT,
                 y_orientation=ORIENTATION_UPWARD,
                 allow_mouse_translation=False,
                 allow_wheel_zoom=False,
                 symbology=None,
                 parent=None):

        """
        Parameters
        ----------
        size: QSize
          Size of the item
        render_type: Literal[POINT_RENDERER, LINE_RENDERER, POLYGON_RENDERER]
          Type of renderer
        x_orientation: Literal[ORIENTATION_LEFT_TO_RIGHT, ORIENTATION_RIGHT_TO_LEFT]
        y_orientation: Literal[ORIENTATION_UPWARD, ORIENTATION_DOWNWARD]
        allow_mouse_translation: bool
          Allow the user to translate the item with the mouse (??)
        allow_wheel_zoom: bool
          Allow the user to zoom with the mouse wheel
        symbology: QDomDocument
          QGIS symbology to use for the renderer
        parent: QObject
        """
        LogItem.__init__(self, parent)

        self.__item_size = size
        self.__data_rect = None
        self.__data = None
        self.__delta = None
        self.__x_orientation = x_orientation
        self.__y_orientation = y_orientation

        # origin point of the graph translation, if any
        self.__translation_orig = None

        self.__render_type = render_type # type: Literal[POINT_RENDERER, LINE_RENDERER, POLYGON_RENDERER]

        self.__allow_mouse_translation = allow_mouse_translation
        self.__allow_wheel_zoom = allow_wheel_zoom

        self.__layer = None

        self.__default_renderers = [QgsFeatureRenderer.defaultRenderer(POINT_RENDERER),
                            QgsFeatureRenderer.defaultRenderer(LINE_RENDERER),
                            QgsFeatureRenderer.defaultRenderer(POLYGON_RENDERER)]
        symbol = self.__default_renderers[1].symbol()
        symbol.setWidth(1.0)
        symbol = self.__default_renderers[0].symbol()
        symbol.setSize(5.0)
        symbol = self.__default_renderers[2].symbol()
        symbol.symbolLayers()[0].setStrokeWidth(1.0)

        if not symbology:
            self.__renderer = self.__default_renderers[self.__render_type]
        else:
            self.__renderer = QgsFeatureRenderer.load(symbology.documentElement(), QgsReadWriteContext())

        # index of the current point to label
        self.__old_point_to_label = None
        self.__point_to_label = None

    def boundingRect(self):
        return QRectF(0, 0, self.__item_size.width(), self.__item_size.height())

    def height(self):
        return self.__item_size.height()
    def set_height(self, height):
        self.__item_size.setHeight(height)

    def width(self):
        return self.__item_size.width()
    def set_width(self, width):
        self.__item_size.setWidth(width)

    def set_data_window(self, window):
        """window: QRectF"""
        self.__data_rect = window

    def min_depth(self):
        if self.__data_rect is None:
            return None
        return self.__data_rect.x()
    def max_depth(self):
        if self.__data_rect is None:
            return None
        return (self.__data_rect.x() + self.__data_rect.width())

    def set_min_depth(self, min_depth):
        if self.__data_rect is not None:
            self.__data_rect.setX(min_depth)
    def set_max_depth(self, max_depth):
        if self.__data_rect is not None:
            w = max_depth - self.__data_rect.x()
            self.__data_rect.setWidth(w)

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

    def data_window(self):
        return self.__data_rect

    def set_data(self, x_values, y_values):
        self.__x_values = x_values
        self.__y_values = y_values

        if len(self.__x_values) != len(self.__y_values):
            raise ValueError("X and Y array has different length : "
                             "{} != {}".format(len(self.__x_values),
                                               len(self.__y_values)))

        # Remove None values
        for i in reversed(range(len(self.__y_values))):
            if (self.__y_values[i] is None
                or self.__x_values[i] is None
                or math.isnan(self.__y_values[i])
                or math.isnan(self.__x_values[i])):
                self.__y_values.pop(i)
                self.__x_values.pop(i)
        if not self.__x_values:
            return

        # Initialize data rect to display all data
        # with a 20% buffer around Y values
        min_x = min(self.__x_values)
        max_x = max(self.__x_values)
        min_y = min(self.__y_values)
        max_y = max(self.__y_values)
        if max_y == 0.0:
            max_y = 1.0
        h = max_y - min_y
        min_y -= h * 0.1
        max_y += h * 0.1
        self.__data_rect = QRectF(
            min_x, min_y,
            max_x-min_x, max_y-min_y)

    def renderer(self):
        return self.__renderer

    def paint(self, painter, option, widget):
        self.draw_background(painter)
        if self.__data_rect is None:
            return

        # Look for the first and last X values that fit our rendering rect
        imin_x = bisect.bisect_left(self.__x_values, self.__data_rect.x())
        imax_x = bisect.bisect_right(self.__x_values, self.__data_rect.right())

        # For lines and polygons, retain also one value before the min and one after the max
        # so that lines do not appear truncated
        # Do this only if we have at least one point to render within out rect
        if imin_x > 0 and imin_x < len(self.__x_values) and self.__x_values[imin_x] >= self.__data_rect.x():
            # FIXME add a test to avoid adding a point too "far away" ?
            imin_x -= 1
        if imax_x < len(self.__x_values) - 1 and self.__x_values[imax_x] <= self.__data_rect.right():
            # FIXME add a test to avoid adding a point too "far away" ?
            imax_x += 1

        x_values_slice = np.array(self.__x_values[imin_x:imax_x])
        y_values_slice = np.array(self.__y_values[imin_x:imax_x])

        if len(x_values_slice) == 0:
            return

        # filter points that are not None (nan in numpy arrays)
        n_points = len(x_values_slice)

        if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD:
            if self.__data_rect.width() > 0:
                rw = float(self.__item_size.width()) / self.__data_rect.width()
            else:
                rw = float(self.__item_size.width())
            if self.__data_rect.height() > 0:
                rh = float(self.__item_size.height()) / self.__data_rect.height()
            else:
                rh = float(self.__item_size.height())
            xx = (x_values_slice - self.__data_rect.x()) * rw
            yy = (y_values_slice - self.__data_rect.y()) * rh
        elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT:
            if self.__data_rect.width() > 0:
                rw = float(self.__item_size.height()) / self.__data_rect.width()
            else:
                rw = float(self.__item_size.height())
            if self.__data_rect.height() > 0:
                rh = float(self.__item_size.width()) / self.__data_rect.height()
            else:
                rh = float(self.__item_size.width())
            xx = (y_values_slice - self.__data_rect.y()) * rh
            yy = self.__item_size.height() - (x_values_slice - self.__data_rect.x()) * rw

        if self.__render_type == LINE_RENDERER:
            # WKB structure of a linestring
            #
            #   01 : endianness
            #   02 00 00 00 : WKB type (linestring)
            #   nn nn nn nn : number of points (int32)
            # Then, for each point:
            #   xx xx xx xx xx xx xx xx : X coordinate (float64)
            #   yy yy yy yy yy yy yy yy : Y coordinate (float64)

            wkb = np.zeros(8*2*n_points+9, dtype='uint8')
            wkb[0] = 1 # wkb endianness
            wkb[1] = 2 # linestring
            size_view = np.ndarray(buffer=wkb, dtype='int32', offset=5, shape=(1,))
            size_view[0] = n_points
            coords_view = np.ndarray(buffer=wkb, dtype='float64', offset=9, shape=(n_points,2))
            coords_view[:,0] = xx[:]
            coords_view[:,1] = yy[:]
        elif self.__render_type == POINT_RENDERER:
            # WKB structure of a multipoint
            # 
            #   01 : endianness
            #   04 00 00 00 : WKB type (multipoint)
            #   nn nn nn nn : number of points (int32)
            # Then, for each point:
            #   01 : endianness
            #   01 00 00 00 : WKB type (point)
            #   xx xx xx xx xx xx xx xx : X coordinate (float64)
            #   yy yy yy yy yy yy yy yy : Y coordinate (float64)

            wkb = np.zeros((8*2+5)*n_points+9, dtype='uint8')
            wkb[0] = 1 # wkb endianness
            wkb[1] = 4 # multipoint
            size_view = np.ndarray(buffer=wkb, dtype='int32', offset=5, shape=(1,))
            size_view[0] = n_points
            coords_view = np.ndarray(buffer=wkb, dtype='float64', offset=9+5, shape=(n_points,2), strides=(16+5,8))
            coords_view[:,0] = xx[:]
            coords_view[:,1] = yy[:]
            # header of each point
            h_view = np.ndarray(buffer=wkb, dtype='uint8', offset=9, shape=(n_points,2), strides=(16+5,1))
            h_view[:,0] = 1 # endianness
            h_view[:,1] = 1 # point

        elif self.__render_type == POLYGON_RENDERER:
            # WKB structure of a polygon
            # 
            #   01 : endianness
            #   03 00 00 00 : WKB type (polygon)
            #   01 00 00 00 : Number of rings (always 1 here)
            #   nn nn nn nn : number of points (int32)
            # Then, for each point:
            #   xx xx xx xx xx xx xx xx : X coordinate (float64)
            #   yy yy yy yy yy yy yy yy : Y coordinate (float64)
            #
            # We add two additional points to close the polygon

            wkb = np.zeros(8*2*(n_points+2)+9+4, dtype='uint8')
            wkb[0] = 1 # wkb endianness
            wkb[1] = 3 # polygon
            wkb[5] = 1 # number of rings
            size_view = np.ndarray(buffer=wkb, dtype='int32', offset=9, shape=(1,))
            size_view[0] = n_points+2
            coords_view = np.ndarray(buffer=wkb, dtype='float64', offset=9+4, shape=(n_points,2))
            coords_view[:,0] = xx[:]
            coords_view[:,1] = yy[:]
            # two extra points
            extra_coords = np.ndarray(buffer=wkb, dtype='float64', offset=8*2*n_points+9+4, shape=(2,2))
            if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD:
                extra_coords[0,0] = coords_view[-1,0]
                extra_coords[0,1] = 0.0
                extra_coords[1,0] = coords_view[0,0]
                extra_coords[1,1] = 0.0
            elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT:
                extra_coords[0,0] = 0.0
                extra_coords[0,1] = coords_view[-1,1]
                extra_coords[1,0] = 0.0
                extra_coords[1,1] = coords_view[0,1]

        # build a geometry from the WKB
        # since numpy arrays have buffer protocol, sip is able to read it
        geom = QgsGeometry()
        geom.fromWkb(wkb.tobytes())

        painter.setClipRect(0, 0, self.__item_size.width(), self.__item_size.height())

        fields = QgsFields()
        #fields.append(QgsField("", QVariant.String))
        feature = QgsFeature(fields, 1)
        feature.setGeometry(geom)

        context = qgis_render_context(painter, self.__item_size.width(), self.__item_size.height())
        context.setExtent(QgsRectangle(0, 1, self.__item_size.width(), self.__item_size.height()))

        self.__renderer.startRender(context, fields)
        self.__renderer.renderFeature(feature, context)
        self.__renderer.stopRender(context)

        if self.__point_to_label is not None:
            i = self.__point_to_label
            x, y = self.__x_values[i], self.__y_values[i]
            if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD:
                px = (x - self.__data_rect.x()) * rw
                py = self.__item_size.height() - (y - self.__data_rect.y()) * rh
            elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT:
                px = (y - self.__data_rect.y()) * rh
                py = (x - self.__data_rect.x()) * rw
            painter.drawLine(px-5, py, px+5, py)
            painter.drawLine(px, py-5, px, py+5)

    def mouseMoveEvent(self, event):
        if not self.__x_values:
            return
        if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD:
            xx = (event.scenePos().x() - self.pos().x()) / self.width() * self.__data_rect.width() + self.__data_rect.x()
        elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT:
            xx = (event.scenePos().y() - self.pos().y()) / self.height() * self.__data_rect.width() + self.__data_rect.x()
        i = bisect.bisect_left(self.__x_values, xx)
        if i >= 0 and i < len(self.__x_values):
            # switch the attached point when we are between two points
            if i > 0 and (xx - self.__x_values[i-1]) < (self.__x_values[i] - xx):
                i -= 1
            self.__point_to_label = i
        else:
            self.__point_to_label = None
        if self.__point_to_label != self.__old_point_to_label:
            self.update()
        if self.__point_to_label is not None:
            x, y = self.__x_values[i], self.__y_values[i]
            if self.__x_orientation == ORIENTATION_LEFT_TO_RIGHT and self.__y_orientation == ORIENTATION_UPWARD:
                dt = datetime.fromtimestamp(x, UTC())
                txt = "Time: {} Value: {}".format(dt.strftime("%x %X"), y)
            elif self.__x_orientation == ORIENTATION_DOWNWARD and self.__y_orientation == ORIENTATION_LEFT_TO_RIGHT:
                txt = "Depth: {} Value: {}".format(x, y)
            self.tooltipRequested.emit(txt)

        self.__old_point_to_label = self.__point_to_label

    def edit_style(self):
        from qgis.gui import QgsSingleSymbolRendererWidget
        from qgis.core import QgsStyle

        style = QgsStyle()
        sw = QStackedWidget()
        sw.addWidget
        for i in range(3):
            if self.__renderer and i == self.__render_type:
                w = QgsSingleSymbolRendererWidget(self.__layer, style, self.__renderer)
            else:
                w = QgsSingleSymbolRendererWidget(self.__layer, style, self.__default_renderers[i])
            sw.addWidget(w)

        combo = QComboBox()
        combo.addItem("Points")
        combo.addItem("Line")
        combo.addItem("Polygon")

        combo.currentIndexChanged[int].connect(sw.setCurrentIndex)
        combo.setCurrentIndex(self.__render_type)

        dlg = QDialog()

        vbox = QVBoxLayout()

        btn = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        btn.accepted.connect(dlg.accept)
        btn.rejected.connect(dlg.reject)

        vbox.addWidget(combo)
        vbox.addWidget(sw)
        vbox.addWidget(btn)

        dlg.setLayout(vbox)
        dlg.resize(800, 600)

        r = dlg.exec_()

        if r == QDialog.Accepted:
            self.__render_type = combo.currentIndex()
            self.__renderer = sw.currentWidget().renderer().clone()
            self.update()
            self.style_updated.emit()

    def qgis_style(self):
        """Returns the current style, as a QDomDocument"""
        from PyQt5.QtXml import QDomDocument
        from qgis.core import QgsReadWriteContext

        doc = QDomDocument()
        elt = self.__renderer.save(doc, QgsReadWriteContext())
        doc.appendChild(elt)
        return (doc, self.__render_type)
Esempio n. 3
0
    def paint_MODEL_SPACE_2D(self):
        rect = self.rect()
        painter = QPainter(self)
        painter.fillRect(rect, self.plugIn.canvas.canvasColor())
        painter.setRenderHint(QPainter.Antialiasing)

        # PICKBOX
        x1 = rect.width() / 3
        y1 = rect.height() - rect.height() / 3
        color = QColor(
            self.tempQadVariables.get(
                QadMsg.translate("Environment variables", "PICKBOXCOLOR")))
        pickSize = 5
        painter.setPen(QPen(color, 1, Qt.SolidLine))
        painter.drawRect(x1 - pickSize, y1 - pickSize, 2 * pickSize,
                         2 * pickSize)

        # CROSSHAIRS
        color = QColor(
            self.tempQadVariables.get(
                QadMsg.translate("Environment variables", "CURSORCOLOR")))
        cursorSize = 20
        painter.setPen(QPen(color, 1, Qt.SolidLine))
        painter.drawLine(x1 - pickSize, y1, x1 - pickSize - cursorSize, y1)
        painter.drawLine(x1 + pickSize, y1, x1 + pickSize + cursorSize, y1)
        painter.drawLine(x1, y1 - pickSize, x1, y1 - pickSize - cursorSize)
        painter.drawLine(x1, y1 + pickSize, x1, y1 + pickSize + cursorSize)

        # AUTOTRECK_VECTOR
        x1 = rect.width() / 3
        color = QColor(
            self.tempQadVariables.get(
                QadMsg.translate("Environment variables",
                                 "AUTOTRECKINGVECTORCOLOR")))
        painter.setPen(QPen(color, 1, Qt.DashLine))
        painter.drawLine(x1, 0, x1, rect.height())
        painter.drawLine(x1 + rect.height() * 2 / 3, 0, x1 - rect.height() / 3,
                         rect.height())

        # AUTOSNAP
        x1 = rect.width() / 3
        y1 = rect.height() / 3
        color = QColor(
            self.tempQadVariables.get(
                QadMsg.translate("Environment variables", "AUTOSNAPCOLOR")))
        pickSize = 5
        painter.setPen(QPen(color, 2, Qt.SolidLine))
        painter.drawRect(x1 - pickSize, y1 - pickSize, 2 * pickSize,
                         2 * pickSize)

        # DYNAMIC INPUT
        x1 = rect.width() / 3
        y1 = rect.height() - rect.height() / 3
        cursorSize = 20
        fMetrics = painter.fontMetrics()
        msg1 = "12.3456"
        sz1 = fMetrics.size(Qt.TextSingleLine, msg1 + "__")
        dynInputRect1 = QRectF(x1 + cursorSize, y1 + cursorSize, sz1.width(),
                               sz1.height() + 2)
        msg2 = "78.9012"
        sz2 = fMetrics.size(Qt.TextSingleLine, msg2 + "__")
        dynInputRect2 = QRectF(dynInputRect1.right() + sz1.height() / 3,
                               dynInputRect1.top(), sz2.width(),
                               sz2.height() + 2)
        # DYNAMIC INPUT COMMAND DESCR BACKGROUND
        color = QColor(
            self.tempQadVariables.get(
                QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR")))
        painter.fillRect(dynInputRect1, color)
        painter.fillRect(dynInputRect2, color)
        # DYNAMIC INPUT COMMAND DESCR BORDER
        color = QColor(
            self.tempQadVariables.get(
                QadMsg.translate("Environment variables",
                                 "DYNEDITBORDERCOLOR")))
        painter.setPen(QPen(color, 1, Qt.SolidLine))
        painter.drawRect(dynInputRect1)
        painter.drawRect(dynInputRect2)
        # DYNAMIC INPUT COMMAND DESCR FOREGROUND
        color = QColor(
            self.tempQadVariables.get(
                QadMsg.translate("Environment variables", "DYNEDITFORECOLOR")))
        painter.setPen(QPen(color, 1, Qt.SolidLine))
        painter.drawText(dynInputRect1, msg1)
        painter.drawText(dynInputRect2, msg2)