Introduction

LuciadCPillar supports expression-based, hardware-accelerated visualization and styling of points. In this approach, you make use of style expressionsstyle expressionsstyle expressions to specify how to display features. LuciadCPillar evaluates these style expressions on the GPU, on each frame, for an entire group of features. The main advantage of this approach over non-expression-based feature styling is that values in the style expressions can adapt continuously with almost no overhead. This means that you can efficiently perform style updates for many features.

earthquake screenshot
Figure 1. Points with varying colors and scale depending on feature properties.

Building expressions to style individual features

Using style expressions, you can control several aspects of feature visualization:

  • The icon used to visualize the point

  • A modulation color to apply to the icon

  • A scale factor to apply to the icon

  • The visibility of the icon itself, which allows you to control which feature is displayed

You can build style expressions from three major building blocks:

  • Constant expressions: of type double, ColorColorColor, CoordinateCoordinateCoordinate, or IIconIIconIIcon.

  • Attribute expressions: resolved independently for each feature, typically based on a feature property.

  • Parameter expressions: act as global variables that you can change at runtime with no performance impact.

You can combine those building blocks in more complex expressions using various arithmetic and control flow operations. The main entry point for building style expressions is the StyleExpressionFactoryStyleExpressionFactoryStyleExpressionFactory class.

Program: Building style expressions
// Use a shortcut for StyleExpressionFactory
using S = StyleExpressionFactory;

// Icon expression = white circle icon
StyleExpression<std::shared_ptr<IIcon>> createIconExpression(size_t iconSize) {
  const auto circleIcon = std::make_shared<BasicIcon>(IconType::FilledCircle, iconSize, Color::white());
  return S::constant(std::dynamic_pointer_cast<IIcon>(circleIcon));
}

// Color expression = some shade of red depending on the "Depth" property (ranges from 1-500).
StyleExpression<Color> createColorExpression() {
  const auto depthAttribute = S::doubleAttribute("Depth");
  const auto red = S::divide(depthAttribute, S::constant(500.0));
  const auto green = S::constant(1.0);
  const auto blue = S::constant(1.0);
  const auto alpha = S::constant(1.0);
  return S::color(red, green, blue, alpha);
}

// Scale expression = some function of the "Magnitude" property of the feature (ranges from 1 to 9.9)
StyleExpression<double> createScaleExpression(size_t iconSize) {
  const auto magnitudeAttribute = S::doubleAttribute("Magnitude");
  const auto magnitudeFactor = S::pow(S::constant(1.5), magnitudeAttribute);
  return S::divide(magnitudeFactor, S::constant(static_cast<double>(iconSize)));
}
using S = Luciad.Layers.Styles.Expressions.StyleExpressionFactory;

public class EarthQuakeStyleExpression
{
    // Icon expression = white circle icon
    public static StyleExpression<IIcon> createIconExpression(uint iconSize)
    {
        var circleIcon = new BasicIcon(BasicIcon.IconType.FilledCircle, iconSize, Color.White);
        return S.Constant<IIcon>(circleIcon);
    }

    // Color expression = some shade of red depending on the "Depth" property (ranges from 1-500).
    public static StyleExpression<Color> createColorExpression()
    {
        var depthAttribute = S.DoubleAttribute("Depth");
        var red = S.Divide(depthAttribute, S.Constant(500.0));
        var green = S.Constant(1.0);
        var blue = S.Constant(1.0);
        var alpha = S.Constant(1.0);
        return S.Color(red, green, blue, alpha);
    }

    // Scale expression = some function of the "Magnitude" property of the feature (ranges from 1 to 9.9)
    public static StyleExpression<double> createScaleExpression(uint iconSize)
    {
        var magnitudeAttribute = S.DoubleAttribute("Magnitude");
        var magnitudeFactor = S.Pow(S.Constant(1.5), magnitudeAttribute);
        return S.Divide(magnitudeFactor, S.Constant((double)iconSize));
    }
}
// Use a shortcut for StyleExpressionFactory
import com.luciad.layers.styles.expressions.StyleExpressionFactory as S

// Icon expression = white circle icon
fun createIconExpression(iconSize: Int): StyleExpression<IIcon> {
    val circleIcon =
        BasicIcon(BasicIcon.IconType.FilledCircle, iconSize, Color.valueOf(Color.WHITE))
    return S.constant(circleIcon)
}

// Color expression = some shade of red depending on the "Depth" property (ranges from 1-500).
fun createColorExpression(): StyleExpression<Color> {
    val depthAttribute = S.doubleAttribute("Depth")
    val red = S.divide(depthAttribute, S.constant(500.0))
    val green = S.constant(1.0)
    val blue = S.constant(1.0)
    val alpha = S.constant(1.0)
    return S.color(red, green, blue, alpha)
}

// Scale expression = some function of the "Magnitude" property of the feature (ranges from 1 to 9.9)
fun createScaleExpression(iconSize: Int): StyleExpression<Double> {
    val magnitudeAttribute = S.doubleAttribute("Magnitude")
    val magnitudeFactor = S.pow(S.constant(1.5), magnitudeAttribute)
    return S.divide(magnitudeFactor, S.constant(iconSize.toDouble()))
}

Parameterized feature painting for feature groups

To paint groups of features and style them using style expressions, you must create a custom parameterized feature painter by deriving from the IParameterizedFeaturePainterIParameterizedFeaturePainterIParameterizedFeaturePainter interface. This interface is an extension of the IFeaturePainterIFeaturePainterIFeaturePainter interface with an additional paintParameterized()paintParameterized()paintParameterized() method. Instead of being called for each individual FeatureFeatureFeature, this paintParameterized()paintParameterized()paintParameterized() method is only called once per feature DataTypeDataTypeDataType with a dedicated ParameterizedFeatureCanvasParameterizedFeatureCanvasParameterizedFeatureCanvas. You use this parameterized canvas to submit icon draw commandsicon draw commandsicon draw commands that accept style expressions.

Note that you can still use the original paint()paint()paint() method, for painting labels for example.

Program: Using style expression to paint feature types
class EarthQuakePainter final : public IParameterizedFeaturePainter {
public:
  EarthQuakePainter() = default;

  void configureMetadata(FeaturePainterMetadata& metadata) const override {
    // ...
  }

  void paint(const Feature& feature, const FeaturePainterContext& context, FeatureCanvas& canvas) const override {
    // ...
  }

  void paintParameterized(const DataType& featureType,
                          const FeaturePainterContext& /* context */,
                          ParameterizedFeatureCanvas& canvas) const override {
    if (featureType != EarthQuakeModel::getFeatureType()) {
      return;
    }
    static constexpr auto iconSize = 15;
    static const auto iconExpression = createIconExpression(iconSize);
    static const auto colorExpression = createColorExpression();
    static const auto scaleExpression = createScaleExpression(iconSize);
    canvas.drawIcons()
      .iconExpression(iconExpression)
      .colorExpression(colorExpression)
      .scaleExpression(scaleExpression)
      .submit();
  }
};
class EarthQuakePainter : IParameterizedFeaturePainter
{
    public void ConfigureMetadata(FeaturePainterMetadata metadata)
    {
    }

    public void Paint(Feature feature, FeaturePainterContext context, FeatureCanvas canvas)
    {
    }

    public void PaintParameterized(DataType featureType,
                                   FeaturePainterContext context,
                                   ParameterizedFeatureCanvas canvas)
    {
        if (featureType != EarthQuakeModel.FeatureType())
        {
            return;
        }

        const uint iconSize = 15;
        var iconExpression = EarthQuakeStyleExpression.createIconExpression(iconSize);
        var colorExpression = EarthQuakeStyleExpression.createColorExpression();
        var scaleExpression = EarthQuakeStyleExpression.createScaleExpression(iconSize);

        canvas.DrawIcons()
            .IconExpression(iconExpression)
            .ColorExpression(colorExpression)
            .ScaleExpression(scaleExpression)
            .Submit();
    }
}
class EarthQuakePainter : IParameterizedFeaturePainter {

    override fun configureMetadata(metadata: FeaturePainterMetadata) {
        // ...
    }

    override fun paint(feature: Feature, context: FeaturePainterContext, canvas: FeatureCanvas) {
        // ...
    }

    override fun paintParameterized(
        featureType: DataType,
        context: FeaturePainterContext,
        canvas: ParameterizedFeatureCanvas
    ) {
        if (featureType != EarthQuakeModel.getFeatureType()) {
            return
        }

        val iconSize: Int = 15;
        val iconExpression: StyleExpression<IIcon> = createIconExpression(iconSize)
        val colorExpression: StyleExpression<Color> = createColorExpression()
        val scaleExpression: StyleExpression<Double> = createScaleExpression(iconSize)

        canvas.drawIcons()
            .iconExpression(iconExpression)
            .colorExpression(colorExpression)
            .scaleExpression(scaleExpression)
            .submit();
    }
}

Putting it all together

Finally, to use a custom parameterized feature painter, set it as the painter on a FeatureLayerFeatureLayerFeatureLayer, and add the layer to the map.

Program: Adding a feature layer with a parameterized painter
auto addEarthQuakeLayer(const std::shared_ptr<Map>& map, const std::shared_ptr<IFeatureModel>& featureModel) {
  const auto painter = std::make_shared<EarthQuakePainter>();
  const auto layer = FeatureLayer::newBuilder().title("EarthQuake").model(featureModel).painter(painter).build();
  map->getLayerList()->add(layer);
}
public void addEarthQuakeLayer(Map map, IFeatureModel featureModel)
{
    var painter = new EarthQuakePainter();
    var layer = FeatureLayer.NewBuilder().Title("EarthQuake").Model(featureModel).Painter(painter).Build();
    map.LayerList.Add(layer);
}
fun addEarthQuakeLayer(map: Map, featureModel: IFeatureModel) {
    var painter = EarthQuakePainter();
    var layer = FeatureLayer.newBuilder()
                    .title("EarthQuake")
                    .model(featureModel)
                    .painter(painter)
                    .build();
    map.getLayerList().add(layer);
}