Changeset 11431 in josm


Ignore:
Timestamp:
2017-01-06T20:26:55+01:00 (3 months ago)
Author:
Don-vip
Message:

fix #13124 - Enhanced GPS data rendering: add new heatmap mode (patch by kidelo)

Location:
trunk
Files:
5 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java

    r11161 r11431  
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66
     7import java.awt.AlphaComposite;
    78import java.awt.BasicStroke;
    89import java.awt.Color;
     10import java.awt.Composite;
    911import java.awt.Graphics2D;
     12import java.awt.LinearGradientPaint;
     13import java.awt.MultipleGradientPaint;
     14import java.awt.Paint;
    1015import java.awt.Point;
     16import java.awt.Rectangle;
    1117import java.awt.RenderingHints;
    1218import java.awt.Stroke;
     19import java.awt.image.BufferedImage;
     20import java.awt.image.DataBufferInt;
     21import java.awt.image.Raster;
     22import java.io.BufferedReader;
     23import java.io.IOException;
    1324import java.util.ArrayList;
    1425import java.util.Arrays;
     
    2839import org.openstreetmap.josm.data.preferences.ColorProperty;
    2940import org.openstreetmap.josm.gui.MapView;
     41import org.openstreetmap.josm.io.CachedFile;
    3042import org.openstreetmap.josm.tools.ColorScale;
     43import org.openstreetmap.josm.tools.JosmRuntimeException;
     44import org.openstreetmap.josm.tools.Utils;
    3145
    3246/**
     
    4660    // draw lines between points belonging to different segments
    4761    private boolean forceLines;
     62    // use alpha blending for line draw
     63    private boolean alphaLines;
    4864    // draw direction arrows on the lines
    4965    private boolean direction;
     66    /** width of line for paint **/
     67    private int lineWidth;
    5068    /** don't draw lines if longer than x meters **/
    51     private int lineWidth;
    5269    private int maxLineLength;
     70    // draw lines
    5371    private boolean lines;
    5472    /** paint large dots for points **/
     
    7492    private ColorMode computeCacheColored;
    7593    private int computeCacheColorTracksTune;
     94    private int computeCacheHeatMapDrawColorTableIdx;
    7695
    7796    //// Color-related fields
     
    105124    };
    106125
     126    /** heat map parameters **/
     127
     128    // enabled or not (override by settings)
     129    private boolean heatMapEnabled;
     130    // draw small extra line
     131    private boolean heatMapDrawExtraLine;
     132    // used index for color table (parameter)
     133    private int heatMapDrawColorTableIdx;
     134
     135    // normal buffered image and draw object (cached)
     136    private BufferedImage heatMapImgGray;
     137    private Graphics2D heatMapGraph2d;
     138
     139    // some cached values
     140    Rectangle heatMapCacheScreenBounds = new Rectangle();
     141    int heatMapCacheVisibleSegments;
     142    double heatMapCacheZoomScale;
     143    int heatMapCacheLineWith;
     144
     145    // copied value for line drawing
     146    private List<Integer> heatMapPolyX = new ArrayList<>();
     147    private List<Integer> heatMapPolyY = new ArrayList<>();
     148
     149    // setup color maps used by heat map
     150    private static Color[] heatMapLutColorJosmInferno = createColorFromResource("inferno");
     151    private static Color[] heatMapLutColorJosmViridis = createColorFromResource("viridis");
     152    private static Color[] heatMapLutColorJosmBrown2Green = createColorFromResource("brown2green");
     153    private static Color[] heatMapLutColorJosmRed2Blue = createColorFromResource("red2blue");
     154
     155    // user defined heatmap color
     156    private Color[] heatMapLutUserColor = createColorLut(Color.BLACK, Color.WHITE);
     157
     158    // heat map color in use
     159    private Color[] heatMapLutColor;
     160
    107161    private void setupColors() {
    108162        hdopAlpha = Main.pref.getInteger("hdop.color.alpha", -1);
     
    112166        dateScale = ColorScale.createHSBScale(256).addTitle(tr("Time"));
    113167        directionScale = ColorScale.createCyclicScale(256).setIntervalCount(4).addTitle(tr("Direction"));
     168        heatMapLutColor = heatMapLutUserColor;
     169
    114170        systemOfMeasurementChanged(null, null);
    115171    }
     
    128184     */
    129185    public enum ColorMode {
    130         NONE, VELOCITY, HDOP, DIRECTION, TIME;
     186        NONE, VELOCITY, HDOP, DIRECTION, TIME, HEATMAP;
    131187
    132188        static ColorMode fromIndex(final int index) {
     
    171227     * Read coloring mode for specified layer from preferences
    172228     * @param layerName name of the GpxLayer
    173      * @return coloting mode
     229     * @return coloring mode
    174230     */
    175231    public ColorMode getColorMode(String layerName) {
     
    199255        direction = Main.pref.getBoolean("draw.rawgps.direction", spec, false);
    200256        lineWidth = Main.pref.getInteger("draw.rawgps.linewidth", spec, 0);
     257        alphaLines = Main.pref.getBoolean("draw.rawgps.lines.alpha-blend", spec, false);
    201258
    202259        if (!data.fromServer) {
     
    220277        largePointAlpha = Main.pref.getInteger("draw.rawgps.large.alpha", -1) & 0xFF;
    221278
     279        // get heatmap parameters
     280        heatMapEnabled = Main.pref.getBoolean("draw.rawgps.heatmap.enabled", spec, false);
     281        heatMapDrawExtraLine = Main.pref.getBoolean("draw.rawgps.heatmap.line-extra", spec, false);
     282        heatMapDrawColorTableIdx = Main.pref.getInteger("draw.rawgps.heatmap.colormap", specName(layerName), 0);
     283
    222284        neutralColor = getColor(layerName, true);
    223285        velocityScale.setNoDataColor(neutralColor);
     
    229291    }
    230292
     293    /**
     294     * Draw all enabled GPX elements of layer.
     295     * @param g               the common draw object to use
     296     * @param mv              the meta data to current displayed area
     297     * @param visibleSegments segments visible in the current scope of mv
     298     */
    231299    public void drawAll(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
     300
     301        final long timeStart = System.currentTimeMillis();
    232302
    233303        checkCache();
     
    238308        }
    239309
    240         Stroke storedStroke = g.getStroke();
    241 
     310        fixColors(visibleSegments);
     311
     312        // backup the environment
     313        Composite oldComposite = g.getComposite();
     314        Stroke oldStroke = g.getStroke();
     315        Paint oldPaint = g.getPaint();
     316
     317        // set hints for the render
    242318        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    243319            Main.pref.getBoolean("mappaint.gpx.use-antialiasing", false) ?
     
    247323            g.setStroke(new BasicStroke(lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
    248324        }
    249         fixColors(visibleSegments);
    250         drawLines(g, mv, visibleSegments);
     325
     326        // global enabled or select via color
     327        boolean useHeatMap = heatMapEnabled || ColorMode.HEATMAP == colored;
     328
     329        // default global alpha level
     330        float layerAlpha = 1.00f;
     331
     332        // extract current alpha blending value
     333        if (oldComposite instanceof AlphaComposite) {
     334            layerAlpha = ((AlphaComposite) oldComposite).getAlpha();
     335        }
     336
     337        // use heatmap background layer
     338        if (useHeatMap) {
     339            drawHeatMap(g, mv, visibleSegments);
     340        } else {
     341            // use normal line style or alpha-blending lines
     342            if (!alphaLines) {
     343                drawLines(g, mv, visibleSegments);
     344            } else {
     345                drawLinesAlpha(g, mv, visibleSegments, layerAlpha);
     346            }
     347        }
     348
     349        // override global alpha settings (smooth overlay)
     350        if (alphaLines || useHeatMap) {
     351            g.setComposite(AlphaComposite.SrcOver.derive(0.25f * layerAlpha));
     352        }
     353
     354        // normal overlays
    251355        drawArrows(g, mv, visibleSegments);
    252356        drawPoints(g, mv, visibleSegments);
    253         if (lineWidth != 0) {
    254             g.setStroke(storedStroke);
    255         }
    256     }
    257 
     357
     358        // restore environment
     359        g.setPaint(oldPaint);
     360        g.setStroke(oldStroke);
     361        g.setComposite(oldComposite);
     362
     363        // show some debug info
     364        if (Main.isDebugEnabled() && !visibleSegments.isEmpty()) {
     365            final long timeDiff = System.currentTimeMillis() - timeStart;
     366
     367            Main.debug("gpxdraw::draw takes " +
     368                         Utils.getDurationString(timeDiff) +
     369                         "(" +
     370                         "segments= " + visibleSegments.size() +
     371                         ", per 10000 = " + Utils.getDurationString(10000 * timeDiff / visibleSegments.size()) +
     372                         ")"
     373              );
     374        }
     375    }
     376
     377    /**
     378     *  Calculate colors of way segments based on latest configuration settings
     379     */
    258380    public void calculateColors() {
    259381        double minval = +1e10;
     
    392514        }
    393515
     516        // heat mode
     517        if (ColorMode.HEATMAP == colored && neutralColor != null) {
     518
     519            // generate new user color map
     520            heatMapLutUserColor = createColorLut(Color.BLACK, neutralColor.darker(),
     521                                                 neutralColor, neutralColor.brighter(), Color.WHITE);
     522
     523            // decide what, keep order is sync with setting on GUI
     524            Color[][] lut = {
     525                    heatMapLutUserColor,
     526                    heatMapLutColorJosmInferno,
     527                    heatMapLutColorJosmViridis,
     528                    heatMapLutColorJosmBrown2Green,
     529                    heatMapLutColorJosmRed2Blue
     530            };
     531
     532            // select by index
     533            if (heatMapDrawColorTableIdx < lut.length) {
     534                heatMapLutColor = lut[ heatMapDrawColorTableIdx ];
     535            } else {
     536                // fallback
     537                heatMapLutColor = heatMapLutUserColor;
     538            }
     539
     540            // force redraw of image
     541            heatMapCacheVisibleSegments = 0;
     542        }
     543
    394544        computeCacheInSync = true;
    395545    }
    396546
     547    /**
     548     * Draw all GPX ways segments
     549     * @param g               the common draw object to use
     550     * @param mv              the meta data to current displayed area
     551     * @param visibleSegments segments visible in the current scope of mv
     552     */
    397553    private void drawLines(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
    398554        if (lines) {
     
    414570    }
    415571
     572    /**
     573     * Draw all GPX arrays
     574     * @param g               the common draw object to use
     575     * @param mv              the meta data to current displayed area
     576     * @param visibleSegments segments visible in the current scope of mv
     577     */
    416578    private void drawArrows(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
    417579        /****************************************************************
     
    475637    }
    476638
     639    /**
     640     * Draw all GPX points
     641     * @param g               the common draw object to use
     642     * @param mv              the meta data to current displayed area
     643     * @param visibleSegments segments visible in the current scope of mv
     644     */
    477645    private void drawPoints(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
    478646        /****************************************************************
     
    551719    }
    552720
     721   /**
     722     * Draw GPX lines by using alpha blending
     723     * @param g               the common draw object to use
     724     * @param mv              the meta data to current displayed area
     725     * @param visibleSegments segments visible in the current scope of mv
     726     * @param layerAlpha      the color alpha value set for that operation
     727     */
     728    private void drawLinesAlpha(Graphics2D g, MapView mv, List<WayPoint> visibleSegments, float layerAlpha) {
     729
     730        // 1st. backup the paint environment ----------------------------------
     731        Composite oldComposite = g.getComposite();
     732        Stroke oldStroke = g.getStroke();
     733        Paint oldPaint = g.getPaint();
     734
     735        // 2nd. determine current scale factors -------------------------------
     736
     737        // adjust global settings
     738        final int globalLineWidth = Math.min(Math.max(lineWidth, 1), 20);
     739
     740        // cache scale of view
     741        final double zoomScale = mv.getScale();
     742
     743        // 3rd. determine current paint parameters -----------------------------
     744
     745        // alpha value is based on zoom and line with combined with global layer alpha
     746        float theLineAlpha = Math.min(Math.max((0.50f/(float) zoomScale)/(globalLineWidth + 1), 0.001f), 0.50f) * layerAlpha;
     747        final int theLineWith = (int) (lineWidth / zoomScale) + 1;
     748
     749        // 4th setup virtual paint area ----------------------------------------
     750
     751        // set line format and alpha channel for all overlays (more lines -> few overlap -> more transparency)
     752        g.setStroke(new BasicStroke(theLineWith, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
     753        g.setComposite(AlphaComposite.SrcOver.derive(theLineAlpha));
     754
     755        // last used / calculated entries
     756        Point lastPaintPnt = null;
     757
     758        // 5th draw the layer ---------------------------------------------------
     759
     760        // for all points
     761        for (WayPoint trkPnt : visibleSegments) {
     762
     763            // transform coordinates
     764            final Point paintPnt = mv.getPoint(trkPnt.getEastNorth());
     765
     766            // skip single points
     767            if (lastPaintPnt != null && trkPnt.drawLine && !lastPaintPnt.equals(paintPnt)) {
     768
     769                // set different color
     770                g.setColor(trkPnt.customColoring);
     771
     772                // draw it
     773                g.drawLine(lastPaintPnt.x, lastPaintPnt.y, paintPnt.x, paintPnt.y);
     774            }
     775
     776            lastPaintPnt = paintPnt;
     777        }
     778
     779        // @last restore modified paint environment -----------------------------
     780        g.setPaint(oldPaint);
     781        g.setStroke(oldStroke);
     782        g.setComposite(oldComposite);
     783    }
     784
     785    /**
     786     * Creates a linear distributed colormap by linear blending between colors
     787     * @param colors 1..n colors
     788     * @return array of Color objects
     789     */
     790    protected static Color[] createColorLut(Color... colors) {
     791
     792        // number of lookup entries
     793        int tableSize = 256;
     794
     795        // create image an paint object
     796        BufferedImage img = new BufferedImage(tableSize, 1, BufferedImage.TYPE_INT_RGB);
     797        Graphics2D g = img.createGraphics();
     798
     799        float[] fract = new float[ colors.length ];
     800
     801        // distribute fractions (define position of color in map)
     802        for (int i = 0; i < colors.length; ++i) {
     803            fract[i] = i * (1.0f / colors.length);
     804        }
     805
     806        // draw the gradient map
     807        LinearGradientPaint gradient = new LinearGradientPaint(0, 0, tableSize, 1, fract, colors,
     808                                                               MultipleGradientPaint.CycleMethod.NO_CYCLE);
     809        g.setPaint(gradient);
     810        g.fillRect(0, 0, tableSize, 1);
     811        g.dispose();
     812
     813        // access it via raw interface
     814        final Raster imgRaster = img.getData();
     815
     816        // the pixel storage
     817        int[] pixel = new int[1];
     818
     819        Color[] colorTable = new Color[tableSize];
     820
     821        // map the range 0..255 to 0..pi/2
     822        final double mapTo90Deg = Math.PI / 2.0 / 255.0;
     823
     824        // create the lookup table
     825        for (int i = 0; i < tableSize; i++) {
     826
     827            // get next single pixel
     828            imgRaster.getDataElements((int) (i * (double) img.getWidth() / tableSize), 0, pixel);
     829
     830            // get color and map
     831            Color c = new Color(pixel[0]);
     832
     833            // smooth alpha like sin curve
     834            int alpha = (int) (Math.sin(i * mapTo90Deg) * 255);
     835
     836            // alpha with pre-offset, first color -> full transparent
     837            alpha = i > 0 ? (75 + alpha) : 0;
     838
     839            // shrink to maximum bound
     840            if (alpha > 255) {
     841                alpha = 255;
     842            }
     843
     844            // increase transparency for higher values ( avoid big saturation )
     845            if (i > 240 && 255 == alpha) {
     846                alpha -= (i - 240);
     847            }
     848
     849            // fill entry in table, assign a alpha value
     850            colorTable[i] = new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
     851        }
     852
     853        // transform into lookup table
     854        return colorTable;
     855    }
     856
     857    /**
     858     * Creates a colormap by using a static color map with 1..n colors (RGB 0.0 ..1.0)
     859     * @param data array of multiple RGB color [n][3]
     860     * @return a dynamic list of color objects
     861     */
     862    protected static Color[] createColorFromRawArray(double[][] data) {
     863
     864        // create the array
     865        Color[] color = new Color[ data.length ];
     866
     867        for (int k = 0; k < data.length; k++) {
     868           // cast an map to linear array
     869           color[k] = new Color((float) data[k][0], (float) data[k][1], (float) data[k][2]);
     870        }
     871
     872        // forward
     873        return createColorLut(color);
     874    }
     875
     876    /**
     877     * Creates a colormap by using a static color map with 1..n colors (RGB 0.0 ..1.0)
     878     * @param str the filename (without extension) to look for into data/gpx
     879     * @return the parsed colormap
     880     */
     881    protected static Color[] createColorFromResource(String str) {
     882
     883        // create resource string
     884        final String colorFile = "resource://data/gpx/" + str + ".txt";
     885
     886        List<Color> colorList = new ArrayList<>();
     887
     888        // try to load the file
     889        try (CachedFile cf = new CachedFile(colorFile); BufferedReader br = cf.getContentReader()) {
     890
     891            String line;
     892
     893            // process lines
     894            while ((line = br.readLine()) != null) {
     895
     896                // use comma as separator
     897                String[] column = line.split(",");
     898
     899                // empty or comment line
     900                if (column.length < 3 || column[0].startsWith("#")) {
     901                    continue;
     902                }
     903
     904                // extract RGB value
     905                float r = Float.parseFloat(column[0]);
     906                float g = Float.parseFloat(column[1]);
     907                float b = Float.parseFloat(column[2]);
     908
     909                // some color tables are 0..1.0 and some 0.255
     910                float scale = (r < 1 && g < 1 && b < 1) ? 1 : 255;
     911
     912                colorList.add(new Color(r/scale, g/scale, b/scale));
     913            }
     914        } catch (IOException e) {
     915            throw new JosmRuntimeException(e);
     916        }
     917
     918        // fallback if empty or failed
     919        if (colorList.isEmpty()) {
     920            colorList.add(Color.BLACK);
     921            colorList.add(Color.WHITE);
     922        }
     923
     924        return createColorLut(colorList.toArray(new Color[ colorList.size() ]));
     925    }
     926
     927    /**
     928     * Draw gray heat map with current Graphics2D setting
     929     * @param gB              the common draw object to use
     930     * @param mv              the meta data to current displayed area
     931     * @param listSegm        segments visible in the current scope of mv
     932     * @param foreComp        composite use to draw foreground objects
     933     * @param foreStroke      stroke use to draw foreground objects
     934     * @param backComp        composite use to draw background objects
     935     * @param backStroke      stroke use to draw background objects
     936     */
     937    private void drawHeatGrayMap(Graphics2D gB, MapView mv, List<WayPoint> listSegm,
     938                                 Composite foreComp, Stroke foreStroke,
     939                                 Composite backComp, Stroke backStroke) {
     940
     941        // draw foreground
     942        boolean drawForeground = foreComp != null && foreStroke != null;
     943
     944        // set initial values
     945        gB.setStroke(backStroke); gB.setComposite(backComp);
     946
     947        // for all points, draw single lines by using optimize drawing
     948        for (WayPoint trkPnt : listSegm) {
     949
     950            // something to paint or color changed (new segment needed, decrease performance ;-()
     951            if (!trkPnt.drawLine && !heatMapPolyX.isEmpty()) {
     952
     953                // convert to primitive type
     954                final int[] polyXArr = heatMapPolyX.stream().mapToInt(Integer::intValue).toArray();
     955                final int[] polyYArr = heatMapPolyY.stream().mapToInt(Integer::intValue).toArray();
     956
     957                // a.) draw background
     958                gB.drawPolyline(polyXArr, polyYArr, polyXArr.length);
     959
     960                // b.) draw extra foreground
     961                if (drawForeground && heatMapDrawExtraLine) {
     962
     963                    gB.setStroke(foreStroke); gB.setComposite(foreComp);
     964                    gB.drawPolyline(polyXArr, polyYArr, polyXArr.length);
     965                    gB.setStroke(backStroke); gB.setComposite(backComp);
     966                }
     967
     968                // drop used pints
     969                heatMapPolyX.clear(); heatMapPolyY.clear();
     970
     971            } else {
     972
     973                // get transformed coordinates
     974                final Point paintPnt = mv.getPoint(trkPnt.getEastNorth());
     975
     976                // store only the integer part (make sense because pixel is 1:1 here)
     977                heatMapPolyX.add((int) paintPnt.getX());
     978                heatMapPolyY.add((int) paintPnt.getY());
     979            }
     980        }
     981    }
     982
     983    /**
     984     * Map the gray map to heat map and draw them with current Graphics2D setting
     985     * @param g               the common draw object to use
     986     * @param imgGray         gray scale input image
     987     * @param sampleRaster    the line with for drawing
     988     */
     989    private void drawHeatMapGrayMap(Graphics2D g, BufferedImage imgGray, int sampleRaster) {
     990
     991        final int[] imgPixels = ((DataBufferInt) imgGray.getRaster().getDataBuffer()).getData();
     992
     993        // samples offset and bounds are scaled with line width derived from zoom level
     994        final int offX = Math.max(1, sampleRaster / 2);
     995        final int offY = Math.max(1, sampleRaster / 2);
     996
     997        final int maxPixelX = imgGray.getWidth();
     998        final int maxPixelY = imgGray.getHeight();
     999
     1000        int lastPixelY = 0;
     1001        int lastPixelColor = 0;
     1002
     1003        // resample gray scale image with line linear weight of next sample in line
     1004        // process each line and draw pixels / rectangles with same color with one operations
     1005        for (int x = 0; x < maxPixelX; x += offX) {
     1006            for (int y = 0; y < maxPixelY; y += offY) {
     1007
     1008                int thePixelColor = 0;
     1009
     1010                // sample the image (it is gray scale)
     1011                int offset = (x * maxPixelX) + y;
     1012
     1013                // merge next pixels of window of line
     1014                for (int k = 0; k < offX && offset + k < imgPixels.length; k++) {
     1015                    thePixelColor += imgPixels[offset+k] & 0xFF;
     1016                }
     1017
     1018                // mean value
     1019                thePixelColor /= offX;
     1020
     1021                // restart -> use initial sample
     1022                if (0 == y) {
     1023                    lastPixelY = 0; lastPixelColor = thePixelColor;
     1024                }
     1025
     1026                // different color to last one ?
     1027                if (Math.abs(lastPixelColor - thePixelColor) > 1) {
     1028
     1029                    // draw only foreground pixels, skip small variations
     1030                    if (lastPixelColor > 1+1) {
     1031
     1032                        // gray to RGB mapping
     1033                        g.setColor(heatMapLutColor[ lastPixelColor ]);
     1034
     1035                        // start point for draw (
     1036                        int yN = lastPixelY > 0 ? lastPixelY : y;
     1037
     1038                        // box from from last Y pixel to current pixel
     1039                        if (offX < sampleRaster) {
     1040                            g.fillRect(yN, x, offY + y - yN, offX);
     1041                        } else {
     1042                            g.drawRect(yN, x, offY + y - yN, offX);
     1043                        }
     1044                    }
     1045                    // restart detection
     1046                    lastPixelY = y; lastPixelColor = thePixelColor;
     1047                }
     1048            }
     1049        }
     1050    }
     1051
     1052    /**
     1053     * Collect and draw GPS segments and displays a heat-map
     1054     * @param g               the common draw object to use
     1055     * @param mv              the meta data to current displayed area
     1056     * @param visibleSegments segments visible in the current scope of mv
     1057     */
     1058    private void drawHeatMap(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
     1059
     1060        // get bounds of screen image and projection, zoom and adjust input parameters
     1061        final Rectangle screenBounds = g.getDeviceConfiguration().getBounds();
     1062        final double zoomScale = mv.getScale();
     1063
     1064        // adjust global settings
     1065        final int globalLineWidth = Math.min(Math.max(lineWidth, 1), 20);
     1066
     1067        // 1st setup virtual paint area ----------------------------------------
     1068
     1069        // HACK: sometime screen bounds does not return valid values when picture is shifted
     1070        // therefore we use a bigger area to avoid missing parts of image
     1071        screenBounds.width = screenBounds.width * 3 / 2;
     1072        screenBounds.height = screenBounds.height * 3 / 2;
     1073
     1074        // new image buffer needed
     1075        final boolean imageSetup = null == heatMapImgGray || !heatMapCacheScreenBounds.equals(screenBounds);
     1076
     1077        // screen bounds changed, need new image buffer ?
     1078        if (imageSetup) {
     1079            // we would use a "pure" grayscale image, but there is not efficient way to map gray scale values to RGB)
     1080            heatMapImgGray = new BufferedImage(screenBounds.width, screenBounds.height, BufferedImage.TYPE_INT_ARGB);
     1081            heatMapGraph2d = heatMapImgGray.createGraphics();
     1082            heatMapGraph2d.setBackground(new Color(0, 0, 0, 255));
     1083            heatMapGraph2d.setColor(Color.WHITE);
     1084
     1085            // cache it
     1086            heatMapCacheScreenBounds = screenBounds;
     1087        }
     1088
     1089        // 2nd. determine current scale factors -------------------------------
     1090
     1091        // the line width (foreground: draw extra small footprint line of track)
     1092        final int lineWidthB = Math.max((int) (globalLineWidth / zoomScale) + 1, 2);
     1093        final int lineWidthF = lineWidthB > 2 ? (globalLineWidth - 1) : 0;
     1094
     1095        // recalculation of image needed
     1096        final boolean imageRecalc = heatMapCacheVisibleSegments != visibleSegments.size() ||
     1097                                    heatMapCacheZoomScale != zoomScale ||
     1098                                    heatMapCacheLineWith != globalLineWidth;
     1099
     1100        // 3rd Calculate the heat map data by draw GPX traces with alpha value ----------
     1101
     1102        // need re-generation of gray image ?
     1103        if (imageSetup || imageRecalc) {
     1104
     1105            // clear background
     1106            heatMapGraph2d.clearRect(0, 0, heatMapImgGray.getWidth(), heatMapImgGray.getHeight());
     1107
     1108            // alpha combines both values, therefore the foreground shall be lighter
     1109            final float lineAlphaB = Math.min(Math.max((0.40f/(float) zoomScale)/(globalLineWidth + 1), 0.001f), 0.50f);
     1110            final float lineAlphaF = lineAlphaB / 1.5f;
     1111
     1112            // derive draw parameters and draw
     1113            drawHeatGrayMap(heatMapGraph2d, mv, visibleSegments,
     1114                            lineWidthF > 1 ? AlphaComposite.SrcOver.derive(lineAlphaF) : null,
     1115                            new BasicStroke(lineWidthF, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND),
     1116                            AlphaComposite.SrcOver.derive(lineAlphaB),
     1117                            new BasicStroke(lineWidthB, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
     1118
     1119            // remember draw parameters
     1120            heatMapCacheVisibleSegments = visibleSegments.size();
     1121            heatMapCacheZoomScale = zoomScale;
     1122            heatMapCacheLineWith = lineWidth;
     1123        }
     1124
     1125        // 4th. Draw data on target layer, map data via color lookup table --------------
     1126        drawHeatMapGrayMap(g, heatMapImgGray, lineWidthB);
     1127    }
     1128
     1129    /**
     1130     * Apply default color configuration to way segments
     1131     * @param visibleSegments segments visible in the current scope of mv
     1132     */
    5531133    private void fixColors(List<WayPoint> visibleSegments) {
    5541134        for (WayPoint trkPnt : visibleSegments) {
     
    5651145        if ((computeCacheMaxLineLengthUsed != maxLineLength) || (!neutralColor.equals(computeCacheColorUsed))
    5661146                || (computeCacheColored != colored) || (computeCacheColorTracksTune != colorTracksTune)
    567                 || (computeCacheColorDynamic != colorModeDynamic)) {
     1147                || (computeCacheColorDynamic != colorModeDynamic)
     1148                || (computeCacheHeatMapDrawColorTableIdx != heatMapDrawColorTableIdx)
     1149      ) {
    5681150            computeCacheMaxLineLengthUsed = maxLineLength;
    5691151            computeCacheInSync = false;
     
    5721154            computeCacheColorTracksTune = colorTracksTune;
    5731155            computeCacheColorDynamic = colorModeDynamic;
    574         }
    575     }
    576 
     1156            computeCacheHeatMapDrawColorTableIdx = heatMapDrawColorTableIdx;
     1157        }
     1158    }
     1159
     1160    /**
     1161     *  callback when data is changed, invalidate cached configuration parameters
     1162     */
    5771163    public void dataChanged() {
    5781164        computeCacheInSync = false;
    5791165    }
    5801166
     1167    /**
     1168     * Draw all GPX arrays
     1169     * @param g               the common draw object to use
     1170     * @param mv              the meta data to current displayed area
     1171     */
    5811172    public void drawColorBar(Graphics2D g, MapView mv) {
    5821173        int w = mv.getWidth();
     1174
     1175        // set do default
     1176        g.setComposite(AlphaComposite.SrcOver.derive(1.00f));
     1177
    5831178        if (colored == ColorMode.HDOP) {
    5841179            hdopScale.drawColorBar(g, w-30, 50, 20, 100, 1.0);
  • trunk/src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java

    r11039 r11431  
    5656    private final JRadioButton colorTypeDilution = new JRadioButton(tr("Dilution of Position (red = high, green = low, if available)"));
    5757    private final JRadioButton colorTypeTime = new JRadioButton(tr("Track date"));
     58    private final JRadioButton colorTypeHeatMap = new JRadioButton(tr("Heat Map (dark = few tracks, bright = many tracks)"));
    5859    private final JRadioButton colorTypeNone = new JRadioButton(tr("Single Color (can be customized for named layers)"));
    5960    private final JRadioButton colorTypeGlobal = new JRadioButton(tr("Use global settings"));
    6061    private final JosmComboBox<String> colorTypeVelocityTune = new JosmComboBox<>(new String[] {tr("Car"), tr("Bicycle"), tr("Foot")});
     62    private final JosmComboBox<String> colorTypeHeatMapTune = new JosmComboBox<>(new String[] {tr("User"), tr("Inferno"), tr("Viridis"),
     63                                                                                 tr("Wood"), tr("Heat")});
    6164    private final JCheckBox makeAutoMarkers = new JCheckBox(tr("Create markers when reading GPX"));
    6265    private final JCheckBox drawGpsArrows = new JCheckBox(tr("Draw Direction Arrows"));
     
    6972    private final JosmTextField audioWaypointLabelPattern = new JosmTextField();
    7073    private final JCheckBox useGpsAntialiasing = new JCheckBox(tr("Smooth GPX graphics (antialiasing)"));
     74    private final JCheckBox drawLineWithAlpha = new JCheckBox(tr("Draw with Opacity (alpha blending) "));
    7175
    7276    private String layerName;
     
    180184        });
    181185        drawGpsArrows.setToolTipText(tr("Draw direction arrows for lines, connecting GPS points."));
    182         add(drawGpsArrows, GBC.eop().insets(40, 0, 0, 0));
     186        add(drawGpsArrows, GBC.eop().insets(20, 0, 0, 0));
    183187
    184188        // drawGpsArrowsFast
    185189        drawGpsArrowsFast.setToolTipText(tr("Draw the direction arrows using table lookups instead of complex math."));
    186         add(drawGpsArrowsFast, GBC.eop().insets(60, 0, 0, 0));
     190        add(drawGpsArrowsFast, GBC.eop().insets(40, 0, 0, 0));
    187191        ExpertToggleAction.addVisibilitySwitcher(drawGpsArrowsFast);
    188192
    189193        // drawGpsArrowsMinDist
    190194        drawGpsArrowsMinDist.setToolTipText(tr("Do not draw arrows if they are not at least this distance away from the last one."));
    191         add(new JLabel(tr("Minimum distance (pixels)")), GBC.std().insets(60, 0, 0, 0));
     195        add(new JLabel(tr("Minimum distance (pixels)")), GBC.std().insets(40, 0, 0, 0));
    192196        add(drawGpsArrowsMinDist, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 5));
    193197
     
    210214        add(useGpsAntialiasing, GBC.eop().insets(20, 0, 0, 0));
    211215        ExpertToggleAction.addVisibilitySwitcher(useGpsAntialiasing);
     216
     217        // alpha blending
     218        drawLineWithAlpha.setToolTipText(tr("Apply dynamic alpha-blending and adjust width based on zoom level for all GPX lines."));
     219        add(drawLineWithAlpha, GBC.eop().insets(20, 0, 0, 0));
     220        ExpertToggleAction.addVisibilitySwitcher(drawLineWithAlpha);
    212221
    213222        // colorTracks
     
    221230        colorGroup.add(colorTypeDilution);
    222231        colorGroup.add(colorTypeTime);
     232        colorGroup.add(colorTypeHeatMap);
    223233
    224234        colorTypeVelocity.addChangeListener(e -> {
     
    226236            colorDynamic.setEnabled(colorTypeVelocity.isSelected() || colorTypeDilution.isSelected());
    227237        });
     238
     239        colorTypeHeatMap.addChangeListener(e -> {
     240            colorTypeHeatMapTune.setEnabled(colorTypeHeatMap.isSelected());
     241            colorDynamic.setEnabled(false);
     242        });
     243
    228244        colorTypeDilution.addChangeListener(e -> colorDynamic.setEnabled(colorTypeVelocity.isSelected() || colorTypeDilution.isSelected()));
    229245
     
    234250                tr("Colors points and track segments by dilution of position (HDOP). Your capture device needs to log that information."));
    235251        colorTypeTime.setToolTipText(tr("Colors points and track segments by its timestamp."));
     252        colorTypeHeatMap.setToolTipText(tr("Collected points and track segments for a position and displayed as heat map."));
    236253
    237254        // color Tracks by Velocity Tune
    238255        colorTypeVelocityTune.setToolTipText(tr("Allows to tune the track coloring for different average speeds."));
     256
     257        colorTypeHeatMapTune.setToolTipText(tr("Selects the color schema for heat map."));
    239258
    240259        add(Box.createVerticalGlue(), GBC.eol().insets(0, 20, 0, 0));
     
    250269        add(colorTypeDilution, GBC.eol().insets(40, 0, 0, 0));
    251270        add(colorTypeTime, GBC.eol().insets(40, 0, 0, 0));
     271        add(colorTypeHeatMap, GBC.std().insets(40, 0, 0, 0));
     272        add(colorTypeHeatMapTune, GBC.eop().insets(5, 0, 0, 5));
     273
    252274        ExpertToggleAction.addVisibilitySwitcher(colorTypeDirection);
    253275        ExpertToggleAction.addVisibilitySwitcher(colorTypeDilution);
     
    316338        drawRawGpsMaxLineLength.setText(Integer.toString(Main.pref.getInteger("draw.rawgps.max-line-length", layerName, 200)));
    317339        drawLineWidth.setText(Integer.toString(Main.pref.getInteger("draw.rawgps.linewidth", layerName, 0)));
     340        drawLineWithAlpha.setSelected(Main.pref.getBoolean("draw.rawgps.lines.alpha-blend", layerName, false));
    318341        forceRawGpsLines.setSelected(Main.pref.getBoolean("draw.rawgps.lines.force", layerName, false));
    319342        drawGpsArrows.setSelected(Main.pref.getBoolean("draw.rawgps.direction", layerName, false));
     
    323346        largeGpsPoints.setSelected(Main.pref.getBoolean("draw.rawgps.large", layerName, false));
    324347        useGpsAntialiasing.setSelected(Main.pref.getBoolean("mappaint.gpx.use-antialiasing", false));
     348
    325349        drawRawGpsLinesActionListener.actionPerformed(null);
    326350
     
    337361            case 3: colorTypeDirection.setSelected(true); break;
    338362            case 4: colorTypeTime.setSelected(true); break;
     363            case 5: colorTypeHeatMap.setSelected(true); break;
    339364            default: Main.warn("Unknown color type: " + colorType);
    340365            }
     
    342367            colorTypeVelocityTune.setSelectedIndex(ccts == 10 ? 2 : (ccts == 20 ? 1 : 0));
    343368            colorTypeVelocityTune.setEnabled(colorTypeVelocity.isSelected() && colorTypeVelocity.isEnabled());
     369
     370            colorTypeHeatMapTune.setSelectedIndex(Main.pref.getInteger("draw.rawgps.heatmap.colormap", layerName, 0));
     371            colorTypeHeatMapTune.setEnabled(colorTypeHeatMap.isSelected() && colorTypeHeatMap.isEnabled());
     372
    344373            colorDynamic.setSelected(Main.pref.getBoolean("draw.rawgps.colors.dynamic", layerName, false));
    345374            colorDynamic.setEnabled(colorTypeVelocity.isSelected() || colorTypeDilution.isSelected());
     
    386415        Main.pref.put("draw.rawgps.large"+layerNameDot, largeGpsPoints.isSelected());
    387416        Main.pref.put("draw.rawgps.linewidth"+layerNameDot, drawLineWidth.getText());
     417        Main.pref.put("draw.rawgps.lines.alpha-blend"+layerNameDot, drawLineWithAlpha.isSelected());
     418
    388419        Main.pref.put("mappaint.gpx.use-antialiasing", useGpsAntialiasing.isSelected());
    389420
     
    404435        } else if (colorTypeTime.isSelected()) {
    405436            Main.pref.putInteger("draw.rawgps.colors"+layerNameDot, 4);
     437        } else if (colorTypeHeatMap.isSelected()) {
     438            Main.pref.putInteger("draw.rawgps.colors"+layerNameDot, 5);
    406439        } else {
    407440            Main.pref.putInteger("draw.rawgps.colors"+layerNameDot, 0);
     
    410443        int ccti = colorTypeVelocityTune.getSelectedIndex();
    411444        Main.pref.putInteger("draw.rawgps.colorTracksTune"+layerNameDot, ccti == 2 ? 10 : (ccti == 1 ? 20 : 45));
     445
     446        Main.pref.putInteger("draw.rawgps.heatmap.colormap"+layerNameDot, colorTypeHeatMapTune.getSelectedIndex());
     447
    412448        return false;
    413449    }
Note: See TracChangeset for help on using the changeset viewer.