About the MBTiles format

MBTiles is a container format for tiled data. The format is based on the SQLite database engine: each MBTiles file is a SQLite database with at least two tables:

  • A metadata table with information about the file format version, the extents of the data, and so on

  • A table with BLOBs of data for each individual tile

The tiles are always structured in an industry-standard Web Mercator quadtree-like tile pyramid. The tiles themselves can contain either images in the JPEG or PNG format, or vector data. In the latter case, Google’s Protocol Buffers library is used to encode the vector shapes into a binary representation.

LuciadLightspeed provides support for the MBTiles format through TLcdMBTilesModelDecoder. This decoder supports both image and vector tiles, but the data type determines how you must visualize the tiles. See Visualizing image tiles and Visualizing vector tiles for more information.

Determining the type of your data

The models returned by TLcdMBTilesModelDecoder always contain a TLcdMBTilesModelDescriptor. This descriptor in turn contains a TLcdMBTilesDataSource. The data source object offers metadata for the MBTiles file, and has a getDataType method which tells you whether the file contains image or vector tiles:

//First create the model
ILcdModelDecoder decoder =
    new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));
ILcdModel model = decoder.decode(sourceName);
ILcdModelDescriptor descriptor = model.getModelDescriptor();
if (descriptor instanceof TLcdMBTilesModelDescriptor) {
  TLcdMBTilesModelDescriptor mbtiles = (TLcdMBTilesModelDescriptor) descriptor;
  TLcdMBTilesDataSource.MBTilesType dataType = mbtiles.getDataSource().getDataType();
  switch (dataType) {
  case IMAGE_TILES:
    // Treat model as image data.
    break;
  case VECTOR_TILES:
    // Treat model as vector data.
    break;
  default:
    throw new IllegalArgumentException(dataType + " is not a recognized MBTiles tile format");
  }
}

Visualizing image tiles

If you have determined that your model contains image tiles, you can visualize the data using standard raster layers in both GXY views and Lightspeed views.

Visualizing image tiles in a GXY view

This snippet shows how to create an ILcdGXYLayer for the ILcdModel returned by the MBTiles decoder, and how to add it to the ILcdGXYView:

//First create the model
ILcdModelDecoder decoder = new TLcdMBTilesModelDecoder();
ILcdModel model = decoder.decode(sourceName);

//Create a layer for the model with default styling
ILcdGXYLayer layer = TLcdGXYLayer.create(model);
//Wrap the layer with an async layer wrapper to ensure
//that the view remains responsive while data is being painted
layer = ILcdGXYAsynchronousLayerWrapper.create(layer);

//Add the async layer to the GXY view (an ILcdGXYView)
view.addGXYLayer(layer);

This results in an MBTiles image layer with default styling. See Visualizing Raster Data for more information about visualizing and styling raster data in GXY views.

Visualizing image tiles in a Lightspeed view

This snippet shows how to create an ILspLayer for the ILcdModel returned by the MBTiles decoder, and add it to the ILspView.

//First create the model
ILcdModelDecoder decoder = new TLcdMBTilesModelDecoder();
ILcdModel model = decoder.decode(sourceName);

//Create a layer for the model with default styling
ILspLayer layer = TLspRasterLayerBuilder.newBuilder()
                                        .model(model)
                                        .build();

//Add the layer to the Lightspeed view (an ILspView)
view.addLayer(layer);

This results in an MBTiles image layer with default styling. See Visualizing Raster Data for more information about visualizing and styling raster data in Lightspeed views.

Visualizing vector tiles

If you have determined that your model contains vector tiles, you can visualize it in a Lightspeed view only.

GXY views currently don’t support vector tiles.

Visualizing vector tiles in a Lightspeed view

To create an ILspLayer for a vector tiles model, you can use TLspMBTilesVectorLayerBuilder, as shown below:

//First create the model
ILcdModelDecoder decoder = new TLcdMBTilesModelDecoder();
ILcdModel model = decoder.decode(sourceName);

//Create a layer for the model using the layer builder
ILspLayer layer = TLspMBTilesVectorLayerBuilder.newBuilder()
                                               .model(model)
                                               .build();

//Add the layer to the Lightspeed view (an ILspView)
view.addLayer(layer);

This results in an MBTiles vector layer with default styling. Customizing the styling of vector tile layers provides more information about customizing the appearance of your data.

Customizing the styling of vector tile layers

MBTiles vector layers support most of the styling options supported by other vector layers in LuciadLightspeed. See Visualizing Vector Data for general information about styling vector data.

Therefore, similar to what you do for other vector formats, you change the appearance of your data by passing a set of styles or, more commonly, an ILspStyler to the TLspMBTilesVectorLayerBuilder. The documentation of TLcdMBTilesModelDecoder provides details about the types of objects you can expect the layer to pass into your styler. You can use this information to implement your styling logic. This is a simple example of a styler that demonstrates the assumptions you can safely make:

private static final ILcdIcon SQUARE_ICON = new TLcdSymbol(TLcdSymbol.FILLED_RECT, 7, Color.BLACK, Color.WHITE);
private final Map<TLcdDataType, Color> fType2Color = new HashMap<>();

@Override
public void style(Collection<?> aObjects, ALspStyleCollector aStyleCollector, TLspContext aContext) {
  for (Object feature : aObjects) {
    ALspStyle style;

    // Each vector tile feature is an ILcdDataObject
    ILcdDataObject dataObject = (ILcdDataObject) feature;
    // Assign a random color to each distinct TLcdDataType we encounter
    Color c = fType2Color.computeIfAbsent(
        dataObject.getDataType(),
        __ -> Color.getHSBColor((float) Math.random(), 1f, 1f)
    );

    // The geometry of the feature is always a shape list
    ILcdShapeList geometry = (ILcdShapeList) ALcdShape.fromDomainObject(feature);
    // The shape list has at least one element, and any subsequent elements are
    // the same type of shape (point, polyline, polygon or complex polygon).
    // Therefore, we can use the type of the first element to choose between an
    // icon, line or fill style.
    ILcdShape firstShape = geometry.getShape(0);
    if (firstShape instanceof ILcdPoint) {
      style = TLspIconStyle.newBuilder().icon(SQUARE_ICON).modulationColor(c).build();
    } else if (firstShape instanceof ILcdPolyline) {
      style = TLspLineStyle.newBuilder().color(c).width(2).build();
    } else {
      style = TLspFillStyle.newBuilder().color(c).opacity(0.7f).build();
    }
    // Finally, submit the feature and the chosen style for painting.
    aStyleCollector.object(feature).styles(style).submit();
  }
}

Note that you can also use the sldStyle() method on the layer build to style the layer using OGC SLD.

The use of TLspComplexStrokedLineStyle isn’t recommended for vector tile layers, because it’s likely to result in visually disturbing breaks in the stroke pattern where lines cross a tile boundary.

Selecting objects in vector tile layers

It’s possible to select vector tile features on the map using TLspSelectController. However, when you do so, you may notice that clicking an object results in only a part of that object being highlighted in the selection color.

This happens because the object, a long road for example, spans across multiple tiles. The tool that creates the MBTiles file cuts a geographic feature into several disconnected pieces if it spans multiple tiles. When you click such a feature using the selection controller, you only select the part of the original geographic feature that lies inside one particular tile. Any parts of that same feature which fall into the neighboring tiles, aren’t selected.

In the first three steps of this illustration, a polyline feature spans across three tiles, and therefore splits into three disjoint segments. Note that each of the segments retains the data properties of the original. Nonetheless, when the user clicks on one of the segments, the other two are not selected.

mbtiles uid provider
Figure 1. Selection styling across tile boundaries using a UID provider

TLspMBTilesVectorLayerBuilder provides a mechanism to address this issue: you can use the method uidProvider() to supply, for each vector tile feature, an identifier that’s unique to the original geographic feature to which that vector tile feature belongs. In the example of the long road, the ID could be the name of the road, for instance. If a UID provider is available, the layer highlights the vector tile feature that the user actually selected, and any other features that share the same UID, even if they’re in other tiles. Figure 1, “Selection styling across tile boundaries using a UID provider” illustrates this in the last step.

A UID provider typically retrieves one of the data properties of the vector tile feature using the ILcdDataObject API, as shown in this example:

TLspMBTilesVectorLayerBuilder
    .newBuilder()
    .model(aModel)
    // Make sure to enable selection:
    .selectable(true)
    // Then register a UID provider to enable cross-tile selection styling.
    .uidProvider(feature -> {
      // All vector tile features implement ILcdDataObject.
      ILcdDataObject dataObject = (ILcdDataObject) feature;
      // Retrieve one of the feature's data properties for use as an ID. Which property to use depends on the
      // data, of course. The value for the chosen property should be the same for all parts of a geographic
      // feature that spans multiple tiles, and should also be unique to that particular geographic feature.
      return dataObject.getValue("name");
    })
    .build();

For this to work, the features must contain a suitable attribute, which is something you must consider when producing MBTiles data. Also note that the MBTiles format provides a built-in "feature ID" attribute that LuciadLightspeed picks up automatically if no UID provider is set. Check the configuration settings of your MBTiles production tool to see if it can output this attribute.