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
QuadTreeRasterModelBuilder
QuadTreeRasterModelBuilder
QuadTreeRasterModelBuilder
and
MultilevelTiledRasterModelBuilder
MultilevelTiledRasterModelBuilder
MultilevelTiledRasterModelBuilder
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 IMultilevelTiledRasterDataRetriever
IMultilevelTiledRasterDataRetriever
IMultilevelTiledRasterDataRetriever
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 IMultilevelTiledRasterDataRetriever
IMultilevelTiledRasterDataRetriever
IMultilevelTiledRasterDataRetriever
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: