Ejemplo n.º 1
0
    def testSetColor(self):
        button = QgsFontButton()

        s = QgsTextFormat()
        s.setFont(getTestFont())
        s.setNamedStyle('Italic')
        s.setSize(5)
        s.setColor(QColor(255, 0, 0))
        s.setOpacity(0.5)
        button.setTextFormat(s)

        signal_spy = QSignalSpy(button.changed)
        button.setColor(QColor(0, 255, 0))
        self.assertEqual(len(signal_spy), 1)

        r = button.textFormat()
        self.assertEqual(r.font().family(), 'QGIS Vera Sans')
        self.assertEqual(r.namedStyle(), 'Italic')
        self.assertEqual(r.size(), 5)
        self.assertEqual(r.color().name(), QColor(0, 255, 0).name())
        self.assertEqual(r.opacity(), 0.5)

        # set same color, should not emit signal
        button.setColor(QColor(0, 255, 0))
        self.assertEqual(len(signal_spy), 1)

        # color with transparency - should be stripped
        button.setColor(QColor(0, 255, 0, 100))
        r = button.textFormat()
        self.assertEqual(r.color(), QColor(0, 255, 0))
Ejemplo n.º 2
0
 def createFormatSettings(self):
     s = QgsTextFormat()
     s.setBuffer(self.createBufferSettings())
     s.setBackground(self.createBackgroundSettings())
     s.setShadow(self.createShadowSettings())
     s.setFont(getTestFont())
     s.setNamedStyle('Roman')
     s.setSize(5)
     s.setSizeUnit(QgsUnitTypes.RenderPoints)
     s.setSizeMapUnitScale(QgsMapUnitScale(1, 2))
     s.setColor(QColor(255, 0, 0))
     s.setOpacity(0.5)
     s.setBlendMode(QPainter.CompositionMode_Difference)
     s.setLineHeight(5)
     return s
Ejemplo n.º 3
0
    def testSetGetFont(self):
        button = QgsFontButton()
        button.setMode(QgsFontButton.ModeQFont)
        self.assertEqual(button.mode(), QgsFontButton.ModeQFont)

        s = getTestFont()
        s.setPointSize(16)

        signal_spy = QSignalSpy(button.changed)
        button.setCurrentFont(s)
        self.assertEqual(len(signal_spy), 1)

        r = button.currentFont()
        self.assertEqual(r.family(), 'QGIS Vera Sans')
        self.assertEqual(r.styleName(), 'Roman')
        self.assertEqual(r.pointSize(), 16)
Ejemplo n.º 4
0
    def testSetGetFormat(self):
        button = QgsFontButton()

        s = QgsTextFormat()
        s.setFont(getTestFont())
        s.setNamedStyle('Italic')
        s.setSize(5)
        s.setColor(QColor(255, 0, 0))
        s.setOpacity(0.5)

        signal_spy = QSignalSpy(button.changed)
        button.setTextFormat(s)
        self.assertEqual(len(signal_spy), 1)

        r = button.textFormat()
        self.assertEqual(r.font().family(), 'QGIS Vera Sans')
        self.assertEqual(r.namedStyle(), 'Italic')
        self.assertEqual(r.size(), 5)
        self.assertEqual(r.color(), QColor(255, 0, 0))
        self.assertEqual(r.opacity(), 0.5)
Ejemplo n.º 5
0
    def testSetGetFormat(self):
        button = QgsFontButton()

        s = QgsTextFormat()
        s.setFont(getTestFont())
        s.setNamedStyle('Italic')
        s.setSize(5)
        s.setColor(QColor(255, 0, 0))
        s.setOpacity(0.5)

        signal_spy = QSignalSpy(button.changed)
        button.setTextFormat(s)
        self.assertEqual(len(signal_spy), 1)

        r = button.textFormat()
        self.assertEqual(r.font().family(), 'QGIS Vera Sans')
        self.assertEqual(r.namedStyle(), 'Italic')
        self.assertEqual(r.size(), 5)
        self.assertEqual(r.color(), QColor(255, 0, 0))
        self.assertEqual(r.opacity(), 0.5)
 def createFormatSettings(self):
     s = QgsTextFormat()
     s.setBuffer(self.createBufferSettings())
     s.setMask(self.createMaskSettings())
     s.setBackground(self.createBackgroundSettings())
     s.setShadow(self.createShadowSettings())
     font = getTestFont()
     font.setKerning(False)
     s.setFont(font)
     s.setNamedStyle('Roman')
     s.setSize(5)
     s.setSizeUnit(QgsUnitTypes.RenderPoints)
     s.setSizeMapUnitScale(QgsMapUnitScale(1, 2))
     s.setColor(QColor(255, 0, 0))
     s.setOpacity(0.5)
     s.setBlendMode(QPainter.CompositionMode_Difference)
     s.setLineHeight(5)
     s.setOrientation(QgsTextFormat.VerticalOrientation)
     s.setPreviewBackgroundColor(QColor(100, 150, 200))
     return s
Ejemplo n.º 7
0
    def testDataDefinedAnnotationDistance(self):
        layout = QgsLayout(QgsProject.instance())
        layout.initializeDefaults()
        map = QgsLayoutItemMap(layout)
        map.attemptSetSceneRect(QRectF(20, 20, 200, 100))
        map.setFrameEnabled(True)
        map.setBackgroundColor(QColor(150, 100, 100))
        layout.addLayoutItem(map)
        myRectangle = QgsRectangle(781662.375, 3339523.125,
                                   793062.375, 3345223.125)
        map.setExtent(myRectangle)
        map.grid().setEnabled(True)
        map.grid().setIntervalX(2000)
        map.grid().setIntervalY(2000)
        map.grid().setAnnotationEnabled(True)
        map.grid().setGridLineColor(QColor(0, 255, 0))
        map.grid().setGridLineWidth(0.5)

        format = QgsTextFormat.fromQFont(getTestFont('Bold', 20))
        format.setColor(QColor(255, 0, 0))
        format.setOpacity(150 / 255)
        map.grid().setAnnotationTextFormat(format)

        map.grid().setAnnotationPrecision(0)
        map.grid().setAnnotationDisplay(QgsLayoutItemMapGrid.HideAll, QgsLayoutItemMapGrid.Left)
        map.grid().setAnnotationPosition(QgsLayoutItemMapGrid.OutsideMapFrame, QgsLayoutItemMapGrid.Right)
        map.grid().setAnnotationDisplay(QgsLayoutItemMapGrid.HideAll, QgsLayoutItemMapGrid.Top)
        map.grid().setAnnotationPosition(QgsLayoutItemMapGrid.OutsideMapFrame, QgsLayoutItemMapGrid.Bottom)
        map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.Horizontal, QgsLayoutItemMapGrid.Right)
        map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.Horizontal, QgsLayoutItemMapGrid.Bottom)
        map.grid().setBlendMode(QPainter.CompositionMode_Overlay)
        map.updateBoundingRect()

        map.grid().dataDefinedProperties().setProperty(QgsLayoutObject.MapGridLabelDistance, QgsProperty.fromValue(10))
        map.grid().refresh()

        checker = QgsLayoutChecker('composermap_datadefined_annotationdistance', layout)
        checker.setControlPathPrefix("composer_mapgrid")
        myTestResult, myMessage = checker.testLayout()
        self.report += checker.report()
        self.assertTrue(myTestResult, myMessage)
Ejemplo n.º 8
0
    def testRenderWithTransform(self):
        item = QgsAnnotationPointTextItem('my text', QgsPointXY(12.3, 13.2))

        format = QgsTextFormat.fromQFont(getTestFont('Bold'))
        format.setColor(QColor(255, 0, 0))
        format.setOpacity(150 / 255)
        format.setSize(20)
        item.setFormat(format)

        item.setAngle(30)
        item.setAlignment(Qt.AlignRight)

        settings = QgsMapSettings()
        settings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
        settings.setExtent(QgsRectangle(1250958, 1386945, 1420709, 1532518))
        settings.setOutputSize(QSize(300, 300))

        settings.setFlag(QgsMapSettings.Antialiasing, False)

        rc = QgsRenderContext.fromMapSettings(settings)
        rc.setCoordinateTransform(
            QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:4326'),
                                   settings.destinationCrs(),
                                   QgsProject.instance()))
        image = QImage(200, 200, QImage.Format_ARGB32)
        image.setDotsPerMeterX(96 / 25.4 * 1000)
        image.setDotsPerMeterY(96 / 25.4 * 1000)
        image.fill(QColor(255, 255, 255))
        painter = QPainter(image)
        rc.setPainter(painter)

        try:
            item.render(rc, None)
        finally:
            painter.end()

        self.assertTrue(
            self.imageCheck('pointtext_item_transform',
                            'pointtext_item_transform', image))
Ejemplo n.º 9
0
class TestQgsPalLabeling(unittest.TestCase):

    _TestDataDir = unitTestDataPath()
    _PalDataDir = os.path.join(_TestDataDir, 'labeling')
    _PalFeaturesDb = os.path.join(_PalDataDir, 'pal_features_v3.sqlite')
    _TestFont = getTestFont()  # Roman at 12 pt
    """:type: QFont"""
    _MapRegistry = None
    """:type: QgsProject"""
    _MapSettings = None
    """:type: QgsMapSettings"""
    _Canvas = None
    """:type: QgsMapCanvas"""
    _BaseSetup = False

    @classmethod
    def setUpClass(cls):
        """Run before all tests"""

        # qgis iface
        cls._Iface = get_iface()
        cls._Canvas = cls._Iface.mapCanvas()

        # verify that spatialite provider is available
        msg = '\nSpatialite provider not found, SKIPPING TEST SUITE'
        # noinspection PyArgumentList
        res = 'spatialite' in QgsProviderRegistry.instance().providerList()
        assert res, msg

        cls._TestFunction = ''
        cls._TestGroup = ''
        cls._TestGroupPrefix = ''
        cls._TestGroupAbbr = ''
        cls._TestGroupCanvasAbbr = ''
        cls._TestImage = ''
        cls._TestMapSettings = None
        cls._Mismatch = 0
        cls._Mismatches = dict()
        cls._ColorTol = 0
        cls._ColorTols = dict()

        # initialize class MapRegistry, Canvas, MapRenderer, Map and PAL
        # noinspection PyArgumentList
        cls._MapRegistry = QgsProject.instance()

        cls._MapSettings = cls.getBaseMapSettings()
        osize = cls._MapSettings.outputSize()
        cls._Canvas.resize(QSize(osize.width(), osize.height()))  # necessary?
        # set color to match render test comparisons background
        cls._Canvas.setCanvasColor(cls._MapSettings.backgroundColor())

        cls.setDefaultEngineSettings()

        cls._BaseSetup = True

    @classmethod
    def tearDownClass(cls):
        """Run after all tests"""

    def setUp(self):
        """Run before each test."""
        TestQgsPalLabeling.setDefaultEngineSettings()
        self.lyr = self.defaultLayerSettings()

    @classmethod
    def setDefaultEngineSettings(cls):
        """Restore default settings for pal labeling"""
        cls._MapSettings.setLabelingEngineSettings(QgsLabelingEngineSettings())

    @classmethod
    def removeAllLayers(cls):
        cls._MapSettings.setLayers([])
        cls._MapRegistry.removeAllMapLayers()

    @classmethod
    def removeMapLayer(cls, layer):
        if layer is None:
            return
        lyr_id = layer.id()
        cls._MapRegistry.removeMapLayer(lyr_id)
        ms_layers = cls._MapSettings.layers()
        if layer in ms_layers:
            ms_layers.remove(layer)
            cls._MapSettings.setLayers(ms_layers)

    @classmethod
    def getTestFont(cls):
        return QFont(cls._TestFont)

    @classmethod
    def loadFeatureLayer(cls, table, chk=False):
        if chk and cls._MapRegistry.mapLayersByName(table):
            return
        uri = QgsDataSourceUri()
        uri.setDatabase(cls._PalFeaturesDb)
        uri.setDataSource('', table, 'geometry')
        vlayer = QgsVectorLayer(uri.uri(), table, 'spatialite')
        # .qml should contain only style for symbology
        vlayer.loadNamedStyle(
            os.path.join(cls._PalDataDir, '{0}.qml'.format(table)))
        # qDebug('render_lyr = {0}'.format(repr(vlayer)))
        cls._MapRegistry.addMapLayer(vlayer)
        # place new layer on top of render stack
        render_lyrs = [vlayer]
        render_lyrs.extend(cls._MapSettings.layers())
        # qDebug('render_lyrs = {0}'.format(repr(render_lyrs)))
        cls._MapSettings.setLayers(render_lyrs)

        # zoom to aoi
        cls._MapSettings.setExtent(cls.aoiExtent())
        cls._Canvas.zoomToFullExtent()
        return vlayer

    @classmethod
    def aoiExtent(cls):
        """Area of interest extent, which matches output aspect ratio"""
        uri = QgsDataSourceUri()
        uri.setDatabase(cls._PalFeaturesDb)
        uri.setDataSource('', 'aoi', 'geometry')
        aoilayer = QgsVectorLayer(uri.uri(), 'aoi', 'spatialite')
        return aoilayer.extent()

    @classmethod
    def getBaseMapSettings(cls):
        """
        :rtype: QgsMapSettings
        """
        ms = QgsMapSettings()
        crs = QgsCoordinateReferenceSystem()
        """:type: QgsCoordinateReferenceSystem"""
        # default for labeling test data: WGS 84 / UTM zone 13N
        crs.createFromSrid(32613)
        ms.setBackgroundColor(QColor(152, 219, 249))
        ms.setOutputSize(QSize(420, 280))
        ms.setOutputDpi(72)
        ms.setFlag(QgsMapSettings.Antialiasing, True)
        ms.setFlag(QgsMapSettings.UseAdvancedEffects, False)
        ms.setFlag(QgsMapSettings.ForceVectorOutput, False)  # no caching?
        ms.setDestinationCrs(crs)
        ms.setExtent(cls.aoiExtent())
        return ms

    def cloneMapSettings(self, oms):
        """
        :param QgsMapSettings oms: Other QgsMapSettings
        :rtype: QgsMapSettings
        """
        ms = QgsMapSettings()
        ms.setBackgroundColor(oms.backgroundColor())
        ms.setOutputSize(oms.outputSize())
        ms.setOutputDpi(oms.outputDpi())
        ms.setFlags(oms.flags())
        ms.setDestinationCrs(oms.destinationCrs())
        ms.setExtent(oms.extent())
        ms.setOutputImageFormat(oms.outputImageFormat())
        ms.setLabelingEngineSettings(oms.labelingEngineSettings())

        ms.setLayers(oms.layers())
        return ms

    def configTest(self, prefix, abbr):
        """Call in setUp() function of test subclass"""
        self._TestGroupPrefix = prefix
        self._TestGroupAbbr = abbr

        # insert test's Class.function marker into debug output stream
        # this helps visually track down the start of a test's debug output
        testid = self.id().split('.')
        self._TestGroup = testid[1]
        self._TestFunction = testid[2]
        testheader = '\n#####_____ {0}.{1} _____#####\n'.\
            format(self._TestGroup, self._TestFunction)
        qDebug(testheader)

        # define the shorthand name of the test (to minimize file name length)
        self._Test = '{0}_{1}'.format(self._TestGroupAbbr,
                                      self._TestFunction.replace('test_', ''))

    def defaultLayerSettings(self):
        lyr = QgsPalLayerSettings()
        lyr.enabled = True
        lyr.fieldName = 'text'  # default in test data sources
        font = self.getTestFont()
        font.setPointSize(32)
        format = lyr.format()
        format.setFont(font)
        format.setNamedStyle('Roman')
        format.setSize(32)
        format.setSizeUnit(QgsUnitTypes.RenderPoints)
        format.buffer().setJoinStyle(Qt.BevelJoin)
        lyr.setFormat(format)
        return lyr

    @staticmethod
    def settingsDict(lyr):
        """Return a dict of layer-level labeling settings

        .. note:: QgsPalLayerSettings is not a QObject, so we can not collect
        current object properties, and the public properties of the C++ obj
        can't be listed with __dict__ or vars(). So, we sniff them out relative
        to their naming convention (camelCase), as reported by dir().
        """
        res = {}
        for attr in dir(lyr):
            if attr[0].islower() and not attr.startswith("__"):
                value = getattr(lyr, attr)
                if not isinstance(value, collections.Callable):
                    res[attr] = value
        return res

    def controlImagePath(self, grpprefix=''):
        if not grpprefix:
            grpprefix = self._TestGroupPrefix
        return os.path.join(self._TestDataDir, 'control_images',
                            'expected_' + grpprefix, self._Test,
                            self._Test + '.png')

    def saveControlImage(self, tmpimg=''):
        # don't save control images for RenderVsOtherOutput (Vs) tests, since
        # those control images belong to a different test result
        if ('PAL_CONTROL_IMAGE' not in os.environ or 'Vs' in self._TestGroup):
            return
        imgpath = self.controlImagePath()
        testdir = os.path.dirname(imgpath)
        if not os.path.exists(testdir):
            os.makedirs(testdir)
        imgbasepath = \
            os.path.join(testdir,
                         os.path.splitext(os.path.basename(imgpath))[0])
        # remove any existing control images
        for f in glob.glob(imgbasepath + '.*'):
            if os.path.exists(f):
                os.remove(f)
        qDebug('Control image for {0}.{1}'.format(self._TestGroup,
                                                  self._TestFunction))

        if not tmpimg:
            # TODO: this can be deprecated, when per-base-test-class rendering
            #       in checkTest() is verified OK for all classes
            qDebug('Rendering control to: {0}'.format(imgpath))
            ms = self._MapSettings  # class settings
            """:type: QgsMapSettings"""
            settings_type = 'Class'
            if self._TestMapSettings is not None:
                ms = self._TestMapSettings  # per test settings
                settings_type = 'Test'
            qDebug('MapSettings type: {0}'.format(settings_type))

            img = renderMapToImage(ms, parallel=False)
            """:type: QImage"""
            tmpimg = getTempfilePath('png')
            if not img.save(tmpimg, 'png'):
                os.unlink(tmpimg)
                raise OSError('Control not created for: {0}'.format(imgpath))

        if tmpimg and os.path.exists(tmpimg):
            qDebug('Copying control to: {0}'.format(imgpath))
            shutil.copyfile(tmpimg, imgpath)
        else:
            raise OSError('Control not copied to: {0}'.format(imgpath))

    def renderCheck(self, mismatch=0, colortol=0, imgpath='', grpprefix=''):
        """Check rendered map canvas or existing image against control image

        :mismatch: number of pixels different from control, and still valid
        :colortol: maximum difference for each color component including alpha
        :imgpath: existing image; if present, skips rendering canvas
        :grpprefix: compare test image/rendering against different test group
        """
        if not grpprefix:
            grpprefix = self._TestGroupPrefix
        chk = QgsMultiRenderChecker()

        chk.setControlPathPrefix('expected_' + grpprefix)

        chk.setControlName(self._Test)

        if imgpath:
            chk.setRenderedImage(imgpath)

        ms = self._MapSettings  # class settings
        if self._TestMapSettings is not None:
            ms = self._TestMapSettings  # per test settings
        chk.setMapSettings(ms)

        chk.setColorTolerance(colortol)
        # noinspection PyUnusedLocal
        res = chk.runTest(self._Test, mismatch)
        if PALREPORT and not res:  # don't report ok checks
            testname = self._TestGroup + ' . ' + self._Test
            PALREPORTS[testname] = chk.report()
        msg = '\nRender check failed for "{0}"'.format(self._Test)
        return res, msg

    def checkTest(self, **kwargs):
        """Intended to be overridden in subclasses"""
        pass
Ejemplo n.º 10
0
class TestQgsPalLabeling(TestCase):

    _TestDataDir = unitTestDataPath()
    _PalDataDir = os.path.join(_TestDataDir, 'labeling')
    _PalFeaturesDb = os.path.join(_PalDataDir, 'pal_features_v3.sqlite')
    _TestFont = getTestFont()  # Roman at 12 pt
    """:type: QFont"""
    _MapRegistry = None
    """:type: QgsMapLayerRegistry"""
    _MapRenderer = None
    """:type: QgsMapRenderer"""
    _MapSettings = None
    """:type: QgsMapSettings"""
    _Canvas = None
    """:type: QgsMapCanvas"""
    _Map = None
    """:type: QgsMapCanvasMap"""
    _Pal = None
    """:type: QgsPalLabeling"""
    _PalEngine = None
    """:type: QgsLabelingEngineInterface"""
    @classmethod
    def setUpClass(cls):
        """Run before all tests"""

        # qgis instances
        cls._QgisApp, cls._Canvas, cls._Iface, cls._Parent = \
            QGISAPP, CANVAS, IFACE, PARENT

        # verify that spatialite provider is available
        msg = '\nSpatialite provider not found, SKIPPING TEST SUITE'
        # noinspection PyArgumentList
        res = 'spatialite' in QgsProviderRegistry.instance().providerList()
        assert res, msg

        cls._TestFunction = ''
        cls._TestGroup = ''
        cls._TestGroupPrefix = ''
        cls._TestGroupAbbr = ''
        cls._TestImage = ''

        # initialize class MapRegistry, Canvas, MapRenderer, Map and PAL
        # noinspection PyArgumentList
        cls._MapRegistry = QgsMapLayerRegistry.instance()
        # set color to match render test comparisons background
        cls._Canvas.setCanvasColor(QColor(152, 219, 249))
        cls._Map = cls._Canvas.map()
        cls._Map.resize(QSize(600, 400))  # is this necessary now?
        cls._MapRenderer = cls._Canvas.mapRenderer()

        cls._MapSettings = QgsMapSettings()
        cls._CRS = QgsCoordinateReferenceSystem()
        """:type: QgsCoordinateReferenceSystem"""
        # default for labeling test data sources: WGS 84 / UTM zone 13N
        cls._CRS.createFromSrid(32613)
        cls._MapSettings.setBackgroundColor(QColor(152, 219, 249))
        cls._MapSettings.setOutputSize(QSize(600, 400))
        cls._MapSettings.setOutputDpi(72)
        cls._MapSettings.setFlag(QgsMapSettings.Antialiasing)
        cls._MapSettings.setDestinationCrs(cls._CRS)
        cls._MapSettings.setCrsTransformEnabled(False)
        cls._MapSettings.setMapUnits(cls._CRS.mapUnits())  # meters
        cls._MapSettings.setExtent(cls.aoiExtent())

        cls.setDefaultEngineSettings()
        msg = ('\nCould not initialize PAL labeling engine, '
               'SKIPPING TEST SUITE')
        assert cls._PalEngine, msg

    @classmethod
    def setDefaultEngineSettings(cls):
        """Restore default settings for pal labelling"""
        cls._Pal = QgsPalLabeling()
        cls._MapRenderer.setLabelingEngine(cls._Pal)
        cls._PalEngine = cls._MapRenderer.labelingEngine()

    @classmethod
    def tearDownClass(cls):
        """Run after all tests"""
        cls.removeAllLayers()

    @classmethod
    def removeAllLayers(cls):
        cls._MapRegistry.removeAllMapLayers()

    @classmethod
    def getTestFont(cls):
        return QFont(cls._TestFont)

    @classmethod
    def loadFeatureLayer(cls, table):
        uri = QgsDataSourceURI()
        uri.setDatabase(cls._PalFeaturesDb)
        uri.setDataSource('', table, 'geometry')
        vlayer = QgsVectorLayer(uri.uri(), table, 'spatialite')
        # .qml should contain only style for symbology
        vlayer.loadNamedStyle(
            os.path.join(cls._PalDataDir, '{0}.qml'.format(table)))
        cls._MapRegistry.addMapLayer(vlayer)
        # place new layer on top of render stack
        render_lyrs = [vlayer.id()] + list(cls._MapSettings.layers())
        cls._MapSettings.setLayers(render_lyrs)

        # zoom to aoi
        cls._MapSettings.setExtent(cls.aoiExtent())
        cls._Canvas.zoomToFullExtent()
        return vlayer

    @classmethod
    def aoiExtent(cls):
        """Area of interest extent, which matches output aspect ratio"""
        uri = QgsDataSourceURI()
        uri.setDatabase(cls._PalFeaturesDb)
        uri.setDataSource('', 'aoi', 'geometry')
        aoilayer = QgsVectorLayer(uri.uri(), 'aoi', 'spatialite')
        return aoilayer.extent()

    def configTest(self, prefix, abbr):
        """Call in setUp() function of test subclass"""
        self._TestGroupPrefix = prefix
        self._TestGroupAbbr = abbr

        # insert test's Class.function marker into debug output stream
        # this helps visually track down the start of a test's debug output
        testid = self.id().split('.')
        self._TestGroup = testid[1]
        self._TestFunction = testid[2]
        testheader = '\n#####_____ {0}.{1} _____#####\n'.\
            format(self._TestGroup, self._TestFunction)
        qDebug(testheader)

        # define the shorthand name of the test (to minimize file name length)
        self._Test = '{0}_{1}'.format(self._TestGroupAbbr,
                                      self._TestFunction.replace('test_', ''))

    def defaultSettings(self):
        lyr = QgsPalLayerSettings()
        lyr.enabled = True
        lyr.fieldName = 'text'  # default in data sources
        font = self.getTestFont()
        font.setPointSize(48)
        lyr.textFont = font
        lyr.textNamedStyle = 'Roman'
        return lyr

    @staticmethod
    def settingsDict(lyr):
        """Return a dict of layer-level labeling settings

        .. note:: QgsPalLayerSettings is not a QObject, so we can not collect
        current object properties, and the public properties of the C++ obj
        can't be listed with __dict__ or vars(). So, we sniff them out relative
        to their naming convention (camelCase), as reported by dir().
        """
        res = {}
        for attr in dir(lyr):
            if attr[0].islower() and not attr.startswith("__"):
                value = getattr(lyr, attr)
                if not callable(value):
                    res[attr] = value
        return res

    def saveContolImage(self, tmpimg=''):
        # don't save control images for RenderVsOtherOutput (Vs) tests, since
        # those control images belong to a different test result
        if ('PAL_CONTROL_IMAGE' not in os.environ or 'Vs' in self._TestGroup):
            return
        testgrpdir = 'expected_' + self._TestGroupPrefix
        testdir = os.path.join(self._TestDataDir, 'control_images', testgrpdir,
                               self._Test)
        if not os.path.exists(testdir):
            os.makedirs(testdir)
        imgbasepath = os.path.join(testdir, self._Test)
        imgpath = imgbasepath + '.png'
        for f in glob.glob(imgbasepath + '.*'):
            if os.path.exists(f):
                os.remove(f)
        if tmpimg:
            if os.path.exists(tmpimg):
                shutil.copyfile(tmpimg, imgpath)
        else:
            self._Map.render()
            self._Canvas.saveAsImage(imgpath)
            # delete extraneous world file (always generated)
            wrld_file = imgbasepath + '.PNGw'
            if os.path.exists(wrld_file):
                os.remove(wrld_file)

    def renderCheck(self, mismatch=0, imgpath='', grpprefix=''):
        """Check rendered map canvas or existing image against control image

        mismatch: number of pixels different from control, and still valid check
        imgpath: existing image; if present, skips rendering canvas
        grpprefix: compare test image/rendering against different test group
        """
        if not grpprefix:
            grpprefix = self._TestGroupPrefix
        chk = QgsRenderChecker()
        chk.setControlPathPrefix('expected_' + grpprefix)
        chk.setControlName(self._Test)
        chk.setMapSettings(self._MapSettings)
        # noinspection PyUnusedLocal
        res = False
        if imgpath:
            res = chk.compareImages(self._Test, mismatch, str(imgpath))
        else:
            res = chk.runTest(self._Test, mismatch)
        if PALREPORT and not res:  # don't report ok checks
            testname = self._TestGroup + ' . ' + self._Test
            PALREPORTS[testname] = str(chk.report().toLocal8Bit())
        msg = '\nRender check failed for "{0}"'.format(self._Test)
        return res, msg
Ejemplo n.º 11
0
    def testEditorWidget(self):
        c = QgsConditionalStyle()
        c.setName('')

        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle(), c)
        w.setRule('my rule')
        self.assertEqual(w.currentStyle().rule(), 'my rule')

        c.setName('n')
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle(), c)

        c.setRule('1=1')
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle(), c)

        c.setBackgroundColor(QColor(255, 0, 0))
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle(), c)

        c.setTextColor(QColor(0, 255, 0))
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle(), c)

        c.setSymbol(QgsMarkerSymbol.createSimple({}))
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle(), c)

        f = getTestFont()
        c.setFont(f)
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle().font(), c.font())

        f.setBold(True)
        c.setFont(f)
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle().font().bold(), True)

        f.setItalic(True)
        c.setFont(f)
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle().font().italic(), True)

        f.setStrikeOut(True)
        c.setFont(f)
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle().font().strikeOut(), True)

        f.setUnderline(True)
        c.setFont(f)
        w = QgsEditConditionalFormatRuleWidget()
        w.loadStyle(c)
        self.assertEqual(w.currentStyle().font().underline(), True)
Ejemplo n.º 12
0
    def testDynamicInterval(self):
        layout = QgsLayout(QgsProject.instance())
        layout.initializeDefaults()
        map = QgsLayoutItemMap(layout)
        map.attemptSetSceneRect(QRectF(20, 20, 200, 100))
        map.setFrameEnabled(True)
        map.setBackgroundColor(QColor(150, 100, 100))
        layout.addLayoutItem(map)
        myRectangle = QgsRectangle(781662.375, 3339523.125, 793062.375,
                                   3345223.125)
        map.setExtent(myRectangle)
        map.grid().setEnabled(True)
        map.grid().setUnits(QgsLayoutItemMapGrid.DynamicPageSizeBased)
        map.grid().setMinimumIntervalWidth(50)
        map.grid().setMaximumIntervalWidth(100)
        map.grid().setAnnotationEnabled(True)
        map.grid().setGridLineColor(QColor(0, 255, 0))
        map.grid().setGridLineWidth(0.5)
        map.grid().setAnnotationFont(getTestFont('Bold', 20))
        map.grid().setAnnotationPrecision(0)
        map.grid().setAnnotationDisplay(QgsLayoutItemMapGrid.HideAll,
                                        QgsLayoutItemMapGrid.Left)
        map.grid().setAnnotationPosition(QgsLayoutItemMapGrid.OutsideMapFrame,
                                         QgsLayoutItemMapGrid.Right)
        map.grid().setAnnotationDisplay(QgsLayoutItemMapGrid.HideAll,
                                        QgsLayoutItemMapGrid.Top)
        map.grid().setAnnotationPosition(QgsLayoutItemMapGrid.OutsideMapFrame,
                                         QgsLayoutItemMapGrid.Bottom)
        map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.Horizontal,
                                          QgsLayoutItemMapGrid.Right)
        map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.Horizontal,
                                          QgsLayoutItemMapGrid.Bottom)
        map.grid().setAnnotationFontColor(QColor(255, 0, 0, 150))
        map.grid().setBlendMode(QPainter.CompositionMode_Overlay)
        map.updateBoundingRect()

        map.grid().refresh()

        checker = QgsLayoutChecker('composermap_dynamic_5_10', layout)
        checker.setControlPathPrefix("composer_mapgrid")
        myTestResult, myMessage = checker.testLayout()
        self.assertTrue(myTestResult, myMessage)

        map.setScale(map.scale() * 1.1)

        checker = QgsLayoutChecker('composermap_dynamic_5_10_2', layout)
        checker.setControlPathPrefix("composer_mapgrid")
        myTestResult, myMessage = checker.testLayout()
        self.assertTrue(myTestResult, myMessage)

        map.setScale(map.scale() * 1.8)

        checker = QgsLayoutChecker('composermap_dynamic_5_10_3', layout)
        checker.setControlPathPrefix("composer_mapgrid")
        myTestResult, myMessage = checker.testLayout()
        self.assertTrue(myTestResult, myMessage)

        map.grid().setMinimumIntervalWidth(10)
        map.grid().setMaximumIntervalWidth(40)
        map.grid().refresh()

        checker = QgsLayoutChecker('composermap_dynamic_5_10_4', layout)
        checker.setControlPathPrefix("composer_mapgrid")
        myTestResult, myMessage = checker.testLayout()
        self.assertTrue(myTestResult, myMessage)
Ejemplo n.º 13
0
    def testDataDefinedTicksAndAnnotationDisplay(self):
        layout = QgsLayout(QgsProject.instance())
        layout.initializeDefaults()
        map = QgsLayoutItemMap(layout)
        map.attemptSetSceneRect(QRectF(40, 20, 200, 100))
        map.setFrameEnabled(True)
        map.setBackgroundColor(QColor(150, 100, 100))
        layout.addLayoutItem(map)
        myRectangle = QgsRectangle(0.5, -5.5, 10.5, 0.5)
        map.setExtent(myRectangle)
        map.setMapRotation(45)
        map.grid().setEnabled(True)
        map.grid().setIntervalX(1)
        map.grid().setIntervalY(1)
        map.grid().setAnnotationEnabled(True)
        map.grid().setGridLineColor(QColor(0, 255, 0))
        map.grid().setGridLineWidth(0.5)
        map.grid().setFrameStyle(QgsLayoutItemMapGrid.ExteriorTicks)
        map.grid().setFrameWidth(4)
        map.grid().setFramePenSize(1)
        map.grid().setFramePenColor(QColor(0, 0, 255))
        map.grid().setAnnotationFrameDistance(5)

        format = QgsTextFormat.fromQFont(getTestFont('Bold', 20))
        format.setColor(QColor(255, 0, 0))
        format.setOpacity(150 / 255)
        map.grid().setAnnotationTextFormat(format)
        map.grid().setAnnotationPrecision(0)

        map.grid().setRotatedTicksEnabled(True)
        map.grid().setRotatedAnnotationsEnabled(True)
        map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.OnTick)

        map.grid().dataDefinedProperties().setProperty(
            QgsLayoutObject.MapGridAnnotationDisplayLeft,
            QgsProperty.fromValue("x_only"))
        map.grid().dataDefinedProperties().setProperty(
            QgsLayoutObject.MapGridAnnotationDisplayRight,
            QgsProperty.fromValue("Y_ONLY"))
        map.grid().dataDefinedProperties().setProperty(
            QgsLayoutObject.MapGridAnnotationDisplayTop,
            QgsProperty.fromValue("disabled"))
        map.grid().dataDefinedProperties().setProperty(
            QgsLayoutObject.MapGridAnnotationDisplayBottom,
            QgsProperty.fromValue("ALL"))
        map.grid().dataDefinedProperties().setProperty(
            QgsLayoutObject.MapGridFrameDivisionsLeft,
            QgsProperty.fromValue("X_ONLY"))
        map.grid().dataDefinedProperties().setProperty(
            QgsLayoutObject.MapGridFrameDivisionsRight,
            QgsProperty.fromValue("y_only"))
        map.grid().dataDefinedProperties().setProperty(
            QgsLayoutObject.MapGridFrameDivisionsTop,
            QgsProperty.fromValue("DISABLED"))
        map.grid().dataDefinedProperties().setProperty(
            QgsLayoutObject.MapGridFrameDivisionsBottom,
            QgsProperty.fromValue("all"))

        map.grid().refresh()

        checker = QgsLayoutChecker(
            'composermap_datadefined_ticksandannotationdisplay', layout)
        checker.setControlPathPrefix("composer_mapgrid")
        myTestResult, myMessage = checker.testLayout()
        self.report += checker.report()
        self.assertTrue(myTestResult, myMessage)
Ejemplo n.º 14
0
    def testAnnotationsVariationsRotatedThresholds(self):
        """
        Tests various rotated grid threshold settings
        """
        layout = QgsLayout(QgsProject.instance())
        layout.initializeDefaults()

        map_configs = [
            (10, 30, QgsLayoutItemMapGrid.OutsideMapFrame,
             QgsLayoutItemMapGrid.InteriorTicks, True, False),
            (10, 120, QgsLayoutItemMapGrid.InsideMapFrame,
             QgsLayoutItemMapGrid.ExteriorTicks, True, False),
            (170, 30, QgsLayoutItemMapGrid.OutsideMapFrame,
             QgsLayoutItemMapGrid.InteriorTicks, False, True),
            (170, 120, QgsLayoutItemMapGrid.InsideMapFrame,
             QgsLayoutItemMapGrid.ExteriorTicks, False, True),
        ]

        for x, y, pos, style, limit_rot, limit_corners in map_configs:

            map = QgsLayoutItemMap(layout)
            layout.addLayoutItem(map)
            map.attemptSetSceneRect(QRectF(x, y, 100, 50))
            map.setExtent(QgsRectangle(5000000, 800000, 6000000, 1300000))
            map.setBackgroundColor(QColor(200, 200, 200))
            map.setMapRotation(0)
            map.setFrameEnabled(True)
            map.setCrs(QgsCoordinateReferenceSystem.fromEpsgId(2056))
            map.grid().setCrs(QgsCoordinateReferenceSystem.fromEpsgId(4326))
            map.grid().setFrameStyle(style)
            map.grid().setFrameWidth(7)
            map.grid().setFramePenSize(1)
            map.grid().setFramePenColor(QColor(255, 0, 0))
            map.grid().setEnabled(True)
            map.grid().setIntervalX(2)
            map.grid().setIntervalY(2)
            map.grid().setAnnotationEnabled(True)
            map.grid().setGridLineColor(QColor(0, 255, 0))
            map.grid().setGridLineWidth(0.5)
            map.grid().setRotatedTicksLengthMode(
                QgsLayoutItemMapGrid.NormalizedTicks)
            map.grid().setAnnotationFont(getTestFont('Bold', 15))
            map.grid().setAnnotationFontColor(QColor(0, 0, 255, 150))
            map.grid().setAnnotationPrecision(0)
            map.grid().setAnnotationFrameDistance(2.5)
            map.grid().setRotatedTicksEnabled(True)
            map.grid().setRotatedAnnotationsEnabled(True)

            map.grid().setAnnotationPosition(pos, QgsLayoutItemMapGrid.Top)
            map.grid().setAnnotationPosition(pos, QgsLayoutItemMapGrid.Right)
            map.grid().setAnnotationPosition(pos, QgsLayoutItemMapGrid.Bottom)
            map.grid().setAnnotationPosition(pos, QgsLayoutItemMapGrid.Left)

            map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.OnTick,
                                              QgsLayoutItemMapGrid.Top)
            map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.OnTick,
                                              QgsLayoutItemMapGrid.Right)
            map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.OnTick,
                                              QgsLayoutItemMapGrid.Bottom)
            map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.OnTick,
                                              QgsLayoutItemMapGrid.Left)

            if limit_rot:
                map.grid().setRotatedAnnotationsMinimumAngle(30)
                map.grid().setRotatedTicksMinimumAngle(30)

            if limit_corners:
                map.grid().setRotatedAnnotationsMarginToCorner(10)
                map.grid().setRotatedTicksMarginToCorner(10)

            map.updateBoundingRect()

        checker = QgsLayoutChecker(
            'composermap_annotations_variations_rotated_thresholds', layout)
        checker.setControlPathPrefix("composer_mapgrid")
        myTestResult, myMessage = checker.testLayout()
        self.assertTrue(myTestResult, myMessage)
Ejemplo n.º 15
0
    def testAnnotationsVariationsRotated(self):
        layout = QgsLayout(QgsProject.instance())
        layout.initializeDefaults()

        map_configs = [
            (10, 30, QgsLayoutItemMapGrid.OutsideMapFrame,
             QgsLayoutItemMapGrid.InteriorTicks, 0),
            (10, 120, QgsLayoutItemMapGrid.OutsideMapFrame,
             QgsLayoutItemMapGrid.InteriorTicks, 3),
            (90, 30, QgsLayoutItemMapGrid.OutsideMapFrame,
             QgsLayoutItemMapGrid.ExteriorTicks, 0),
            (90, 120, QgsLayoutItemMapGrid.OutsideMapFrame,
             QgsLayoutItemMapGrid.ExteriorTicks, 3),
            (170, 30, QgsLayoutItemMapGrid.InsideMapFrame,
             QgsLayoutItemMapGrid.InteriorTicks, 0),
            (170, 120, QgsLayoutItemMapGrid.InsideMapFrame,
             QgsLayoutItemMapGrid.InteriorTicks, 3),
            (250, 30, QgsLayoutItemMapGrid.InsideMapFrame,
             QgsLayoutItemMapGrid.ExteriorTicks, 0),
            (250, 120, QgsLayoutItemMapGrid.InsideMapFrame,
             QgsLayoutItemMapGrid.ExteriorTicks, 3),
        ]

        for x, y, pos, style, dist in map_configs:

            map = QgsLayoutItemMap(layout)
            layout.addLayoutItem(map)
            map.attemptSetSceneRect(QRectF(x, y, 50, 50))
            map.setBackgroundColor(QColor(200, 200, 200))
            map.setExtent(QgsRectangle(5, 5, 15, 15))
            map.setMapRotation(30)
            map.setFrameEnabled(True)
            map.grid().setFrameStyle(style)
            map.grid().setFrameWidth(7)
            map.grid().setFramePenSize(1)
            map.grid().setFramePenColor(QColor(255, 0, 0))
            map.grid().setEnabled(True)
            map.grid().setIntervalX(10)
            map.grid().setIntervalY(10)
            map.grid().setAnnotationEnabled(True)
            map.grid().setGridLineColor(QColor(0, 255, 0))
            map.grid().setGridLineWidth(0.5)
            map.grid().setAnnotationFont(getTestFont('Bold', 20))
            map.grid().setAnnotationFontColor(QColor(0, 0, 255, 150))
            map.grid().setAnnotationPrecision(0)
            map.grid().setAnnotationFrameDistance(dist)
            map.grid().setRotatedTicksEnabled(True)
            map.grid().setRotatedAnnotationsEnabled(True)

            map.grid().setAnnotationPosition(pos, QgsLayoutItemMapGrid.Top)
            map.grid().setAnnotationPosition(pos, QgsLayoutItemMapGrid.Right)
            map.grid().setAnnotationPosition(pos, QgsLayoutItemMapGrid.Bottom)
            map.grid().setAnnotationPosition(pos, QgsLayoutItemMapGrid.Left)

            map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.AboveTick,
                                              QgsLayoutItemMapGrid.Top)
            map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.OnTick,
                                              QgsLayoutItemMapGrid.Right)
            map.grid().setAnnotationDirection(QgsLayoutItemMapGrid.UnderTick,
                                              QgsLayoutItemMapGrid.Bottom)
            map.grid().setAnnotationDirection(
                QgsLayoutItemMapGrid.BoundaryDirection,
                QgsLayoutItemMapGrid.Left)

            map.updateBoundingRect()

        checker = QgsLayoutChecker(
            'composermap_annotations_variations_rotated', layout)
        checker.setControlPathPrefix("composer_mapgrid")
        myTestResult, myMessage = checker.testLayout()
        self.assertTrue(myTestResult, myMessage)
Ejemplo n.º 16
0
class TestQgsPalLabeling(TestCase):

    _TestDataDir = unitTestDataPath()
    _PalDataDir = os.path.join(_TestDataDir, 'labeling')
    _PalFeaturesDb = os.path.join(_PalDataDir, 'pal_features_v3.sqlite')
    _TestFont = getTestFont()  # Roman at 12 pt
    """:type: QFont"""
    _MapRegistry = None
    """:type: QgsMapLayerRegistry"""
    _MapRenderer = None
    """:type: QgsMapRenderer"""
    _MapSettings = None
    """:type: QgsMapSettings"""
    _Canvas = None
    """:type: QgsMapCanvas"""
    _Pal = None
    """:type: QgsPalLabeling"""
    _PalEngine = None
    """:type: QgsLabelingEngineInterface"""
    _BaseSetup = False

    @classmethod
    def setUpClass(cls):
        """Run before all tests"""

        # qgis instances
        cls._QgisApp, cls._Canvas, cls._Iface, cls._Parent = \
            QGISAPP, CANVAS, IFACE, PARENT

        # verify that spatialite provider is available
        msg = '\nSpatialite provider not found, SKIPPING TEST SUITE'
        # noinspection PyArgumentList
        res = 'spatialite' in QgsProviderRegistry.instance().providerList()
        assert res, msg

        cls._TestFunction = ''
        cls._TestGroup = ''
        cls._TestGroupPrefix = ''
        cls._TestGroupAbbr = ''
        cls._TestGroupCanvasAbbr = ''
        cls._TestImage = ''
        cls._TestMapSettings = None
        cls._Mismatch = 0
        cls._Mismatches = dict()
        cls._ColorTol = 0
        cls._ColorTols = dict()

        # initialize class MapRegistry, Canvas, MapRenderer, Map and PAL
        # noinspection PyArgumentList
        cls._MapRegistry = QgsMapLayerRegistry.instance()
        cls._MapRenderer = cls._Canvas.mapRenderer()

        cls._MapSettings = cls.getBaseMapSettings()
        osize = cls._MapSettings.outputSize()
        cls._Canvas.resize(QSize(osize.width(), osize.height()))  # necessary?
        # set color to match render test comparisons background
        cls._Canvas.setCanvasColor(cls._MapSettings.backgroundColor())

        cls.setDefaultEngineSettings()
        msg = ('\nCould not initialize PAL labeling engine, '
               'SKIPPING TEST SUITE')
        assert cls._PalEngine, msg

        cls._BaseSetup = True

    @classmethod
    def tearDownClass(cls):
        """Run after all tests"""

    def setUp(self):
        """Run before each test."""
        TestQgsPalLabeling.setDefaultEngineSettings()
        self.lyr = self.defaultLayerSettings()

    @classmethod
    def setDefaultEngineSettings(cls):
        """Restore default settings for pal labelling"""
        cls._Pal = QgsPalLabeling()
        cls._MapRenderer.setLabelingEngine(cls._Pal)
        cls._PalEngine = cls._MapRenderer.labelingEngine()

    @classmethod
    def removeAllLayers(cls):
        cls._MapRegistry.removeAllMapLayers()
        cls._MapSettings.setLayers([])

    @classmethod
    def removeMapLayer(cls, layer):
        if layer is None:
            return
        lyr_id = layer.id()
        cls._MapRegistry.removeMapLayer(lyr_id)
        ms_layers = cls._MapSettings.layers()
        """:type: QStringList"""
        if ms_layers.contains(lyr_id):
            ms_layers.removeAt(ms_layers.indexOf(lyr_id))
            cls._MapSettings.setLayers(ms_layers)

    @classmethod
    def getTestFont(cls):
        return QFont(cls._TestFont)

    @classmethod
    def loadFeatureLayer(cls, table, chk=False):
        if chk and cls._MapRegistry.mapLayersByName(table):
            return
        uri = QgsDataSourceURI()
        uri.setDatabase(cls._PalFeaturesDb)
        uri.setDataSource('', table, 'geometry')
        vlayer = QgsVectorLayer(uri.uri(), table, 'spatialite')
        # .qml should contain only style for symbology
        vlayer.loadNamedStyle(
            os.path.join(cls._PalDataDir, '{0}.qml'.format(table)))
        # qDebug('render_lyr = {0}'.format(repr(vlayer)))
        cls._MapRegistry.addMapLayer(vlayer)
        # place new layer on top of render stack
        render_lyrs = [vlayer.id()]
        render_lyrs.extend(cls._MapSettings.layers())
        # qDebug('render_lyrs = {0}'.format(repr(render_lyrs)))
        cls._MapSettings.setLayers(render_lyrs)

        # zoom to aoi
        cls._MapSettings.setExtent(cls.aoiExtent())
        cls._Canvas.zoomToFullExtent()
        return vlayer

    @classmethod
    def aoiExtent(cls):
        """Area of interest extent, which matches output aspect ratio"""
        uri = QgsDataSourceURI()
        uri.setDatabase(cls._PalFeaturesDb)
        uri.setDataSource('', 'aoi', 'geometry')
        aoilayer = QgsVectorLayer(uri.uri(), 'aoi', 'spatialite')
        return aoilayer.extent()

    @classmethod
    def getBaseMapSettings(cls):
        """
        :rtype: QgsMapSettings
        """
        ms = QgsMapSettings()
        crs = QgsCoordinateReferenceSystem()
        """:type: QgsCoordinateReferenceSystem"""
        # default for labeling test data: WGS 84 / UTM zone 13N
        crs.createFromSrid(32613)
        ms.setBackgroundColor(QColor(152, 219, 249))
        ms.setOutputSize(QSize(420, 280))
        ms.setOutputDpi(72)
        ms.setFlag(QgsMapSettings.Antialiasing)
        ms.setDestinationCrs(crs)
        ms.setCrsTransformEnabled(False)
        ms.setMapUnits(crs.mapUnits())  # meters
        ms.setExtent(cls.aoiExtent())
        return ms

    def cloneMapSettings(self, oms):
        """
        :param oms: QgsMapSettings
        :rtype: QgsMapSettings
        """
        ms = QgsMapSettings()
        ms.setBackgroundColor(oms.backgroundColor())
        ms.setOutputSize(oms.outputSize())
        ms.setOutputDpi(oms.outputDpi())
        ms.setFlags(oms.flags())
        ms.setDestinationCrs(oms.destinationCrs())
        ms.setCrsTransformEnabled(oms.hasCrsTransformEnabled())
        ms.setMapUnits(oms.mapUnits())
        ms.setExtent(oms.extent())

        ms.setLayers(oms.layers())
        return ms

    def configTest(self, prefix, abbr):
        """Call in setUp() function of test subclass"""
        self._TestGroupPrefix = prefix
        self._TestGroupAbbr = abbr

        # insert test's Class.function marker into debug output stream
        # this helps visually track down the start of a test's debug output
        testid = self.id().split('.')
        self._TestGroup = testid[1]
        self._TestFunction = testid[2]
        testheader = '\n#####_____ {0}.{1} _____#####\n'.\
            format(self._TestGroup, self._TestFunction)
        qDebug(testheader)

        # define the shorthand name of the test (to minimize file name length)
        self._Test = '{0}_{1}'.format(self._TestGroupAbbr,
                                      self._TestFunction.replace('test_', ''))

    def defaultLayerSettings(self):
        lyr = QgsPalLayerSettings()
        lyr.enabled = True
        lyr.fieldName = 'text'  # default in test data sources
        font = self.getTestFont()
        font.setPointSize(32)
        lyr.textFont = font
        lyr.textNamedStyle = 'Roman'
        return lyr

    @staticmethod
    def settingsDict(lyr):
        """Return a dict of layer-level labeling settings

        .. note:: QgsPalLayerSettings is not a QObject, so we can not collect
        current object properties, and the public properties of the C++ obj
        can't be listed with __dict__ or vars(). So, we sniff them out relative
        to their naming convention (camelCase), as reported by dir().
        """
        res = {}
        for attr in dir(lyr):
            if attr[0].islower() and not attr.startswith("__"):
                value = getattr(lyr, attr)
                if not callable(value):
                    res[attr] = value
        return res

    def controlImagePath(self, grpprefix=''):
        if not grpprefix:
            grpprefix = self._TestGroupPrefix
        return os.path.join(self._TestDataDir, 'control_images',
                            'expected_' + grpprefix, self._Test,
                            self._Test + '.png')

    def saveControlImage(self, tmpimg=''):
        # don't save control images for RenderVsOtherOutput (Vs) tests, since
        # those control images belong to a different test result
        if ('PAL_CONTROL_IMAGE' not in os.environ or 'Vs' in self._TestGroup):
            return
        imgpath = self.controlImagePath()
        # print "saveControlImage: {0}".format(imgpath)
        testdir = os.path.dirname(imgpath)
        if not os.path.exists(testdir):
            os.makedirs(testdir)
        imgbasepath = \
            os.path.join(testdir,
                         os.path.splitext(os.path.basename(imgpath))[0])
        for f in glob.glob(imgbasepath + '.*'):
            if os.path.exists(f):
                os.remove(f)
        if tmpimg and os.path.exists(tmpimg):
            shutil.copyfile(tmpimg, imgpath)
        else:
            print '\nsaveControlImage.render(): entered'
            print '{0}.{1}'.format(self._TestGroup, self._TestFunction)

            ms = self._MapSettings  # class settings
            """:type: QgsMapSettings"""
            if self._TestMapSettings is not None:
                ms = self._TestMapSettings  # per test settings
            print 'self._MapSettings...'
            print 'ms.layers(): {0}'.format(
                [self._MapRegistry.mapLayer(i).name() for i in ms.layers()])
            print 'ms.outputSize(): {0} x {1}'.format(ms.outputSize().width(),
                                                      ms.outputSize().height())
            print 'ms.outputDpi(): {0}'.format(ms.outputDpi())
            print 'ms.mapUnits(): {0}'.format(ms.mapUnits())
            print 'ms.extent(): {0}'.format(ms.extent().toString())
            print 'ms.hasCrsTransformEnabled(): {0}'.format(
                ms.hasCrsTransformEnabled())
            print 'ms.destinationCrs(): {0}'.format(
                ms.destinationCrs().authid())

            # pal = QgsPalLabeling()
            pal = self._Pal.clone()  # or custom settings are lost
            pal.init(ms)
            r = QgsMapRenderer()
            r.setLabelingEngine(pal)

            # this seems too redundant
            r.setOutputSize(ms.outputSize(), ms.outputDpi())
            r.setMapUnits(ms.mapUnits())
            r.setExtent(ms.extent())
            r.setProjectionsEnabled(ms.hasCrsTransformEnabled())
            r.setDestinationCrs(ms.destinationCrs())
            r.setLayerSet(ms.layers())

            ctx = r.rendererContext()
            ctx.setDrawEditingInformation(
                ms.testFlag(QgsMapSettings.DrawEditingInfo))
            ctx.setForceVectorOutput(
                ms.testFlag(QgsMapSettings.ForceVectorOutput))
            ctx.setUseAdvancedEffects(
                ms.testFlag(QgsMapSettings.UseAdvancedEffects))

            image = QImage(ms.outputSize(), QImage.Format_ARGB32)
            image.fill(ms.backgroundColor().rgb())
            image.setDotsPerMeterX(ms.outputDpi() / 25.4 * 1000)
            image.setDotsPerMeterY(ms.outputDpi() / 25.4 * 1000)

            p = QPainter(image)
            r.render(p)
            p.end()

            if not image.save(imgpath, 'png'):
                os.unlink(imgpath)

            # delete extraneous world file (always generated)
            # wrld_file = imgbasepath + '.PNGw'
            # if os.path.exists(wrld_file):
            #     os.remove(wrld_file)

        if not os.path.exists(imgpath):
            raise OSError('Control image not created: {0}'.format(imgpath))

    def renderCheck(self, mismatch=0, colortol=0, imgpath='', grpprefix=''):
        """Check rendered map canvas or existing image against control image

        :mismatch: number of pixels different from control, and still valid
        :colortol: maximum difference for each color component including alpha
        :imgpath: existing image; if present, skips rendering canvas
        :grpprefix: compare test image/rendering against different test group
        """
        if not grpprefix:
            grpprefix = self._TestGroupPrefix
        ctl_path = self.controlImagePath(grpprefix)
        if not os.path.exists(ctl_path):
            raise OSError('Missing control image: {0}'.format(ctl_path))
        chk = QgsRenderChecker()
        chk.setControlPathPrefix('expected_' + grpprefix)
        chk.setControlName(self._Test)
        chk.setColorTolerance(colortol)
        chk.setMapSettings(self._MapSettings)
        # noinspection PyUnusedLocal
        res = False
        if imgpath:
            res = chk.compareImages(self._Test, mismatch, str(imgpath))
        else:
            res = chk.runTest(self._Test, mismatch)
        if PALREPORT and not res:  # don't report ok checks
            testname = self._TestGroup + ' . ' + self._Test
            PALREPORTS[testname] = str(chk.report().toLocal8Bit())
        msg = '\nRender check failed for "{0}"'.format(self._Test)
        return res, msg

    def checkTest(self, **kwargs):
        """Intended to be overridden in subclasses"""
        pass