MapBean fails to update layers unless MapBean.setScale() is called, and panning using a MouseListener and MouseMotionListener is very slow

46 views
Skip to first unread message

Tim Fielder

unread,
Jul 24, 2019, 11:00:18 AM7/24/19
to openmap-users
I'm working on an application that involves the use of a map to do some very basic drawing tasks.  Currently, I need to: show the globe, allow panning and zooming using the mouse, and draw simple shapes in layers on the map.

Here is the class that handles display of the map:
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.IOException;

import com.bbn.openmap.MapBean;
import com.bbn.openmap.PropertyHandler;
import com.bbn.openmap.gui.OpenMapFrame;
import com.bbn.openmap.layer.shape.ShapeLayer;
import com.bbn.openmap.proj.Orthographic;
import com.bbn.openmap.proj.coords.LatLonPoint;

/**
 * <h1>MapFrame</h1>
 * TODO
 * <p>
 *
 * @author tfielder
 * @since  2019-07-22
 *         <p>
 */

public class MapFrame extends OpenMapFrame
{
   
/* #Region Constants */

   
private static final long serialVersionUID = 1L;

   
public static final int SPLITPANE_DIVLOC = Integer.parseInt(System.getProperty("screenWidth")) / 3;
   
public static final int SPLITPANE_DIVSIZE = 5;

   
public static final String MAP_CONFIG_FILE = "map/openmap.properties";
   
private static final LatLonPoint DEFAULT_CENTER = new LatLonPoint.Double(0.0, 0.0);
   
private static final float DEFAULT_SCALE = 200000000.0f;
   
private static final float SCALE_DOWN = 0.8f;
   
private static final float SCALE_UP = 1.25f;

   
/* #Region Globals */

   
private LatLonPoint gPoint;
   
private boolean gDragging;

   
private DrawLayer gDrawLayer = new DrawLayer();
   
private int gDrawMode = 0;

   
/* #Region Constructors */

   
public MapFrame()
   
{
       
final MapBean map = new MapBean();

        map
.add(gDrawLayer);

       
PropertyHandler properties = null;
       
try
       
{
            properties
= new PropertyHandler.Builder().setPropertiesFile(System.getProperty("openmap.properties")).build();
       
}
       
catch (IOException ioEx)
       
{
           
Logging.logError(ioEx, "Failed to load map properties file");
       
}

       
ShapeLayer layer = new ShapeLayer();
       
if (properties != null)
       
{
            layer
.setProperties(properties.getProperties());
       
}
        layer
.setAddAsBackground(true);
        map
.add(layer);

        map
.setCenter(DEFAULT_CENTER);
        map
.setScale(DEFAULT_SCALE);
        map
.setDoubleBuffered(true);

        map
.addMouseListener(new MouseListener()
       
{
           
@Override
           
public void mouseClicked(MouseEvent event)
           
{
           
}

           
@Override
           
public void mouseEntered(MouseEvent event)
           
{
           
}

           
@Override
           
public void mouseExited(MouseEvent event)
           
{
           
}

           
@Override
           
public void mousePressed(MouseEvent event)
           
{
               
if (gDrawMode == 0)
               
{
                    gPoint
= new LatLonPoint.Double(map.getCoordinates(event));
                    gDrawMode
= event.getButton();
                   
if (event.getButton() == MouseEvent.BUTTON1)
                   
{
                        gDragging
= true;
                   
}
               
}
           
}

           
@Override
           
public void mouseReleased(MouseEvent event)
           
{
               
if (event.getButton() == gDrawMode)
               
{
                    gDragging
= false;
                    gDrawLayer
.clear();
                    gDrawMode
= 0;
               
}
           
}
       
});

        map
.addMouseMotionListener(new MouseMotionListener()
       
{
           
@Override
           
public void mouseDragged(MouseEvent event)
           
{
                map
.setScale(map.getScale());
               
if (gDrawMode == 1)
               
{
                   
if (gDragging)
                   
{
                       
LatLonPoint coords = new LatLonPoint.Double(map.getCoordinates(event));
                        map
.setCenter(
                               
new LatLonPoint.Double(
                                       
new LatLonPoint.Double(map.getCenter()).getLatitude()
                                               
- (coords.getLatitude() - gPoint.getLatitude()),
                                       
new LatLonPoint.Double(map.getCenter()).getLongitude()
                                               
- (coords.getLongitude() - gPoint.getLongitude())));
                        map
.setScale(map.getScale());
                   
}
               
}
               
if (gDrawMode > 1)
               
{
                    gDrawLayer
.draw(gPoint, new LatLonPoint.Double(map.getCoordinates(event)), gDrawMode == 2);
               
}
                map
.setScale(map.getScale());
           
}

           
@Override
           
public void mouseMoved(MouseEvent event)
           
{
                gDrawLayer
.clear();
                gDrawLayer
.draw(new LatLonPoint.Double(map.getCoordinates(event)));
                map
.setScale(map.getScale());
           
}

       
});

        map
.addMouseWheelListener(new MouseWheelListener()
       
{
           
@Override
           
public void mouseWheelMoved(MouseWheelEvent event)
           
{
                map
.setScale(map.getScale() * (event.getPreciseWheelRotation() > 0.0 ? SCALE_UP : SCALE_DOWN));
           
}
       
});

        map
.setProjection(new Orthographic(new LatLonPoint.Double(map.getCenter()), map.getScale(), map.getWidth(), map.getHeight()));

        getContentPane
().add(map, BorderLayout.CENTER);
   
}
}

Here is the class that handles drawing the text and shapes on the map:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;

import com.bbn.openmap.Layer;
import com.bbn.openmap.event.ProjectionEvent;
import com.bbn.openmap.omGraphics.OMCircle;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMLine;
import com.bbn.openmap.omGraphics.OMRect;
import com.bbn.openmap.omGraphics.OMText;
import com.bbn.openmap.proj.Length;
import com.bbn.openmap.proj.coords.LatLonPoint;

/**
 * <h1>DrawLayer</h1>
 * TODO
 * <p>
 *
 * @author tfielder
 * @since  2019-07-22
 *         <p>
 */

public class DrawLayer extends Layer
{
   
/* #Region Constants */

   
private static final long serialVersionUID = 1L;

   
/* #Region Globals */

   
protected OMGraphicList gGraphic;

   
/* #Region Constructors */

   
public DrawLayer()
   
{
        gGraphic
= new OMGraphicList();
   
}

   
/* #Region Methods */

   
public void clear()
   
{
        gGraphic
.clear();
        repaint
();
   
}

   
public void draw(LatLonPoint start, LatLonPoint end, boolean circle)
   
{
        gGraphic
.clear();
       
if (circle)
       
{
           
OMCircle shape =
                   
new OMCircle(start.getLatitude(), start.getLongitude(), Length.DECIMAL_DEGREE.fromRadians(start.distance(end)));
            shape
.setStroke(new BasicStroke(2));
            shape
.setLinePaint(Color.RED);
            gGraphic
.add(shape);

           
OMLine radius =
                   
new OMLine(
                            start
.getLatitude(),
                            start
.getLongitude(),
                           
end.getLatitude(),
                           
end.getLongitude(),
                           
OMLine.LINETYPE_GREATCIRCLE);
            radius
.setStroke(new BasicStroke(2));
            radius
.setLinePaint(Color.RED);
            gGraphic
.add(radius);

           
OMText center =
                   
new OMText(
                            start
.getLatitude(),
                            start
.getLongitude(),
                           
0.0f,
                           
0.0f,
                           
String.format("%.2f, %.2f", start.getLatitude(), start.getLongitude()),
                           
OMText.DEFAULT_FONT,
                           
OMText.JUSTIFY_CENTER);
            center
.setLinePaint(Color.BLACK);
            gGraphic
.add(center);

           
OMText dist =
                   
new OMText(
                           
(start.getLatitude() + end.getLatitude()) / 2.0,
                           
(start.getLongitude() + end.getLongitude()) / 2.0,
                           
0.0f,
                           
0.0f,
                           
String.format("%.2fkm", Length.KM.fromRadians(start.distance(end))),
                           
OMText.DEFAULT_FONT,
                           
OMText.JUSTIFY_CENTER);
            dist
.setLinePaint(Color.BLACK);
            gGraphic
.add(dist);
       
}
       
else
       
{
           
OMRect shape =
                   
new OMRect(
                            start
.getLatitude(),
                            start
.getLongitude(),
                           
end.getLatitude(),
                           
end.getLongitude(),
                           
OMRect.LINETYPE_GREATCIRCLE);
            shape
.setStroke(new BasicStroke(2));
            shape
.setLinePaint(Color.RED);
            gGraphic
.add(shape);

           
OMText text =
                   
new OMText(
                            start
.getLatitude(),
                            start
.getLongitude(),
                           
0.0f,
                           
-5.0f,
                           
String.format("%.2f, %.2f", start.getLatitude(), start.getLongitude()),
                           
OMText.DEFAULT_FONT,
                           
OMText.JUSTIFY_CENTER);
            text
.setLinePaint(Color.BLACK);
            gGraphic
.add(text);

            text
=
                   
new OMText(
                           
end.getLatitude(),
                           
end.getLongitude(),
                           
-50.0f,
                           
15.0f,
                           
String.format("%.2f, %.2f", end.getLatitude(), end.getLongitude()),
                           
OMText.DEFAULT_FONT,
                           
OMText.JUSTIFY_CENTER);
            text
.setLinePaint(Color.BLACK);
            gGraphic
.add(text);
       
}
        repaint
();
   
}
   
   
public void draw(LatLonPoint center)
   
{
        gGraphic
.clear();
       
OMText text =
               
new OMText(
                        center
.getLatitude(),
                        center
.getLongitude(),
                       
0.0f,
                       
-5.0f,
                       
String.format("%.2f, %.2f", center.getLatitude(), center.getLongitude()),
                       
OMText.DEFAULT_FONT,
                       
OMText.JUSTIFY_CENTER);
        text
.setLinePaint(Color.BLACK);
        gGraphic
.add(text);
        repaint
();
   
}

   
@Override
   
public void paint(Graphics g)
   
{
        gGraphic
.render(g);
   
}

   
/**
     * <h1>projectionChanged</h1>
     * TODO
     * <p>
     *
     * @see com.bbn.openmap.event.ProjectionListener#projectionChanged(com.bbn.openmap.event.ProjectionEvent)
     */

   
@Override
   
public void projectionChanged(ProjectionEvent event)
   
{
        gGraphic
.project(event.getProjection(), true);
        repaint
();
   
}
}

And here's the openmap.properties file I'm using:
prettyName=Political Solid
lineColor
=000000
fillColor
=BDDE83
shapeFile
=map/shape/dcwpo-browse.shp
spatialIndex
=map/shape/dcwpo-browse.ssx

The first issue I'm seeing is that when panning the map, the political layer disappears and only reappears a few seconds after releasing the left mouse button.  Ideally, it should render smoothly at all times.  This may be an issue with the fact that I'm running on on-board graphics, so it's possible that if someone else tries this code it will work as expected.
The second issue is that I have to pepper the code with map.setScale(map.getScale()) to force the layers to update as expected.  For whatever reason, calling validate() and repaint() don't seem to work.  This is certainly a limitation that I can work around, but if there's a method that has a faster execution time than resetting the scale that would be good.
The third issue is that I have to add the layers to the MapBean in reverse order of their display order to get the DrawLayer on top of the political layer.  I need some way of ordering the layers manually on-the-fly so that what I start saving the drawing layers as permanent objects I can force the selected one to display on top of the other layers.

Thanks.

Don Dietrick

unread,
Jul 25, 2019, 4:14:22 PM7/25/19
to Tim Fielder, openmap-users
Hi Tim,

I think I understand what you are trying to do, and I can offer some suggestions.  The main problem you are seeing is that the OMGraphics aren’t being generated with the current projection before you try to render them.  Calling map.setScale(…) is a workaround because that in turn calls Layer.projectionChanged(…), which generates the OMGraphics and calls repaint again.  So that’s the main problem.

I notice you’re working with the most base classes, things would be easier if you used some of the major subclasses of some of the objects you’ve chosen.  I’d only use Layer if you really want to use the Graphics2D methods in the Layer.paint() method.  If you’re using OMGraphics, use the OMGraphicHandlerLayer as a base class and take advantage of some helpful features.  There are examples of using OMGraphics and that layer in the com.bbn.openmap.layer.learn package.

Also, you’ll want to use the BufferedLayerMapBean instead of the general MapBean.  That version understands the idea of a background layer, and is double-buffered.  The MapBean is really a top-level base class and I wouldn’t recommend using it directly.  In fact, I’d recommend using the BasicMapPanel to take advantage advantage of some other OpenMap components that will help you out.

I’ve enclosed a java file of my approach of what I think you were trying to do - it uses a BasicMapPanel (which uses a BufferedLayerMapBean and MapHandler), and I’ve added a MouseDelegator, LayerHandler, and a modified version of the PanMouseMode to handle map panning.  I changed your layer to extend OMGraphicHandlerLayer, and set a MapMouseInterpreter that works with the PanMouseMode to draw circles and rectangles when the middle and right mouse buttons are used.

The LayerHandler will automatically find the Layers added as map components and manages the orders of layers on the MapBean, and should be used to turn layers on/off.

I had to modify the PanMouseMode because, like other mouse modes, talks to the layers after it has already reacted to the mouse event.  I’ll have to rethink that, since in this case the layer needs to react first.

Hope this helps,

Don

SampleMap.java
Message has been deleted

Tim Fielder

unread,
Oct 3, 2019, 11:45:04 AM10/3/19
to openmap-users
Ultimately I ended up doing away with the idea of click-and-drag panning of the map, and instead opted for re-centering the map at the lat/lon location of a click. In the interest of closing the loop on this subject, I'm posting all of my code for what is effectively the final version of my map code, sans javadoc/comments and with some proprietary bits omitted.

The MapFrame class handles displaying and zooming/panning the map, and contains a SplitPane that shows the map on the right and a set of layers on the left.

The MapLayer class handles the little widgets in the left part of the SplitPane, and provides the ability to show/hide the corresponding layer, update the line/fill colors of the layer, and update the clustering of layers that implement that feature.

The DrawLayer class is the parent class for all of the layers that get displayed on the map.

The CoordPoints class extends DrawLayer, and implements the LingPipe hierarchical agglomerative clusterer to group large number of points together to reduce the number of things being drawn on a given layer.

The LatLonDistance class is just a wrapper for getting distance between two LatLonPoints

The CitiesLayer class extends CoordPoints, and parses the cities.csv file to display all of the cities and capitals on the map.  It contains a method for drawing a star at the location of a capital.

Finally, the TempCoords class handles displaying the temporary rectangular and circular coordinate blocks on the map when you drag with the middle or right mouse buttons.

There's obviously more being done with the map and layers, but this is all I can safely share.  Thanks for the help Don, and hopefully others find this stuff useful.
CitiesLayer.java
CoordPoints.java
DrawLayer.java
LatLonDistance.java
MapFrame.java
MapLayer.java
TempCoords.java
Reply all
Reply to author
Forward
0 new messages