This tutorial explains the basics of working with LuciadCPillar:

  • Initializing the LuciadCPillar environment

  • Creating a map

  • Adding data

The sample_firstapp sample supports this tutorial.

Configuring the library through global initialization

Before you can use the LuciadCPillar library, you need to define a global configuration. You can configure global settings for the library by initializing an Environment. While you are interacting with the library, you must hold a reference to that Environment object. The lifetime of this object determines when the functionality of the library is available.

Creating and disposing the Environment

You use a builder pattern to create the Environment, through Environment::createInitializer(). Typically you need to configure the following:

  • the contents of the license file

  • the location to the LuciadCPillar resources. You can find those in the cpp/resources folder of the release.

When the application is closed, you must dispose of the created environment. In C++, the destructor is automatically invoked when the environment object goes out of scope. Please make sure that a reference to the environment is kept while the application uses the LuciadCPillar API.

For example:

Program: Initialize and disposing an environment
std::string licenseText = readLicenseText();
std::shared_ptr<Environment>
environment = Environment::createInitializer().withLicenseText(std::move(licenseText)).withResourcePath("resources").initialize();

Setting global configuration options

The initializer allows you to set global configuration options for the library, so that you can replace some default behavior:

  • You can plug in a logging framework. See Logging for more information.

  • You can decide whether to initialize and tear down the curl library for network requests.

Creating a map and visualizing geographical data

To visualize geographic data, you need to create a map.

Next, you typically need to integrate the map into an UI framework.

  • For C++: the LuciadCPillar samples integrate the map with QtQuick by default. A specific Qt Widgets integration sample is provided.

  • For C#: the LuciadCPillar samples integrate with WPF by default.

Creating the map

When creating the map, you must pass a spatial reference that will be used to display the data. All data will be transformed to this reference on-the-fly. For a 3D view, you must specify a geocentric reference, for example EPSG:4978. For a 2D view, you can pick another, non-geodetic reference. The map is created using the builder pattern.

Program: Creating the map
luciad::expected<std::shared_ptr<CoordinateReference>, ErrorInfo> reference = CoordinateReferenceProvider::create("EPSG:4978");
if (!reference) {
std::cerr << "Could not create the coordinate reference: " << reference.error() << std::endl;
std::exit(EXIT_FAILURE);
}
std::shared_ptr<Map> map = Map::newBuilder().reference(*reference).build();

Visualizing data

To visualize data, you must take these steps:

  1. Create a model for the data

  2. Create a layer for that model

  3. Add the layer to the map

You typically create a model by decoding a data source. LuciadCPillar offers out-of-the-box functionality to create models from data in certain formats, such as the GeoPackageModelDecoder for the GeoPackage format. If necessary, you can add support for other formats yourself.

Decoding and visualizing raster data

Raster data consists of matrices of pixels, or grid cells. Models containing raster data implement the IRasterModel interface.

To visualize a raster model, you must create a RasterLayer. You can use a builder pattern to create the layer.

Finally, you can show the layer on the map by adding it to the layer list of the map.

Program: Adding a raster layer
void addRasterLayer(const std::shared_ptr<Map>& map, const std::string& fileName) {
std::shared_ptr<GeoPackageDataSource> datasource = GeoPackageDataSource::newBuilder().source(fileName).build();
luciad::expected<std::shared_ptr<Model>, ErrorInfo> model = GeoPackageModelDecoder::decode(datasource);
if (!model) {
ErrorInfo& errorInfo = model.error();
std::cerr << "Cannot load GeoPackage file (raster) " + fileName + ": " + errorInfo.getMessage() << std::endl;
return;
}
std::shared_ptr<IRasterModel> rasterModel = std::dynamic_pointer_cast<IRasterModel>(*model);
if (!rasterModel) {
std::cerr << "Not a raster model" << std::endl;
return;
}
std::shared_ptr<RasterLayer> rasterLayer = RasterLayer::newBuilder().model(rasterModel).build();
map->getLayerList()->add(rasterLayer);
}

Decoding and visualizing vector data

Vector data consists of objects that have a geometry and typically some extra object information in the attributes, such as name, type and so on. Those types of objects are called features, and are represented in LuciadCPillar by the Feature class. Models containing features implement the IFeatureModel interface.

To visualize a feature model, you need a FeatureLayer. When you pass the model to the layer, you must also provide an instance of IFeaturePainter. This painter determines how the features will be visualized. In the samples, a simple implementation, called the RiverPainter, is used to visualize river features.

Again, you can show the layer on the map by adding it to the layer list of the map.

Program: Adding a feature layer
void addFeatureLayer(const std::shared_ptr<Map>& map, const std::string& fileName) {
std::shared_ptr<GeoPackageDataSource> datasource = GeoPackageDataSource::newBuilder().source(fileName).build();
luciad::expected<std::shared_ptr<Model>, ErrorInfo> model = GeoPackageModelDecoder::decode(datasource);
if (!model) {
ErrorInfo& errorInfo = model.error();
std::cerr << "Cannot load GeoPackage file (vector) " + fileName + ": " + errorInfo.getMessage() << std::endl;
return;
}
std::shared_ptr<IFeatureModel> featureModel = std::dynamic_pointer_cast<IFeatureModel>(*model);
if (!featureModel) {
std::cerr << "Not a feature model" << std::endl;
return;
}
std::shared_ptr<FeatureLayer> featureLayer = FeatureLayer::newBuilder().model(featureModel).painter(std::make_shared<RiverPainter>()).build();
map->getLayerList()->add(featureLayer);
}

Fitting on the data

When you are adding data to the map, it can be useful to automatically move the viewport of the map to the data position, so that it becomes visible to the map user. This is referred to as fitting on the data in LuciadCPillar.

To fit on map data:

  1. Create the bounds that you want to fit on, or take the bounds of the data model.

  2. Create a map navigator for the map. The map navigator is an object that allows you to navigate in the map programmatically.

  3. Use the map navigator to fit on the bounds.

Program: Fitting the map
luciad::expected<std::shared_ptr<CoordinateReference>, ErrorInfo> crs = CoordinateReferenceProvider::create("EPSG:4326");
if (!crs) {
return;
}
static constexpr Coordinate lowerLeft{5.5, 45.5, 0};
const Bounds bounds{crs.value(), lowerLeft, 6, 2.5, 0};
map->getMapNavigator().newFitAction().bounds(bounds).fit();