]> gitweb.fperrin.net Git - GpsPrune.git/blobdiff - tim/prune/gui/map/MapCanvas.java
Version 17, September 2014
[GpsPrune.git] / tim / prune / gui / map / MapCanvas.java
index 96ff22367e89d5137e1710c96e104a2911ff80de..7a6bce81a5dc285050a8a1e97a5ed3db4b84db37 100644 (file)
@@ -1,5 +1,6 @@
 package tim.prune.gui.map;
 
+import java.awt.AlphaComposite;
 import java.awt.BasicStroke;
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -31,6 +32,7 @@ import javax.swing.JMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.JSlider;
+import javax.swing.SwingUtilities;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 
@@ -57,12 +59,14 @@ import tim.prune.function.compress.MarkPointsInRectangleFunction;
 import tim.prune.function.edit.FieldEdit;
 import tim.prune.function.edit.FieldEditList;
 import tim.prune.gui.IconManager;
+import tim.prune.gui.colour.PointColourer;
+import tim.prune.tips.TipManager;
 
 /**
  * Class for the map canvas, to display a background map and draw on it
  */
 public class MapCanvas extends JPanel implements MouseListener, MouseMotionListener, DataSubscriber,
-       KeyListener, MouseWheelListener
+       KeyListener, MouseWheelListener, TileConsumer
 {
        /** App object for callbacks */
        private App _app = null;
@@ -82,6 +86,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        private MapTileManager _tileManager = new MapTileManager(this);
        /** Image to display */
        private BufferedImage _mapImage = null;
+       /** Second image for drawing track (only needed for alpha blending) */
+       private BufferedImage _trackImage = null;
        /** Slider for transparency */
        private JSlider _transparencySlider = null;
        /** Checkbox for scale bar */
@@ -292,6 +298,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                add(_scaleBar, BorderLayout.SOUTH);
                // Make popup menu
                makePopup();
+               // Get currently selected map from Config, pass to MapTileManager
+               _tileManager.setMapSource(Config.getConfigInt(Config.KEY_MAPSOURCE_INDEX));
        }
 
 
@@ -390,25 +398,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                int selectedPoint = _selection.getCurrentPointIndex();
                                if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
                                {
-                                       int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(selectedPoint));
-                                       int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(selectedPoint));
-                                       int panX = 0;
-                                       int panY = 0;
-                                       if (px < PAN_DISTANCE) {
-                                               panX = px - AUTOPAN_DISTANCE;
-                                       }
-                                       else if (px > (getWidth()-PAN_DISTANCE)) {
-                                               panX = AUTOPAN_DISTANCE + px - getWidth();
-                                       }
-                                       if (py < PAN_DISTANCE) {
-                                               panY = py - AUTOPAN_DISTANCE;
-                                       }
-                                       if (py > (getHeight()-PAN_DISTANCE)) {
-                                               panY = AUTOPAN_DISTANCE + py - getHeight();
-                                       }
-                                       if (panX != 0 || panY != 0) {
-                                               _mapPosition.pan(panX, panY);
-                                       }
+                                       autopanToPoint(selectedPoint);
                                }
                                _prevSelectedPoint = selectedPoint;
                        }
@@ -446,7 +436,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                                inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
                                        }
                                        break;
-                                       
+
                                case MODE_DRAW_POINTS_CONT:
                                        // draw line to mouse position to show drawing mode
                                        inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
@@ -469,6 +459,45 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                paintChildren(inG);
        }
 
+       /**
+        * @return true if the currently selected point is visible, false if off-screen or nothing selected
+        */
+       private boolean isCurrentPointVisible()
+       {
+               if (_trackInfo.getCurrentPoint() == null) {return false;}
+               final int selectedPoint = _selection.getCurrentPointIndex();
+               final int xFromCentre = Math.abs(_mapPosition.getXFromCentre(_track.getX(selectedPoint)));
+               if (xFromCentre > (getWidth()/2)) {return false;}
+               final int yFromCentre = Math.abs(_mapPosition.getYFromCentre(_track.getY(selectedPoint)));
+               return yFromCentre < (getHeight()/2);
+       }
+
+       /**
+        * If the specified point isn't visible, pan to it
+        * @param inIndex index of selected point
+        */
+       private void autopanToPoint(int inIndex)
+       {
+               int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(inIndex));
+               int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(inIndex));
+               int panX = 0;
+               int panY = 0;
+               if (px < PAN_DISTANCE) {
+                       panX = px - AUTOPAN_DISTANCE;
+               }
+               else if (px > (getWidth()-PAN_DISTANCE)) {
+                       panX = AUTOPAN_DISTANCE + px - getWidth();
+               }
+               if (py < (2*PAN_DISTANCE)) {
+                       panY = py - AUTOPAN_DISTANCE;
+               }
+               if (py > (getHeight()-PAN_DISTANCE)) {
+                       panY = AUTOPAN_DISTANCE + py - getHeight();
+               }
+               if (panX != 0 || panY != 0) {
+                       _mapPosition.pan(panX, panY);
+               }
+       }
 
        /**
         * Paint the map tiles and the points on to the _mapImage
@@ -487,8 +516,19 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                g.fillRect(0, 0, getWidth(), getHeight());
 
                // Check whether maps are on or not
-               boolean showMap = Config.getConfigBoolean(Config.KEY_SHOW_MAP);
+               final boolean showMap = Config.getConfigBoolean(Config.KEY_SHOW_MAP);
                _mapCheckBox.setSelected(showMap);
+               // Check whether disk cache is on or not
+               final boolean usingDiskCache = Config.getConfigString(Config.KEY_DISK_CACHE) != null;
+               // Show tip to recommend setting up a cache
+               if (showMap && !usingDiskCache && Config.getConfigBoolean(Config.KEY_ONLINE_MODE))
+               {
+                       SwingUtilities.invokeLater(new Runnable() {
+                               public void run() {
+                                       _app.showTip(TipManager.Tip_UseAMapCache);
+                               }
+                       });
+               }
 
                // reset error message
                if (!showMap) {_shownOsmErrorAlready = false;}
@@ -523,7 +563,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                                // Loop over layers
                                                for (int l=0; l<numLayers; l++)
                                                {
-                                                       Image image = _tileManager.getTile(l, tileX, tileY);
+                                                       Image image = _tileManager.getTile(l, tileX, tileY, true);
                                                        if (image != null) {
                                                                g.drawImage(image, x, y, 256, 256, null);
                                                        }
@@ -544,24 +584,58 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        }
                }
 
-               // Paint the track points on top
-               int pointsPainted = 1;
-               try
-               {
-                       pointsPainted = paintPoints(g);
+               // Work out track opacity according to slider
+               final float[] opacities = {1.0f, 0.75f, 0.5f, 0.3f, 0.15f, 0.0f};
+               float trackOpacity = 1.0f;
+               if (_transparencySlider.getValue() < 0) {
+                       trackOpacity = opacities[-1 - _transparencySlider.getValue()];
                }
-               catch (NullPointerException npe) { // ignore, probably due to data being changed during drawing
+
+               if (trackOpacity > 0.0f)
+               {
+                       // Paint the track points on top
+                       int pointsPainted = 1;
+                       try
+                       {
+                               if (trackOpacity > 0.9f)
+                               {
+                                       // Track is fully opaque, just draw it directly
+                                       pointsPainted = paintPoints(g);
+                                       _trackImage = null;
+                               }
+                               else
+                               {
+                                       // Track is partly transparent, so use a separate BufferedImage
+                                       if (_trackImage == null || _trackImage.getWidth() != getWidth() || _trackImage.getHeight() != getHeight())
+                                       {
+                                               _trackImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
+                                       }
+                                       // Clear to transparent
+                                       Graphics2D gTrack = _trackImage.createGraphics();
+                                       gTrack.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
+                                       gTrack.fillRect(0, 0, getWidth(), getHeight());
+                                       gTrack.setPaintMode();
+                                       // Draw the track onto this separate image
+                                       pointsPainted = paintPoints(gTrack);
+                                       ((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, trackOpacity));
+                                       g.drawImage(_trackImage, 0, 0, null);
+                               }
+                       }
+                       catch (NullPointerException npe) {} // ignore, probably due to data being changed during drawing
+                       catch (ArrayIndexOutOfBoundsException obe) {} // also ignore
+
+                       // Zoom to fit if no points found
+                       if (pointsPainted <= 0 && _checkBounds)
+                       {
+                               zoomToFit();
+                               _recalculate = true;
+                               repaint();
+                       }
                }
 
                // free g
                g.dispose();
 
-               // Zoom to fit if no points found
-               if (pointsPainted <= 0 && _checkBounds) {
-                       zoomToFit();
-                       _recalculate = true;
-                       repaint();
-               }
                _checkBounds = false;
                // enable / disable transparency slider
                _transparencySlider.setEnabled(showMap);
@@ -577,15 +651,12 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        {
                // Set up colours
                final ColourScheme cs = Config.getColourScheme();
-               final int[] opacities = {255, 190, 130, 80, 40, 0};
-               int opacity = 255;
-               if (_transparencySlider.getValue() < 0)
-                       opacity = opacities[-1 - _transparencySlider.getValue()];
-               final Color pointColour  = makeTransparentColour(cs.getColour(ColourScheme.IDX_POINT), opacity);
-               final Color rangeColour  = makeTransparentColour(cs.getColour(ColourScheme.IDX_SELECTION), opacity);
-               final Color currentColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_PRIMARY), opacity);
-               final Color secondColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_SECONDARY), opacity);
-               final Color textColour   = makeTransparentColour(cs.getColour(ColourScheme.IDX_TEXT), opacity);
+               final Color pointColour  = cs.getColour(ColourScheme.IDX_POINT);
+               final Color rangeColour  = cs.getColour(ColourScheme.IDX_SELECTION);
+               final Color currentColour = cs.getColour(ColourScheme.IDX_PRIMARY);
+               final Color secondColour = cs.getColour(ColourScheme.IDX_SECONDARY);
+               final Color textColour   = cs.getColour(ColourScheme.IDX_TEXT);
+               final PointColourer pointColourer = _app.getPointColourer();
 
                final int winWidth  = getWidth();
                final int winHeight = getHeight();
@@ -623,26 +694,34 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        currPointVisible = px >= 0 && px < winWidth && py >= 0 && py < winHeight;
                        isWaypoint = _track.getPoint(i).isWaypoint();
                        anyWaypoints = anyWaypoints || isWaypoint;
-                       if (currPointVisible)
+                       if (!isWaypoint)
                        {
-                               if (!isWaypoint)
+                               if (currPointVisible || prevPointVisible)
                                {
-                                       // Draw rectangle for track point
+                                       // For track points, work out which colour to use
                                        if (_track.getPoint(i).getDeleteFlag()) {
                                                inG.setColor(currentColour);
                                        }
-                                       else {
+                                       else if (pointColourer != null)
+                                       {  // use the point colourer if there is one
+                                               Color trackColour = pointColourer.getColour(i);
+                                               inG.setColor(trackColour);
+                                       }
+                                       else
+                                       {
                                                inG.setColor(pointColour);
                                        }
-                                       inG.drawRect(px-2, py-2, 3, 3);
-                                       pointsPainted++;
+
+                                       // Draw rectangle for track point if it's visible
+                                       if (currPointVisible)
+                                       {
+                                               inG.drawRect(px-2, py-2, 3, 3);
+                                               pointsPainted++;
+                                       }
                                }
-                       }
-                       if (!isWaypoint)
-                       {
+
                                // Connect track points if either of them are visible
-                               if (connectPoints && (currPointVisible || prevPointVisible)
-                                && !(prevX == -1 && prevY == -1)
+                               if (connectPoints && !(prevX == -1 && prevY == -1)
                                 && !_track.getPoint(i).getSegmentStart())
                                {
                                        inG.drawLine(prevX, prevY, px, py);
@@ -658,6 +737,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                int nameHeight = fm.getHeight();
                if (anyWaypoints)
                {
+                       int numWaypoints = 0;
                        for (int i=0; i<_track.getNumPoints(); i++)
                        {
                                if (_track.getPoint(i).isWaypoint())
@@ -668,11 +748,18 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                        {
                                                inG.fillRect(px-3, py-3, 6, 6);
                                                pointsPainted++;
+                                               numWaypoints++;
                                        }
                                }
                        }
+                       // Take more care with waypoint names if less than 100 are visible
+                       final int numNameSteps = (numWaypoints > 100 ? 1 : 4);
+                       final int numPointSteps = (numWaypoints > 1000 ? 2 : 1);
+
                        // Loop over points again, now draw names for waypoints
-                       for (int i=0; i<_track.getNumPoints(); i++)
+                       int[] nameXs = {0, 0, 0, 0};
+                       int[] nameYs = {0, 0, 0, 0};
+                       for (int i=0; i<_track.getNumPoints(); i += numPointSteps)
                        {
                                if (_track.getPoint(i).isWaypoint())
                                {
@@ -685,19 +772,21 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                                int nameWidth = fm.stringWidth(waypointName);
                                                boolean drawnName = false;
                                                // Make arrays for coordinates right left up down
-                                               int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
-                                               int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
-                                               for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
+                                               nameXs[0] = px + 2; nameXs[1] = px - nameWidth - 2;
+                                               nameXs[2] = nameXs[3] = px - nameWidth/2;
+                                               nameYs[0] = nameYs[1] = py + (nameHeight/2);
+                                               nameYs[2] = py - 2; nameYs[3] = py + nameHeight + 2;
+                                               for (int extraSpace = 0; extraSpace < numNameSteps && !drawnName; extraSpace++)
                                                {
                                                        // Shift arrays for coordinates right left up down
-                                                       nameXs[0] += 2; nameXs[1] -= 2;
-                                                       nameYs[2] -= 2; nameYs[3] += 2;
+                                                       nameXs[0] += 3; nameXs[1] -= 3;
+                                                       nameYs[2] -= 3; nameYs[3] += 3;
                                                        // Check each direction in turn right left up down
                                                        for (int a=0; a<4; a++)
                                                        {
                                                                if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < winWidth
                                                                        && nameYs[a] < winHeight && (nameYs[a] - nameHeight) > 0
-                                                                       && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight, textColour))
+                                                                       && !MapUtils.overlapsPoints(_mapImage, nameXs[a], nameYs[a], nameWidth, nameHeight, textColour))
                                                                {
                                                                        // Found a rectangle to fit - draw name here and quit
                                                                        inG.drawString(waypointName, nameXs[a], nameYs[a]);
@@ -813,82 +902,29 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                }
        }
 
-       /**
-        * Tests whether there are any dark pixels within the specified x,y rectangle
-        * @param inX left X coordinate
-        * @param inY bottom Y coordinate
-        * @param inWidth width of rectangle
-        * @param inHeight height of rectangle
-        * @param inTextColour colour of text
-        * @return true if the rectangle overlaps stuff too close to the given colour
-        */
-       private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight, Color inTextColour)
-       {
-               // each of the colour channels must be further away than this to count as empty
-               final int BRIGHTNESS_LIMIT = 80;
-               final int textRGB = inTextColour.getRGB();
-               final int textLow = textRGB & 255;
-               final int textMid = (textRGB >> 8) & 255;
-               final int textHigh = (textRGB >> 16) & 255;
-               try
-               {
-                       // loop over x coordinate of rectangle
-                       for (int x=0; x<inWidth; x++)
-                       {
-                               // loop over y coordinate of rectangle
-                               for (int y=0; y<inHeight; y++)
-                               {
-                                       int pixelColor = _mapImage.getRGB(inX + x, inY - y);
-                                       // split into four components rgba
-                                       int pixLow = pixelColor & 255;
-                                       int pixMid = (pixelColor >> 8) & 255;
-                                       int pixHigh = (pixelColor >> 16) & 255;
-                                       //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
-                                       // If colours are too close in any channel then it's an overlap
-                                       if (Math.abs(pixLow-textLow) < BRIGHTNESS_LIMIT ||
-                                               Math.abs(pixMid-textMid) < BRIGHTNESS_LIMIT ||
-                                               Math.abs(pixHigh-textHigh) < BRIGHTNESS_LIMIT) {return true;}
-                               }
-                       }
-               }
-               catch (NullPointerException e) {
-                       // ignore null pointers, just return false
-               }
-               return false;
-       }
-
-       /**
-        * Make a semi-transparent colour for drawing with
-        * @param inColour base colour (fully opaque)
-        * @param inOpacity opacity where 0=invisible and 255=full
-        * @return new colour object
-        */
-       private static Color makeTransparentColour(Color inColour, int inOpacity)
-       {
-               if (inOpacity > 240) return inColour;
-               return new Color(inColour.getRed(), inColour.getGreen(), inColour.getBlue(), inOpacity);
-       }
-
        /**
         * Inform that tiles have been updated and the map can be repainted
         * @param inIsOk true if data loaded ok, false for error
         */
-       public synchronized void tilesUpdated(boolean inIsOk)
+       public void tilesUpdated(boolean inIsOk)
        {
-               // Show message if loading failed (but not too many times)
-               if (!inIsOk && !_shownOsmErrorAlready && _mapCheckBox.isSelected())
+               synchronized(this)
                {
-                       _shownOsmErrorAlready = true;
-                       // use separate thread to show message about failing to load osm images
-                       new Thread(new Runnable() {
-                               public void run() {
-                                       try {Thread.sleep(500);} catch (InterruptedException ie) {}
-                                       _app.showErrorMessage("error.osmimage.dialogtitle", "error.osmimage.failed");
-                               }
-                       }).start();
+                       // Show message if loading failed (but not too many times)
+                       if (!inIsOk && !_shownOsmErrorAlready && _mapCheckBox.isSelected())
+                       {
+                               _shownOsmErrorAlready = true;
+                               // use separate thread to show message about failing to load osm images
+                               new Thread(new Runnable() {
+                                       public void run() {
+                                               try {Thread.sleep(500);} catch (InterruptedException ie) {}
+                                               _app.showErrorMessage("error.osmimage.dialogtitle", "error.osmimage.failed");
+                                       }
+                               }).start();
+                       }
+                       _recalculate = true;
+                       repaint();
                }
-               _recalculate = true;
-               repaint();
        }
 
        /**
@@ -906,7 +942,12 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
         */
        public void zoomIn()
        {
+               // See if selected point is currently visible, if so (and autopan on) then autopan after zoom to keep it visible
+               boolean wasVisible = _autopanCheckBox.isSelected() && isCurrentPointVisible();
                _mapPosition.zoomIn();
+               if (wasVisible && !isCurrentPointVisible()) {
+                       autopanToPoint(_selection.getCurrentPointIndex());
+               }
                _recalculate = true;
                repaint();
        }
@@ -1040,8 +1081,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                        else if (_drawMode == MODE_DRAW_POINTS_CONT)
                                        {
                                                DataPoint point = createPointFromClick(inE.getX(), inE.getY());
-                                               _app.createPoint(point);
-                                               point.setSegmentStart(false);
+                                               _app.createPoint(point, false); // not a new segment
                                        }
                                }
                                else if (inE.getClickCount() == 2)
@@ -1176,7 +1216,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        _app.setCurrentMode(App.AppMode.NORMAL);
                        _drawMode = MODE_DEFAULT;
                        // Call a function to mark the points
-                       MarkPointsInRectangleFunction marker = new MarkPointsInRectangleFunction(_app);
+                       MarkPointsInRectangleFunction marker = (MarkPointsInRectangleFunction) FunctionLibrary.FUNCTION_MARK_IN_RECTANGLE;
                        double lon1 = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_dragFromX, getWidth()));
                        double lat1 = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_dragFromY, getHeight()));
                        double lon2 = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_dragToX, getWidth()));
@@ -1306,7 +1346,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        _checkBounds = true;
                }
                if ((inUpdateType & DataSubscriber.MAPSERVER_CHANGED) > 0) {
-                       _tileManager.resetConfig();
+                       // Get the selected map source index and pass to tile manager
+                       _tileManager.setMapSource(Config.getConfigInt(Config.KEY_MAPSOURCE_INDEX));
                }
                if ((inUpdateType & (DataSubscriber.DATA_ADDED_OR_REMOVED + DataSubscriber.DATA_EDITED)) > 0) {
                        _midpoints.updateData(_track);