The typical usage of the LuciadRIA Map component consists of visualizing and editing georeferenced data in the browser. The Map also allows you to visualize data that is not referenced geospatially. You can 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. A vertical view example implementation is available in the LuciadRIA samples.

verticalview
Figure 1. Vertical view of a trajectory through airspaces

A non-georeferenced Map in LuciadRIA is the same as a georeferenced luciad.view.Map: it just has a Cartesian reference instead of a georeference. Because only the reference is different from that of a georeferenced map, the API to visualize data on a non-georeferenced view is completely identical. This means that all of the existing LuciadRIA components and concepts can be re-used.

Compared to the use of a third-party library, the re-use of the Map has one major benefit: you can re-use all functionality for a georeferenced map. This means that support is available for multiple layers, selection and editing of elements, labels and label decluttering, and so on. You can also re-use the existing controllers, painters, and label painters, for example, and even the models. This is illustrated in the vertical view sample. Regular FeatureLayer instances configured with a FeaturePainter are added 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: each point is specified uniquely in a plane by a pair of numerical coordinates. 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 certain quantity, such as a length or a weight, and expressed in a certain unit of measure, such as meters or kilograms.

A Cartesian reference can be created with the ReferenceProvider#createCartesianReference method. When creating the reference ,you must specify the unit and quantity for each of the axes by passing a luciad.uom.UnitOfMeasure instance for each axis. This is shown in Program: Creation of a Cartesian reference.

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

A UnitOfMeasure is associated with a certain kind of quantity, and indicates in which unit this quantity is expressed. Instances of this class cannot be created directly but must be retrieved from the luciad.uom.UnitOfMeasureRegistry class. For more information, refer to the class 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 will take 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 considered compatible when the X-axes of both references represent the same kind of quantity, and the Y axes of both references represent the same kind of quantity. Examples of quantity kinds are physical quantities like distance, weight, or time. Each axis may represent a different quantity kind. While the quantity kind for each axis must be the same in both references, they may be expressed in different units of measure.

Consider two compatible references, A and B. In both, the X-axes represent distance and the Y-axes represent time. In A, distance is expressed in meters, while in B it is represented in feet. Even though the units of measure differ, A and B are still compatible. It is thus possible to add a model with reference A to a Map with reference B.

All georeferences currently supported by LuciadRIA are compatible. Therefore, you can add any model with a georeference to a georeferenced map, and the data will be correctly visualized.

Dealing with reference incompatibility

The Map also supports the scenario where the model reference is incompatible with the map reference. The goal of the main use-case is to share a model between maps with different references, for example between a georeferenced map and a vertical view. The benefit of sharing the model is that updates to the model are immediately visible on both maps. However, this results in a model reference which is incompatible with the reference of the map.

To support this scenario, the FeatureLayer that contains the model must be configured with a luciad.model.feature.ShapeProvider. The role of the ShapeProvider is to provide shapes in a reference that is compatible with the map reference.

The shape provider must create a new shape for each of the Feature instances in the model. This shape should be defined in a reference that is compatible with the map reference, and that matches the reference of the ShapeProvider. The shapes returned by the ShapeProvider are the ones which will be visualized.

As a result, the FeaturePainter receives the original Feature contained in the model as well as the the shape returned by the ShapeProvider in its paintBody method. The original shape can still be accessed through the Feature.

Note that the reference of the ShapeProvider does not need to be identical to the map reference. It is sufficient 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 contains two example ShapeProvider implementations: the AirspaceShapeProvider and AirTrackShapeProvider classes.

A ShapeProvider is only required when the model reference is incompatible with the map reference. If both references are compatible, the ShapeProvider is optional. If it is set on the layer, it will be used, whether the model and map references are compatible or not.

Visualizing and decorating the axes

When your Map has a Cartesian reference, you may wish to show the X-axis and Y-axis, 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, you must first configure the Map with a border. The purpose of the border is to create space (expressed in pixels) at the left or bottom of the map. 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, you can use a luciad.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. This configuration object allows you to specify how long the measuring ticks on the axis should be, what style they should have, and how they should be labeled. For more information on the details of these properties, please refer to 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.

The paintBorderBody method is called when PaintRepresentation.BOTTOM_BORDER_BODY or PaintRepresentation.LEFT_BORDER_BODY representation is enabled. The paintBorderLabel method is called when PaintRepresentation.BOTTOM_BORDER_LABEL or PaintRepresentation.LEFT_BORDER_LABEL representation is enabled.

These methods are executed for each border, and you must inspect paintstate.border to determine for what border LuciadRIA is requesting painting and styling information.

If those methods are implemented, and a feature falls within the X-range of the Map, the feature will be drawn on the bottom border. Similarly if a feature falls within the Y-range of the Map, the feature will be drawn on the left border as well. Bottom border labels are centered by default. Left border labels are right-aligned 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. The drawing of shapes is not supported. The drawing of in-path or on-path labels is not supported either.

The left border decorations are not painted 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);
};