Coordinate transformation is what GeoCalc was built to do. This lesson and the next lesson provide a description of precisely how to convert coordinates from one coordinate system to another. This lesson covers the most common kind of coordinate transformation performed by GeoCalc - one in which no vertical data is considered.
In order to compile the code samples provided in this lesson, it will be necessary to include the "WDataSource.h" header file.
Since all of the definitions we will be using reside in the DataSource, we will start by creating and loading a DataSource. For more information about the DataSource object, see lesson 4.
DataSource * dataSource = 0;
dataSource = DataSource::CreateDataSource();
dataSource->LoadFile(L"c:\\bmg\\geocalcpbw\\data\\geocalc.xml", false, L"c:\\bmg\\geocalcpbw\\data\\custom.xml");
The CoordTransform object facilitates the transformation of points from one coordinate system to another. It requires two CoordSys objects, one of which corresponds to the SourceCoordSys property, and the other of which corresponds to the TargetCoordSys property. The SourceCoordSys represents the coordinate system from which points will be transformed, and the TargetCoordSys represents the coordinate system to which points will be transformed. If you think of the CoordTransform as a mathematical function that acts upon CoordPoints, then the SourceCoordSys is the domain of the function and the TargetCoordSys is the co-domain of the function.
So the first step in creating our CoordTransform will be to get a couple CoordSys objects. It does not matter what type the CoordSys objects are, or if they use the same HorizontalDatum; the CoordTransform can facilitate a transformation between any two CoordSys objects. To make things interesting, let's use two CoordSys objects that are different types, one ProjectedCoordSys and one GeodeticCoordSys. Let's also select CoordSys objects that use different HorizontalDatums, so that we will need to perform some DatumShifts. For this example, we will perform a transformation from the US State Plane 27 Maine East ProjectedCoordSys and the WGS84 GeodeticCoordSys. First, we will need to retrieve these objects from the DataSource:
CoordSys * source = 0;
source = dataSource->GetProjectedCoordSys(L"BMG", L"ME-27E");
CoordSys * target = 0;
target = dataSource->GetGeodeticCoordSys(L"BMG", L"WGS84_coordinate_system");
Now that we have these objects, we can use them to construct a new CoordTransform:
CoordTransform * ct = 0;
ct = CoordTransform::CreateCoordTransform(*source, *target);
We now have a CoordTransform that we will use to convert between two different coordinate systems. However, we are not quite ready to transform any points yet. Since our source and target coordinate systems use different HorizontalDatums, we will need to perform one or more DatumShifts. Given any two HorizontalDatums, there are probably multiple DatumShift combinations that will shift between the HorizontalDatums. The CoordTransform object will not make any assumptions about how you wish to perform a DatumShift; you must explicitly specify all of the DatumShifts.
The CoordTransform::DatumShifts property holds a DatumShiftCollection that holds all of the DatumShifts used by the CoordTransform. In many cases, a single DatumShift will be sufficient for a CoordTransform. However, the GeoCalc data source does not contain each type of DatumShift for each pair of HorizontalDatums. In some cases, it is necessary to use multiple DatumShifts to shift from one HorizontalDatum to another.
There are multiple ways to find the DatumShifts needed for a CoordTransform, and lesson 6 describes these methods. For the purpose of this lesson, we will just retrieve a single DatumShift from the DataSource and put it into our CoordTransform::DatumShifts collection:
DatumShift * ds = 0;
ds = dataSource->GetDatumShift(L"BMG", L"NAD27-EAST US_to_WGS84");
ct->get_DatumShifts().AddBack(*ds);
It is also possible to intentionally not use a DatumShift by not populating the DatumShifts property. Users should be aware that some coordinate shift may occur when shifting between ProjectedCoordSys' even though no DatumShift is being used. This can be occur if the source and target HorizontalDatums use different Ellipsoids.
Our CoordTransform is now ready to transform a point. In order to do this, we will first need some CoordPoints and some coordinate values. We can get the CoordPoints we need by cloning the PointStyle objects on our source and target CoordSys objects:
CoordPoint * inPt = 0;
inPt = ct->get_SourceCoordSys().get_PointStyle().CloneCoordPoint();
CoordPoint * outPt = 0;
outPt = ct->get_TargetCoordSys().get_PointStyle().CloneCoordPoint();
Now inPt is an instance of a ProjectedPoint, and outPt is an instance of a GeodeticPoint. Our inPt is the point that we will transform, and outPt is the point that will hold the result of the transformation. Let's set the North coordinate to 1200 US feet, and let's set the East coordinate to -250 US feet. This can be accomplished with this line of code:
inPt->set_InUnits(-250, 1200);
To transform inPt, we need to call the ConvertAhead method:
try
{
if(! ct->ConvertAhead(*inPt, *outPt))
{
// ConvertAhead failed
return;
}
}
catch(GeoCalcException & ex)
{
if(ex.get_ErrorCode() == GeoCalcException::Code::InPointWrongType)
{
// Then inPt has the wrong ClassType
return;
}
else if(ex.get_ErrorCode() == GeoCalcException::Code::OutPointWrongType)
{
// Then outPt has the wrong ClassType
return;
}
else if(ex.get_ErrorCode() == GeoCalcException::Code::InPointOutOfBounds)
{
// Then inPt is not a valid point within the SourceCoordSys
return;
}
else if(ex.get_ErrorCode() == GeoCalcException::Code::OutPointOutOfBounds)
{
// Then outPt is not a valid point within the TargetCoordSys
return;
}
}
Assuming that ConvertAhead succeeded in transforming our point, outPt will now hold the result. We can get the coordinate values for outPt like this:
double lat, lon;
outPt->get_InUnits(lon, lat);
The coordinate values for outPt are lat = 43.82089062 and lon = -70.39491858. Please note that deviating from the steps outlined in this lesson may cause different output values than are shown here. Specifically, if you used a different method for selecting DatumShifts and selected some different DatumShifts, then the output values would not match those shown above.
In many cases, it is convenient to transform many points at once, and GeoCalc provides this functionality. The CoordTransform::ConvertListAhead method will transform all of the CoordPoints in a CoordPointCollection. In order to use this method, we must first construct two CoordPointCollections: one to hold our input points and one to hold the output points.
CoordPointCollection inCPC(*inPt);
CoordPointCollection outCPC(*outPt);
The CoordPoints that we pass to the CoordPointCollection constructor serve as a template, or point style, for all points in the collection. Next, we put some points that we wish to convert into the input CoordPointCollection:
inPt->set_InUnits(-250, 1200);
inCPC.AddPoint(*inPt);
inPt->set_InUnits(0, 0);
inCPC.AddPoint(*inPt);
Finally, we call the ConvertListAhead method to send our points through the CoordTransform:
int numConverted;
try
{
numConverted = ct->ConvertListAhead(inCPC, outCPC);
}
catch(GeoCalcException & ex)
{
if(ex.get_ErrorCode() == GeoCalcException::Code::InPointWrongType)
{
// Then inPt has the wrong ClassType
return;
}
else if(ex.get_ErrorCode() == GeoCalcException::Code::OutPointWrongType)
{
// Then outPt has the wrong ClassType
return;
}
else if(ex.get_ErrorCode() == GeoCalcException::Code::InPointOutOfBounds)
{
// Then inPt is not a valid point within the SourceCoordSys
return;
}
else if(ex.get_ErrorCode() == GeoCalcException::Code::OutPointOutOfBounds)
{
// Then outPt is not a valid point within the TargetCoordSsy
return;
}
}
The return value of the ConvertListAhead method indicates the number of points that were successfully transformed. If you set up everything in the same way as described in this lesson, then the return value of ConvertListAhead should be two, which means that both of the points we wanted to transform were successfully transformed.
Finally, we must clean up after ourselves and free the memory that we have allocated in this lesson using the Disposal::Dispose method:
if(ct) Disposal::Dispose(ct);
if(source) Disposal::Dispose(source);
if(target) Disposal::Dispose(target);
if(ds) Disposal::Dispose(ds);
if(inPt) Disposal::Dispose(inPt);
if(outPt) Disposal::Dispose(outPt);
if(dataSource) Disposal::Dispose(dataSource);