An example of a simple .vrt file referring to a 512x512 dataset with one band loaded from utm.tif might look like this:
<VRTDataset rasterXSize="512" rasterYSize="512"> <GeoTransform>440720.0, 60.0, 0.0, 3751320.0, 0.0, -60.0</GeoTransform> <VRTRasterBand dataType="Byte" band="1"> <ColorInterp>Gray</ColorInterp> <SimpleSource> <SourceFilename relativeToVRT="1">utm.tif</SourceFilename> <SourceBand>1</SourceBand> <SrcRect xOff="0" yOff="0" xSize="512" ySize="512"/> <DstRect xOff="0" yOff="0" xSize="512" ySize="512"/> </SimpleSource> </VRTRasterBand> </VRTDataset>
Many aspects of the VRT file are a direct XML encoding of the GDAL Data Model which should be reviewed for understanding of the semantics of various elements.
VRT files can be produced by translating to VRT format. The resulting file can then be edited to modify mappings, add metadata or other purposes. VRT files can also be produced programmatically by various means.
This tutorial will cover the .vrt file format (suitable for users editing .vrt files), and how .vrt files may be created and manipulated programmatically for developers.
VRTDataset: This is the root element for the whole GDAL dataset. It must have the attributes rasterXSize and rasterYSize describing the width and height of the dataset in pixels. It may have SRS, GeoTransform, GCPList, Metadata, and VRTRasterBand subelements.
<VRTDataset rasterXSize="512" rasterYSize="512">
The allowed subelements for VRTDataset are :
<SRS>PROJCS["NAD27 / UTM zone 11N",GEOGCS["NAD27",DATUM["North_American_Datum_1927",SPHEROID["Clarke 1866",6378206.4,294.9786982139006,AUTHORITY["EPSG","7008"]],AUTHORITY["EPSG","6267"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4267"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-117],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","26711"]]</SRS>
<GeoTransform>440720.0, 60, 0.0, 3751320.0, 0.0, -60.0</GeoTransform>
<Metadata>
<MDI key="md_key">Metadata value</MDI>
</Metadata>
The allowed subelements for VRTRasterBand are :
<ColorInterp>Gray</ColorInterp>:
<NoDataValue>-100.0</NoDataValue>
<ColorTable> <Entry c1="0" c2="0" c3="0" c4="255"/> <Entry c1="145" c2="78" c3="224" c4="255"/> </ColorTable>
<Description>Crop Classification Layer</Description>
<UnitType>ft</UnitType>
<Offset>0.0</Offset>
<Scale>0.0</Scale>
<CategoryNames> <Category>Missing</Category> <Category>Non-Crop</Category> <Category>Wheat</Category> <Category>Corn</Category> <Category>Soybeans</Category> </CategoryNames>
The relativeToVRT attribute on the SourceFilename indicates whether the filename should be interpreted as relative to the .vrt file (value is 1) or not relative to the .vrt file (value is 0). The default is 0.
Some characteristics of the source band can be specified in the optional SourceProperties tag to enable the VRT driver to differ the opening of the source dataset until it really needs to read data from it. This is particularly useful when building VRTs with a big number of source datasets. The needed parameters are the raster dimensions, the size of the blocks and the data type. If the SourceProperties tag is not present, the source dataset will be opened at the same time as the VRT itself.
<SimpleSource> <SourceFilename relativeToVRT="1">utm.tif</SourceFilename> <SourceBand>1</SourceBand> <SourceProperties RasterXSize="512" RasterYSize="512" DataType="Byte" BlockXSize="128" BlockYSize="128"/> <SrcRect xOff="0" yOff="0" xSize="512" ySize="512"/> <DstRect xOff="0" yOff="0" xSize="512" ySize="512"/> </SimpleSource>
The ComplexSource supports adding a custom lookup table to transform the source values to the destination. The LUT can be specified using the following form:
<LUT>[src value 1]:[dest value 1],[src value 2]:[dest value 2],...</LUT>
The intermediary values are calculated using a linear interpolation between the bounding destination values of the corresponding range.
The ComplexSource supports fetching a color component from a source raster band that has a color table. The ColorTableComponent value is the index of the color component to extract : 1 for the red band, 2 for the green band, 3 for the blue band or 4 for the alpha band.
When transforming the source values the operations are executed in the following order:
<ComplexSource> <SourceFilename relativeToVRT="1">utm.tif</SourceFilename> <SourceBand>1</SourceBand> <ScaleOffset>0</ScaleOffset> <ScaleRatio>1</ScaleRatio> <ColorTableComponent>1</ColorTableComponent> <LUT>0:0,2345.12:64,56789.5:128,2364753.02:255</LUT> <NODATA>0</NODATA> <SrcRect xOff="0" yOff="0" xSize="512" ySize="512"/> <DstRect xOff="0" yOff="0" xSize="512" ySize="512"/> </ComplexSource>
<KernelFilteredSource>
<SourceFilename>/debian/home/warmerda/openev/utm.tif</SourceFilename>
<SourceBand>1</SourceBand>
<Kernel normalized="1">
<Size>3</Size>
<Coefs>0.11111111 0.11111111 0.11111111 0.11111111 0.11111111 0.11111111 0.11111111 0.11111111 0.11111111</Coefs>
</Kernel>
</KernelFilteredSource>
For example, the following .vrt describes a raw raster file containing floating point complex pixels in a file called l2p3hhsso.img. The image data starts from the first byte (ImageOffset=0). The byte offset between pixels is 8 (PixelOffset=8), the size of a CFloat32. The byte offset from the start of one line to the start of the next is 9376 bytes (LineOffset=9376) which is the width (1172) times the size of a pixel (8).
<VRTDataset rasterXSize="1172" rasterYSize="1864"> <VRTRasterBand dataType="CFloat32" band="1" subClass="VRTRawRasterBand"> <SourceFilename relativetoVRT="1">l2p3hhsso.img</SourceFilename> <ImageOffset>0</ImageOffset> <PixelOffset>8</PixelOffset> <LineOffset>9376</LineOffset> <ByteOrder>MSB</ByteOrder> </VRTRasterBand> </VRTDataset>
Some things to note are that the VRTRasterBand has a subClass specifier of "VRTRawRasterBand". Also, the VRTRawRasterBand contains a number of previously unseen elements but no "source" information. VRTRawRasterBands may never have sources (ie. SimpleSource), but should contain the following elements in addition to all the normal "metadata" elements previously described which are still supported.
A few other notes:
Another example, in this case a 400x300 RGB pixel interleaved image.
<VRTDataset rasterXSize="400" rasterYSize="300"> <VRTRasterBand dataType="Byte" band="1" subClass="VRTRawRasterBand"> <ColorInterp>Red</ColorInterp> <SourceFilename relativetoVRT="1">rgb.raw</SourceFilename> <ImageOffset>0</ImageOffset> <PixelOffset>3</PixelOffset> <LineOffset>1200</LineOffset> </VRTRasterBand> <VRTRasterBand dataType="Byte" band="2" subClass="VRTRawRasterBand"> <ColorInterp>Green</ColorInterp> <SourceFilename relativetoVRT="1">rgb.raw</SourceFilename> <ImageOffset>1</ImageOffset> <PixelOffset>3</PixelOffset> <LineOffset>1200</LineOffset> </VRTRasterBand> <VRTRasterBand dataType="Byte" band="3" subClass="VRTRawRasterBand"> <ColorInterp>Blue</ColorInterp> <SourceFilename relativetoVRT="1">rgb.raw</SourceFilename> <ImageOffset>2</ImageOffset> <PixelOffset>3</PixelOffset> <LineOffset>1200</LineOffset> </VRTRasterBand> </VRTDataset>
To create a VRT dataset that is a clone of an existing dataset use the CreateCopy() method. For example to clone utm.tif into a wrk.vrt file in C++ the following could be used:
GDALDriver *poDriver = (GDALDriver *) GDALGetDriverByName( "VRT" ); GDALDataset *poSrcDS, *poVRTDS; poSrcDS = (GDALDataset *) GDALOpenShared( "utm.tif", GA_ReadOnly ); poVRTDS = poDriver->CreateCopy( "wrk.vrt", poSrcDS, FALSE, NULL, NULL, NULL ); GDALClose((GDALDatasetH) poVRTDS); GDALClose((GDALDatasetH) poSrcDS);
Note the use of GDALOpenShared() when opening the source dataset. It is advised to use GDALOpenShared() in this situation so that you are able to release the explicit reference to it before closing the VRT dataset itself. In other words, in the previous example, you could also invert the 2 last lines, whereas if you open the source dataset with GDALOpen(), you'd need to close the VRT dataset before closing the source dataset.
To create a virtual copy of a dataset with some attributes added or changed such as metadata or coordinate system that are often hard to change on other formats, you might do the following. In this case, the virtual dataset is created "in memory" only by virtual of creating it with an empty filename, and then used as a modified source to pass to a CreateCopy() written out in TIFF format.
poVRTDS = poDriver->CreateCopy( "", poSrcDS, FALSE, NULL, NULL, NULL ); poVRTDS->SetMetadataItem( "SourceAgency", "United States Geological Survey"); poVRTDS->SetMetadataItem( "SourceDate", "July 21, 2003" ); poVRTDS->GetRasterBand( 1 )->SetNoDataValue( -999.0 ); GDALDriver *poTIFFDriver = (GDALDriver *) GDALGetDriverByName( "GTiff" ); GDALDataset *poTiffDS; poTiffDS = poTIFFDriver->CreateCopy( "wrk.tif", poVRTDS, FALSE, NULL, NULL, NULL ); GDALClose((GDALDatasetH) poTiffDS);
In this example a virtual dataset is created with the Create() method, and adding bands and sources programmatically, but still via the "generic" API. A special attribute of VRT datasets is that sources can be added to the VRTRasterBand (but not to VRTRawRasterBand) by passing the XML describing the source into SetMetadata() on the special domain target "new_vrt_sources". The domain target "vrt_sources" may also be used, in which case any existing sources will be discarded before adding the new ones. In this example we construct a simple averaging filter source instead of using the simple source.
// construct XML for simple 3x3 average filter kernel source. const char *pszFilterSourceXML = "<KernelFilteredSource>" " <SourceFilename>utm.tif</SourceFilename><SourceBand>1</SourceBand>" " <Kernel>" " <Size>3</Size>" " <Coefs>0.111 0.111 0.111 0.111 0.111 0.111 0.111 0.111 0.111</Coefs>" " </Kernel>" "</KernelFilteredSource>"; // Create the virtual dataset. poVRTDS = poDriver->Create( "", 512, 512, 1, GDT_Byte, NULL ); poVRTDS->GetRasterBand(1)->SetMetadataItem("source_0",pszFilterSourceXML", "new_vrt_sources");
A more general form of this that will produce a 3x3 average filtered clone of any input datasource might look like the following. In this case we deliberately set the filtered datasource as in the "vrt_sources" domain to override the SimpleSource created by the CreateCopy() method. The fact that we used CreateCopy() ensures that all the other metadata, georeferencing and so forth is preserved from the source dataset ... the only thing we are changing is the data source for each band.
int nBand; GDALDriver *poDriver = (GDALDriver *) GDALGetDriverByName( "VRT" ); GDALDataset *poSrcDS, *poVRTDS; poSrcDS = (GDALDataset *) GDALOpenShared( pszSourceFilename, GA_ReadOnly ); poVRTDS = poDriver->CreateCopy( "", poSrcDS, FALSE, NULL, NULL, NULL ); for( nBand = 1; nBand <= poVRTDS->GetRasterCount(); nBand++ ) { char szFilterSourceXML[10000]; GDALRasterBand *poBand = poVRTDS->GetRasterBand( nBand ); sprintf( szFilterSourceXML, "<KernelFilteredSource>" " <SourceFilename>%s</SourceFilename><SourceBand>%d</SourceBand>" " <Kernel>" " <Size>3</Size>" " <Coefs>0.111 0.111 0.111 0.111 0.111 0.111 0.111 0.111 0.111</Coefs>" " </Kernel>" "</KernelFilteredSource>", pszSourceFilename, nBand ); poBand->SetMetadataItem( "source_0", szFilterSourceXML, "vrt_sources" ); }
The VRTDataset class is one of the few dataset implementations that supports the AddBand() method. The options passed to the AddBand() method can be used to control the type of the band created (VRTRasterBand, VRTRawRasterBand, VRTDerivedRasterBand), and in the case of the VRTRawRasterBand to set its various parameters. For standard VRTRasterBand, sources should be specified with the above SetMetadata() / SetMetadataItem() examples.
GDALDriver *poDriver = (GDALDriver *) GDALGetDriverByName( "VRT" ); GDALDataset *poVRTDS; poVRTDS = poDriver->Create( "out.vrt", 512, 512, 0, GDT_Byte, NULL ); char** papszOptions = NULL; papszOptions = CSLAddNameValue(papszOptions, "subclass", "VRTRawRasterBand"); // if not specified, default to VRTRasterBand papszOptions = CSLAddNameValue(papszOptions, "SourceFilename", "src.tif"); // mandatory papszOptions = CSLAddNameValue(papszOptions, "ImageOffset", "156"); // optionnal. default = 0 papszOptions = CSLAddNameValue(papszOptions, "PixelOffset", "2"); // optionnal. default = size of band type papszOptions = CSLAddNameValue(papszOptions, "LineOffset", "1024"); // optionnal. default = size of band type * width papszOptions = CSLAddNameValue(papszOptions, "ByteOrder", "LSB"); // optionnal. default = machine order papszOptions = CSLAddNameValue(papszOptions, "RelativeToVRT", "true"); // optionnal. default = false poVRTDS->AddBand(GDT_Byte, papszOptions); CSLDestroy(papszOptions); delete poVRTDS;
A specialized type of band is a 'derived' band which derives its pixel information from its source bands. With this type of band you must also specify a pixel function, which has the responsibility of generating the output raster. Pixel functions are created by an application and then registered with GDAL using a unique key.
Using derived bands you can create VRT datasets that manipulate bands on the fly without having to create new band files on disk. For example, you might want to generate a band using four source bands from a nine band input dataset (x0, x3, x4, and x8):
band_value = sqrt((x3*x3+x4*x4)/(x0*x8));
You could write the pixel function to compute this value and then register it with GDAL with the name "MyFirstFunction". Then, the following VRT XML could be used to display this derived band:
<VRTDataset rasterXSize="1000" rasterYSize="1000"> <VRTRasterBand dataType="Float32" band="1" subClass="VRTDerivedRasterBand"> <Description>Magnitude</Description> <PixelFunctionType>MyFirstFunction</PixelFunctionType> <SimpleSource> <SourceFilename relativeToVRT="1">nine_band.dat</SourceFilename> <SourceBand>1</SourceBand> <SrcRect xOff="0" yOff="0" xSize="1000" ySize="1000"/> <DstRect xOff="0" yOff="0" xSize="1000" ySize="1000"/> </SimpleSource> <SimpleSource> <SourceFilename relativeToVRT="1">nine_band.dat</SourceFilename> <SourceBand>4</SourceBand> <SrcRect xOff="0" yOff="0" xSize="1000" ySize="1000"/> <DstRect xOff="0" yOff="0" xSize="1000" ySize="1000"/> </SimpleSource> <SimpleSource> <SourceFilename relativeToVRT="1">nine_band.dat</SourceFilename> <SourceBand>5</SourceBand> <SrcRect xOff="0" yOff="0" xSize="1000" ySize="1000"/> <DstRect xOff="0" yOff="0" xSize="1000" ySize="1000"/> </SimpleSource> <SimpleSource> <SourceFilename relativeToVRT="1">nine_band.dat</SourceFilename> <SourceBand>9</SourceBand> <SrcRect xOff="0" yOff="0" xSize="1000" ySize="1000"/> <DstRect xOff="0" yOff="0" xSize="1000" ySize="1000"/> </SimpleSource> </VRTRasterBand> </VRTDataset>
In addition to the subclass specification (VRTDerivedRasterBand) and the PixelFunctionType value, there is another new parameter that can come in handy: SourceTransferType. Typically the source rasters are obtained using the data type of the derived band. There might be times, however, when you want the pixel function to have access to higher resolution source data than the data type being generated. For example, you might have a derived band of type "Float", which takes a single source of type "CFloat32" or "CFloat64", and returns the imaginary portion. To accomplish this, set the SourceTransferType to "CFloat64". Otherwise the source would be converted to "Float" prior to calling the pixel function, and the imaginary portion would be lost.
<VRTDataset rasterXSize="1000" rasterYSize="1000"> <VRTRasterBand dataType="Float32" band="1" subClass="VRTDerivedRasterBand"> <Description>Magnitude</Description> <PixelFunctionType>MyFirstFunction</PixelFunctionType> <SourceTransferType>"CFloat64"</SourceTransferType> ...
To register this function with GDAL (prior to accessing any VRT datasets with derived bands that use this function), an application calls GDALAddDerivedBandPixelFunc with a key and a GDALDerivedPixelFunc:
GDALAddDerivedBandPixelFunc("MyFirstFunction", TestFunction);
A good time to do this is at the beginning of an application when the GDAL drivers are registered.
GDALDerivedPixelFunc is defined with a signature similar to IRasterIO:
papoSources | A pointer to packed rasters; one per source. The datatype of all will be the same, specified in the eSrcType parameter. | |
nSources | The number of source rasters. | |
pData | The buffer into which the data should be read, or from which it should be written. This buffer must contain at least nBufXSize * nBufYSize words of type eBufType. It is organized in left to right, top to bottom pixel order. Spacing is controlled by the nPixelSpace, and nLineSpace parameters. | |
nBufXSize | The width of the buffer image into which the desired region is to be read, or from which it is to be written. | |
nBufYSize | The height of the buffer image into which the desired region is to be read, or from which it is to be written. | |
eSrcType | The type of the pixel values in the papoSources raster array. | |
eBufType | The type of the pixel values that the pixel function must generate in the pData data buffer. | |
nPixelSpace | The byte offset from the start of one pixel value in pData to the start of the next pixel value within a scanline. If defaulted (0) the size of the datatype eBufType is used. | |
nLineSpace | The byte offset from the start of one scanline in pData to the start of the next. |
typedef CPLErr (*GDALDerivedPixelFunc)(void **papoSources, int nSources, void *pData, int nXSize, int nYSize, GDALDataType eSrcType, GDALDataType eBufType, int nPixelSpace, int nLineSpace);
The following is an implementation of the pixel function:
#include "gdal.h" CPLErr TestFunction(void **papoSources, int nSources, void *pData, int nXSize, int nYSize, GDALDataType eSrcType, GDALDataType eBufType, int nPixelSpace, int nLineSpace) { int ii, iLine, iCol; double pix_val; double x0, x3, x4, x8; // ---- Init ---- if (nSources != 4) return CE_Failure; // ---- Set pixels ---- for( iLine = 0; iLine < nYSize; iLine++ ) { for( iCol = 0; iCol < nXSize; iCol++ ) { ii = iLine * nXSize + iCol; /* Source raster pixels may be obtained with SRCVAL macro */ x0 = SRCVAL(papoSources[0], eSrcType, ii); x3 = SRCVAL(papoSources[1], eSrcType, ii); x4 = SRCVAL(papoSources[2], eSrcType, ii); x8 = SRCVAL(papoSources[3], eSrcType, ii); pix_val = sqrt((x3*x3+x4*x4)/(x0*x8)); GDALCopyWords(&pix_val, GDT_Float64, 0, ((GByte *)pData) + nLineSpace * iLine + iCol * nPixelSpace, eBufType, nPixelSpace, 1); } } // ---- Return success ---- return CE_None; }