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.

This tutorial assumes that you are using a decoded imageimageimage. Though with minor changes, you can also use a compressed image, represented by a byte array for example.

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.

Program: Create a raster model with a single tile
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.

Program: Create a data retriever using the image
// 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:

Program: Add the raster model to the map
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);
}
image on map
Figure 1. The image added to the map