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