The DataSource object is the cornerstone of the GeoCalc SDK. It is based on a complete description of geodetic objects which are generally defined in an XML database file. All objects used to perform conversions and transformations in the toolkit are defined through the Datasource. In order to retrieve or modify any of the definitions from a given XML file, you must interact with this class. Applications should only create one instance of the DataSource object and pass it around.
In order to compile the code examples contained in this lesson, it will be necessary to include the WDataSource.h header file.
The DataSource object can be instantiated in the same way as any other object. The construction of the DataSource may throw a GeoCalcException if a valid license for GeoCalc cannot be found. For instructions on licensing, see Lesson 1. Here is how we will create a DataSource:
DataSource * dataSource = 0;
try
{
dataSource = DataSource::CreateDataSource();
}
catch(GeoCalcException & ex)
{
if(ex.get_ErrorCode() == GeoCalcException::Code::NoLicensedDataSource)
{
// Then you do not have a valid license to use GeoCalc
return;
}
}
Once we have created our DataSource, we must populate it with data by loading an XML datasource file. This is accomplished by using the LoadFile method. There are several different versions of the LoadFile method, and a number of parameters that may be set to provide a range of functionality including (but not limited to) locking of the datasource, logging of changes, and providing the user with a custom definition file. Here we use a variation that loads the base datasource locked against changes, but with an editable view and custom file that will allow users to add their own objects and to rearrange and hide objects in the dialogs.
if(! dataSource->LoadFile(BMG_T("C:\\bmg\\GeoCalcPBW\\data\\geodata.xml"), BMG_T("C:\\bmg\\GeoCalcPBW\\data\\geodata.xvw"), true, BMG_T("C:\\bmg\\GeoCalcPBW\\data\custom.xml"), 0, 0, 0))
{
// Then GeoCalc was unable to load the specified data source file
return;
}
We now have an instance of the DataSource object that is full of useful definitions and ready to be used.
The DataView allows users to control how DataSource information is displayed in their applications. The DataView is used to organize data into folders, hide unwanted objects from view, and also provide alternate "display names".
The GeoCalc library ships with a standard view file (geodata.xvw) that organizes all objects in the base DataSource (geodata.xml). Please note that if you choose to alter this view or create a new customized view, any modifications will need to be re-incorporated into future default views that may be shipped with updated GeoCalc releases. The MergeDataViews function is available on the DataSource to help facilitate this process.
WRAPPER_API void MergeDataViews(const BmgChar *viewFileToMerge, const BmgChar *mergeLogFile, bool ignoreTopLevel);
Users may wish to preserve a copy of the base view untouched, and save the customized views to a separate file. In this case, the following alternate LoadFile call should be employed:
if(! dataSource->LoadFile(BMG_T("C:\\bmg\\GeoCalcPBW\\data\\geodata.xml"), BMG_T("C:\\bmg\\GeoCalcPBW\\data\\customview.xvw"), true, BMG_T("C:\\bmg\\GeoCalcPBW\\data\custom.xml"), 0, 0, BMG_T("C:\\bmg\\GeoCalcPBW\\data\\geodata.xvw")))
{
// Then GeoCalc was unable to load the specified data source file
return;
}
This will use the "customview.xvw" file if it exists. If not, it will make a copy of the default geodata.xvw and save it as "customview.xvw" during the CommitToFile(). If it is desired to immediately save this new custom copy, the set_AutoSaveView() method on Datasource should be set to true.
In all cases, if objects are encountered during the load of the DataSource xml file that do not exist in the view file, they will be added on the top level of the folder for their object type. By default they will be visible. Thus, if you are using an older view file and a newer datasource file that has new objects added to it, they will be visible in the dialogs. In order to re-folder or hide these objects, updates will need to be made to the viewfile. If there is a desired to have new objects added in a hidden state, the set_RefreshViewHidden method on DataSource should be called.
The DataView can be accessed directly to retrieve lists of folders, objects, and display names. Most users will not need to access to this information unless they plan to create their own user dialogs similar to the DataSourceEditor and DataSourcePicker provided as part of the GeoCalc Library.
All objects that can be stored in the DataSource inherit from the Serializable object. For each Serializable type, there are three methods on the DataSource for retrieving and modifying the object: a Get method, a Put method, and a Remove method.
For example, there is a GetProjectedCoordSys method that retrieves a ProjectedCoordSys from the DataSource, a PutProjectedCoordSys method that puts a ProjectedCoordSys into the DataSource, and a RemoveProjectedCoordSys method that removes a ProjectedCoordSys from the DataSource.
Let's walk through using each of these methods with a ProjectedCoordSys. In order to retrieve a ProjectedCoordSys from the DataSource, we will need to know an identifier for the object. For more information about identifiers, see Lesson 3. If we did not know the identifier for an object, it would be possible to get the object by using the GeoCalc dialogs and browsing through the DataSource. These dialogs are covered in more depth in Lesson 5. For this example we will work with the ProjectedCoordSys that corresponds to the UTM 19N coordinate system:
ProjectedCoordSys * pcs = 0;
try
{
pcs = dataSource->GetProjectedCoordSys(BMG_T("BMG"), BMG_T("UTM-19N"));
}
catch(GeoCalcException & ex)
{
if(ex.get_ErrorCode() == GeoCalcException::Code::IdentifierNotFound)
{
// Then there is not an object in the DataSource with the specified identifier
return;
}
}
Now we have an instance of a ProjectedCoordSys that we can access and modify.
If we want to modify and keep our friendly neighborhood ProjectedCoordSys, we’ll need to save these modifications in the DataSource. This is done by using the Put method, or more specifically PutProjectedCoordSys. This method can be used to either add a new object to the DataSource, or to change an existing object. *Note: If you load a base datasource as read-only, you cannot edit the base objects. As part of the LoadFile() call, you can also specify a custom datasource for any objects that you might want to add. The Put method takes two arguments, the first of which is the object to be put into the DataSource, and the second of which is a boolean value indicating whether this object should overwrite an existing object in the DataSource with the same identifier.
Let's make some changes to our ProjectedCoordSys, and then put the changes in the DataSource by using the PutProjectedCoordSys method. In this case we do not want to overwrite the existing definition for the UTM 19N system, so we will change the identifiers on our ProjectedCoordSys and add it as a new object:
HorizontalDatum * datum = 0;
datum = dataSource->GetHorizontalDatum(BMG_T("BMG"), BMG_T("NAD27"));
pcs->set_Datum(*datum);
pcs->get_Identifiers().set_Item(BMG_T("GC"), BMG_T("UTM-19N-NAD27"));
pcs->get_Identifiers().set_Item(BMG_T("BMG"), BMG_T("UTM-19N-NAD27"));
pcs->get_Identifiers().Remove(BMG_T("EPSG"));
try
{
if(! dataSource->PutProjectedCoordSys(*pcs, false))
{
// Then the ProjectedCoordSys could not be added to the DataSource
return;
}
}
catch(GeoCalcException & ex)
{
if(ex.get_ErrorCode() == GeoCalcException::Code::GCIdentifierMissing)
{
// The ProjectedCoordSys could not be put in the DataSource because
// it did not have an identifier where issuer="GC".
return;
}
else if(ex.get_ErrorCode() == GeoCalcException::Code::FileReadOnly)
{
// The DataSource is not editable
return;
}
}
Now we have added our modified UTM definition into the data source without changing the existing definition for the UTM 19N system. It is important to note that we have not yet made any changes to the XML file - we have just modified the in-memory DataSource to include this new object. We will discuss saving these changes to a file later in this lesson.
Putting a new object into the DataSource will also add the object to the default view, making it visible in the picker and editor dialogs. By default, an object will be added to the top level of the folder for its object type. So in this case, the new ProjectedCoordSys will be added at the top level of the "Projected Coordinate System" folder.
We can remove existing definitions from the DataSource by using the Remove methods. Suppose we have become disillusioned with our newly created ProjectedCoordSys definition that was just added to the DataSource. Since it no longer seems to serve any purpose, we now want to remove it. This is accomplished with the RemoveProjectedCoordSys method. In order to remove a ProjectedCoordSys from the DataSource, we will need to know the identifier for the object. For more information about identifiers, see Lesson 3. If we did not know the identifier for an object, it would be possible to remove the object by using the GeoCalc dialogs and browsing through the DataSource. These dialogs are covered in more depth in Lesson 5. Fortunately we know the identifier for this object. Here is how we will go about with its removal:
try
{
// This will only work if our datasource is not read-only, or the system
// is in our custom datasource
if(! dataSource->RemoveProjectedCoordSys(BMG_T("MYCS"), BMG_T("MYNAD27-CoordinateSystem")))
{
// Then the specified ProjectedCoordSys could not be removed from the DataSource
return;
}
}
catch(GeoCalcException & ex)
{
if(ex.get_ErrorCode() == GeoCalcException::Code::IdentifierNotFound)
{
// Then the specified identifier could not be found in the DataSource
return;
}
else if(ex.get_ErrorCode() == GeoCalcException::Code::FileReadOnly)
{
// The DataSource is not editable
return;
}
}
Now we have removed our new definition, and our DataSource is back to the state it was in when we first loaded it.
It is possible to import external coordinate system and coordinate transformation definitions into a GeoCalc Datasource. This is accomplished with the ImportCoordSysFromFile, ImportCoordSysFromString, and ImportFile methods. GeoCalc has the ability to import definitions in the form of Well-Known Text (WKT), Esri PRJ files, MapInfo MAP files, and MapInfo TAB files, Esri GTF files, GeoTiff image files, GML 3.x coordinate system files, as well as XML datasources from other GeoCalc applications. If these coordinate systems, transforms, and other objects do not match systems that are currently in our datasource, they will be added to our custom datasource file. For this lesson, let's assume we have a PRJ file located on our C drive called "MyCoordSys.prj". We can import this into GeoCalc using the following workflow:
CoordSys * importedCS = 0;
try
{
importedCS = dataSource->ImportCoordSysFromFile(BMG_T("C:\\MyCoordSys.prj)");
}
catch(GeoCalcException & ex)
{
if(ex.get_ErrorCode() == GeoCalcException::Code::FileNotFound)
{
// Then the specified file could not be found
return;
}
if(ex.get_ErrorCode() == GeoCalcException::Code::CannotImportCoordinateSystem)
{
// Failed to import the CoordSys
return;
}
}
We now have a CoordSys object that contains the coordinate system definition given by the PRJ file.
GeoCalc also provides the ability to export CoordSys definitions to various well known (and sometimes obscure) formats. The methods ExportCoordSysToFile and ExportCoordSysToString will export CoordSys definitions as Well-Known Text (WKT). Here we export the CoordSys we just imported to a WKT string:
BmgChar * wktString = 0;
try
{
wktString = dataSource->ExportCoordSysToString(*importedCS);
}
catch(GeoCalcException & ex)
{
if(ex.get_ErrorCode() == GeoCalcException::Code::CannotExportCoordinateSystem)
{
// Failed to export CoordSys
return;
}
}
Additionally, there are methods on the Datasource class for creating GML CRS files, MapInfo TAB CRS clauses, and GeoTIFF datablocks.
In some cases a user may want to share a set of DataSource objects with other users. Perhaps an application administrator has a set of objects that are not in the base geodata.xml file but are nevertheless treated as locked objects that end users should not be altering. The solution is to export a supplementary DataSource file containing these custom definitions, along with all their component information. This file can be shipped with a program and then imported somewhere in the application workflow. Alternatively, we can pass a supplementary file around to via the user, and the application can provide a mechanism for manually importing through an appropriate application interface. By default these supplementary DataSource files use the .xsp extension. If you open the file in a text editor, you will find that they are formatted like regular DataSource xml files. Exported files will contain all sub-objects necessary to fully describe the objects being exported.
To create an exported file, the following call should be made:
std::list<GEOCALCPBW_NAMESPACE::Serializable*> objectsToExport;
// populate objectsToExport with the desired objects
dataSource->ExportToSupplementaryFile(BMG_T("C:\\bmg\\GeoCalcPBW\\data\\export.xsp"), objectsToExport);
This will generate the specified xsp file containing the objects in the list provided, as well as any objects they rely upon for full definition. Once the export process is complete, we can use the analog import method to add our custom definitions to another user’s datasource. To import a file, the following call should be made:
bool addToCustom = false; // Objects do not use the custom datasource file
dataSource->ImportFromSupplementaryFile(BMG_T("C:\\bmg\\GeoCalcPBW\\data\\export.xsp"), addToCustom);
In this case, the imported objects will be treated as if they are part of the base DataSource. Thus if the base DataSource is locked, the imported objects will also
be read-only, and will not be saved out during a CommitToFile. If it is desired that the imported objects be added to the current custom datasource, the addToCustom parameter should be set to true. In this case, the imported objects will be editable and will be saved out to the custom xml file during a CommitToFile.
When changes are made to the DataSource, they are not automatically made to the XML file. It is necessary to use the CommitToFile method to save the DataSource to an XML file, such that the modifications made in one instance of the DataSource can be accessed by another instance. There are two signatures for the CommitToFile method. The first methods takes no arguments and saves the DataSource to the file from which it was loaded, and the second takes a string argument that specifies where to save the DataSource.
This is how you save the DataSource:
if(! dataSource->CommitToFile())
{
// Failed to save the contents of the DataSource to the source file
return;
}
*Note: If we are using a read-only base datasource, and a custom datasource, only the custom file will be updated. Saving the DataSource will also save any current DataView and base and custom ChangeLog files.
Finally, we must clean up after ourselves and free the memory that we have allocated in this lesson using the Disposal::Dispose method:
if(pcs) Disposal::Dispose(pcs);
if(datum) Disposal::Dispose(datum);
if(importedCS) Disposal::Dispose(importedCS);
if(wktString) Disposal::Dispose(wktString);
if(dataSource) Disposal::Dispose(dataSource);