def addDHMasWMS(self):
        crs = self.gh.getGetMapCrs(self.iface).authid()
        if crs != 'EPSG:31370' or  crs != 'EPSG:3857' or  crs != 'EPSG:3043':
           crs = 'EPSG:31370' 
        dhmUrl =  "url=https://geoservices.informatievlaanderen.be/raadpleegdiensten/DHMV/wms&layers=DHMVII_DTM_1m&&format=image/png&styles=default&crs="+ crs

        try:
            rlayer = QgsRasterLayer(dhmUrl, 'Hoogtemodel', 'wms') 
            if rlayer.isValid():
               rlayer.renderer().setOpacity(0.8)
               QgsProject.instance().addMapLayer(rlayer)
            else: self.bar.pushMessage("Error", 
                QCoreApplication.translate("geopunt4QgisElevationDialog", "Kan WMS niet laden"), 
                level=Qgis.Critical, duration=10) 
        except: 
            self.bar.pushMessage("Error", str( sys.exc_info()[1] ), level=Qgis.Critical, duration=10)
            return 
Example #2
0
def display_one_band(layer, keyword, iface):
    index_group = TerreImageConstant().index_group
    logger.debug("keyword " + str(keyword))
    corres = {'red': "_bande_rouge", 'green': "_bande_verte", 'blue': "_bande_bleue",
              'pir': "_bande_pir", 'mir': "_bande_mir", "nat": "_couleurs_naturelles"}

    raster_layer = QgsRasterLayer(layer.get_source(), layer.name() + corres[keyword])

    if keyword == 'nat':
        logger.debug("display on natural colors")
        band_red = layer.bands['red']
        band_green = layer.bands['green']
        band_blue = layer.bands['blue']
        renderer = raster_layer.renderer()
        # raster_layer.setDrawingStyle("MultiBandColor")
        renderer.setRedBand(band_red)
        renderer.setGreenBand(band_green)
        renderer.setBlueBand(band_blue)
        # raster_layer.setRenderer( renderer )
        # contrastForRasters( raster_layer, 0, 0, [pir, red, green] )
        histogram_stretching(raster_layer, iface.mapCanvas())
        QgsMapLayerRegistry.instance().addMapLayer(raster_layer)
        TerreImageConstant().legendInterface.moveLayer(raster_layer, index_group)
        return raster_layer
    else:

        band = layer.bands[keyword]
        if band:
            logger.debug("band num: " + str(band))
            raster_layer.setDrawingStyle("MultiBandSingleBandGray")
            renderer = raster_layer.renderer()
            logger.debug(renderer)
            renderer.setGrayBand(band)

            # contrastForRasters( raster_layer, 0, 0 )
            histogram_stretching(raster_layer, iface.mapCanvas())
            QgsMapLayerRegistry.instance().addMapLayer(raster_layer)
            TerreImageConstant().legendInterface.moveLayer(raster_layer, index_group)
            return raster_layer
Example #3
0
    def test_setRenderer(self):
        myPath = os.path.join(unitTestDataPath("raster"), "band1_float32_noct_epsg4326.tif")
        myFileInfo = QFileInfo(myPath)
        myBaseName = myFileInfo.baseName()
        layer = QgsRasterLayer(myPath, myBaseName)

        self.rendererChanged = False
        layer.rendererChanged.connect(self.onRendererChanged)

        rShader = QgsRasterShader()
        r = QgsSingleBandPseudoColorRenderer(layer.dataProvider(), 1, rShader)

        layer.setRenderer(r)
        assert self.rendererChanged
        assert layer.renderer() == r
Example #4
0
    def testClone(self):
        myPath = os.path.join(unitTestDataPath('raster'),
                              'band1_float32_noct_epsg4326.tif')
        myFileInfo = QFileInfo(myPath)
        myBaseName = myFileInfo.baseName()
        layer = QgsRasterLayer(myPath, myBaseName)

        renderer = layer.renderer().clone()
        renderer.setOpacity(33.3)
        layer.setRenderer(renderer)

        # clone layer
        clone = layer.clone()

        # generate xml from layer
        layer_doc = QDomDocument("doc")
        layer_elem = layer_doc.createElement("maplayer")
        layer.writeLayerXml(layer_elem, layer_doc, QgsReadWriteContext())

        # generate xml from clone
        clone_doc = QDomDocument("doc")
        clone_elem = clone_doc.createElement("maplayer")
        clone.writeLayerXml(clone_elem, clone_doc, QgsReadWriteContext())

        # replace id within xml of clone
        clone_id_elem = clone_elem.firstChildElement("id")
        clone_id_elem_patch = clone_doc.createElement("id")
        clone_id_elem_patch_value = clone_doc.createTextNode(layer.id())
        clone_id_elem_patch.appendChild(clone_id_elem_patch_value)
        clone_elem.replaceChild(clone_id_elem_patch, clone_id_elem)

        # update doc
        clone_doc.appendChild(clone_elem)
        layer_doc.appendChild(layer_elem)

        # compare xml documents
        self.assertEqual(layer_doc.toString(), clone_doc.toString())
Example #5
0
def fix_style(layer: QgsRasterLayer) -> None:
    ''' Sets a sensible default style for loaded raster layers and fix up other issues.

    By default QGIS uses MultiBandColor renderer if there are multiple bands.
    (See https://github.com/qgis/QGIS/blob/final-3_0_1/src/core/raster/qgsrasterlayer.cpp#L729).
    This function picks the right renderers, either a palette or gray-band renderer.

    Also, fake categories/classes that exist due to GDAL limitations are removed.
    Search for UNUSED_CATEGORY_LABEL to find details. '''
    provider = layer.dataProvider()  # type: QgsRasterDataProvider
    color_interp = provider.colorInterpretation(1)
    is_palette = color_interp == QgsRaster.PaletteIndex

    # See the link below on how to create default-type renderers.
    # https://github.com/qgis/QGIS/blob/final-3_0_1/src/core/raster/qgsrasterrendererregistry.cpp#L128-L137

    renderer = layer.renderer()  # type: QgsRasterRenderer
    new_renderer = None
    if is_palette:
        # For paletted layers we always re-create the renderer even if it is already a
        # paletted renderer. This is because we need to remove the UNUSED categories.
        color_table = provider.colorTable(1)
        classes = QgsPalettedRasterRenderer.colorTableToClassData(color_table)
        if not any(c.label == gis4wrf.core.UNUSED_CATEGORY_LABEL
                   for c in classes):
            return
        new_classes = filter(
            lambda c: c.label != gis4wrf.core.UNUSED_CATEGORY_LABEL, classes)
        new_renderer = QgsPalettedRasterRenderer(renderer.input(), 1,
                                                 new_classes)
        layer.setRenderer(new_renderer)
    else:
        if not isinstance(renderer, QgsSingleBandGrayRenderer):
            new_renderer = QgsSingleBandGrayRenderer(renderer.input(), 1)
            layer.setRenderer(new_renderer)
            layer.setDefaultContrastEnhancement(
            )  # must be *after* setting the renderer
Example #6
0
    def testClone(self):
        myPath = os.path.join(unitTestDataPath('raster'),
                              'band1_float32_noct_epsg4326.tif')
        myFileInfo = QFileInfo(myPath)
        myBaseName = myFileInfo.baseName()
        layer = QgsRasterLayer(myPath, myBaseName)

        renderer = layer.renderer().clone()
        renderer.setOpacity(33.3)
        layer.setRenderer(renderer)

        # clone layer
        clone = layer.clone()

        # generate xml from layer
        layer_doc = QDomDocument("doc")
        layer_elem = layer_doc.createElement("maplayer")
        layer.writeLayerXml(layer_elem, layer_doc, QgsReadWriteContext())

        # generate xml from clone
        clone_doc = QDomDocument("doc")
        clone_elem = clone_doc.createElement("maplayer")
        clone.writeLayerXml(clone_elem, clone_doc, QgsReadWriteContext())

        # replace id within xml of clone
        clone_id_elem = clone_elem.firstChildElement("id")
        clone_id_elem_patch = clone_doc.createElement("id")
        clone_id_elem_patch_value = clone_doc.createTextNode(layer.id())
        clone_id_elem_patch.appendChild(clone_id_elem_patch_value)
        clone_elem.replaceChild(clone_id_elem_patch, clone_id_elem)

        # update doc
        clone_doc.appendChild(clone_elem)
        layer_doc.appendChild(layer_elem)

        # compare xml documents
        self.assertEqual(layer_doc.toString(), clone_doc.toString())
Example #7
0
    def test_homogenize_colors(self):
        """Test color homogenize"""

        tmp_dir = QTemporaryDir()
        shutil.copy(
            os.path.join(os.path.dirname(__file__), 'data',
                         'ExistingVegetationTypes_sample.img'), tmp_dir.path())
        shutil.copy(
            os.path.join(os.path.dirname(__file__), 'data',
                         'ExistingVegetationTypes_sample.img.vat.dbf'),
            tmp_dir.path())

        raster_layer = QgsRasterLayer(
            os.path.join(tmp_dir.path(), 'ExistingVegetationTypes_sample.img'),
            'rat_test', 'gdal')
        rat = get_rat(raster_layer, 1)
        self.assertTrue(rat.isValid())

        unique_labels = rat_classify(raster_layer, 1, rat, 'EVT_NAME')
        if Qgis.QGIS_VERSION_INT >= 31800:
            self.assertEqual(unique_labels, list(range(1, 60)))
        else:
            self.assertEqual(unique_labels, list(range(0, 59)))

        # Get color map
        color_map = {}
        for klass in raster_layer.renderer().classes():
            color_map[klass.value] = klass.color.name()

        # Two different colors for EVT_NAME
        self.assertEqual(color_map[11.0], '#0000ff')
        self.assertEqual(color_map[12.0], '#9fa1f0')

        # Reclass
        unique_labels = rat_classify(raster_layer, 1, rat, 'NVCSCLASS')
        if Qgis.QGIS_VERSION_INT >= 31800:
            self.assertEqual(unique_labels, [1, 3, 5, 6, 7, 22, 31, 41, 44])
        else:
            self.assertEqual(unique_labels, [0, 2, 4, 5, 6, 21, 30, 40, 43])

        color_map = {}
        for klass in raster_layer.renderer().classes():
            color_map[klass.value] = klass.color.name()

        # Same colors for NVCSCLASS
        self.assertEqual(color_map[11.0], '#0000ff')
        self.assertEqual(color_map[12.0], '#0000ff')

        # Manually change one color
        classes = raster_layer.renderer().classes()
        classes[0].color = QColor(10, 20, 30)

        renderer = QgsPalettedRasterRenderer(raster_layer.dataProvider(), 1,
                                             classes)
        raster_layer.setRenderer(renderer)

        color_map = {}
        for klass in raster_layer.renderer().classes():
            color_map[klass.value] = klass.color.name()

        # Manually changed colors for NVCSCLASS
        self.assertEqual(color_map[11.0], '#0a141e')
        self.assertEqual(color_map[12.0], '#0000ff')

        self.assertTrue(homogenize_colors(raster_layer))

        color_map = {}
        for klass in raster_layer.renderer().classes():
            color_map[klass.value] = klass.color.name()

        # Same colors for NVCSCLASS
        self.assertEqual(color_map[11.0], '#0a141e')
        self.assertEqual(color_map[12.0], '#0a141e')
Example #8
0
pcolor = []

pcolor.append(QgsColorRampShader.ColorRampItem(1, QColor("#d2ca97")))
pcolor.append(QgsColorRampShader.ColorRampItem(2, QColor("#f7f7f7")))
pcolor.append(QgsColorRampShader.ColorRampItem(3, QColor("#a1d99b")))
pcolor.append(QgsColorRampShader.ColorRampItem(4, QColor("#41ab5d")))
pcolor.append(QgsColorRampShader.ColorRampItem(5, QColor("#006d2c")))
pcolor.append(QgsColorRampShader.ColorRampItem(6, QColor("#00441b")))

renderer = QgsPalettedRasterRenderer(layer.dataProvider(), 1,
                                     QgsPalettedRasterRenderer.colorTableToClassData(pcolor))
layer.setRenderer(renderer)

extent = layer.extent()
width, height = layer.width(), layer.height()
renderer = layer.renderer()
provider = layer.dataProvider()
crs = layer.crs().toWkt()
pipe = QgsRasterPipe()
pipe.set(provider.clone())
pipe.set(renderer.clone())
file_writer = QgsRasterFileWriter("C:/temp/naip/classified_res.tif")
file_writer.writeRaster(pipe,
                        width,
                        height,
                        extent,
                        layer.crs())

print("Done!")
Example #9
0
class TestQgsRasterRendererCreateSld(unittest.TestCase):
    """
     This class tests the creation of SLD from QGis raster layers
    """
    @classmethod
    def setUpClass(self):
        pass

    def setUp(self):
        pass

    def tearDown(self):
        pass

    def __init__(self, methodName):
        """Run once on class initialization."""
        unittest.TestCase.__init__(self, methodName)
        myPath = os.path.join(TEST_DATA_DIR, 'landsat.tif')
        rasterFileInfo = QFileInfo(myPath)
        self.raster_layer = QgsRasterLayer(rasterFileInfo.filePath(),
                                           rasterFileInfo.completeBaseName())

    def testSingleBandPseudoColorRenderer_Interpolated(self):
        # get min and max of the band to renderer
        bandNo = 3
        stats = self.raster_layer.dataProvider().bandStatistics(
            bandNo, QgsRasterBandStats.Min | QgsRasterBandStats.Max)
        minValue = stats.minimumValue
        maxValue = stats.maximumValue
        # create shader for the renderer
        shader = QgsRasterShader(minValue, maxValue)
        colorRampShaderFcn = QgsColorRampShader(minValue, maxValue)
        colorRampShaderFcn.setColorRampType(QgsColorRampShader.Interpolated)
        colorRampShaderFcn.setClassificationMode(QgsColorRampShader.Continuous)
        colorRampShaderFcn.setClip(True)
        items = []
        for index in range(10):
            items.append(
                QgsColorRampShader.ColorRampItem(
                    index, QColor('#{0:02d}{0:02d}{0:02d}'.format(index)),
                    "{}".format(index)))
        colorRampShaderFcn.setColorRampItemList(items)
        shader.setRasterShaderFunction(colorRampShaderFcn)
        # create instance to test
        rasterRenderer = QgsSingleBandPseudoColorRenderer(
            self.raster_layer.dataProvider(), bandNo, shader)
        self.raster_layer.setRenderer(rasterRenderer)

        # do test
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '{}'.format(bandNo))
        # check ColorMapEntry classes
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertFalse(colorMap.isNull())
        self.assertEqual(colorMap.attribute('type'), 'ramp')
        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(colorMapEntries.count(), 10)
        for index in range(colorMapEntries.count()):
            colorMapEntry = colorMapEntries.at(index).toElement()
            self.assertEqual(colorMapEntry.attribute('quantity'),
                             '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('label'),
                             '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('opacity'), '')
            self.assertEqual(colorMapEntry.attribute('color'),
                             '#{0:02d}{0:02d}{0:02d}'.format(index))

    def testSingleBandPseudoColorRenderer_Discrete(self):
        # get min and max of the band to renderer
        bandNo = 3
        stats = self.raster_layer.dataProvider().bandStatistics(
            bandNo, QgsRasterBandStats.Min | QgsRasterBandStats.Max)
        minValue = stats.minimumValue
        maxValue = stats.maximumValue
        # create shader for the renderer
        shader = QgsRasterShader(minValue, maxValue)
        colorRampShaderFcn = QgsColorRampShader(minValue, maxValue)
        colorRampShaderFcn.setColorRampType(QgsColorRampShader.Discrete)
        colorRampShaderFcn.setClassificationMode(QgsColorRampShader.Continuous)
        colorRampShaderFcn.setClip(True)
        items = []
        for index in range(10):
            items.append(
                QgsColorRampShader.ColorRampItem(
                    index, QColor('#{0:02d}{0:02d}{0:02d}'.format(index)),
                    "{}".format(index)))
        colorRampShaderFcn.setColorRampItemList(items)
        shader.setRasterShaderFunction(colorRampShaderFcn)
        # create instance to test
        rasterRenderer = QgsSingleBandPseudoColorRenderer(
            self.raster_layer.dataProvider(), bandNo, shader)
        self.raster_layer.setRenderer(rasterRenderer)

        # do test
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '{}'.format(bandNo))
        # check ColorMapEntry classes
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertFalse(colorMap.isNull())
        self.assertEqual(colorMap.attribute('type'), 'intervals')
        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(colorMapEntries.count(), 10)
        for index in range(colorMapEntries.count()):
            colorMapEntry = colorMapEntries.at(index).toElement()
            self.assertEqual(colorMapEntry.attribute('quantity'),
                             '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('label'),
                             '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('opacity'), '')
            self.assertEqual(colorMapEntry.attribute('color'),
                             '#{0:02d}{0:02d}{0:02d}'.format(index))

    def testSingleBandPseudoColorRenderer_Exact(self):
        # get min and max of the band to renderer
        bandNo = 3
        stats = self.raster_layer.dataProvider().bandStatistics(
            bandNo, QgsRasterBandStats.Min | QgsRasterBandStats.Max)
        minValue = stats.minimumValue
        maxValue = stats.maximumValue
        # create shader for the renderer
        shader = QgsRasterShader(minValue, maxValue)
        colorRampShaderFcn = QgsColorRampShader(minValue, maxValue)
        colorRampShaderFcn.setColorRampType(QgsColorRampShader.Exact)
        colorRampShaderFcn.setClassificationMode(QgsColorRampShader.Continuous)
        colorRampShaderFcn.setClip(True)
        items = []
        for index in range(10):
            items.append(
                QgsColorRampShader.ColorRampItem(
                    index, QColor('#{0:02d}{0:02d}{0:02d}'.format(index)),
                    "{}".format(index)))
        colorRampShaderFcn.setColorRampItemList(items)
        shader.setRasterShaderFunction(colorRampShaderFcn)
        # create instance to test
        rasterRenderer = QgsSingleBandPseudoColorRenderer(
            self.raster_layer.dataProvider(), bandNo, shader)
        self.raster_layer.setRenderer(rasterRenderer)

        # do test
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '{}'.format(bandNo))
        # check ColorMapEntry classes
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertFalse(colorMap.isNull())
        self.assertEqual(colorMap.attribute('type'), 'values')
        self.assertFalse(colorMap.hasAttribute('extendend'))
        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(colorMapEntries.count(), 10)
        for index in range(colorMapEntries.count()):
            colorMapEntry = colorMapEntries.at(index).toElement()
            self.assertEqual(colorMapEntry.attribute('quantity'),
                             '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('label'),
                             '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('opacity'), '')
            self.assertEqual(colorMapEntry.attribute('color'),
                             '#{0:02d}{0:02d}{0:02d}'.format(index))

        # add check that is set ColoMap extended="true" if colormap is bigger that 255 entries
        # !NOTE! can't reuse previous shader => segmentation fault
        shader = QgsRasterShader(minValue, maxValue)
        colorRampShaderFcn = QgsColorRampShader(minValue, maxValue)
        colorRampShaderFcn.setColorRampType(QgsColorRampShader.Exact)
        colorRampShaderFcn.setClassificationMode(QgsColorRampShader.Continuous)
        colorRampShaderFcn.setClip(True)
        items = []
        for index in range(255):
            items.append(
                QgsColorRampShader.ColorRampItem(
                    index, QColor.fromHsv(index, 255, 255, 255),
                    "{}".format(index)))
        colorRampShaderFcn.setColorRampItemList(items)
        shader.setRasterShaderFunction(colorRampShaderFcn)
        # create instance to test
        rasterRenderer = QgsSingleBandPseudoColorRenderer(
            self.raster_layer.dataProvider(), bandNo, shader)
        # self.raster_layer.setRenderer(rasterRenderer)
        # dom, root = self.rendererToSld(self.raster_layer.renderer())
        # self.assertTrue( colorMap.hasAttribute( 'extendend' ) )
        # self.assertEqual( colorMap.attribute( 'extendend' ), 'true' )

    def testPalettedRasterRenderer(self):
        # create 10 color classes
        #classesString = '122 0 0 0 255 122\n123 1 1 1 255 123\n124 2 2 2 255 124\n125 3 3 3 255 125\n126 4 4 4 255 126\n127 5 5 5 255 127\n128 6 6 6 255 128\n129 7 7 7 255 129\n130 8 8 8 255 130'
        classesString = ''
        for index in range(10):
            classesString += '{0} {0} {0} {0} 255 {0}\n'.format(index)
        classes = QgsPalettedRasterRenderer.classDataFromString(classesString)

        rasterRenderer = QgsPalettedRasterRenderer(
            self.raster_layer.dataProvider(), 3, classes)
        self.raster_layer.setRenderer(rasterRenderer)

        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '3')
        # check ColorMapEntry classes
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertFalse(colorMap.isNull())
        self.assertEqual(colorMap.attribute('type'), 'values')
        self.assertFalse(colorMap.hasAttribute('extendend'))
        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(colorMapEntries.count(), 10)
        for index in range(colorMapEntries.count()):
            colorMapEntry = colorMapEntries.at(index).toElement()
            self.assertEqual(colorMapEntry.attribute('quantity'),
                             '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('label'),
                             '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('opacity'), '')
            self.assertEqual(colorMapEntry.attribute('color'),
                             '#{0:02d}{0:02d}{0:02d}'.format(index))

        # add check that is set ColoMap extended="true" if colormap is bigger that 255 entries
        classesString = ''
        values = range(255)
        for index in range(255):
            classesString += '{0} {1} {1} {1} 255 {0}\n'.format(
                index, random.choice(values))
        classes = QgsPalettedRasterRenderer.classDataFromString(classesString)
        rasterRenderer = QgsPalettedRasterRenderer(
            self.raster_layer.dataProvider(), 3, classes)
        self.raster_layer.setRenderer(rasterRenderer)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertTrue(colorMap.hasAttribute('extended'))
        self.assertEqual(colorMap.attribute('extended'), 'true')

    def testMultiBandColorRenderer(self):
        rasterRenderer = QgsMultiBandColorRenderer(
            self.raster_layer.dataProvider(), 3, 1, 2)
        self.raster_layer.setRenderer(rasterRenderer)
        self.raster_layer.setContrastEnhancement(
            algorithm=QgsContrastEnhancement.StretchToMinimumMaximum,
            limits=QgsRasterMinMaxOrigin.MinMax)

        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:RedChannel', '3')
        self.assertChannelBand(root, 'sld:GreenChannel', '1')
        self.assertChannelBand(root, 'sld:BlueChannel', '2')

    def testSingleBandGrayRenderer(self):
        # check with StretchToMinimumMaximum
        rasterRenderer = QgsSingleBandGrayRenderer(
            self.raster_layer.dataProvider(), 3)
        self.raster_layer.setRenderer(rasterRenderer)
        self.raster_layer.setContrastEnhancement(
            algorithm=QgsContrastEnhancement.StretchToMinimumMaximum,
            limits=QgsRasterMinMaxOrigin.MinMax)
        maximum = self.raster_layer.renderer().contrastEnhancement(
        ).maximumValue()
        minmum = self.raster_layer.renderer().contrastEnhancement(
        ).minimumValue()
        self.assertEqual(minmum, 51)
        self.assertEqual(maximum, 172)

        # check default values
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '3')

        elements = root.elementsByTagName('sld:ContrastEnhancement')
        self.assertEqual(len(elements), 1)
        enhancement = elements.at(0).toElement()
        self.assertFalse(enhancement.isNull())

        normalize = enhancement.firstChildElement('sld:Normalize')
        self.assertFalse(normalize.isNull())
        self.assertVendorOption(normalize, 'algorithm',
                                'StretchToMinimumMaximum')
        self.assertVendorOption(normalize, 'minValue', '51')
        self.assertVendorOption(normalize, 'maxValue', '172')

        elements = root.elementsByTagName('sld:ColorMap')
        self.assertEqual(len(elements), 1)
        colorMap = elements.at(0).toElement()
        self.assertFalse(colorMap.isNull())

        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(len(colorMapEntries), 2)
        clorMap1 = colorMapEntries.at(0)
        self.assertEqual(clorMap1.attributes().namedItem('color').nodeValue(),
                         '#000000')
        self.assertEqual(
            clorMap1.attributes().namedItem('quantity').nodeValue(), '0')

        clorMap2 = colorMapEntries.at(1)
        self.assertEqual(clorMap2.attributes().namedItem('color').nodeValue(),
                         '#ffffff')
        self.assertEqual(
            clorMap2.attributes().namedItem('quantity').nodeValue(), '255')

        # check when StretchAndClipToMinimumMaximum
        # then min/max have always to be the real one and not that set in the contrastEnhancement
        self.raster_layer.setContrastEnhancement(
            algorithm=QgsContrastEnhancement.StretchAndClipToMinimumMaximum,
            limits=QgsRasterMinMaxOrigin.MinMax)
        minmum = self.raster_layer.renderer().contrastEnhancement(
        ).setMinimumValue(100)
        maximum = self.raster_layer.renderer().contrastEnhancement(
        ).maximumValue()
        minmum = self.raster_layer.renderer().contrastEnhancement(
        ).minimumValue()
        self.assertEqual(minmum, 100)
        self.assertEqual(maximum, 172)

        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '3')

        elements = root.elementsByTagName('sld:ContrastEnhancement')
        self.assertEqual(len(elements), 1)
        enhancement = elements.at(0).toElement()
        self.assertFalse(enhancement.isNull())

        normalize = enhancement.firstChildElement('sld:Normalize')
        self.assertFalse(normalize.isNull())
        self.assertVendorOption(normalize, 'minValue', '51')
        self.assertVendorOption(normalize, 'maxValue', '172')

        elements = root.elementsByTagName('sld:ColorMap')
        self.assertEqual(len(elements), 1)
        colorMap = elements.at(0).toElement()
        self.assertFalse(colorMap.isNull())

        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(len(colorMapEntries), 4)
        clorMap1 = colorMapEntries.at(0)
        self.assertEqual(clorMap1.attributes().namedItem('color').nodeValue(),
                         '#000000')
        self.assertEqual(
            clorMap1.attributes().namedItem('quantity').nodeValue(), '100')
        self.assertEqual(
            clorMap1.attributes().namedItem('opacity').nodeValue(), '0')

        clorMap2 = colorMapEntries.at(1)
        self.assertEqual(clorMap2.attributes().namedItem('color').nodeValue(),
                         '#000000')
        self.assertEqual(
            clorMap2.attributes().namedItem('quantity').nodeValue(), '100')

        clorMap3 = colorMapEntries.at(2)
        self.assertEqual(clorMap3.attributes().namedItem('color').nodeValue(),
                         '#ffffff')
        self.assertEqual(
            clorMap3.attributes().namedItem('quantity').nodeValue(), '172')

        clorMap4 = colorMapEntries.at(3)
        self.assertEqual(clorMap4.attributes().namedItem('color').nodeValue(),
                         '#ffffff')
        self.assertEqual(
            clorMap4.attributes().namedItem('quantity').nodeValue(), '172')
        self.assertEqual(
            clorMap4.attributes().namedItem('opacity').nodeValue(), '0')

        # check when ClipToMinimumMaximum
        # then min/max have always to be the real one and not that set in the contrastEnhancement
        self.raster_layer.setContrastEnhancement(
            algorithm=QgsContrastEnhancement.ClipToMinimumMaximum,
            limits=QgsRasterMinMaxOrigin.MinMax)
        minmum = self.raster_layer.renderer().contrastEnhancement(
        ).setMinimumValue(100)
        maximum = self.raster_layer.renderer().contrastEnhancement(
        ).maximumValue()
        minmum = self.raster_layer.renderer().contrastEnhancement(
        ).minimumValue()
        self.assertEqual(minmum, 100)
        self.assertEqual(maximum, 172)

        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '3')

        elements = root.elementsByTagName('sld:ContrastEnhancement')
        self.assertEqual(len(elements), 1)
        enhancement = elements.at(0).toElement()
        self.assertFalse(enhancement.isNull())

        normalize = enhancement.firstChildElement('sld:Normalize')
        self.assertFalse(normalize.isNull())
        self.assertVendorOption(normalize, 'minValue', '51')
        self.assertVendorOption(normalize, 'maxValue', '172')

        elements = root.elementsByTagName('sld:ColorMap')
        self.assertEqual(len(elements), 1)
        colorMap = elements.at(0).toElement()
        self.assertFalse(colorMap.isNull())

        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(len(colorMapEntries), 4)
        clorMap1 = colorMapEntries.at(0)
        self.assertEqual(clorMap1.attributes().namedItem('color').nodeValue(),
                         '#000000')
        self.assertEqual(
            clorMap1.attributes().namedItem('quantity').nodeValue(), '100')
        self.assertEqual(
            clorMap1.attributes().namedItem('opacity').nodeValue(), '0')

        clorMap2 = colorMapEntries.at(1)
        self.assertEqual(clorMap2.attributes().namedItem('color').nodeValue(),
                         '#000000')
        self.assertEqual(
            clorMap2.attributes().namedItem('quantity').nodeValue(), '100')

        clorMap3 = colorMapEntries.at(2)
        self.assertEqual(clorMap3.attributes().namedItem('color').nodeValue(),
                         '#ffffff')
        self.assertEqual(
            clorMap3.attributes().namedItem('quantity').nodeValue(), '172')

        clorMap4 = colorMapEntries.at(3)
        self.assertEqual(clorMap4.attributes().namedItem('color').nodeValue(),
                         '#ffffff')
        self.assertEqual(
            clorMap4.attributes().namedItem('quantity').nodeValue(), '172')
        self.assertEqual(
            clorMap4.attributes().namedItem('opacity').nodeValue(), '0')

    def testRasterRenderer(self):
        class fakerenderer(QgsRasterRenderer):
            def __init__(self, interface):
                QgsRasterRenderer.__init__(self, interface, '')

        rasterRenderer = fakerenderer(self.raster_layer.dataProvider())
        self.raster_layer.setRenderer(rasterRenderer)

        # check opacity default value is not exported
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        # check if opacity is not the default value
        rasterRenderer.setOpacity(1.1)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertOpacity(root, '1.1')

        # check gamma properties from [-100:0] streched to [0:1]
        #  and (0:100] stretche dto (1:100]
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '-100'})
        # self.assertGamma(root, '0')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '-50'})
        # self.assertGamma(root, '0.5')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '0'})
        # self.assertGamma(root, '1')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '1'})
        # self.assertGamma(root, '1')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '100'})
        # self.assertGamma(root, '100')
        # # input contrast are always integer, btw the value is managed also if it's double
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '1.1'})
        # self.assertGamma(root, '1.1')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '1.6'})
        # self.assertGamma(root, '1.6')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '-50.5'})
        # self.assertGamma(root, '0.495')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '-0.1'})
        # self.assertGamma(root, '0.999')

    def testStretchingAlgorithm(self):
        rasterRenderer = QgsMultiBandColorRenderer(
            self.raster_layer.dataProvider(), 3, 1, 2)
        self.raster_layer.setRenderer(rasterRenderer)

        # check StretchToMinimumMaximum stretching alg
        self.raster_layer.setContrastEnhancement(
            algorithm=QgsContrastEnhancement.StretchToMinimumMaximum,
            limits=QgsRasterMinMaxOrigin.MinMax)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertContrastEnhancement(root, 'sld:RedChannel',
                                       'StretchToMinimumMaximum', '51', '172')
        self.assertContrastEnhancement(root, 'sld:GreenChannel',
                                       'StretchToMinimumMaximum', '122', '130')
        self.assertContrastEnhancement(root, 'sld:BlueChannel',
                                       'StretchToMinimumMaximum', '133', '148')

        # check StretchAndClipToMinimumMaximum stretching alg
        self.raster_layer.setContrastEnhancement(
            algorithm=QgsContrastEnhancement.StretchAndClipToMinimumMaximum,
            limits=QgsRasterMinMaxOrigin.MinMax)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertContrastEnhancement(root, 'sld:RedChannel', 'ClipToZero',
                                       '51', '172')
        self.assertContrastEnhancement(root, 'sld:GreenChannel', 'ClipToZero',
                                       '122', '130')
        self.assertContrastEnhancement(root, 'sld:BlueChannel', 'ClipToZero',
                                       '133', '148')

        # check ClipToMinimumMaximum stretching alg
        self.raster_layer.setContrastEnhancement(
            algorithm=QgsContrastEnhancement.ClipToMinimumMaximum,
            limits=QgsRasterMinMaxOrigin.MinMax)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertContrastEnhancement(root, 'sld:RedChannel',
                                       'ClipToMinimumMaximum', '51', '172')
        self.assertContrastEnhancement(root, 'sld:GreenChannel',
                                       'ClipToMinimumMaximum', '122', '130')
        self.assertContrastEnhancement(root, 'sld:BlueChannel',
                                       'ClipToMinimumMaximum', '133', '148')

        # check NoEnhancement stretching alg
        self.raster_layer.setContrastEnhancement(
            algorithm=QgsContrastEnhancement.NoEnhancement)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertContrastEnhancement(root, 'sld:RedChannel')
        self.assertContrastEnhancement(root, 'sld:GreenChannel')
        self.assertContrastEnhancement(root, 'sld:BlueChannel')

    def assertVendorOption(self, root, name, expectedValue):
        """Set expectedValue=None to check that the vendor option is not present."""
        vendorOptions = root.elementsByTagName('sld:VendorOption')
        found = False
        for vendorOptionIndex in range(vendorOptions.count()):
            vendorOption = vendorOptions.at(vendorOptionIndex)
            self.assertEqual('sld:VendorOption', vendorOption.nodeName())
            if (vendorOption.attributes().namedItem('name').nodeValue() == name
                ):
                found = True
                self.assertEqual(vendorOption.firstChild().nodeValue(),
                                 expectedValue)
        if (expectedValue is None) and found:
            self.fail(
                "found VendorOption: {} where supposed not present".format(
                    name))
        if expectedValue and not found:
            self.fail("Not found VendorOption: {}".format(name))

    def assertGamma(self, root, expectedValue, index=0):
        enhancement = root.elementsByTagName('sld:ContrastEnhancement').item(
            index)
        gamma = enhancement.firstChildElement('sld:GammaValue')
        self.assertEqual(expectedValue, gamma.firstChild().nodeValue())

    def assertOpacity(self, root, expectedValue, index=0):
        opacity = root.elementsByTagName('sld:Opacity').item(index)
        self.assertEqual(expectedValue, opacity.firstChild().nodeValue())

    def assertNoOpacity(self, root):
        opacities = root.elementsByTagName('sld:Opacity')
        self.assertEqual(opacities.size(), 0)

    def assertContrastEnhancement(self,
                                  root,
                                  bandTag,
                                  expectedAlg=None,
                                  expectedMin=None,
                                  expectedMax=None,
                                  index=0):
        channelSelection = root.elementsByTagName('sld:ChannelSelection').item(
            index)
        self.assertIsNotNone(channelSelection)
        band = channelSelection.firstChildElement(bandTag)
        # check if no enhancement alg is iset
        if (not expectedAlg):
            contrastEnhancementName = band.firstChildElement(
                'sld:ContrastEnhancement')
            self.assertEqual('',
                             contrastEnhancementName.firstChild().nodeName())
            return
        # check if enhancement alg is set
        contrastEnhancementName = band.firstChildElement(
            'sld:ContrastEnhancement')
        self.assertEqual('sld:Normalize',
                         contrastEnhancementName.firstChild().nodeName())
        normalize = contrastEnhancementName.firstChildElement('sld:Normalize')
        vendorOptions = normalize.elementsByTagName('VendorOption')
        for vendorOptionIndex in range(vendorOptions.count()):
            vendorOption = vendorOptions.at(vendorOptionIndex)
            self.assertEqual('VendorOption', vendorOption.nodeName())
            if (vendorOption.attributes().namedItem('name').nodeValue() ==
                    'algorithm'):
                self.assertEqual(expectedAlg,
                                 vendorOption.firstChild().nodeValue())
            elif (vendorOption.attributes().namedItem('name').nodeValue() ==
                  'minValue'):
                self.assertEqual(expectedMin,
                                 vendorOption.firstChild().nodeValue())
            elif (vendorOption.attributes().namedItem('name').nodeValue() ==
                  'maxValue'):
                self.assertEqual(expectedMax,
                                 vendorOption.firstChild().nodeValue())
            else:
                self.fail('Unrecognised vendorOption name {}'.format(
                    vendorOption.attributes().namedItem('name').nodeValue()))

    def assertChannelBand(self, root, bandTag, expectedValue, index=0):
        channelSelection = root.elementsByTagName('sld:ChannelSelection').item(
            index)
        self.assertIsNotNone(channelSelection)
        band = channelSelection.firstChildElement(bandTag)
        sourceChannelName = band.firstChildElement('sld:SourceChannelName')
        self.assertEqual(expectedValue,
                         sourceChannelName.firstChild().nodeValue())

    def rendererToSld(self, renderer, properties={}):
        dom = QDomDocument()
        root = dom.createElement("FakeRoot")
        dom.appendChild(root)
        renderer.toSld(dom, root, properties)
        return dom, root
class TestRATClasses(TestCase):
    @classmethod
    def setUpClass(cls):

        cls.qgs = QgsApplication([], False)
        cls.qgs.initQgis()

    @classmethod
    def tearDownClass(cls):

        cls.qgs.exitQgis()

    def tearDown(self):

        del (self.raster_layer)
        del (self.raster_layer_dbf)
        del (self.raster_layer_athematic)

    def setUp(self):

        self.tmp_dir = QTemporaryDir()
        self.tmp_path = os.path.join(self.tmp_dir.path(), 'data')

        shutil.copytree(os.path.join(os.path.dirname(__file__), 'data'),
                        self.tmp_path)

        self.raster_layer = QgsRasterLayer(
            os.path.join(
                self.tmp_path,
                'NBS_US5PSMBE_20200923_0_generalized_p.source_information.tiff'
            ), 'rat_test', 'gdal')
        self.assertTrue(self.raster_layer.isValid())

        self.raster_layer_dbf = QgsRasterLayer(
            os.path.join(self.tmp_path, 'ExistingVegetationTypes_sample.img'),
            'rat_test', 'gdal')
        self.assertTrue(self.raster_layer_dbf.isValid())

        self.raster_layer_athematic = QgsRasterLayer(
            os.path.join(self.tmp_path, '2x2_1_BAND_FLOAT.tif'), 'rat_test',
            'gdal')
        self.assertTrue(self.raster_layer_athematic.isValid())

    def test_field_usages(self):

        rat = get_rat(self.raster_layer, 1)
        self.assertTrue(rat.isValid())
        self.assertEqual(rat.field_usages, {0, 1, 5})

    def test_insert_column(self):

        rat = get_rat(self.raster_layer, 1)
        self.assertTrue(rat.isValid())
        self.assertEqual(len(rat.keys), 16)
        self.assertEqual(len(rat.keys), len(rat.fields))

        # Not valid insertions
        field = RATField('f1', gdal.GFU_MinMax, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(4, field)[0])

        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(0, field)[0])

        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(1, field)[0])

        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(100, field)[0])

        field = RATField('significant_features', gdal.GFU_Generic,
                         gdal.GFT_Real)
        self.assertFalse(rat.insert_column(4, field)[0])

        # Valid insertions

        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertEqual(field.qgis_type, QVariant.Double)
        self.assertTrue(rat.insert_column(4, field)[0])
        self.assertIn('f1', rat.fields.keys())
        self.assertEqual(len(rat.data['f1']), len(rat.data['Value']))

        field = RATField('f2', gdal.GFU_Generic, gdal.GFT_Integer)
        self.assertEqual(field.qgis_type, QVariant.Int)
        self.assertTrue(rat.insert_column(2, field)[0])
        self.assertIn('f2', rat.fields.keys())
        self.assertEqual(len(rat.data['f2']), len(rat.data['Value']))
        self.assertEqual(rat.data['f2'][0], 0)

        field = RATField('f3', gdal.GFU_Generic, gdal.GFT_String)
        self.assertEqual(field.qgis_type, QVariant.String)
        self.assertTrue(rat.insert_column(len(rat.keys) - 1, field)[0])
        self.assertIn('f3', rat.fields.keys())
        self.assertEqual(len(rat.data['f3']), len(rat.data['Value']))
        self.assertEqual(rat.data['f3'][0], '')

    def test_remove_column(self):

        rat = get_rat(self.raster_layer, 1)
        self.assertTrue(rat.isValid())
        self.assertEqual(len(rat.keys), 16)
        self.assertEqual(len(rat.keys), len(rat.fields))

        # Invalid removals
        self.assertFalse(rat.remove_column('Value')[0])
        self.assertFalse(rat.remove_column('Count')[0])
        self.assertFalse(rat.remove_column('not found')[0])

        # Valid removals
        self.assertTrue(rat.remove_column('data_assessment')[0])
        self.assertEqual(len(rat.keys), 15)
        self.assertEqual(len(rat.keys), len(rat.fields))

    def test_insert_column_dbf(self):

        rat = get_rat(self.raster_layer_dbf, 1)
        self.assertTrue(rat.isValid())
        self.assertEqual(len(rat.keys), 17)
        # has color, so fields are one more than keys
        self.assertEqual(len(rat.keys), len(rat.fields) + 1)

        # Not valid insertions
        field = RATField('f1', gdal.GFU_MinMax, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(4, field)[0])

        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(0, field)[0])

        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(1, field)[0])

        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(100, field)[0])

        field = RATField('SYSTMGRPNA', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertFalse(rat.insert_column(4, field)[0])

        # Valid insertions

        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_Real)
        self.assertEqual(field.qgis_type, QVariant.Double)
        self.assertTrue(rat.insert_column(4, field)[0])
        self.assertIn('f1', rat.fields.keys())
        self.assertEqual(len(rat.data['f1']), len(rat.data['VALUE']))

        field = RATField('f2', gdal.GFU_Generic, gdal.GFT_Integer)
        self.assertEqual(field.qgis_type, QVariant.Int)
        self.assertTrue(rat.insert_column(2, field)[0])
        self.assertIn('f2', rat.fields.keys())
        self.assertEqual(len(rat.data['f2']), len(rat.data['VALUE']))
        self.assertEqual(rat.data['f2'][0], 0)

        field = RATField('f3', gdal.GFU_Generic, gdal.GFT_String)
        self.assertEqual(field.qgis_type, QVariant.String)
        self.assertTrue(rat.insert_column(len(rat.keys) - 1, field)[0])
        self.assertIn('f3', rat.fields.keys())
        self.assertEqual(len(rat.data['f3']), len(rat.data['VALUE']))
        self.assertEqual(rat.data['f3'][0], '')

        field = RATField('R', gdal.GFU_Red, gdal.GFT_Integer)
        self.assertFalse(rat.insert_column(len(rat.keys) - 1, field)[0])

    def test_remove_column_dbf(self):

        rat = get_rat(self.raster_layer_dbf, 1)
        self.assertTrue(rat.isValid())
        self.assertEqual(len(rat.keys), 17)
        self.assertEqual(len(rat.keys), len(rat.fields) + 1)

        # Invalid removals
        self.assertFalse(rat.remove_column('VALUE')[0])
        self.assertFalse(rat.remove_column('COUNT')[0])
        self.assertFalse(rat.remove_column('not found')[0])

        # Valid removals
        self.assertTrue(rat.remove_column('SYSTMGRPNA')[0])
        self.assertEqual(len(rat.keys), 16)
        self.assertEqual(len(rat.keys), len(rat.fields) + 1)

    def test_qgis_features(self):

        rat = get_rat(self.raster_layer_dbf, 1)
        features = rat.qgis_features()
        self.assertEqual(len(features), 59)

        rat = get_rat(self.raster_layer, 1)
        features = rat.qgis_features()
        self.assertEqual(len(features), 27)

    def test_get_set_color(self):

        rat = get_rat(self.raster_layer_dbf, 1)
        color = rat.get_color(0)
        self.assertTrue(color.isValid())

        # Invalid
        self.assertFalse(rat.get_color(-1).isValid())
        self.assertFalse(rat.get_color(100).isValid())

        # Setter
        self.assertTrue(rat.set_color(1, QColor(10, 20, 30, 120)))
        self.assertEqual(rat.get_color(1), QColor(10, 20, 30, 120))

    def test_field_name(self):

        rat = get_rat(self.raster_layer_dbf, 1)

        usages = []
        for field in rat.fields.values():
            if field.usage not in usages:
                usages.append(field.usage)
                self.assertEqual(rat.field_name(field.usage), field.name)

        self.assertEqual(rat.field_name(gdal.GFU_AlphaMax), '')
        self.assertEqual(rat.field_name(gdal.GFU_RedMax), '')
        self.assertEqual(rat.field_name(gdal.GFU_RedMin), '')

    def test_update_color_from_raster(self):

        rat = get_rat(self.raster_layer_dbf, 1)
        self.assertTrue(rat.has_color)
        rat_classify(self.raster_layer_dbf, 1, rat, 'EVT_NAME')

        color_map = {
            klass.value: klass.color
            for klass in self.raster_layer_dbf.renderer().classes()
        }

        # Remove color
        self.assertTrue(rat.remove_color_fields())
        self.assertFalse(rat.has_color)

        # Add color
        result, error_message = rat.insert_color_fields(len(rat.keys) - 1)
        self.assertTrue(result, error_message)
        self.assertTrue(rat.has_color)

        for color in rat.data[RAT_COLOR_HEADER_NAME]:
            self.assertEqual(color, QColor(Qt.black))

        # Update color from raster
        self.assertTrue(rat.update_colors_from_raster(self.raster_layer_dbf))

        value_column = rat.value_columns[0]
        self.assertEqual(value_column, rat.field_name(gdal.GFU_MinMax))

        for row_index in range(len(rat.data[RAT_COLOR_HEADER_NAME])):
            self.assertEqual(rat.data[RAT_COLOR_HEADER_NAME][row_index],
                             color_map[rat.data[value_column][row_index]])

    def test_update_color_from_raster_athematic(self):

        rat = get_rat(self.raster_layer_athematic, 1)
        self.assertTrue(rat.has_color)
        rat_classify(self.raster_layer_athematic, 1, rat, 'Class')

        shader = self.raster_layer_athematic.renderer().shader()
        colorRampShaderFcn = shader.rasterShaderFunction()
        classes = classes = colorRampShaderFcn.colorRampItemList()

        color_map = {klass.value: klass.color for klass in classes}

        # Remove color
        self.assertTrue(rat.remove_color_fields())
        self.assertFalse(rat.has_color)

        # Add color
        result, error_message = rat.insert_color_fields(len(rat.keys) - 1)
        self.assertTrue(result, error_message)
        self.assertTrue(rat.has_color)

        for color in rat.data[RAT_COLOR_HEADER_NAME]:
            self.assertEqual(color, QColor(Qt.black))

        # Update color from raster
        self.assertTrue(
            rat.update_colors_from_raster(self.raster_layer_athematic))

        value_column = rat.value_columns[1]
        self.assertEqual(value_column, rat.field_name(gdal.GFU_Max))

        for row_index in range(len(rat.data[RAT_COLOR_HEADER_NAME])):
            self.assertEqual(rat.data[RAT_COLOR_HEADER_NAME][row_index],
                             color_map[rat.data[value_column][row_index]])

    def test_add_remove_row(self):
        def _test(raster_layer):

            rat = get_rat(raster_layer, 1)
            value_column = rat.value_columns[0]
            self.assertEqual(value_column, rat.field_name(gdal.GFU_MinMax))

            self.assertNotEqual(rat.data[value_column][-1], 0)
            row_count = len(rat.data[value_column])

            result, error_message = rat.insert_row(0)
            self.assertTrue(result)
            self.assertEqual(len(rat.data[value_column]), row_count + 1)
            self.assertEqual(rat.data[value_column][0], 0)

            result, error_message = rat.remove_row(0)
            self.assertTrue(result)
            self.assertEqual(len(rat.data[value_column]), row_count)
            self.assertNotEqual(rat.data[value_column][0], 0)

            last = len(rat.data[value_column])
            result, error_message = rat.insert_row(last)
            self.assertTrue(result)
            self.assertEqual(len(rat.data[value_column]), row_count + 1)
            self.assertEqual(rat.data[value_column][last], 0)

            result, error_message = rat.remove_row(last)
            self.assertTrue(result)
            self.assertEqual(len(rat.data[value_column]), row_count)
            self.assertNotEqual(rat.data[value_column][last - 1], 0)

            # Invalid ranges
            last = len(rat.data[value_column])
            self.assertFalse(rat.insert_row(-1)[0])
            self.assertFalse(rat.insert_row(last + 1)[0])
            self.assertFalse(rat.remove_row(-1)[0])
            self.assertFalse(rat.remove_row(last)[0])

        _test(self.raster_layer_dbf)
        _test(self.raster_layer)

    def test_edit_rat(self):

        raster_layer = QgsRasterLayer(
            os.path.join(self.tmp_path, '2x2_2_BANDS_INT16.tif'), 'rat_test',
            'gdal')
        self.assertTrue(raster_layer.isValid())

        band = 1

        rat = get_rat(raster_layer, band)
        self.assertTrue(rat.isValid())

        self.assertEqual(rat.data['Red'], [0, 100, 200])
        rat.data['Red'] = [111, 222, 123]
        rat.save(band)

        rat = get_rat(raster_layer, band)
        self.assertTrue(rat.isValid())
        self.assertEqual(rat.data['Red'], [111, 222, 123])

    def test_athematic_dbf_roundtrip(self):
        """Test that saving as athematic and reloading does not loose type"""

        rat = get_rat(self.raster_layer_athematic, 1)
        self.assertTrue(rat.has_color)
        self.assertTrue(rat.isValid())
        self.assertEqual(rat.thematic_type, gdal.GRTT_ATHEMATIC)

        # Delete the layer and the PAM file
        raster_source = self.raster_layer_athematic.source()
        pam_path = raster_source + '.aux.xml'
        del (self.raster_layer_athematic)
        os.unlink(pam_path)

        rat.save_as_dbf(raster_source)
        self.assertTrue(os.path.exists(raster_source + '.vat.dbf'))

        self.raster_layer_athematic = QgsRasterLayer(raster_source, 'rat_test',
                                                     'gdal')

        rat_dbf = get_rat(self.raster_layer_athematic, 1)
        self.assertTrue(rat_dbf.has_color)
        self.assertTrue(rat_dbf.isValid())
        self.assertEqual(rat_dbf.thematic_type, gdal.GRTT_ATHEMATIC)
        self.assertEqual(
            rat_dbf.field_usages, {
                gdal.GFU_Generic,
                gdal.GFU_Red,
                gdal.GFU_Green,
                gdal.GFU_Blue,
                gdal.GFU_Min,
                gdal.GFU_Max,
            })

    def test_charset(self):
        """Test that we can save/load non-ASCII chars"""

        rat = get_rat(self.raster_layer, 1)
        self.assertTrue(rat.isValid())

        # Delete the layer and the PAM file
        raster_source = self.raster_layer.source()

        pam_path = raster_source + '.aux.xml'
        dbf_path = raster_source + '.vat.dbf'

        del (self.raster_layer)
        os.unlink(pam_path)

        rat.data['License_Name'][0] = 'Some accented chars èé 😁'

        rat.save_as_dbf(raster_source)
        self.assertTrue(os.path.exists(dbf_path))

        self.raster_layer = QgsRasterLayer(raster_source, 'rat_test', 'gdal')

        rat_dbf = get_rat(self.raster_layer, 1)
        self.assertTrue(rat_dbf.isValid())

        self.assertEqual(rat_dbf.data['License_Na'][0],
                         'Some accented chars èé 😁')

        # Save as XML
        rat.save_as_xml(raster_source, 1)
        self.assertTrue(os.path.exists(pam_path))
        del (self.raster_layer)
        os.unlink(dbf_path)

        self.raster_layer = QgsRasterLayer(raster_source, 'rat_test', 'gdal')

        rat_xml = get_rat(self.raster_layer, 1)
        self.assertTrue(rat_xml.isValid())

        self.assertEqual(rat_xml.data['License_Name'][0],
                         'Some accented chars èé 😁')
Example #11
0
class ExploreMapWindow(QMainWindow):
    """This class offers a canvas and tools to preview and explore data 
        provided by Geocubes. Preview raster layers are fetched from the Geocubes
        cached WMTS server. The user can simply view the data or get legend info
        on a single point."""

    # the window is initiated with the Geocubes url base defined on the main plugin
    # this means that the base doesn't have to be manually changed here if it changes
    def __init__(self, url_base):
        QMainWindow.__init__(self)

        # creating map canvas, which draws the maplayers
        # setting up features like canvas color
        self.canvas = QgsMapCanvas()
        self.canvas.setMinimumSize(550, 700)
        self.canvas.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.canvas.setCanvasColor(Qt.white)
        #self.canvas.enableAntiAliasing(True)

        self.url_base = url_base

        # Qmainwindow requires a central widget. Canvas is placed
        self.setCentralWidget(self.canvas)
        """'tile widths' refer to the Map proxy WMTS server's settings for displaying
        data of different resolutions. If I understood correctly, these values 
        (got by examining properties of a layer from that server in QGIS) are
        the thresholds at which a different resolution is loaded on the GRIDI-FIN
        tileset. The values represent the tile size in map units (meters). 
        
        Each tile widths is tied to the corresponding resolution, which is used
        to get the correct resolution legend info. The method is only an estimation,
        but ought to produce good enough results for this purpose.
        Smallest resolutions (1, 2, 5) are omitted since only some layers 
        have them. """
        self.tile_widths = {
            2560: 10,
            5120: 20,
            12800: 50,
            25600: 100,
            51200: 200,
            128000: 500,
            256000: 1000
        }

        # get all keys i.e. widths
        self.all_widths = [i for i in self.tile_widths]

        # creating background layer box and housing it with the hardcoded options
        self.bg_layer_box = QComboBox()
        # if ortokuva ever updates to newer versions, just change the year here
        bg_layers = ['Taustakartta', 'Ortokuva_2018', 'No reference layer']

        # set 'No reference layer' as the default option
        self.bg_layer_box.addItems(layer for layer in bg_layers)
        self.bg_layer_box.setCurrentIndex(2)
        self.bg_layer_box.currentIndexChanged.connect(self.addBackgroundLayer)

        # initialize the slider that will control BG layer opacity/transparency
        self.opacity_slider = QSlider(Qt.Horizontal)
        self.opacity_slider.setMinimum(0)
        self.opacity_slider.setMaximum(100)
        self.opacity_slider.setSingleStep(1)
        self.opacity_slider.setMaximumWidth(100)
        self.opacity_slider.valueChanged.connect(self.setBackgroundMapOpacity)

        self.legend_checkbox = QCheckBox("Get attribute info on all layers")

        # explanatory texts for the different widgets are stored as label widgets
        bg_layer_label = QLabel(" Background: ")
        bg_opacity_label = QLabel(" BG opacity: ")
        data_label = QLabel("Data: ")
        spacing = QLabel(" ")

        # all of the data layers are housed in this combobox
        self.layer_box = QComboBox()
        self.layer_box.currentIndexChanged.connect(self.addLayer)

        # creating each desired action
        self.actionPan = QAction("Pan tool", self)
        self.actionLegend = QAction("Attribute info tool", self)
        self.actionCancel = QAction("Close window", self)
        self.actionZoom = QAction("Zoom to full extent", self)

        # these two work as on/off. the rest are clickable
        self.actionPan.setCheckable(True)
        self.actionLegend.setCheckable(True)

        # when actions are clicked, do corresponding function
        self.actionPan.triggered.connect(self.pan)
        self.actionLegend.triggered.connect(self.info)
        self.actionCancel.triggered.connect(self.cancel)
        self.actionZoom.triggered.connect(self.zoomToExtent)

        # defining two toolbars: first one houses layer and opacity selection
        # the other has all the tools and functions
        self.layers_toolbar = self.addToolBar("Select layers")
        self.layers_toolbar.setContextMenuPolicy(Qt.PreventContextMenu)
        self.layers_toolbar.setMovable(False)
        self.addToolBarBreak()
        self.tools_toolbar = self.addToolBar("Tools")
        self.tools_toolbar.setContextMenuPolicy(Qt.PreventContextMenu)
        self.tools_toolbar.setMovable(False)

        # change order here to change their placement on window
        # starting with the layer widgets and the corresponding label texts
        self.layers_toolbar.addWidget(data_label)
        self.layers_toolbar.addWidget(self.layer_box)
        self.layers_toolbar.addWidget(bg_layer_label)
        self.layers_toolbar.addWidget(self.bg_layer_box)
        self.layers_toolbar.addWidget(bg_opacity_label)
        self.layers_toolbar.addWidget(self.opacity_slider)
        self.layers_toolbar.addWidget(spacing)
        self.layers_toolbar.addWidget(self.legend_checkbox)

        # then setting all the canvas tools on the second toolbar
        self.tools_toolbar.addAction(self.actionLegend)
        self.tools_toolbar.addAction(self.actionPan)
        self.tools_toolbar.addAction(self.actionZoom)
        self.tools_toolbar.addAction(self.actionCancel)

        # a large text box that will house the legend info
        self.text_browser = QTextEdit("Legend will be shown here")
        self.text_browser.setReadOnly(True)

        # a dock widget is required for the text browser. Docked to main window
        dock_widget = QDockWidget()
        dock_widget.setFeatures(QDockWidget.NoDockWidgetFeatures)
        dock_widget.setWindowTitle("Legend")
        dock_widget.setWidget(self.text_browser)

        self.addDockWidget(Qt.RightDockWidgetArea, dock_widget)

        # link actions to premade map tools
        self.toolPan = QgsMapToolPan(self.canvas)
        self.toolPan.setAction(self.actionPan)

        self.toolClick = QgsMapToolEmitPoint(self.canvas)
        self.toolClick.canvasClicked.connect(self.getLegendInfo)

        # this is to ensure that the map isn't zoomed out everytime the layer changes
        self.first_start = True

        # this boolean is true while there is no active background layer
        # needed to ensure that e.g. opacity isn't attempted to be set on a nonexisting layer
        self.no_bg_layer_flag = True

        # set pantool as default
        self.pan()

    def pan(self):
        """Simply activates the tool and deactivates the other tool if active"""
        self.canvas.setMapTool(self.toolPan)
        # make sure the other button isn't checked to avoid confusion
        self.actionLegend.setChecked(False)

    def info(self):
        self.canvas.setMapTool(self.toolClick)
        self.actionLegend.setChecked(True)
        self.actionPan.setChecked(False)

    def zoomToExtent(self):
        """zooms out/in so that the raster layer is centered"""
        self.canvas.setExtent(self.layer.extent())
        self.canvas.refresh()

    def setBackgroundMapOpacity(self):
        if self.no_bg_layer_flag:
            return
        else:
            self.bg_layer.renderer().setOpacity(self.getBackgroundMapOpacity())
            self.canvas.refresh()

    def getBackgroundMapOpacity(self):
        """Returns the current BG layer opacity as a double [0, 1]. Slider only
            accepts integers, therefore the initial value is divided by hundred."""
        return (self.opacity_slider.value() / 100)

    def showCanvas(self, all_datasets):
        """Called to activate the the window. Input is all of the datasets on 
            the Geocubes server as a dictionary (see main plugin py-file). First a
            default layer (background map, which is on the WMTS server
            but not on Geocubes files) is inserted to the combobox. Then the 
            keys of the dictionary (which are in format layer_name;year) are inserted."""

        # empty box on restart
        self.layer_box.clear()
        self.all_datasets = all_datasets
        self.no_bg_layer_flag = True

        for key in self.all_datasets:
            self.layer_box.addItem(key)

        # zoom to the full extent of the current map
        self.zoomToExtent()

        # default values set
        self.text_browser.setText("Legend will be shown here")
        self.bg_layer_box.setCurrentIndex(2)
        self.opacity_slider.setValue(50)
        self.show()

    def getLegendInfo(self, point):
        """Activated when the canvas is clicked. The click returns a point, which
            is parsed to a string of X and Y coordinates separated by a comma.
            An url to get legend info on this point is formed and used.
            If the request is succesful, the response string is decoded and passed
            to be inserted to the text browser."""
        formatted_point = str(int(point.x())) + "," + str(int(point.y()))

        url = self.formLegendUrl(formatted_point)

        if not url:
            return

        response = requests.get(url, timeout=6)

        # 200 = succesful request
        # the field won't be updated in case of a failed request
        if response.status_code == 200:
            legend_string = response.content.decode("utf-8")
            self.setTextToBrowser(legend_string)

    def setTextToBrowser(self, string):
        """Formats and inserts legend text to the browser. Input is string of 
        raw text data. This is split at semicolons if there are multiple features."""

        # empty on multiple clicks
        self.text_browser.clear()
        strings = string.split(';')

        # no need for a loop if there's only one line
        if len(strings) == 1:
            self.text_browser.setText(string)
        else:
            for text_string in strings:
                # appending allows to insert multi-line texts
                self.text_browser.append(text_string)

    def formLegendUrl(self, formatted_point):
        """Forms an url for querying legend data on a specific coordinate point.
            Data is queried either from the currently selected layer or, if selected
            by the user, from all available layers."""
        key = self.layer_box.currentText()
        resolution = self.getResolutionFromExtent()

        if not key:
            return

        if not resolution:
            resolution = 100

        if self.legend_checkbox.isChecked():
            layer_name = "all"
            year = "2015"
        else:
            value = self.all_datasets[key]
            layer_name, year = value[0], value[3]

        url = (self.url_base + "/legend/" + str(resolution) + "/" +
               layer_name + "/" + formatted_point + "/" + year)

        return url

    def getResolutionFromExtent(self):
        """Estimates the resolution of the imagery currently viewed by user
         based on the width of the canvas. Returns said resolution. Used by
         the legend tool to get info of the correct dataset."""
        # extent as a QgsRectangle
        canvas_extent = self.canvas.extent()

        # width (in meters, since CRS is EPSG:3067) of the current canvas view
        width = canvas_extent.xMaximum() - canvas_extent.xMinimum()

        # find the width threshold closest to the current one
        try:
            closest_width = min(self.all_widths, key=lambda x: abs(x - width))
        except Exception:
            return

        # use the width key to get the corrensponding resolution
        closest_resolution = self.tile_widths[closest_width]

        return closest_resolution

    def addLayer(self):
        """Adds a new layer on the map canvas based on the selection on the combobox.
            Everything else is hardcoded, but the layer name of course changes.
            Layers are identified by name and year (i.e. km2_2018). These type
            of strings are formed first, then the whole url"""
        # often a layer already exists. If so, remove
        try:
            QgsProject.instance().removeMapLayer(self.layer)
        except Exception:
            pass

        key = self.layer_box.currentText()

        if not key:
            return
        # background map doesn't have a specific year attached to it
        if key == "Taustakartta":
            layer_name = key
        else:
            # the desired parameters are housed in the dictionary. Luckily the
            # combobox houses the keys to it. gets a tuple with four values
            value = self.all_datasets[key]
            # name is first value, year last. separated with an underscore
            layer_name = value[0] + "_" + value[3]

        url = ("https://vm0160.kaj.pouta.csc.fi/ogiir_cache/wmts/1.0.0/" +
               "WMTSCapabilities.xml&crs=EPSG:3067&dpiMode=7&format=image/" +
               "png&layers=" + layer_name +
               "&styles=default&tileMatrixSet=GRIDI-FIN")

        self.layer = QgsRasterLayer("url=" + url,
                                    'GEOCUBES DATALAYER - TEMPORARY', 'wms')

        if self.layer.isValid():
            QgsProject.instance().addMapLayer(self.layer, False)
            # if layer is valid and added to the instance, insert it to the canvas
            self.setMapLayers()
            # zoom to the full extent of the map if canvas is started for the first time
            if self.first_start:
                self.zoomToExtent()
                self.first_start = False

    def addBackgroundLayer(self):
        """Adds a background layer to help user locating what they want.
            This layer will be either background map (taustakarta), ortographic
            imagery or nothing at all. BG layer has an opacity value that set by
            the user. Function is called when user selects a layer on the
            combobox."""
        layer_name = self.bg_layer_box.currentText()

        # remove the old background layer, if one exists
        try:
            QgsProject.instance().removeMapLayer(self.bg_layer)
        except Exception:
            pass

        # if user wants no background layer, return without setting a new layer
        if not layer_name or layer_name == 'No reference layer':
            self.no_bg_layer_flag = True
            self.canvas.refresh()
            return
        else:
            self.bg_layer = QgsRasterLayer(
                "url=https://vm0160.kaj.pouta.csc.fi/ogiir_cache/wmts/1.0.0/" +
                "WMTSCapabilities.xml&crs=EPSG:3067&dpiMode=7&format=image/" +
                "png&layers=" + layer_name.lower() +
                "&styles=default&tileMatrixSet=GRIDI-FIN",
                'GEOCUBES BG-LAYER - TEMPORARY', 'wms')

            if self.bg_layer.isValid():
                self.no_bg_layer_flag = False
                QgsProject.instance().addMapLayer(self.bg_layer, False)
                self.bg_layer.renderer().setOpacity(
                    self.getBackgroundMapOpacity())
                self.setMapLayers()

    def setMapLayers(self):
        """Called anytime a new layer is added to the project instance.
        Setting layers to canvas decides what's shown to the user and in which
        order. If there's a background layer, it must be set before the data layer."""
        if self.no_bg_layer_flag:
            self.canvas.setLayers([self.layer])
        else:
            self.canvas.setLayers([self.bg_layer, self.layer])

        self.canvas.refresh()

    def cancel(self):
        self.close()

    def closeEvent(self, event):
        """Activated anytime Mapwindow is closed either by buttons given or
            if the user finds some other way to close the window. 
            Deletes scrap maplayers."""
        try:
            QgsProject.instance().removeMapLayer(self.layer)
            QgsProject.instance().removeMapLayer(self.bg_layer)
        except Exception:
            pass
        QMainWindow.closeEvent(self, event)
Example #12
0
def readRasterLayer(i, allBandData):
    # pylint: disable=too-many-locals

    fileInfo = QFileInfo(shared.rasterFileName[i])
    fileBaseName = fileInfo.baseName()

    layer = QgsRasterLayer(shared.rasterFileName[i], fileBaseName)
    if not layer.isValid():
        shared.fpOut.write("Raster layer '" + shared.rasterFileName[i] +
                           "'failed to load")
        return -1

    # Store the title as the first list item
    allBandData.append(shared.rasterFileTitle[i])

    # Get more info
    xSize = layer.width()
    ySize = layer.height()

    cellWidth = layer.rasterUnitsPerPixelX()
    cellHeight = layer.rasterUnitsPerPixelY()

    provider = layer.dataProvider()
    extnt = provider.extent()
    dpi = provider.dpi()

    shared.fpOut.write("Raster layer '" + shared.rasterFileTitle[i] +
                       "' loaded with X, Y resolution = " + str(cellWidth) +
                       ", " + str(cellHeight) + " m\n")
    #shared.fpOut.write(layer.metadata())
    #shared.fpOut.write(layer.rasterType())

    # Store the above as the second list item
    allBandData.append(
        [provider, xSize, ySize, cellWidth, cellHeight, extnt, dpi])

    # Now store the data for each band as a QgsRasterBlock
    nBands = layer.bandCount()
    for band in range(nBands):
        #shared.fpOut.write(layer.bandName(i))

        bandData = provider.block(band, extnt, xSize, ySize)

        # Store as a further list item
        allBandData.append(bandData)

    # Sort out style
    #shared.fpOut.write("Style file " + str(i) + " is " + shared.rasterFileStyle[i])
    if not shared.rasterFileStyle[i]:
        # No style file specified, so try the default style for this layer
        shared.rasterFileStyle[i] = layer.styleURI()
        #shared.fpOut.write("Trying default style file " + shared.rasterFileStyle[i])

        if not layer.loadDefaultStyle():
            shared.fpOut.write("Could not load default style '" +
                               shared.rasterFileStyle[i] +
                               "' for raster layer '" +
                               shared.rasterFileTitle[i] + "'")

    else:
        # A style file was specified, so try to load it
        #shared.fpOut.write("Trying style file " + shared.rasterFileStyle[i])
        if not layer.loadNamedStyle(shared.rasterFileStyle[i]):
            shared.fpOut.write("Could not load style '" +
                               shared.rasterFileStyle[i] +
                               "' for raster layer '" +
                               shared.rasterFileTitle[i] + "'")

    # Set opacity
    layer.renderer().setOpacity(shared.rasterFileOpacity[i])

    # Add this layer to the app's registry
    QgsProject.instance().addMapLayer(layer)

    return layer
Example #13
0
    def select_HDF5_file(self):
        """Run method that performs all the real work"""
        # show the dialog
        #self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # close the dialog
        self.dlg.close()
        
        # See if OK was pressed
        if result:
            
        #### to open windows browser and search for HDF5 files only:
            myfileNames = QFileDialog.getOpenFileNames(self.dlg,
                                                       self.tr("HDF5 File Selector"), 
                                                       "", 
                                                       self.tr("HDF5 (*.hdf5 *.h5)"))
        
            #### to loop through all selected HDF5 files, just handling one for now
            for i in myfileNames:
                #### bind the HDF5 file
                
                myfile = QgsRasterLayer(i)
                
                #### to deal with multiple datasets within file
                if (len(myfile.subLayers()) > 1):
                    #### to open dataset with desired data and name it Reflectance
                    #mydset = QgsRasterLayer(myfile.subLayers()[0], 'Reflectance')
                    
                    fileName = myfile.subLayers()[0]
                    fileInfo = QFileInfo(fileName)
                    baseName = fileInfo.baseName()
                    mydset = QgsRasterLayer(fileName, baseName)
                    
                    ##################################### Trials of setting proper extent begin here
                    
                    print 'Extent before: '
                    print mydset.extent().toString()
                    
                    ########################## test almost worked
                    ### loaded layer in right place but without data
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #mydset.setExtent(rect)
                    ####################################
                    
                    ######################## test almost worked
                    ### loaded layer in right place but without data
                    #size = QSizeF(1,1)
                    #point = QPointF(326380.0, 4103390.0)
                    #rect = QRectF(point, size)
                    #rect = QgsRectangle(rect)
                    #mydset.setExtent(rect)
                    ###################################
                    
                    ########################## did not change extent
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #context = QgsRenderContext()
                    #context.setExtent(rect)
                    #mydset.draw(context)
                    ####################################
                    
                    #################################### did not change extent
                    #size = QSizeF(1,1)
                    #point = QPointF(326380.0, 4103390.0)
                    #rect = QRectF(point, size)
                    #rect = QgsRectangle(rect)
                    #context = QgsRenderContext()
                    #context.setExtent(rect)
                    #mydset.draw(context)
                    #####################################
                    
                    ###################################### did not change extent
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #context = QgsRenderContext()
                    #context.setExtent(rect)
                    #print 'Context extent: '
                    #print context.extent().toString() #this printed correct extent
                    #mydset.createMapRenderer(context)
                    #######################################
                    
                    ######################## test almost worked
                    ### loaded layer in right place but without data
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #provider = mydset.dataProvider().clone()
                    #mydset.setExtent(rect)
                    #provider.reloadData()
                    ######################################
                    
                    ########################################
                    # did not change extent, loaded data
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #context = QgsRenderContext()
                    #context.setExtent(rect)
                    #renderer = mydset.createMapRenderer(context)
                    #renderer.render()
                    #########################################
                    
                    ###########################################
                    # did not change extent, loaded data
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #mydset.dataProvider().block(52, rect, 1332, 2853)
                    ##########################################
                    
                    #############################################
                    # did not change extent, loaded data
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #mydset.dataProvider().block(426, rect, 1332, 2853)
                    ###############################################
                    
                    #################################################
                    # changed extent but overwrote to no data
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #mydset.setExtent(rect)
                    #mydset.dataProvider().reload()
                    ####################################################
                    
                    ####################################################
                    # changed extent but overwrote to no data
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #mydset.setExtent(rect)
                    #mydset.dataProvider().block(52, rect, 1332, 2853)
                    #mydset.dataProvider().reload()
                    #####################################################
                    
                    #############################################
                    # not setting new cloned data provider correctly
                    # method wants QString object but gets meaningless string
                    #rect = QgsRectangle(326380.0,4103390.0-2853,326380.0+1332,4103390.0)
                    #provider = mydset.dataProvider()
                    #print 'Provider: '
                    #print provider
                    #clone = provider.clone()
                    #print 'Provider clone: '
                    #print clone
                    #clone.block(1, rect, 1332, 2853)
                    #mydset.setDataProvider(str(clone))
                    #print 'New provider: '
                    #print mydset.dataProvider()
                    ##############################################
                    
                    ######## printing result of extent changes
                    print 'Extent after: '
                    print mydset.extent().toString()
                    ###################################
                    
                    ####################################### Trials of setting proper extent end here
                    
                    #### to set proper Coordinate Reference System (crs) info
                    crs = mydset.crs()
                    crs.createFromId(32611)
                    mydset.setCrs(crs)
                    
                    ### this was josh's recommendation
                    #mycrs = QgsCoordinateReferenceSystem(32611)
                    #self.iface.mapCanvas().mapRenderer().setDestinationCrs(mycrs)

                    #### to set raster bands to load as RGB
                    mydset.renderer().setGreenBand(34)
                    mydset.renderer().setRedBand(52)
                    mydset.renderer().setBlueBand(18)
                    
                    ####### Tristan changes
                    
                    #myrenderer = mydset.renderer()
                    #print 'Renderer Type: '
                    #print mydset.renderer().type()
                    
                   # print 'Renderer block before: '
                    #print myrenderer.block(426, rect, 1, 1)
                    #myrenderer.block(426, rect, 1, 1)
                    #print 'Renderer block after: '
                    #print myrenderer.block() not enough arguments
                    
                    #if hasattr(mydset, "setCacheImage"):
                        #mydset.setCacheImage(None)
                        #mydset.triggerRepaint()
                        
                    #mydset.dataProvider().reloadData()
                    #mydset.triggerRepaint()
                    
                    #self.iface.legendInterface().refreshLayerSymbology(mydset)
                    
                    #mydset.reload()
                    #mydset.reloadData()
                    #mydset.triggerRepaint()
                    #mydset.draw()
                    #print 'Extent: '
                    #print mydset.extent().toString()
                    
                   # ident = rlayer.dataProvider().identify(QgsPoint(15.30, 40.98), \
                   # QgsRaster.IdentifyFormatValue)
                  #  if ident.isValid():
                 #   print ident.results()
                    
                    ####### End Tristan changes
                    
                    #### to add selected dataset/raster to map canvas
                    QgsMapLayerRegistry.instance().addMapLayer(mydset)
                    #canvas=QgsMapCanvas()
                    #canvas.show()
                    #canvas.setExtent(mydset.extent())
                    #canvas.setLayerSet([QgsMapCanvasLayer(mydset)])
                    
                    ####################################### Script for getting all raster values
                    # takes 11 minutes to run on (426,1332,2583)
                    #list = []
                    #for x in range(mydset.width()):
                        #x_coord = x
                        
                        #print 'Doing ' +str(x) + 'x right now...'
                        #for y in range(mydset.height()):
                            #y_coord = y
                            #ident = mydset.dataProvider().identify(QgsPoint(x_coord, y_coord), \
                                                           #QgsRaster.IdentifyFormatValue)
                            #list.append(ident.results().values())
                            
                    
                    #print 'Length of list is: '
                    #print len(list)
                    #print 'Length of list[0] is: '
                    #print len(list[0])
                    
                    ############################################    
                        
                        
                    #if ident.isValid():
                        #print ident.results()
                    
                        
Example #14
0
class ProjectModule(Module):

    def __init__(self, plugin):
        super(ProjectModule, self).__init__(plugin)

        # Project settings
        # self.projectLayerView = None  # QgsLayerTreeView()
        self.metadata = None  # Metadata()

        self.projectGroupIndex = -1
        self.drawingsGroupIndex = -1
        self.drawingsGroupName = ''

        self.geoLayer = None  # QgsRasterLayer()
        self.plan = None  # Collection()
        self.section = None  # Collection()
        self.grid = None  # Collection()
        self.site = None  # Collection()
        self._collections = {}  # {Collection()}

        # Tools
        self.identifyMapTool = None  # MapToolIndentifyItems()

        # Internal variables
        self._mapAction = MapAction.MoveMap
        self._filterAction = FilterAction.ExclusiveHighlightFilter
        self._drawingAction = DrawingAction.NoDrawingAction
        self._layerSnappingAction = None  # LayerSnappingAction()
        self._itemLogPath = ''

    # Create the gui when the plugin is first created
    def initGui(self):
        dock = ProjectDock(self._plugin.iface.mainWindow())
        self._initDockGui(dock, Qt.LeftDockWidgetArea, self._plugin.pluginAction)
        # self.projectLayerView = QgsLayerTreeView()
        # self._layerSnappingAction = LayerSnappingAction(self._plugin.iface, self.projectLayerView)
        # self._plugin.iface.legendInterface().addLegendLayerAction(
        #    self._layerSnappingAction, '', 'arksnap', QgsMapLayer.VectorLayer, True)

        """
        # Create the Layer Model and View
        # TODO Should only show our subgroup but crashes!
        # self.projectLayerModel
        # = QgsLayerTreeModel(QgsProject.instance().layerTreeRoot().findGroup(Config.projectGroupName), self);
        self.projectLayerModel = QgsLayerTreeModel(QgsProject.instance().layerTreeRoot(), self)
        self.projectLayerModel.setFlag(QgsLayerTreeModel.ShowLegend)
        self.projectLayerModel.setFlag(QgsLayerTreeModel.ShowLegendAsTree)
        self.projectLayerModel.setFlag(QgsLayerTreeModel.AllowNodeReorder, True)
        self.projectLayerModel.setFlag(QgsLayerTreeModel.AllowNodeRename, False)
        self.projectLayerModel.setFlag(QgsLayerTreeModel.AllowNodeChangeVisibility)
        self.projectLayerModel.setFlag(QgsLayerTreeModel.AllowLegendChangeState)
        self.projectLayerModel.setFlag(QgsLayerTreeModel.AllowSymbologyChangeState)
        self.projectLayerModel.setAutoCollapseLegendNodes(-1)
        self.projectLayerView.setModel(self.projectLayerModel)
        menuProvider = LayerTreeMenu(self, self.projectLayerView)
        self.projectLayerView.setMenuProvider(menuProvider)
        self.projectLayerView.setCurrentLayer(self._plugin.iface.activeLayer())
        self.projectLayerView.doubleClicked.connect(self._plugin.iface.actionOpenTable().trigger)
        self.projectLayerView.currentLayerChanged.connect(self._plugin.mapCanvas().setCurrentLayer)
        self.projectLayerView.currentLayerChanged.connect(self._plugin.iface.setActiveLayer)
        self._plugin.iface.currentLayerChanged.connect(self.projectLayerView.setCurrentLayer)
        self.layerViewAction = self.addDockAction(
            ':/plugins/ark/tree.svg', self.tr(u'Toggle Layer View'), callback=self._toggleLayerView, checkable=True)
        self.layerViewAction.setChecked(True)
        """

        # Add Settings to the toolbar TODO move to end of dock?
        self.addDockSeparator()
        self.addDockAction(':/plugins/ark/settings.svg',
                           self._plugin.tr(u'Project Settings'), self._triggerSettingsDialog)

        # Init the identify tool and add to the toolbar
        self.identifyAction = self.addDockAction(
            ':/plugins/ark/filter/identify.png',
            self._plugin.tr(u'Identify Items'),
            callback=self.triggerIdentifyAction,
            checkable=True
        )
        self.identifyMapTool = MapToolIndentifyItems(self._plugin)
        self.identifyMapTool.setAction(self.identifyAction)

        # Init the Load Item tool and add to the toolbar
        self.showItemAction = self.addDockAction(
            ':/plugins/ark/filter/showContext.png',
            self._plugin.tr(u'Show Item'),
            callback=self._showItem
        )

        # If the project or layers or legend indexes change make sure we stay updated
        self._plugin.legendInterface().groupIndexChanged.connect(self._groupIndexChanged)

    # Load the project settings when project is loaded
    def loadProject(self):
        if Settings.isProjectConfigured():
            self.projectGroupIndex = layers.createLayerGroup(self._plugin.iface, Config.projectGroupName)

            # Load the layer collections
            self._addCollection('grid')
            self._addCollection('plan')
            self._addCollection('section')
            self._addCollection('site')
            self.drawingsGroupName = Config.drawings['context']['layersGroupName']
            if (self.collection('grid').loadCollection()
                    and self.collection('plan').loadCollection()
                    and self.collection('section').loadCollection()
                    and self.collection('site').loadCollection()):

                self._dock.loadProject(self._plugin)

                if self.collection('plan').isLogged():
                    self._itemLogPath = os.path.join(self.collection('plan').projectPath,
                                                     self.collection('plan').settings.collectionPath,
                                                     'log/itemLog.csv')
                    if not QFile.exists(self._itemLogPath):
                        fd = open(self._itemLogPath, 'a')
                        fd.write('timestamp,action,siteCode,classCode,itemId\n')
                        fd.close()

                    # TODO Think of a better way...
                    # self.metadata = Metadata(self._dock.widget.sourceWidget)
                    # self.metadata.metadataChanged.connect(self.updateMapToolAttributes)
            self._initialised = True
            return True
        return False

    # Close the project
    def closeProject(self):
        if self.identifyMapTool.action() and self.identifyMapTool.action().isChecked():
            self._plugin.iface.actionPan().trigger()
        # TODO Unload the drawing tools!
        self._dock.closeProject()
        # self.metadata.metadataChanged.disconnect(self.updateMapToolAttributes)
        # Unload the layers
        for collection in self._collections:
            self._collections[collection].unload()
        del self._collections
        self._collections = {}
        self._plugin.iface.legendInterface().removeLegendLayerAction(self._layerSnappingAction)
        self._initialised = False

    # Project

    def crs(self):
        return self._plugin.iface.mapCanvas().mapSettings().destinationCrs()

    def crsId(self):
        return self.crs().authid()

    def projectFolder(self):
        proj = Project.dir()
        proj.cdUp()
        return proj.absolutePath()

    def configure(self):
        wizard = ProjectWizard(self._plugin.iface.mainWindow())

        ok = wizard.exec_()

        if ok:

            if wizard.newProject():
                if Project.exists():
                    Project.write()
                Project.clear()
                projectFolderPath = os.path.join(wizard.projectFolder(), 'project')
                if not QDir(projectFolderPath).mkpath('.'):
                    return False
                projectFilePath = os.path.join(projectFolderPath, wizard.projectFilename() + '.qgs')
                Project.setFileName(projectFilePath)

            Settings.setProjectCode(wizard.project().projectCode())
            Settings.setProjectName(wizard.project().projectName())
            Settings.setSiteCode(wizard.project().siteCode())
            if not Project.title():
                Project.setTitle(wizard.project().projectCode() + ' - ' + wizard.project().projectName())

            self._initialised = Project.write()
            if self._initialised:

                # We always want the site collection
                self._addCollection('site')
                self.collection('site').loadCollection()
                # Add the Site Location if entered
                location = wizard.projectLocation()
                if not location.isEmpty():
                    siteCode = wizard.project().siteCode() if wizard.project().siteCode() else wizard.project().projectCode()
                    item = Item(siteCode, 'site', siteCode)
                    source = Source('other', item)
                    audit = Audit(Settings.userFullName(), utils.timestamp())
                    itemFeature = ItemFeature(item, 'loc', None, source, 'New Project Wizard', audit)
                    layer = self.collection('site').layer('points')
                    feature = QgsFeature(layer.pendingFields(), 0)
                    feature.setGeometry(QgsGeometry(location))
                    attributes = itemFeature.toFeature(feature)
                    layers.addFeatures([feature], layer)

                # Temp load of other collection, later do on demand
                self._addCollection('plan')
                self.collection('plan').loadCollection()
                self._addCollection('section')
                self.collection('section').loadCollection()
                self._addCollection('grid')
                self.collection('grid').loadCollection()

                # self._configureDrawing('context')
                # self._configureDrawing('plan')
                # self._configureDrawing('section')

                Settings.setProjectConfigured()

        return ok

    def _triggerSettingsDialog(self):
        if Settings.isProjectConfigured():
            self.showSettingsDialog()
        else:
            self.configure()

    def _configureDrawing(self, drawing):
        Settings.drawingDir(drawing).mkpath('.')
        Settings.georefDrawingDir(drawing).mkpath('.')

    def _setDrawing(self, pmd):
        self.metadata.setSiteCode(pmd.siteCode)
        self.metadata.setClassCode(pmd.sourceClass)
        if pmd.sourceId > 0:
            self.metadata.setItemId(pmd.sourceId)
            self.metadata.setSourceId(pmd.sourceId)
        self.metadata.setSourceCode('drawing')
        self.metadata.setSourceClass(pmd.sourceClass)
        self.metadata.setSourceFile(pmd.filename)
        self.metadata.setEditor(Settings.userFullName())

    def _groupIndexChanged(self, oldIndex, newIndex):
        if (oldIndex == self.projectGroupIndex):
            self.projectGroupIndex = newIndex

    def loadGeoLayer(self, geoFile, zoomToLayer=True):
        # TODO Check if already loaded, remove old one?
        self.geoLayer = QgsRasterLayer(geoFile.absoluteFilePath(), geoFile.completeBaseName())
        self.geoLayer.renderer().setOpacity(Settings.drawingTransparency() / 100.0)
        QgsMapLayerRegistry.instance().addMapLayer(self.geoLayer)
        if (self.drawingsGroupIndex < 0):
            self.drawingsGroupIndex = layers.createLayerGroup(
                self._plugin.iface, self.drawingsGroupName, Config.projectGroupName)
        self._plugin.legendInterface().moveLayer(self.geoLayer, self.drawingsGroupIndex)
        if zoomToLayer:
            self._plugin.mapCanvas().setExtent(self.geoLayer.extent())

    def clearDrawings(self):
        if (self.drawingsGroupIndex >= 0):
            self.drawingsLayerTreeGroup().removeAllChildren()

    def drawingsLayerTreeGroup(self):
        if (self.drawingsGroupIndex >= 0):
            return QgsProject.instance().layerTreeRoot().findGroup(self.drawingsGroupName)
        else:
            return None

    def isArkGroup(self, name):
        for collection in self._collections:
            if self._collections[collection].isCollectionGroup(name):
                return True
        return name == Config.projectGroupName or name == self.drawingsGroupName

    def isArkLayer(self, layerId):
        for collection in self._collections:
            if self._collections[collection].isCollectionLayer(layerId):
                return True
        return False

    def _pluginStylesPath(self):
        return os.path.join(self._plugin.pluginPath, 'ark', 'styles')

    def _styleFile(self, layerPath, layerName):
        # First see if the layer itself has a default style saved
        filePath = layerPath + '/' + layerName + '.qml'
        if QFile.exists(filePath):
            return filePath
        # Next see if the layer name has a style in the styles folder (which may
        # be a special folder, the site folder or the plugin folder)
        filePath = Settings.stylePath() + '/' + layerName + '.qml'
        if QFile.exists(filePath):
            return filePath
        # Finally, check the plugin folder for the default style
        filePath = self._pluginStylesPath() + '/' + layerName + '.qml'
        if QFile.exists(filePath):
            return filePath
        # If we didn't find that then something is wrong!
        return ''

    def _addCollection(self, collection):
        config = Config.collections[collection]
        path = config['path']
        bufferPath = path + '/buffer'
        logPath = path + '/log'

        config['collection'] = collection
        config['crs'] = self.crs()
        config['parentGroupName'] = Config.projectGroupName

        for layer in config['layers']:
            name = layer['name']
            layer['fields'] = config['fields']
            layer['multi'] = config['multi']
            layer['crs'] = self.crs()
            layer['path'] = layers.shapeFilePath(path, name)
            layer['stylePath'] = layers.styleFilePath(self._pluginStylesPath(), name)
            layer['buffer'] = config['buffer']
            if config['buffer']:
                bufferName = name + Config.bufferSuffix
                layer['bufferName'] = bufferName
                layer['bufferName'] = bufferName
                layer['bufferPath'] = layers.shapeFilePath(bufferPath, bufferName)
            else:
                layer['bufferName'] = ''
                layer['bufferPath'] = ''
            layer['log'] = config['log']
            if config['log']:
                logName = name + Config.logSuffix
                layer['logName'] = logName
                layer['logPath'] = layers.shapeFilePath(logPath, logName)
            else:
                layer['logName'] = ''
                layer['logPath'] = ''

        settings = CollectionSettings.fromArray(config)
        if config['item']:
            self._collections[collection] = ItemCollection(self._plugin.iface, self.projectFolder(), settings)
        else:
            self._collections[collection] = Collection(self._plugin.iface, self.projectFolder(), settings)

    def addDockSeparator(self):
        self._dock.toolbar.addSeparator()

    def addDockAction(self, iconPath, text, callback=None, enabled=True, checkable=False, tip=None, whatsThis=None):
        action = QAction(QIcon(iconPath), text, self._dock)
        if callback is not None:
            action.triggered.connect(callback)
        action.setEnabled(enabled)
        action.setCheckable(checkable)
        if tip is not None:
            action.setStatusTip(tip)
        if whatsThis is not None:
            action.setWhatsThis(whatsThis)
        self._dock.toolbar.addAction(action)
        # self.actions.append(action)
        return action

    def collection(self, collection):
        if collection in self._collections:
            return self._collections[collection]
        return None

    # Identify Tool

    def triggerIdentifyAction(self, checked):
        if checked:
            self._plugin.mapCanvas().setMapTool(self.identifyMapTool)
        else:
            self._plugin.mapCanvas().unsetMapTool(self.identifyMapTool)

    # Show Items Tool

    def _showItem(self):
        classCodes = sorted(set(self.collection('plan').uniqueValues('class')))
        dialog = SelectItemDialog(Settings.siteCodes(), Settings.siteCode(),
                                  classCodes, self._plugin.iface.mainWindow())
        if dialog.exec_():
            self.drawingModule.showItem(dialog.item(), dialog.loadDrawings(), dialog.zoomToItem())

    # Plan Tools

    def loadDrawing(self, item, zoomToDrawing=True):
        if not Config.classCodes[item.classCode()]['drawing']:
            return
        drawingDir = Settings.georefDrawingDir(item.classCode())
        drawingDir.setFilter(QDir.Files | QDir.NoDotAndDotDot)
        name = item.name()
        nameList = []
        nameList.append(name + '.png')
        nameList.append(name + '.tif')
        nameList.append(name + '.tiff')
        nameList.append(name + '_*.png')
        nameList.append(name + '_*.tif')
        nameList.append(name + '_*.tiff')
        drawingDir.setNameFilters(nameList)
        drawings = drawingDir.entryInfoList()
        for drawing in drawings:
            self._setDrawing(Drawing(drawing))
            self._plugin.loadGeoLayer(drawing, zoomToDrawing)

    def loadSourceDrawings(self, item, clearDrawings=False):
        if item.isInvalid():
            return
        sourceKeys = set()
        sourceKeys.add(item)
        itemRequest = item.featureRequest()
        for feature in self.collection('plan').layer('polygons').getFeatures(itemRequest):
            source = Source(feature)
            if source.item().isValid():
                sourceKeys.add(source.item())
        for feature in self.collection('plan').layer('lines').getFeatures(itemRequest):
            source = Source(feature)
            if source.item.isValid():
                sourceKeys.add(source.item())
        for feature in self.collection('plan').layer('points').getFeatures(itemRequest):
            source = Source(feature)
            if source.item().isValid():
                sourceKeys.add(source.item())
        if clearDrawings and len(sourceKeys) > 0:
            self.clearDrawings()
        for sourceKey in sorted(sourceKeys):
            self.loadDrawing(sourceKey)

    # Layer Methods

    def mergeBuffers(self):
        self._mergeBuffers(self.collection('plan'))
        self._mergeBuffers(self.collection('section'))
        self._mergeBuffers(self.collection('site'))

    def _mergeBuffers(self, collection):
        # Check the layers are writable
        name = collection.settings.collectionGroupName
        if not collection.isWritable():
            self._plugin.showCriticalMessage(
                name + ' layers are not writable! Please correct the permissions and log out.', 0)
            return

        # Check the buffers contain valid data
        errors = self._preMergeBufferCheck(collection.buffer('points'))
        errors.extend(self._preMergeBufferCheck(collection.buffer('lines')))
        errors.extend(self._preMergeBufferCheck(collection.buffer('polygons')))
        if len(errors) > 0:
            dialog = ItemFeatureErrorDialog()
            dialog.loadErrors(errors)
            dialog.exec_()
            if not dialog.ignoreErrors():
                return

        # Update the audit attributes
        timestamp = utils.timestamp()
        user = Settings.userFullName()
        self._preMergeBufferUpdate(collection.buffer('points'), timestamp, user)
        self._preMergeBufferUpdate(collection.buffer('lines'), timestamp, user)
        self._preMergeBufferUpdate(collection.buffer('polygons'), timestamp, user)

        # Finally actually merge the data
        if collection.mergeBuffers('Merge data', timestamp):
            self._plugin.showInfoMessage(name + ' data successfully merged.')
            # TODO pass current Item...
            self._logItemAction(Item(), 'Merge Buffers', timestamp)
            # TODO Signal out layers merged for schematic dock to catch
            # if self._editSchematic:
            #     self._editSchematic = False
            #     self._dock.activateSchematicCheck()
            #     self._findContext()
        else:
            self._plugin.showCriticalMessage(
                name + ' data merge failed! Some data has not been saved, please check your data.', 5)

    def _preMergeBufferCheck(self, layer):
        errors = []
        row = 0
        for feature in layer.getFeatures():
            # Set up the error template
            error = ItemFeatureError()
            error.layer = layer.name()
            error.row = row
            row = row + 1
            error.fid = feature.id()
            error.feature.fromFeature(feature)
            # Feature must be valid
            if not feature.isValid():
                error.field = 'feature'
                error.message = 'Invalid Feature'
                errors.append(copy.deepcopy(error))
            # Geometry must be valid
            error.field = 'geometry'
            if feature.geometry() is None:
                error.message = 'No Geometry'
                errors.append(copy.deepcopy(error))
            elif feature.geometry().isEmpty():
                error.message = 'Empty Geometry'
                errors.append(copy.deepcopy(error))
            else:
                error.message = 'Invalid Geometry'
                geomErrs = feature.geometry().validateGeometry()
                # Ignore the last error, it is just a total
                for err in geomErrs[:-1]:
                    error.message = err.what()
                    errors.append(copy.deepcopy(error))
            # Key attributes that must always be populated
            if utils.isEmpty(feature.attribute('site')):
                error.field = 'site'
                error.message = 'Site Code is required'
                errors.append(copy.deepcopy(error))
            if utils.isEmpty(feature.attribute('class')):
                error.field = 'class'
                error.message = 'Class Code is required'
                errors.append(copy.deepcopy(error))
            if utils.isEmpty(feature.attribute('id')):
                error.field = 'id'
                error.message = 'ID is required'
                errors.append(copy.deepcopy(error))
            if utils.isEmpty(feature.attribute('category')):
                error.field = 'category'
                error.message = 'Category is required'
                errors.append(copy.deepcopy(error))
            if utils.isEmpty(feature.attribute('source_cd')):
                error.field = 'source_cd'
                error.message = 'Source Code is required'
                error.ignore = True
                # errors.append(copy.deepcopy(error))
            # Source attributes required depend on the source type
            if feature.attribute('source_cd') == 'creator' or feature.attribute('source_cd') == 'other':
                if utils.isEmpty(feature.attribute('comment')):
                    error.field = 'source_cd'
                    error.message = 'Comment is required for Source type of Creator or Other'
                    error.ignore = True
                    # errors.append(copy.deepcopy(error))
            elif feature.attribute('source_cd') == 'survey':
                if utils.isEmpty(feature.attribute('file')):
                    error.field = 'source_cd'
                    error.message = 'Filename is required for Source type of Survey'
                    error.ignore = True
                    # errors.append(copy.deepcopy(error))
            else:  # 'drw', 'unc', 'skt', 'cln', 'mod', 'inf'
                if ((feature.attribute('source_cd') == 'drawing' or feature.attribute('source_cd') == 'unchecked')
                        and utils.isEmpty(feature.attribute('file'))):
                    error.field = 'source_cd'
                    error.message = 'Filename is required for Source type of Drawing'
                    error.ignore = True
                    # errors.append(copy.deepcopy(error))
                if (utils.isEmpty(feature.attribute('source_cl')) or utils.isEmpty(feature.attribute('source_id'))):
                    error.field = 'source_cd'
                    error.message = 'Source Class and ID is required'
                    error.ignore = True
                    # errors.append(copy.deepcopy(error))
        return errors

    def _preMergeBufferUpdate(self, layer, timestamp, user):
        createdIdx = layer.fieldNameIndex('created')
        creatorIdx = layer.fieldNameIndex('creator')
        modifiedIdx = layer.fieldNameIndex('modified')
        modifierIdx = layer.fieldNameIndex('modifier')
        for feature in layer.getFeatures():
            if utils.isEmpty(feature.attribute('created')):
                layer.changeAttributeValue(feature.id(), createdIdx, timestamp)
                layer.changeAttributeValue(feature.id(), creatorIdx, user)
            else:
                layer.changeAttributeValue(feature.id(), modifiedIdx, timestamp)
                layer.changeAttributeValue(feature.id(), modifierIdx, user)

    def resetBuffers(self):
        self.collection('plan').resetBuffers('Clear Buffers')
        self.collection('section').resetBuffers('Clear Buffers')
        self.collection('site').resetBuffers('Clear Buffers')
        # TODO Signal out layers reset for schematic dock to catch
        # if self._editSchematic:
        #     self._editSchematic = False
        #     self._dock.activateSchematicCheck()

    def _confirmDelete(self, itemId, title='Confirm Delete Item', label=None):
        if not label:
            label = 'This action ***DELETES*** item ' + \
                str(itemId) + ' from the saved data.\n\nPlease enter the item ID to confirm.'
        confirm, ok = QInputDialog.getText(None, title, label, text='')
        return ok and confirm == str(itemId)

    def _logItemAction(self, item, action, timestamp=None):
        if self.collection('plan').settings.log:
            if not timestamp:
                timestamp = utils.timestamp()
            fd = open(self._itemLogPath, 'a')
            fd.write(utils.doublequote(timestamp) + ',' + utils.doublequote(action) + ',' + item.toCsv() + '\n')
            fd.close()

    def editInBuffers(self, item):
        timestamp = utils.timestamp()
        if self.collection('plan').moveItemToBuffers(item, 'Edit Item', timestamp):
            self._logItemAction(item, 'Edit Item', timestamp)
            self._metadataFromBuffers(item)

    def deleteItem(self, item):
        if self._confirmDelete(item.itemId(), 'Confirm Delete Item'):
            timestamp = utils.timestamp()
            if self.collection('plan').deleteItem(item, 'Delete Item', timestamp):
                self._logItemAction(item, 'Delete Item', timestamp)

    def applyItemActions(self,
                         item,
                         mapAction=MapAction.NoMapAction,
                         filterAction=FilterAction.NoFilterAction,
                         drawingAction=DrawingAction.NoDrawingAction):
        if drawingAction != DrawingAction.NoDrawingAction:
            self.loadSourceDrawings(item, drawingAction == DrawingAction.LoadDrawings)

        if filterAction != FilterAction.NoFilterAction:
            self._plugin.filter().applyItemAction(item, filterAction)

        if mapAction == MapAction.ZoomMap:
            self._zoomToItem(item)
        elif mapAction == MapAction.PanMap:
            self._panToItem(item)
        elif mapAction == MapAction.MoveMap:
            self._moveToItem(item)
        self._plugin.mapCanvas().refresh()

    def showItem(self, item, loadDrawings=True, zoom=True):
        self._plugin.showMessage('Loading ' + item.itemLabel())
        self._plugin.filter().filterItem(item)
        if loadDrawings:
            self.loadSourceDrawings(item, True)
        if zoom:
            self._zoomToItem(item)

    def panToItem(self, item, highlight=False):
        if highlight:
            self._plugin.filter().highlightItem(item)
        self._panToItem(item)
        self._plugin.mapCanvas().refresh()

    def zoomToItem(self, item, highlight=False):
        if highlight:
            self._plugin.filter().highlightItem(item)
        self._zoomToItem(item)
        self._plugin.mapCanvas().refresh()

    def moveToItem(self, item, highlight=False):
        ret = -1
        if highlight:
            ret = self._plugin.filter().highlightItem(item)
        self._moveToItem(item)
        self._plugin.mapCanvas().refresh()
        return ret

    def _moveToItem(self, item):
        self._moveToExtent(self.itemExtent(item))

    def _moveToExtent(self, extent):
        if extent is None or extent.isNull() or extent.isEmpty():
            return
        mapExtent = self._plugin.mapCanvas().extent()
        if (extent.width() > mapExtent.width() or extent.height() > mapExtent.height()
                or extent.width() * extent.height() > mapExtent.width() * mapExtent.height()):
            self._zoomToExtent(extent)
        else:
            self._panToExtent(extent)

    def _panToItem(self, item):
        self._panToExtent(self.itemExtent(item))

    def _panToExtent(self, extent):
        if extent is None or extent.isNull() or extent.isEmpty():
            return
        self._plugin.mapCanvas().setCenter(extent.center())

    def _zoomToItem(self, item):
        self._zoomToExtent(self.itemExtent(item))

    def _zoomToExtent(self, extent):
        if extent is None or extent.isNull() or extent.isEmpty():
            return
        extent.scale(1.05)
        self._plugin.mapCanvas().setExtent(extent)

    def filterItem(self, item):
        self._plugin.filter().filterItem(item)
        self._plugin.mapCanvas().refresh()

    def excludeFilterItem(self, item):
        self._plugin.filter().excludeItem(item)
        self._plugin.mapCanvas().refresh()

    def highlightItem(self, item):
        self._plugin.filter().highlightItem(item)
        self._plugin.mapCanvas().refresh()

    def addHighlightItem(self, item):
        self._plugin.filter().addHighlightItem(item)
        self._plugin.mapCanvas().refresh()

    def itemExtent(self, item):
        requestKey = self._plugin.data().nodesItem(item)
        request = requestKey.featureRequest()
        points = self._requestAsLayer(request, self.collection('plan').layer('points'), 'points')
        lines = self._requestAsLayer(request, self.collection('plan').layer('lines'), 'lines')
        polygons = self._requestAsLayer(request, self.collection('plan').layer('polygons'), 'polygons')
        extent = None
        extent = self._combineExtentWith(extent, polygons)
        extent = self._combineExtentWith(extent, lines)
        extent = self._combineExtentWith(extent, points)
        return extent

    def _requestAsLayer(self, request, fromLayer, toName):
        toLayer = layers.cloneAsMemoryLayer(fromLayer, toName)
        layers.copyFeatureRequest(request, fromLayer, toLayer)
        toLayer.updateExtents()
        return toLayer

    def _combineExtentWith(self, extent, layer):
        if (layer is not None and layer.isValid() and layer.featureCount() > 0):
            layerExtent = layer.extent()
            if layerExtent.isNull() or layerExtent.isEmpty():
                return extent
            if extent is None:
                extent = layerExtent
            else:
                extent.combineExtentWith(layerExtent)
        return extent

    def _sectionItemList(self, siteCode):
        # TODO in 2.14 use addOrderBy()
        request = utils.featureRequest(utils.eqClause('site', siteCode) + ' and ' + utils.eqClause('class', 'sec'))
        features = layers.getAllFeaturesRequest(request, self.collection('plan').layer('lines'))
        lst = []
        for feature in features:
            lst.append(ItemFeature(feature))
        lst.sort()
        return lst

    def _sectionLineGeometry(self, item):
        if item and item.isValid():
            sln = ItemFeature(item, 'sln')
            request = sln.featureRequest()
            features = layers.getAllFeaturesRequest(request, self.collection('plan').layer('lines'))
            for feature in features:
                return QgsGeometry(feature.geometry())
        return QgsGeometry()

    def _metadataFromBuffers(self, item):
        feature = self._getFeature(self.collection('plan').buffer('polygons'), item, 'sch')
        if feature:
            self.metadata.fromFeature(feature)
            return
        feature = self._getFeature(self.collection('plan').buffer('polygons'), item, 'scs')
        if feature:
            self.metadata.fromFeature(feature)
            return
        feature = self._getFeature(self.collection('plan').buffer('polygons'), item)
        if feature:
            self.metadata.fromFeature(feature)
            return
        feature = self._getFeature(self.collection('plan').buffer('lines'), item)
        if feature:
            self.metadata.fromFeature(feature)
            return
        feature = self._getFeature(self.collection('plan').buffer('points'), item)
        if feature:
            self.metadata.fromFeature(feature)

    def _getFeature(self, layer, item, category=''):
        req = None
        if category:
            sch = ItemFeature(item, 'sch')
            req = sch.featureRequest()
        else:
            req = item.featureRequest()
        try:
            return layer.getFeatures(req).next()
        except StopIteration:
            return None
        return None
class TestQgsRasterRendererCreateSld(unittest.TestCase):

    """
     This class tests the creation of SLD from QGis raster layers
    """

    @classmethod
    def setUpClass(self):
        pass

    def setUp(self):
        pass

    def tearDown(self):
        pass

    def __init__(self, methodName):
        """Run once on class initialization."""
        unittest.TestCase.__init__(self, methodName)
        myPath = os.path.join(TEST_DATA_DIR, 'landsat.tif')
        rasterFileInfo = QFileInfo(myPath)
        self.raster_layer = QgsRasterLayer(rasterFileInfo.filePath(),
                                           rasterFileInfo.completeBaseName())

    def testSingleBandPseudoColorRenderer_Interpolated(self):
        # get min and max of the band to renderer
        bandNo = 3
        stats = self.raster_layer.dataProvider().bandStatistics(bandNo, QgsRasterBandStats.Min | QgsRasterBandStats.Max)
        minValue = stats.minimumValue
        maxValue = stats.maximumValue
        # create shader for the renderer
        shader = QgsRasterShader(minValue, maxValue)
        colorRampShaderFcn = QgsColorRampShader(minValue, maxValue)
        colorRampShaderFcn.setColorRampType(QgsColorRampShader.Interpolated)
        colorRampShaderFcn.setClassificationMode(QgsColorRampShader.Continuous)
        colorRampShaderFcn.setClip(True)
        items = []
        for index in range(10):
            items.append(QgsColorRampShader.ColorRampItem(index, QColor('#{0:02d}{0:02d}{0:02d}'.format(index)), "{}".format(index)))
        colorRampShaderFcn.setColorRampItemList(items)
        shader.setRasterShaderFunction(colorRampShaderFcn)
        # create instance to test
        rasterRenderer = QgsSingleBandPseudoColorRenderer(self.raster_layer.dataProvider(), bandNo, shader)
        self.raster_layer.setRenderer(rasterRenderer)

        # do test
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '{}'.format(bandNo))
        # check ColorMapEntry classes
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertFalse(colorMap.isNull())
        self.assertEqual(colorMap.attribute('type'), 'ramp')
        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(colorMapEntries.count(), 10)
        for index in range(colorMapEntries.count()):
            colorMapEntry = colorMapEntries.at(index).toElement()
            self.assertEqual(colorMapEntry.attribute('quantity'), '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('label'), '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('opacity'), '')
            self.assertEqual(colorMapEntry.attribute('color'), '#{0:02d}{0:02d}{0:02d}'.format(index))

    def testSingleBandPseudoColorRenderer_Discrete(self):
        # get min and max of the band to renderer
        bandNo = 3
        stats = self.raster_layer.dataProvider().bandStatistics(bandNo, QgsRasterBandStats.Min | QgsRasterBandStats.Max)
        minValue = stats.minimumValue
        maxValue = stats.maximumValue
        # create shader for the renderer
        shader = QgsRasterShader(minValue, maxValue)
        colorRampShaderFcn = QgsColorRampShader(minValue, maxValue)
        colorRampShaderFcn.setColorRampType(QgsColorRampShader.Discrete)
        colorRampShaderFcn.setClassificationMode(QgsColorRampShader.Continuous)
        colorRampShaderFcn.setClip(True)
        items = []
        for index in range(10):
            items.append(QgsColorRampShader.ColorRampItem(index, QColor('#{0:02d}{0:02d}{0:02d}'.format(index)), "{}".format(index)))
        colorRampShaderFcn.setColorRampItemList(items)
        shader.setRasterShaderFunction(colorRampShaderFcn)
        # create instance to test
        rasterRenderer = QgsSingleBandPseudoColorRenderer(self.raster_layer.dataProvider(), bandNo, shader)
        self.raster_layer.setRenderer(rasterRenderer)

        # do test
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '{}'.format(bandNo))
        # check ColorMapEntry classes
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertFalse(colorMap.isNull())
        self.assertEqual(colorMap.attribute('type'), 'intervals')
        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(colorMapEntries.count(), 10)
        for index in range(colorMapEntries.count()):
            colorMapEntry = colorMapEntries.at(index).toElement()
            self.assertEqual(colorMapEntry.attribute('quantity'), '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('label'), '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('opacity'), '')
            self.assertEqual(colorMapEntry.attribute('color'), '#{0:02d}{0:02d}{0:02d}'.format(index))

    def testSingleBandPseudoColorRenderer_Exact(self):
        # get min and max of the band to renderer
        bandNo = 3
        stats = self.raster_layer.dataProvider().bandStatistics(bandNo, QgsRasterBandStats.Min | QgsRasterBandStats.Max)
        minValue = stats.minimumValue
        maxValue = stats.maximumValue
        # create shader for the renderer
        shader = QgsRasterShader(minValue, maxValue)
        colorRampShaderFcn = QgsColorRampShader(minValue, maxValue)
        colorRampShaderFcn.setColorRampType(QgsColorRampShader.Exact)
        colorRampShaderFcn.setClassificationMode(QgsColorRampShader.Continuous)
        colorRampShaderFcn.setClip(True)
        items = []
        for index in range(10):
            items.append(QgsColorRampShader.ColorRampItem(index, QColor('#{0:02d}{0:02d}{0:02d}'.format(index)), "{}".format(index)))
        colorRampShaderFcn.setColorRampItemList(items)
        shader.setRasterShaderFunction(colorRampShaderFcn)
        # create instance to test
        rasterRenderer = QgsSingleBandPseudoColorRenderer(self.raster_layer.dataProvider(), bandNo, shader)
        self.raster_layer.setRenderer(rasterRenderer)

        # do test
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '{}'.format(bandNo))
        # check ColorMapEntry classes
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertFalse(colorMap.isNull())
        self.assertEqual(colorMap.attribute('type'), 'values')
        self.assertFalse(colorMap.hasAttribute('extendend'))
        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(colorMapEntries.count(), 10)
        for index in range(colorMapEntries.count()):
            colorMapEntry = colorMapEntries.at(index).toElement()
            self.assertEqual(colorMapEntry.attribute('quantity'), '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('label'), '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('opacity'), '')
            self.assertEqual(colorMapEntry.attribute('color'), '#{0:02d}{0:02d}{0:02d}'.format(index))

        # add check that is set ColoMap extended="true" if colormap is bigger that 255 entries
        # !NOTE! can't reuse previous shader => segmentation fault
        shader = QgsRasterShader(minValue, maxValue)
        colorRampShaderFcn = QgsColorRampShader(minValue, maxValue)
        colorRampShaderFcn.setColorRampType(QgsColorRampShader.Exact)
        colorRampShaderFcn.setClassificationMode(QgsColorRampShader.Continuous)
        colorRampShaderFcn.setClip(True)
        items = []
        for index in range(255):
            items.append(QgsColorRampShader.ColorRampItem(index, QColor.fromHsv(index, 255, 255, 255), "{}".format(index)))
        colorRampShaderFcn.setColorRampItemList(items)
        shader.setRasterShaderFunction(colorRampShaderFcn)
        # create instance to test
        rasterRenderer = QgsSingleBandPseudoColorRenderer(self.raster_layer.dataProvider(), bandNo, shader)
        # self.raster_layer.setRenderer(rasterRenderer)
        # dom, root = self.rendererToSld(self.raster_layer.renderer())
        # self.assertTrue( colorMap.hasAttribute( 'extendend' ) )
        # self.assertEqual( colorMap.attribute( 'extendend' ), 'true' )

    def testPalettedRasterRenderer(self):
        # create 10 color classes
        #classesString = '122 0 0 0 255 122\n123 1 1 1 255 123\n124 2 2 2 255 124\n125 3 3 3 255 125\n126 4 4 4 255 126\n127 5 5 5 255 127\n128 6 6 6 255 128\n129 7 7 7 255 129\n130 8 8 8 255 130'
        classesString = ''
        for index in range(10):
            classesString += '{0} {0} {0} {0} 255 {0}\n'.format(index)
        classes = QgsPalettedRasterRenderer.classDataFromString(classesString)

        rasterRenderer = QgsPalettedRasterRenderer(
            self.raster_layer.dataProvider(), 3, classes)
        self.raster_layer.setRenderer(rasterRenderer)

        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '3')
        # check ColorMapEntry classes
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertFalse(colorMap.isNull())
        self.assertEqual(colorMap.attribute('type'), 'values')
        self.assertFalse(colorMap.hasAttribute('extendend'))
        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(colorMapEntries.count(), 10)
        for index in range(colorMapEntries.count()):
            colorMapEntry = colorMapEntries.at(index).toElement()
            self.assertEqual(colorMapEntry.attribute('quantity'), '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('label'), '{}'.format(index))
            self.assertEqual(colorMapEntry.attribute('opacity'), '')
            self.assertEqual(colorMapEntry.attribute('color'), '#{0:02d}{0:02d}{0:02d}'.format(index))

        # add check that is set ColoMap extended="true" if colormap is bigger that 255 entries
        classesString = ''
        values = range(255)
        for index in range(255):
            classesString += '{0} {1} {1} {1} 255 {0}\n'.format(index, random.choice(values))
        classes = QgsPalettedRasterRenderer.classDataFromString(classesString)
        rasterRenderer = QgsPalettedRasterRenderer(
            self.raster_layer.dataProvider(), 3, classes)
        self.raster_layer.setRenderer(rasterRenderer)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        colorMap = root.elementsByTagName('sld:ColorMap')
        colorMap = colorMap.item(0).toElement()
        self.assertTrue(colorMap.hasAttribute('extended'))
        self.assertEqual(colorMap.attribute('extended'), 'true')

    def testMultiBandColorRenderer(self):
        rasterRenderer = QgsMultiBandColorRenderer(
            self.raster_layer.dataProvider(), 3, 1, 2)
        self.raster_layer.setRenderer(rasterRenderer)
        self.raster_layer.setContrastEnhancement(algorithm=QgsContrastEnhancement.StretchToMinimumMaximum,
                                                 limits=QgsRasterMinMaxOrigin.MinMax)

        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:RedChannel', '3')
        self.assertChannelBand(root, 'sld:GreenChannel', '1')
        self.assertChannelBand(root, 'sld:BlueChannel', '2')

    def testSingleBandGrayRenderer(self):
        # check with StretchToMinimumMaximum
        rasterRenderer = QgsSingleBandGrayRenderer(self.raster_layer.dataProvider(), 3)
        self.raster_layer.setRenderer(rasterRenderer)
        self.raster_layer.setContrastEnhancement(algorithm=QgsContrastEnhancement.StretchToMinimumMaximum,
                                                 limits=QgsRasterMinMaxOrigin.MinMax)
        maximum = self.raster_layer.renderer().contrastEnhancement().maximumValue()
        minmum = self.raster_layer.renderer().contrastEnhancement().minimumValue()
        self.assertEqual(minmum, 51)
        self.assertEqual(maximum, 172)

        # check default values
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '3')

        elements = root.elementsByTagName('sld:ContrastEnhancement')
        self.assertEqual(len(elements), 1)
        enhancement = elements.at(0).toElement()
        self.assertFalse(enhancement.isNull())

        normalize = enhancement.firstChildElement('sld:Normalize')
        self.assertFalse(normalize.isNull())
        self.assertVendorOption(normalize, 'algorithm', 'StretchToMinimumMaximum')
        self.assertVendorOption(normalize, 'minValue', '51')
        self.assertVendorOption(normalize, 'maxValue', '172')

        elements = root.elementsByTagName('sld:ColorMap')
        self.assertEqual(len(elements), 1)
        colorMap = elements.at(0).toElement()
        self.assertFalse(colorMap.isNull())

        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(len(colorMapEntries), 2)
        clorMap1 = colorMapEntries.at(0)
        self.assertEqual(clorMap1.attributes().namedItem('color').nodeValue(), '#000000')
        self.assertEqual(clorMap1.attributes().namedItem('quantity').nodeValue(), '0')

        clorMap2 = colorMapEntries.at(1)
        self.assertEqual(clorMap2.attributes().namedItem('color').nodeValue(), '#ffffff')
        self.assertEqual(clorMap2.attributes().namedItem('quantity').nodeValue(), '255')

        # check when StretchAndClipToMinimumMaximum
        # then min/max have always to be the real one and not that set in the contrastEnhancement
        self.raster_layer.setContrastEnhancement(algorithm=QgsContrastEnhancement.StretchAndClipToMinimumMaximum,
                                                 limits=QgsRasterMinMaxOrigin.MinMax)
        minmum = self.raster_layer.renderer().contrastEnhancement().setMinimumValue(100)
        maximum = self.raster_layer.renderer().contrastEnhancement().maximumValue()
        minmum = self.raster_layer.renderer().contrastEnhancement().minimumValue()
        self.assertEqual(minmum, 100)
        self.assertEqual(maximum, 172)

        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '3')

        elements = root.elementsByTagName('sld:ContrastEnhancement')
        self.assertEqual(len(elements), 1)
        enhancement = elements.at(0).toElement()
        self.assertFalse(enhancement.isNull())

        normalize = enhancement.firstChildElement('sld:Normalize')
        self.assertFalse(normalize.isNull())
        self.assertVendorOption(normalize, 'minValue', '51')
        self.assertVendorOption(normalize, 'maxValue', '172')

        elements = root.elementsByTagName('sld:ColorMap')
        self.assertEqual(len(elements), 1)
        colorMap = elements.at(0).toElement()
        self.assertFalse(colorMap.isNull())

        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(len(colorMapEntries), 4)
        clorMap1 = colorMapEntries.at(0)
        self.assertEqual(clorMap1.attributes().namedItem('color').nodeValue(), '#000000')
        self.assertEqual(clorMap1.attributes().namedItem('quantity').nodeValue(), '100')
        self.assertEqual(clorMap1.attributes().namedItem('opacity').nodeValue(), '0')

        clorMap2 = colorMapEntries.at(1)
        self.assertEqual(clorMap2.attributes().namedItem('color').nodeValue(), '#000000')
        self.assertEqual(clorMap2.attributes().namedItem('quantity').nodeValue(), '100')

        clorMap3 = colorMapEntries.at(2)
        self.assertEqual(clorMap3.attributes().namedItem('color').nodeValue(), '#ffffff')
        self.assertEqual(clorMap3.attributes().namedItem('quantity').nodeValue(), '172')

        clorMap4 = colorMapEntries.at(3)
        self.assertEqual(clorMap4.attributes().namedItem('color').nodeValue(), '#ffffff')
        self.assertEqual(clorMap4.attributes().namedItem('quantity').nodeValue(), '172')
        self.assertEqual(clorMap4.attributes().namedItem('opacity').nodeValue(), '0')

        # check when ClipToMinimumMaximum
        # then min/max have always to be the real one and not that set in the contrastEnhancement
        self.raster_layer.setContrastEnhancement(algorithm=QgsContrastEnhancement.ClipToMinimumMaximum,
                                                 limits=QgsRasterMinMaxOrigin.MinMax)
        minmum = self.raster_layer.renderer().contrastEnhancement().setMinimumValue(100)
        maximum = self.raster_layer.renderer().contrastEnhancement().maximumValue()
        minmum = self.raster_layer.renderer().contrastEnhancement().minimumValue()
        self.assertEqual(minmum, 100)
        self.assertEqual(maximum, 172)

        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        self.assertChannelBand(root, 'sld:GrayChannel', '3')

        elements = root.elementsByTagName('sld:ContrastEnhancement')
        self.assertEqual(len(elements), 1)
        enhancement = elements.at(0).toElement()
        self.assertFalse(enhancement.isNull())

        normalize = enhancement.firstChildElement('sld:Normalize')
        self.assertFalse(normalize.isNull())
        self.assertVendorOption(normalize, 'minValue', '51')
        self.assertVendorOption(normalize, 'maxValue', '172')

        elements = root.elementsByTagName('sld:ColorMap')
        self.assertEqual(len(elements), 1)
        colorMap = elements.at(0).toElement()
        self.assertFalse(colorMap.isNull())

        colorMapEntries = colorMap.elementsByTagName('sld:ColorMapEntry')
        self.assertEqual(len(colorMapEntries), 4)
        clorMap1 = colorMapEntries.at(0)
        self.assertEqual(clorMap1.attributes().namedItem('color').nodeValue(), '#000000')
        self.assertEqual(clorMap1.attributes().namedItem('quantity').nodeValue(), '100')
        self.assertEqual(clorMap1.attributes().namedItem('opacity').nodeValue(), '0')

        clorMap2 = colorMapEntries.at(1)
        self.assertEqual(clorMap2.attributes().namedItem('color').nodeValue(), '#000000')
        self.assertEqual(clorMap2.attributes().namedItem('quantity').nodeValue(), '100')

        clorMap3 = colorMapEntries.at(2)
        self.assertEqual(clorMap3.attributes().namedItem('color').nodeValue(), '#ffffff')
        self.assertEqual(clorMap3.attributes().namedItem('quantity').nodeValue(), '172')

        clorMap4 = colorMapEntries.at(3)
        self.assertEqual(clorMap4.attributes().namedItem('color').nodeValue(), '#ffffff')
        self.assertEqual(clorMap4.attributes().namedItem('quantity').nodeValue(), '172')
        self.assertEqual(clorMap4.attributes().namedItem('opacity').nodeValue(), '0')

    def testRasterRenderer(self):
        class fakerenderer(QgsRasterRenderer):

            def __init__(self, interface):
                QgsRasterRenderer.__init__(self, interface, '')

        rasterRenderer = fakerenderer(self.raster_layer.dataProvider())
        self.raster_layer.setRenderer(rasterRenderer)

        # check opacity default value is not exported
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertNoOpacity(root)
        # check if opacity is not the default value
        rasterRenderer.setOpacity(1.1)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertOpacity(root, '1.1')

        # check gamma properties from [-100:0] stretched to [0:1]
        #  and (0:100] stretche dto (1:100]
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '-100'})
        # self.assertGamma(root, '0')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '-50'})
        # self.assertGamma(root, '0.5')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '0'})
        # self.assertGamma(root, '1')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '1'})
        # self.assertGamma(root, '1')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '100'})
        # self.assertGamma(root, '100')
        # # input contrast are always integer, btw the value is managed also if it's double
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '1.1'})
        # self.assertGamma(root, '1.1')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '1.6'})
        # self.assertGamma(root, '1.6')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '-50.5'})
        # self.assertGamma(root, '0.495')
        # dom, root = self.rendererToSld(rasterRenderer, {'contrast': '-0.1'})
        # self.assertGamma(root, '0.999')

    def testStretchingAlgorithm(self):
        rasterRenderer = QgsMultiBandColorRenderer(
            self.raster_layer.dataProvider(), 3, 1, 2)
        self.raster_layer.setRenderer(rasterRenderer)

        # check StretchToMinimumMaximum stretching alg
        self.raster_layer.setContrastEnhancement(algorithm=QgsContrastEnhancement.StretchToMinimumMaximum,
                                                 limits=QgsRasterMinMaxOrigin.MinMax)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertContrastEnhancement(root, 'sld:RedChannel', 'StretchToMinimumMaximum', '51', '172')
        self.assertContrastEnhancement(root, 'sld:GreenChannel', 'StretchToMinimumMaximum', '122', '130')
        self.assertContrastEnhancement(root, 'sld:BlueChannel', 'StretchToMinimumMaximum', '133', '148')

        # check StretchAndClipToMinimumMaximum stretching alg
        self.raster_layer.setContrastEnhancement(algorithm=QgsContrastEnhancement.StretchAndClipToMinimumMaximum,
                                                 limits=QgsRasterMinMaxOrigin.MinMax)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertContrastEnhancement(root, 'sld:RedChannel', 'ClipToZero', '51', '172')
        self.assertContrastEnhancement(root, 'sld:GreenChannel', 'ClipToZero', '122', '130')
        self.assertContrastEnhancement(root, 'sld:BlueChannel', 'ClipToZero', '133', '148')

        # check ClipToMinimumMaximum stretching alg
        self.raster_layer.setContrastEnhancement(algorithm=QgsContrastEnhancement.ClipToMinimumMaximum,
                                                 limits=QgsRasterMinMaxOrigin.MinMax)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertContrastEnhancement(root, 'sld:RedChannel', 'ClipToMinimumMaximum', '51', '172')
        self.assertContrastEnhancement(root, 'sld:GreenChannel', 'ClipToMinimumMaximum', '122', '130')
        self.assertContrastEnhancement(root, 'sld:BlueChannel', 'ClipToMinimumMaximum', '133', '148')

        # check NoEnhancement stretching alg
        self.raster_layer.setContrastEnhancement(algorithm=QgsContrastEnhancement.NoEnhancement)
        dom, root = self.rendererToSld(self.raster_layer.renderer())
        self.assertContrastEnhancement(root, 'sld:RedChannel')
        self.assertContrastEnhancement(root, 'sld:GreenChannel')
        self.assertContrastEnhancement(root, 'sld:BlueChannel')

    def assertVendorOption(self, root, name, expectedValue):
        """Set expectedValue=None to check that the vendor option is not present."""
        vendorOptions = root.elementsByTagName('sld:VendorOption')
        found = False
        for vendorOptionIndex in range(vendorOptions.count()):
            vendorOption = vendorOptions.at(vendorOptionIndex)
            self.assertEqual('sld:VendorOption', vendorOption.nodeName())
            if (vendorOption.attributes().namedItem('name').nodeValue() == name):
                found = True
                self.assertEqual(vendorOption.firstChild().nodeValue(), expectedValue)
        if (expectedValue is None) and found:
            self.fail("found VendorOption: {} where supposed not present".format(name))
        if expectedValue and not found:
            self.fail("Not found VendorOption: {}".format(name))

    def assertGamma(self, root, expectedValue, index=0):
        enhancement = root.elementsByTagName('sld:ContrastEnhancement').item(index)
        gamma = enhancement.firstChildElement('sld:GammaValue')
        self.assertEqual(expectedValue, gamma.firstChild().nodeValue())

    def assertOpacity(self, root, expectedValue, index=0):
        opacity = root.elementsByTagName('sld:Opacity').item(index)
        self.assertEqual(expectedValue, opacity.firstChild().nodeValue())

    def assertNoOpacity(self, root):
        opacities = root.elementsByTagName('sld:Opacity')
        self.assertEqual(opacities.size(), 0)

    def assertContrastEnhancement(self, root, bandTag, expectedAlg=None, expectedMin=None, expectedMax=None, index=0):
        channelSelection = root.elementsByTagName('sld:ChannelSelection').item(index)
        self.assertIsNotNone(channelSelection)
        band = channelSelection.firstChildElement(bandTag)
        # check if no enhancement alg is iset
        if (not expectedAlg):
            contrastEnhancementName = band.firstChildElement('sld:ContrastEnhancement')
            self.assertEqual('', contrastEnhancementName.firstChild().nodeName())
            return
        # check if enhancement alg is set
        contrastEnhancementName = band.firstChildElement('sld:ContrastEnhancement')
        self.assertEqual('sld:Normalize', contrastEnhancementName.firstChild().nodeName())
        normalize = contrastEnhancementName.firstChildElement('sld:Normalize')
        vendorOptions = normalize.elementsByTagName('VendorOption')
        for vendorOptionIndex in range(vendorOptions.count()):
            vendorOption = vendorOptions.at(vendorOptionIndex)
            self.assertEqual('VendorOption', vendorOption.nodeName())
            if (vendorOption.attributes().namedItem('name').nodeValue() == 'algorithm'):
                self.assertEqual(expectedAlg, vendorOption.firstChild().nodeValue())
            elif (vendorOption.attributes().namedItem('name').nodeValue() == 'minValue'):
                self.assertEqual(expectedMin, vendorOption.firstChild().nodeValue())
            elif (vendorOption.attributes().namedItem('name').nodeValue() == 'maxValue'):
                self.assertEqual(expectedMax, vendorOption.firstChild().nodeValue())
            else:
                self.fail('Unrecognised vendorOption name {}'.format(vendorOption.attributes().namedItem('name').nodeValue()))

    def assertChannelBand(self, root, bandTag, expectedValue, index=0):
        channelSelection = root.elementsByTagName('sld:ChannelSelection').item(index)
        self.assertIsNotNone(channelSelection)
        band = channelSelection.firstChildElement(bandTag)
        sourceChannelName = band.firstChildElement('sld:SourceChannelName')
        self.assertEqual(expectedValue, sourceChannelName.firstChild().nodeValue())

    def rendererToSld(self, renderer, properties={}):
        dom = QDomDocument()
        root = dom.createElement("FakeRoot")
        dom.appendChild(root)
        renderer.toSld(dom, root, properties)
        return dom, root
    def testTransparency(self):
        myPath = os.path.join(unitTestDataPath('raster'),
                              'band1_float32_noct_epsg4326.tif')
        myFileInfo = QFileInfo(myPath)
        myBaseName = myFileInfo.baseName()
        myRasterLayer = QgsRasterLayer(myPath, myBaseName)
        myMessage = 'Raster not loaded: %s' % myPath
        assert myRasterLayer.isValid(), myMessage

        renderer = QgsSingleBandGrayRenderer(myRasterLayer.dataProvider(), 1)
        myRasterLayer.setRenderer(renderer)
        myRasterLayer.setContrastEnhancementAlgorithm(
            QgsContrastEnhancement.StretchToMinimumMaximum,
            QgsRasterLayer.ContrastEnhancementMinMax)

        myContrastEnhancement = myRasterLayer.renderer().contrastEnhancement()
        #print ("myContrastEnhancement.minimumValue = %.17g" %
        #       myContrastEnhancement.minimumValue())
        #print ("myContrastEnhancement.maximumValue = %.17g" %
        #        myContrastEnhancement.maximumValue())

        # Unfortunately the minimum/maximum values calculated in C++ and Python
        # are slightly different (e.g. 3.3999999521443642e+38 x
        # 3.3999999521444001e+38)
        # It is not clear where the precision is lost.
        # We set the same values as C++.
        myContrastEnhancement.setMinimumValue(-3.3319999287625854e+38)
        myContrastEnhancement.setMaximumValue(3.3999999521443642e+38)
        #myType = myRasterLayer.dataProvider().dataType(1);
        #myEnhancement = QgsContrastEnhancement(myType);



        myTransparentSingleValuePixelList = []
        rasterTransparency = QgsRasterTransparency()

        myTransparentPixel1 = \
            QgsRasterTransparency.TransparentSingleValuePixel()
        myTransparentPixel1.min = -2.5840000772112106e+38
        myTransparentPixel1.max = -1.0879999684602689e+38
        myTransparentPixel1.percentTransparent = 50
        myTransparentSingleValuePixelList.append(myTransparentPixel1)

        myTransparentPixel2 = \
            QgsRasterTransparency.TransparentSingleValuePixel()
        myTransparentPixel2.min = 1.359999960575336e+37
        myTransparentPixel2.max = 9.520000231087593e+37
        myTransparentPixel2.percentTransparent = 70
        myTransparentSingleValuePixelList.append(myTransparentPixel2)

        rasterTransparency.setTransparentSingleValuePixelList(
            myTransparentSingleValuePixelList)

        rasterRenderer = myRasterLayer.renderer()
        assert rasterRenderer

        rasterRenderer.setRasterTransparency(rasterTransparency)

        QgsMapLayerRegistry.instance().addMapLayers([ myRasterLayer, ])

        myMapRenderer = QgsMapRenderer()

        myLayers = QStringList()
        myLayers.append(myRasterLayer.id())
        myMapRenderer.setLayerSet(myLayers)
        myMapRenderer.setExtent(myRasterLayer.extent())

        myChecker = QgsRenderChecker()
        myChecker.setControlName("expected_raster_transparency")
        myChecker.setMapRenderer(myMapRenderer)

        myResultFlag = myChecker.runTest("raster_transparency_python");
        assert myResultFlag, "Raster transparency rendering test failed"
Example #17
0
def create_raster_layer(matrix):
    driver = gdal.GetDriverByName("GTiff")

    filename = tempfile.mktemp(prefix="hmtk", suffix=".tif")

    # sort the data by lon, lat
    gridded_data = numpy.array(
        sorted(matrix, key=lambda row: (90 + row[1]) * 180 + (180 + row[0])))

    # extract it into separate vars
    lons, lats, vals = (
        gridded_data[:, 0], gridded_data[:, 1], gridded_data[:, 3])

    ncols = lons[lons == lons[0]].size
    nrows = lats[lats == lats[0]].size

    # put values in a grid
    gridded_vals = vals.reshape((ncols, nrows)).T

    dataset = driver.Create(filename, ncols, nrows, 1, gdal.GDT_Float32)

    dataset.SetGeoTransform((
        min(lons),
        (max(lons) - min(lons)) / ncols,
        0,
        max(lats),
        0,
        -(max(lats) - min(lats)) / nrows))

    out_srs = osr.SpatialReference()
    out_srs.ImportFromEPSG(4326)
    dataset.SetProjection(out_srs.ExportToWkt())

    out_band = dataset.GetRasterBand(1)
    out_band.WriteArray(gridded_vals)
    out_band.SetNoDataValue(0)
    out_band.FlushCache()
    out_band = None
    dataset = None

    fileInfo = QFileInfo(filename)
    baseName = fileInfo.baseName()
    layer = QgsRasterLayer(filename, baseName)

    stat = layer.dataProvider().bandStatistics(1)

    minVal = stat.minimumValue
    maxVal = stat.maximumValue
    entries_nr = 20

    colorRamp = QgsStyleV2().defaultStyle().colorRamp("Spectral")
    currentValue = float(minVal)
    intervalDiff = float(maxVal - minVal) / float(entries_nr - 1)

    colorRampItems = []
    for i in reversed(xrange(entries_nr)):
        item = QgsColorRampShader.ColorRampItem()
        item.value = currentValue
        item.label = unicode(currentValue)
        currentValue += intervalDiff
        item.color = colorRamp.color(float(i) / float(entries_nr))
        item.color.setAlphaF(0.75)
        colorRampItems.append(item)

    rasterShader = QgsRasterShader()
    colorRampShader = QgsColorRampShader()

    colorRampShader.setColorRampItemList(colorRampItems)
    colorRampShader.setColorRampType(QgsColorRampShader.INTERPOLATED)
    rasterShader.setRasterShaderFunction(colorRampShader)

    layer.setDrawingStyle('SingleBandPseudoColor')
    layer.renderer().setShader(rasterShader)

    QgsMapLayerRegistry.instance().addMapLayer(layer)

    return layer
Example #18
0
    def testTransparency(self):
        myPath = os.path.join(unitTestDataPath('raster'),
                              'band1_float32_noct_epsg4326.tif')
        myFileInfo = QFileInfo(myPath)
        myBaseName = myFileInfo.baseName()
        myRasterLayer = QgsRasterLayer(myPath, myBaseName)
        myMessage = 'Raster not loaded: %s' % myPath
        assert myRasterLayer.isValid(), myMessage

        renderer = QgsSingleBandGrayRenderer(myRasterLayer.dataProvider(), 1)
        myRasterLayer.setRenderer(renderer)
        myRasterLayer.setContrastEnhancement(
            QgsContrastEnhancement.StretchToMinimumMaximum,
            QgsRasterMinMaxOrigin.MinMax)

        myContrastEnhancement = myRasterLayer.renderer().contrastEnhancement()
        # print ("myContrastEnhancement.minimumValue = %.17g" %
        #       myContrastEnhancement.minimumValue())
        # print ("myContrastEnhancement.maximumValue = %.17g" %
        #        myContrastEnhancement.maximumValue())

        # Unfortunately the minimum/maximum values calculated in C++ and Python
        # are slightly different (e.g. 3.3999999521443642e+38 x
        # 3.3999999521444001e+38)
        # It is not clear where the precision is lost.
        # We set the same values as C++.
        myContrastEnhancement.setMinimumValue(-3.3319999287625854e+38)
        myContrastEnhancement.setMaximumValue(3.3999999521443642e+38)
        #myType = myRasterLayer.dataProvider().dataType(1);
        #myEnhancement = QgsContrastEnhancement(myType);

        myTransparentSingleValuePixelList = []
        rasterTransparency = QgsRasterTransparency()

        myTransparentPixel1 = \
            QgsRasterTransparency.TransparentSingleValuePixel()
        myTransparentPixel1.min = -2.5840000772112106e+38
        myTransparentPixel1.max = -1.0879999684602689e+38
        myTransparentPixel1.percentTransparent = 50
        myTransparentSingleValuePixelList.append(myTransparentPixel1)

        myTransparentPixel2 = \
            QgsRasterTransparency.TransparentSingleValuePixel()
        myTransparentPixel2.min = 1.359999960575336e+37
        myTransparentPixel2.max = 9.520000231087593e+37
        myTransparentPixel2.percentTransparent = 70
        myTransparentSingleValuePixelList.append(myTransparentPixel2)

        rasterTransparency.setTransparentSingleValuePixelList(
            myTransparentSingleValuePixelList)

        rasterRenderer = myRasterLayer.renderer()
        assert rasterRenderer

        rasterRenderer.setRasterTransparency(rasterTransparency)

        QgsProject.instance().addMapLayers([myRasterLayer, ])

        myMapSettings = QgsMapSettings()
        myMapSettings.setLayers([myRasterLayer])
        myMapSettings.setExtent(myRasterLayer.extent())

        myChecker = QgsRenderChecker()
        myChecker.setControlName("expected_raster_transparency")
        myChecker.setMapSettings(myMapSettings)

        myResultFlag = myChecker.runTest("raster_transparency_python")
        assert myResultFlag, "Raster transparency rendering test failed"
Example #19
0
    def add_layer_to_map(item: QStandardItem):
        """
        Add a layer to the map
        :param layer:
        :return:
        """

        # No multiselect so there is only ever one item
        pt_data: ProjectTreeData = item.data(Qt.UserRole)
        project = pt_data.project
        map_layer: QRaveMapLayer = pt_data.data

        settings = Settings()

        # Loop over all the parent group layers for this raster
        # ensuring they are in the tree in correct, nested order
        ancestry = []
        if map_layer.exists is True:
            parent = item.parent()
            while parent is not None and len(ancestry) < 50:
                ancestry.append((parent.text(), parent.row()))
                parent = parent.parent()
        else:
            # Layer does not exist. do not try to put it on the map
            return

        ancestry.reverse()
        parentGroup = None
        for agroup in ancestry:
            parentGroup = QRaveMapLayer._addgrouptomap(agroup[0], agroup[1],
                                                       parentGroup)

        assert parentGroup, "All rasters should be nested and so parentGroup should be instantiated by now"

        # Loop over all the parent group layers for this raster
        # ensuring they are in the tree in correct, nested order

        # Only add the layer if it's not already in the registry
        exists = False
        existing_layers = QgsProject.instance().mapLayersByName(
            map_layer.label)
        layers_ancestry = [
            QRaveMapLayer.get_layer_ancestry(lyr) for lyr in existing_layers
        ]

        # Now we compare the ancestry group labels to the business logic ancestry branch names
        # to see if this layer is already in the map
        for lyr in layers_ancestry:
            if len(lyr) == len(ancestry) \
                    and all(iter([ancestry[x][0] == lyr[x] for x in range(len(ancestry))])):
                exists = True
                break

        if not exists:
            layer_uri = map_layer.layer_uri
            rOutput = None
            # This might be a basemap
            if map_layer.layer_type == QRaveMapLayer.LayerTypes.WEBTILE:
                rOutput = QgsRasterLayer(layer_uri, map_layer.label, 'wms')

            elif map_layer.layer_type in [
                    QRaveMapLayer.LayerTypes.LINE,
                    QRaveMapLayer.LayerTypes.POLYGON,
                    QRaveMapLayer.LayerTypes.POINT
            ]:
                if map_layer.layer_name is not None:
                    layer_uri += "|layername={}".format(map_layer.layer_name)
                rOutput = QgsVectorLayer(layer_uri, map_layer.label, "ogr")

            elif map_layer.layer_type == QRaveMapLayer.LayerTypes.RASTER:
                # Raster
                rOutput = QgsRasterLayer(layer_uri, map_layer.label)

            if rOutput is not None:
                ##########################################
                # Symbology
                ##########################################

                symbology = map_layer.bl_attr[
                    'symbology'] if map_layer.bl_attr is not None and 'symbology' in map_layer.bl_attr else None
                # If the business logic has symbology defined
                if symbology is not None:
                    qml_fname = '{}.qml'.format(symbology)
                    os.path.abspath(
                        os.path.join(project.project_dir, qml_fname))

                    # Here are the search paths for QML files in order of precedence
                    hierarchy = [
                        os.path.abspath(
                            os.path.join(project.project_dir, qml_fname)),
                        # This is the default one
                        os.path.abspath(
                            os.path.join(SYMBOLOGY_DIR, project.project_type,
                                         qml_fname)),
                        os.path.abspath(
                            os.path.join(SYMBOLOGY_DIR, 'Shared', qml_fname))
                    ]
                    # Find the first match
                    try:
                        chosen_qml = next(
                            iter([
                                candidate for candidate in hierarchy
                                if os.path.isfile(candidate)
                            ]))
                        # Report to the terminal if we couldn't find a qml file to use
                        if chosen_qml is None:
                            settings.msg_bar(
                                "Missing Symbology",
                                "Could not find a valid .qml symbology file for layer {}. Search paths: [{}]"
                                .format(layer_uri, ', '.join(hierarchy)),
                                level=Qgis.Warning)
                        # Apply the QML file
                        else:
                            rOutput.loadNamedStyle(chosen_qml)

                    except StopIteration:
                        settings.log(
                            'Could not find valid symbology for layer at any of the following search paths: [ {} ]'
                            .format(', '.join(hierarchy)), Qgis.Warning)

                ############################################################
                # Transparency. A few notes:
                # - QML transparency will prevail for rasters before 3.18
                # - We set this here so that QML layer transparency will be
                #   overruled
                ############################################################
                transparency = 0

                try:
                    if 'transparency' in map_layer.bl_attr:
                        transparency = int(map_layer.bl_attr['transparency'])
                except Exception as e:
                    settings.log(
                        'Error deriving transparency from layer: {}'.format(e))

                try:
                    if transparency > 0:
                        if rOutput.__class__ is QgsVectorLayer:
                            rOutput.setLayerTransparency(transparency)
                            # rOutput.triggerRepaint()
                        elif rOutput.__class__ is QgsRasterLayer:
                            renderer = rOutput.renderer()
                            renderer.setOpacity((100 - transparency) / 100.0)
                            # rOutput.triggerRepaint()
                except Exception as e:
                    settings.log(
                        'Error deriving transparency from layer: {}'.format(e))

                QgsProject.instance().addMapLayer(rOutput, False)
                parentGroup.insertLayer(item.row(), rOutput)

        # if the layer already exists trigger a refresh
        else:
            QgsProject.instance().mapLayersByName(
                map_layer.label)[0].triggerRepaint()