def test_insert_column_color(self):

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

        model = RATModel(rat)
        tester = QAbstractItemModelTester(
            model, QAbstractItemModelTester.FailureReportingMode.Warning)
        column_count = model.columnCount(QModelIndex())
        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_String)
        self.assertTrue(model.insert_column(3, field)[0])
        self.assertEqual(model.columnCount(QModelIndex()), column_count + 1)
        self.assertEqual(model.headers.index('f1'), 3)

        # Error
        field = RATField('f1', gdal.GFU_Generic, gdal.GFT_String)
        self.assertFalse(model.insert_column(3, field)[0])
        self.assertEqual(model.columnCount(QModelIndex()), column_count + 1)
    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_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 addColumn(self):

        dlg = AddColumnDialog(self.model, self.iface)

        if self.model.has_color:
            dlg.mColumnType.hide()

        # List columns where insertion is allowed: skip value and count
        for field in self.model.rat.fields.values():
            if field.usage not in {gdal.GFU_MinMax, gdal.GFU_PixelCount}:
                dlg.mColumn.addItem(field.name)

        # List allowed usages
        allowed_usages = self.allowedAddedUsages()

        # Set insertion point
        col = self.proxyModel.mapToSource(
            self.mRATView.selectionModel().currentIndex()).column()
        header = self.model.headers[col]
        dlg.mColumn.setCurrentIndex(dlg.mColumn.findText(header))

        if not allowed_usages:
            if not self.model.has_color:
                dlg.mColor.setChecked(True)
                dlg.mStandardColumn.setEnabled(False)
            else:  # We cannot add any field, we should never get here!
                rat_log(
                    'Cannot add any column: this should have been checked before getting so far!',
                    Qgis.Critical)

        else:
            usages_info = rat_column_info()
            for usage in allowed_usages:
                dlg.mUsage.addItem(usages_info[usage]['name'], usage)

        if dlg.exec_() == QDialog.Accepted:
            position = dlg.mColumn.currentText()
            after = dlg.mAfter.isChecked()
            insertion_point = self.model.headers.index(position) + (1 if after
                                                                    else 0)

            if dlg.mColor.isChecked():
                if not self.model.insert_color(insertion_point):
                    QMessageBox.warning(
                        None,
                        QCoreApplication.translate('RAT',
                                                   "Error Adding Colors"),
                        QCoreApplication.translate(
                            'RAT',
                            "An error occourred while adding colors to the RAT!"
                        ))
                else:
                    # Try to update colors from current raster
                    self.model.rat.update_colors_from_raster(self.raster_layer)
                    self.is_dirty = True

            else:
                data_type = dlg.mDataType.currentData()
                usage = dlg.mUsage.currentData()
                name = dlg.mName.text()
                field = RATField(name, usage, data_type)
                result, error_message = self.model.insert_column(
                    insertion_point, field)
                if not result:
                    QMessageBox.warning(
                        None,
                        QCoreApplication.translate('RAT',
                                                   "Error Adding Column"),
                        QCoreApplication.translate(
                            'RAT',
                            "An error occourred while adding a column to the RAT: %s"
                            % error_message))
                else:
                    self.is_dirty = True
예제 #5
0
def create_rat_from_raster(raster_layer, is_dbf, path, feedback=QgsRasterBlockFeedback()) -> RAT:
    """Creates a new RAT object from a raster layer, an invalid RAT is returned in case of errors.

    :param raster_layer: raster layer
    :type raster_layer: QgsRasterLayer
    :param is_dbf: raster layer
    :type is_dbf: bool
    :return: new RAT
    :rtype: RAT
    """

    if not can_create_rat(raster_layer):
        return RAT()

    renderer = raster_layer.renderer()
    band = renderer.band()

    is_range = False
    has_histogram = True

    if isinstance(renderer, QgsPalettedRasterRenderer):
        classes = renderer.classes()
    elif isinstance(renderer, QgsSingleBandPseudoColorRenderer):
        shader = renderer.shader()
        if not shader:
            rat_log('Invalid shader for renderer: %s' % renderer)
            return RAT()
        func = shader.rasterShaderFunction()
        classes = func.colorRampItemList()
        is_range = True
        has_histogram = False
    else:
        rat_log('Unsupported renderer: %s' % renderer)
        return RAT()

    if len(classes) == 0:
        return RAT()

    is_real = isinstance(classes[0].value, float)

    # If we have a range we have no histogram and separate min/max
    if is_range:
        fields = {
            'Value Min': RATField('Value Min', gdal.GFU_Min, gdal.GFT_Real if is_real else gdal.GFT_Integer),
            'Value Max': RATField('Value Max', gdal.GFU_Max, gdal.GFT_Real if is_real else gdal.GFT_Integer),
        }
        data = {
            RAT_COLOR_HEADER_NAME: [],
            'Value Min': [],
            'Value Max': [],
        }
        # Store min and max
        stats = raster_layer.dataProvider().bandStatistics(
            band, QgsRasterBandStats.Min | QgsRasterBandStats.Max, raster_layer.extent(), 0)

        # Set as min float value
        min_value = -3.40282e+38
        # unused: max_value = stats.maximumValue

    else:
        fields = {
            'Value': RATField('Value', gdal.GFU_MinMax, gdal.GFT_Real if is_real else gdal.GFT_Integer),
        }
        data = {
            RAT_COLOR_HEADER_NAME: [],
            'Value': []
        }

        histogram = raster_layer.dataProvider().histogram(band, feedback=feedback)
        histogram_values = []
        if histogram.valid:
            fields['Count'] = RATField(
                'Count', gdal.GFU_PixelCount, gdal.GFT_Integer)
            data['Count'] = []
            vector_is_complete = (len(histogram.histogramVector) == len(classes))
            for val in histogram.histogramVector:
                if val != 0 or vector_is_complete:
                    histogram_values.append(val)

    fields['Class'] = RATField('Class', gdal.GFU_Name, gdal.GFT_String)
    data['Class'] = []

    fields['R'] = RATField('R', gdal.GFU_Red, gdal.GFT_Integer)
    fields['G'] = RATField('G', gdal.GFU_Green, gdal.GFT_Integer)
    fields['B'] = RATField('B', gdal.GFU_Blue, gdal.GFT_Integer)
    fields['A'] = RATField('A', gdal.GFU_Alpha, gdal.GFT_Integer)
    data['R'] = []
    data['G'] = []
    data['B'] = []
    data['A'] = []

    i = 0
    for klass in classes:

        data[RAT_COLOR_HEADER_NAME].append(klass.color)

        value = klass.value if klass.value != float('inf') else 3.40282e+38

        if is_range:
            data['Value Min'].append(min_value)
            data['Value Max'].append(value)
            min_value = value
        else:
            data['Value'].append(value)

        if has_histogram:
            data['Count'].append(histogram_values[i])

        data['Class'].append(klass.label)
        data['R'].append(klass.color.red())
        data['G'].append(klass.color.green())
        data['B'].append(klass.color.blue())
        data['A'].append(klass.color.alpha())
        i += 1

    return RAT(data, is_dbf, fields, path)
예제 #6
0
def get_rat(raster_layer, band, colors=('R', 'G', 'B', 'A')):
    """Extracts RAT from raster layer and given band

    :param raster_layer: the raster layer to classify
    :type raster_layer: QgsRasterLayer
    :param band: band number (1-based)
    :type band: int
    :param colors: default name of the RGB(A) columns for sidecar DBF files, defaults to ('R', 'G', 'B', 'A'), these are searched first
    :type red_column_name: tuple, optional
    :return: RAT
    :rtype: RAT
    """

    headers = []
    values = {}
    fields = {}
    # For sidecar files
    path = None

    COLOR_ROLES = (gdal.GFU_Red, gdal.GFU_Green, gdal.GFU_Blue, gdal.GFU_Alpha)

    is_dbf = False

    ds = gdal.OpenEx(raster_layer.source())
    if ds:
        band = ds.GetRasterBand(band)
        if band:
            rat = band.GetDefaultRAT()
            if rat is not None:
                for i in range(0, rat.GetColumnCount()):
                    column = rat.GetNameOfCol(i)
                    headers.append(column)
                    values[column] = []
                    fields[column] = RATField(
                        column, rat.GetUsageOfCol(i), rat.GetTypeOfCol(i))

                for r in range(0, rat.GetRowCount()):
                    for c in range(0, rat.GetColumnCount()):
                        column = headers[c]
                        if fields[column].type == gdal.GFT_Integer:
                            values[headers[c]].append(rat.GetValueAsInt(r, c))
                        elif fields[column].type == gdal.GFT_Real:
                            values[headers[c]].append(
                                rat.GetValueAsDouble(r, c))
                        else:
                            values[headers[c]].append(
                                html.unescape(rat.GetValueAsString(r, c)))

                # Try to identify fields in case of RAT with wrong usages
                usages = [f.usage for f in fields.values()]
                if gdal.GFU_MinMax not in usages and not {gdal.GFU_Min, gdal.GFU_Max}.issubset(usages):
                    try:
                        field_name = [f.name for f in fields.values() if f.name.upper() == 'VALUE'][0]
                        fields[field_name].usage = gdal.GFU_MinMax
                    except IndexError:
                        pass

                    try:
                        field_name = [f.name for f in fields.values() if f.name.upper() in ('VALUE MIN', 'MIN', 'MIN VALUE', 'VALUE_MIN', 'MIN_VALUE')][0]
                        fields[field_name].usage = gdal.GFU_Min
                    except IndexError:
                        pass

                    try:
                        field_name = [f.name for f in fields.values() if f.name.upper() in ('VALUE MAX', 'MAX', 'MAX VALUE', 'VALUE_MAX', 'MAX_VALUE')][0]
                        fields[field_name].usage = gdal.GFU_Max
                    except IndexError:
                        pass

                if gdal.GFU_PixelCount not in usages:
                    try:
                        field_name = [f.name for f in fields.values() if f.name.upper() == 'COUNT'][0]
                        fields[field_name].usage = gdal.GFU_PixelCount
                    except IndexError:
                        pass

                path = raster_layer.source() + '.aux.xml'

    # Search for sidecar DBF files, `band` is ignored!
    if not values:

        info = QFileInfo(raster_layer.publicSource())
        directory = info.dir().path()
        basename = info.baseName()
        filename = info.fileName()
        candidates = (basename + '.dbf', basename + '.vat.dbf',
                      filename + '.dbf', filename + '.vat.dbf')

        for candidate in candidates:

            if os.path.exists(os.path.join(directory, candidate)):
                rat_layer = QgsVectorLayer(os.path.join(
                    directory, candidate), 'rat', 'ogr')

                if rat_layer.isValid():
                    path = os.path.join(directory, candidate)

                    # Get fields
                    # Check if color fields are there, fall-back to RED GREEN BLUE ALPHA if not
                    field_upper_names = [f.name().upper() for f in rat_layer.fields()]
                    upper_colors = [c.upper() for c in colors]

                    def _search_color():
                        color_found = True
                        for color_field_name in upper_colors[:3]:
                            if color_field_name not in field_upper_names:
                                color_found = False
                        return color_found

                    if not _search_color() and colors == ('R', 'G', 'B', 'A'):
                        upper_colors = ('RED', 'GREEN', 'BLUE', 'ALPHA')

                    # Create fields
                    for f in rat_layer.fields():

                        headers.append(f.name())
                        field_name_upper = f.name().upper()
                        if field_name_upper in upper_colors:
                            fields[f.name()] = RATField(
                                f.name(), COLOR_ROLES[upper_colors.index(field_name_upper)], gdal.GFT_Integer if f.type() in (QVariant.Int, QVariant.LongLong) else gdal.GFT_Real)
                        elif field_name_upper == 'COUNT':
                            fields[f.name()] = RATField(
                                f.name(), gdal.GFU_PixelCount, gdal.GFT_Integer)
                        elif field_name_upper == 'VALUE':
                            fields[f.name()] = RATField(
                                f.name(), gdal.GFU_MinMax, gdal.GFT_Integer if f.type() in (QVariant.Int, QVariant.LongLong) else gdal.GFT_Real)
                        elif field_name_upper in ('VALUE MIN', 'VALUE_MIN', 'MIN VALUE', 'MIN_VALUE'):
                            fields[f.name()] = RATField(
                                f.name(), gdal.GFU_Min, gdal.GFT_Integer if f.type() in (QVariant.Int, QVariant.LongLong) else gdal.GFT_Real)
                        elif field_name_upper in ('VALUE MAX', 'VALUE_MAX', 'MAX VALUE', 'MAX_VALUE'):
                            fields[f.name()] = RATField(
                                f.name(), gdal.GFU_Max, gdal.GFT_Integer if f.type() in (QVariant.Int, QVariant.LongLong) else gdal.GFT_Real)
                        else:
                            if f.type() in (QVariant.Int, QVariant.LongLong):
                                type = gdal.GFT_Integer
                            elif f.type() == QVariant.Double:
                                type = gdal.GFT_Real
                            else:
                                type = gdal.GFT_String

                            fields[f.name()] = RATField(
                                f.name(), gdal.GFU_Generic, type)

                    for header in headers:
                        values[header] = []
                    for f in rat_layer.getFeatures():
                        for header in headers:
                            values[header].append(f.attribute(header))
                    is_dbf = True
                    break

    # Colors
    if headers:
        red = None
        green = None
        blue = None
        alpha = None
        is_integer = False

        for name, f in fields.items():
            if f.usage == gdal.GFU_Red:
                red = name
                is_integer = f.type == gdal.GFT_Integer
                continue
            if f.usage == gdal.GFU_Green:
                green = name
                continue
            if f.usage == gdal.GFU_Blue:
                blue = name
                continue
            if f.usage == gdal.GFU_Alpha:
                alpha = name
                continue

        if red and green and blue:
            headers.append(RAT_COLOR_HEADER_NAME)
            values[RAT_COLOR_HEADER_NAME] = []
            for i in range(len(values[red])):
                func = 'fromRgb' if is_integer else 'fromRgbF'
                if alpha:
                    values[RAT_COLOR_HEADER_NAME].append(getattr(QColor, func)(
                        values[red][i], values[green][i], values[blue][i], values[alpha][i]))
                else:
                    values[RAT_COLOR_HEADER_NAME].append(getattr(QColor, func)(
                        values[red][i], values[green][i], values[blue][i]))

    return RAT(values, is_dbf, fields, path)
예제 #7
0
def get_rat(raster_layer, band, colors=('R', 'G', 'B', 'A')):
    """Extracts RAT from raster layer and given band

    :param raster_layer: the raster layer to classify
    :type raster_layer: QgsRasterLayer
    :param band: band number (1-based)
    :type band: int
    :param colors: name of the RGB(A) columns for sidecar DBF files, defaults to ('R', 'G', 'B', 'A')
    :type red_column_name: tuple, optional
    :return: RAT
    :rtype: RAT
    """

    headers = []
    values = {}
    fields = {}
    # For sidecar files
    path = None

    COLOR_ROLES = (gdal.GFU_Red, gdal.GFU_Green, gdal.GFU_Blue, gdal.GFU_Alpha)

    is_sidecar = False

    ds = gdal.OpenEx(raster_layer.source())
    if ds:
        band = ds.GetRasterBand(band)
        if band:
            rat = band.GetDefaultRAT()
            if rat is not None:
                for i in range(0, rat.GetColumnCount()):
                    column = rat.GetNameOfCol(i)
                    headers.append(column)
                    values[column] = []
                    fields[column] = RATField(column, rat.GetUsageOfCol(i),
                                              rat.GetTypeOfCol(i))

                for r in range(0, rat.GetRowCount()):
                    for c in range(0, rat.GetColumnCount()):
                        column = headers[c]
                        if fields[column].type == gdal.GFT_Integer:
                            values[headers[c]].append(rat.GetValueAsInt(r, c))
                        elif fields[column].type == gdal.GFT_Real:
                            values[headers[c]].append(
                                rat.GetValueAsDouble(r, c))
                        else:
                            values[headers[c]].append(
                                html.unescape(rat.GetValueAsString(r, c)))

            path = raster_layer.source() + '.aux.xml'

    # Search for sidecar DBF files, `band` is ignored!
    if not values:

        info = QFileInfo(raster_layer.publicSource())
        directory = info.dir().path()
        basename = info.baseName()
        filename = info.fileName()
        candidates = (basename + '.dbf', basename + '.vat.dbf',
                      filename + '.dbf', filename + '.vat.dbf')

        for candidate in candidates:
            if os.path.exists(os.path.join(directory, candidate)):
                rat_layer = QgsVectorLayer(os.path.join(directory, candidate),
                                           'rat', 'ogr')
                if rat_layer.isValid():
                    path = os.path.join(directory, candidate)
                    for f in rat_layer.fields():
                        headers.append(f.name())
                        if f.name().upper() in colors:
                            fields[f.name()] = RATField(
                                f.name(),
                                COLOR_ROLES[colors.index(f.name().upper())],
                                gdal.GFT_Integer if f.type()
                                in (QVariant.Int,
                                    QVariant.LongLong) else gdal.GFT_Real)
                        elif f.name().upper() == 'COUNT':
                            fields[f.name()] = RATField(
                                f.name(), gdal.GFU_PixelCount,
                                gdal.GFT_Integer)
                        elif f.name().upper() == 'VALUE':
                            fields[f.name()] = RATField(
                                f.name(), gdal.GFU_MinMax,
                                gdal.GFT_Integer if f.type()
                                in (QVariant.Int,
                                    QVariant.LongLong) else gdal.GFT_Real)
                        else:
                            if f.type() in (QVariant.Int, QVariant.LongLong):
                                type = gdal.GFT_Integer
                            elif f.type() == QVariant.Double:
                                type = gdal.GFT_Real
                            else:
                                type = gdal.GFT_String

                            fields[f.name()] = RATField(
                                f.name(), gdal.GFU_Generic, type)

                    for header in headers:
                        values[header] = []
                    for f in rat_layer.getFeatures():
                        for header in headers:
                            values[header].append(f.attribute(header))
                    is_sidecar = True
                    break

    # Colors
    if headers:
        red = None
        green = None
        blue = None
        alpha = None
        is_integer = False

        for name, f in fields.items():
            if f.usage == gdal.GFU_Red:
                red = name
                is_integer = f.type == gdal.GFT_Integer
                continue
            if f.usage == gdal.GFU_Green:
                green = name
                continue
            if f.usage == gdal.GFU_Blue:
                blue = name
                continue
            if f.usage == gdal.GFU_Alpha:
                alpha = name
                continue

        if red and green and blue:
            headers.append(RAT_COLOR_HEADER_NAME)
            values[RAT_COLOR_HEADER_NAME] = []
            for i in range(len(values[red])):
                func = 'fromRgb' if is_integer else 'fromRgbF'
                if alpha:
                    values[RAT_COLOR_HEADER_NAME].append(
                        getattr(QColor,
                                func)(values[red][i], values[green][i],
                                      values[blue][i], values[alpha][i]))
                else:
                    values[RAT_COLOR_HEADER_NAME].append(
                        getattr(QColor, func)(values[red][i], values[green][i],
                                              values[blue][i]))

    return RAT(values, is_sidecar, fields, path)
예제 #8
0
    dst_ds = gdal.GetDriverByName('GTiff').Create(
        os.path.join(dest, '2x2_2_BANDS_INT16.tif'), ny, nx, 2, gdal.GDT_Int16)

    dst_ds.SetGeoTransform(geotransform)  # specify coords
    srs = osr.SpatialReference()  # establish encoding
    srs.ImportFromEPSG(3857)  # WGS84 lat/long
    dst_ds.SetProjection(srs.ExportToWkt())  # export coords to file
    dst_ds.GetRasterBand(1).WriteArray(r_pixels)  # write r-band to the raster
    dst_ds.GetRasterBand(2).WriteArray(g_pixels)  # write g-band to the raster

    dst_ds.FlushCache()  # write to disk
    dst_ds = None

    # Create RAT
    fields = []
    fields.append(RATField('Value', gdal.GFU_MinMax, gdal.GFT_Integer))
    fields.append(RATField('Count', gdal.GFU_PixelCount, gdal.GFT_Integer))
    fields.append(RATField('Class', gdal.GFU_Name, gdal.GFT_String))
    fields.append(RATField('Class2', gdal.GFU_Name, gdal.GFT_String))
    fields.append(RATField('Class3', gdal.GFU_Generic, gdal.GFT_String))
    fields.append(RATField('Red', gdal.GFU_Red, gdal.GFT_Integer))
    fields.append(RATField('Green', gdal.GFU_Green, gdal.GFT_Integer))
    fields.append(RATField('Blue', gdal.GFU_Blue, gdal.GFT_Integer))

    fields = {field.name: field for field in fields}

    data = {
        'Value': [0, 2, 4],
        'Count': [1, 1, 2],
        'Class': ['zero', 'one', 'two'],
        'Class2': ['zero2', 'one2', 'two2'],