Example #1
0
def main(stdscr):
    #Initialize curses
    curses.curs_set(False)
    #Initialize debug output printer
    dbgoutput.init(stdscr)
    #Initialize tile manager
    tile_m = TileManager()
    #Clear the terminal
    stdscr.clear()

    #Create a new map to fill the screen. This is where the magic happens.
    map = Map(curses.COLS, curses.LINES - 1)
    #Iterate over the generated map and print its contents via curses
    maparray = map.get_map_array()
    for y, row in enumerate(maparray):
        for x, tile in enumerate(row):
            try:
                stdscr.addstr(y, x, tile.char, tile.color)
            except TypeError:
                raise TypeError("X:{0} Y:{1} char:{2} color:{3}".format(
                    x, y, tile.char, tile.color))

    #Output debugging messages in the upper-left corner
    dbgoutput.print_output()

    #Close curses and put the terminal back in normal mode.
    stdscr.refresh()
    stdscr.getkey()
    def __init__(self, plugin, layerDef):
        QgsPluginLayer.__init__(self, IdahoLayer.LAYER_TYPE, layerDef.imageid)

        self.plugin = plugin
        self.iface = plugin.iface
        self.layerDef = layerDef
        self.tile_manager = TileManager(self, layerDef)
        self.zoom = 0
        self.tile_range = None

        # set custom properties
        self.setCustomProperty("service_url", layerDef.service_url)
        self.setCustomProperty("bucket_name", layerDef.bucket_name)
        self.setCustomProperty("imageid", layerDef.imageid)
        self.setCustomProperty("zmin", layerDef.zmin)
        self.setCustomProperty("zmax", layerDef.zmax)
        self.setCustomProperty("bbox", layerDef.bbox.toString())
        self.setCustomProperty("epsg", layerDef.epsg)
        self.setCustomProperty("max_connections", layerDef.max_connections)

        # set crs default to Pseudo Mercator
        if plugin.crs3857 is None:
            # create a QgsCoordinateReferenceSystem instance if plugin has no instance yet
            plugin.crs3857 = QgsCoordinateReferenceSystem(3857)

        self.setCrs(plugin.crs3857)

        # set QGIS extent as QgsRectangle
        if not layerDef.epsg:
            layerDef.epsg = 4326
        if layerDef.epsg == 3857 or layerDef.epsg == 900913:
            self.setExtent(layerDef.bbox.toQgsRectangle())
        else:
            self.setExtent(BoundingBox.asPseudoMercator(layerDef.bbox, layerDef.epsg).toQgsRectangle())

        # set styles
        self.setTransparency(0)
        self.setBrightness(0)
        self.setContrast(1.0)
        self.setSaturation(1.0)
        self.setBlendModeByName(self.DEFAULT_BLEND_MODE)
        self.setSmoothRender(self.DEFAULT_SMOOTH_RENDER)


        if self.iface:
            self.statusSignal.connect(self.showStatusMessageSlot)
            self.messageBarSignal.connect(self.showMessageBarSlot)

        self.setValid(True)
    def readXml(self, node):
        self.readCustomProperties(node)

        service_url = self.customProperty("service_url", "")
        imageid = self.customProperty("imageid", "")
        bucket_name = self.customProperty("bucket_name", "")
        zmin = int(self.customProperty("zmin", TileDefaultSettings.ZMIN))
        zmax = int(self.customProperty("zmax", TileDefaultSettings.ZMAX))
        bbox = BoundingBox.fromString(self.customProperty("bbox", ""))
        epsg = int(self.customProperty("epsg", 3857))
        max_connections = int(self.customProperty("max_connections", 16))

        self.layerDef = IdahoLayerDefinition(service_url=service_url, imageid=imageid, bucket_name=bucket_name,
                                             zmin=zmin, zmax=zmax, bbox=bbox, epsg=epsg,
                                             max_connections=max_connections)

         # set parameters
        self.layerDef.parameters.colorinterpretation = self.customProperty("colorinterpretation", "UNSPECIFIED")
        self.layerDef.parameters.bands = self.customProperty("bands", [0])
        self.layerDef.parameters.format = self.customProperty("format", None)
        self.layerDef.parameters.token = self.customProperty("token", None)
        self.layerDef.parameters.doDRA = self.customProperty("doDRA", None)
        self.layerDef.parameters.lowCutoff = self.customProperty("lowCutoff", None)
        self.layerDef.parameters.highCutoff = self.customProperty("highCutoff", None)
        self.layerDef.update()

        self.tile_manager = TileManager(self, self.layerDef)

        self.setExtent(BoundingBox.asPseudoMercator(self.layerDef.bbox, self.layerDef.epsg).toQgsRectangle())

        # layer style
        self.setTransparency(int(self.customProperty("transparency", 0)))
        self.setBlendModeByName(self.customProperty("blendMode", self.DEFAULT_BLEND_MODE))
        self.setSmoothRender(int(self.customProperty("smoothRender", self.DEFAULT_SMOOTH_RENDER)))


        return True
class IdahoLayer(QgsPluginLayer):
    LAYER_TYPE = "IdahoLayer"
    MAX_TILE_COUNT = 256
    DEFAULT_BLEND_MODE = "SourceOver"
    DEFAULT_SMOOTH_RENDER = True

    # PyQt signals
    statusSignal = pyqtSignal(str, int)
    messageBarSignal = pyqtSignal(str, str, int, int)

    def __init__(self, plugin, layerDef):
        QgsPluginLayer.__init__(self, IdahoLayer.LAYER_TYPE, layerDef.imageid)

        self.plugin = plugin
        self.iface = plugin.iface
        self.layerDef = layerDef
        self.tile_manager = TileManager(self, layerDef)
        self.zoom = 0
        self.tile_range = None

        # set custom properties
        self.setCustomProperty("service_url", layerDef.service_url)
        self.setCustomProperty("bucket_name", layerDef.bucket_name)
        self.setCustomProperty("imageid", layerDef.imageid)
        self.setCustomProperty("zmin", layerDef.zmin)
        self.setCustomProperty("zmax", layerDef.zmax)
        self.setCustomProperty("bbox", layerDef.bbox.toString())
        self.setCustomProperty("epsg", layerDef.epsg)
        self.setCustomProperty("max_connections", layerDef.max_connections)

        # set crs default to Pseudo Mercator
        if plugin.crs3857 is None:
            # create a QgsCoordinateReferenceSystem instance if plugin has no instance yet
            plugin.crs3857 = QgsCoordinateReferenceSystem(3857)

        self.setCrs(plugin.crs3857)

        # set QGIS extent as QgsRectangle
        if not layerDef.epsg:
            layerDef.epsg = 4326
        if layerDef.epsg == 3857 or layerDef.epsg == 900913:
            self.setExtent(layerDef.bbox.toQgsRectangle())
        else:
            self.setExtent(BoundingBox.asPseudoMercator(layerDef.bbox, layerDef.epsg).toQgsRectangle())

        # set styles
        self.setTransparency(0)
        self.setBrightness(0)
        self.setContrast(1.0)
        self.setSaturation(1.0)
        self.setBlendModeByName(self.DEFAULT_BLEND_MODE)
        self.setSmoothRender(self.DEFAULT_SMOOTH_RENDER)


        if self.iface:
            self.statusSignal.connect(self.showStatusMessageSlot)
            self.messageBarSignal.connect(self.showMessageBarSlot)

        self.setValid(True)


    def setBlendModeByName(self, modeName):
        self.blendModeName = modeName
        blendMode = getattr(QPainter, "CompositionMode_" + modeName, 0)
        self.setBlendMode(blendMode)
        self.setCustomProperty("blendMode", modeName)

    def setTransparency(self, transparency):
        self.transparency = transparency
        self.setCustomProperty("transparency", transparency)

    def setBrightness(self, brightness):
        self.brightness = brightness
        self.setCustomProperty("brightness", brightness)

    def setContrast(self, contrast):
        self.contrast = contrast
        self.setCustomProperty("contrast", contrast)

    def setSaturation(self, saturation):
        self.saturation = saturation
        self.setCustomProperty("saturation", saturation)

    def setSmoothRender(self, isSmooth):
        self.smoothRender = isSmooth
        self.setCustomProperty("smoothRender", 1 if isSmooth else 0)

    def calculate_tile_range(self, extent):

        size = self.layerDef.PSEUDO_MERCATOR_MAX_COORD / 2 ** (self.zoom - 1)
        matrixSize = 2 ** self.zoom
        ulx = max(0, int((extent.xMinimum() + self.layerDef.PSEUDO_MERCATOR_MAX_COORD) / size))
        uly = max(0, int((self.layerDef.PSEUDO_MERCATOR_MAX_COORD - extent.yMaximum()) / size))
        lrx = min(int((extent.xMaximum() + self.layerDef.PSEUDO_MERCATOR_MAX_COORD) / size), matrixSize - 1)
        lry = min(int((self.layerDef.PSEUDO_MERCATOR_MAX_COORD - extent.yMinimum()) / size), matrixSize - 1)

        # bounding box limit
        if self.layerDef.bbox:
            valid = True
            trange = None
            if not self.layerDef.epsg:
                self.layerDef.epsg = 4326
            elif self.layerDef.epsg == 3857 or self.layerDef.epsg == 900913:
                trange = self.layerDef.bboxMercatorToTileRange(self.zoom, self.layerDef.bbox)
            else:
                trange = self.layerDef.epsgToTileRange(self.zoom, self.layerDef.bbox)
            if trange:
                ulx = max(ulx, trange.xmin)
                uly = max(uly, trange.ymin)
                lrx = min(lrx, trange.xmax)
                lry = min(lry, trange.ymax)
                if lrx < ulx or lry < uly:
                    # tile range is out of the bounding box
                    valid = False

        self.tile_range = TileRange(ulx=ulx, uly=uly, lrx=lrx, lry=lry, valid=valid)


    def draw(self, renderContext):
        self.renderContext = renderContext
        painter = renderContext.painter()
        viewport = painter.viewport()

        extent = renderContext.extent()
        if extent.isEmpty() or extent.width() == float("inf"):
            qDebug("Drawing is skipped because map extent is empty or inf.")
            return True

        map2pixel = renderContext.mapToPixel()
        mupp = map2pixel.mapUnitsPerPixel()
        rotation = map2pixel.mapRotation() if self.plugin.apiChanged27 else 0.0

        mpp = mupp  # meters per pixel
        if rotation != 0.0:
            # get bounding box of the extent
            mapExtent = RotatedRect(extent.center(), mupp * viewport.width(), mupp * viewport.height(), rotation)
            extent = mapExtent.boundingBox()

        # calculate zoom level
        tile_mpp1 = self.layerDef.PSEUDO_MERCATOR_MAX_COORD / self.layerDef.TILE_SIZE
        self.zoom = int(math.ceil(math.log(tile_mpp1 / mpp, 2) + 1))
        self.zoom = max(0, min(self.zoom, self.layerDef.zmax))

        #if current zoom is not in layer range then bail
        if not self.zoom_is_in_range():
            return True

        self.calculate_tile_range(extent)

        #if tile range is not good
        if not self.tile_range.valid:
            return True

        if not self.tilecount_is_in_range():
            return True

        self.tile_manager.update(self.zoom, self.tile_range)
        tiles = self.tile_manager.get_tiles()

        # apply layer style
        # save painter state
        painter.save()
        oldOpacity = painter.opacity()
        painter.setOpacity(0.01 * (100 - self.transparency))
        oldSmoothRenderHint = painter.testRenderHint(QPainter.SmoothPixmapTransform)
        if self.smoothRender:
            painter.setRenderHint(QPainter.SmoothPixmapTransform)

        # draw tiles
        if rotation == 0.0:
            # no need to reproject tiles
            self.render_tiles(renderContext, tiles)

        else:
            # reproject tiles
            cx, cy = 0.5 * viewport.width(), 0.5 * viewport.height()
            center = map2pixel.toMapCoordinatesF(cx, cy)
            mapExtent = RotatedRect(center, mupp * viewport.width(), mupp * viewport.height(), rotation)
            self.render_tiles_warp_on_the_fly(renderContext, mapExtent, tiles)

        # restore layer style
        painter.setOpacity(oldOpacity)
        if self.smoothRender:
            painter.setRenderHint(QPainter.SmoothPixmapTransform, oldSmoothRenderHint)

        # restore painter state
        painter.restore()

        return True

    def render_tiles(self, renderContext, tiles, sdx=1.0, sdy=1.0):

        # create an compose_image that has the same resolution as the tiles
        image = tiles.compose_image()

        QgsImageOperation.adjustBrightnessContrast(image, self.brightness, self.contrast)
        QgsImageOperation.adjustHueSaturation(image, self.saturation)

        # tile extent to pixel
        map2pixel = renderContext.mapToPixel()
        extent = tiles.extent()
        topLeft = map2pixel.transform(extent.xMinimum(), extent.yMaximum())
        bottomRight = map2pixel.transform(extent.xMaximum(), extent.yMinimum())
        rect = QRectF(QPointF(topLeft.x() * sdx, topLeft.y() * sdy),
                      QPointF(bottomRight.x() * sdx, bottomRight.y() * sdy))

        # draw the compose_image on the map canvas
        renderContext.painter().drawImage(rect, image)

        self.log("TilesCollection extent: " + str(extent))
        self.log("Draw into canvas rect: " + str(rect))

    def render_tiles_warp_on_the_fly(self, renderContext, mapExtent, tiles, sdx=1.0, sdy=1.0):
        if not hasGdal:
            msg = self.tr("Rotation/Reprojection requires python-gdal")
            self.showMessageBar(msg, QgsMessageBar.INFO, 2)
            return

        transform = renderContext.coordinateTransform()
        if transform:
            sourceCrs = transform.sourceCrs()
            destCrs = transform.destCRS()
        else:
            sourceCrs = destCrs = self.crs()

        # create compose_image from the tiles
        image = tiles.compose_image()

        # tile extent
        extent = tiles.extent()
        geotransform = [extent.xMinimum(), extent.width() / image.width(), 0, extent.yMaximum(), 0,
                        -extent.height() / image.height()]

        # source raster dataset
        driver = gdal.GetDriverByName("MEM")
        tile_ds = driver.Create("", image.width(), image.height(), 1, gdal.GDT_UInt32)
        tile_ds.SetProjection(str(sourceCrs.toWkt()))
        tile_ds.SetGeoTransform(geotransform)

        # QImage to raster
        ba = image.bits().asstring(image.numBytes())
        tile_ds.GetRasterBand(1).WriteRaster(0, 0, image.width(), image.height(), ba)

        # target raster size - if smoothing is enabled, create raster of twice each of width and height of viewport size
        # in order to get high quality compose_image
        oversampl = 2 if self.smoothRender else 1

        painter = renderContext.painter()
        viewport = painter.viewport()
        width, height = viewport.width() * oversampl, viewport.height() * oversampl

        # target raster dataset
        canvas_ds = driver.Create("", width, height, 1, gdal.GDT_UInt32)
        canvas_ds.SetProjection(str(destCrs.toWkt()))
        canvas_ds.SetGeoTransform(mapExtent.geotransform(width, height, is_grid_point=False))

        # reproject compose_image
        gdal.ReprojectImage(tile_ds, canvas_ds)

        # raster to QImage
        ba = canvas_ds.GetRasterBand(1).ReadRaster(0, 0, width, height)
        reprojected_image = QImage(ba, width, height, QImage.Format_ARGB32_Premultiplied)

        # draw the compose_image on the map canvas
        rect = QRectF(QPointF(0, 0), QPointF(viewport.width() * sdx, viewport.height() * sdy))
        painter.drawImage(rect, reprojected_image)

    def drawDebugInfo(self, renderContext, zoom, ulx, uly, lrx, lry):
        painter = renderContext.painter()
        scaleX, scaleY = self.getScaleToVisibleExtent(renderContext)
        painter.scale(scaleX, scaleY)

        self.drawFrames(renderContext, zoom, ulx, uly, lrx, lry, 1.0 / scaleX, 1.0 / scaleY)
        self.drawNumbers(renderContext, zoom, ulx, uly, lrx, lry, 1.0 / scaleX, 1.0 / scaleY)

    def drawFrame(self, renderContext, zoom, x, y, sdx, sdy):
        rect = self.getTileRect(renderContext, zoom, x, y, sdx, sdy)
        p = renderContext.painter()
        # p.drawRect(rect)   # A slash appears on the top-right tile without Antialiasing render hint.
        pts = [rect.topLeft(), rect.topRight(), rect.bottomRight(), rect.bottomLeft(), rect.topLeft()]
        for i in range(4):
            p.drawLine(pts[i], pts[i + 1])

    def drawFrames(self, renderContext, zoom, xmin, ymin, xmax, ymax, sdx, sdy):
        for y in range(ymin, ymax + 1):
            for x in range(xmin, xmax + 1):
                self.drawFrame(renderContext, zoom, x, y, sdx, sdy)

    def drawNumber(self, renderContext, zoom, x, y, sdx, sdy):
        rect = self.getTileRect(renderContext, zoom, x, y, sdx, sdy)
        p = renderContext.painter()
        y = (2 ** zoom - 1) - y
        p.drawText(rect, Qt.AlignCenter, "(%d, %d)\nzoom: %d" % (x, y, zoom));

    def drawNumbers(self, renderContext, zoom, xmin, ymin, xmax, ymax, sdx, sdy):
        for y in range(ymin, ymax + 1):
            for x in range(xmin, xmax + 1):
                self.drawNumber(renderContext, zoom, x, y, sdx, sdy)

    def getScaleToVisibleExtent(self, renderContext):
        mapSettings = self.iface.mapCanvas().mapSettings() if self.plugin.apiChanged23 else self.iface.mapCanvas().mapRenderer()
        painter = renderContext.painter()
        if painter.device().logicalDpiX() == mapSettings.outputDpi():
            return 1.0, 1.0  # scale should be 1.0 in rendering on map canvas

        extent = renderContext.extent()
        ct = renderContext.coordinateTransform()
        if ct:
            # FIX ME: want to get original visible extent in project CRS or visible view size in pixels

            # extent = ct.transformBoundingBox(extent)
            # xmax, ymin = extent.xMaximum(), extent.yMinimum()

            pt1 = ct.transform(extent.xMaximum(), extent.yMaximum())
            pt2 = ct.transform(extent.xMaximum(), extent.yMinimum())
            pt3 = ct.transform(extent.xMinimum(), extent.yMinimum())
            xmax, ymin = min(pt1.x(), pt2.x()), max(pt2.y(), pt3.y())
        else:
            xmax, ymin = extent.xMaximum(), extent.yMinimum()

        bottomRight = renderContext.mapToPixel().transform(xmax, ymin)
        viewport = painter.viewport()
        scaleX = bottomRight.x() / viewport.width()
        scaleY = bottomRight.y() / viewport.height()
        return scaleX, scaleY

    def getTileRect(self, renderContext, zoom, x, y, sdx=1.0, sdy=1.0, toInt=True):
        """ get tile pixel rect in the render context """
        r = self.layerDef.getTileRect(zoom, x, y)
        map2pix = renderContext.mapToPixel()
        topLeft = map2pix.transform(r.xMinimum(), r.yMaximum())
        bottomRight = map2pix.transform(r.xMaximum(), r.yMinimum())
        if toInt:
            return QRect(QPoint(round(topLeft.x() * sdx), round(topLeft.y() * sdy)),
                         QPoint(round(bottomRight.x() * sdx), round(bottomRight.y() * sdy)))
        else:
            return QRectF(QPointF(topLeft.x() * sdx, topLeft.y() * sdy),
                          QPointF(bottomRight.x() * sdx, bottomRight.y() * sdy))

    def isProjectCrsWebMercator(self):
        mapSettings = self.iface.mapCanvas().mapSettings() if self.plugin.apiChanged23 else self.iface.mapCanvas().mapRenderer()
        return mapSettings.destinationCrs().postgisSrid() == 3857

    def zoom_is_in_range(self):
        # zoom limit
        if self.zoom < self.layerDef.zmin:
            if self.plugin.navigationMessagesEnabled:  # or zoom > self.layerDef.zmax is not necessary because it will use the last zoom level when zooming in
                msg = None
                tpl = "Zoom {_direction} to see {_layer}. Zoom levels {_min}-{_max} in layer. Currently at zoom level {_cur}."
                if self.zoom < self.layerDef.zmin:
                    msg = self.tr(tpl).format(_min=self.layerDef.zmin, _max=self.layerDef.zmax, _cur=self.zoom,
                                              _layer=self.layerDef.imageid, _direction="in")
                if msg:
                    self.showMessageBar(msg, QgsMessageBar.INFO, 2)
            return False
        return True

    #TODO: check this logic... seems a little off
    def tilecount_is_in_range(self):
        # tile count limit
        tileCount = (self.tile_range.lrx - self.tile_range.ulx + 1) * (self.tile_range.lry - self.tile_range.uly + 1)
        if tileCount > self.MAX_TILE_COUNT:
            # if the zoom level is less than the minimum, do not draw
            if self.zoom < self.layerDef.zmin:
                msg = self.tr("Tile count is over limit ({0}, max={1})").format(tileCount, self.MAX_TILE_COUNT)
                self.showMessageBar(msg, QgsMessageBar.WARNING, 4)
                return False
        return True

    def readXml(self, node):
        self.readCustomProperties(node)

        service_url = self.customProperty("service_url", "")
        imageid = self.customProperty("imageid", "")
        bucket_name = self.customProperty("bucket_name", "")
        zmin = int(self.customProperty("zmin", TileDefaultSettings.ZMIN))
        zmax = int(self.customProperty("zmax", TileDefaultSettings.ZMAX))
        bbox = BoundingBox.fromString(self.customProperty("bbox", ""))
        epsg = int(self.customProperty("epsg", 3857))
        max_connections = int(self.customProperty("max_connections", 16))

        self.layerDef = IdahoLayerDefinition(service_url=service_url, imageid=imageid, bucket_name=bucket_name,
                                             zmin=zmin, zmax=zmax, bbox=bbox, epsg=epsg,
                                             max_connections=max_connections)

         # set parameters
        self.layerDef.parameters.colorinterpretation = self.customProperty("colorinterpretation", "UNSPECIFIED")
        self.layerDef.parameters.bands = self.customProperty("bands", [0])
        self.layerDef.parameters.format = self.customProperty("format", None)
        self.layerDef.parameters.token = self.customProperty("token", None)
        self.layerDef.parameters.doDRA = self.customProperty("doDRA", None)
        self.layerDef.parameters.lowCutoff = self.customProperty("lowCutoff", None)
        self.layerDef.parameters.highCutoff = self.customProperty("highCutoff", None)
        self.layerDef.update()

        self.tile_manager = TileManager(self, self.layerDef)

        self.setExtent(BoundingBox.asPseudoMercator(self.layerDef.bbox, self.layerDef.epsg).toQgsRectangle())

        # layer style
        self.setTransparency(int(self.customProperty("transparency", 0)))
        self.setBlendModeByName(self.customProperty("blendMode", self.DEFAULT_BLEND_MODE))
        self.setSmoothRender(int(self.customProperty("smoothRender", self.DEFAULT_SMOOTH_RENDER)))


        return True

    def writeXml(self, node, doc):
        self.setCustomProperty("colorinterpretation", self.layerDef.parameters.colorinterpretation)
        self.setCustomProperty("bands", self.layerDef.parameters.bands)
        self.setCustomProperty("format", self.layerDef.parameters.format)
        self.setCustomProperty("token", self.layerDef.parameters.token)
        self.setCustomProperty("doDRA", self.layerDef.parameters.doDRA)
        self.setCustomProperty("lowCutoff", self.layerDef.parameters.lowCutoff)
        self.setCustomProperty("highCutoff", self.layerDef.parameters.highCutoff)
        self.writeCustomProperties(node, doc)
        element = node.toElement();
        element.setAttribute("type", "plugin")
        element.setAttribute("name", IdahoLayer.LAYER_TYPE);
        return True

    def readSymbology(self, node, errorMessage):
        return False

    def writeSymbology(self, node, doc, errorMessage):
        return False

    def metadata(self):
        lines = []
        fmt = u"%s:\t%s"
        lines.append(fmt % (self.tr("Image ID"), self.layerDef.imageid))

        if self.layerDef.bbox:
            extent = self.layerDef.bbox.toString()
        else:
            extent = self.tr("Not set")
        lines.append(fmt % (self.tr("Zoom range"), "%d - %d" % (self.layerDef.zmin, self.layerDef.zmax)))
        lines.append(fmt % (self.tr("Layer Extent"), extent))
        return "\n".join(lines)

    def showStatusMessage(self, msg, timeout=0):
        self.statusSignal.emit(msg, timeout)

    def showStatusMessageSlot(self, msg, timeout):
        self.iface.mainWindow().statusBar().showMessage(msg, timeout)

    def showMessageBar(self, text, level=QgsMessageBar.INFO, duration=0, imageid=None):
        if imageid is None:
            imageid = self.plugin.pluginName
        self.messageBarSignal.emit(imageid, text, level, duration)

    def showMessageBarSlot(self, imageid, text, level, duration):
        self.iface.messageBar().pushMessage(imageid, text, level, duration)

    def log(self, msg):
        if debug_mode:
            qDebug(msg)

    def logT(self, msg):
        if debug_mode:
            qDebug("%s: %s" % (str(threading.current_thread()), msg))