Desktop Java

OpenMap Tutorial Part 2 – Build a basic map application using the MapHandler – Part 1

1. Introduction

In the first tutorial we created a basic OpenMap GIS application that displays a map with one shape layer, loaded from the filesystem, inside a JFrame. That tutorial was based on com.bbn.openmap.app.example.SimpleMap. We used the following OpenMap classes in that tutorial: MapBean, PropertyHandler, ShapeLayer, com.bbn.openmap.util.SwingWorker.

We added a MapBean to a JFrame. However, OpenMap provides its own JFrame, OpenMapFrame, which can hold a MapPanel. The MapPanel is an interface (see Figure 1) describing a component that contains a MapBean, MapHandler, menu widgets and all the other components connected to make an OpenMap map widget. A MapPanel is a self-contained OpenMap Swing component. It is expected that the MapPanel will extend from java.awt.Container, otherwise it might not be automatically added to the OpenMapFrame if it is found in the MapHandler (we talk about MapHandler later in this article).

The com.bbn.openmap.BufferedMapBean extends the MapBean by forcing its layers to paint their map features into a buffered image. This drawing buffer is then rendered whenever the Java AWT thread is called to paint the Layers. This dramatically increases performance since it avoids the (potentially expensive) Layer painting process. If a layer requests to be painted, then the drawing buffer is regenerated by the Layers and painted into the map window.

The com.bbn.openmap.BufferedLayerMapBean extends the BufferedMapBean with a special internal image buffer that holds all layers that have been designated as ‘background’ layers. This buffer is especially useful when some layers are animating moving map features and the map is getting repainted often. Using a separate buffered image for background layers greatly reduces the amount of time and work needed to render the map, increasing the rate at which the map can be updated. By default, the OpenMap application uses the BufferedLayerMapBean instead of the MapBean due to this increased performance.

2. OpenMapFrame and MapPanel

Let’s see how we can modify our MapFrame from the previous tutorial to take advantage of the above OpenMap classes:

  1. Modify MapFrame so that it extends OpenMapFrame instead of javax.swing.JFrame.
  2. Fix imports (Ctrl+I). Optionally, you may execute the application to make sure that it runs as before.
  3. Switch to the Design view by clicking on the Design button.
  4. Select and delete the MapBean instance (from the Navigate window).
  5. Drag a BasicMapPanel onto the OpenMapFrame from the OpenMap palette group.
  6. Rename it to mapPanel.
  7. Change its layout direction to Center from the Properties window.
  8. Back to Source view, modify the line mapBean.add(shapeLayer); to mapPanel.getMapBean().add(shapeLayer);.

The result is shown in Listing 1 (assuming that you chose the last implementation from Tutorial 1).

As an exercise, replace BasicMapPanel with OverlayMapPanel in step 5 above. The various MapPanels (see Figure 1) contain a BufferedLayerMapBean so you don’t need to do anything else to increase performance.

Listing 1: MapFrame Basic OpenMap application

public class MapFrame extends OpenMapFrame {

   /** Creates new form MapFrame */
   public MapFrame() {
      super("Simple Map");
      initComponents();
      initMap();
   }

  @SuppressWarnings("unchecked")                         
   private void initComponents() {
      mapPanel = new com.bbn.openmap.gui.BasicMapPanel();
      setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
      getContentPane().add(mapPanel, java.awt.BorderLayout.PAGE_END);

      pack();
   }  

   /** @param args the command line arguments */
   public static void main(String args[]) {
      
      /* Create and display the form */
      java.awt.EventQueue.invokeLater(
          () -> new MapFrame().setVisible(true)
      );
   }

   // Variables declaration - do not modify                     
   private com.bbn.openmap.gui.BasicMapPanel mapPanel;
   // End of variables declaration  

   private void initMap() {
      CompletableFuture.supplyAsync(() -> getShapeLayer())
           .thenAcceptAsync(
                shapeLayer -> {
                   // Add the political layer to the map
                   mapPanel.getMapBean().add(shapeLayer);
                   MapFrame.this.revalidate();
      });
   }
   // ...
}

Figure 1: OpenMap's main classes class diagram
Figure 1: OpenMap’s main classes class diagram

3. MapHandler

The MapHandler is a java.beans.beancontext.BeanContext which can be thought of as a big bucket that can have objects added to or removed from it. The benefit of having a BeanContext object as the center of the architecture is that it sends events to listeners when its object membership changes. Any java.beans.beancontext.BeanContextMembershipListener added to a BeanContext will receive these events, and can use the events to set up or serve connections with the objects being added or removed.

The MapHandler can be thought of as a map, complete with the MapBean, Layers, and other management components that are contained within it. It can be used by those components that need to get a handle to other objects and services. It can be used to add or remove components to the application, at run-time, and all the other objects added to the MapHandler get notified of the addition/removal automatically.

Let’s see how we could take advantage of the MapHandler. The modified initMap() is shown in Listing 2. As already mentioned, one can think of MapHandler as a big bucket where objects can be added to or removed from it. We get the MapHandler from the MapPanel. To be able to add layers to it, we need to add a LayerHandler to the MapHandler. We add the shapeLayer as well as a GraticuleLayer to it. The order is important, i.e. the layer added last is the one appearing on top. Finally, we need to add the OpenMapFrame to the MapHandler. The MapHandler is the substance that glues all these together. Check com.bbn.openmap.app.example.SimpleMap2, too.

The reason that the MapHandler exists, as opposed to simply using the BeanContext, is that it is an extended BeanContext that keeps track of SoloMapComponents (com.bbn.openmap.SoloMapComponents). SoloMapComponent is an interface that can be used on an object to indicate that there is only one instance of that component type in the BeanContext at a time. For instance, the MapBean is a SoloMapComponent, and there can be only one MapBean in a MapHandler at a time (a Highlander!). A MapHandler has a com.bbn.openmap.SoloMapComponentPolicy that tells it what to do if it gets into a situation where duplicate instances of SoloMapComponents are added. Depending on the policy, the MapHandler will reject the second instance of the SoloMapComponent (com.bbn.openmap.SoloMapComponentRejectPolicy) or replace the previous component (com.bbn.openmap.SoloMapComponentReplacePolicy).

Listing 2: initMap() using MapHandler

private void initMap() {
   try {
      // Get the default MapHandler the BasicMapPanel created.
      MapHandler mapHandler = mapPanel.getMapHandler();
      // Set the map's center
      mapPanel.getMapBean().setCenter(new LatLonPoint.Double(38.0, 24.5));
      // Set the map's scale 1:120 million
      mapPanel.getMapBean().setScale(120000000f);
      /*
       * Create and add a LayerHandler to the MapHandler. The LayerHandler
       * manages Layers, whether they are part of the map or not.
       * layer.setVisible(true) will add it to the map. The LayerHandler
       * has methods to do this, too. The LayerHandler will find the
       * MapBean in the MapHandler.
       */
       mapHandler.add(new LayerHandler());

       CompletableFuture.supplyAsync(() -> getShapeLayer())
          .thenAcceptAsync(
             shapeLayer -> {
                  // Add the political layer to the map
                  mapHandler.add(shapeLayer);
                  mapHandler.add(new GraticuleLayer());
                  MapFrame.this.revalidate();
             });
         // Add the map to the frame
         mapHandler.add(this);
    } catch (MultipleSoloMapComponentException msmce) {
         // The MapHandler is only allowed to have one of certain
         // items. These items implement the SoloMapComponent
         // interface. The MapHandler can have a policy that
         // determines what to do when duplicate instances of the
         // same type of object are added - replace or ignore.

         // In this example, this will never happen, since we are
         // controlling that one MapBean, LayerHandler,
         // MouseDelegator, etc is being added to the MapHandler.
      }
}

However, many things are missing from this basic application. E.g. no actions on the map, like zoom in/out, pan etc., can be performed. But they can be easily added with the help of the MapHandler. Simply add the relevant handlers to the MapHandler inside the try-catch block of initMap() as shown in Listing 3. You can now zoom in/out with the middle mouse wheel and pan the map with the left mouse button.

Listing 3: initMap() with the addition of mouse events

private void initMap() {
   //...
   // Add MouseDelegator, which handles mouse modes (managing mouse
   // events)
   mapHandler.add(new MouseDelegator());     
   // Add OMMouseMode, which handles how the map reacts to mouse
   // movements
   mapHandler.add(new OMMouseMode());
   //... 
}

So far we have seen how to use: MapBean, MapHandler, LayerHandler, PropertyHandler, ShapeLayer, GraticuleLayer, OpenMapFrame.

4. openmap.properties

However, OpenMap is even more flexible than that. With the help of BeanContext technology, we can define the components that consist our application in a properties file, openmap.properties. We have already created an openmap.properties in our application that contains properties for ESRI shape layers. The properties file can contain properties scoped for a particular component. Scoping is performed using a property prefix, so the properties can be defined as:

prefix.property=value

Let’s begin by prefixing our existing properties and see what modifications are required in our code.

Listing 4: openmap.properties

shapePolitical.prettyName=Political Solid
shapePolitical.lineColor=000000
shapePolitical.fillColor=BDDE83
shapePolitical.shapeFile=resources/map/shape/dcwpo-browse.shp
shapePolitical.spatialIndex=resources/map/shape/dcwpo-browse.ssx

PropertyHandler has support for prefixes as shown in bold in Listing 5. In the next tutorial we shall see how useful is property scoping. Since the next part will be quite extensive, better to break it here.

Listing 5: PropertyHandler with prefixes support

private ShapeLayer getShapeLayer() {
   PropertyHandler propertyHandler = null;
   try {
      propertyHandler = new PropertyHandler.Builder().setPropertiesFile("./openmap.properties").setPropertyPrefix("shapePolitical").build();
   } catch (IOException ex) {
      Logger.getLogger(MapFrame.class.getName()).log(Level.SEVERE, null, ex);
   }
   //ShapeLayer: 
   ShapeLayer shapeLayer = new ShapeLayer(); 
   if (propertyHandler != null) {
        shapeLayer.setProperties(propertyHandler.getPropertyPrefix(), propertyHandler.getProperties(propertyHandler.getPropertyPrefix()));
   }
   return shapeLayer;
}

5. Conclusion

In this tutorial we learned how to use: MapBean, MapHandler, LayerHandler, PropertyHandler, ShapeLayer, GraticuleLayer, OpenMapFrame. We also saw the flexibility provided by the openmap.properties file. New applications can be configured there without recompilation. In the next tutorial we shall see how to do this without the need to recompile our application.

References

  1. OpenMap Developer’s Guide
  2. OpenMap Developer Hints

Ioannis Kostaras

Software architect awarded the 2012 Duke's Choice Community Choice Award and co-organizing the hottest Java conference on earth, JCrete.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button