#ifndef DOXYGEN_SKIP
/* $Id: vb6_tutorial.dox 13528 2008-01-14 19:37:42Z warmerdam $ */
#endif /* DOXYGEN_SKIP */
/*!
\page vb6_tutorial GDAL VB6 Bindings Tutorial
\section Intro Introduction
A partial set of Visual Basic 6 bindings have been build for GDAL. Internally
these bindings use Declare based calls into the GDAL DLL C API but a set of
shadow classes are also provided to provide object oriented access to
GDAL services in VB6 similar to those provided in C++.
Note that the VB6 bindings are nowhere near comprehensive, nor are they
documented. However, in combination with the corresponding C++ class
documentation, and the following docs, it should be possible to use GDAL
to accomplish a variety of operations. It is not believed that the VB6
bindings will be of any utility with earlier version of VB nor with VB.Net.
The classes for which access has been implemented includes
GDALDriver, GDALDataset, GDALRasterBand, GDALColorTable, OGRSpatialReference
and OGRCoordinateTransformation.
A mailing list specifically on VB6 GDAL topics has been setup at
http://groups.yahoo.com/group/gdal-vb6-appdev .
\section UsingVB6 Using GDAL VB6 Classes
To use VB6 GDAL bindings it is necessary to ensure that GDAL has been
built with appropriate C entry points exported using the "stdcall" calling
convention. This is the current default, but was not as recently as GDAL
1.2.6. So ensure you get a version more recent than 1.2.6.
Then add the GDAL VB6 class and module files to your VB6 project. These
come from the gdal/vb6 directory
and include the following key files:
- GDAL.bas - The main user visible module.
- GDALCore.bas - This module is for internal use.
- GDALDriver.cls - The GDALDriver class.
- GDALDataset.cls - The GDALDataset class.
- GDALRasterBand.cls - The GDALRasterBand class.
- GDALColorTable.cls - The GDALColorTable class.
- OGRSpatialReference.cls - The OGRSpatialReference class.
- OGRCoordinateTransformation.cls - The OGRCoordinateTransformation class.
You may need to edit GDALCore.bas, and change occurrences of gdal12.dll
to match what your GDAL DLL is called. You can include a full path to the
DLL if it can't be guaranteed to be in the current working directory of the
application (or the windows system32 directory).
You should also be able to load the "test" project from the gdal\\vb6\\test
directory. The test project has test menu items roughly corresponding to
the tasks in the following tutorial topics.
\section TutorialRead Tutorial - Read Dataset
This brief tutorial will demonstrate open a GDAL file, and fetching out
some information, about the dataset, and the individual bands. The
results are printed to the default from in the following example for
simplicity.
Before opening the file we need to register the GDAL format drivers.
Normally we will just register all the drivers with GDALAllRegister().
\code
Call GDAL.AllRegister()
\endcode
Then we need to try and open the dataset. The GDAL.OpenDS() function
returns a GDALDataset object, so we dimension an appropriate object for
this. GDAL.OpenDS() is the VB6 equivalent of the GDALDataset::GDALOpen()
function.
\code
Dim ds As GDALDataset
Set ds = GDAL.OpenDS( "utm.tif", GDAL.GA_ReadOnly )
\endcode
Then we need to check if the open succeeded, and if not report an error.
\code
If not ds.IsValid() Then
Call MsgBox( "Open failed: " & GDAL.GetLastErrorMsg() )
Exit Sub
End If
\endcode
If things succeeded, we query width of the image in pixels (XSize),
Height of the image in pixels (YSize) and number of bands (BandCount)
from the dataset properties.
\code
Print "Size: " & ds.XSize & "x" & ds.YSize & "x" & ds.BandCount
\endcode
Next we read metadata from the dataset using the VB6 equivalent of the
GDALMajorObject::GetMetadata() method, and report it to the user. Metadata
is returned as an array of strings of "name=value" items. Array indices
start at zero in the returned array. The domain argument should normally
be vbNullString though in specialized circumstances other domains might
apply.
\code
Dim MD As Variant
MD = ds.GetMetadata(vbNullString)
If (UBound(MD) > 0) Then
Print "Metadata:"
For i = 1 To UBound(MD)
Print " " & MD(i)
Next i
End If
\endcode
Parsing the "name=value" strings from GetMetadata() can be a bit of a bother,
so if we were looking for specific values we could use GetMetadataItem()
and provide a specific item we want to extract. This would extract just
the value if it is found, or an empty string otherwise. The GetMetadataItem()
is an analog of the C++ GDALMajorObject::GetMetadataItem() method.
\code
Dim MDValue As String
MDValue = ds.GetMetadataItem( "TIFF_DATETIME", vbNullString )
if MDValue <> "" Then
Print "Creation Date: " & MDValue
End If
\endcode
The GDALDataset::GetGeoTransform() method is used to get fetch the affine
transformation used to relate pixel/line locations on the image to
georeferenced locations in the current coordinate system. In the most common
case (image is not rotated or sheared) you can just report the origin
(upper left corner) and pixel size from these values. The method returns
0 on success or an error class if it fails, so we only use the return result
(placed into the Geotransform array) on success.
\code
Dim Geotransform(6) As Double
If ds.GetGeoTransform( Geotransform ) = 0 Then
If Geotransform(2) = 0 and Geotransform(4) = 0 Then
Print "Origin: " & Geotransform(0) & "," & Geotransform(3)
Print "Pixel Size: " & Geotransform(1) & "x" & (-1 * Geotransform(5))
End If
End If
\endcode
The coordinate system can be fetched using the GDALDataset::GetProjectionRef()
analog, GDALDataset.GetProjection(). The returned string is in OpenGIS
Well Known Text format. A later example will show how to use an
OGRSpatialReference object to reformat the WKT into more readable format
and make other use of it.
\code
Dim WKT As String
WKT = ds.GetProjection()
If Len(WKT) > 0 Then
Print "Projection: " & WKT
End If
\endcode
GDALDataset objects have one or more raster bands associated with them.
GDALRasterBand objects can have metadata (accessed the same as on the
GDALDataset) as well as an array of pixel values, and various specialized
metadata items like data type, color interpretation, offset/scale. Here
we report a few of the items.
First we loop over all the bands, fetching a band object for each band
and report the band number, and block size.
\code
For i = 1 To ds.BandCount
Dim band As GDALRasterBand
Set band = ds.GetRasterBand(i)
Print "Band " & i & " BlockSize: " & band.BlockXSize & "x" & band.BlockYSize
\endcode
The GDALRasterBand has a DataType property which has the value returned
by the C++ method GDALRasterBand::GetRasterDataType(). The returned value
is an integer, but may be compared to the predefined constants GDAL.GDT_Byte,
GDAL.GDT_UInt16, GDAL.GDT_Int16, GDAL.GDT_UInt32, GDAL.GDT_Int32,
GDAL.GDT_Float32, GDAL.GDT_Float64, GDAL.GDT_CInt16, GDAL.GDT_CInt32,
GDAL.GDT_CFloat32 and GDAL.GDT_CFloat64. In this case we use the
GDAL.GetDataTypeName() method to convert the data type into a name we
can show the user.
\code
Print " DataType=" & GDAL.GetDataTypeName(band.DataType) _
\endcode
We also report the offset, scale, minimum and maximum for the band.
\code
Print " Offset=" & band.GetOffset() & " Scale=" & band.GetScale() _
& " Min=" & band.GetMinimum() & " Max=" & band.GetMaximum()
\endcode
GDALRasterBands can also have GDALColorTable objects associated with them.
They are read with the GDALRasterBand::GetColorTable() analog in VB6.
Individual RGBA entries should be read into a 4 Integer array.
\code
Dim ct As GDALColorTable
Set ct = band.GetColorTable()
If ct.IsValid() Then
Dim CEntry(4) As Integer
Print " Has Color Table, " & ct.EntryCount & " entries"
For iColor = 0 To ct.EntryCount - 1
Call ct.GetColorEntryAsRGB(iColor, CEntry)
Print " " & iColor & ": " & CEntry(0) & "," & CEntry(1) & "," & CEntry(2) & "," & CEntry(3)
Next iColor
End If
\endcode
But of course, the most important contents of a GDAL file is the raster
pixel values themselves. The C++ GDALRasterBand::RasterIO() method is
provided in a somewhat simplified form. A predimensioned 1D or 2D
array of type Byte, Int, Long, Float or Double is passed to the RasterIO()
method along with the band and window to be read. Internally the "buffer
size" and datatype is extracted from the dimensions of the passed in buffer.
This example dimensions the RawData array to be the size of one scanline
of data (XSize x 1) and reads the first whole scanline of data from the
file, but only prints out the second and tenth values (since the buffer
indexes are zero based).
\code
Dim err As Long
Dim RawData() As Double
ReDim RawData(ds.XSize) As Double
err = band.RasterIO(GDAL.GF_Read, 0, 0, ds.XSize, 1, RawData)
if err = 0 Then
Print " Data: " & RawData(1) & " " & RawData(9)
End If
\endcode
Finally, when done accessing a GDALDataset we can explicitly close
it using the CloseDS() method, or just let it fall out of scope in which
case it will be closed automatically.
\code
Call ds.CloseDS()
\endcode
\section TutorialCreate Tutorial - Creating Files
Next we address creating a new file from an existing file. To create a
new file, you have to select a GDALDriver to do the creating. The GDALDriver
is essentially an object representing a file format. We fetch it with the
GetDriverByName() call from the GDAL module using the driver name.
\code
Dim Drv As GDALDriver
Call GDAL.AllRegister
Drv = GDALCore.GetDriverByName( "GTiff" )
If Not Drv.IsValid() Then
Call MsgBox( "GTiff driver not found " )
Exit Sub
End If
\endcode
You could get a list of registered drivers, and identify which support
creation something like this:
\code
drvCount = GDAL.GetDriverCount
For drvIndex = 0 To drvCount - 1
Set Drv = GDAL.GetDriver(drvIndex)
If Drv.GetMetadataItem(GDAL.DCAP_CREATE, "") = "YES" _
Or Drv.GetMetadataItem(GDAL.DCAP_CREATECOPY, "") = "YES" Then
xMsg = " (Read/Write)"
Else
xMsg = " (ReadOnly)"
End If
Print Drv.GetShortName() & ": " & Drv.GetMetadataItem(GDAL.DMD_LONGNAME, "") & xMsg
Next drvIndex
\endcode
Once we have the driver object, the simplest way of creating a new file
is to use CreateCopy(). This tries to create a copy of the input file
in the new format. A complete segment (without any error checking)
would look like the following. The CreateCopy() method corresponds to the
C++ method GDALDriver::CreateCopy(). The VB6 implementation does not
support the use of progress callbacks.
\code
Dim Drv As GDALDriver
Dim SrcDS As GDALDataset, DstDS As GDALDataset
Call GDAL.AllRegister
Set Drv = GDALCore.GetDriverByName( "GTiff" )
Set SrcDS = GDAL.Open( "in.tif", GDAL.GA_ReadOnly )
Set DstDS = Drv.CreateCopy( "out.tif", SrcDS, True, Nothing )
\endcode
This is nice and simple, but sometimes we need to create a file with more
detailed control. So, next we show how to create a file and then copy
pieces of data to it "manually". The GDALDriver::Create() analog is
Create().
\code
Set DstDS = Drv.Create("out.tif", SrcDS.XSize, SrcDS.YSize, _
SrcDS.BandCount, GDAL.GDT_Byte, Nothing)
\endcode
In some cases we may want to provide some creation options,
which is demonstrated here. Creation options (like metadata set through the
SetMetadata() method) are arrays of Strings.
\code
Dim CreateOptions(1) As String
CreateOptions(1) = "PHOTOMETRIC=MINISWHITE"
Set DstDS = Drv.Create("out.tif", SrcDS.XSize, SrcDS.YSize, _
SrcDS.BandCount, GDAL.GDT_Byte, CreateOptions)
\endcode
When copying the GeoTransform, we take care to check that reading the
geotransform actually worked. Most methods which return CPLErr in C++
also return it in VB6. A return value of 0 will indicate success, and non-zero
is failure.
\code
Dim err As Long
Dim gt(6) As Double
err = SrcDS.GetGeoTransform(gt)
If err = 0 Then
Call DstDS.SetGeoTransform(gt)
End If
\endcode
Copy the projection. Even if GetProjection() fails we get an empty string
which is safe enough to set on the target. Similarly for metadata.
\code
Call DstDS.SetProjection(SrcDS.GetProjection())
Call DstDS.SetMetadata(SrcDS.GetMetadata(""), "")
\endcode
Next we loop, processing bands, and copy some common data items.
\code
For iBand = 1 To SrcDS.BandCount
Dim SrcBand As GDALRasterBand, DstBand As GDALRasterBand
Set SrcBand = SrcDS.GetRasterBand(iBand)
Set DstBand = DstDS.GetRasterBand(iBand)
Call DstBand.SetMetadata(SrcBand.GetMetadata(""), "")
Call DstBand.SetOffset(SrcBand.GetOffset())
Call DstBand.SetScale(SrcBand.GetScale())
Dim NoDataValue As Double, Success As Long
NoDataValue = SrcBand.GetNoDataValue(Success)
If Success <> 0 Then
Call DstBand.SetNoDataValue(NoDataValue)
End If
\endcode
Then, if one is available, we copy the palette.
\code
Dim ct As GDALColorTable
Set ct = SrcBand.GetColorTable()
If ct.IsValid() Then
err = DstBand.SetColorTable(ct)
End If
\endcode
Finally, the meat and potatoes. We copy the image data. We do this one
scanline at a time so that we can support very large images without require
large amounts of RAM. Here we use a Double buffer for the scanline, but
if we knew in advance the type of the image, we could dimension a buffer
of the appropriate type. The RasterIO() method internally knows how to
convert pixel data types, so using Double ensures all data types (except
for complex) are properly preserved, though at the cost of some extra data
conversion internally.
\code
Dim Scanline() As Double, iLine As Long
ReDim Scanline(SrcDS.XSize) As Double
' Copy band raster data.
For iLine = 0 To SrcDS.YSize - 1
Call SrcBand.RasterIO(GDAL.GF_Read, 0, iLine, SrcDS.XSize, 1, _
Scanline)
Call DstBand.RasterIO(GDAL.GF_Write, 0, iLine, SrcDS.XSize, 1, _
Scanline)
Next iLine
\endcode
\section OSRTut Tutorial - Coordinate Systems and Reprojection
The GDAL VB6 bindings also include limited support for use of the
OGRSpatialReference and OGRCoordinateTransformation classes. The
OGRSpatialReference represents a coordinate system and can be used to
parse, manipulate and form WKT strings, such as those returned by the
GDALDataset.GetProjection() method. The OGRCoordinateTransformation
class provides a way of reprojecting between two coordinate systems.
The following example shows how to report the corners of an image in
georeferenced and geographic (lat/long) coordinates. First, we open
the file, and read the geotransform.
\code
Dim ds As GDALDataset
Call GDALCore.GDALAllRegister
Set ds = GDAL.OpenDS(FileDlg.Filename, GDAL.GA_ReadOnly)
If ds.IsValid() Then
Dim Geotransform(6) As Double
Call ds.GetGeoTransform(Geotransform)
\endcode
Next, we fetch the coordinate system, and if it is non-empty we try to
instantiate an OGRSpatialReference from it.
\code
' report projection in pretty format.
Dim WKT As String
Dim srs As New OGRSpatialReference
Dim latlong_srs As OGRSpatialReference
Dim ct As New OGRCoordinateTransformation
WKT = ds.GetProjection()
If Len(WKT) > 0 Then
Print "Projection: "
Call srs.SetFromUserInput(WKT)
\endcode
If the coordinate system is projected it will have a PROJECTION node. In
that case we build a new coordinate system which is the corresponding
geographic coordinate system. So for instance if the "srs" was UTM 11 WGS84
then it's corresponding geographic coordinate system would just be WGS84.
Once we have these two coordinate systems, we build a transformer to convert
between them.
\code
If srs.GetAttrValue("PROJECTION", 0) <> "" Then
Set latlong_srs = srs.CloneGeogCS()
Set ct = GDAL.CreateCoordinateTransformation(srs, latlong_srs)
End If
End If
\endcode
Next we call a helper function to report each corner, and the center.
We pass in the name of the corner, the pixel/line location at the corner,
and the geotransform and transformer object.
\code
Call ReportCorner("Top Left ", 0, 0, _
Geotransform, ct)
Call ReportCorner("Top Right ", ds.XSize, 0, _
Geotransform, ct)
Call ReportCorner("Bottom Left ", 0, ds.YSize, _
Geotransform, ct)
Call ReportCorner("Bottom Right ", ds.XSize, ds.YSize, _
Geotransform, ct)
Call ReportCorner("Center ", ds.XSize / 2#, ds.YSize / 2#, _
Geotransform, ct)
\endcode
The ReportCorner subroutine starts by computing the corresponding
georeferenced x and y location using the pixel/line coordinates and the
geotransform.
\code
Private Sub ReportCorner(CornerName As String, pixel As Double, line As Double, _
gt() As Double, ct As OGRCoordinateTransformation)
Dim geox As Double, geoy As Double
geox = gt(0) + pixel * gt(1) + line * gt(2)
geoy = gt(3) + pixel * gt(4) + line * gt(5)
\endcode
Next, if we have a transformer, we use it to compute a corresponding
latitude and longitude.
\code
Dim longitude As Double, latitude As Double, Z As Double
Dim latlong_valid As Boolean
latlong_valid = False
If ct.IsValid() Then
Z = 0
longitude = geox
latitude = geoy
latlong_valid = ct.TransformOne(longitude, latitude, Z)
End If
\endcode
Then we report the corner location in georeferenced, and if we have it
geographic coordinates.
\code
If latlong_valid Then
Print CornerName & geox & "," & geoy & " " & longitude & "," & latitude
Else
Print CornerName & geox & "," & geoy
End If
End Sub
\endcode
\htmlonly
$Id: vb6_tutorial.dox 13528 2008-01-14 19:37:42Z warmerdam $
\endhtmlonly
*/