Skip to content
Snippets Groups Projects
utils.py 47.8 KiB
Newer Older
  • Learn to ignore specific revisions
  •     else:
            if math.fabs(number) > 1:
                return '{:0.2f}'.format(number)
            else:
                return '{:f}'.format(number)
    
    
    
    
    def nicePredecessor(l):
        mul = -1 if l < 0 else 1
        l = np.abs(l)
        if l > 1.0:
            exp = np.fix(np.log10(l))
            # normalize to [0.0,1.0]
            l2 = l / 10 ** (exp)
            m = np.fix(l2)
            rest = l2 - m
            if rest >= 0.5:
                m += 0.5
    
            return mul * m * 10 ** exp
    
        elif l < 1.0:
            exp = np.fix(np.log10(l))
            #normalize to [0.0,1.0]
            m = l / 10 ** (exp-1)
            if m >= 5:
                m = 5.0
            else:
                m = 1.0
            return mul * m * 10 ** (exp-1)
        else:
            return 0.0
    
    
    loadUI = lambda p : loadUIFormClass(jp(DIR_UI, p))
    
    loadIcon = lambda p: jp(DIR_UI, *['icons',p])
    
    #dictionary to store form classes and avoid multiple calls to read <myui>.ui
    FORM_CLASSES = dict()
    
    
    def loadUIFormClass(pathUi, from_imports=False, resourceSuffix=''):
    
        """
        Loads Qt UI files (*.ui) while taking care on QgsCustomWidgets.
    
        Uses PyQt4.uic.loadUiType (see http://pyqt.sourceforge.net/Docs/PyQt4/designer.html#the-uic-module)
    
        :param pathUi: *.ui file path
        :param from_imports:  is optionally set to use import statements that are relative to '.'. At the moment this only applies to the import of resource modules.
        :param resourceSuffix: is the suffix appended to the basename of any resource file specified in the .ui file to create the name of the Python module generated from the resource file by pyrcc4. The default is '_rc', i.e. if the .ui file specified a resource file called foo.qrc then the corresponding Python module is foo_rc.
        :return: the form class, e.g. to be used in a class definition like MyClassUI(QFrame, loadUi('myclassui.ui'))
        """
    
        RC_SUFFIX =  resourceSuffix
        assert os.path.exists(pathUi), '*.ui file does not exist: {}'.format(pathUi)
    
    
        if pathUi not in FORM_CLASSES.keys():
            #parse *.ui xml and replace *.h by qgis.gui
            doc = QDomDocument()
    
            #remove new-lines. this prevents uic.loadUiType(buffer, resource_suffix=RC_SUFFIX)
            #to mess up the *.ui xml
    
            f = open(pathUi, 'r')
            txt = ''.join(f.readlines())
            f.close()
    
            doc.setContent(txt)
    
            # Replace *.h file references in <customwidget> with <class>Qgs...</class>, e.g.
            #       <header>qgscolorbutton.h</header>
            # by    <header>qgis.gui</header>
            # this is require to compile QgsWidgets on-the-fly
            elem = doc.elementsByTagName('customwidget')
            for child in [elem.item(i) for i in range(elem.count())]:
                child = child.toElement()
                className = str(child.firstChildElement('class').firstChild().nodeValue())
                if className.startswith('Qgs'):
                    cHeader = child.firstChildElement('header').firstChild()
                    cHeader.setNodeValue('qgis.gui')
    
            #collect resource file locations
            elem = doc.elementsByTagName('include')
            qrcPathes = []
            for child in [elem.item(i) for i in range(elem.count())]:
                path = child.attributes().item(0).nodeValue()
                if path.endswith('.qrc'):
                    qrcPathes.append(path)
    
    
    
    
            buffer = io.StringIO()  # buffer to store modified XML
    
            buffer.write(doc.toString())
            buffer.flush()
            buffer.seek(0)
    
    
            #make resource file directories available to the python path (sys.path)
            baseDir = os.path.dirname(pathUi)
            tmpDirs = []
            for qrcPath in qrcPathes:
                d = os.path.dirname(os.path.join(baseDir, os.path.dirname(qrcPath)))
                if d not in sys.path:
                    tmpDirs.append(d)
            sys.path.extend(tmpDirs)
    
            #load form class
            try:
                FORM_CLASS, _ = uic.loadUiType(buffer, resource_suffix=RC_SUFFIX)
            except SyntaxError as ex:
                FORM_CLASS, _ = uic.loadUiType(pathUi, resource_suffix=RC_SUFFIX)
    
            buffer.close()
    
            FORM_CLASSES[pathUi] = FORM_CLASS
    
            #remove temporary added directories from python path
            for d in tmpDirs:
                sys.path.remove(d)
    
        return FORM_CLASSES[pathUi]
    
    
    def zipdir(pathDir, pathZip):
        """
        :param pathDir: directory to compress
        :param pathZip: path to new zipfile
        """
        #thx to https://stackoverflow.com/questions/1855095/how-to-create-a-zip-archive-of-a-directory
        """
        import zipfile
        assert os.path.isdir(pathDir)
        zipf = zipfile.ZipFile(pathZip, 'w', zipfile.ZIP_DEFLATED)
        for root, dirs, files in os.walk(pathDir):
            for file in files:
                zipf.write(os.path.join(root, file))
        zipf.close()
        """
        import zipfile
        relroot = os.path.abspath(os.path.join(pathDir, os.pardir))
        with zipfile.ZipFile(pathZip, "w", zipfile.ZIP_DEFLATED) as zip:
            for root, dirs, files in os.walk(pathDir):
                # add directory (needed for empty dirs)
                zip.write(root, os.path.relpath(root, relroot))
                for file in files:
                    filename = os.path.join(root, file)
                    if os.path.isfile(filename):  # regular files only
                        arcname = os.path.join(os.path.relpath(root, relroot), file)
                        zip.write(filename, arcname)
    
    
            for zname in zip.namelist():
                if zname.find('..') != -1 or zname.find(os.path.sep) == 0:
                    s = ""
    
            s  =""
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    class TestObjects():
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        @staticmethod
        def createTestImageSeries(n=1) -> list:
            assert n > 0
    
            datasets = []
            for i in range(n):
                ds = TestObjects.inMemoryImage()
                datasets.append(ds)
            return datasets
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        @staticmethod
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def inMemoryImage(nl=10, ns=20, nb=3, crs='EPSG:32632', eType:int=None, path:str=None)->gdal.Dataset:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            Create an in-memory gdal.Dataset
            :param nl:
            :param ns:
            :param nb:
            :param crs:
            :return:
            """
            drv = gdal.GetDriverByName('GTiff')
            assert isinstance(drv, gdal.Driver)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            id = uuid.uuid4()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if not isinstance(path, str):
                path = '/vsimem/testimage.multiband.{}.tif'.format(id)
    
            if eType is None:
                eType = gdal.GDT_Float32
    
            assert isinstance(eType, int) and eType >= 0
    
            ds = drv.Create(path, ns, nl, bands=nb, eType=eType)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            if isinstance(crs, str):
                c = QgsCoordinateReferenceSystem(crs)
                ds.SetProjection(c.toWkt())
    
            gt = [1000,30,0, \
                  1000,0 ,-30]
    
            ds.SetGeoTransform(gt)
            for b in range(1, nb + 1):
                band = ds.GetRasterBand(b)
                assert isinstance(band, gdal.Band)
                array = np.random.random((nl, ns)) - 1
                band.WriteArray(array)
            ds.FlushCache()
            return ds
    
    
    
        @staticmethod
        def inMemoryClassification(n=3, nl=10, ns=20, nb=1, crs='EPSG:32632'):
            from .classificationscheme import ClassificationScheme
            scheme = ClassificationScheme()
            scheme.createClasses(n)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            drv = gdal.GetDriverByName('GTiff')
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(drv, gdal.Driver)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            id = uuid.uuid4()
            path = '/vsimem/testimage.class._{}.tif'.format(id)
            ds = drv.Create(path, ns, nl, bands=nb, eType=gdal.GDT_Byte)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            if isinstance(crs, str):
                c = QgsCoordinateReferenceSystem(crs)
                ds.SetProjection(c.toWkt())
    
            step = int(np.ceil(float(nl) / len(scheme)))
    
            assert isinstance(ds, gdal.Dataset)
            for b in range(1, nb + 1):
                band = ds.GetRasterBand(b)
                array = np.zeros((nl, ns), dtype=np.uint8) - 1
                y0 = 0
                for i, c in enumerate(scheme):
                    y1 = min(y0 + step, nl - 1)
                    array[y0:y1, :] = c.label()
                    y0 += y1 + 1
                band.SetCategoryNames(scheme.classNames())
                band.SetColorTable(scheme.gdalColorTable())
            ds.FlushCache()
            return ds
    
        @staticmethod
        def qgisInterfaceMockup():
    
            return QgisMockup()
    
        @staticmethod
        def createDropEvent(mimeData:QMimeData):
            """Creates a QDropEvent conaining the provided QMimeData"""
            return QDropEvent(QPointF(0, 0), Qt.CopyAction, mimeData, Qt.LeftButton, Qt.NoModifier)
    
    
        @staticmethod
        def processingAlgorithm():
    
            from qgis.core import QgsProcessingAlgorithm
    
            class TestProcessingAlgorithm(QgsProcessingAlgorithm):
    
                def __init__(self):
                    super(TestProcessingAlgorithm, self).__init__()
                    s = ""
    
                def createInstance(self):
                    return TestProcessingAlgorithm()
    
                def name(self):
                    return 'exmaplealg'
    
                def displayName(self):
                    return 'Example Algorithm'
    
                def groupId(self):
                    return 'exampleapp'
    
                def group(self):
                    return 'TEST APPS'
    
                def initAlgorithm(self, configuration=None):
                    self.addParameter(QgsProcessingParameterRasterLayer('pathInput', 'The Input Dataset'))
                    self.addParameter(
                        QgsProcessingParameterNumber('value', 'The value', QgsProcessingParameterNumber.Double, 1, False,
                                                     0.00, 999999.99))
                    self.addParameter(QgsProcessingParameterRasterDestination('pathOutput', 'The Output Dataset'))
    
                def processAlgorithm(self, parameters, context, feedback):
                    assert isinstance(parameters, dict)
                    assert isinstance(context, QgsProcessingContext)
                    assert isinstance(feedback, QgsProcessingFeedback)
    
    
                    outputs = {}
                    return outputs
    
            return TestProcessingAlgorithm()
    
    
    
        @staticmethod
        def enmapBoxApplication():
    
            from enmapbox.gui.applications import EnMAPBoxApplication
            from enmapbox.gui.enmapboxgui import EnMAPBox
            enmapbox = EnMAPBox.instance()
    
            class TestApp(EnMAPBoxApplication):
                def __init__(self, enmapbox):
                    super(TestApp, self).__init__(enmapbox)
    
                    self.name = 'TestApp'
                    self.licence = 'GPL-3'
                    self.version = '-12345'
    
                def menu(self, appMenu:QMenu)->QMenu:
                    menu = appMenu.addMenu('Test Menu')
                    action = menu.addAction('Test Action')
                    action.triggered.connect(self.onAction)
                    return menu
    
                def onAction(self):
                    print('TestApp action called')
    
                def processingAlgorithms(self):
                    return [TestObjects.processingAlgorithm()]
    
            return TestApp(enmapbox)
    
    
    
    class QgisMockup(QgisInterface):
        """
        A "fake" QGIS Desktop instance that should provide all the inferfaces a plugin developer might need (and nothing more)
        """
    
        def pluginManagerInterface(self)->QgsPluginManagerInterface:
            return self.mPluginManager
    
        @staticmethod
        def create()->QgisInterface:
            """
            Create the QgisMockup and sets the global variables
            :return: QgisInterface
            """
    
            iface = QgisMockup()
    
            import qgis.utils
            # import processing
            # p = processing.classFactory(iface)
            if not isinstance(qgis.utils.iface, QgisInterface):
    
                import processing
                qgis.utils.iface = iface
                processing.Processing.initialize()
    
                import pkgutil
                prefix = str(processing.__name__ + '.')
                for importer, modname, ispkg in pkgutil.walk_packages(processing.__path__, prefix=prefix):
                    try:
                        module = __import__(modname, fromlist="dummy")
                        if hasattr(module, 'iface'):
                            print(modname)
                            module.iface = iface
                    except:
                        pass
            #set 'home_plugin_path', which is required from the QGIS Plugin manager
            assert qgis.utils.iface == iface
            qgis.utils.home_plugin_path = os.path.join(QgsApplication.instance().qgisSettingsDirPath(), *['python', 'plugins'])
            return iface
    
        def __init__(self, *args):
            # QgisInterface.__init__(self)
            super(QgisMockup, self).__init__()
    
            self.mCanvas = QgsMapCanvas()
            self.mCanvas.blockSignals(False)
            self.mCanvas.setCanvasColor(Qt.black)
            self.mCanvas.extentsChanged.connect(self.testSlot)
            self.mLayerTreeView = QgsLayerTreeView()
            self.mRootNode = QgsLayerTree()
            self.mLayerTreeModel = QgsLayerTreeModel(self.mRootNode)
            self.mLayerTreeView.setModel(self.mLayerTreeModel)
            self.mLayerTreeMapCanvasBridge = QgsLayerTreeMapCanvasBridge(self.mRootNode, self.mCanvas)
            self.mLayerTreeMapCanvasBridge.setAutoSetupOnFirstLayer(True)
    
            import pyplugin_installer.installer
            PI = pyplugin_installer.instance()
            self.mPluginManager = QgsPluginManagerMockup()
    
            self.ui = QMainWindow()
    
    
    
            self.mMessageBar = QgsMessageBar()
            mainFrame = QFrame()
    
            self.ui.setCentralWidget(mainFrame)
            self.ui.setWindowTitle('QGIS Mockup')
    
    
            l = QHBoxLayout()
            l.addWidget(self.mLayerTreeView)
            l.addWidget(self.mCanvas)
            v = QVBoxLayout()
            v.addWidget(self.mMessageBar)
            v.addLayout(l)
            mainFrame.setLayout(v)
            self.ui.setCentralWidget(mainFrame)
            self.lyrs = []
            self.createActions()
    
        def iconSize(self, dockedToolbar=False):
            return QSize(30,30)
    
        def testSlot(self, *args):
            # print('--canvas changes--')
            s = ""
    
        def mainWindow(self):
            return self.ui
    
    
        def addToolBarIcon(self, action):
            assert isinstance(action, QAction)
    
        def removeToolBarIcon(self, action):
            assert isinstance(action, QAction)
    
    
        def addVectorLayer(self, path, basename=None, providerkey=None):
            if basename is None:
                basename = os.path.basename(path)
            if providerkey is None:
                bn, ext = os.path.splitext(basename)
    
                providerkey = 'ogr'
            l = QgsVectorLayer(path, basename, providerkey)
            assert l.isValid()
            QgsProject.instance().addMapLayer(l, True)
            self.mRootNode.addLayer(l)
            self.mLayerTreeMapCanvasBridge.setCanvasLayers()
            s = ""
    
        def legendInterface(self):
            return None
    
        def addRasterLayer(self, path, baseName=''):
            l = QgsRasterLayer(path, os.path.basename(path))
            self.lyrs.append(l)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            QgsProject.instance().addMapLayer(l, True)
            self.mRootNode.addLayer(l)
            self.mLayerTreeMapCanvasBridge.setCanvasLayers()
            return
    
            cnt = len(self.canvas.layers())
    
    
            self.canvas.setLayers([QgsMapCanvasLayer(l)])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            l.dataProvider()
            if cnt == 0:
                self.canvas.mapSettings().setDestinationCrs(l.crs())
                self.canvas.setExtent(l.extent())
    
                spatialExtent = SpatialExtent.fromMapLayer(l)
                # self.canvas.blockSignals(True)
                self.canvas.setDestinationCrs(spatialExtent.crs())
                self.canvas.setExtent(spatialExtent)
                # self.blockSignals(False)
                self.canvas.refresh()
    
            self.canvas.refresh()
    
        def createActions(self):
            m = self.ui.menuBar().addAction('Add Vector')
            m = self.ui.menuBar().addAction('Add Raster')
    
        def mapCanvas(self):
            return self.mCanvas
    
        def mapNavToolToolBar(self):
            super().mapNavToolToolBar()
    
        def messageBar(self, *args, **kwargs):
            return self.mMessageBar
    
        def rasterMenu(self):
            super().rasterMenu()
    
        def vectorMenu(self):
            super().vectorMenu()
    
        def viewMenu(self):
            super().viewMenu()
    
        def windowMenu(self):
            super().windowMenu()
    
        def zoomFull(self, *args, **kwargs):
            super().zoomFull(*args, **kwargs)
    
    
    
    
    class PythonRunnerImpl(QgsPythonRunner):
        """
        A Qgs PythonRunner implementation
        """
    
        def __init__(self):
            super(PythonRunnerImpl, self).__init__()
    
    
        def evalCommand(self, cmd:str, result:str):
            try:
                o = compile(cmd)
            except Exception as ex:
                result = str(ex)
                return False
            return True
    
        def runCommand(self, command, messageOnError=''):
            try:
                o = compile(command, 'fakemodule', 'exec')
                exec(o)
            except Exception as ex:
                messageOnError = str(ex)
                command = ['{}:{}'.format(i+1, l) for i,l in enumerate(command.splitlines())]
                print('\n'.join(command), file=sys.stderr)
                raise ex
                return False
            return True
    
    
    
    def createCRSTransform(src, dst):
        assert isinstance(src, QgsCoordinateReferenceSystem)
        assert isinstance(dst, QgsCoordinateReferenceSystem)
        t = QgsCoordinateTransform()
        t.setSourceCrs(src)
        t.setDestinationCrs(dst)
        return t