This tutorial shows how you can add a single image to the map using the custom raster API.
The concepts introduced in this article do not limit you to adding a single image. The API also allows you to
-
Create a grid of images
-
Create multiple levels of detail for images
-
Create custom elevation data
These additional topics are not explained explicitly in this article. Refer to the API documentation for
QuadTreeRasterModelBuilderQuadTreeRasterModelBuilderQuadTreeRasterModelBuilder and
MultilevelTiledRasterModelBuilderMultilevelTiledRasterModelBuilderMultilevelTiledRasterModelBuilder for more
information.
Step 1: Create a raster model with a single tile
In the first step, we define the structure of the raster model. The raster model uses a single level, with just 1 tile in it. This is where we add the image.
Note that this snippet mentions the SingleImageDataRetriever class. Step 2: Create a raster data retriever for the image elaborates on this class.
std::shared_ptr<IRasterModel> createImageModel(std::shared_ptr<Image> image, const Bounds& imageBounds) {
auto modelMetadata = ModelMetadata::newBuilder().title("SingleImage").build();
// Create a data retriever that always returns the given image
auto widthPx = image->getWidth();
auto heightPx = image->getHeight();
auto dataRetriever = std::make_shared<SingleImageDataRetriever>(std::move(image));
// Create a raster model with a single tile
return MultilevelTiledRasterModelBuilder::newBuilder() //
.reference(imageBounds.getReference())
.addLevel(widthPx, heightPx, 1, 1, imageBounds)
.modelMetadata(modelMetadata)
.dataRetriever(dataRetriever)
.build();
}
private IRasterModel CreateImageModel(Image image, Bounds imageBounds)
{
var modelMetadata = ModelMetadata.NewBuilder().Title("SingleImage").Build();
// Create a data retriever that always returns the given image
var widthPx = image.Width;
var heightPx = image.Height;
var dataRetriever = new SingleImageDataRetriever(image);
// Create a raster model with a single tile
return MultilevelTiledRasterModelBuilder.NewBuilder() //
.Reference(imageBounds.Reference)
.AddLevel(widthPx, heightPx, 1, 1, imageBounds)
.ModelMetadata(modelMetadata)
.DataRetriever(dataRetriever)
.Build();
}
private IRasterModel createImageModel(Image image, Bounds imageBounds) {
ModelMetadata modelMetadata = ModelMetadata.newBuilder().title("SingleImage").build();
// Create a data retriever that always returns the given image
long widthPx = image.getWidth();
long heightPx = image.getHeight();
SingleImageDataRetriever dataRetriever = new SingleImageDataRetriever(image);
// Create a raster model with a single tile
return MultilevelTiledRasterModelBuilder.newBuilder()
.reference(imageBounds.getReference())
.addLevel(widthPx, heightPx, 1, 1, imageBounds)
.modelMetadata(modelMetadata)
.dataRetriever(dataRetriever)
.build();
}
Step 2: Create a raster data retriever for the image
The next step is to create an IMultilevelTiledRasterDataRetrieverIMultilevelTiledRasterDataRetrieverIMultilevelTiledRasterDataRetriever
implementation that returns the image for the tile defined in Step 1: Create a raster model with a single tile. When the raster visualization
engine requests data for that tile, the IMultilevelTiledRasterDataRetrieverIMultilevelTiledRasterDataRetrieverIMultilevelTiledRasterDataRetriever class is called.
These snippets show how this interface is implemented.
// Returns a single Image, no matter which tile is requested
class SingleImageDataRetriever final : public IMultilevelTiledRasterDataRetriever {
public:
explicit SingleImageDataRetriever(std::shared_ptr<Image> image) : _image(std::move(image)) {
}
void retrieveTileData(const MultilevelTileCoordinate& tileCoordinate,
const CancellationToken& cancellationToken,
const std::shared_ptr<IMultilevelTiledRasterDataRetrieverCallback>& callback) override {
if (cancellationToken.isCanceled()) {
callback->onCanceled(tileCoordinate);
} else {
callback->onImageAvailable(tileCoordinate, _image);
}
}
private:
std::shared_ptr<Image> _image;
};
// Returns a single Image, no matter which tile is requested
private class SingleImageDataRetriever : IMultilevelTiledRasterDataRetriever
{
private readonly Image _image;
public SingleImageDataRetriever(Image image)
{
_image = image;
}
public void RetrieveTileData(MultilevelTileCoordinate tileCoordinate,
CancellationToken cancellationToken,
IMultilevelTiledRasterDataRetrieverCallback callback)
{
if (cancellationToken.IsCanceled)
{
callback.OnCanceled(tileCoordinate);
}
else
{
callback.OnImageAvailable(tileCoordinate, _image);
}
}
}
// Returns a single Image, no matter which tile is requested
private static class SingleImageDataRetriever implements IMultilevelTiledRasterDataRetriever {
private final Image _image;
public SingleImageDataRetriever(Image image) {
_image = image;
}
@Override
public void retrieveTileData(MultilevelTileCoordinate tileCoordinate,
CancellationToken cancellationToken,
IMultilevelTiledRasterDataRetrieverCallback callback) {
if (cancellationToken.isCanceled()) {
callback.onCanceled(tileCoordinate);
} else {
callback.onImageAvailable(tileCoordinate, _image);
}
}
}
Step 3: Add the raster model to the map
The final step is adding the raster model to the map at the location you want:
void addImageToMap(std::shared_ptr<Image> image, const Map& map) {
// Define the bounds where the Image should be added on the Map. In this case,
// a geodetic (lon-lat) reference is used, and the image is placed somewhere around Paris
auto crs = CoordinateReferenceProvider::create("EPSG:4326");
auto locationCenter = Coordinate(2.349014, 48.864716);
auto lowerLeft = locationCenter - Coordinate(3.0, 2.0);
auto upperRight = locationCenter + Coordinate(3.0, 2.0);
auto imageBounds = Bounds(*crs, lowerLeft, upperRight);
// Create a raster model based on the Image
std::shared_ptr<IRasterModel> rasterModel = createImageModel(std::move(image), imageBounds);
// Create a raster layer
std::shared_ptr<RasterLayer> rasterLayer = RasterLayer::newBuilder().model(rasterModel).build();
// Add the raster layer to the map
map.getLayerList()->add(rasterLayer);
}
void AddImageToMap(Image image, Map map)
{
// Define the bounds where the Image should be added on the Map. In this case,
// a geodetic (lon-lat) reference is used, and the image is placed somewhere around Paris
var crs = CoordinateReferenceProvider.Create("EPSG:4326");
var locationCenter = new Coordinate(2.349014, 48.864716);
var lowerLeft = locationCenter - new Coordinate(3.0, 2.0);
var upperRight = locationCenter + new Coordinate(3.0, 2.0);
var imageBounds = new Bounds(crs, lowerLeft, upperRight);
// Create a raster model based on the Image
IRasterModel rasterModel = CreateImageModel(image, imageBounds);
// Create a raster layer
RasterLayer rasterLayer = RasterLayer.NewBuilder().Model(rasterModel).Build();
// Add the raster layer to the map
map.LayerList.Add(rasterLayer);
}
void addImageToMap(Image image, Map map) throws ParseException {
// Define the bounds where the Image should be added on the Map. In this case,
// a geodetic (lon-lat) reference is used, and the image is placed somewhere around Paris
CoordinateReference crs = CoordinateReferenceProvider.create("EPSG:4326");
Coordinate locationCenter = new Coordinate(2.349014, 48.864716);
Coordinate lowerLeft = locationCenter.subtract(new Coordinate(3.0, 2.0));
Coordinate upperRight = locationCenter.add(new Coordinate(3.0, 2.0));
Bounds imageBounds = new Bounds(crs, lowerLeft, upperRight);
// Create a raster model based on the Image
IRasterModel rasterModel = createImageModel(image, imageBounds);
// Create a raster layer
RasterLayer rasterLayer = RasterLayer.newBuilder().model(rasterModel).build();
// Add the raster layer to the map
map.getLayerList().add(rasterLayer);
}
Figure 1, “The image added to the map” shows the end result: