예제 #1
0
class Imageobj(QGraphicsView):
    leftMouseButtonPressed = pyqtSignal(float, float)
    rightMouseButtonPressed = pyqtSignal(float, float)
    leftMouseButtonReleased = pyqtSignal(float, float)
    rightMouseButtonReleased = pyqtSignal(float, float)
    leftMouseButtonDoubleClicked = pyqtSignal(float, float)
    rightMouseButtonDoubleClicked = pyqtSignal(float, float)

    def __init__(self):
        QGraphicsView.__init__(self)

        # Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView.
        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        self.rectgroup = QGraphicsItemGroup()
        self.linegroup = QGraphicsItemGroup()
        self._pixmapHandle = None
        self.canZoom = True
        self.canPan = True
        self.zoomStack = []
        self.aspectRatioMode = Qt.KeepAspectRatio
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.begin = QPoint()
        self.end = QPoint()
        self.helper_bool = False
        self.helper_bool2 = False

        self.rect = QGraphicsRectItem()
        self.line = QGraphicsLineItem()

    def hasImage(self):
        """ Returns whether or not the scene contains an image pixmap.
        """
        return self._pixmapHandle is not None

    def pixmap(self):
        """ Returns the scene's current image pixmap as a QPixmap, or else None if no image exists.
        :rtype: QPixmap | None
        """
        if self.hasImage():
            return self._pixmapHandle.pixmap()
        return None

    def image(self):
        """ Returns the scene's current image pixmap as a QImage, or else None if no image exists.
        :rtype: QImage | None
        """
        if self.hasImage():
            return self._pixmapHandle.pixmap().toImage()
        return None

    def setImage(self, image):
        """ Set the scene's current image pixmap to the input QImage or QPixmap.
        Raises a RuntimeError if the input image has type other than QImage or QPixmap.
        :type image: QImage | QPixmap
        """
        if type(image) is QPixmap:
            pixmap = image
        elif type(image) is QImage:
            pixmap = QPixmap.fromImage(image)
        else:
            raise RuntimeError(
                "ImageViewer.setImage: Argument must be a QImage or QPixmap.")
        if self.hasImage():
            self._pixmapHandle.setPixmap(pixmap)
        else:
            self._pixmapHandle = self.scene.addPixmap(pixmap)
        self.setSceneRect(QRectF(
            pixmap.rect()))  # Set scene size to image size.
        self.updateViewer()

    def loadImageFromFile(self, fileName=""):
        """ Load an image from file.
        Without any arguments, loadImageFromFile() will popup a file dialog to choose the image file.
        With a fileName argument, loadImageFromFile(fileName) will attempt to load the specified image file directly.
        """
        fileDir = os.path.dirname(os.path.realpath('__file__'))
        results_dir = os.path.join(fileDir,
                                   'results\\200_frames_of_bloodflow_results')
        if len(fileName) == 0:
            if QT_VERSION_STR[0] == '4':
                fileName = QFileDialog.getOpenFileName(self,
                                                       "Open image file.")
            elif QT_VERSION_STR[0] == '5':
                fileName, dummy = QFileDialog.getOpenFileName(
                    self, "Open image file.")
        if len(fileName) and os.path.isfile(fileName):
            imageName, file_data = self.create_image(fileName, results_dir)
            image = QImage(imageName)
            self.setImage(image)
        print(fileName)

    def Extract_image_and_metadata(self, file_data):
        global file_metadata

        im2show_path = file_data["im2show_path"]
        metadata_path = file_data["metadata_path"]
        # fileName = file_data["fileName"]
        filepath = file_data["path"]
        if os.path.isfile(metadata_path):
            with open(metadata_path, 'r') as fp:
                file_metadata = json.load(fp)
        else:
            with tifffile.TiffFile(filepath) as tif:
                file_metadata = tif.scanimage_metadata
                with open(metadata_path, 'w') as fp:
                    json.dump(file_metadata, fp)

        if not os.path.isfile(im2show_path):
            tif_original = tifffile.imread(filepath)
            # first_five = tif_full_original[1:10:2]#selecting 5 image from tif, only vessels
            tif_original_size = tif_original.shape
            if len(tif_original_size) == 2:
                tif = tif_original.copy()
                arr = tif
            else:
                first_five = tif_original[1:10:2].copy()
                if file_metadata["SI.hScan2D.bidirectional"]:
                    num_im, w, h = first_five.shape
                    tif = np.zeros((num_im, int(h / 2), w))
                for i in range(num_im):
                    tif[i] = first_five[i][::2]
                else:
                    tif = tif_original.copy()
                arr = tif[0]
            tif_size = tif.shape
            print(tif_size)
            # image5 = (tif[0]/5)+(tif[1]/5)+(tif[2]/5)+(tif[3]/5)+(tif[4]/5)
            # arr_avg=np.array(np.round(image5),dtype=np.int16) #round the pixels values
            # arr = tif[0]
            im2show = Image.fromarray(arr)
            try:
                im2show.save(im2show_path)
            except OSError:
                im2show = im2show.convert(mode='I')
                im2show.save(im2show_path)

        return file_metadata

    def updateViewer(self):
        """ Show current zoom (if showing entire image, apply current aspect ratio mode).
        """
        if not self.hasImage():
            return
        if len(self.zoomStack) and self.sceneRect().contains(
                self.zoomStack[-1]):
            self.fitInView(self.zoomStack[-1], Qt.IgnoreAspectRatio
                           )  # Show zoomed rect (ignore aspect ratio).
        else:
            self.zoomStack = [
            ]  # Clear the zoom stack (in case we got here because of an invalid zoom).
            self.fitInView(
                self.sceneRect(), self.aspectRatioMode
            )  # Show entire image (use current aspect ratio mode).

    def resizeEvent(self, event):
        """ Maintain current zoom on resize.
        """
        self.updateViewer()

    def mousePressEvent(self, event):
        """ Start mouse pan or zoom mode.
        """

        scenePos = self.mapToScene(event.pos())
        if event.button() == Qt.LeftButton:
            if self.canPan and self.helper_bool:
                self.setDragMode(QGraphicsView.RubberBandDrag)
            elif self.canPan and self.helper_bool2:
                self.setDragMode(QGraphicsView.ScrollHandDrag)
                self.start = scenePos
                #QGraphicsView.mouseMoveEvent(self,event)
                #self.setMouseTracking(True)
            self.leftMouseButtonPressed.emit(scenePos.x(), scenePos.y())
        # self.cursorStartPosition = self.leftMouseButtonPressed.emit(scenePos.x(), scenePos.y())
        # self.start = QPoint(self.cursorStartPosition.x(),self.cursorStartPosition.y())
        elif event.button() == Qt.RightButton:
            if self.canZoom:
                self.setDragMode(QGraphicsView.RubberBandDrag)
            self.rightMouseButtonPressed.emit(scenePos.x(), scenePos.y())
        QGraphicsView.mousePressEvent(self, event)

    def mouseReleaseEvent(self, event):
        """ Stop mouse pan or zoom mode (apply zoom if valid).
        """

        QGraphicsView.mouseReleaseEvent(self, event)
        scenePos = self.mapToScene(event.pos())
        if event.button() == Qt.LeftButton:
            if self.helper_bool:
                viewBBox = self.zoomStack[-1] if len(
                    self.zoomStack) else self.sceneRect()
                selectionBBox = self.scene.selectionArea().boundingRect(
                ).intersected(viewBBox)
                if selectionBBox.isValid():

                    self.rect.setRect(selectionBBox)
                    self.rect.setPen(QPen(Qt.white))
                    self.scene.addItem(self.rect)

            elif self.helper_bool2:
                viewBBox = self.zoomStack[-1] if len(
                    self.zoomStack) else self.sceneRect()
                self.cursorCurrentPosition = scenePos
                self.current = QPointF(self.cursorCurrentPosition.x(),
                                       self.cursorCurrentPosition.y())
                pen = QPen(Qt.red, 1, Qt.SolidLine)

                self.line.setLine(QLineF(self.start, self.current))
                self.line.setPen(pen)
                self.scene.addItem(self.line)

            self.setDragMode(QGraphicsView.NoDrag)
            self.leftMouseButtonReleased.emit(scenePos.x(), scenePos.y())

        elif event.button() == Qt.RightButton:
            if self.canZoom:
                viewBBox = self.zoomStack[-1] if len(
                    self.zoomStack) else self.sceneRect()
                selectionBBox = self.scene.selectionArea().boundingRect(
                ).intersected(viewBBox)
                self.scene.setSelectionArea(
                    QPainterPath())  # Clear current selection area.
                if selectionBBox.isValid() and (selectionBBox != viewBBox):
                    self.zoomStack.append(selectionBBox)
                    self.updateViewer()
            self.setDragMode(QGraphicsView.NoDrag)
            self.rightMouseButtonReleased.emit(scenePos.x(), scenePos.y())
        # self.scene.addItem(self.group)
        self.updateViewer()

    def mouseDoubleClickEvent(self, event):
        """ Show entire image.
        """

        scenePos = self.mapToScene(event.pos())
        if event.button() == Qt.LeftButton:

            self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
        elif event.button() == Qt.RightButton:
            if self.canZoom:
                self.zoomStack = []  # Clear zoom stack.
                self.updateViewer()
            self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
        QGraphicsView.mouseDoubleClickEvent(self, event)

    def approve_obj(self):
        self.scene.removeItem(self.rect)
        self.scene.removeItem(self.line)

        viewBBox = self.zoomStack[-1] if len(
            self.zoomStack) else self.sceneRect()
        selectionBBox = self.scene.selectionArea().boundingRect().intersected(
            viewBBox)
        rect = QGraphicsRectItem()
        rect.setRect(selectionBBox)
        rect.setPen(QPen(Qt.green))
        self.rectgroup.addToGroup(rect)
        self.scene.addItem(self.rectgroup)

        line = QGraphicsLineItem()
        line.setLine(QLineF(self.start, self.current))
        line.setPen(QPen(Qt.green))
        self.linegroup.addToGroup(line)
        self.scene.addItem(self.linegroup)

        return (selectionBBox, self.start, self.current)

    def create_fromDB(self, pos_list=[], obj_type=""):
        if obj_type == "rois":
            num_rois = len(pos_list)
            for i in range(num_rois):
                points = pos_list[i]
                rect = QGraphicsRectItem()
                rect.setPen(QPen(Qt.green))
                rect.setRect(points[0], points[1], points[2], points[3])
                self.rectgroup.addToGroup(rect)
            self.scene.addItem(self.rectgroup)
        elif obj_type == "vector":
            num_vec = len(pos_list)
            for i in range(num_vec):
                points = pos_list[i]
                vec = QGraphicsLineItem()
                vec.setPen(QPen(Qt.green))
                vec.setLine(points[0][0], points[0][1], points[1][0],
                            points[1][1])
                self.linegroup.addToGroup(vec)
            self.scene.addItem(self.linegroup)

    def mark_item(self, item_num):
        rect_items = self.rectgroup.childItems()
        line_items = self.linegroup.childItems()
        rect_item = rect_items[item_num]
        line_item = line_items[item_num]
        rect_item.setPen(QPen(Qt.red))
        line_item.setPen(QPen(Qt.red))
        rect_items.remove(rect_item)
        line_items.remove(line_item)
        for i in rect_items:
            i.setPen(QPen(Qt.green))
        for i in line_items:
            i.setPen(QPen(Qt.green))

    def delete_roi(self, num):
        self.scene.removeItem(self.rectgroup)
        self.scene.removeItem(self.linegroup)
        rect_item = self.rectgroup.childItems()[num]
        line_item = self.linegroup.childItems()[num]
        self.rectgroup.removeFromGroup(rect_item)
        self.linegroup.removeFromGroup(line_item)
        self.scene.addItem(self.rectgroup)
        self.scene.addItem(self.linegroup)

    def restart_obj(self):
        self.scene.removeItem(self.rectgroup)
        self.scene.removeItem(self.linegroup)
        rect_items = self.rectgroup.childItems()
        line_items = self.linegroup.childItems()
        for rect_item in rect_items:
            self.rectgroup.removeFromGroup(rect_item)
        for line_item in line_items:
            self.linegroup.removeFromGroup(line_item)
예제 #2
0
class Node(QGraphicsItemGroup):
    """
    Parent class for all types of nodes.

    It contains all necessary functions to print a node in the GUI
    """

    def __init__(self, node, parent, background, border, text, x=0, y=0, offset=20):
        """
        Constructor for the node class.
        It generates all necessary variables and calls the draw function

        :param node: data node which it gets the data from
        :param parent: parent widget
        :param background: background color of the node
        :param border: border color for the node
        :param text: text color for the node
        :param x: x-position of the node
        :param y: y-position of the node
        :param offset: offset for the type to center it
        """
        super().__init__()

        self.typeOffset = offset

        self.node = node

        self.parent = parent

        self.threatEdge = None
        self.counterEdge = None
        self.defaultEdge = None

        self.childEdges = []
        self.parentEdges = []

        self.headerGroup = QGraphicsItemGroup()
        self.attributes = QGraphicsItemGroup()
        self.footerGroup = QGraphicsItemGroup()

        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)

        self.attributesHeight = 0
        self.headerHeight = 0

        self.printHeader(background, border, text)
        self.printAttributes(background, border, text)
        self.printFooter(background, border, text)

        self.setAcceptDrops(True)

        self.setPos(x, y)

    def getTypeRecursiveDown(self):
        """
        Searches the children of a node to get a node with type != Conjunction
        If there is no other node with type != Conjunction, Conjunction will be returned
        :return: type of first child node with type != Conjunction or Conjunction
        """
        if isinstance(self, Conjunction):
            for c in self.childEdges:
                if isinstance(c.dst, Conjunction):
                    return c.dst.getTypeRecursiveDown()
                else:
                    return type(c.dst)
            return type(self)
        else:
            return type(self)

    def getTypeRecursiveUp(self):
        """
        Searches the parents of a node to get a node with type != Conjunction
        If there is no other node with type != Conjunction, Conjunction will be returned
        :return: type of first parent node with type != Conjunction or Conjunction
        """
        if isinstance(self, Conjunction):
            for c in self.childEdges:
                if isinstance(c.dst, Conjunction):
                    return c.dst.getTypeRecursiveUp()
                else:
                    return type(c.dst)
            return type(self)
        else:
            return type(self)

    def addEdge(self, dst):
        """
        Adds an child edge to this node an places the start of the arrow in the right place
        :param dst: destination node for the edge
        """
        if isinstance(self, Threat) and dst.getTypeRecursiveDown() is Threat:
            edge = Edge(self, dst, -50)
        elif isinstance(self, Threat) and dst.getTypeRecursiveDown() is Countermeasure:
            edge = Edge(self, dst, 50)
        else:
            edge = Edge(self, dst, 0)
        self.parent.scene.addItem(edge)
        self.childEdges.append(edge)
        dst.parentEdges.append(self.childEdges[-1])

    def actualizeEdges(self):
        """
        Actualizes all child edges of this node so they start at the right position
        """
        for e in self.childEdges:
            if isinstance(self, Threat) and e.dst.getTypeRecursiveDown() is Threat:
                e.offset = -50
            elif isinstance(self, Threat) and e.dst.getTypeRecursiveDown() is Countermeasure:
                e.offset = 50
            else:
                e.offset = 0

    def fixParentEdgeRec(self):
        """
        Fixes all starts of the parent edges so they start at the right position
        """
        for p in self.parentEdges:
            if isinstance(p.start, Conjunction):
                p.start.fixParentEdgeRec()
            else:
                p.start.actualizeEdges()

    def getLeftRightChildren(self):
        """
        Splits the children in to arrays with the same size
        :return: Tuple (left, right) with child elements split in to arrays
        """
        left = []
        right = []
        neutralLeft = False
        for e in self.childEdges:
            if e.offset < 0:
                left.append(e.dst)
            elif e.offset > 0:
                right.append(e.dst)
            else:
                if neutralLeft is False:
                    left.append(e.dst)
                    neutralLeft = True
                else:
                    right.append(e.dst)
                    neutralLeft = False
        return left, right

    def printHeader(self, background, border, text):
        """
        Prints the the header of the node.
        It contains the Node id, title and type

        :param background: background color of the node
        :param border: border color for the node
        :param text: text color for the node
        """
        x = self.x()
        y = self.y()

        self.idText = QGraphicsTextItem()
        self.typeText = QGraphicsTextItem()
        self.titleText = QGraphicsTextItem()

        self.idRect = QGraphicsRectItem()
        self.typeRect = QGraphicsRectItem()
        self.titleRect = QGraphicsRectItem()

        self.typeText.setFont(Configuration.font)
        self.titleText.setFont(Configuration.font)
        self.idText.setFont(Configuration.font)

        self.typeText.setDefaultTextColor(QColor(text))
        self.titleText.setDefaultTextColor(QColor(text))
        self.idText.setDefaultTextColor(QColor(text))

        self.titleText.setTextWidth(200)

        self.idText.setPlainText(self.node.id)
        self.typeText.setPlainText(type(self.node).__name__)
        self.titleText.setPlainText(self.node.title)

        titleHeight = int(self.titleText.boundingRect().height() / 20 + 0.5) * 20

        self.idRect.setRect(x, y, 50, 20)
        self.typeRect.setRect(x + 50, y, 150, 20)
        self.titleRect.setRect(x, y + 20, 200, titleHeight)

        self.idRect.setBrush(QBrush(QColor(background)))
        self.typeRect.setBrush(QBrush(QColor(background)))
        self.titleRect.setBrush(QBrush(QColor(background)))

        self.idRect.setPen(QPen(QColor(border), 2))
        self.typeRect.setPen(QPen(QColor(border), 2))
        self.titleRect.setPen(QPen(QColor(border), 2))

        self.idText.setPos(x, y - 2)
        self.typeText.setPos(x + self.typeOffset, y - 2)
        self.titleText.setPos(x, y + 18)

        self.headerHeight = titleHeight + 20

        self.setToolTip(self.node.description)

        self.headerGroup.addToGroup(self.idRect)
        self.headerGroup.addToGroup(self.typeRect)
        self.headerGroup.addToGroup(self.titleRect)
        self.headerGroup.addToGroup(self.idText)
        self.headerGroup.addToGroup(self.typeText)
        self.headerGroup.addToGroup(self.titleText)
        self.addToGroup(self.headerGroup)

    def printAttributes(self, background, border, text):
        """
        Prints the attributes of the node
        The attributes are a key, value pair

        :param background: background color of the node
        :param border: border color for the node
        :param text: text color for the node
        """
        y = self.y() + self.headerHeight
        x = self.x()

        self.attributesHeight = 0

        for k, v in self.node.attributes.items():
            key = QGraphicsTextItem()
            key.setFont(Configuration.font)
            key.setDefaultTextColor(QColor(text))
            key.setTextWidth(100)
            key.setPlainText(k)
            keyHeight = int(key.boundingRect().height() / 20 + 0.5) * 20

            value = QGraphicsTextItem()
            value.setFont(Configuration.font)
            value.setDefaultTextColor(QColor(text))
            value.setTextWidth(100)
            value.setPlainText(v)
            valueHeight = int(value.boundingRect().height() / 20 + 0.5) * 20

            height = valueHeight if valueHeight > keyHeight else keyHeight

            keyRect = QGraphicsRectItem()
            keyRect.setRect(x, y, 100, height)
            valueRect = QGraphicsRectItem()
            valueRect.setRect(x + 100, y, 100, height)

            keyRect.setBrush(QBrush(QColor(background)))
            valueRect.setBrush(QBrush(QColor(background)))

            keyRect.setPen(QPen(QColor(border), 2))
            valueRect.setPen(QPen(QColor(border), 2))

            key.setPos(x, y - 2)
            value.setPos(x + 100, y - 2)

            self.attributes.addToGroup(keyRect)
            self.attributes.addToGroup(valueRect)
            self.attributes.addToGroup(key)
            self.attributes.addToGroup(value)

            y = y + height
            self.attributesHeight += height

        self.addToGroup(self.attributes)

    def redrawOptions(self, background, border, text):
        """
        Redraws the node with option for the background, border and text color

        :param background: background color of the node
        :param border: border color for the node
        :param text: text color for the node
        """
        y = self.y()
        x = self.x()

        self.prepareGeometryChange()

        for i in self.attributes.childItems():
            self.attributes.removeFromGroup(i)
            self.parent.scene.removeItem(i)

        for i in self.headerGroup.childItems():
            self.headerGroup.removeFromGroup(i)
            self.parent.scene.removeItem(i)

        for i in self.footerGroup.childItems():
            self.footerGroup.removeFromGroup(i)
            self.parent.scene.removeItem(i)

        self.removeFromGroup(self.attributes)
        self.removeFromGroup(self.footerGroup)
        self.removeFromGroup(self.headerGroup)

        self.printHeader(background, border, text)

        self.printAttributes(background, border, text)

        self.printFooter(background, border, text)

        self.parent.scene.removeItem(self)
        self.parent.scene.addItem(self)

        self.setPos(x, y)

    def redraw(self):
        """
        Redraws the node with standard colors
        """
        self.redrawOptions(Qt.white, Qt.black, Qt.black)

    def printFooter(self, background, border, text):
        """
        Prototype function for the footer.
        Implemented in the child classes

        :param background: background color of the node
        :param border: border color for the node
        :param text: text color for the node
        """
        pass

    def setPos(self, x, y):
        """
        Overloads setPos to set the position of the visible node in the data node

        :param x: X part of the position
        :param y: Y part of the position
        """
        self.node.position = (x, y)
        super().setPos(x, y)

    def paint(self, painter, options, widget=None):
        """
        Reimplementation for the paint function of the QGraphicsItemGroup.
        The Reimplementation is needed to print a proper border when the item is selected

        :param painter: The painter, which draws the node
        :param options: options for the paint job
        :param widget: widget of the Item
        """
        myOption = QStyleOptionGraphicsItem(options)
        myOption.state &= ~QStyle.State_Selected

        super().paint(painter, myOption, widget=None)

        if options.state & QStyle.State_Selected:
            painter.setPen(QPen(Qt.black, 2, Qt.DotLine))
            rect = QRect(self.boundingRect().x() - 1.5, self.boundingRect().y() - 1.5,
                         self.boundingRect().x() + self.boundingRect().width() + 3,
                         self.boundingRect().y() + self.boundingRect().height() + 0.5)
            painter.drawRect(rect)

    def selectChildren(self):
        """
        Select all children
        """
        self.setSelected(True)
        for i in self.childEdges:
            i.setSelected(True)
            i.dst.selectChildren()

    def delete(self):
        """
        Deletes this node
        """
        for e in self.parentEdges:
            self.parent.scene.removeItem(e)
        for e in self.childEdges:
            self.parent.scene.removeItem(e)
        self.parent.tree.removeNode(self.node.id)
        self.parent.scene.removeItem(self)

    def edit(self):
        """
        Opens the edit dialog
        """
        NodeEdit(self, self.parent).exec()

    def mouseDoubleClickEvent(self, event):
        """
        Handles a double click on the node.
        The double click opens the edit window for this node

        :param event: click event
        """
        self.edit()

    def dropEvent(self, event):
        """
        Sets the correct position to the data node if the item is drag & dropped

        :param event: Drop event
        :return: Changed Value
        """

        print("ok")
        self.node.position = (event.pos().x(), event.pos().y())
        return super().dropEvent(self, event)
예제 #3
0
파일: nodeitems.py 프로젝트: bucaneer/flint
class NodeItem(QGraphicsItem):
    def __init__ (self, nodeobj, parent=None, view=None, state=1):
        super().__init__()
        self.edge = None
        self.linkIDs = None
        self.children = None
        self.childpos = None
        self.nodeobj = nodeobj
        self.style = FlGlob.mainwindow.style
        self.view = weakref.proxy(view)
        self.refID = parent.realid() if parent is not None else None
        self.state = state
        self.setrank(parent)
        self.setCursor(Qt.ArrowCursor)
        self.yoffset = 0
        self.graphicsetup()
        self.setstate(state)
    
    def id (self):
        return (self.refID, self.nodeobj.ID)
    
    def realid (self):
        return self.nodeobj.ID
    
    def childlist (self, generate=False):
        ID = self.nodeobj.ID
        itemtable = self.view.itemtable
        if self.state == 1 and ID in itemtable and not self.iscollapsed():
            if self.children and self.nodeobj.linkIDs == self.linkIDs and None not in [c() for c in self.children]:
                ret = self.children
            else:
                children = []
                for child in self.nodeobj.linkIDs:
                    if child in itemtable[ID]:
                        item = itemtable[ID][child]
                    else:
                        continue
                    children.append(weakref.ref(item))
                self.linkIDs = self.nodeobj.linkIDs.copy()
                self.children = children
                ret = children
        else:
            ret = []
        if generate:
            x = self.x()
            y = self.y()
            self.childpos = []
            for target in ret:
                t = target()
                self.childpos.append((t.x()+t.boundingRect().left()-self.style.activemargin-x, t.y()-y))
            if self.edge:
                if self.childpos != self.edge.childpos:
                    self.edge.prepareGeometryChange()
                    self.edge.sourceright = self.boundingRect().right()
                    self.edge.update(self.edge.boundingRect())
        return ret
    
    def setedge (self, edge):
        self.edge = edge
        edge.setX(self.x())
    
    def setactive (self, active):
        if active:
            self.activebox.show()
            self.mainbox.setBrush(QBrush(self.altcolor))
        else:
            self.activebox.hide()
            self.mainbox.setBrush(QBrush(self.maincolor))
    
    def setselected (self, selected):
        if selected:
            self.selectbox.show()
        else:
            self.selectbox.hide()
    
    def setstate (self, state):
        self.state = state
        if state == 1: # normal
            self.show()
            self.graphgroup.setOpacity(1)
            self.shadowbox.show()
        elif state == 0: # ghost
            self.show()
            self.graphgroup.setOpacity(0.7)
            self.shadowbox.hide()
        elif state == -1: # hidden
            self.hide()
    
    def setplaymode (self, playmode):
        if playmode:
            self.setOpacity(0.5)
        else:
            self.setOpacity(1)
    
    def setY (self, y):
        parent = self.view.itembyID(self.refID)
        y += self.getyoffset()
        if self.edge is not None:
            self.edge.setY(y)
        super().setY(y)
    
    def setrank (self, parent):
        if parent is None:
            return
        if self.issubnode():
            x = parent.x()
            self.setX(x)
        else:
            x = parent.x()+self.style.rankwidth
            self.setX(x)
            self.nudgechildren()
        if self.edge is not None:
            self.edge.setX(x)
    
    def nudgechildren (self):
        for child in self.childlist():
            child().setrank(self)
    
    def getyoffset (self):
        if self.nodeobj.nodebank == -1:
            return self.yoffset
        else:
            return self.view.itembyID(self.refID).getyoffset() + self.yoffset
    
    def hide (self):
        super().hide()
        if self.edge:
            self.edge.hide()
    
    def show (self):
        super().show()
        if self.edge:
            self.edge.show()
    
    def issubnode (self):
        return self.nodeobj.nodebank is not -1
    
    def isghost (self):
        return not self.state
    
    def realnode (self):
        return self.view.itembyID(self.nodeobj.ID)
    
    def isactive (self):
        return self.view.activenode is self
    
    def isselected (self):
        return self.view.selectednode is self
    
    def iscollapsed (self):
        return self.id() in self.view.collapsednodes
    
    def y_top (self):
        return self.y() - self.boundingRect().height()//2
    
    def y_bottom (self):
        return self.y() + self.boundingRect().height()//2
    
    def bulkshift (self, children, diff):
        self.setY(self.y() + diff)
        if children is None:
            children = [c() for c in self.childlist()]
        for child in children:
            child.bulkshift(None, diff)
    
    def treeposition (self, ranks=None):
        if ranks is None:
            ranks = dict()
        localranks = dict()
        children = [c() for c in self.childlist()]
        for child in children:
            localranks = child.treeposition(localranks)
        rank = self.x() // self.style.rankwidth
        if children:
            top = children[0].y_top()
            bottom = children[-1].y_bottom()
            self.setY((top+bottom)//2)
        localranks[rank] = [self.y_top, self.y_bottom]
        streeshift = None
        for r in localranks:
            if r in ranks:
                rankshift = ranks[r][1]() + self.style.rowgap - localranks[r][0]()
                if streeshift is None or rankshift > streeshift:
                    streeshift = rankshift
                ranks[r][1] = localranks[r][1]
            else:
                ranks[r] = localranks[r]
        if streeshift:
            self.bulkshift(children, streeshift)
        return ranks
    
    def siblings (self):
        if self.refID is None:
            return None
        parent = self.view.nodecontainer.nodes[self.refID]
        if self.issubnode():
            return parent.subnodes
        else:
            return parent.linkIDs
    
    def siblingabove (self):
        sibs = self.siblings()
        if sibs is None or self.nodeobj.ID not in sibs:
            return None
        myindex = sibs.index(self.nodeobj.ID)
        if myindex:
            sibID = (self.refID, sibs[myindex-1])
            return self.view.itembyfullID(sibID)
        else:
            return None
    
    def siblingbelow (self):
        sibs = self.siblings()
        if sibs is None or self.nodeobj.ID not in sibs:
            return None
        myindex = sibs.index(self.nodeobj.ID)
        if len(sibs) > myindex+1:
            sibID = (self.refID, sibs[myindex+1])
            return self.view.itembyfullID(sibID)
        else:
            return None
    
    def subtreesize (self, depth=-1):
        """Find vertical extents of a subtree.
        
        Returns min/max y coordinates up to given depth (negative depth means
        whole subtree)."""

        # calculate child positions for EgdeItem only once when calculating scenerect
        if depth<0:
            generate = True
        else:
            generate = False
        
        children = [c() for c in self.childlist(generate=generate)]
        maxdepth = abs(depth)
        if children and depth:
            nextdepth = depth-1
            ymin = self.y_top()
            ymax = self.y_bottom()
            for child in children:
                top, bottom, depth = child.subtreesize(nextdepth)
                ymin = min(ymin, top)
                ymax = max(ymax, bottom)
                maxdepth = max(maxdepth, depth)
        else:
            ymin = self.y_top()
            ymax = self.y_bottom()
        return ymin, ymax, maxdepth
        
    def boundingRect (self):
        return self.rect
    
    def paint (self, painter, style, widget):
        pass
    
    def pixmap (self, path):
        return QPixmap(path).scaledToWidth(self.style.boldheight, Qt.SmoothTransformation)
    
    def graphicsetup (self):
        lightbrush = QBrush(FlPalette.light)
        mainbrush = QBrush(self.maincolor)
        altbrush = QBrush(self.altcolor)
        nopen = QPen(0)
        viewport = self.view.viewport()
        
        self.graphgroup = QGraphicsItemGroup(self)
        self.fggroup = QGraphicsItemGroup(self)
        
        self.shadowbox = QGraphicsRectItem(self)
        self.shadowbox.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.shadowbox.setBrush(FlPalette.dark)
        self.shadowbox.setPen(nopen)
        self.shadowbox.setPos(*(self.style.shadowoffset,)*2)
        self.graphgroup.addToGroup(self.shadowbox)
        
        self.activebox = QGraphicsRectItem(self)
        self.activebox.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        activepen = QPen(self.maincolor, self.style.selectmargin, join=Qt.MiterJoin)
        self.activebox.setPen(activepen)
        self.activebox.hide()
        self.graphgroup.addToGroup(self.activebox)
        
        self.selectbox = QGraphicsRectItem(self)
        self.selectbox.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        selectpen = QPen(FlPalette.light, self.style.selectmargin, join=Qt.MiterJoin)
        self.selectbox.setPen(selectpen)
        self.selectbox.hide()
        self.graphgroup.addToGroup(self.selectbox)
        
        self.mainbox = QGraphicsRectItem(self)
        self.mainbox.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.mainbox.setBrush(mainbrush)
        self.mainbox.setPen(nopen)
        self.graphgroup.addToGroup(self.mainbox)
        
        self.nodelabel = QGraphicsSimpleTextItemCond(self, viewport)
        self.nodelabel.setBrush(lightbrush)
        self.nodelabel.setFont(self.style.boldfont)
        self.nodelabel.setText(self.label % self.realid())
        self.nodelabel.setPos(self.style.itemmargin, self.style.itemmargin)
        self.fggroup.addToGroup(self.nodelabel)
        
        self.icon = self.pixmap("images/blank.png")
        self.iwidth = self.icon.width()
        self.iconx = self.style.nodetextwidth
        
        self.condicon = QGraphicsPixmapItemCond(self.icon, self, viewport)
        self.condicon.setPos(self.iconx-self.iwidth, self.style.itemmargin)
        self.iconx = self.condicon.x()
        self.fggroup.addToGroup(self.condicon)
        
        self.randicon = QGraphicsPixmapItemCond(self.icon, self, viewport)
        self.randicon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin)
        self.iconx = self.randicon.x()
        self.fggroup.addToGroup(self.randicon)
        
        self.exiticon = QGraphicsPixmapItemCond(self.icon, self, viewport)
        self.exiticon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin)
        self.iconx = self.exiticon.x()
        self.fggroup.addToGroup(self.exiticon)
        
        self.entericon = QGraphicsPixmapItemCond(self.icon, self, viewport)
        self.entericon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin)
        self.iconx = self.entericon.x()
        self.fggroup.addToGroup(self.entericon)
        
        self.persisticon = QGraphicsPixmapItemCond(self.icon, self, viewport)
        self.persisticon.setPos(self.iconx-self.style.itemmargin-self.iwidth, self.style.itemmargin)
        self.iconx = self.persisticon.x()
        self.fggroup.addToGroup(self.persisticon)
        
        self.comment = QGraphicsTextItemCond(self, viewport)
        self.comment.setTextWidth(self.style.nodetextwidth)
        self.comment.setDefaultTextColor(FlPalette.light)
        self.comment.setPos(0, self.nodelabel.y()+self.nodelabel.boundingRect().height()+self.style.itemmargin)
        self.fggroup.addToGroup(self.comment)
        
        self.graphgroup.addToGroup(self.fggroup)
        
        self.view.nodedocs[self.realid()]["comment"].contentsChanged.connect(self.updatecomment)
        self.updatecondition()
        self.updateenterscripts()
        self.updateexitscripts()
        self.updaterandweight()
        self.updatepersistence()
        
        # Never call updatelayout() from here (or any inheritable reimplementation)!
    
    def collapse (self, collapse):
        for item in self.fggroup.childItems():
            if item is not self.nodelabel:
                if collapse:
                    item.hide()
                else:
                    item.show()
        self.updatelayout()
    
    def updatecondition (self):
        icons = {True: "key", False: "blank"}
        pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.hascond()])
        self.condicon.setPixmap(pixmap)
        if self.nodeobj.hascond():
            self.condicon.setToolTip("Condition")
        else:
            self.condicon.setToolTip("")
    
    def updateenterscripts (self):
        icons = {True: "script-enter", False: "blank"}
        pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.hasenterscripts()])
        self.entericon.setPixmap(pixmap)
        if self.nodeobj.hasenterscripts():
            self.entericon.setToolTip("Enter Scripts")
        else:
            self.entericon.setToolTip("")
    
    def updateexitscripts (self):
        icons = {True: "script-exit", False: "blank"}
        pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.hasexitscripts()])
        self.exiticon.setPixmap(pixmap)
        if self.nodeobj.hasexitscripts():
            self.exiticon.setToolTip("Exit Scripts")
        else:
            self.exiticon.setToolTip("")
    
    def updaterandweight (self):
        icons = {True: "dice", False: "blank"}
        pixmap = self.pixmap("images/%s.png" % icons[bool(self.nodeobj.randweight)])
        self.randicon.setPixmap(pixmap)
        if self.nodeobj.randweight:
            self.randicon.setToolTip("Random Weight: %s" % self.nodeobj.randweight)
        else:
            self.randicon.setToolTip("")
    
    def updatepersistence (self):
        icons = {"Mark": "mark", "OncePerConv": "once", "OnceEver": "onceever", "": "blank"}
        pixmap = self.pixmap("images/%s.png" % icons[self.nodeobj.persistence])
        self.persisticon.setPixmap(pixmap)
        if self.nodeobj.persistence:
            self.persisticon.setToolTip("Persistence: %s" % self.nodeobj.persistence)
        else:
            self.persisticon.setToolTip("")
    
    def updatecomment (self):
        self.fggroup.removeFromGroup(self.comment)
        contents = self.view.nodedocs[self.realid()]["comment"].toPlainText()
        if not contents:
            self.comment.hide()
        else:
            self.comment.show()
            self.comment.setPlainText(contents)
            self.fggroup.addToGroup(self.comment)
        self.updatelayout()
    
    def updatelayout (self):
        if self.iscollapsed():
            rect = self.nodelabel.mapRectToParent(self.nodelabel.boundingRect())
        else:
            rect = self.fggroup.childrenBoundingRect()
        mainrect = rect.marginsAdded(self.style.nodemargins)
        self.mainbox.setRect(mainrect)
        self.shadowbox.setRect(mainrect)
        self.selectbox.setRect(mainrect.marginsAdded(self.style.selectmargins))
        activerect = mainrect.marginsAdded(self.style.activemargins)
        self.activebox.setRect(activerect)
        self.graphgroup.setPos(-activerect.width()//2-activerect.x(), -activerect.height()//2-activerect.y())
        self.prepareGeometryChange()
        self.rect = self.graphgroup.mapRectToParent(mainrect)
        self.view.updatelayout()
    
    def mouseDoubleClickEvent (self, event):
        super().mouseDoubleClickEvent(event)
        event.accept()
        if event.button() == Qt.LeftButton:
            self.view.setactivenode(self)
    
    def mousePressEvent (self, event):
        super().mousePressEvent(event)
        if event.button() & (Qt.LeftButton | Qt.RightButton) :
            self.view.setselectednode(self)
            event.accept()
    
    def __repr__ (self):
        return "<%s %s>" % (type(self).__name__, self.id())
예제 #4
0
class MapWidget(QDialog):
    def __init__(self):
        QDialog.__init__(self)
    
        self.__mapWidgetUi = Ui_MapWidget()
        self.__mapWidgetUi.setupUi(self)
        
        self.__view = self.__mapWidgetUi.graphicsView
        self.__view.setObjectName("ACS_Map_View")
        self.__scene = QGraphicsScene()
                
        self.__view.setScene(self.__scene)

        self.__current_lat = 35.720428
        self.__current_lon = -120.769924
        self.__current_ground_width = 41000000. #meters(start w/ view of whole earth)
        #TODO: don't hard code location
        self.__tiler = acs_map_tiler.ACS_MapTiler(35.720428, -120.769924)

        self.__current_detail_layer = 0 #corresponds to "zoom" in map_tiler module
        self.__detail_layers = []
        self.__rect_tiles = OrderedDict() #see rectKey method for how key works

        #detail layers are various levels of raster detail for the map.
        #0 is lowest level of detail, 20 highest.  0 loads fast and the 
        #entire world can fit on a single tile.  20 loads slow, and it is unwise
        #to try to show the entire world due to the number of tiles required 
        #(higher numbered detail layers are intended for "zooming in")
        self.setupDetailLayers()

        self.__plane_layer = QGraphicsItemGroup()
        self.__scene.addItem(self.__plane_layer)
        self.__plane_icons = {}
        self.__plane_labels = {}
        self.__plane_icon_pixmaps = {}
        img_bytes = pkg_resources.resource_stream("acs_dashboards", "data/images/flyingWingTiny.png").read()
        self.__plane_icon_pixmaps[0] = QPixmap()
        self.__plane_icon_pixmaps[0].loadFromData(img_bytes)
        img_bytes2 = pkg_resources.resource_stream("acs_dashboards", "data/images/flyingWingTiny2.png").read()
        self.__plane_icon_pixmaps[1] = QPixmap()
        self.__plane_icon_pixmaps[1].loadFromData(img_bytes2)

        #for drawing waypoints:
        self.__mission_layer = QGraphicsItemGroup()
        self.__scene.addItem(self.__mission_layer)
        self.__wp_diameter = 0.0001
        self.__prev_drawn_nav_wp = None  #needed when drawing lines between wps
        self.__wp_loiter_radius = None #NOTE: this is in meters; the map itself
                                       #is in degrees

        #for drawing fence:
        self.__fence_layer = QGraphicsItemGroup()
        self.__scene.addItem(self.__fence_layer)

        #slots
        self.__view.just_zoomed.connect(self.onZoom)
        self.__mapWidgetUi.zoom_sb.valueChanged.connect(self.onZoomSBValueChanged)
        self.__view.just_panned.connect(self.onPan)
        
    def getView(self):
        return self.__view
    
    def rectKey(self, x, y):
        '''rect_tiles key'''
        return (self.__current_detail_layer, x, y)

    #returns (min_lat, max_lat, min_lon, max_lon) as a tuple
    def extentsOfVisibleWorld(self):
        topLeft = self.__view.mapToScene(0,0)
        bottomRight = self.__view.mapToScene(self.__view.width(), 
                                             self.__view.height())

        return (-topLeft.y(), -bottomRight.y(), topLeft.x(), bottomRight.x())

    #returns a list of TileInfos that are currently visible
    def tilesInVisibleWorld(self):
        (latTop, latBottom, lonLeft, lonRight) = self.extentsOfVisibleWorld()
        #print("Extents:", latTop, latBottom, lonLeft, lonRight, "\n")
        tile_info_list = self.__tiler.area_to_tile_list_lat_lon(latTop, latBottom,
                lonLeft, lonRight, self.__current_detail_layer)

        return tile_info_list

    def setupDetailLayers(self):
        #setup detail layers 0-20
        for i in range(0,21):
            self.__detail_layers.append(QGraphicsItemGroup())
            #add layer to scene:
            self.__scene.addItem(self.__detail_layers[i])            
            #hide all detail layers until it's time to show one:
            self.__detail_layers[i].hide()

        #show only the current detail layer
        self.setCurrentDetailLayer(0)

    def setCurrentDetailLayer(self, layerNum):
        self.__detail_layers[self.__current_detail_layer].hide()
        self.__detail_layers[layerNum].show()
        self.__current_detail_layer = layerNum

        self.__tiler.set_max_zoom(self.__current_detail_layer+1)
        #self.__tiler.prefetch()

        self.addTilesToCurrentDetailLayer()

    def createRectFromTileInfo(self, tile_info):
        #if Rectangle already exists, don't create it again
        key = self.rectKey(tile_info.x, tile_info.y)
        if key in self.__rect_tiles:
            return self.__rect_tiles[key]

        (y, x) = tile_info.coord()
        #TODO: do something about the hard coded 256s
        (end_y, end_x) = tile_info.coord((256, 256))
        
        #y values need to reflect across the equator due to the origin in scene space
        #being on the top right (as opposed to bottom left in tile space) 
        y = -y
        end_y = -end_y

        #keep things simple at the edges of the map:
        if y < -85.0:
            y = -85.0
        if end_y > 85.0:
            end_y = 85.0

        width = end_x - x
        height = end_y - y

        #create rectangle for the TileInfo and put it into the scene
        self.__rect_tiles[key] = QGraphicsRectItem(x, y, width, height, self.__detail_layers[self.__current_detail_layer])

        #add raster data to the rect tile
        self.__tiler.load_tile(tile_info)
        #no border
        self.__rect_tiles[key].setPen(QPen(Qt.NoPen))

        #remember the tiling data
        self.__rect_tiles[key].setData(0, tile_info)

        #attempt to add tile texture to rectangle:
        self.textureRect(self.__rect_tiles[key])

        return self.__rect_tiles[key]

    def addTilesToCurrentDetailLayer(self):
        tile_info_list = self.tilesInVisibleWorld()

        for next_tile_info in tile_info_list:
            next_rect = self.createRectFromTileInfo(next_tile_info)

            #add rect tile to appropriate detail layer
            self.__detail_layers[self.__current_detail_layer].addToGroup(next_rect)

    #returns True if texture successfully applied, False otherwise
    #depth applies to how many zoom levels (or detail layers) we have traveled
    #while attempting to get a lower resolution texture
    def textureRect(self, rect_tile):
        tile_info = rect_tile.data(0)

        if tile_info is None:
            return False

        pm = QPixmap(self.__tiler.tile_to_path(tile_info))  
        if pm.width() != 256:
            #print("Probably didn't get tile:", next_tile_info.x, next_tile_info.y, "\n")
            #TODO: Attempt to texture with a lower res tile
            #Bear in mind that you will have to take Mercator projection into
            #account on the lower res tile.
            
            #First Attempt: didn't work
            #if tile_info.zoom <= self.__tiler.get_min_zoom():
            #    return False
            #
            #find colocated lower res tile
            #(lat,lon) = tile_info.coord()
            #tile_info2 = self.__tiler.coord_to_tile(lat,lon, tile_info.zoom-1)
            #rect_tile.setData(0, tile_info2)
            #print("prev tile: ", tile_info.tile, tile_info.coord())
            #return self.textureRect(rect_tile, depth + 1)

            #until such time as we can pull lower res tiles and figure out
            #which area to render on a rectangle, skip:
            return False

        topLeft = rect_tile.boundingRect().topLeft()
        bottomRight = rect_tile.boundingRect().bottomRight()   
       
        width = bottomRight.x() - topLeft.x()
        height = bottomRight.y() - topLeft.y()

        brush_trans = QTransform()        
        brush_trans.translate(topLeft.x(), topLeft.y())
        brush_trans.scale(width/256.0, height/256.0)

        qb = QBrush(pm)
        qb.setTransform(brush_trans)
        rect_tile.setBrush(qb)
   
        return True

    def checkForNewTextures(self):
        #ONLY care about rects in the current view:
        tile_info_list = self.tilesInVisibleWorld()

        for next_tile_info in tile_info_list:
            key = self.rectKey(next_tile_info.x, next_tile_info.y)
            # make sure key exists in self.__rect_tiles
            if key not in self.__rect_tiles:
                self.createRectFromTileInfo(next_tile_info)
            
            if self.__rect_tiles[key].brush().texture().width() != 256:
                self.textureRect(self.__rect_tiles[key])
             
            #TODO: this code becomes applicable when lower res tiles become
            #available to higher zoom levels.
            #if self.__rect_tiles[key].data(0).zoom < self.__current_detail_layer:
                #lower res in there, try to get higher res 
                #(remove tile info of the lower res tile):
            #    del self.__rect_tiles[key]

    def clearFencePoints(self):
        children = self.__fence_layer.childItems()
        for child in children:
            self.__fence_layer.removeFromGroup(child)

    def clearWPs(self):
        children = self.__mission_layer.childItems()
        for child in children:
            self.__mission_layer.removeFromGroup(child)
        
        self.__prev_drawn_nav_wp = None

    def addWP(self, wp):
        if wp.command in [mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
                          mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
                          mavutil.mavlink.MAV_CMD_NAV_LOITER_TO_ALT,
                          mavutil.mavlink.MAV_CMD_NAV_LOITER_TURNS,
                          mavutil.mavlink.MAV_CMD_NAV_LOITER_TIME,
                          mavutil.mavlink.MAV_CMD_NAV_LOITER_UNLIM,
                          mavutil.mavlink.MAV_CMD_NAV_LAND]:
            

            #point
            rad = self.__wp_diameter * 0.5
            ellipse = QGraphicsEllipseItem(wp.y - rad, -wp.x - rad, 
                self.__wp_diameter, self.__wp_diameter, self.__mission_layer)
            ellipse.setBrush(QBrush(QColor(255, 255, 255)))
            e_pen = QPen(QColor(255, 255, 255))
            e_pen.setWidth(0)
            ellipse.setPen(e_pen)
            self.__mission_layer.addToGroup(ellipse)

            #label
            label = QGraphicsTextItem(str(wp.seq), self.__mission_layer)
            label.setZValue(2)
            label.setDefaultTextColor(Qt.white)
            label.setPos(wp.y + rad, -wp.x - rad)
            label.setScale(0.00002)  #bit hacky --  really should scale based on
                                 #current zoom, but I'm in a hurry.
            self.__mission_layer.addToGroup(label)
            label.show()

    #argument is a dictionary of waypoints (keys = ids)
    def drawNewMission(self, wps):
        self.clearWPs()

        self.drawMissionLines(wps)        
                
    def drawWPLine(self, wp1, wp2):
        if wp1 is None or wp2 is None:
            print("Error: Can't draw line for Null endpoint")
            return
    
        rad = self.__wp_diameter * 0.5
        mapped_wp1_x = wp1.y - rad
        mapped_wp1_y = -wp1.x
        #tangential approach?
        if (wp1.command == mavutil.mavlink.MAV_CMD_NAV_LOITER_TO_ALT
                and wp1.param1 == 1.0):
            l_rad = abs(self.__wp_loiter_radius)
            if l_rad is not None:
                dis = acs_math.gps_distance(wp1.x, wp1.y, wp2.x, wp2.y)
                theta = math.degrees(math.atan(l_rad / dis))

                #sign of self.__wp_loiter_radius indicates the direction of the
                #loiter (negative is counter-clockwise, postive is clockwise)
                #Also, the waypoint itself can override the default setting
                #via param2
                if ((wp1.param2 == 0 and self.__wp_loiter_radius > 0.0) or
                     wp1.param2 > 0.0):
                    theta = -theta

                tan_dis = math.sqrt( dis * dis - l_rad * l_rad)
                bearing = acs_math.gps_bearing(wp2.x, wp2.y, wp1.x, wp1.y)

                (tan_wp_x, tan_wp_y) = acs_math.gps_newpos(wp2.x, wp2.y,
                    bearing - theta, tan_dis)

                mapped_wp1_x = tan_wp_y - rad
                mapped_wp1_y = -tan_wp_x

        next_line = QGraphicsLineItem(mapped_wp1_x, mapped_wp1_y,
                            wp2.y - rad, -wp2.x, self.__mission_layer)
        l_pen = QPen(QColor(255, 255, 255))
        l_pen.setWidth(0.00002)
        next_line.setPen(l_pen)
        self.__mission_layer.addToGroup(next_line)

    def setWPLoiterRad(self, rad):
        self.__wp_loiter_radius = rad

    def drawWPCircle(self, wp):
        if self.__wp_loiter_radius is None:
            print("Can't draw WP loiter circles; wp_loiter_radius not set.")
            return

        rad_deg = acs_math.gps_meters_to_degrees_lat(abs(self.__wp_loiter_radius), wp.x, wp.y)
        diameter = rad_deg * 2.0
        ellipse = QGraphicsEllipseItem(wp.y - rad_deg, -wp.x - rad_deg, 
            diameter, diameter, self.__mission_layer)
        #ellipse.setBrush(QBrush(QColor(255, 255, 255)))
        e_pen = QPen(QColor(255, 255, 255))
        e_pen.setWidth(0.00002)
        ellipse.setPen(e_pen)
        self.__mission_layer.addToGroup(ellipse)

    def drawMissionLines(self, wps):
        for i in range(1, len(wps)):
            self.addWP(wps[i])

            if wps[i].command in [mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
                                  mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
                                  mavutil.mavlink.MAV_CMD_NAV_LOITER_TO_ALT,
                                  mavutil.mavlink.MAV_CMD_NAV_LOITER_TURNS,
                                  mavutil.mavlink.MAV_CMD_NAV_LOITER_TIME,
                                  mavutil.mavlink.MAV_CMD_NAV_LOITER_UNLIM]:

                if self.__prev_drawn_nav_wp is not None:
                    self.drawWPLine(self.__prev_drawn_nav_wp, wps[i])
            
                if wps[i].command in [mavutil.mavlink.MAV_CMD_NAV_LOITER_UNLIM,
                                      mavutil.mavlink.MAV_CMD_NAV_LOITER_TURNS,
                                      mavutil.mavlink.MAV_CMD_NAV_LOITER_TIME,
                                    mavutil.mavlink.MAV_CMD_NAV_LOITER_TO_ALT]:
                    self.drawWPCircle(wps[i])

                self.__prev_drawn_nav_wp = wps[i]

            elif wps[i].command == mavutil.mavlink.MAV_CMD_DO_JUMP:
                wp_id = wps[i].param1
                if self.__prev_drawn_nav_wp is not None:
                    self.drawWPLine(self.__prev_drawn_nav_wp, wps[wp_id])
                self.__prev_drawn_nav_wp = None
    
    def drawNewFence(self, fps):
        self.clearFencePoints()

        prev_fp = None
        for i in range(1, len(fps)):
            if prev_fp is not None:
                prev_x = prev_fp.lon
                prev_y = -prev_fp.lat
                mapped_x = fps[i].lon
                mapped_y = -fps[i].lat

                next_line = QGraphicsLineItem(prev_x, prev_y, 
                    mapped_x, mapped_y, self.__fence_layer)
                l_pen = QPen(QColor(0, 255, 0))
                l_pen.setWidth(0.1)
                next_line.setPen(l_pen)
                self.__fence_layer.addToGroup(next_line)

            prev_fp = fps[i]

    def updateIcon(self, id, uav_state, image_num=0):
        if uav_state.get_lon() == 0.0:
            #haven't received a Pose message yet
            return

        if id not in self.__plane_icons:
            #make the plane's label first
            label = QGraphicsTextItem(str(id), self.__plane_layer)
            label.setZValue(2)
            label.setDefaultTextColor(Qt.red)
            self.__plane_layer.addToGroup(label) 
            label.show()
            self.__plane_labels[id] = label

            self.__plane_icons[id] = MapGraphicsIcon(id, label,
                                                    self.__plane_layer)
            self.__plane_icons[id].centerIconAt(uav_state.get_lon(),
                                               -uav_state.get_lat())
            self.__plane_icons[id].textureIcon(self.__plane_icon_pixmaps[image_num])

            #plane icons need to be drawn on top of map tiles:
            self.__plane_icons[id].setZValue(1)
            self.__plane_layer.addToGroup(self.__plane_icons[id])

            #key 0 = last update time
            self.__plane_icons[id].setData(0, 0)

            #refresh:
            #HACK: don't know why zooming works to refresh. Couldn't
            #get scene.invalidate() nor scene.update() to work
            self.onZoom(self.__current_detail_layer)
        #end if

        now = time.time()

        #if we don't have new pose data, then we don't update the plane icon
        if (self.__plane_icons[id].data(0) is None 
         or self.__plane_icons[id].data(0) >= uav_state.get_last_pose_update()):
            return

        #place icon
        self.__plane_icons[id].centerIconAt(uav_state.get_lon(), -uav_state.get_lat())
        #give correct heading:
        quat = uav_state.get_quat()
        heading = acs_math.yaw_from_quat(quat[0], quat[1], quat[2], quat[3])
        self.__plane_icons[id].setHeading(heading)
          
        #set last update time
        self.__plane_icons[id].setData(0, now)
     
    def zoomTo(self, zoomLevel, lat = 0, lon = 0):
        self.__view.zoomTo(zoomLevel, lat, lon)

    def onZoom(self, zoom_level):
        self.setCurrentDetailLayer(zoom_level)
        self.__mapWidgetUi.zoom_sb.setValue(zoom_level)
        for id, nextPlaneIcon in self.__plane_icons.items():
            nextPlaneIcon.scaleByViewAndTexture(self.__view)

    def onZoomSBValueChanged(self, new_zoom):
        if (int(self.__view.getCurrentZoom()) != new_zoom):
            self.__view.zoomTo(new_zoom)

    def onPan(self, new_lat, new_lon):
        lat_lon_str = str(new_lat) + ", " + str(new_lon) 
        self.__mapWidgetUi.coords_lb.setText(lat_lon_str)

    def hideUAVIcon(self, id):
        if id in self.__plane_icons:
            self.__plane_icons[id].hide()
            self.__plane_labels[id].hide()

    def showUAVIcon(self, id):
        if id in self.__plane_icons:
            self.__plane_icons[id].show()
            self.__plane_labels[id].show()