Layer styling
Introduction
When an ILcdModel
is visualized in a view by means of an ILcdLayer
, the visual appearance of the layer is determined by the elements in the model, but also by settings on the layer and painters.
For example, when the model only specifies that a line must be drawn at a certain location, the line color and line width
can be determined by the layer and/or painter settings.
The Lightspeed API introduces the concepts of ALspStyle
and ILspStyler
to represent and provide the style of a layer. The Lightspeed layers created by Lucy use the same mechanism, and allow you
to make modifications to the style through this API. See Visualizing vector data in Lightspeed views for more information.
While almost anything is possible by using the Lightspeed styling API directly, Lucy provides extra utilities to support common style use cases out-of-the-box for both the developer and the end user. See the Lucy user guide for the possibilities available to the end users. This article illustrates the styling options for Lucy developers.
Lucy style integration
You can access the ILspStyler
instances of each ILspStyledLayer
through the ILspStyledLayer.getStyler
method. At the interface level, ILspStyler
does not reveal any information about the styles it provides. There is however a more specific extension of ILspStyler
that exposes information about the styles it uses: ILspCustomizableStyler
. The style information is accessible through its ILspCustomizableStyler.getStyles
methods.
The Lucy style support is focused on those ILspCustomizableStyler
instances, because it allows us to access and modify those styles. If a custom layer uses and exposes ILspCustomizableStyler
instances, it will integrate with the Lucy style mechanism. In turn, most of the Lightspeed layers created by the Lucy formats
use ILspCustomizableStyler
instances, allowing the Lucy developer to easily access and modify the style of existing layers.
See Linking objects to styles with ILspStyler for more information on the ILspCustomizableStyler
.
Adjust the default layer style
Most of the Lightspeed formats in Lucy allow you to configure a default style file. The settings in this file will be applied
to each new layer created by the layer factory of that format. See the Javadoc of ALcyLspStyleFormat.DEFAULT_STYLE_FILE_SUFFIX
for more information. Because the Lucy UI allows you to save the layer style through File | Save Layer Style, it is possible to adjust the style of a layer through the UI, save the layer style to file, and configure this file as the
new default style in the configuration file of the corresponding format add-on.
Create UI to modify the layer style
Lucy provides a customizable UI which allows users to modify the general style of a layer: make it visible, selectable, and
so on. If the layer uses one or more ILspCustomizableStyler
instances, this UI also allows you to change the ALspStyle
settings, for example change the color of a line. This UI is shown when the Layer properties action of the layer control panel is triggered.
This functionality is provided by the TLcyLspLayerCustomizerAddOn
. This add-on registers a customizer panel factory, TLcyLspStyledLayerCustomizerPanelFactory
, which creates a panel to modify the settings of a Lightspeed layer based on the ILspCustomizableStyler
instances retrieved through the ILspStyledLayer.getStyler
method.
Read on for an overview of how the factory constructs an ILcyCustomizerPanel
and a discussion of the different customization options.
Construction mechanism of the panel
The figure Figure 1, “Inner workings of the TLcyLspStyledLayerCustomizerPanelFactory
” illustrates how the TLcyLspStyledLayerCustomizerPanelFactory
creates an ILcyCustomizerPanel
.

TLcyLspStyledLayerCustomizerPanelFactory
The factory can create a customizer panel for a TLcyLayerContext
, as illustrated in step 1. It first asks another factory to create a panel for the general layer settings by passing the
TLcyLayerContext
to this factory (step 2). The created panel typically contains checkboxes for visible, labeled, and so on.
Then it retrieves the ILspCustomizableStyler
instances from the layer if any are available. Each of those stylers is asked for its TLspCustomizableStyle
instances, and for those style instances a customizer panel is created (step 3). It does so by wrapping each TLspCustomizableStyle
in a TLcyLspCustomizableStyleContext
, which also provides the layer, the paint representation and the paint state of the style.
The customizer panels are created by the instances registered with the back-end for each TLcyLspCustomizableStyleContext
(step 3). This context wrapper is useful when the panel requires layer information to know which settings should be displayed
in the customizer panel, for instance. Examples of such panels are panels that allow users to modify the line, fill and icon
style.
Once all the panels are created, the factory creates a ILcyCompositeCustomizerPanel
(step 4) to which all panels created in steps 2 and 3 are added. The panels from steps 2 and 3 are not added directly to
the composite panel, but are wrapped. To allow the composite panel to recognize the different panels, a Map
is passed to the createCompositeCustomizerPanel
method containing the mapping between the customizer panels which will be added, and the object for which the panels were
created in step 2 and 3.
Step 5 and 6 show how the general layer settings panel from step 2 and the style related panels from step 3 are added to the composite panel.
Once all panels are added, the factory returns the composite panel, which now contains the panels created for the general layer settings and the style-related panels (step 7).
This mechanism allows you to customize the resulting UI:
-
By overriding the
createCompositeCustomizerPanel
method inTLcyLspStyledLayerCustomizerPanelFactory
, you have full control of how the different subpanels are combined into one UI. A possible use case is to switch from the default tabbed UI to UI that combines all panels into one panel. This is illustrated in Program: Override thecreateCompositeCustomizerPanel
method to modify how the panels are combined..Program: Override thecreateCompositeCustomizerPanel
method to modify how the panels are combined. (fromsamples/lucy/lightspeed/style/newiconstyle/SHPCustomizerPanelFactory
)@Override protected ILcyCompositeCustomizerPanel createCompositeCustomizerPanel(Map<ILcyCustomizerPanel, Object> aCustomizerPanelObjectMap) { TLcyCompositeCustomizerPanel result = new TLcyCompositeCustomizerPanel() { @Override public void addCustomizerPanel(ILcyCustomizerPanel aCustomizerPanel) { //do not add the panel to the UI since we want to combine all customizer panels on one panel super.addCustomizerPanel(aCustomizerPanel, false); //all customizer panels are components, so it is save to perform the cast add(((Component) aCustomizerPanel)); } }; result.setLayout(new BoxLayout(result, BoxLayout.Y_AXIS)); return result; }
-
You can replace the general layer settings UI by passing another
ILcyCustomizerPanelFactory
instance to the constructor ofTLcyLspStyledLayerCustomizerPanelFactory
. -
You can replace the UI for the customizable style contexts by registering
ILcyCustomizerPanelFactory
instances as a service that accepts theTLcyLspCustomizableStyleContext
instances. Thelucy.lightspeed.style.modifiedlinestylecustomizer
sample illustrates this by registering a custom factory for line styles. See theCustomizableLineStyleCustomizerPanelFactory
class for more information.
Because the UI to customize the layer style depends only on the presence of an |
Replace for all formats
Most of the default Lightspeed formats do not register an ILcyCustomizerPanelFactory
to alter the layer settings. Instead they rely on the presence of the TLcyLspLayerCustomizerAddOn
to register such an ILcyCustomizerPanelFactory
. If you want to replace the UI for all formats, you can simply remove that add-on and plug in your own ILcyCustomizerPanelFactory
. All those formats will then use that factory.
There are a few formats that provide their own ILcyCustomizerPanelFactory
and do not rely on the TLcyLspLayerCustomizerAddOn
. If you want to change those panels as well, you must adjust the format-specific panel as well.
Obtain a consistent layout across multiple panels
As explained in the first part of Construction mechanism of the panel, the layer properties panel in Lucy is constructed from individual panels for each of the exposed styles. By using the TLcyTwoColumnLayoutBuilder
for each of the individual style panels, it is possible to have a consistent layout for the whole panel. That is, the columns
will be aligned across all panels. Although the columns have the same width, it is still derived from the preferred sizes
of the containing components.
If you want to customize a panel and keep this consistent layout, it is sufficient to use the layout builder for your panel.
This is illustrated in Program: Using the two-column layout builder to add components to the UI, which shows the code responsible for adding the UI components to a modified line style customer panel. See CustomizableLineStyleCustomizerPanel
in the samples for an illustration. When this custom line style panel is added to the composite panel, the composite panel
makes sure that the layout of this custom panel is consistent with that of the other panels in the composite panel.
TLcyTwoColumnLayoutBuilder
to add components to the UI (from samples/lucy/lightspeed/style/modifiedlinestylecustomizer/CustomizableLineStyleCustomizerPanel
)
/**
* Add all components to the panel by using the TLcyTwoColumnLayoutBuilder, which makes sure
* all components are aligned in the same way as the other style panels
*/
private void initUI() {
TLcyTwoColumnLayoutBuilder builder = TLcyTwoColumnLayoutBuilder.newBuilder();
builder.
row().columnOne(new JLabel(TLcyLang.getString("Width")), fSpinner).
build();
builder.populate(this);
}
Consult the class Javadoc of TLcyTwoColumnLayoutBuilder
for more details on how to use this layout builder.
Using the |
Replace for a specific format
When the add-on responsible for a specific format exposes the creation of its ALcyLspFormat
, you can override the ALcyLspFormat.createLayerCustomizerPanelFactories
method and return your own ILcyCustomizerPanelFactory
. This will only affect the UI for the layers of that specific format. The lucy.lightspeed.style.newiconstyle
sample illustrates this:
samples/lucy/lightspeed/style/newiconstyle/SHPFormat
)
@Override
protected ILcyCustomizerPanelFactory[] createLayerCustomizerPanelFactories() {
return new ILcyCustomizerPanelFactory[]{new SHPCustomizerPanelFactory(getLucyEnv())};
}
Use OGC SLD styling
The majority of the Lightspeed layers in Lucy automatically pick up an SLD file located next to the source file of the model. If such an SLD file is present, the created layer is styled according to the styling rules defined in that SLD file.
Currently, Lucy does not offer any UI to customize the SLD styling. When SLD styling is used, the layer properties panel only shows UI to change the general layer properties such as the visibility, the label, and so on.
The use of an SLD file in conjunction with the model source file does not just allow you to set up more complicated styling
than you could set up with |
Managing layer styles
Lucy has a more general layer style concept that groups all style settings of a layer into one object: the ILcyLayerStyle
interface. This concept allows to save and load layer styles, copy styles between layers and keep track of changes to the
style in a uniform way for all layers.
The ILcyLayerStyle
is a black-box object at the interface level, so it can only be used between similar layers that are aware of their type
of style. It only allows to register ILcdChangeListener
instances that will be warned when the style changes. For example, for a Lightspeed layer the actual ILcyLayerStyle
implementation could contain references to the different ILspStyler
instances of the layer.
To retrieve the ILcyLayerStyle
of a layer, the concept of an ILcyLayerStyleProvider
has been introduced. This provider can be asked to provide the ILcyLayerStyle
of a layer, as shown in Program: Retrieving an ILcyLayerStyle
by using an ILcyLayerStyleProvider
. Making changes to this layer style will change the corresponding settings on the layer and/or painter. Typically an ILcyLayerStyleProvider
is created in an ALcyFormat
or ALcyLspFormat
, because the implementation of such a style provider depends on the implementation of the layer, and it is registered as
a service with the Lucy back-end. For more information, see the Lucy services mechanism documentation).
ILcyLayerStyle
by using an ILcyLayerStyleProvider
(from samples/lucy/loadstyle/ChangeStyleAction
)
//retrieve the style from the new layer
TLcyCompositeLayerStyleProvider layerStyleProvider = new TLcyCompositeLayerStyleProvider(fLucyEnv);
if (layerStyleProvider.canGetStyle(layerWithNewStyle)) {
layerStyle = layerStyleProvider.getStyle(layerWithNewStyle);
}
Furthermore, the ILcyLayerStyleProvider
allows you to apply a certain ILcyLayerStyle
to a layer, after which the layer settings will match those contained in the ILcyLayerStyle
. Applying a style to a layer is illustrated in Program: Applying an ILcyLayerStyle
by using an ILcyLayerStyleProvider
.
ILcyLayerStyle
by using an ILcyLayerStyleProvider
(from samples/lucy/loadstyle/ChangeStyleAction
)
if (layerStyleProvider.canApplyStyle(layerStyle, countriesLayer)) {
layerStyleProvider.applyStyle(layerStyle, countriesLayer);
}
The combination of both these mechanisms allows you to copy the layer style from one layer to another, for example, as shown in Program: Copy the style from one layer to another layer.
samples/lucy/syncstyle/LayerStyleSynchronizer
)
private void copyLayerStyle(ILcdLayer aLayerToCopyFrom, ILcdLayer aLayerToCopyToSFCT) {
TLcyCompositeLayerStyleProvider layerStyleProvider = new TLcyCompositeLayerStyleProvider(fLucyEnv);
if (layerStyleProvider.canGetStyle(aLayerToCopyFrom)) {
ILcyLayerStyle layerStyle = layerStyleProvider.getStyle(aLayerToCopyFrom);
if (layerStyleProvider.canApplyStyle(layerStyle, aLayerToCopyToSFCT)) {
layerStyleProvider.applyStyle(layerStyle, aLayerToCopyToSFCT);
}
}
}
The layers of the default Lucy formats allow copying the layer style from a GXY layer to a Lightspeed layer, and the other way round. |
Although this section illustrates the power of the ILcyLayerStyle
and ILcyLayerStyleProvider
concepts, there is no need to use these concepts. You can achieve all the functionality provided by these interfaces and
their implementations by working directly against the Lightspeed API. This will have no effect on how Lucy behaves. If you
are integrating an existing Lightspeed application that already has a similar mechanism into Lucy, for example, there is no
need to convert this mechanism to start using ILcyLayerStyle
and ILcyLayerStyleProvider
. If you do want to use these concepts and integrate them in your own formats, see Integrate styling in your own format. That section discusses the best approach to provide style support in your format, and what functionality is already available.
Saving/loading a layer style
You can make layer styles persistent with ILcyLayerStyleCodec
instances. Such a codec has the necessary methods to encode and decode a layer style to/from a stream. Possible use cases
are saving the style of a layer to disk and load it afterwards, or encoding/decoding the layer settings in/from a workspace.
This is illustrated in Program: Loading a layer style and applying it to an existing layer..
samples/lucy/lightspeed/style/loading/LoadStyleAction
)
private void loadStyle(ILspLayer aLayer) throws IOException {
TLcyCompositeLayerStyleProvider layerStyleProvider = new TLcyCompositeLayerStyleProvider(fLucyEnv);
if (!layerStyleProvider.canGetStyle(aLayer)) {
return;
}
ILcyLayerStyle style = layerStyleProvider.getStyle(aLayer);
TLcyCompositeLayerStyleCodec layerStyleCodec = new TLcyCompositeLayerStyleCodec(fLucyEnv);
if (layerStyleCodec.canDecode(aLayer, style)) {
try (InputStream inputStream = fInputStreamFactory.createInputStream(fStyleFile)) {
layerStyleCodec.decode(aLayer, style, inputStream);
}
}
}
Currently this functionality only works for Lightspeed layers. The lucy.style
sample illustrates how to achieve similar functionality with GXY layers.
Integrate styling in your own format
Lucy provides support for integrating styling in your own format when the layers of your format can use a ILspCustomizableStyler
. Such a styler uses the same set of styles as the base to determine the styling of each object, so it is suited for models/layers
where the same set of styles is applicable for all the objects, for example. In this case, it is recommended to extend from
ALcyLspStyleFormat
when you are implementing your own format.
This format provides an ILcyLayerStyleProvider
, saves the layer style in the workspace, and registers a ILcyLayerStyleCodec
for the layers of your formats.
The only thing left to the user of this class is to provide a layer factory by implementing the ALcyLspStyleFormat#createLayerFactoryImpl
method.
An example implementation of this method is shown in Program: Example implementation of the ALcyLspStyleFormat#createLayerFactoryImpl
method..
ALcyLspStyleFormat#createLayerFactoryImpl
method. (from samples/lucy/lightspeed/style/newiconstyle/SHPFormat
)
@Override
protected ILspLayerFactory createLayerFactoryImpl() {
return new ALspSingleLayerFactory() {
@Override
public ILspLayer createLayer(ILcdModel aModel) {
//create a customizable styler containing one icon style
TLspIconStyle iconStyle = TLspIconStyle.newBuilder().icon(new TLcdImageIcon(CustomizableIconStyleCustomizerPanel.ICON_SOURCE_NAMES.get(0))).build();
TLspCustomizableStyler styler = new TLspCustomizableStyler(iconStyle);
//use the TLspShapeLayerBuilder to construct the layer
TLspShapeLayerBuilder builder = TLspShapeLayerBuilder.newBuilder();
builder.model(aModel).bodyStyler(TLspPaintState.REGULAR, styler);
//apply the application wide selection style when available
ALcyLspStyleRepository styleRepository = ALcyLspStyleRepository.getInstance(getLucyEnv());
ILspStyler decoratedStyler = styleRepository.createSelectionStyler(styler);
builder.bodyStyler(TLspPaintState.SELECTED, decoratedStyler);
//make the layer selectable by default
builder.selectable(true);
return builder.build();
}
@Override
public boolean canCreateLayers(ILcdModel aModel) {
//the format is wrapped with a TLcyLspSafeGuardFormatWrapper so we can just return true
return true;
}
};
}
There are two extensions of the ALcyLspStyleFormat
classes that already contain a layer factory: TLcyLspVectorFormat
and TLcyLspRasterFormat
. These extensions are suitable for simple vector data and raster formats respectively. They both feature a customizeLayer
method that allows you to make minor changes to the created layers, such as changing the label or the icon. Consult the Javadoc
of these classes to determine which one is the most suited for your format.
If your format cannot use ILspCustomizableStyler
instances, it is recommended to extend from ALcyLspStyleFileFormat
, ALcyLspGeneralFormat
or ALcyLspFormat
and provide the style support yourself.
Use |
Integrate SLD styling in your own format
The automatic pick-up functionality for SLD files located next to source data of the model is provided by the ALcyLspStyleFormat
class.
Extensions of this class, such as TLcyLspVectorFormat
, are recommended to provide a layer factory that applies SLD styling when available.
An example of such a layer factory is available in the Javadoc of the ALcyLspStyleFormat.createLayerFactoryImpl
method.
Application-wide selection style
A common use case consists of having distinct styling for selected objects and unselected objects. Lucy supports storing the selection style settings in one location, and lets you access this location. This allows you to offer consistent styling for selected objects for all layers in the whole application, instead of on a per-layer basis. The default Lucy layer factories make sure that newly created layers respect these selection style settings.
Those settings are stored in the ALcyLspStyleRepository
container. By default, this style repository is registered as a service with the back-end by the TLcyLspStyleRepositoryAddOn
. The style repository add-on also provides a UI for changing the selection style, and workspace support for storing those
settings. See the Lucy user’s guide for more information about the selection style UI.
Adjust the application-wide selection style
At runtime, you can change the application-wide selection style through the UI and the API. In the UI, the action is located
under the Map | Style settings…​ menu item. To change the style through the API, you can adjust the ALcyProperties
of the TLcyLspStyleRepositoryAddOn
, as shown below:
samples/lucy/lightspeed/style/loading/LoadStyleAction
)
private void updateSelectionStyleColors() {
TLcyLspStyleRepositoryAddOn styleRepositoryAddOn = fLucyEnv.retrieveAddOnByClass(TLcyLspStyleRepositoryAddOn.class);
if (styleRepositoryAddOn != null) {
ALcyProperties preferences = styleRepositoryAddOn.getPreferences();
preferences.putColor("TLcyLspStyleRepositoryAddOn.selection.fillColor", fSelectionFillColor);
preferences.putColor("TLcyLspStyleRepositoryAddOn.selection.lineColor", fSelectionLineColor);
preferences.putColor("TLcyLspStyleRepositoryAddOn.selection.textColor", fSelectionTextColor);
}
}
You can adjust the default selection colors in the TLcyLspStyleRepositoryAddOn.cfg
configuration file of the TLcyLspStyleRepositoryAddOn
.
Integrate the application-wide selection style into your own format
To integrate new formats with the application-wide selection mechanism, decorate the ILspStyler
for the regular style, and use the decorated version as ILspStyler
for the selected objects. Program: Decorating the styler to use the application-wide selection style colors shows you how.
samples/lucy/lightspeed/style/newiconstyle/SHPFormat
)
//apply the application wide selection style when available
ALcyLspStyleRepository styleRepository = ALcyLspStyleRepository.getInstance(getLucyEnv());
ILspStyler decoratedStyler = styleRepository.createSelectionStyler(styler);
builder.bodyStyler(TLspPaintState.SELECTED, decoratedStyler);
The layers of this new format will then pick up any change made to the application-wide selection style.
Application-wide elevation color mapping
When you are representing elevation data, each elevation is typically visualized with a certain color. If you are visualizing multiple elevation data sets, it is recommended to apply the same elevation-to-color mapping to all elevation data sets. Mixing distinct color mappings makes no sense as it would become impossible for the user to learn which color corresponds to which elevation value.
Lucy allows storing such an elevation-value-to-color mapping in one location, and lets you access this location. This allows you to offer consistent styling for all elevation layers in the whole application. The default Lucy elevation layer factories ensure that newly created layers respect this color mapping.
The color mapping is stored in the ALcyLspStyleRepository
container. By default, this style repository is registered as a service with the back-end by the TLcyLspStyleRepositoryAddOn
.
The style repository add-on also provides a UI for changing the elevation color mapping, and workspace support for storing
those settings. See the Lucy user’s guide for more information about the color mapping UI.
Adjust the application-wide elevation color mapping
At runtime, you can change the application-wide elevation color mapping through the UI and the API.
In the UI, the action is located under the Map | Style settings…​ menu item.
To change the color mapping through the API, call the ALcyLspStyleRepository.setElevationColorMap
method:
ALcyLspStyleRepository repo = lucyEnv.getService( ALcyLspStyleRepository.class );
TLcdColorMap customColorMap = ... ;
repo.setElevationColorMap( customColorMap );
You can adjust the default color mapping in the configuration file TLcyLspStyleRepositoryAddOn.cfg
of the TLcyLspStyleRepositoryAddOn
.
Integrate application-wide elevation color mapping into your own format
To integrate new formats with the application-wide elevation color mapping mechanism, use an elevation styler created by the style repository. This is illustrated below:
ALcyLspStyleRepository repo = lucyEnv.getService( ALcyLspStyleRepository.class );
ILcdModel elevationModel = ...
ILspStyler elevationStyler = repo.createElevationStyler( elevationModel );
The layers of this new format will then pick up any change made to the application-wide elevation color mapping.