You typically use the LuciadRIA Map or WebGLMap components to visualize and edit data with a geospatial reference in the browser. You can also visualize data without a geospatial reference, though. You visualize non-georeferenced data by setting a Cartesian reference on the map, and positioning the data along axes that represent value ranges for variables of the data. Possible use cases include the creation of scatter plots or the creation of vertical views, which represent distance and height on their axes. See the LuciadRIA vertical view sample for an example of an implementation.

verticalview
Figure 1. Vertical view of a trajectory through airspaces

You can use a non-georeferenced map in LuciadRIA in the same way as a georeferenced Map or WebGLMap: a non-georeferenced map just has a Cartesian reference instead of a georeference. Because only the reference is different, the API to visualize data on a non-georeferenced view is the same.

Re-using the LuciadRIA map instead of introducing a third-party library has its benefits. It means that you can use all existing LuciadRIA map features: you can add more than one layer, select and edit elements, add labels, and declutter labels. You can also re-use the existing controllers, painters, and label painters, and even the models. See the vertical view sample for an illustration. It adds regular FeatureLayer instances configured with a FeaturePainter to the vertical view.

To visualize data in a non-georeferenced view, you need to:

  • Create a Cartesian reference for your view

  • Add your data to the Cartesian view

Creating a Cartesian reference

A Cartesian reference is a luciad.reference.CoordinateReference which uses a Cartesian coordinate system: a pair of numerical coordinates specifies each point uniquely in a plane. These are the signed distances from the point to two fixed perpendicular axes X and Y. In LuciadRIA, both axes of a Cartesian reference are associated with a quantity, such as a length or a weight. They’re expressed in a unit of measure, such as meters or kilograms.

You can create a Cartesian reference with the ReferenceProvider.createCartesianReference method. You must specify the unit and quantity for each of the axes by passing a uom.UnitOfMeasure instance for each axis. See Program: Creation of a Cartesian reference.

Program: Creation of a Cartesian reference (from samples/verticalview/common/VerticalViewOptions.ts)
const xAxisUnit = getUnitOfMeasure("NauticalMile");
const yAxisUnit = getUnitOfMeasure("FlightLevel");
const cartesianReference = createCartesianReference({
  xUnitOfMeasure: xAxisUnit,
  yUnitOfMeasure: yAxisUnit
});

A UnitOfMeasure is associated with a certain type of quantity, and indicates in which unit this quantity is expressed. You can’t create instances of this class directly. You must retrieve them from the uom.UnitOfMeasureRegistry class. For more information, see the reference documentation of those classes.

Adding data to a Cartesian view

A LuciadRIA map is capable of visualizing the data of models with different references. Those model references may be different from the reference of the map itself. The map takes care of the conversion of the model reference to the map reference if both references are compatible. This automatic conversion applies to both georeferences and Cartesian references.

When are references compatible?

Two Cartesian references are compatible when:

  • The X-axes of both references represent the same type of quantity

  • The Y axes of both references represent the same type of quantity.

Examples of quantity types are physical quantities like distance, weight, or time. Each axis may represent a different type of quantity. Although the quantity type for each axis must be the same in both references, you can express them in distinct units of measure.

Consider two compatible references A and B. In both, the X-axes represent distance and the Y-axes represent time. Reference A expresses distance in meters, while B expresses it in feet. Even though the units of measure differ, A and B are still compatible. This means that you can add a model with reference A to a Map with reference B.

All georeferences that LuciadRIA supports, are compatible. That means that you can add any model with a georeference to a georeferenced map, with a correct data visualization as a result.

Dealing with reference incompatibility

LuciadRIA maps also support scenarios in which the model reference is incompatible with the map reference.

The main goal is to share a model between maps with distinct references, for example between a georeferenced map and a vertical view. Sharing a model between maps has the benefit that you can visualize updates to the model on both maps. The consequence is a model reference that’s incompatible with the reference of the map, though.

To support such a scenario, you must configure the FeatureLayer with a view.feature.ShapeProvider. The ShapeProvider provides shapes in a reference that’s compatible with the map reference.

The shape provider must create a new shape for each of the Feature instances in the model. It must define the new shape in a reference that’s compatible with the map reference, and that matches the reference of the ShapeProvider. The map displays the shapes returned by the ShapeProvider.

The FeaturePainter receives the original Feature contained in the model and the shape returned by the ShapeProvider in its paintBody method. You can still access the original shape through the Feature.

You don’t need to make the reference of the ShapeProvider the same as the map reference. Just verify that both references are compatible. For example, if the map has a Cartesian reference where the Y-axis represents a height measured in Flight Level, the shape provider might be using a reference where the Y-axis represents the heights measured in meters.

The vertical view sample shows you two example ShapeProvider implementations: the AirspaceShapeProvider and AirTrackShapeProvider classes.

You only need a ShapeProvider when the model reference is incompatible with the map reference. If both references are compatible, the ShapeProvider is optional. If you set a ShapeProvider on the layer, LuciadRIA will use it, no matter if the model and map references are compatible.

Visualizing and decorating the axes

When your map has a Cartesian reference, you probably want to show the X-axis and Y-axis. You may also want to style those axes with ruler-like tick marks, and add labels to those axes.

axis decorations
Figure 2. Vertical view showing tick marks and label decorations in the bottom border of the map

This section explains how to visualize axes, and decorate those axes by adding measurement units in the form of tick marks, tick labels, feature icons, and feature labels.

Creating room for axis decorations

To visualize an axis and decorate it, first configure the map with a border. The purpose of the border is to create space at the left or bottom of the map. You express the space in pixels. This way, you reserve a blank area that you can use to style either the axes themselves, or to draw features.

Displaying axis tick marks and tick mark labels

To show tick marks for the measurement units on the X and Y axes, use a view.axis.AxisConfiguration object. Use the map axes property to configure the axes with such an AxisConfiguration for both the X-axis and the Y-axis. In that configuration object, you can set the length of the measuring ticks on the axis, the style of the ticks, and their labels. For more detail about those properties, see the API documentation.

Program: Configuring a map with a border, using a custom visualization for the X-axis to add tick marks, tick mark labels and grid lines
const map = new Map("map", {
  border: {
    left: 16,
    bottom: 32
  },
  axes: {
    xAxis: {
      labelRotation: 0,
      spacing: {
        minimumTickSpacing: 200,
        mapSpacing: [1, 5, 10, 50, 100, 500, 1000, 5000, 10000, 50000]
      },
      labelStyle: {
        fill: 'rgb(0,0,0)',
        strokeWidth: 1,
        font: '10px sans-serif',
        offsetX: -10,
        offsetY: 0,
        textAnchor: 'end',
        alignmentBaseline: 'middle'
      },
      gridLine: true,
      subTicks: 9
    },
    yAxis: {}
  }
});

Decorating the bottom border and the left border with features and feature labels

You can use the bottom border and the left border of the map to draw a feature with the optional paintBorderBody and paintBorderLabel methods of FeaturePainter.

To draw border features, enable the PaintRepresentation.BOTTOM_BORDER_BODY or PaintRepresentation.LEFT_BORDER_BODY representation, and call the paintBorderBody method. To draw feature labels, enable the PaintRepresentation.BOTTOM_BORDER_LABEL or PaintRepresentation.LEFT_BORDER_LABEL representation, and call the paintBorderLabel method.

LuciadRIA executes those methods for each border. Inspect paintstate.border to find out for what border LuciadRIA requests the painting and styling information.

If you implemented those methods, and a feature falls within the X-range of the map, the feature ends up on the bottom border. Similarly, if a feature falls within the Y-range of the map, the feature also ends up on the left border. LuciadRIA centers bottom border labels by default. It also right-aligns left border labels to the Y-axis by default. You can change the label position by setting the PointLabelStyle.positions property.

You can only draw icon features and simple point labels in the border. You can’t draw shapes, nor can you draw in-path or on-path labels.

The vertical view map doesn’t display the left border decorations by default. You must enable the left border paint representations explicitly for layers used in the vertical view map.

Program: A FeaturePainter that draws an icon at the bottom of the map, and labels on both borders
const painter = new FeaturePainter();
painter.paintBorderBody = function(borderGeoCanvas, feature, shape, layer, map, state) {
  if (state.border === Map.Border.BOTTOM) {
    borderGeoCanvas.drawIcon(shape, {
      image: image,
      offsetY: 16
    });
  }
};
painter.paintBorderLabel = function(borderLabelCanvas, feature, shape, layer, map, state) {
  const borderStyle = state.border === Map.Border.LEFT ? leftBorderLabelStyle : bottomBorderLabelStyle;
  const text = state.border === Map.Border.LEFT ? (feature.properties as any).level : (feature.properties as any).name;
  borderLabelCanvas.drawLabel((feature.properties as any).name, shape, borderStyle);
};