This article explains how to modify the functionality of the map add-on.

Customizing maps

This section describes how you can modify the layout of the map component using a GUI factory. This includes adding and removing items in the tool bar and menu bar, changing existing items and putting parts of the map, the tool bar for example, in a different location.

Note that the factory also allows you to customize more advanced, not directly GUI-related functionality, such as the layer ordering algorithm. See to the API reference of TLcyMapComponentFactory for more details.

Concept

The map add-on provides the TLcyMapComponentFactory class, an extension of ALcyMapComponentFactory. The map component is created by TLcyMapComponentFactory according to the principles of a GUI factory. This means that the GUI, a TLcyMapComponent in this case, is created in small, customizable parts. The TLcyMapComponentFactory has a number of method pairs, one to create a component, such as a toolbar or the view, and one to insert this component in the TLcyMapComponent. These are protected methods to allow extended classes to override the default creation or insertion behavior. Overriding creation behavior allows you to change, replace or remove the default component, while overriding insertion behavior allows you to change the layout of the component. These components are customizable in TLcyMapComponentFactory using the protected methods:

  • Tool bar: there is a ILcyToolBar createToolBar( TLcyMapComponent ) method and a void setToolBar( ILcyToolBar, TLcyMapComponent ) method. The first method serves the purpose of creating the toolbar, the second method is responsible for inserting the toolbar into the map component.

  • Menu bar: createMenuBar and setMenuBar handle the menu. Note that the items of the menu bar of the map are merged into the main menu by default.

  • Popup menu: createPopupMenu and setPopupMenu take care of the popup menu. The popup menu is displayed by default if the select controller is active and the user right-clicks on the map.

  • GXY view: The ILcdGXYView is handled by createGXYView and setGXYView.

  • Controllers: use a generic createGXYController and insertGXYController method pair with an extra ID parameter. The ID describes which controller it is, for example the zoom controller or the pan controller. All possible IDs that can be passed to the createGXYController and insertGXYController methods, end with the same suffix CONTROLLER.

  • Actions: createAction and insertAction take care of the actions. The given ID is one of the constants ending in ACTION.

  • Active settables: createActiveSettable and insertActiveSettable use a constant ending in ACTIVE_SETTABLE as their ID.

  • Layers: createGXYLayer and insertGXYLayer take care of the grid and area-of-interest layers for example. The ID has to end in LAYER.

  • Other components: use a generic createComponent and insertComponent method pair with an extra ID parameter. The ID describes which component it is, for example the map scale label or the layer control. All possible IDs that can be passed to the createComponent / insertComponent methods, end in the suffix COMPONENT. The layer control is special in that the actual creation is delegated to a TLcyMapLayerControlFactory (can be set using setMapLayerControlFactory), and the same GUI factory principle applies to the customization of the parts of the layer control.

For example, imagine that you want to change the behavior of the edit-current-projection action. The Javadoc of TLcyMapComponentFactory shows that there is an EDIT_CURRENT_PROJECTION_ACTION constant. Because it ends in ACTION, we can pass this constant to the createAction method only. Therefore, we can override the method createAction, check if the ID is EDIT_CURRENT_PROJECTION_ACTION and return a custom action instance. We could also use super.createAction and change the icon before returning it. If you want to remove the action completely, simply return null.

Example

All this is illustrated in the GUI factory sample, in samples/lucy/gxy/custommapui.

The add-ons file of this sample, addons_gui_factory_sample.xml, shows that the map add-on is configured through a custom configuration file, namely lucy_gui_factory_sample_map_addon.cfg. In this configuration file, the property mapComponentFactoryName is set to samples.lucy.gxy.custommapui.MapComponentFactory to make this factory create the map components instead of the default one. The MapComponentFactory is an extension of TLcyMapComponentFactory that changes the icon of the file open action, removes the ruler controller, and puts the tool bar below the map. The scale combo box is no longer inside the tool bar, but on the right side.

To change the icon of the open action, you must identify where this action is created.

You can also configure the icon by setting TLcyMapAddOn.openFileAction.menuBar.smallIcon=someIcon.png in map_addon.cfg. However, this example shows how you can adapt the actual action instance, which offers far more flexibility than just changing an icon.

The Javadoc of TLcyMapComponent shows that there is an OPEN_FILE_ACTION constant. This constant ends in ACTION, so it is being used by the createAction method. That means that you need to re-define the createAction method to modify the behavior. This is shown in Program: This snippet shows how to modify the open action.. The ID is used to decide if the action currently being created is the open action, or some other action. If some other action is created, the behavior of the method of the superclass is left untouched. If the action being created is the open action, the icon is modified by using action.putValue( ILcdAction.SMALL_ICON, new_icon ) on the action instance.

Program: This snippet shows how to modify the open action. (from samples/lucy/gxy/custommapui/MapComponentFactory)
@Override
protected ILcdAction createAction(int aID, TLcyMapComponent aMapComponent) {
  ILcdAction action = super.createAction(aID, aMapComponent);

  //Modify the icon of the open action
  if (aID == OPEN_FILE_ACTION && action != null) {
    ILcdIcon new_icon = TLcdIconFactory.create(TLcdIconFactory.GLOBE_ICON);
    action.putValue(ILcdAction.SMALL_ICON, new_icon);
  }

  return action;
}

Removing the ruler controller is similar.

To remove the ruler, you can also configure the properties TLcyMapAddOn.rulerControllerActiveSettable.menuBar.insert=false and TLcyMapAddOn.rulerControllerActiveSettable.toolBar.insert=false`in `map_addon.cfg.

First, the constant RULER_CONTROLLER needs to be identified, which leads us to the createGXYController method. Using the ID to detect when the ruler controller needs to be created, we return null instead of the ruler controller. This is demonstrated in Program: This snippet shows how to remove the ruler controller..

Program: This snippet shows how to remove the ruler controller (from samples/lucy/gxy/custommapui/MapComponentFactory)
@Override
protected ILcdGXYController createGXYController(
    int aID,
    TLcyMapComponent tLcyMapComponent) {

  //Remove the ruler controller
  if (aID == RULER_CONTROLLER) {
    return null;
  }
  //leave all other controllers untouched
  else {
    return super.createGXYController(aID, tLcyMapComponent);
  }
}

The tool bar is created in the createToolBar method and installed in the setToolBar method. To move the tool bar, you do not need to modify it, so the createToolBar method is left untouched. The setToolBar method is re-defined to insert the tool bar at a different location. See Program: This code fragment shows how to move the toolbar to a different location. Note that it is also necessary to call setToolBar on the TLcyMapComponent itself because it gives the TLcyMapComponent a reference to the toolbar instance it is using. In addition, the toolbar must be inserted into the GUI of the TLcyMapComponent, using Swing.

Program: This code fragment shows how to move the toolbar to a different location (from samples/lucy/gxy/custommapui/MapComponentFactory)
@Override
protected void setToolBar(ILcyToolBar aToolBar, TLcyMapComponent aMapComponentSFCT) {
  //tell the map component about the toolbar it should use
  aMapComponentSFCT.setToolBar(aToolBar);
  aMapComponentSFCT.setToolBarComponent(aToolBar.getComponent());

  //add the toolbar (using swing) in a different location
  Container south_content_pane = aMapComponentSFCT.getSouthPanel();
  if (south_content_pane != null) {
    south_content_pane.add(aToolBar.getComponent(), BorderLayout.WEST);
  }
}
Program: This code fragment shows how to move the scale label to a different location (from samples/lucy/gxy/custommapui/MapComponentFactory)
@Override
protected void insertComponent(int aID, Component aComponent, TLcyMapComponent aMapComponentSFCT) {
  if (aID == MAP_SCALE_LABEL_COMPONENT) {
    //insert the map scale label at a different place, using swing
    if (aComponent != null) {
      fRightToolBar.insertComponent(aComponent, new TLcyGroupDescriptor("ScaleGroup"));
    }
  } else {
    super.insertComponent(aID, aComponent, aMapComponentSFCT);
  }
}

After moving the tool bar below the map, you will notice that the scale label is still inside the toolbar. To move the scale label to the far right of the tool bar, the scale label is inserted into the right tool bar instead of in the regular tool bar. This is illustrated in Program: This code fragment shows how to move the scale label to a different location.. The right tool bar is inserted underneath the map, similar to the regular toolbar. This is demonstrated in Program: This code fragment shows how to move the right toolbar to a different location..

Program: This code fragment shows how to move the right tool bar to a different location (from samples/lucy/gxy/custommapui/MapComponentFactory)
@Override
protected void setRightToolBar(ILcyToolBar aRightToolBar, TLcyMapComponent aMapComponentSFCT) {
  //add the toolbar (using swing) in a different location
  Container south_content_pane = aMapComponentSFCT.getSouthPanel();
  if (south_content_pane != null) {
    south_content_pane.add(aRightToolBar.getComponent(), BorderLayout.EAST);
  }
}

Extending opening and saving of files

The File → Open action opens a file by passing it to the TLcyDataFormatManager.

The File→ Save action saves the ILcdModel of an ILcdGXYLayer of choice. The ILcdModelEncoder of the ILcdModel is used for this purpose. See ILcdModel.getModelEncoder(). So if custom ILcdModel instances need to be saved, they must have an associated ILcdModelEncoder.

The File→ Save As action saves the ILcdModel of an ILcdGXYLayer of choice, but it uses ILcyLucyEnv.getDataFormatManager().getCompositeModelEncoder().canExport( model, destinationFileName ) to find out if a model can be saved. So if custom ILcdModel instances need to be saved, the appropriate ILcdModelEncoder instances must be registered using ILcyLucyEnv.getDataFormatManager().addModelEncoder( encoder, fileType ).

Snapping

The LuciadLightspeed API provides quite an extensive mechanism for snapping. Part of this mechanism entails that you need to add your domain objects to a 'snappables' list that can be used by the editing controllers, so that these objects can be evaluated as snap target candidates. Lucy provides a mechanism to automatically maintain such a list per map component that contains the visible and relevant domain objects of the model in the visible layers. If you want to make use of this mechanism, your layer should implement the ILcySnappable interface. The best practices sample contains an implementation of such a layer. When a layer implements this interface and the isSnappingOn method returns true, all its relevant objects are added to the snappables list maintained by Lucy. This list can be retrieved using the method getSnapList of the ILcyMapComponent. Amongst others, the Lucy edit controller makes use of this list. If you implement your own controller and it needs a snap list, you can use this list.

Note that this mechanism just adds the objects in your layers to this central snap list. This mechanism does not take care of deriving an actual snap target from one of these objects, nor does it provide the visual indication of the accepted snap targets. This support needs to be included in the painter. Most painters in LuciadLightspeed support this mechanism. See Implementing a painter and editor in a GXY view for more information about writing custom painters that support snapping.

Workspace considerations

Required object codecs

The map add-on stores the existing map components and the layers that are added to those maps. As such, it needs to encode references to map components and GXY layers. The object codecs that can save references to layers are specific to the data format and are therefore added by the data add-ons that come with Lucy. When you write an add-on that adds support for a custom format, you must write your own implementation of ALcyWorkspaceObjectCodec that can encode the state of a layer.

The map add-on itself adds an object codec that is able to encode the state of the default map components, but if you have written your own map component factory that returns another implementation, you need to provide a custom object codec for that.

Registered object codecs

The map add-on adds object codecs to the Lucy back-end for the following objects:

  • Map components

  • Views

  • Layer controls

The object codec for map components can encode references to instances of TLcyMapComponent, the default implementation of ILcyMapComponent.

The object codec for views can encode references to instances of ILcdGXYView that represent the main GXY view of a map component. Use ILcyMapComponent.getMainGXYView() to get the main GXY view. That map must be encodable by the map component codec mentioned above.

The object codec for layer controls can encode the default layer controls for map components that are present in the map manager. This means that if you write and use a custom layer control factory that returns an implementation of ILcyMapLayerControl other than the default implementation (TLcyMapLayerControl) and wish other add-ons to be able to reference this layer control in their workspace, you should provide an object codec that can store the state of your layer control.

If other classes are created in a custom ALcyMapComponentFactory, appropriate workspace codecs need to be registered for these. If not, the state of a map cannot be correctly saved or restored.

Asynchronous behavior considerations

If we assume that the asynchronous behavior is enabled so that painting a slow or heavy layer does not block the user interface, the default controllers created by the TLcyMapComponentFactory continuously repaint the view when they are used. . However, if the asynchronous behavior is disabled, the continuous repainting could be detrimental to the user experience. Therefore, you can configure the controllers so that they do not re-paint continuously:

  • Pan controller: set the configuration option TLcyMapAddOn.panController.continuousRepaint to false in the configuration file of the map add-on to disable the continuous repainting. When the view is dragged with the pan controller, the current image of the view will be re-positioned, and the view will only be re-painted when the user releases the mouse button.

  • Zoom controller: there are two zoom controllers available: the regular and the continuous zoom controller. The regular zoom controller lets the user specify the rectangle to which he wants to zoom, and performs no continuous re-painting. The continuous zoom controller zooms instantly in response to drag gestures by the user. By default, only the continuous controller is visible in the user interface. You can change which zoom controllers are visible by modifying the configuration of the menu item and tool bar item. The configuration options for the regular zoom controller start with TLcyMapAddOn.zoomControllerActiveSettable.. Those for the continuous zoom controller start with TLcyMapAddOn.continuousZoomControllerActiveSettable.

  • Navigate controller: by default, when the user drags on the map with the middle mouse button, the view is continuously re-painted. Changing the configuration option TLcyMapAddOn.navigateController.continuousRepaint to false will disable the continuous re-painting. As with the regular pan controller, the view is only re-painted when the user releases the mouse and not for every drag event.

Program: With this configuration file, the controllers of the map add-on will be configured for synchronous painting is a sample configuration file that configures the controllers of the map add-on for cases in which the asynchronous behavior is disabled.

Program: With this configuration file, the controllers of the map add-on will be configured for synchronous painting.
includeConfig=lucy/map/map_addon.cfg

# Disable the continuous repaint of the pan controller. The view will only be
# repainted when the user releases the mouse button.
TLcyMapAddOn.panController.continuousRepaint=false

# Disable the continuous repaint of the navigate controller. The view will only be
# repainted when the user releases the middle mouse button.
TLcyMapAddOn.navigateController.continuousRepaint=true

# Insert the regular zoom controller. With this zoom controller, the user
# specifies the region to where he wants to zoom.
TLcyMapAddOn.zoomControllerActiveSettable.toolBar.insert=true

# Remove the continuous zoom controller from the user interface.
TLcyMapAddOn.continuousZoomControllerActiveSettable.toolBar.insert=false