Out-of-the-box, you can use our PanoramaConverter sample or use LuciadFusion to process panoramic images. The output consists of:

  • Panorama "feature" information such as positions, image tiling structure, and data properties. You can find those in a GeoJson file named cubemap.json.

  • Panorama image tiles. Those are stored in a folder named images.

The LuciadFusion PANORAMICS service serves these files and LuciadRIA can load them. This works great for most datasets.

For some use cases though, you want to merge many datasets into one, for example if you have scans made on different days. You may also have a large number of panoramas across a large area. Storing such a number in a single GeoJson file has its limits.

Our TLcdLuciadPanoramaModelEncoder supports many other spatial formats as output format, besides GeoJson. You can:

  • Store the panorama feature information in a spatial database such as PostGIS, Amazon RDS, Oracle or Microsoft SQL Server, and serve it over the OGC WFS protocol with LuciadFusion.

  • Store the panorama images on any storage system, including Amazon S3, and serve them through a regular HTTP file server, or a CDN such as Amazon CloudFront.

In this how-to article, we’re going to show you how to store the panorama feature information in a spatial database. Next, we expose the result through LuciadFusion, and load the panoramas in LuciadRIA.

Decoding the input data

In this example, we’re using a Pegasus Mobile Mapping track, but other panorama data also works, including your own custom format.

First, we’re going to decode the source panorama data. We can use a specific model decoder, or find the right one using TLcdCompositeModelDecoder.

ILcdModelDecoder modelDecoder = new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));
ILcdModel inputModel = modelDecoder.decode("/path/to/pegasus/Track_ASphere/External Orientation.csv");

Creating or opening the spatial table

Next, we must create the table in the spatial database that’s going to hold the feature information. You can create this using the SQL CREATE TABLE statement with the right columns, but we’re going to let our TLcdPostGISModelEncoder create it.

The TLcdPostGISModelEncoder and TLcdPostGISModelDecoder need an entry file with the database connection and table details. Make a file named cubemap.pgs with the content below — adapted to your database of course. For details on the properties in the file, please see the PostGIS documentation.

You need at least the spatial column and the properties id, type, version, levelCount, tileWidth, and tileHeight. You can add other columns, like filename in this example. It stores the value of the filename data property in the input model.

driver   = org.postgresql.Driver
url      = jdbc:postgresql://localhost:5432/luciad
user     = luciad
password = luciad

table         = cubemap
spatialColumn = location

featureNames.0 = id
featureNames.1 = version
featureNames.2 = type
featureNames.3 = levelCount
featureNames.4 = tileWidth
featureNames.5 = tileHeight
featureNames.6 = filename   # this property is taken over from the input model

primaryFeatureIndex = 0
primaryFeatureAutoGenerated = false

# The properties below are only used creating a new table

dropBeforeCreatingTable = false    # enable this to remove an existing table, use with caution

SRID = 4326
geometryType = POINT
dimensions = 3

featureDataTypes.0 = VARCHAR(40)
featureDataTypes.1 = VARCHAR(10)
featureDataTypes.2 = VARCHAR(10)
featureDataTypes.3 = INTEGER
featureDataTypes.4 = INTEGER
featureDataTypes.5 = INTEGER
featureDataTypes.6 = VARCHAR(40)

We need to create the table once. To create the table, you just export an empty model.

new TLcdPostGISModelEncoder().export(new TLcdVectorModel(), "/path/to/cubemap.pgs");

Let’s verify that the table exists. Note that it has a spatial index on the location column, and a primary key index on the id column.

panorama postgis table
Figure 1. The SQL table

Processing the panoramas

Now that we have the input panoramas in a model, and have created the SQL table, we can start processing.

We can use the standard TLcdLuciadPanoramaModelEncoder, but pass a delegate encoder and decoder that it uses for storing feature information.

TLcdLuciadPanoramaModelEncoder modelEncoder = new TLcdLuciadPanoramaModelEncoder(new TLcdPostGISModelDecoder(), new TLcdPostGISModelEncoder());

We’re good to go. We can kick off the export.

if (modelEncoder.canExport(inputModel, "/path/to/cubemap.pgs")) {
  modelEncoder.export(inputModel, "/path/to/cubemap.pgs");

We can have a look inside the table using a SQL query.

SELECT *, ST_ASTEXT(location) FROM cubemap;
panorama postgis list
Figure 2. Rows in the cubemap table
The actual panorama image tiles should be in a folder named images/, right next to the cubemap.pgs file.

Adding more panoramas to the database

You can add more datasets to this table. Every following export adds rows in the database, unless a row with the same id already exists.

By default, the id property is a simple incrementing number, though, restarting from 1 for each dataset. To distinguish panoramas of distinct datasets, you must give each a unique id. This can be a UUID or another automatically generated ID, but preferably it’s something deterministic, based on a property of your object for example. This way, if you export the same dataset twice, to resume after aborting for example, objects get the same id as before. Plug in your id scheme with setFeatureIDRetriever. In the example below, we’re using the filename property as id.

modelEncoder.setFeatureIDRetriever(o -> (String) ((ILcdDataObject) o).getValue("filename"));

Serving the database table with LuciadFusion WFS

The LuciadFusion PANORAMICS service exposes only panoramas stored as GeoJson, but you can simply serve your database with a WFS service instead. Let LuciadFusion crawl the cubemap.pgs file through a Data root, upload the cubemap.pgs file, or add a data entry for it directly using Add data. Then, create a service of type WFS.

panorama postgis wfs
Figure 3. A LuciadFusion WFS service based on the PostGIS table
You can serve the images/ folder with any HTTP server or CDN.

Loading the panoramas with LuciadRIA

In LuciadRIA, you create a regular WFS model for this service. You attach a FusionPanoramaModel to it with the path to the images. Then, create a FeatureLayer with a PanoramaFeaturePainter as usual. This layer uses spatial WFS queries to load panorama positions relevant to the visible part of the map.

    .then(function(capabilities) {
      const featureType = capabilities.featureTypes[0];
      const store = WFSFeatureStore.createFromCapabilities(capabilities, featureType.name);
      const model = new FeatureModel(store);
      const panoModel = new FusionPanoramaModel("https://d341tnsdj5u5dv.cloudfront.net/data/panoramics/");
      return new FeatureLayer(model, {
        panoramaModel: panoModel,
        selectable: false,
        painter: new PanoramaFeaturePainter({
          iconHeightOffset: 2.5
        label: "Panoramas",

LuciadRIA now displays the panorama.

panorama postgis ria
Figure 4. Panoramas loaded from PostGIS table and CDN.