Home
History
News
Downloads
OpenMap Demo
OpenMap Software License Agreement
How We Do Business
Who Else is Using OpenMap?
API Documentation
User's Guide
Frequently Asked Questions
openmap-users mailing list archives
Join the mailing list
Contributors & Submissions
Contact Us
 

BBN Technologies

 

Developer Hints (or things you'll wish you knew about before coding)

The OpenMap Developer's Guide

In addition to the specific developer hints listed below, feel free to download our OpenMap Developer Guide, a PDF file that takes you through all aspects of OpenMap: event handling, architecture components, displaying data, and projections.

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 object reference that can be accessed with setAppObject()  and getAppObject().  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).  You can always extend the standard OMGraphics to make them contain the particular features you want.

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 Swing thread will then call paint() on the layer at the proper time.  paint() can also be called automatically by the Swing 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 Swing thread.

If you want to save and reuse the projection that the layer receives in the ProjectionEvent, call layer.setProjection(ProjectionEvent) from within the projectionChanged() method. This method will return the Projection object extracted from the ProjectionEvent, and will also save it so it can be retrieved using the layer.getProjection() method.

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 one thing most OpenMap layers have in common is the use of the SwingWorker, which is a thread-launching object.  It's used by layers to spawn a thread when a new projection is received, in order to let the Java awt thread that the ProjectionChanged event arrived on be free to move to the next ProjectionListener.

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 PlugIns...

PlugIns (com.bbn.openmap.plugin) have been reworked and are reintroduced for OpenMap 4.2.  PlugIns are objects that simply manage OMGraphics.  There is a PlugInLayer (threaded with a SwingWorker) that can be used by a PlugIn to get graphics on the map.  If you extend AbstractPlugIn to create a customized PlugIn, the getRectangle() method is the only one that needs to be written.  The idea behind a PlugIn is that data access and OMGraphic creation can be isolated into a single object, and then that object can be used by a PlugInLayer for local data access, or by a server for remote data access by another OpenMap layer.

The PlugIn can also provide a GUI through its getGUI() method.  The AbstractPlugIn returns null by default.

As of OpenMap 4.4, PlugIns have been elevated to the same management level as Layers. For the OpenMap application, they can be defined the openmap.properties file the same way as layers, in the openmap.layers property. The LayerHandler, when parsing the list and creating a PlugIn, will automatically wrap a PlugInLayer around the PlugIn.

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.

PlugIns can also provide and/or implement the MapMouseListener interface - the PlugInLayer passes the request through to the PlugIn.

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.

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.

Feedback on this page is most welcome.  If there is something about OpenMap that took you awhile to figure out when a simple explanation would have been nice, let us know at openmap@bbn.com, and we'll add it here.

OpenMap is a trademark of BBN Technologies.
© 2005 BBNT Solutions LLC. All rights reserved.

All other trademarks are either owned by BBN Technologies or by other companies.
Contact openmap@bbn.com for more information about OpenMap.

Last update: