Developer Hints, a Quick-Start Introduction

The OpenMap Developer's Guide is an in-depth resource for how OpenMap components work. This page is a summary of the high points, to give you a quick idea of what's what.

Consider OMGraphics...

OMGraphics are important, because they are how you represent data as objects on a map. OMGraphics come in different flavors - OMBitmap, OMCircle, OMLine, OMPoint, OMPoly, OMRaster, OMRect, OMText. An OMGraphicList can be used to contain OMGraphics, and is also an OMGraphic itself. This allows you to nest OMGraphicLists.

When considering how to represent your data as OMGraphics, there are a couple of things to think about:

You can create customized OMGraphics by combining standard OMGraphics into a new object that contains an OMGraphicList, or extend OMGraphicList to contain the OMGraphics you need, adding methods you require. The com.bbn.openmap.layer.location.Location object is an example of creating an object to contain OMGraphics, combining a generic OMGraphic marking a location, and a OMText object for the location label.

OMGraphics contain an attribute map that can contain any other objects you need to associate with the OMGraphic. You can access these attributes using the putAttribute(key, value) and getAttribute(key, value) methods. This can be really handy to use to store more information about the data you are representing with that OMGraphic (web page addresses, additional attributes, etc).

OMGraphics can be rendered in three ways, which are represented by renderType.

  • RENDERTYPE_LATLON means that the object should be placed on the map in its lat/lon location. You should expect the OMGraphic to scale the object as the map scale changes, and the object's location on the screen will change as the map location changes.
  • RENDERTYPE_XY means the object should be placed at a screen pixel location on the map. The OMGraphic does not move or scale as the map projection changes.
  • RENDERTYPE_OFFSET means the object should be placed at some screen pixel location offset from a lat/lon coordinate. The object will move with the map location changes, but will not scale if the map scale changes.
There are three different line types associated with OMGraphics that have lines rendered in lat/lon space. These setting do not affect OMGraphics with RENDERTYPE_XY or RENDERTYPE_OFFSET:
  • LINETYPE_STRAIGHT means the lines will be straight on the screen between points of the OMGraphic, regardless of the projection type.
  • LINETYPE_GREATCIRCLE means that the lines drawn between points of the OMGraphic will be the shortest geographical distance between those points.
  • LINETYPE_RHUMB means that the line drawn between points of the OMGraphics will be of constant bearing, or going in the same direction.
Most importantly, there is a paradigm you have to work in with OMGraphics. Once an OMGraphic is created, it MUST be projected, which means that its representation, in relation to the map, needs to be calculated. This is done by taking the Projection object that arrives in the ProjectionChanged event, and using it on the OMGraphic.generate(Projection) method. If the projection changes, the OMGraphics will need to be generated(). If the position of the OMGraphic has changed, or certain attributes of the OMGraphic are changed, the OMGraphics needs to be generated. The OMGraphics are smart enough to know when a attribute change requires a generation, so go ahead an call it, and the OMGraphic will decide to do the work or not. If you try to render an OMGraphic that has not been generated, it will not appear on the map. After the OMGraphic is generated, the java.awt.Graphics object that arrives in the Layer.paint() method can be passed to the OMGraphic.render(java.awt.Graphics) method. See the layer section below for more information about managing the Projection object for use with your OMGraphics.

and Layers...

Layers can do anything they want to in order to render their data on the map. When a layer is added to a map, it becomes a Java Swing component, so its rendering in relation to other layers on the map is taken care of automatically.

When a layer is added to the MapBean, it automatically gets added as a ProjectionListener to the MapBean. That means that when the map changes, the layer will receive a ProjectionChanged event, letting it know what the new map projection looks like. It's then up to the layer to decide what it wants to draw on the screen based on that projection, and then call repaint() on itself when it is ready to have its paint() method called. The Java AWT event thread will then call paint() on the layer at the proper time. paint() can also be called automatically by the AWT thread, for map window repaints and when another layer asks to be repainted. paint() methods should not do more than simply render graphics that are currently on the map, in order to take up as little time as necessary with that AWT thread.

The OMGraphicHandlerLayer is a super-class implementation of Layer that does a lot of work for you. You should extend any layer you write from this class, and simply override the prepare() method to create and return OMGraphics to display on the map. The prepare() method is called whenever the projection changes (pan, zoom, window resize). The current projection can be retrieved in the prepare method by calling getProjection(), and this can be used to call generate(projection) on your OMGraphics before they are returned from this method. You must do this. Look at the com.bbn.openmap.layer.learn package to see best practices of creating layers to do different things. The OMGraphicHandlerLayers in that package are well commented, and each demonstrate a certain aspect of managing and interacting with OMGraphics.

The OMGraphicHandlerLayer automatically launches a separate thread when it calls prepare(), and automatically calls repaint() on itself when prepare() returns. If you want to force a new thread to be created to call prepare() on the layer, call doPrepare().

You can also use the OpenMap layers as examples of different ways to create and manage OMGraphics. The GraticuleLayer creates its OMGraphics internally, while the ShapeLayer reads data from a file. The DTED and RpfLayers have image caches. The CSVLocationLayer uses a quadtree to store OMGraphics. You can also access a spatial database to create OMGraphics. Any technique of managing graphics can be used within a layer.

The LayerHandler object is used in the OpenMap application to manage layers - both those visible on the map, and those available for the map. The LayerHandler uses the Layer.isVisible() attribute to decide which layers are active on the map. It has methods to change the visibility of layers, add layers, remove layers, and change their order. It does not have a user interface, so it can be used with any application.

For the OpenMap application, layers are added or removed by modifying the openmap.properties file. The property file contains instructions on how to do this. For OpenMap layers, their unique properties that can be set to initialize them should be listed in the layer's JavaDocs.

The Layer.getGUI() method provides a way for a layer to create its user interface which can control its attributes. The getGUI() method should just return a java.awt.Component, which means you can customize it any way you want. The parent Layer class returns null by default if you decide not to provide a GUI.

and Mouse Events....

MouseEvents can be managed by certain OpenMap components, directing them to layers and to OMGraphics. MouseModes describe how MouseEvents and MouseMotionEvents are interpreted and consumed.

The MouseDelegator is the real MouseListener and MouseMotionListener on the MapBean. The MouseDelegator manages a list of MouseModes, and knows which one is 'active' at any given time. The MouseDelegator also asks the active Layers for their MapMouseListeners, and adds the ones that are interested in events from the active MouseMode as listeners to that mode.

When a MouseEvent gets fired from the MapBean, it goes through the MouseDelegator to the active MouseMode, where the MouseMode starts providing the MouseEvent to its MapMouseListeners. Each listener is given the chance to consume the event. A MapMouseListener is free to act on an event and not consume it, so that it can continue to be passed on to other listeners.

From the Layer point of view, it has a method where it can be asked for its MapMouseListener. The Layer can implement the MapMouseListener interface, or it can delegate that responsibility to another object, or can just return null if it's not interested in receiving events (the Layer default). The MapMouseListener provides a String array of all the MouseMode ID strings it is interested in receiving events from, and also has its own methods that the MouseEvents and MouseMotionEvents arrive in. The MapMouseListener can use these events, combined with the OMGraphicList, to find out if events have occurred over any OMGraphics, and respond if necessary. Remember, if something on the layer changes as a result of an event, the layer can call repaint() on itself.

Once again, the OMGraphicHandlerLayer makes things easier for you by handling MouseEvents over its OMGraphics. Look at thecom.bbn.openmap.layer.learn.InteractionLayerfor instructions on how to take advantage of this functionality.

and the BeanContext, a.k.a. the MapHandler...

Understanding the MapHandler is one of the most important aspects of customizing an OpenMap application if you want to make the whole process pretty trivial.

The MapHandler is a Java BeanContext, which is a big bucket where you can add or remove objects. If an object is a BeanContextMembershipListener, it will receive events when other objects get added to or removed from the BeanContext.

The reason that the MapHandler (as opposed to simply using the BeanContext) exists is that it is an extended BeanContext that keeps track of SoloMapComponents. SoloMapComponent is an interface, and can be used to say that there is only supposed to be one instance of a component type in the BeanContext at a time. For instance, the MapBean is a SoloMapComponent, and there can only be one MapBean in a MapHandler at a time. The SoloMapComponentPolicy is an object that tells the MapHandler what to do if another MapBean (or other duplicate SMC instance) is added to the MapHandler, either rejecting the second instance of the MapBean, or replacing the previous MapBean.

So, a MapHandler can be thought of as a Map, complete with the MapBean, Layers, and other management components that are contained within.

That said, the MapHandler is incredibly useful. It can be used by objects that need to get a hold of other objects and services. It can be used to add or remove components to the application, at runtime, and all the other objects added to the MapHandler get notified of the addition/removal automatically.

In the OpenMap application, the openmap.properties file has an openmap.components property that lists all the components that make up the application. To change the components in the application, edit this list.

If you want your component to be told of the BeanContext, make it a BeanContextChild. It will get added to the MapHandler so that other components can find it, if it is on the openmap.components property list. If you are creating your own components programmatically, simply add the BeanContextChild component to the MapHandler yourself.

The com.bbn.openmap.MapHandlerChild is an abstract class that contains all the methods and fields necessary for an object to be a BeanContextChild and a BeanContextMembershipListener. If your object extends this class, you just have to implement the methods findAndInit() which is called whenever an object is added to the MapHandler, and childrenRemoved() which is called when objects are removed. You can use the Iterator that gets send to these methods to find other components that have been added to or removed from the application, and adjust your component accordingly. Make sure your component is stable if it doesn't find what it needs - you shouldn't assume that the other objects will be added in any particular order, or even added at all. Also, you should check that when objects are removed that the instance of the object is the same that is being used by your component before you disconnect from it (not just the same class type). As a MapHandlerChild, your component can be added to the OpenMap application without recompiling any OpenMap source code. You'll notice that the application class (com.bbn.openmap.app.OpenMap) is pretty basic, using the PropertyHandler to instantiate all the components and add them to the MapHandler.

and the PropertyConsumer interface...

The PropertyConsumer interface can be implemented by any component that wants to be able to configure itself with a java.awt.Properties object. It also has methods that let it provide information about the properties it can use, and what they mean.

In general, Properties are a set of key-value pairs, each defined as Java Strings. The com.bbn.openmap.layer.util.LayerUtils class has methods that can be used to translate the value Java Strings into Java primitives and objects, like ints, floats, booleans, Color, etc.

Several PropertyConsumers may have their properties defined in a single properties file, which is what happens when the OpenMap application uses the openmap.properties file. In order for each PropertyConsumer to be able to figure out which properties are intended for it, the PropertyConsumer can be given a unique scoping property prefix string. In the openmap.properties instructions, this scoping string is referred to as a marker name. If the property prefix is set in a PropertyConsumer, it should prepend that string to each property key, separating them with a period. For example a layer may have a property key called lineWidth, which tells it how thick to draw its line graphics. If it is given a property prefix of layer1, it should check its properties for a 'layer1.lineWidth' property. If the layer is given a null prefix (default), then it should look for a 'lineWidth' property.

The methods for the PropertyConsumer are:

  • setPropertyPrefix(String prefix) - set the scoping prefix.
  • setProperties(Properties props) - provide the properties, with a null prefix.
  • setProperties(String prefix, Properties props) - provide the properties with a prefix.
  • getProperties(Properties props) - set the current values of the properties in the Properties object provided. If Properties is null, create one to fill. The keys in this Properties object should be scoped with a prefix if one is set.
  • getPropertyInfo(Properties props) - set the metadata for the properties in the Properties object. Again, if Properties is null, create one and fill it. The keys in this Properties object should NOT be scoped, and the values for the keys should be a short explaination for what the property means. The PropertyConsumer may also provide a 'key.editor' property here with the value a fully qualified class name of the com.bbn.openmap.util.propertyEditor.PropertyEditor to use to modify the value in a GUI, if needed.

PropertyConsumers can use the com.bbn.openmap.util.propertyEditor.Inspector to provide an interface to the user to configure it at runtime. It also allows the PropertyConsumer to provide its current state for properties files being saved for later use.

Lastly, when the OpenMap application is creating objects from the openmap.components property, the marker name on that list becomes the property prefix for components. The ComponentFactory, which creates the components on behalf of the PropertyHandler, checks to see if the component is a PropertyConsumer, and if so it calls setProperties(prefix, properties) on it to let the component configure itself.

and hints to help with certain application Design Patterns...

The OpenMap application is really a framework. The application can be adjusted and components swapped in and out by modifying the openmap.components property to create the components you want. If you want to create your own application, it's likely that you can still use the OpenMap application for it and still completely customize it.

You don't have to use Properties - feel free to create any object you want, programmatically, and simply add it to the MapHandler.

If you want your layer to be driven by an external object, check out the com.bbn.openmap.plugin.graphicLoader package. A GraphicLoader is an object that is able to provide OMGraphics to an OMGraphicHandler (which can be thought of as a receiver). The graphicLoader package contains the AbstractGraphicLoader, an abstract GraphicLoader implementation that has a Swing Timer in it to trigger itself to deliver OMGraphic updates. You can extend this class to customize how and when these updates occur. If you place the GraphicLoaderConnector in the MapHandler along with your GraphicLoaders, the GraphicLoaderConnector will create a GraphicLoaderPlugIn/PlugInLayer combination to listen to each GraphicLoader if the GraphicLoader doesn't already have a receiver specified.

and creating images...

Since OpenMap layers respond asynchronously to projection changes, you have to use certain methods on them to create images. If you just try to draw the MapBean into an image, you may miss some layers that aren't ready to render.

Once you have a MapBean with layers added to it, you create formatted images using an com.bbn.openmap.image.ImageFormatter. The formatters use the Layer renderDataForProjection(...) method to get the layer to gather data appropriate for the projection, and then to draw that data into an java.awt.Graphics object.

If you want to create images without displaying a map on the screen, you have a couple of options. You can use the com.bbn.openmap.image.ImageServer class to read a properties file and create an image, specifying the formatter and layers to use in the ImageServer. You can also just create the layers you want to use and define the Projection for the image, and just call the layer's renderDataForProjection(...) method directly, from the backmost layer to the top. No MapBean is needed, and the result is a map drawn into a standard Java Image (or anything with a java.awt.Graphics object).


Back to Top
Twitter
Follow @openmap on Twitter for notifications for new releases and updates to the repository.
Discuss
There is an OpenMap forum on Google Groups. It's the preferred place to post questions, bugs and comments since other people with the same issues can get information there too. The old discussion group at BBN is still archived (it goes back to 2004, and thanks to BBN for providing the archive!!), there are a lot of questions and answers here.