Prerequisites

In this tutorial, it is assumed that the reader is familiar with the LuciadLightspeed concepts introduced in the Create your first 2D map application tutorial:

Goal

This tutorial teaches you the basic concepts in the LuciadLightspeed API for the styling of raster data on a GXY view.

This tutorial creates a JFrame containing a GXY view. We will add 2 layers to this view:

  • One background layer with satellite imagery. This layer will use the default styling.

  • One layer with raster data from the city of Treviso in Italy. We will change the brightness and the contrast of this layer.

gxy rasterpainting result
Figure 1. The finished application with the 2 layers

You can find the complete, runnable code at the end of the tutorial.

Initial setup

This tutorial starts from an application that shows a GXY view in a JFrame:

Program: Basic setup for this tutorial
public class BasicGXYRasterStylingTutorial {
  final TLcdMapJPanel fView = new TLcdMapJPanel();

  public JFrame createUI() {
    JFrame frame = new JFrame("Raster painting tutorial");

    frame.getContentPane().add(fView, BorderLayout.CENTER);
    frame.getContentPane().add(new TLcdLayerTree(fView), BorderLayout.EAST);

    frame.setSize(800, 600);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    return frame;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame frame = new BasicGXYRasterStylingTutorial().createUI();
      frame.setVisible(true);
    });
  }
}

Apply default styling to the background layer

To make the rest of the code a bit more compact, we start by introducing a utility method to decode the data:

Program: Utility method to decode data into a model
private ILcdModel decodeModel(String aSourceName) {
  try {
    TLcdCompositeModelDecoder modelDecoder =
        new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));
    return modelDecoder.decode(aSourceName);
  } catch (IOException e) {
    //In a real application, we would need proper error handling
    throw new RuntimeException(e);
  }
}

For the background layer, we use a GeoPackage raster file with low-resolution satellite imagery from the whole world. To create a layer for that data, we use the TLcdGXYLayer class. This class can deal with both vector data and raster data.

We do not specify any style settings for the background layer,so that the default styling is applied. In the case of an imagery layer, this means that its images are displayed as-is.

Program: Creating a raster layer with default styling using TLcdGXYLayer
private void addSatelliteLayer() {
  ILcdGXYLayer layer = TLcdGXYLayer.create(decodeModel("Data/GeoPackage/bluemarble.gpkg"));
  //Wrap the layer with an async layer wrapper to ensure
  //that the view remains responsive while data is being painted
  layer = ILcdGXYAsynchronousLayerWrapper.create(layer);

  fView.addGXYLayer(layer);
}

To prevent a blocked view painting thread while the raster is being painted, we wrap our layer with an asynchronous layer wrapper. This ensures that the data is painted on a background thread, and that we can still interact with the view while the raster is painted. You can find out out more about asynchronous painting here.

Apply custom styling to the Treviso layer

For the layer with data of the city of Treviso, we are going to customize the styling. The styling is specified on the painter. You can use setter methods to change it.

In this case, we adjust the contract and brightness of the raster images.

Program: Creating the painter and using the setters
//Setup the styling for the raster using the setters on the painter
TLcdGXYImagePainter painter = new TLcdGXYImagePainter();
painter.setBrightness(1.1f);
painter.setContrast(1.5f);

Most raster data is suitable to be shown at certain scales only. For example, it makes no sense to show very detailed raster data of a certain area when a map has been zoomed out, because the details will no longer be distinguishable on the map.

To avoid the loading of unnecessary raster data in such cases, the GXY map shows just the geographical bounds of the raster data set when it is zoomed out. As a result, the performance of the application increases, and the consumption of huge amounts of memory is prevented.

When the default styling of the TLcdGXYImagePainter is applied, those bounds are styled with a red outline. We are going to tweak this, and also define a hatch pattern to fill that outline:

Program: Specifying the styling for the raster outline
//Define the styling for the raster bounds when zoomed out
painter.setOutlineColor(Color.RED);
painter.setOutlineAreaFillStyle(new TLcdGXYHatchedFillStyle(EnumSet.of(TLcdGXYHatchedFillStyle.Pattern.SLASH), Color.RED));
painter.setFillOutlineArea(true);

On the map, this looks like:

gxy rasterpainting red hatch
Figure 2. The red hatching when zoomed out, indicating the bounds of the Treviso data set

To allow the layer to use that painter, we need to install it on the layer using the setGXYPainterProvider method:

Program: Creating the Treviso layer
private void addTrevisoLayer() {
  //Setup the styling for the raster using the setters on the painter
  TLcdGXYImagePainter painter = new TLcdGXYImagePainter();
  painter.setBrightness(1.1f);
  painter.setContrast(1.5f);
  //Define the styling for the raster bounds when zoomed out
  painter.setOutlineColor(Color.RED);
  painter.setOutlineAreaFillStyle(new TLcdGXYHatchedFillStyle(EnumSet.of(TLcdGXYHatchedFillStyle.Pattern.SLASH), Color.RED));
  painter.setFillOutlineArea(true);

  TLcdGXYLayer layer = TLcdGXYLayer.create(decodeModel("Data/Rst/treviso.rst"));
  layer.setGXYPainterProvider(painter);
  //Wrap the layer with an async layer wrapper to ensure
  //that the view remains responsive while data is being painted
  fView.addGXYLayer(ILcdGXYAsynchronousLayerWrapper.create(layer));
}

Just like the background layer, we wrap the layer with an asynchronous layer wrapper before adding it to the view.

The full code

Program: The full code
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.io.IOException;
import java.util.EnumSet;

import javax.swing.JFrame;
import javax.swing.WindowConstants;

import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.TLcdCompositeModelDecoder;
import com.luciad.util.service.TLcdServiceLoader;
import com.luciad.view.gxy.ILcdGXYLayer;
import com.luciad.view.gxy.TLcdGXYHatchedFillStyle;
import com.luciad.view.gxy.TLcdGXYLayer;
import com.luciad.view.gxy.asynchronous.ILcdGXYAsynchronousLayerWrapper;
import com.luciad.view.gxy.painter.TLcdGXYImagePainter;
import com.luciad.view.map.TLcdMapJPanel;
import com.luciad.view.swing.TLcdLayerTree;

public class BasicGXYRasterStylingTutorial {
  final TLcdMapJPanel fView = new TLcdMapJPanel();

  public JFrame createUI() {
    JFrame frame = new JFrame("Raster painting tutorial");

    addSatelliteLayer();
    addTrevisoLayer();

    frame.getContentPane().add(fView, BorderLayout.CENTER);
    frame.getContentPane().add(new TLcdLayerTree(fView), BorderLayout.EAST);

    frame.setSize(800, 600);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    return frame;
  }

  private ILcdModel decodeModel(String aSourceName) {
    try {
      TLcdCompositeModelDecoder modelDecoder =
          new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));
      return modelDecoder.decode(aSourceName);
    } catch (IOException e) {
      //In a real application, we would need proper error handling
      throw new RuntimeException(e);
    }
  }

  private void addSatelliteLayer() {
    ILcdGXYLayer layer = TLcdGXYLayer.create(decodeModel("Data/GeoPackage/bluemarble.gpkg"));
    //Wrap the layer with an async layer wrapper to ensure
    //that the view remains responsive while data is being painted
    layer = ILcdGXYAsynchronousLayerWrapper.create(layer);

    fView.addGXYLayer(layer);
  }

  private void addTrevisoLayer() {
    //Setup the styling for the raster using the setters on the painter
    TLcdGXYImagePainter painter = new TLcdGXYImagePainter();
    painter.setBrightness(1.1f);
    painter.setContrast(1.5f);
    //Define the styling for the raster bounds when zoomed out
    painter.setOutlineColor(Color.RED);
    painter.setOutlineAreaFillStyle(new TLcdGXYHatchedFillStyle(EnumSet.of(TLcdGXYHatchedFillStyle.Pattern.SLASH), Color.RED));
    painter.setFillOutlineArea(true);

    TLcdGXYLayer layer = TLcdGXYLayer.create(decodeModel("Data/Rst/treviso.rst"));
    layer.setGXYPainterProvider(painter);
    //Wrap the layer with an async layer wrapper to ensure
    //that the view remains responsive while data is being painted
    fView.addGXYLayer(ILcdGXYAsynchronousLayerWrapper.create(layer));
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame frame = new BasicGXYRasterStylingTutorial().createUI();
      frame.setVisible(true);
    });
  }
}