Some panorama datasets don’t offer full georeference information. For example, datasets obtained from indoor sensors or small hand-held devices typically don’t have that information.

This how-to explains how you can load, process, and visualize such panoramas.

Even though you have no georeference information, most datasets share some properties that you can rely on.

The positions in the dataset are defined in a local 3D Cartesian reference, expressed in meters. Although the origin of the reference on the earth is unknown, the Z-axis nearly always aligns with the gravitational up direction.

To map this reference to the earth, you can use a topocentric reference: attach the origin of the reference to a longitude-latitude-altitude anchor point. Then apply a rotation, typically around the Z-axis.

You have two options for placing your dataset on the earth, also known as geolocating your data:

  • At the back end: in this scenario, we add a georeference when we load the data in LuciadFusion. The advantage of this approach is that you don’t need extra configuration on the client. The downside is that the location becomes permanent. You can change it only by re-processing the data.
    The easiest way is to specify a topocentric reference at your chosen location through Well-Known Text (WKT) from a .prj file.

  • At the front end: in this scenario, you load, process, and serve the dataset in LuciadFusion without a georeference. You position the panoramas on the earth only when we load them in LuciadRIA. You gain flexibility with this approach, because you don’t need to know the location up front.

Consider your use case to decide what the best option is for you.

To prepare your data for either approach in LuciadFusion, see the article How to process and visualize non-georeferenced panorama data in the LuciadFusion documentation.

Geo-locating your dataset on the client in LuciadRIA

If you choose to leave your data non-referenced at the back end, you need to geolocate it on the client.

The panorama feature data in the cubemap.json file in the Luciad Panorama Format is defined in a local 3D Cartesian reference. To position it on the earth, you can wrap the original UrlStore and adapt the features' shapes with an Affine3DTransformation so that they end up on the globe. We also set the panorama orientation so that they’re rotated properly.

export class GeoLocateStore implements Store {
  _store: Store;
  _geoLocateTransformation: Transformation;
  _geocentricToLonLatTransformation: Transformation;
  _rotation: number;

  constructor(delegateStore: Store, geoLocation: Point, geoLocateOptions?: Affine3DTransformationCreateGeoLocationOptions) {
    this._store = delegateStore;
    this._geoLocateTransformation = createTransformationFromGeoLocation(geoLocation, geoLocateOptions);
    this._geocentricToLonLatTransformation = createTransformation(getReference("EPSG:4978"), geoLocation.reference!);
    this._rotation = geoLocateOptions?.azimuth || 0.0;

  query(query?: any, options?: { reference?: CoordinateReference; }): Promise<Cursor> {
    return Promise.when(this._store.query(query, options), (cursor: Cursor) => {
      return {
        hasNext: (): boolean => {
          return cursor.hasNext();
        next: (): Feature => {
          return this.adaptFeature(;

  adaptFeature(feature: Feature): Feature {
    if (feature?.shape.type !== ShapeType.POINT) {
      throw new ProgrammingError("GeoLocateStore can only be used with point shapes");
    const nonReferencedPoint = feature.shape as Point;
    const geocentricPoint = this._geoLocateTransformation.transform(nonReferencedPoint);
    const lonLatPoint = this._geocentricToLonLatTransformation.transform(geocentricPoint);

    const properties = {};
    properties.orientation = ( || 0.0) + this._rotation;

    return new Feature(lonLatPoint, properties,;

To use this GeoLocateStore, pass a regular UrlStore and the geolocation parameters (position and rotation) normally passed to Affine3DTransformation.createTransformationFromGeoLocation:

    const luciadFusionPanoramaEndpoint = "http://localhost:8081/panoramic/lucerne/cubemap.json";
    const wgs84 = getReference("CRS:84");
    const store = new GeoLocateStore(new UrlStore({target: luciadFusionPanoramaEndpoint}), createPoint(wgs84, [4.66883444, 50.86472046, 30.12]), {azimuth: -32});
    const model = new FeatureModel(store, {reference: wgs84});
    const panoModel = new FusionPanoramaModel(luciadFusionPanoramaEndpoint);

You can even position the panoramas interactively, if you create a UI controller that invalidates the features in the GeoLocateStore when necessary.

ria panoramas nonreferenced
Figure 1. The Luciad Leuven office captured with a phone, positioned in LuciadRIA.