Ticket #7979: 7979.patch

File 7979.patch, 132.9 KB (added by Don-vip, 13 years ago)
  • core/src/org/openstreetmap/josm/data/osm/visitor/paint/AbstractMapRenderer.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.osm.visitor.paint;
    33
     4import java.awt.Color;
    45import java.awt.Graphics2D;
     6import java.awt.Point;
     7import java.awt.geom.GeneralPath;
     8import java.awt.geom.Point2D;
     9import java.util.Iterator;
    510
     11import org.openstreetmap.josm.Main;
     12import org.openstreetmap.josm.data.osm.BBox;
     13import org.openstreetmap.josm.data.osm.DataSet;
     14import org.openstreetmap.josm.data.osm.Node;
     15import org.openstreetmap.josm.data.osm.Way;
     16import org.openstreetmap.josm.data.osm.WaySegment;
    617import org.openstreetmap.josm.gui.NavigatableComponent;
    718import org.openstreetmap.josm.tools.CheckParameterUtil;
    819
     
    1627    protected Graphics2D g;
    1728    /** the map viewport - provides projection and hit detection functionality */
    1829    protected NavigatableComponent nc;
     30   
    1931    /** if true, the paint visitor shall render OSM objects such that they
    20      * look inactive. Example: rendering of data in an inactive layer using light gray as color only.
    21      */
     32     * look inactive. Example: rendering of data in an inactive layer using light gray as color only. */
    2233    protected boolean isInactiveMode;
     34    /** Color Preference for background */
     35    protected Color backgroundColor;
     36    /** Color Preference for inactive objects */
     37    protected Color inactiveColor;
     38    /** Color Preference for selected objects */
     39    protected Color selectedColor;
     40    /** Color Preference for nodes */
     41    protected Color nodeColor;
     42
     43    /** Color Preference for hightlighted objects */
     44    protected Color highlightColor;
     45    /** Preference: size of virtual nodes (0 displayes display) */
     46    protected int virtualNodeSize;
     47    /** Preference: minimum space (displayed way length) to display virtual nodes */
     48    protected int virtualNodeSpace;
    2349
     50    /** Preference: minimum space (displayed way length) to display segment numbers */
     51    protected int segmentNumberSpace;
     52   
    2453    /**
    2554     * <p>Creates an abstract paint visitor</p>
    2655     *
     
    3867        this.nc = nc;
    3968        this.isInactiveMode = isInactiveMode;
    4069    }
     70   
     71    /**
     72     * Draw the node as small rectangle with the given color.
     73     *
     74     * @param n  The node to draw.
     75     * @param color The color of the node.
     76     */
     77    public abstract void drawNode(Node n, Color color, int size, boolean fill);
     78
     79    /**
     80     * Draw an number of the order of the two consecutive nodes within the
     81     * parents way
     82     *
     83     * @param p1 First point of the way segment.
     84     * @param p2 Second point of the way segment.
     85     * @param orderNumber The number of the segment in the way.
     86     */
     87    protected void drawOrderNumber(Point p1, Point p2, int orderNumber, Color clr) {
     88        if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
     89            String on = Integer.toString(orderNumber);
     90            int strlen = on.length();
     91            int x = (p1.x+p2.x)/2 - 4*strlen;
     92            int y = (p1.y+p2.y)/2 + 4;
     93
     94            if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) {
     95                y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
     96            }
     97
     98            g.setColor(backgroundColor);
     99            g.fillRect(x-1, y-12, 8*strlen+1, 14);
     100            g.setColor(clr);
     101            g.drawString(on, x, y);
     102        }
     103    }
     104   
     105    /**
     106     * Draws virtual nodes.
     107     *
     108     * @param data The data set being rendered.
     109     * @param bbox The bounding box being displayed.
     110     */
     111    public void drawVirtualNodes(DataSet data, BBox bbox) {
     112        if (virtualNodeSize == 0 || data == null || bbox == null)
     113            return;
     114        // print normal virtual nodes
     115        GeneralPath path = new GeneralPath();
     116        for (Way osm : data.searchWays(bbox)) {
     117            if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) {
     118                visitVirtual(path, osm);
     119            }
     120        }
     121        g.setColor(nodeColor);
     122        g.draw(path);
     123        try {
     124            // print highlighted virtual nodes. Since only the color changes, simply
     125            // drawing them over the existing ones works fine (at least in their current
     126            // simple style)
     127            path = new GeneralPath();
     128            for (WaySegment wseg: data.getHighlightedVirtualNodes()) {
     129                if (wseg.way.isUsable() && !wseg.way.isDisabled()) {
     130                    visitVirtual(path, wseg.toWay());
     131                }
     132            }
     133            g.setColor(highlightColor);
     134            g.draw(path);
     135        } catch (ArrayIndexOutOfBoundsException e) {
     136            // Silently ignore any ArrayIndexOutOfBoundsException that may be raised
     137            // if the way has changed while being rendered (fix #7979)
     138        }
     139    }
     140   
     141    /**
     142     * Reads the color definitions from preferences. This function is <code>public</code>, so that
     143     * color names in preferences can be displayed even without calling the wireframe display before.
     144     */
     145    public void getColors() {
     146        this.backgroundColor = PaintColors.BACKGROUND.get();
     147        this.inactiveColor = PaintColors.INACTIVE.get();
     148        this.selectedColor = PaintColors.SELECTED.get();
     149        this.nodeColor = PaintColors.NODE.get();
     150        this.highlightColor = PaintColors.HIGHLIGHT.get();
     151    }
     152   
     153    /**
     154     * Reads all the settings from preferences. Calls the @{link #getColors}
     155     * function.
     156     *
     157     * @param virtual <code>true</code> if virtual nodes are used
     158     */
     159    protected void getSettings(boolean virtual) {
     160        this.virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
     161        this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
     162        this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
     163        getColors();
     164    }
     165   
     166    /**
     167     * Checks if a way segemnt is large enough for additional information display.
     168     *
     169     * @param p1 First point of the way segment.
     170     * @param p2 Second point of the way segment.
     171     * @param space The free space to check against.
     172     * @return <code>true</code> if segment is larger than required space
     173     */
     174    public static boolean isLargeSegment(Point2D p1, Point2D p2, int space) {
     175        double xd = Math.abs(p1.getX()-p2.getX());
     176        double yd = Math.abs(p1.getY()-p2.getY());
     177        return (xd+yd > space);
     178    }
     179   
     180    /**
     181     * Checks if segment is visible in display.
     182     *
     183     * @param p1 First point of the way segment.
     184     * @param p2 Second point of the way segment.
     185     * @return <code>true</code> if segment is visible.
     186     */
     187    protected boolean isSegmentVisible(Point p1, Point p2) {
     188        if ((p1.x < 0) && (p2.x < 0)) return false;
     189        if ((p1.y < 0) && (p2.y < 0)) return false;
     190        if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
     191        if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
     192        return true;
     193    }
     194   
     195    /**
     196     * Creates path for drawing virtual nodes for one way.
     197     *
     198     * @param path The path to append drawing to.
     199     * @param w The ways to draw node for.
     200     */
     201    public void visitVirtual(GeneralPath path, Way w) {
     202        Iterator<Node> it = w.getNodes().iterator();
     203        if (it.hasNext()) {
     204            Point lastP = nc.getPoint(it.next());
     205            while (it.hasNext())
     206            {
     207                Point p = nc.getPoint(it.next());
     208                if (isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
     209                {
     210                    int x = (p.x+lastP.x)/2;
     211                    int y = (p.y+lastP.y)/2;
     212                    path.moveTo(x-virtualNodeSize, y);
     213                    path.lineTo(x+virtualNodeSize, y);
     214                    path.moveTo(x, y-virtualNodeSize);
     215                    path.lineTo(x, y+virtualNodeSize);
     216                }
     217                lastP = p;
     218            }
     219        }
     220    }
    41221}
  • core/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java

     
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.data.osm.visitor.paint;
    3 
    4 import java.awt.AlphaComposite;
    5 import java.awt.BasicStroke;
    6 import java.awt.Color;
    7 import java.awt.Font;
    8 import java.awt.FontMetrics;
    9 import java.awt.Graphics2D;
    10 import java.awt.Image;
    11 import java.awt.Point;
    12 import java.awt.Polygon;
    13 import java.awt.Rectangle;
    14 import java.awt.Shape;
    15 import java.awt.TexturePaint;
    16 import java.awt.font.FontRenderContext;
    17 import java.awt.font.GlyphVector;
    18 import java.awt.font.LineMetrics;
    19 import java.awt.geom.AffineTransform;
    20 import java.awt.geom.GeneralPath;
    21 import java.awt.geom.Path2D;
    22 import java.awt.geom.Point2D;
    23 import java.awt.geom.Rectangle2D;
    24 import java.util.Arrays;
    25 import java.util.Collection;
    26 import java.util.Iterator;
    27 import java.util.List;
    28 
    29 import javax.swing.ImageIcon;
    30 
    31 import org.openstreetmap.josm.Main;
    32 import org.openstreetmap.josm.data.coor.EastNorth;
    33 import org.openstreetmap.josm.data.osm.Node;
    34 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    35 import org.openstreetmap.josm.data.osm.OsmUtils;
    36 import org.openstreetmap.josm.data.osm.Relation;
    37 import org.openstreetmap.josm.data.osm.RelationMember;
    38 import org.openstreetmap.josm.data.osm.Way;
    39 import org.openstreetmap.josm.data.osm.WaySegment;
    40 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
    41 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
    42 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
    43 import org.openstreetmap.josm.gui.NavigatableComponent;
    44 import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle;
    45 import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.HorizontalTextAlignment;
    46 import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.VerticalTextAlignment;
    47 import org.openstreetmap.josm.gui.mappaint.MapImage;
    48 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol;
    49 import org.openstreetmap.josm.gui.mappaint.TextElement;
    50 import org.openstreetmap.josm.tools.ImageProvider;
    51 import org.openstreetmap.josm.tools.Pair;
    52 import org.openstreetmap.josm.tools.Utils;
    53 
    54 public class MapPainter {
    55 
    56     private final Graphics2D g;
    57     private final NavigatableComponent nc;
    58     private final boolean inactive;
    59     private final MapPaintSettings settings;
    60     private final Collection<WaySegment> highlightWaySegments;
    61 
    62     private final boolean useStrokes;
    63     private final boolean showNames;
    64     private final boolean showIcons;
    65 
    66     private final boolean  isOutlineOnly;
    67 
    68     private final Color inactiveColor;
    69     private final Color selectedColor;
    70     private final Color relationSelectedColor;
    71     private final Color nodeColor;
    72     private final Color highlightColor;
    73     private final Color highlightColorTransparent;
    74     private final Color backgroundColor;
    75 
    76     private final Font orderFont;
    77     private final int virtualNodeSize;
    78     private final int virtualNodeSpace;
    79     private final int segmentNumberSpace;
    80 
    81     private final double circum;
    82 
    83     private final boolean leftHandTraffic;
    84 
    85     private static final double PHI = Math.toRadians(20);
    86     private static final double cosPHI = Math.cos(PHI);
    87     private static final double sinPHI = Math.sin(PHI);
    88 
    89     public MapPainter(MapPaintSettings settings, Graphics2D g,
    90             boolean inactive, NavigatableComponent nc, boolean virtual,
    91             double circum, boolean leftHandTraffic,
    92             Collection<WaySegment> highlightWaySegments){
    93         this.settings = settings;
    94         this.g = g;
    95         this.inactive = inactive;
    96         this.nc = nc;
    97         this.highlightWaySegments = highlightWaySegments;
    98         this.useStrokes = settings.getUseStrokesDistance() > circum;
    99         this.showNames = settings.getShowNamesDistance() > circum;
    100         this.showIcons = settings.getShowIconsDistance() > circum;
    101 
    102         this.isOutlineOnly = settings.isOutlineOnly();
    103 
    104         this.inactiveColor = PaintColors.INACTIVE.get();
    105         this.selectedColor = PaintColors.SELECTED.get();
    106         this.relationSelectedColor = PaintColors.RELATIONSELECTED.get();
    107         this.nodeColor = PaintColors.NODE.get();
    108         this.highlightColor = PaintColors.HIGHLIGHT.get();
    109         this.highlightColorTransparent = new Color(highlightColor.getRed(), highlightColor.getGreen(), highlightColor.getBlue(), 100);
    110         this.backgroundColor = PaintColors.getBackgroundColor();
    111 
    112         this.orderFont = new Font(Main.pref.get("mappaint.font", "Helvetica"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
    113         this.virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
    114         this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
    115         this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
    116 
    117         this.circum = circum;
    118         this.leftHandTraffic = leftHandTraffic;
    119     }
    120 
    121     /**
    122      * draw way
    123      * @param showOrientation show arrows that indicate the technical orientation of
    124      *              the way (defined by order of nodes)
    125      * @param showOneway show symbols that indicate the direction of the feature,
    126      *              e.g. oneway street or waterway
    127      * @param onewayReversed for oneway=-1 and similar
    128      */
    129     public void drawWay(Way way, Color color, BasicStroke line, BasicStroke dashes, Color dashedColor, float offset,
    130             boolean showOrientation, boolean showHeadArrowOnly,
    131             boolean showOneway, boolean onewayReversed) {
    132 
    133         GeneralPath path = new GeneralPath();
    134         GeneralPath orientationArrows = showOrientation ? new GeneralPath() : null;
    135         GeneralPath onewayArrows = showOneway ? new GeneralPath() : null;
    136         GeneralPath onewayArrowsCasing = showOneway ? new GeneralPath() : null;
    137         Rectangle bounds = g.getClipBounds();
    138         bounds.grow(100, 100);                  // avoid arrow heads at the border
    139 
    140         double wayLength = 0;
    141         Point lastPoint = null;
    142         boolean initialMoveToNeeded = true;
    143         List<Node> wayNodes = way.getNodes();
    144         if (wayNodes.size() < 2) return;
    145 
    146         // only highlight the segment if the way itself is not highlighted
    147         if(!way.isHighlighted()) {
    148             GeneralPath highlightSegs = null;
    149             for(WaySegment ws : highlightWaySegments) {
    150                 if(ws.way != way || ws.lowerIndex < offset) {
    151                     continue;
    152                 }
    153                 if(highlightSegs == null) {
    154                     highlightSegs = new GeneralPath();
    155                 }
    156 
    157                 Point p1 = nc.getPoint(ws.getFirstNode());
    158                 Point p2 = nc.getPoint(ws.getSecondNode());
    159                 highlightSegs.moveTo(p1.x, p1.y);
    160                 highlightSegs.lineTo(p2.x, p2.y);
    161             }
    162 
    163             drawPathHighlight(highlightSegs, line);
    164         }
    165 
    166 
    167         Iterator<Point> it = new OffsetIterator(wayNodes, offset);
    168         while (it.hasNext()) {
    169             Point p = it.next();
    170             if (lastPoint != null) {
    171                 Point p1 = lastPoint;
    172                 Point p2 = p;
    173 
    174                 /**
    175                  * Do custom clipping to work around openjdk bug. It leads to
    176                  * drawing artefacts when zooming in a lot. (#4289, #4424)
    177                  * (Looks like int overflow.)
    178                  */
    179                 LineClip clip = new LineClip(p1, p2, bounds);
    180                 if (clip.execute()) {
    181                     if (!p1.equals(clip.getP1())) {
    182                         p1 = clip.getP1();
    183                         path.moveTo(p1.x, p1.y);
    184                     } else if (initialMoveToNeeded) {
    185                         initialMoveToNeeded = false;
    186                         path.moveTo(p1.x, p1.y);
    187                     }
    188                     p2 = clip.getP2();
    189                     path.lineTo(p2.x, p2.y);
    190 
    191                     /* draw arrow */
    192                     if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
    193                         final double segmentLength = p1.distance(p2);
    194                         if (segmentLength != 0.0) {
    195                             final double l =  (10. + line.getLineWidth()) / segmentLength;
    196 
    197                             final double sx = l * (p1.x - p2.x);
    198                             final double sy = l * (p1.y - p2.y);
    199 
    200                             orientationArrows.moveTo (p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy);
    201                             orientationArrows.lineTo(p2.x, p2.y);
    202                             orientationArrows.lineTo (p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy);
    203                         }
    204                     }
    205                     if (showOneway) {
    206                         final double segmentLength = p1.distance(p2);
    207                         if (segmentLength != 0.0) {
    208                             final double nx = (p2.x - p1.x) / segmentLength;
    209                             final double ny = (p2.y - p1.y) / segmentLength;
    210 
    211                             final double interval = 60;
    212                             // distance from p1
    213                             double dist = interval - (wayLength % interval);
    214 
    215                             while (dist < segmentLength) {
    216                                 for (Pair<Float, GeneralPath> sizeAndPath : Arrays.asList(new Pair[] {
    217                                         new Pair<Float, GeneralPath>(3f, onewayArrowsCasing),
    218                                         new Pair<Float, GeneralPath>(2f, onewayArrows)})) {
    219 
    220                                     // scale such that border is 1 px
    221                                     final double fac = - (onewayReversed ? -1 : 1) * sizeAndPath.a * (1 + sinPHI) / (sinPHI * cosPHI);
    222                                     final double sx = nx * fac;
    223                                     final double sy = ny * fac;
    224 
    225                                     // Attach the triangle at the incenter and not at the tip.
    226                                     // Makes the border even at all sides.
    227                                     final double x = p1.x + nx * (dist + (onewayReversed ? -1 : 1) * (sizeAndPath.a / sinPHI));
    228                                     final double y = p1.y + ny * (dist + (onewayReversed ? -1 : 1) * (sizeAndPath.a / sinPHI));
    229 
    230                                     sizeAndPath.b.moveTo(x, y);
    231                                     sizeAndPath.b.lineTo (x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
    232                                     sizeAndPath.b.lineTo (x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
    233                                     sizeAndPath.b.lineTo(x, y);
    234                                 }
    235                                 dist += interval;
    236                             }
    237                         }
    238                         wayLength += segmentLength;
    239                     }
    240                 }
    241             }
    242             lastPoint = p;
    243         }
    244         if(way.isHighlighted()) {
    245             drawPathHighlight(path, line);
    246         }
    247         displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor);
    248     }
    249 
    250     /**
    251      * Iterates over a list of Way Nodes and returns screen coordinates that
    252      * represent a line that is shifted by a certain offset perpendicular
    253      * to the way direction.
    254      *
    255      * There is no intention, to handle consecutive duplicate Nodes in a
    256      * perfect way, but it is should not throw an exception.
    257      */
    258     public class OffsetIterator implements Iterator<Point> {
    259 
    260         private List<Node> nodes;
    261         private float offset;
    262         private int idx;
    263 
    264         private Point prev = null;
    265         /* 'prev0' is a point that has distance 'offset' from 'prev' and the
    266          * line from 'prev' to 'prev0' is perpendicular to the way segment from
    267          * 'prev' to the next point.
    268          */
    269         private int x_prev0, y_prev0;
    270 
    271         public OffsetIterator(List<Node> nodes, float offset) {
    272             this.nodes = nodes;
    273             this.offset = offset;
    274             idx = 0;
    275         }
    276 
    277         @Override
    278         public boolean hasNext() {
    279             return idx < nodes.size();
    280         }
    281 
    282         @Override
    283         public Point next() {
    284             if (Math.abs(offset) < 0.1f) return nc.getPoint(nodes.get(idx++));
    285 
    286             Point current = nc.getPoint(nodes.get(idx));
    287 
    288             if (idx == nodes.size() - 1) {
    289                 ++idx;
    290                 return new Point(x_prev0 + current.x - prev.x, y_prev0 + current.y - prev.y);
    291             }
    292 
    293             Point next = nc.getPoint(nodes.get(idx+1));
    294 
    295             int dx_next = next.x - current.x;
    296             int dy_next = next.y - current.y;
    297             double len_next = Math.sqrt(dx_next*dx_next + dy_next*dy_next);
    298 
    299             if (len_next == 0) {
    300                 len_next = 1; // value does not matter, because dy_next and dx_next is 0
    301             }
    302 
    303             int x_current0 = current.x + (int) Math.round(offset * dy_next / len_next);
    304             int y_current0 = current.y - (int) Math.round(offset * dx_next / len_next);
    305 
    306             if (idx==0) {
    307                 ++idx;
    308                 prev = current;
    309                 x_prev0 = x_current0;
    310                 y_prev0 = y_current0;
    311                 return new Point(x_current0, y_current0);
    312             } else {
    313                 int dx_prev = current.x - prev.x;
    314                 int dy_prev = current.y - prev.y;
    315 
    316                 // determine intersection of the lines parallel to the two
    317                 // segments
    318                 int det = dx_next*dy_prev - dx_prev*dy_next;
    319 
    320                 if (det == 0) {
    321                     ++idx;
    322                     prev = current;
    323                     x_prev0 = x_current0;
    324                     y_prev0 = y_current0;
    325                     return new Point(x_current0, y_current0);
    326                 }
    327 
    328                 int m = dx_next*(y_current0 - y_prev0) - dy_next*(x_current0 - x_prev0);
    329 
    330                 int cx_ = x_prev0 + Math.round((float)m * dx_prev / det);
    331                 int cy_ = y_prev0 + Math.round((float)m * dy_prev / det);
    332                 ++idx;
    333                 prev = current;
    334                 x_prev0 = x_current0;
    335                 y_prev0 = y_current0;
    336                 return new Point(cx_, cy_);
    337             }
    338         }
    339 
    340         @Override
    341         public void remove() {
    342             throw new UnsupportedOperationException();
    343         }
    344     }
    345 
    346     private void displaySegments(GeneralPath path, GeneralPath orientationArrows, GeneralPath onewayArrows, GeneralPath onewayArrowsCasing,
    347             Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) {
    348         g.setColor(inactive ? inactiveColor : color);
    349         if (useStrokes) {
    350             g.setStroke(line);
    351         }
    352         g.draw(path);
    353 
    354         if(!inactive && useStrokes && dashes != null) {
    355             g.setColor(dashedColor);
    356             g.setStroke(dashes);
    357             g.draw(path);
    358         }
    359 
    360         if (orientationArrows != null) {
    361             g.setColor(inactive ? inactiveColor : color);
    362             g.setStroke(new BasicStroke(line.getLineWidth(), line.getEndCap(), BasicStroke.JOIN_MITER, line.getMiterLimit()));
    363             g.draw(orientationArrows);
    364         }
    365 
    366         if (onewayArrows != null) {
    367             g.setStroke(new BasicStroke(1, line.getEndCap(), BasicStroke.JOIN_MITER, line.getMiterLimit()));
    368             g.fill(onewayArrowsCasing);
    369             g.setColor(inactive ? inactiveColor : backgroundColor);
    370             g.fill(onewayArrows);
    371         }
    372 
    373         if(useStrokes) {
    374             g.setStroke(new BasicStroke());
    375         }
    376     }
    377 
    378     /**
    379      * highlights a given GeneralPath using the settings from BasicStroke to match the line's
    380      * style. Width of the highlight is hard coded.
    381      * @param path
    382      * @param line
    383      */
    384     private void drawPathHighlight(GeneralPath path, BasicStroke line) {
    385         if(path == null)
    386             return;
    387         g.setColor(highlightColorTransparent);
    388         float w = (line.getLineWidth() + 4);
    389         while(w >= line.getLineWidth()) {
    390             g.setStroke(new BasicStroke(w, line.getEndCap(), line.getLineJoin(), line.getMiterLimit()));
    391             g.draw(path);
    392             w -= 4;
    393         }
    394     }
    395 
    396     private boolean isSegmentVisible(Point p1, Point p2) {
    397         if ((p1.x < 0) && (p2.x < 0)) return false;
    398         if ((p1.y < 0) && (p2.y < 0)) return false;
    399         if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
    400         if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
    401         return true;
    402     }
    403 
    404     private static Boolean IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG = null;
    405 
    406     /**
    407      * Check, if this System has the GlyphVector double translation bug.
    408      *
    409      * With this bug, <code>gv.setGlyphTransform(i, trfm)</code> has a different
    410      * effect than on most other systems, namely the translation components
    411      * ("m02" & "m12", {@link AffineTransform}) appear to be twice as large, as
    412      * they actually are. The rotation is unaffected (scale & shear not tested
    413      * so far).
    414      *
    415      * This bug has only been observed on Mac OS X, see #7841.
    416      *
    417      * @return true, if the GlyphVector double translation bug is present on
    418      * this System
    419      */
    420     public static boolean isGlyphVectorDoubleTranslationBug() {
    421         if (IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG != null)
    422             return IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG;
    423         FontRenderContext frc = new FontRenderContext(null, false, false);
    424         Font font = new Font("Dialog", Font.PLAIN, 12);
    425         GlyphVector gv = font.createGlyphVector(frc, "x");
    426         gv.setGlyphTransform(0, AffineTransform.getTranslateInstance(1000, 1000));
    427         Shape shape = gv.getGlyphOutline(0);
    428         // x is about 1000 on normal stystems and about 2000 when the bug occurs
    429         int x = shape.getBounds().x;
    430         IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG = x > 1500;
    431         return IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG;
    432     }
    433 
    434     public void drawTextOnPath(Way way, TextElement text) {
    435         if (way == null || text == null)
    436             return;
    437         String name = text.getString(way);
    438         if (name == null || name.isEmpty())
    439             return;
    440 
    441         Polygon poly = new Polygon();
    442         Point lastPoint = null;
    443         Iterator<Node> it = way.getNodes().iterator();
    444         double pathLength = 0;
    445         long dx, dy;
    446         while (it.hasNext()) {
    447             Node n = it.next();
    448             Point p = nc.getPoint(n);
    449             poly.addPoint(p.x, p.y);
    450 
    451             if(lastPoint != null) {
    452                 dx = p.x - lastPoint.x;
    453                 dy = p.y - lastPoint.y;
    454                 pathLength += Math.sqrt(dx*dx + dy*dy);
    455             }
    456             lastPoint = p;
    457         }
    458 
    459         FontMetrics fontMetrics = g.getFontMetrics(text.font); // if slow, use cache
    460         Rectangle2D rec = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
    461 
    462         if (rec.getWidth() > pathLength)
    463             return;
    464 
    465         double t1 = (pathLength/2 - rec.getWidth()/2) / pathLength;
    466         double t2 = (pathLength/2 + rec.getWidth()/2) / pathLength;
    467 
    468         double[] p1 = pointAt(t1, poly, pathLength);
    469         double[] p2 = pointAt(t2, poly, pathLength);
    470        
    471         if (p1 == null || p2 == null)
    472             return;
    473 
    474         double angleOffset;
    475         double offsetSign;
    476         double tStart;
    477 
    478         if (p1[0] < p2[0] &&
    479                 p1[2] < Math.PI/2 &&
    480                 p1[2] > -Math.PI/2) {
    481             angleOffset = 0;
    482             offsetSign = 1;
    483             tStart = t1;
    484         } else {
    485             angleOffset = Math.PI;
    486             offsetSign = -1;
    487             tStart = t2;
    488         }
    489 
    490         FontRenderContext frc = g.getFontRenderContext();
    491         GlyphVector gv = text.font.createGlyphVector(frc, name);
    492 
    493         for (int i=0; i<gv.getNumGlyphs(); ++i) {
    494             Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D();
    495             double t = tStart + offsetSign * (rect.getX() + rect.getWidth()/2) / pathLength;
    496             double[] p = pointAt(t, poly, pathLength);
    497             if (p != null) {
    498                 AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]);
    499                 trfm.rotate(p[2]+angleOffset);
    500                 double off = -rect.getY() - rect.getHeight()/2 + text.yOffset;
    501                 trfm.translate(-rect.getWidth()/2, off);
    502                 if (isGlyphVectorDoubleTranslationBug()) {
    503                     // scale the translation components by one half
    504                     AffineTransform tmp = AffineTransform.getTranslateInstance(-0.5 * trfm.getTranslateX(), -0.5 * trfm.getTranslateY());
    505                     tmp.concatenate(trfm);
    506                     trfm = tmp;
    507                 }
    508                 gv.setGlyphTransform(i, trfm);
    509             }
    510         }
    511         if (text.haloRadius != null) {
    512             Shape textOutline = gv.getOutline();
    513             g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
    514             g.setColor(text.haloColor);
    515             g.draw(textOutline);
    516             g.setStroke(new BasicStroke());
    517             g.setColor(text.color);
    518             g.fill(textOutline);
    519         } else {
    520             g.setColor(text.color);
    521             g.drawGlyphVector(gv, 0, 0);
    522         }
    523     }
    524 
    525     private double[] pointAt(double t, Polygon poly, double pathLength) {
    526         double totalLen = t * pathLength;
    527         double curLen = 0;
    528         long dx, dy;
    529         double segLen;
    530 
    531         // Yes, it is inefficient to iterate from the beginning for each glyph.
    532         // Can be optimized if it turns out to be slow.
    533         for (int i = 1; i < poly.npoints; ++i) {
    534             dx = poly.xpoints[i] - poly.xpoints[i-1];
    535             dy = poly.ypoints[i] - poly.ypoints[i-1];
    536             segLen = Math.sqrt(dx*dx + dy*dy);
    537             if (totalLen > curLen + segLen) {
    538                 curLen += segLen;
    539                 continue;
    540             }
    541             return new double[] {
    542                     poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx,
    543                     poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
    544                     Math.atan2(dy, dx)};
    545         }
    546         return null;
    547     }
    548 
    549     public void drawLinePattern(Way way, Image pattern) {
    550         final int width = pattern.getWidth(null);
    551         final int height = pattern.getHeight(null);
    552 
    553         Point lastP = null;
    554         double wayLength = 0;
    555 
    556         Iterator<Node> it = way.getNodes().iterator();
    557         while (it.hasNext()) {
    558             Node n = it.next();
    559             Point thisP = nc.getPoint(n);
    560 
    561             if (lastP != null) {
    562                 final double segmentLength = thisP.distance(lastP);
    563 
    564                 final double dx = thisP.x - lastP.x;
    565                 final double dy = thisP.y - lastP.y;
    566 
    567                 double dist = wayLength == 0 ? 0 : width - (wayLength % width);
    568 
    569                 AffineTransform saveTransform = g.getTransform();
    570                 g.translate(lastP.x, lastP.y);
    571                 g.rotate(Math.atan2(dy, dx));
    572 
    573                 if (dist > 0) {
    574                     g.drawImage(pattern, 0, 0, (int) dist, height,
    575                             width - (int) dist, 0, width, height, null);
    576                 }
    577                 while (dist < segmentLength) {
    578                     if (dist + width > segmentLength) {
    579                         g.drawImage(pattern, (int) dist, 0, (int) segmentLength, height,
    580                                 0, 0, (int) segmentLength - (int) dist, height, null);
    581                     } else {
    582                         g.drawImage(pattern, (int) dist, 0, nc);
    583                     }
    584                     dist += width;
    585                 }
    586                 g.setTransform(saveTransform);
    587 
    588                 wayLength += segmentLength;
    589             }
    590             lastP = thisP;
    591         }
    592     }
    593 
    594     public void drawNodeIcon(Node n, Image img, float alpha, boolean selected, boolean member) {
    595         Point p = nc.getPoint(n);
    596 
    597         final int w = img.getWidth(null), h=img.getHeight(null);
    598         if(n.isHighlighted()) {
    599             drawPointHighlight(p, Math.max(w, h));
    600         }
    601 
    602         if (alpha != 1f) {
    603             g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
    604         }
    605         g.drawImage(img, p.x-w/2, p.y-h/2, nc);
    606         g.setPaintMode();
    607         if (selected || member)
    608         {
    609             Color color = null;
    610             if (inactive || n.isDisabled()) {
    611                 color = inactiveColor;
    612             } else if (selected) {
    613                 color = selectedColor;
    614             } else {
    615                 color = relationSelectedColor;
    616             }
    617             g.setColor(color);
    618             g.drawRect(p.x-w/2-2, p.y-h/2-2, w+4, h+4);
    619         }
    620     }
    621 
    622     private Polygon buildPolygon(Point center, int radius, int sides, double rotation) {
    623         Polygon polygon = new Polygon();
    624         for (int i = 0; i < sides; i++) {
    625             double angle = ((2 * Math.PI / sides) * i) - rotation;
    626             int x = (int) Math.round(center.x + radius * Math.cos(angle));
    627             int y = (int) Math.round(center.y + radius * Math.sin(angle));
    628             polygon.addPoint(x, y);
    629         }
    630         return polygon;
    631     }
    632 
    633     private Polygon buildPolygon(Point center, int radius, int sides) {
    634         return buildPolygon(center, radius, sides, 0.0);
    635     }
    636 
    637     public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor) {
    638         Point p = nc.getPoint(n);
    639         int radius = s.size / 2;
    640 
    641         if(n.isHighlighted()) {
    642             drawPointHighlight(p, s.size);
    643         }
    644 
    645         if (fillColor != null) {
    646             g.setColor(fillColor);
    647             switch (s.symbol) {
    648             case SQUARE:
    649                 g.fillRect(p.x - radius, p.y - radius, s.size, s.size);
    650                 break;
    651             case CIRCLE:
    652                 g.fillOval(p.x - radius, p.y - radius, s.size, s.size);
    653                 break;
    654             case TRIANGLE:
    655                 g.fillPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
    656                 break;
    657             case PENTAGON:
    658                 g.fillPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
    659                 break;
    660             case HEXAGON:
    661                 g.fillPolygon(buildPolygon(p, radius, 6));
    662                 break;
    663             case HEPTAGON:
    664                 g.fillPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
    665                 break;
    666             case OCTAGON:
    667                 g.fillPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
    668                 break;
    669             case NONAGON:
    670                 g.fillPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
    671                 break;
    672             case DECAGON:
    673                 g.fillPolygon(buildPolygon(p, radius, 10));
    674                 break;
    675             default:
    676                 throw new AssertionError();
    677             }
    678         }
    679         if (s.stroke != null) {
    680             g.setStroke(s.stroke);
    681             g.setColor(strokeColor);
    682             switch (s.symbol) {
    683             case SQUARE:
    684                 g.drawRect(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
    685                 break;
    686             case CIRCLE:
    687                 g.drawOval(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
    688                 break;
    689             case TRIANGLE:
    690                 g.drawPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
    691                 break;
    692             case PENTAGON:
    693                 g.drawPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
    694                 break;
    695             case HEXAGON:
    696                 g.drawPolygon(buildPolygon(p, radius, 6));
    697                 break;
    698             case HEPTAGON:
    699                 g.drawPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
    700                 break;
    701             case OCTAGON:
    702                 g.drawPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
    703                 break;
    704             case NONAGON:
    705                 g.drawPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
    706                 break;
    707             case DECAGON:
    708                 g.drawPolygon(buildPolygon(p, radius, 10));
    709                 break;
    710             default:
    711                 throw new AssertionError();
    712             }
    713             g.setStroke(new BasicStroke());
    714         }
    715     }
    716 
    717     /**
    718      * Draw the node as small rectangle with the given color.
    719      *
    720      * @param n  The node to draw.
    721      * @param color The color of the node.
    722      */
    723     public void drawNode(Node n, Color color, int size, boolean fill) {
    724         if(size <= 0 && !n.isHighlighted())
    725             return;
    726 
    727         Point p = nc.getPoint(n);
    728 
    729         if(n.isHighlighted()) {
    730             drawPointHighlight(p, size);
    731         }
    732 
    733         if (size > 1) {
    734             if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
    735             int radius = size / 2;
    736 
    737             if (inactive || n.isDisabled()) {
    738                 g.setColor(inactiveColor);
    739             } else {
    740                 g.setColor(color);
    741             }
    742             if (fill) {
    743                 g.fillRect(p.x-radius-1, p.y-radius-1, size + 1, size + 1);
    744             } else {
    745                 g.drawRect(p.x-radius-1, p.y-radius-1, size, size);
    746             }
    747         }
    748     }
    749 
    750     /**
    751      * highlights a given point by drawing a rounded rectangle around it. Give the
    752      * size of the object you want to be highlighted, width is added automatically.
    753      */
    754     private void drawPointHighlight(Point p, int size) {
    755         g.setColor(highlightColorTransparent);
    756         int s = size + 7;
    757         while(s >= size) {
    758             int r = (int) Math.floor(s/2);
    759             g.fillRoundRect(p.x-r, p.y-r, s, s, r, r);
    760             s -= 4;
    761         }
    762     }
    763 
    764     public void drawBoxText(Node n, BoxTextElemStyle bs) {
    765         if (!isShowNames() || bs == null)
    766             return;
    767 
    768         Point p = nc.getPoint(n);
    769         TextElement text = bs.text;
    770         String s = text.labelCompositionStrategy.compose(n);
    771         if (s == null) return;
    772 
    773         Font defaultFont = g.getFont();
    774         g.setFont(text.font);
    775 
    776         int x = p.x + text.xOffset;
    777         int y = p.y + text.yOffset;
    778         /**
    779          *
    780          *       left-above __center-above___ right-above
    781          *         left-top|                 |right-top
    782          *                 |                 |
    783          *      left-center|  center-center  |right-center
    784          *                 |                 |
    785          *      left-bottom|_________________|right-bottom
    786          *       left-below   center-below    right-below
    787          *
    788          */
    789         Rectangle box = bs.getBox();
    790         if (bs.hAlign == HorizontalTextAlignment.RIGHT) {
    791             x += box.x + box.width + 2;
    792         } else {
    793             FontRenderContext frc = g.getFontRenderContext();
    794             Rectangle2D bounds = text.font.getStringBounds(s, frc);
    795             int textWidth = (int) bounds.getWidth();
    796             if (bs.hAlign == HorizontalTextAlignment.CENTER) {
    797                 x -= textWidth / 2;
    798             } else if (bs.hAlign == HorizontalTextAlignment.LEFT) {
    799                 x -= - box.x + 4 + textWidth;
    800             } else throw new AssertionError();
    801         }
    802 
    803         if (bs.vAlign == VerticalTextAlignment.BOTTOM) {
    804             y += box.y + box.height;
    805         } else {
    806             FontRenderContext frc = g.getFontRenderContext();
    807             LineMetrics metrics = text.font.getLineMetrics(s, frc);
    808             if (bs.vAlign == VerticalTextAlignment.ABOVE) {
    809                 y -= - box.y + metrics.getDescent();
    810             } else if (bs.vAlign == VerticalTextAlignment.TOP) {
    811                 y -= - box.y - metrics.getAscent();
    812             } else if (bs.vAlign == VerticalTextAlignment.CENTER) {
    813                 y += (metrics.getAscent() - metrics.getDescent()) / 2;
    814             } else if (bs.vAlign == VerticalTextAlignment.BELOW) {
    815                 y += box.y + box.height + metrics.getAscent() + 2;
    816             } else throw new AssertionError();
    817         }
    818         if (inactive || n.isDisabled()) {
    819             g.setColor(inactiveColor);
    820         } else {
    821             g.setColor(text.color);
    822         }
    823         if (text.haloRadius != null) {
    824             g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
    825             g.setColor(text.haloColor);
    826             FontRenderContext frc = g.getFontRenderContext();
    827             GlyphVector gv = text.font.createGlyphVector(frc, s);
    828             Shape textOutline = gv.getOutline(x, y);
    829             g.draw(textOutline);
    830             g.setStroke(new BasicStroke());
    831             g.setColor(text.color);
    832             g.fill(textOutline);
    833         } else {
    834             g.drawString(s, x, y);
    835         }
    836         g.setFont(defaultFont);
    837     }
    838 
    839     private Path2D.Double getPath(Way w) {
    840         Path2D.Double path = new Path2D.Double();
    841         boolean initial = true;
    842         for (Node n : w.getNodes())
    843         {
    844             Point2D p = n.getEastNorth();
    845             if (p != null) {
    846                 if (initial) {
    847                     path.moveTo(p.getX(), p.getY());
    848                     initial = false;
    849                 } else {
    850                     path.lineTo(p.getX(), p.getY());
    851                 }
    852             }
    853         }
    854         return path;
    855     }
    856 
    857     public void drawArea(Way w, Color color, MapImage fillImage, TextElement text) {
    858         drawArea(w, getPath(w), color, fillImage, text);
    859     }
    860 
    861     protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color, MapImage fillImage, TextElement text) {
    862 
    863         Shape area = path.createTransformedShape(nc.getAffineTransform());
    864 
    865         if (!isOutlineOnly) {
    866             if (fillImage == null) {
    867                 if (inactive) {
    868                     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.33f));
    869                 }
    870                 g.setColor(color);
    871                 g.fill(area);
    872             } else {
    873                 TexturePaint texture = new TexturePaint(fillImage.getImage(),
    874                         //                        new Rectangle(polygon.xpoints[0], polygon.ypoints[0], fillImage.getWidth(), fillImage.getHeight()));
    875                         new Rectangle(0, 0, fillImage.getWidth(), fillImage.getHeight()));
    876                 g.setPaint(texture);
    877                 Float alpha = Utils.color_int2float(fillImage.alpha);
    878                 if (alpha != 1f) {
    879                     g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
    880                 }
    881                 g.fill(area);
    882                 g.setPaintMode();
    883             }
    884         }
    885 
    886         if (text != null && isShowNames()) {
    887             /*
    888              * abort if we can't compose the label to be rendered
    889              */
    890             if (text.labelCompositionStrategy == null) return;
    891             String name = text.labelCompositionStrategy.compose(osm);
    892             if (name == null) return;
    893 
    894             Rectangle pb = area.getBounds();
    895             FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache
    896             Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
    897 
    898             // Point2D c = getCentroid(polygon);
    899             // Using the Centroid is Nicer for buildings like: +--------+
    900             // but this needs to be fast.  As most houses are  |   42   |
    901             // boxes anyway, the center of the bounding box    +---++---+
    902             // will have to do.                                    ++
    903             // Centroids are not optimal either, just imagine a U-shaped house.
    904             // Point2D c = new Point2D.Double(pb.x + pb.width / 2.0, pb.y + pb.height / 2.0);
    905             // Rectangle2D.Double centeredNBounds =
    906             //     new Rectangle2D.Double(c.getX() - nb.getWidth()/2,
    907             //                            c.getY() - nb.getHeight()/2,
    908             //                            nb.getWidth(),
    909             //                            nb.getHeight());
    910 
    911             Rectangle centeredNBounds = new Rectangle(pb.x + (int)((pb.width - nb.getWidth())/2.0),
    912                     pb.y + (int)((pb.height - nb.getHeight())/2.0),
    913                     (int)nb.getWidth(),
    914                     (int)nb.getHeight());
    915 
    916             if ((pb.width >= nb.getWidth() && pb.height >= nb.getHeight()) && // quick check
    917                     area.contains(centeredNBounds) // slow but nice
    918             ) {
    919                 if (inactive || osm.isDisabled()) {
    920                     g.setColor(inactiveColor);
    921                 } else {
    922                     g.setColor(text.color);
    923                 }
    924                 Font defaultFont = g.getFont();
    925                 g.setFont (text.font);
    926                 g.drawString (name,
    927                         (int)(centeredNBounds.getMinX() - nb.getMinX()),
    928                         (int)(centeredNBounds.getMinY() - nb.getMinY()));
    929                 g.setFont(defaultFont);
    930             }
    931         }
    932     }
    933 
    934     public void drawArea(Relation r, Color color, MapImage fillImage, TextElement text) {
    935         Multipolygon multipolygon = MultipolygonCache.getInstance().get(nc, r);
    936         if (!r.isDisabled() && !multipolygon.getOuterWays().isEmpty()) {
    937             for (PolyData pd : multipolygon.getCombinedPolygons()) {
    938                 Path2D.Double p = pd.get();
    939                 if (!isAreaVisible(p)) {
    940                     continue;
    941                 }
    942                 drawArea(r, p,
    943                         pd.selected ? settings.getRelationSelectedColor(color.getAlpha()) : color,
    944                                 fillImage, text);
    945             }
    946         }
    947     }
    948 
    949     private boolean isAreaVisible(Path2D.Double area) {
    950         Rectangle2D bounds = area.getBounds2D();
    951         if (bounds.isEmpty()) return false;
    952         Point2D p = nc.getPoint2D(new EastNorth(bounds.getX(), bounds.getY()));
    953         if (p.getX() > nc.getWidth()) return false;
    954         if (p.getY() < 0) return false;
    955         p = nc.getPoint2D(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));
    956         if (p.getX() < 0) return false;
    957         if (p.getY() > nc.getHeight()) return false;
    958         return true;
    959     }
    960 
    961     public void drawRestriction(Image img, Point pVia, double vx, double vx2, double vy, double vy2, double angle, boolean selected) {
    962         /* rotate image with direction last node in from to */
    963         Image rotatedImg = ImageProvider.createRotatedImage(null , img, angle);
    964 
    965         /* scale down image to 16*16 pixels */
    966         Image smallImg = new ImageIcon(rotatedImg.getScaledInstance(16 , 16, Image.SCALE_SMOOTH)).getImage();
    967         int w = smallImg.getWidth(null), h=smallImg.getHeight(null);
    968         g.drawImage(smallImg, (int)(pVia.x+vx+vx2)-w/2, (int)(pVia.y+vy+vy2)-h/2, nc);
    969 
    970         if (selected) {
    971             g.setColor(inactive ? inactiveColor : relationSelectedColor);
    972             g.drawRect((int)(pVia.x+vx+vx2)-w/2-2,(int)(pVia.y+vy+vy2)-h/2-2, w+4, h+4);
    973         }
    974     }
    975 
    976     public void drawRestriction(Relation r, MapImage icon) {
    977         Way fromWay = null;
    978         Way toWay = null;
    979         OsmPrimitive via = null;
    980 
    981         /* find the "from", "via" and "to" elements */
    982         for (RelationMember m : r.getMembers())
    983         {
    984             if(m.getMember().isIncomplete())
    985                 return;
    986             else
    987             {
    988                 if(m.isWay())
    989                 {
    990                     Way w = m.getWay();
    991                     if(w.getNodesCount() < 2) {
    992                         continue;
    993                     }
    994 
    995                     if("from".equals(m.getRole())) {
    996                         if(fromWay == null) {
    997                             fromWay = w;
    998                         }
    999                     } else if("to".equals(m.getRole())) {
    1000                         if(toWay == null) {
    1001                             toWay = w;
    1002                         }
    1003                     } else if("via".equals(m.getRole())) {
    1004                         if(via == null) {
    1005                             via = w;
    1006                         }
    1007                     }
    1008                 }
    1009                 else if(m.isNode())
    1010                 {
    1011                     Node n = m.getNode();
    1012                     if("via".equals(m.getRole()) && via == null) {
    1013                         via = n;
    1014                     }
    1015                 }
    1016             }
    1017         }
    1018 
    1019         if (fromWay == null || toWay == null || via == null)
    1020             return;
    1021 
    1022         Node viaNode;
    1023         if(via instanceof Node)
    1024         {
    1025             viaNode = (Node) via;
    1026             if(!fromWay.isFirstLastNode(viaNode))
    1027                 return;
    1028         }
    1029         else
    1030         {
    1031             Way viaWay = (Way) via;
    1032             Node firstNode = viaWay.firstNode();
    1033             Node lastNode = viaWay.lastNode();
    1034             Boolean onewayvia = false;
    1035 
    1036             String onewayviastr = viaWay.get("oneway");
    1037             if(onewayviastr != null)
    1038             {
    1039                 if("-1".equals(onewayviastr)) {
    1040                     onewayvia = true;
    1041                     Node tmp = firstNode;
    1042                     firstNode = lastNode;
    1043                     lastNode = tmp;
    1044                 } else {
    1045                     onewayvia = OsmUtils.getOsmBoolean(onewayviastr);
    1046                     if (onewayvia == null) {
    1047                         onewayvia = false;
    1048                     }
    1049                 }
    1050             }
    1051 
    1052             if(fromWay.isFirstLastNode(firstNode)) {
    1053                 viaNode = firstNode;
    1054             } else if (!onewayvia && fromWay.isFirstLastNode(lastNode)) {
    1055                 viaNode = lastNode;
    1056             } else
    1057                 return;
    1058         }
    1059 
    1060         /* find the "direct" nodes before the via node */
    1061         Node fromNode = null;
    1062         if(fromWay.firstNode() == via) {
    1063             fromNode = fromWay.getNode(1);
    1064         } else {
    1065             fromNode = fromWay.getNode(fromWay.getNodesCount()-2);
    1066         }
    1067 
    1068         Point pFrom = nc.getPoint(fromNode);
    1069         Point pVia = nc.getPoint(viaNode);
    1070 
    1071         /* starting from via, go back the "from" way a few pixels
    1072            (calculate the vector vx/vy with the specified length and the direction
    1073            away from the "via" node along the first segment of the "from" way)
    1074          */
    1075         double distanceFromVia=14;
    1076         double dx = (pFrom.x >= pVia.x) ? (pFrom.x - pVia.x) : (pVia.x - pFrom.x);
    1077         double dy = (pFrom.y >= pVia.y) ? (pFrom.y - pVia.y) : (pVia.y - pFrom.y);
    1078 
    1079         double fromAngle;
    1080         if(dx == 0.0) {
    1081             fromAngle = Math.PI/2;
    1082         } else {
    1083             fromAngle = Math.atan(dy / dx);
    1084         }
    1085         double fromAngleDeg = Math.toDegrees(fromAngle);
    1086 
    1087         double vx = distanceFromVia * Math.cos(fromAngle);
    1088         double vy = distanceFromVia * Math.sin(fromAngle);
    1089 
    1090         if(pFrom.x < pVia.x) {
    1091             vx = -vx;
    1092         }
    1093         if(pFrom.y < pVia.y) {
    1094             vy = -vy;
    1095         }
    1096 
    1097         /* go a few pixels away from the way (in a right angle)
    1098            (calculate the vx2/vy2 vector with the specified length and the direction
    1099            90degrees away from the first segment of the "from" way)
    1100          */
    1101         double distanceFromWay=10;
    1102         double vx2 = 0;
    1103         double vy2 = 0;
    1104         double iconAngle = 0;
    1105 
    1106         if(pFrom.x >= pVia.x && pFrom.y >= pVia.y) {
    1107             if(!leftHandTraffic) {
    1108                 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90));
    1109                 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90));
    1110             } else {
    1111                 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90));
    1112                 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90));
    1113             }
    1114             iconAngle = 270+fromAngleDeg;
    1115         }
    1116         if(pFrom.x < pVia.x && pFrom.y >= pVia.y) {
    1117             if(!leftHandTraffic) {
    1118                 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg));
    1119                 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg));
    1120             } else {
    1121                 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180));
    1122                 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180));
    1123             }
    1124             iconAngle = 90-fromAngleDeg;
    1125         }
    1126         if(pFrom.x < pVia.x && pFrom.y < pVia.y) {
    1127             if(!leftHandTraffic) {
    1128                 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90));
    1129                 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90));
    1130             } else {
    1131                 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90));
    1132                 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90));
    1133             }
    1134             iconAngle = 90+fromAngleDeg;
    1135         }
    1136         if(pFrom.x >= pVia.x && pFrom.y < pVia.y) {
    1137             if(!leftHandTraffic) {
    1138                 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180));
    1139                 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180));
    1140             } else {
    1141                 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg));
    1142                 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg));
    1143             }
    1144             iconAngle = 270-fromAngleDeg;
    1145         }
    1146 
    1147         drawRestriction(inactive || r.isDisabled() ? icon.getDisabled() : icon.getImage(),
    1148                 pVia, vx, vx2, vy, vy2, iconAngle, r.isSelected());
    1149     }
    1150 
    1151     public void drawVirtualNodes(Collection<Way> ways, Collection<WaySegment> highlightVirtualNodes) {
    1152         if (virtualNodeSize == 0)
    1153             return;
    1154         // print normal virtual nodes
    1155         GeneralPath path = new GeneralPath();
    1156         for (Way osm: ways){
    1157             if (osm.isUsable() && !osm.isDisabled()) {
    1158                 visitVirtual(path, osm);
    1159             }
    1160         }
    1161         g.setColor(nodeColor);
    1162         g.draw(path);
    1163         // print highlighted virtual nodes. Since only the color changes, simply
    1164         // drawing them over the existing ones works fine (at least in their current
    1165         // simple style)
    1166         path = new GeneralPath();
    1167         for (WaySegment wseg: highlightVirtualNodes){
    1168             if (wseg.way.isUsable() && !wseg.way.isDisabled()) {
    1169                 visitVirtual(path, wseg.toWay());
    1170             }
    1171         }
    1172         g.setColor(highlightColor);
    1173         g.draw(path);
    1174     }
    1175 
    1176     public void visitVirtual(GeneralPath path, Way w) {
    1177         Iterator<Node> it = w.getNodes().iterator();
    1178         if (it.hasNext()) {
    1179             Point lastP = nc.getPoint(it.next());
    1180             while(it.hasNext())
    1181             {
    1182                 Point p = nc.getPoint(it.next());
    1183                 if(isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
    1184                 {
    1185                     int x = (p.x+lastP.x)/2;
    1186                     int y = (p.y+lastP.y)/2;
    1187                     path.moveTo(x-virtualNodeSize, y);
    1188                     path.lineTo(x+virtualNodeSize, y);
    1189                     path.moveTo(x, y-virtualNodeSize);
    1190                     path.lineTo(x, y+virtualNodeSize);
    1191                 }
    1192                 lastP = p;
    1193             }
    1194         }
    1195     }
    1196 
    1197     private static boolean isLargeSegment(Point p1, Point p2, int space)  {
    1198         int xd = p1.x-p2.x; if(xd < 0) {
    1199             xd = -xd;
    1200         }
    1201         int yd = p1.y-p2.y; if(yd < 0) {
    1202             yd = -yd;
    1203         }
    1204         return (xd+yd > space);
    1205     }
    1206 
    1207     /**
    1208      * Draw a number of the order of the two consecutive nodes within the
    1209      * parents way
    1210      */
    1211     public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) {
    1212         Point p1 = nc.getPoint(n1);
    1213         Point p2 = nc.getPoint(n2);
    1214         drawOrderNumber(p1, p2, orderNumber, clr);
    1215     }
    1216 
    1217     /**
    1218      * Draw an number of the order of the two consecutive nodes within the
    1219      * parents way
    1220      */
    1221     protected void drawOrderNumber(Point p1, Point p2, int orderNumber, Color clr) {
    1222         if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
    1223             String on = Integer.toString(orderNumber);
    1224             int strlen = on.length();
    1225             int x = (p1.x+p2.x)/2 - 4*strlen;
    1226             int y = (p1.y+p2.y)/2 + 4;
    1227 
    1228             if(virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace))
    1229             {
    1230                 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
    1231             }
    1232 
    1233             g.setColor(backgroundColor);
    1234             g.fillRect(x-1, y-12, 8*strlen+1, 14);
    1235             g.setColor(clr);
    1236             g.drawString(on, x, y);
    1237         }
    1238     }
    1239 
    1240     public boolean isShowNames() {
    1241         return showNames;
    1242     }
    1243 
    1244     public double getCircum() {
    1245         return circum;
    1246     }
    1247 
    1248     public boolean isShowIcons() {
    1249         return showIcons;
    1250     }
    1251 
    1252     public boolean isInactiveMode() {
    1253         return inactive;
    1254     }
    1255 }
  • core/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java

     
    11// License: GPL. Copyright 2007 by Immanuel Scholz and others
    22package org.openstreetmap.josm.data.osm.visitor.paint;
    33
     4import java.awt.AlphaComposite;
     5import java.awt.BasicStroke;
     6import java.awt.Color;
     7import java.awt.Font;
     8import java.awt.FontMetrics;
    49import java.awt.Graphics2D;
     10import java.awt.Image;
     11import java.awt.Point;
     12import java.awt.Polygon;
     13import java.awt.Rectangle;
    514import java.awt.RenderingHints;
     15import java.awt.Shape;
     16import java.awt.TexturePaint;
     17import java.awt.font.FontRenderContext;
     18import java.awt.font.GlyphVector;
     19import java.awt.font.LineMetrics;
     20import java.awt.geom.AffineTransform;
     21import java.awt.geom.GeneralPath;
     22import java.awt.geom.Path2D;
     23import java.awt.geom.Point2D;
     24import java.awt.geom.Rectangle2D;
    625import java.util.ArrayList;
     26import java.util.Arrays;
    727import java.util.Collection;
    828import java.util.Collections;
     29import java.util.Iterator;
    930import java.util.List;
    1031
     32import javax.swing.ImageIcon;
     33
    1134import org.openstreetmap.josm.Main;
    1235import org.openstreetmap.josm.data.Bounds;
     36import org.openstreetmap.josm.data.coor.EastNorth;
    1337import org.openstreetmap.josm.data.osm.BBox;
    1438import org.openstreetmap.josm.data.osm.DataSet;
    1539import org.openstreetmap.josm.data.osm.Node;
    1640import org.openstreetmap.josm.data.osm.OsmPrimitive;
     41import org.openstreetmap.josm.data.osm.OsmUtils;
    1742import org.openstreetmap.josm.data.osm.Relation;
     43import org.openstreetmap.josm.data.osm.RelationMember;
    1844import org.openstreetmap.josm.data.osm.Way;
    1945import org.openstreetmap.josm.data.osm.WaySegment;
     46import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
     47import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
     48import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
    2049import org.openstreetmap.josm.gui.NavigatableComponent;
    2150import org.openstreetmap.josm.gui.mappaint.AreaElemStyle;
     51import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle;
     52import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.HorizontalTextAlignment;
     53import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.VerticalTextAlignment;
    2254import org.openstreetmap.josm.gui.mappaint.ElemStyle;
    2355import org.openstreetmap.josm.gui.mappaint.ElemStyles;
     56import org.openstreetmap.josm.gui.mappaint.MapImage;
    2457import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
    2558import org.openstreetmap.josm.gui.mappaint.NodeElemStyle;
     59import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol;
    2660import org.openstreetmap.josm.gui.mappaint.StyleCache.StyleList;
     61import org.openstreetmap.josm.gui.mappaint.TextElement;
     62import org.openstreetmap.josm.tools.ImageProvider;
     63import org.openstreetmap.josm.tools.Pair;
     64import org.openstreetmap.josm.tools.Utils;
    2765
    2866/**
    2967 * <p>A map renderer which renders a map according to style rules in a set of style sheets.</p>
    3068 *
    3169 */
    32 public class StyledMapRenderer extends AbstractMapRenderer{
     70public class StyledMapRenderer extends AbstractMapRenderer {
    3371
    34     private ElemStyles styles;
    35     private double circum;
    36     private MapPainter painter;
    37     private MapPaintSettings paintSettings;
     72    /**
     73     * Iterates over a list of Way Nodes and returns screen coordinates that
     74     * represent a line that is shifted by a certain offset perpendicular
     75     * to the way direction.
     76     *
     77     * There is no intention, to handle consecutive duplicate Nodes in a
     78     * perfect way, but it is should not throw an exception.
     79     */
     80    private class OffsetIterator implements Iterator<Point> {
    3881
    39     private static int FLAG_NORMAL = 0;
    40     private static int FLAG_DISABLED = 1;
    41     private static int FLAG_MEMBER_OF_SELECTED = 2;
    42     private static int FLAG_SELECTED = 4;
     82        private List<Node> nodes;
     83        private float offset;
     84        private int idx;
    4385
    44     private static class StyleRecord implements Comparable<StyleRecord> {
    45         final ElemStyle style;
    46         final OsmPrimitive osm;
    47         final int flags;
     86        private Point prev = null;
     87        /* 'prev0' is a point that has distance 'offset' from 'prev' and the
     88         * line from 'prev' to 'prev0' is perpendicular to the way segment from
     89         * 'prev' to the next point.
     90         */
     91        private int x_prev0, y_prev0;
    4892
    49         public StyleRecord(ElemStyle style, OsmPrimitive osm, int flags) {
    50             this.style = style;
    51             this.osm = osm;
    52             this.flags = flags;
     93        public OffsetIterator(List<Node> nodes, float offset) {
     94            this.nodes = nodes;
     95            this.offset = offset;
     96            idx = 0;
    5397        }
    5498
    5599        @Override
    56         public int compareTo(StyleRecord other) {
    57             if ((this.flags & FLAG_DISABLED) != 0 && (other.flags & FLAG_DISABLED) == 0)
    58                 return -1;
    59             if ((this.flags & FLAG_DISABLED) == 0 && (other.flags & FLAG_DISABLED) != 0)
    60                 return 1;
     100        public boolean hasNext() {
     101            return idx < nodes.size();
     102        }
    61103
    62             int d0 = Float.compare(this.style.major_z_index, other.style.major_z_index);
    63             if (d0 != 0)
    64                 return d0;
     104        @Override
     105        public Point next() {
     106            if (Math.abs(offset) < 0.1f) return nc.getPoint(nodes.get(idx++));
    65107
    66             // selected on top of member of selected on top of unselected
    67             // FLAG_DISABLED bit is the same at this point
    68             if (this.flags > other.flags)
    69                 return 1;
    70             if (this.flags < other.flags)
    71                 return -1;
     108            Point current = nc.getPoint(nodes.get(idx));
    72109
    73             int dz = Float.compare(this.style.z_index, other.style.z_index);
    74             if (dz != 0)
    75                 return dz;
     110            if (idx == nodes.size() - 1) {
     111                ++idx;
     112                return new Point(x_prev0 + current.x - prev.x, y_prev0 + current.y - prev.y);
     113            }
    76114
    77             // simple node on top of icons and shapes
    78             if (this.style == NodeElemStyle.SIMPLE_NODE_ELEMSTYLE && other.style != NodeElemStyle.SIMPLE_NODE_ELEMSTYLE)
    79                 return 1;
    80             if (this.style != NodeElemStyle.SIMPLE_NODE_ELEMSTYLE && other.style == NodeElemStyle.SIMPLE_NODE_ELEMSTYLE)
    81                 return -1;
     115            Point next = nc.getPoint(nodes.get(idx+1));
    82116
    83             // newer primitives to the front
    84             long id = this.osm.getUniqueId() - other.osm.getUniqueId();
    85             if (id > 0)
    86                 return 1;
    87             if (id < 0)
    88                 return -1;
     117            int dx_next = next.x - current.x;
     118            int dy_next = next.y - current.y;
     119            double len_next = Math.sqrt(dx_next*dx_next + dy_next*dy_next);
    89120
    90             return Float.compare(this.style.object_z_index, other.style.object_z_index);
     121            if (len_next == 0) {
     122                len_next = 1; // value does not matter, because dy_next and dx_next is 0
     123            }
     124
     125            int x_current0 = current.x + (int) Math.round(offset * dy_next / len_next);
     126            int y_current0 = current.y - (int) Math.round(offset * dx_next / len_next);
     127
     128            if (idx==0) {
     129                ++idx;
     130                prev = current;
     131                x_prev0 = x_current0;
     132                y_prev0 = y_current0;
     133                return new Point(x_current0, y_current0);
     134            } else {
     135                int dx_prev = current.x - prev.x;
     136                int dy_prev = current.y - prev.y;
     137
     138                // determine intersection of the lines parallel to the two
     139                // segments
     140                int det = dx_next*dy_prev - dx_prev*dy_next;
     141
     142                if (det == 0) {
     143                    ++idx;
     144                    prev = current;
     145                    x_prev0 = x_current0;
     146                    y_prev0 = y_current0;
     147                    return new Point(x_current0, y_current0);
     148                }
     149
     150                int m = dx_next*(y_current0 - y_prev0) - dy_next*(x_current0 - x_prev0);
     151
     152                int cx_ = x_prev0 + Math.round((float)m * dx_prev / det);
     153                int cy_ = y_prev0 + Math.round((float)m * dy_prev / det);
     154                ++idx;
     155                prev = current;
     156                x_prev0 = x_current0;
     157                y_prev0 = y_current0;
     158                return new Point(cx_, cy_);
     159            }
     160        }
     161
     162        @Override
     163        public void remove() {
     164            throw new UnsupportedOperationException();
    91165        }
    92166    }
    93167
     
    112186            }
    113187        }
    114188
    115         public void add(Way osm, int flags) {
     189        public void add(Relation osm, int flags) {
    116190            StyleList sl = styles.get(osm, circum, nc);
    117191            for (ElemStyle s : sl) {
    118                 if (!(drawArea && (flags & FLAG_DISABLED) == 0) && s instanceof AreaElemStyle) {
    119                     continue;
     192                if (drawMultipolygon && drawArea && s instanceof AreaElemStyle && (flags & FLAG_DISABLED) == 0) {
     193                    styleElems.add(new StyleRecord(s, osm, flags));
     194                } else if (drawRestriction && s instanceof NodeElemStyle) {
     195                    styleElems.add(new StyleRecord(s, osm, flags));
    120196                }
    121                 styleElems.add(new StyleRecord(s, osm, flags));
    122197            }
    123198        }
    124199
    125         public void add(Relation osm, int flags) {
     200        public void add(Way osm, int flags) {
    126201            StyleList sl = styles.get(osm, circum, nc);
    127202            for (ElemStyle s : sl) {
    128                 if (drawMultipolygon && drawArea && s instanceof AreaElemStyle && (flags & FLAG_DISABLED) == 0) {
    129                     styleElems.add(new StyleRecord(s, osm, flags));
    130                 } else if (drawRestriction && s instanceof NodeElemStyle) {
    131                     styleElems.add(new StyleRecord(s, osm, flags));
     203                if (!(drawArea && (flags & FLAG_DISABLED) == 0) && s instanceof AreaElemStyle) {
     204                    continue;
    132205                }
     206                styleElems.add(new StyleRecord(s, osm, flags));
    133207            }
    134208        }
    135209
     
    139213                r.style.paintPrimitive(
    140214                        r.osm,
    141215                        paintSettings,
    142                         painter,
     216                        StyledMapRenderer.this,
    143217                        (r.flags & FLAG_SELECTED) != 0,
    144218                        (r.flags & FLAG_MEMBER_OF_SELECTED) != 0
    145219                );
    146220            }
    147221        }
    148222    }
     223   
     224    private static class StyleRecord implements Comparable<StyleRecord> {
     225        final ElemStyle style;
     226        final OsmPrimitive osm;
     227        final int flags;
     228
     229        public StyleRecord(ElemStyle style, OsmPrimitive osm, int flags) {
     230            this.style = style;
     231            this.osm = osm;
     232            this.flags = flags;
     233        }
     234
     235        @Override
     236        public int compareTo(StyleRecord other) {
     237            if ((this.flags & FLAG_DISABLED) != 0 && (other.flags & FLAG_DISABLED) == 0)
     238                return -1;
     239            if ((this.flags & FLAG_DISABLED) == 0 && (other.flags & FLAG_DISABLED) != 0)
     240                return 1;
     241
     242            int d0 = Float.compare(this.style.major_z_index, other.style.major_z_index);
     243            if (d0 != 0)
     244                return d0;
     245
     246            // selected on top of member of selected on top of unselected
     247            // FLAG_DISABLED bit is the same at this point
     248            if (this.flags > other.flags)
     249                return 1;
     250            if (this.flags < other.flags)
     251                return -1;
     252
     253            int dz = Float.compare(this.style.z_index, other.style.z_index);
     254            if (dz != 0)
     255                return dz;
     256
     257            // simple node on top of icons and shapes
     258            if (this.style == NodeElemStyle.SIMPLE_NODE_ELEMSTYLE && other.style != NodeElemStyle.SIMPLE_NODE_ELEMSTYLE)
     259                return 1;
     260            if (this.style != NodeElemStyle.SIMPLE_NODE_ELEMSTYLE && other.style == NodeElemStyle.SIMPLE_NODE_ELEMSTYLE)
     261                return -1;
     262
     263            // newer primitives to the front
     264            long id = this.osm.getUniqueId() - other.osm.getUniqueId();
     265            if (id > 0)
     266                return 1;
     267            if (id < 0)
     268                return -1;
     269
     270            return Float.compare(this.style.object_z_index, other.style.object_z_index);
     271        }
     272    }
     273
     274    private static Boolean IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG = null;
     275
     276    /**
     277     * Check, if this System has the GlyphVector double translation bug.
     278     *
     279     * With this bug, <code>gv.setGlyphTransform(i, trfm)</code> has a different
     280     * effect than on most other systems, namely the translation components
     281     * ("m02" & "m12", {@link AffineTransform}) appear to be twice as large, as
     282     * they actually are. The rotation is unaffected (scale & shear not tested
     283     * so far).
     284     *
     285     * This bug has only been observed on Mac OS X, see #7841.
     286     *
     287     * @return true, if the GlyphVector double translation bug is present on
     288     * this System
     289     */
     290    public static boolean isGlyphVectorDoubleTranslationBug() {
     291        if (IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG != null)
     292            return IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG;
     293        FontRenderContext frc = new FontRenderContext(null, false, false);
     294        Font font = new Font("Dialog", Font.PLAIN, 12);
     295        GlyphVector gv = font.createGlyphVector(frc, "x");
     296        gv.setGlyphTransform(0, AffineTransform.getTranslateInstance(1000, 1000));
     297        Shape shape = gv.getGlyphOutline(0);
     298        // x is about 1000 on normal stystems and about 2000 when the bug occurs
     299        int x = shape.getBounds().x;
     300        IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG = x > 1500;
     301        return IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG;
     302    }
     303
     304    private ElemStyles styles;
     305    private double circum;
     306
     307    private MapPaintSettings paintSettings;
     308
     309    private Color relationSelectedColor;
     310    private Color highlightColorTransparent;
     311   
     312    private static int FLAG_NORMAL = 0;
     313    private static int FLAG_DISABLED = 1;
     314    private static int FLAG_MEMBER_OF_SELECTED = 2;
     315    private static int FLAG_SELECTED = 4;
     316
     317    private static final double PHI = Math.toRadians(20);
     318    private static final double cosPHI = Math.cos(PHI);
     319    private static final double sinPHI = Math.sin(PHI);
     320
     321    private Collection<WaySegment> highlightWaySegments;
     322
     323    private boolean useStrokes;
     324    private boolean showNames;
     325    private boolean showIcons;
     326    private boolean  isOutlineOnly;
     327
     328    private Font orderFont;
     329
     330    private boolean leftHandTraffic;
    149331
    150332    /**
    151333     * {@inheritDoc}
     
    154336        super(g, nc, isInactiveMode);
    155337    }
    156338
     339    private Polygon buildPolygon(Point center, int radius, int sides) {
     340        return buildPolygon(center, radius, sides, 0.0);
     341    }
     342
     343    private Polygon buildPolygon(Point center, int radius, int sides, double rotation) {
     344        Polygon polygon = new Polygon();
     345        for (int i = 0; i < sides; i++) {
     346            double angle = ((2 * Math.PI / sides) * i) - rotation;
     347            int x = (int) Math.round(center.x + radius * Math.cos(angle));
     348            int y = (int) Math.round(center.y + radius * Math.sin(angle));
     349            polygon.addPoint(x, y);
     350        }
     351        return polygon;
     352    }
     353
    157354    private void collectNodeStyles(DataSet data, StyleCollector sc, BBox bbox) {
    158355        for (final Node n: data.searchNodes(bbox)) {
    159356            if (n.isDrawable()) {
     
    170367        }
    171368    }
    172369
     370    private void collectRelationStyles(DataSet data, StyleCollector sc, BBox bbox) {
     371        for (Relation r: data.searchRelations(bbox)) {
     372            if (r.isDrawable()) {
     373                if (r.isDisabled()) {
     374                    sc.add(r, FLAG_DISABLED);
     375                } else if (data.isSelected(r)) {
     376                    sc.add(r, FLAG_SELECTED);
     377                } else {
     378                    sc.add(r, FLAG_NORMAL);
     379                }
     380            }
     381        }
     382    }
     383
    173384    private void collectWayStyles(DataSet data, StyleCollector sc, BBox bbox) {
    174385        for (final Way w : data.searchWays(bbox)) {
    175386            if (w.isDrawable()) {
     
    186397        }
    187398    }
    188399
    189     private void collectRelationStyles(DataSet data, StyleCollector sc, BBox bbox) {
    190         for (Relation r: data.searchRelations(bbox)) {
    191             if (r.isDrawable()) {
    192                 if (r.isDisabled()) {
    193                     sc.add(r, FLAG_DISABLED);
    194                 } else if (data.isSelected(r)) {
    195                     sc.add(r, FLAG_SELECTED);
     400    private void displaySegments(GeneralPath path, GeneralPath orientationArrows, GeneralPath onewayArrows, GeneralPath onewayArrowsCasing,
     401            Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) {
     402        g.setColor(isInactiveMode ? inactiveColor : color);
     403        if (useStrokes) {
     404            g.setStroke(line);
     405        }
     406        g.draw(path);
     407
     408        if(!isInactiveMode && useStrokes && dashes != null) {
     409            g.setColor(dashedColor);
     410            g.setStroke(dashes);
     411            g.draw(path);
     412        }
     413
     414        if (orientationArrows != null) {
     415            g.setColor(isInactiveMode ? inactiveColor : color);
     416            g.setStroke(new BasicStroke(line.getLineWidth(), line.getEndCap(), BasicStroke.JOIN_MITER, line.getMiterLimit()));
     417            g.draw(orientationArrows);
     418        }
     419
     420        if (onewayArrows != null) {
     421            g.setStroke(new BasicStroke(1, line.getEndCap(), BasicStroke.JOIN_MITER, line.getMiterLimit()));
     422            g.fill(onewayArrowsCasing);
     423            g.setColor(isInactiveMode ? inactiveColor : backgroundColor);
     424            g.fill(onewayArrows);
     425        }
     426
     427        if (useStrokes) {
     428            g.setStroke(new BasicStroke());
     429        }
     430    }
     431   
     432    protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color, MapImage fillImage, TextElement text) {
     433
     434        Shape area = path.createTransformedShape(nc.getAffineTransform());
     435
     436        if (!isOutlineOnly) {
     437            if (fillImage == null) {
     438                if (isInactiveMode) {
     439                    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.33f));
     440                }
     441                g.setColor(color);
     442                g.fill(area);
     443            } else {
     444                TexturePaint texture = new TexturePaint(fillImage.getImage(),
     445                        //                        new Rectangle(polygon.xpoints[0], polygon.ypoints[0], fillImage.getWidth(), fillImage.getHeight()));
     446                        new Rectangle(0, 0, fillImage.getWidth(), fillImage.getHeight()));
     447                g.setPaint(texture);
     448                Float alpha = Utils.color_int2float(fillImage.alpha);
     449                if (alpha != 1f) {
     450                    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
     451                }
     452                g.fill(area);
     453                g.setPaintMode();
     454            }
     455        }
     456
     457        if (text != null && isShowNames()) {
     458            /*
     459             * abort if we can't compose the label to be rendered
     460             */
     461            if (text.labelCompositionStrategy == null) return;
     462            String name = text.labelCompositionStrategy.compose(osm);
     463            if (name == null) return;
     464
     465            Rectangle pb = area.getBounds();
     466            FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache
     467            Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
     468
     469            // Point2D c = getCentroid(polygon);
     470            // Using the Centroid is Nicer for buildings like: +--------+
     471            // but this needs to be fast.  As most houses are  |   42   |
     472            // boxes anyway, the center of the bounding box    +---++---+
     473            // will have to do.                                    ++
     474            // Centroids are not optimal either, just imagine a U-shaped house.
     475            // Point2D c = new Point2D.Double(pb.x + pb.width / 2.0, pb.y + pb.height / 2.0);
     476            // Rectangle2D.Double centeredNBounds =
     477            //     new Rectangle2D.Double(c.getX() - nb.getWidth()/2,
     478            //                            c.getY() - nb.getHeight()/2,
     479            //                            nb.getWidth(),
     480            //                            nb.getHeight());
     481
     482            Rectangle centeredNBounds = new Rectangle(pb.x + (int)((pb.width - nb.getWidth())/2.0),
     483                    pb.y + (int)((pb.height - nb.getHeight())/2.0),
     484                    (int)nb.getWidth(),
     485                    (int)nb.getHeight());
     486
     487            if ((pb.width >= nb.getWidth() && pb.height >= nb.getHeight()) && // quick check
     488                    area.contains(centeredNBounds) // slow but nice
     489            ) {
     490                if (isInactiveMode || osm.isDisabled()) {
     491                    g.setColor(inactiveColor);
    196492                } else {
    197                     sc.add(r, FLAG_NORMAL);
     493                    g.setColor(text.color);
    198494                }
     495                Font defaultFont = g.getFont();
     496                g.setFont (text.font);
     497                g.drawString (name,
     498                        (int)(centeredNBounds.getMinX() - nb.getMinX()),
     499                        (int)(centeredNBounds.getMinY() - nb.getMinY()));
     500                g.setFont(defaultFont);
    199501            }
    200502        }
    201503    }
     504   
     505    public void drawArea(Relation r, Color color, MapImage fillImage, TextElement text) {
     506        Multipolygon multipolygon = MultipolygonCache.getInstance().get(nc, r);
     507        if (!r.isDisabled() && !multipolygon.getOuterWays().isEmpty()) {
     508            for (PolyData pd : multipolygon.getCombinedPolygons()) {
     509                Path2D.Double p = pd.get();
     510                if (!isAreaVisible(p)) {
     511                    continue;
     512                }
     513                drawArea(r, p,
     514                        pd.selected ? paintSettings.getRelationSelectedColor(color.getAlpha()) : color,
     515                                fillImage, text);
     516            }
     517        }
     518    }
     519   
     520    public void drawArea(Way w, Color color, MapImage fillImage, TextElement text) {
     521        drawArea(w, getPath(w), color, fillImage, text);
     522    }
     523
     524    public void drawBoxText(Node n, BoxTextElemStyle bs) {
     525        if (!isShowNames() || bs == null)
     526            return;
     527
     528        Point p = nc.getPoint(n);
     529        TextElement text = bs.text;
     530        String s = text.labelCompositionStrategy.compose(n);
     531        if (s == null) return;
     532
     533        Font defaultFont = g.getFont();
     534        g.setFont(text.font);
     535
     536        int x = p.x + text.xOffset;
     537        int y = p.y + text.yOffset;
     538        /**
     539         *
     540         *       left-above __center-above___ right-above
     541         *         left-top|                 |right-top
     542         *                 |                 |
     543         *      left-center|  center-center  |right-center
     544         *                 |                 |
     545         *      left-bottom|_________________|right-bottom
     546         *       left-below   center-below    right-below
     547         *
     548         */
     549        Rectangle box = bs.getBox();
     550        if (bs.hAlign == HorizontalTextAlignment.RIGHT) {
     551            x += box.x + box.width + 2;
     552        } else {
     553            FontRenderContext frc = g.getFontRenderContext();
     554            Rectangle2D bounds = text.font.getStringBounds(s, frc);
     555            int textWidth = (int) bounds.getWidth();
     556            if (bs.hAlign == HorizontalTextAlignment.CENTER) {
     557                x -= textWidth / 2;
     558            } else if (bs.hAlign == HorizontalTextAlignment.LEFT) {
     559                x -= - box.x + 4 + textWidth;
     560            } else throw new AssertionError();
     561        }
     562
     563        if (bs.vAlign == VerticalTextAlignment.BOTTOM) {
     564            y += box.y + box.height;
     565        } else {
     566            FontRenderContext frc = g.getFontRenderContext();
     567            LineMetrics metrics = text.font.getLineMetrics(s, frc);
     568            if (bs.vAlign == VerticalTextAlignment.ABOVE) {
     569                y -= - box.y + metrics.getDescent();
     570            } else if (bs.vAlign == VerticalTextAlignment.TOP) {
     571                y -= - box.y - metrics.getAscent();
     572            } else if (bs.vAlign == VerticalTextAlignment.CENTER) {
     573                y += (metrics.getAscent() - metrics.getDescent()) / 2;
     574            } else if (bs.vAlign == VerticalTextAlignment.BELOW) {
     575                y += box.y + box.height + metrics.getAscent() + 2;
     576            } else throw new AssertionError();
     577        }
     578        if (isInactiveMode || n.isDisabled()) {
     579            g.setColor(inactiveColor);
     580        } else {
     581            g.setColor(text.color);
     582        }
     583        if (text.haloRadius != null) {
     584            g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
     585            g.setColor(text.haloColor);
     586            FontRenderContext frc = g.getFontRenderContext();
     587            GlyphVector gv = text.font.createGlyphVector(frc, s);
     588            Shape textOutline = gv.getOutline(x, y);
     589            g.draw(textOutline);
     590            g.setStroke(new BasicStroke());
     591            g.setColor(text.color);
     592            g.fill(textOutline);
     593        } else {
     594            g.drawString(s, x, y);
     595        }
     596        g.setFont(defaultFont);
     597    }
     598
     599    public void drawLinePattern(Way way, Image pattern) {
     600        final int width = pattern.getWidth(null);
     601        final int height = pattern.getHeight(null);
     602
     603        Point lastP = null;
     604        double wayLength = 0;
     605
     606        Iterator<Node> it = way.getNodes().iterator();
     607        while (it.hasNext()) {
     608            Node n = it.next();
     609            Point thisP = nc.getPoint(n);
     610
     611            if (lastP != null) {
     612                final double segmentLength = thisP.distance(lastP);
     613
     614                final double dx = thisP.x - lastP.x;
     615                final double dy = thisP.y - lastP.y;
     616
     617                double dist = wayLength == 0 ? 0 : width - (wayLength % width);
     618
     619                AffineTransform saveTransform = g.getTransform();
     620                g.translate(lastP.x, lastP.y);
     621                g.rotate(Math.atan2(dy, dx));
     622
     623                if (dist > 0) {
     624                    g.drawImage(pattern, 0, 0, (int) dist, height,
     625                            width - (int) dist, 0, width, height, null);
     626                }
     627                while (dist < segmentLength) {
     628                    if (dist + width > segmentLength) {
     629                        g.drawImage(pattern, (int) dist, 0, (int) segmentLength, height,
     630                                0, 0, (int) segmentLength - (int) dist, height, null);
     631                    } else {
     632                        g.drawImage(pattern, (int) dist, 0, nc);
     633                    }
     634                    dist += width;
     635                }
     636                g.setTransform(saveTransform);
    202637
     638                wayLength += segmentLength;
     639            }
     640            lastP = thisP;
     641        }
     642    }
     643   
    203644    @Override
    204     public void render(final DataSet data, boolean renderVirtualNodes, Bounds bounds) {
    205         //long start = System.currentTimeMillis();
    206         BBox bbox = new BBox(bounds);
     645    public void drawNode(Node n, Color color, int size, boolean fill) {
     646        if(size <= 0 && !n.isHighlighted())
     647            return;
    207648
    208         styles = MapPaintStyles.getStyles();
     649        Point p = nc.getPoint(n);
     650
     651        if(n.isHighlighted()) {
     652            drawPointHighlight(p, size);
     653        }
    209654
    210         this.paintSettings = MapPaintSettings.INSTANCE;
     655        if (size > 1) {
     656            if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
     657            int radius = size / 2;
     658
     659            if (isInactiveMode || n.isDisabled()) {
     660                g.setColor(inactiveColor);
     661            } else {
     662                g.setColor(color);
     663            }
     664            if (fill) {
     665                g.fillRect(p.x-radius-1, p.y-radius-1, size + 1, size + 1);
     666            } else {
     667                g.drawRect(p.x-radius-1, p.y-radius-1, size, size);
     668            }
     669        }
     670    }
     671   
     672    public void drawNodeIcon(Node n, Image img, float alpha, boolean selected, boolean member) {
     673        Point p = nc.getPoint(n);
     674
     675        final int w = img.getWidth(null), h=img.getHeight(null);
     676        if(n.isHighlighted()) {
     677            drawPointHighlight(p, Math.max(w, h));
     678        }
     679
     680        if (alpha != 1f) {
     681            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
     682        }
     683        g.drawImage(img, p.x-w/2, p.y-h/2, nc);
     684        g.setPaintMode();
     685        if (selected || member)
     686        {
     687            Color color = null;
     688            if (isInactiveMode || n.isDisabled()) {
     689                color = inactiveColor;
     690            } else if (selected) {
     691                color = selectedColor;
     692            } else {
     693                color = relationSelectedColor;
     694            }
     695            g.setColor(color);
     696            g.drawRect(p.x-w/2-2, p.y-h/2-2, w+4, h+4);
     697        }
     698    }
     699   
     700    public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor) {
     701        Point p = nc.getPoint(n);
     702        int radius = s.size / 2;
     703
     704        if(n.isHighlighted()) {
     705            drawPointHighlight(p, s.size);
     706        }
     707
     708        if (fillColor != null) {
     709            g.setColor(fillColor);
     710            switch (s.symbol) {
     711            case SQUARE:
     712                g.fillRect(p.x - radius, p.y - radius, s.size, s.size);
     713                break;
     714            case CIRCLE:
     715                g.fillOval(p.x - radius, p.y - radius, s.size, s.size);
     716                break;
     717            case TRIANGLE:
     718                g.fillPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
     719                break;
     720            case PENTAGON:
     721                g.fillPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
     722                break;
     723            case HEXAGON:
     724                g.fillPolygon(buildPolygon(p, radius, 6));
     725                break;
     726            case HEPTAGON:
     727                g.fillPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
     728                break;
     729            case OCTAGON:
     730                g.fillPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
     731                break;
     732            case NONAGON:
     733                g.fillPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
     734                break;
     735            case DECAGON:
     736                g.fillPolygon(buildPolygon(p, radius, 10));
     737                break;
     738            default:
     739                throw new AssertionError();
     740            }
     741        }
     742        if (s.stroke != null) {
     743            g.setStroke(s.stroke);
     744            g.setColor(strokeColor);
     745            switch (s.symbol) {
     746            case SQUARE:
     747                g.drawRect(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
     748                break;
     749            case CIRCLE:
     750                g.drawOval(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
     751                break;
     752            case TRIANGLE:
     753                g.drawPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
     754                break;
     755            case PENTAGON:
     756                g.drawPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
     757                break;
     758            case HEXAGON:
     759                g.drawPolygon(buildPolygon(p, radius, 6));
     760                break;
     761            case HEPTAGON:
     762                g.drawPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
     763                break;
     764            case OCTAGON:
     765                g.drawPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
     766                break;
     767            case NONAGON:
     768                g.drawPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
     769                break;
     770            case DECAGON:
     771                g.drawPolygon(buildPolygon(p, radius, 10));
     772                break;
     773            default:
     774                throw new AssertionError();
     775            }
     776            g.setStroke(new BasicStroke());
     777        }
     778    }
     779
     780    /**
     781     * Draw a number of the order of the two consecutive nodes within the
     782     * parents way
     783     */
     784    public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) {
     785        Point p1 = nc.getPoint(n1);
     786        Point p2 = nc.getPoint(n2);
     787        StyledMapRenderer.this.drawOrderNumber(p1, p2, orderNumber, clr);
     788    }
     789   
     790    /**
     791     * highlights a given GeneralPath using the settings from BasicStroke to match the line's
     792     * style. Width of the highlight is hard coded.
     793     * @param path
     794     * @param line
     795     */
     796    private void drawPathHighlight(GeneralPath path, BasicStroke line) {
     797        if(path == null)
     798            return;
     799        g.setColor(highlightColorTransparent);
     800        float w = (line.getLineWidth() + 4);
     801        while(w >= line.getLineWidth()) {
     802            g.setStroke(new BasicStroke(w, line.getEndCap(), line.getLineJoin(), line.getMiterLimit()));
     803            g.draw(path);
     804            w -= 4;
     805        }
     806    }
     807    /**
     808     * highlights a given point by drawing a rounded rectangle around it. Give the
     809     * size of the object you want to be highlighted, width is added automatically.
     810     */
     811    private void drawPointHighlight(Point p, int size) {
     812        g.setColor(highlightColorTransparent);
     813        int s = size + 7;
     814        while(s >= size) {
     815            int r = (int) Math.floor(s/2);
     816            g.fillRoundRect(p.x-r, p.y-r, s, s, r, r);
     817            s -= 4;
     818        }
     819    }
     820
     821    public void drawRestriction(Image img, Point pVia, double vx, double vx2, double vy, double vy2, double angle, boolean selected) {
     822        /* rotate image with direction last node in from to */
     823        Image rotatedImg = ImageProvider.createRotatedImage(null , img, angle);
     824
     825        /* scale down image to 16*16 pixels */
     826        Image smallImg = new ImageIcon(rotatedImg.getScaledInstance(16 , 16, Image.SCALE_SMOOTH)).getImage();
     827        int w = smallImg.getWidth(null), h=smallImg.getHeight(null);
     828        g.drawImage(smallImg, (int)(pVia.x+vx+vx2)-w/2, (int)(pVia.y+vy+vy2)-h/2, nc);
     829
     830        if (selected) {
     831            g.setColor(isInactiveMode ? inactiveColor : relationSelectedColor);
     832            g.drawRect((int)(pVia.x+vx+vx2)-w/2-2,(int)(pVia.y+vy+vy2)-h/2-2, w+4, h+4);
     833        }
     834    }
     835   
     836    public void drawRestriction(Relation r, MapImage icon) {
     837        Way fromWay = null;
     838        Way toWay = null;
     839        OsmPrimitive via = null;
     840
     841        /* find the "from", "via" and "to" elements */
     842        for (RelationMember m : r.getMembers())
     843        {
     844            if(m.getMember().isIncomplete())
     845                return;
     846            else
     847            {
     848                if(m.isWay())
     849                {
     850                    Way w = m.getWay();
     851                    if(w.getNodesCount() < 2) {
     852                        continue;
     853                    }
     854
     855                    if("from".equals(m.getRole())) {
     856                        if(fromWay == null) {
     857                            fromWay = w;
     858                        }
     859                    } else if("to".equals(m.getRole())) {
     860                        if(toWay == null) {
     861                            toWay = w;
     862                        }
     863                    } else if("via".equals(m.getRole())) {
     864                        if(via == null) {
     865                            via = w;
     866                        }
     867                    }
     868                }
     869                else if(m.isNode())
     870                {
     871                    Node n = m.getNode();
     872                    if("via".equals(m.getRole()) && via == null) {
     873                        via = n;
     874                    }
     875                }
     876            }
     877        }
     878
     879        if (fromWay == null || toWay == null || via == null)
     880            return;
     881
     882        Node viaNode;
     883        if(via instanceof Node)
     884        {
     885            viaNode = (Node) via;
     886            if(!fromWay.isFirstLastNode(viaNode))
     887                return;
     888        }
     889        else
     890        {
     891            Way viaWay = (Way) via;
     892            Node firstNode = viaWay.firstNode();
     893            Node lastNode = viaWay.lastNode();
     894            Boolean onewayvia = false;
     895
     896            String onewayviastr = viaWay.get("oneway");
     897            if(onewayviastr != null)
     898            {
     899                if("-1".equals(onewayviastr)) {
     900                    onewayvia = true;
     901                    Node tmp = firstNode;
     902                    firstNode = lastNode;
     903                    lastNode = tmp;
     904                } else {
     905                    onewayvia = OsmUtils.getOsmBoolean(onewayviastr);
     906                    if (onewayvia == null) {
     907                        onewayvia = false;
     908                    }
     909                }
     910            }
     911
     912            if(fromWay.isFirstLastNode(firstNode)) {
     913                viaNode = firstNode;
     914            } else if (!onewayvia && fromWay.isFirstLastNode(lastNode)) {
     915                viaNode = lastNode;
     916            } else
     917                return;
     918        }
     919
     920        /* find the "direct" nodes before the via node */
     921        Node fromNode = null;
     922        if(fromWay.firstNode() == via) {
     923            fromNode = fromWay.getNode(1);
     924        } else {
     925            fromNode = fromWay.getNode(fromWay.getNodesCount()-2);
     926        }
     927
     928        Point pFrom = nc.getPoint(fromNode);
     929        Point pVia = nc.getPoint(viaNode);
     930
     931        /* starting from via, go back the "from" way a few pixels
     932           (calculate the vector vx/vy with the specified length and the direction
     933           away from the "via" node along the first segment of the "from" way)
     934         */
     935        double distanceFromVia=14;
     936        double dx = (pFrom.x >= pVia.x) ? (pFrom.x - pVia.x) : (pVia.x - pFrom.x);
     937        double dy = (pFrom.y >= pVia.y) ? (pFrom.y - pVia.y) : (pVia.y - pFrom.y);
     938
     939        double fromAngle;
     940        if(dx == 0.0) {
     941            fromAngle = Math.PI/2;
     942        } else {
     943            fromAngle = Math.atan(dy / dx);
     944        }
     945        double fromAngleDeg = Math.toDegrees(fromAngle);
     946
     947        double vx = distanceFromVia * Math.cos(fromAngle);
     948        double vy = distanceFromVia * Math.sin(fromAngle);
     949
     950        if(pFrom.x < pVia.x) {
     951            vx = -vx;
     952        }
     953        if(pFrom.y < pVia.y) {
     954            vy = -vy;
     955        }
     956
     957        /* go a few pixels away from the way (in a right angle)
     958           (calculate the vx2/vy2 vector with the specified length and the direction
     959           90degrees away from the first segment of the "from" way)
     960         */
     961        double distanceFromWay=10;
     962        double vx2 = 0;
     963        double vy2 = 0;
     964        double iconAngle = 0;
     965
     966        if(pFrom.x >= pVia.x && pFrom.y >= pVia.y) {
     967            if(!leftHandTraffic) {
     968                vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90));
     969                vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90));
     970            } else {
     971                vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90));
     972                vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90));
     973            }
     974            iconAngle = 270+fromAngleDeg;
     975        }
     976        if(pFrom.x < pVia.x && pFrom.y >= pVia.y) {
     977            if(!leftHandTraffic) {
     978                vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg));
     979                vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg));
     980            } else {
     981                vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180));
     982                vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180));
     983            }
     984            iconAngle = 90-fromAngleDeg;
     985        }
     986        if(pFrom.x < pVia.x && pFrom.y < pVia.y) {
     987            if(!leftHandTraffic) {
     988                vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90));
     989                vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90));
     990            } else {
     991                vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90));
     992                vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90));
     993            }
     994            iconAngle = 90+fromAngleDeg;
     995        }
     996        if(pFrom.x >= pVia.x && pFrom.y < pVia.y) {
     997            if(!leftHandTraffic) {
     998                vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180));
     999                vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180));
     1000            } else {
     1001                vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg));
     1002                vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg));
     1003            }
     1004            iconAngle = 270-fromAngleDeg;
     1005        }
     1006
     1007        drawRestriction(isInactiveMode || r.isDisabled() ? icon.getDisabled() : icon.getImage(),
     1008                pVia, vx, vx2, vy, vy2, iconAngle, r.isSelected());
     1009    }
     1010   
     1011    public void drawTextOnPath(Way way, TextElement text) {
     1012        if (way == null || text == null)
     1013            return;
     1014        String name = text.getString(way);
     1015        if (name == null || name.isEmpty())
     1016            return;
     1017
     1018        Polygon poly = new Polygon();
     1019        Point lastPoint = null;
     1020        Iterator<Node> it = way.getNodes().iterator();
     1021        double pathLength = 0;
     1022        long dx, dy;
     1023        while (it.hasNext()) {
     1024            Node n = it.next();
     1025            Point p = nc.getPoint(n);
     1026            poly.addPoint(p.x, p.y);
     1027
     1028            if(lastPoint != null) {
     1029                dx = p.x - lastPoint.x;
     1030                dy = p.y - lastPoint.y;
     1031                pathLength += Math.sqrt(dx*dx + dy*dy);
     1032            }
     1033            lastPoint = p;
     1034        }
     1035
     1036        FontMetrics fontMetrics = g.getFontMetrics(text.font); // if slow, use cache
     1037        Rectangle2D rec = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
     1038
     1039        if (rec.getWidth() > pathLength)
     1040            return;
     1041
     1042        double t1 = (pathLength/2 - rec.getWidth()/2) / pathLength;
     1043        double t2 = (pathLength/2 + rec.getWidth()/2) / pathLength;
     1044
     1045        double[] p1 = pointAt(t1, poly, pathLength);
     1046        double[] p2 = pointAt(t2, poly, pathLength);
     1047       
     1048        if (p1 == null || p2 == null)
     1049            return;
     1050
     1051        double angleOffset;
     1052        double offsetSign;
     1053        double tStart;
     1054
     1055        if (p1[0] < p2[0] &&
     1056                p1[2] < Math.PI/2 &&
     1057                p1[2] > -Math.PI/2) {
     1058            angleOffset = 0;
     1059            offsetSign = 1;
     1060            tStart = t1;
     1061        } else {
     1062            angleOffset = Math.PI;
     1063            offsetSign = -1;
     1064            tStart = t2;
     1065        }
     1066
     1067        FontRenderContext frc = g.getFontRenderContext();
     1068        GlyphVector gv = text.font.createGlyphVector(frc, name);
     1069
     1070        for (int i=0; i<gv.getNumGlyphs(); ++i) {
     1071            Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D();
     1072            double t = tStart + offsetSign * (rect.getX() + rect.getWidth()/2) / pathLength;
     1073            double[] p = pointAt(t, poly, pathLength);
     1074            if (p != null) {
     1075                AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]);
     1076                trfm.rotate(p[2]+angleOffset);
     1077                double off = -rect.getY() - rect.getHeight()/2 + text.yOffset;
     1078                trfm.translate(-rect.getWidth()/2, off);
     1079                if (isGlyphVectorDoubleTranslationBug()) {
     1080                    // scale the translation components by one half
     1081                    AffineTransform tmp = AffineTransform.getTranslateInstance(-0.5 * trfm.getTranslateX(), -0.5 * trfm.getTranslateY());
     1082                    tmp.concatenate(trfm);
     1083                    trfm = tmp;
     1084                }
     1085                gv.setGlyphTransform(i, trfm);
     1086            }
     1087        }
     1088        if (text.haloRadius != null) {
     1089            Shape textOutline = gv.getOutline();
     1090            g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
     1091            g.setColor(text.haloColor);
     1092            g.draw(textOutline);
     1093            g.setStroke(new BasicStroke());
     1094            g.setColor(text.color);
     1095            g.fill(textOutline);
     1096        } else {
     1097            g.setColor(text.color);
     1098            g.drawGlyphVector(gv, 0, 0);
     1099        }
     1100    }
     1101   
     1102    /**
     1103     * draw way
     1104     * @param showOrientation show arrows that indicate the technical orientation of
     1105     *              the way (defined by order of nodes)
     1106     * @param showOneway show symbols that indicate the direction of the feature,
     1107     *              e.g. oneway street or waterway
     1108     * @param onewayReversed for oneway=-1 and similar
     1109     */
     1110    public void drawWay(Way way, Color color, BasicStroke line, BasicStroke dashes, Color dashedColor, float offset,
     1111            boolean showOrientation, boolean showHeadArrowOnly,
     1112            boolean showOneway, boolean onewayReversed) {
     1113
     1114        GeneralPath path = new GeneralPath();
     1115        GeneralPath orientationArrows = showOrientation ? new GeneralPath() : null;
     1116        GeneralPath onewayArrows = showOneway ? new GeneralPath() : null;
     1117        GeneralPath onewayArrowsCasing = showOneway ? new GeneralPath() : null;
     1118        Rectangle bounds = g.getClipBounds();
     1119        bounds.grow(100, 100);                  // avoid arrow heads at the border
     1120
     1121        double wayLength = 0;
     1122        Point lastPoint = null;
     1123        boolean initialMoveToNeeded = true;
     1124        List<Node> wayNodes = way.getNodes();
     1125        if (wayNodes.size() < 2) return;
     1126
     1127        // only highlight the segment if the way itself is not highlighted
     1128        if (!way.isHighlighted()) {
     1129            GeneralPath highlightSegs = null;
     1130            for (WaySegment ws : highlightWaySegments) {
     1131                if (ws.way != way || ws.lowerIndex < offset) {
     1132                    continue;
     1133                }
     1134                if(highlightSegs == null) {
     1135                    highlightSegs = new GeneralPath();
     1136                }
     1137
     1138                Point p1 = nc.getPoint(ws.getFirstNode());
     1139                Point p2 = nc.getPoint(ws.getSecondNode());
     1140                highlightSegs.moveTo(p1.x, p1.y);
     1141                highlightSegs.lineTo(p2.x, p2.y);
     1142            }
     1143
     1144            drawPathHighlight(highlightSegs, line);
     1145        }
     1146
     1147        Iterator<Point> it = new OffsetIterator(wayNodes, offset);
     1148        while (it.hasNext()) {
     1149            Point p = it.next();
     1150            if (lastPoint != null) {
     1151                Point p1 = lastPoint;
     1152                Point p2 = p;
     1153
     1154                /**
     1155                 * Do custom clipping to work around openjdk bug. It leads to
     1156                 * drawing artefacts when zooming in a lot. (#4289, #4424)
     1157                 * (Looks like int overflow.)
     1158                 */
     1159                LineClip clip = new LineClip(p1, p2, bounds);
     1160                if (clip.execute()) {
     1161                    if (!p1.equals(clip.getP1())) {
     1162                        p1 = clip.getP1();
     1163                        path.moveTo(p1.x, p1.y);
     1164                    } else if (initialMoveToNeeded) {
     1165                        initialMoveToNeeded = false;
     1166                        path.moveTo(p1.x, p1.y);
     1167                    }
     1168                    p2 = clip.getP2();
     1169                    path.lineTo(p2.x, p2.y);
     1170
     1171                    /* draw arrow */
     1172                    if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
     1173                        final double segmentLength = p1.distance(p2);
     1174                        if (segmentLength != 0.0) {
     1175                            final double l =  (10. + line.getLineWidth()) / segmentLength;
     1176
     1177                            final double sx = l * (p1.x - p2.x);
     1178                            final double sy = l * (p1.y - p2.y);
     1179
     1180                            orientationArrows.moveTo (p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy);
     1181                            orientationArrows.lineTo(p2.x, p2.y);
     1182                            orientationArrows.lineTo (p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy);
     1183                        }
     1184                    }
     1185                    if (showOneway) {
     1186                        final double segmentLength = p1.distance(p2);
     1187                        if (segmentLength != 0.0) {
     1188                            final double nx = (p2.x - p1.x) / segmentLength;
     1189                            final double ny = (p2.y - p1.y) / segmentLength;
     1190
     1191                            final double interval = 60;
     1192                            // distance from p1
     1193                            double dist = interval - (wayLength % interval);
     1194
     1195                            while (dist < segmentLength) {
     1196                                for (Pair<Float, GeneralPath> sizeAndPath : Arrays.asList(new Pair[] {
     1197                                        new Pair<Float, GeneralPath>(3f, onewayArrowsCasing),
     1198                                        new Pair<Float, GeneralPath>(2f, onewayArrows)})) {
     1199
     1200                                    // scale such that border is 1 px
     1201                                    final double fac = - (onewayReversed ? -1 : 1) * sizeAndPath.a * (1 + sinPHI) / (sinPHI * cosPHI);
     1202                                    final double sx = nx * fac;
     1203                                    final double sy = ny * fac;
     1204
     1205                                    // Attach the triangle at the incenter and not at the tip.
     1206                                    // Makes the border even at all sides.
     1207                                    final double x = p1.x + nx * (dist + (onewayReversed ? -1 : 1) * (sizeAndPath.a / sinPHI));
     1208                                    final double y = p1.y + ny * (dist + (onewayReversed ? -1 : 1) * (sizeAndPath.a / sinPHI));
     1209
     1210                                    sizeAndPath.b.moveTo(x, y);
     1211                                    sizeAndPath.b.lineTo (x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
     1212                                    sizeAndPath.b.lineTo (x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
     1213                                    sizeAndPath.b.lineTo(x, y);
     1214                                }
     1215                                dist += interval;
     1216                            }
     1217                        }
     1218                        wayLength += segmentLength;
     1219                    }
     1220                }
     1221            }
     1222            lastPoint = p;
     1223        }
     1224        if(way.isHighlighted()) {
     1225            drawPathHighlight(path, line);
     1226        }
     1227        displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor);
     1228    }
     1229
     1230    public double getCircum() {
     1231        return circum;
     1232    }
     1233
     1234    @Override
     1235    public void getColors() {
     1236        super.getColors();
     1237        this.relationSelectedColor = PaintColors.RELATIONSELECTED.get();
     1238        this.highlightColorTransparent = new Color(highlightColor.getRed(), highlightColor.getGreen(), highlightColor.getBlue(), 100);
     1239        this.backgroundColor = PaintColors.getBackgroundColor();
     1240    }
     1241   
     1242    @Override
     1243    protected void getSettings(boolean virtual) {
     1244        super.getSettings(virtual);
     1245        paintSettings = MapPaintSettings.INSTANCE;
    2111246
    2121247        circum = nc.getDist100Pixel();
    213         boolean drawArea = circum <= Main.pref.getInteger("mappaint.fillareas", 10000000);
    214         boolean drawMultipolygon = drawArea && Main.pref.getBoolean("mappaint.multipolygon", true);
    215         styles.setDrawMultipolygon(drawMultipolygon);
    216         boolean drawRestriction = Main.pref.getBoolean("mappaint.restriction", true);
    217         boolean leftHandTraffic = Main.pref.getBoolean("mappaint.lefthandtraffic", false);
     1248
     1249        leftHandTraffic = Main.pref.getBoolean("mappaint.lefthandtraffic", false);
     1250
     1251        useStrokes = paintSettings.getUseStrokesDistance() > circum;
     1252        showNames = paintSettings.getShowNamesDistance() > circum;
     1253        showIcons = paintSettings.getShowIconsDistance() > circum;
     1254        isOutlineOnly = paintSettings.isOutlineOnly();
     1255        orderFont = new Font(Main.pref.get("mappaint.font", "Helvetica"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
    2181256
    2191257        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    2201258                Main.pref.getBoolean("mappaint.use-antialiasing", true) ?
    2211259                        RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
     1260    }
    2221261
    223         Collection<WaySegment> hws = data.getHighlightedWaySegments();
     1262    private Path2D.Double getPath(Way w) {
     1263        Path2D.Double path = new Path2D.Double();
     1264        boolean initial = true;
     1265        for (Node n : w.getNodes()) {
     1266            Point2D p = n.getEastNorth();
     1267            if (p != null) {
     1268                if (initial) {
     1269                    path.moveTo(p.getX(), p.getY());
     1270                    initial = false;
     1271                } else {
     1272                    path.lineTo(p.getX(), p.getY());
     1273                }
     1274            }
     1275        }
     1276        return path;
     1277    }
    2241278
    225         this.painter = new MapPainter(paintSettings, g, isInactiveMode, nc, renderVirtualNodes, circum, leftHandTraffic, hws);
     1279    private boolean isAreaVisible(Path2D.Double area) {
     1280        Rectangle2D bounds = area.getBounds2D();
     1281        if (bounds.isEmpty()) return false;
     1282        Point2D p = nc.getPoint2D(new EastNorth(bounds.getX(), bounds.getY()));
     1283        if (p.getX() > nc.getWidth()) return false;
     1284        if (p.getY() < 0) return false;
     1285        p = nc.getPoint2D(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()));
     1286        if (p.getX() < 0) return false;
     1287        if (p.getY() > nc.getHeight()) return false;
     1288        return true;
     1289    }
     1290
     1291    public boolean isInactiveMode() {
     1292        return isInactiveMode;
     1293    }
     1294
     1295    public boolean isShowIcons() {
     1296        return showIcons;
     1297    }
     1298
     1299    public boolean isShowNames() {
     1300        return showNames;
     1301    }
     1302
     1303    private double[] pointAt(double t, Polygon poly, double pathLength) {
     1304        double totalLen = t * pathLength;
     1305        double curLen = 0;
     1306        long dx, dy;
     1307        double segLen;
     1308
     1309        // Yes, it is inefficient to iterate from the beginning for each glyph.
     1310        // Can be optimized if it turns out to be slow.
     1311        for (int i = 1; i < poly.npoints; ++i) {
     1312            dx = poly.xpoints[i] - poly.xpoints[i-1];
     1313            dy = poly.ypoints[i] - poly.ypoints[i-1];
     1314            segLen = Math.sqrt(dx*dx + dy*dy);
     1315            if (totalLen > curLen + segLen) {
     1316                curLen += segLen;
     1317                continue;
     1318            }
     1319            return new double[] {
     1320                    poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx,
     1321                    poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
     1322                    Math.atan2(dy, dx)};
     1323        }
     1324        return null;
     1325    }
     1326
     1327    @Override
     1328    public void render(final DataSet data, boolean renderVirtualNodes, Bounds bounds) {
     1329        //long start = System.currentTimeMillis();
     1330        BBox bbox = new BBox(bounds);
     1331        getSettings(renderVirtualNodes);
     1332
     1333        boolean drawArea = circum <= Main.pref.getInteger("mappaint.fillareas", 10000000);
     1334        boolean drawMultipolygon = drawArea && Main.pref.getBoolean("mappaint.multipolygon", true);
     1335        boolean drawRestriction = Main.pref.getBoolean("mappaint.restriction", true);
     1336
     1337        styles = MapPaintStyles.getStyles();
     1338        styles.setDrawMultipolygon(drawMultipolygon);
     1339
     1340        highlightWaySegments = data.getHighlightedWaySegments();
    2261341
    2271342        StyleCollector sc = new StyleCollector(drawArea, drawMultipolygon, drawRestriction);
    2281343        collectNodeStyles(data, sc, bbox);
     
    2311346        //long phase1 = System.currentTimeMillis();
    2321347        sc.drawAll();
    2331348        sc = null;
    234         painter.drawVirtualNodes(data.searchWays(bbox), data.getHighlightedVirtualNodes());
     1349        drawVirtualNodes(data, bbox);
    2351350
    2361351        //long now = System.currentTimeMillis();
    2371352        //System.err.println(String.format("PAINTING TOOK %d [PHASE1 took %d] (at scale %s)", now - start, phase1 - start, circum));
  • core/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java

     
    1010import java.awt.RenderingHints;
    1111import java.awt.Stroke;
    1212import java.awt.geom.GeneralPath;
    13 import java.awt.geom.Point2D;
    14 import java.util.Collection;
    1513import java.util.Iterator;
    1614
    1715import org.openstreetmap.josm.Main;
     
    3432 */
    3533public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor {
    3634
    37     /** Color Preference for inactive objects */
    38     protected Color inactiveColor;
    39     /** Color Preference for selected objects */
    40     protected Color selectedColor;
    41     /** Color Preference for nodes */
    42     protected Color nodeColor;
    4335    /** Color Preference for ways not matching any other group */
    4436    protected Color dfltWayColor;
    4537    /** Color Preference for relations */
    4638    protected Color relationColor;
    4739    /** Color Preference for untagged ways */
    4840    protected Color untaggedWayColor;
    49     /** Color Preference for background */
    50     protected Color backgroundColor;
    51     /** Color Preference for hightlighted objects */
    52     protected Color highlightColor;
    5341    /** Color Preference for tagged nodes */
    5442    protected Color taggedColor;
    5543    /** Color Preference for multiply connected nodes */
     
    8068    protected int connectionNodeSize;
    8169    /** Preference: size of tagged nodes */
    8270    protected int taggedNodeSize;
    83     /** Preference: size of virtual nodes (0 displayes display) */
    84     protected int virtualNodeSize;
    85     /** Preference: minimum space (displayed way length) to display virtual nodes */
    86     protected int virtualNodeSpace;
    87     /** Preference: minimum space (displayed way length) to display segment numbers */
    88     protected int segmentNumberSpace;
    8971
    9072    /** Color cache to draw subsequent segments of same color as one <code>Path</code>. */
    9173    protected Color currentColor = null;
     
    122104        super(g, nc, isInactiveMode);
    123105    }
    124106
    125     /**
    126      * Reads the color definitions from preferences. This function is <code>public</code>, so that
    127      * color names in preferences can be displayed even without calling the wireframe display before.
    128      */
    129     public void getColors()
    130     {
    131         inactiveColor = PaintColors.INACTIVE.get();
    132         selectedColor = PaintColors.SELECTED.get();
    133         nodeColor = PaintColors.NODE.get();
     107    @Override
     108    public void getColors() {
     109        super.getColors();
    134110        dfltWayColor = PaintColors.DEFAULT_WAY.get();
    135111        relationColor = PaintColors.RELATION.get();
    136112        untaggedWayColor = PaintColors.UNTAGGED_WAY.get();
    137         backgroundColor = PaintColors.BACKGROUND.get();
    138113        highlightColor = PaintColors.HIGHLIGHT_WIREFRAME.get();
    139114        taggedColor = PaintColors.TAGGED.get();
    140115        connectionColor = PaintColors.CONNECTION.get();
     
    146121        }
    147122    }
    148123
    149     /**
    150      * Reads all the settings from preferences. Calls the @{link #getColors}
    151      * function.
    152      *
    153      * @param virtual <code>true</code> if virtual nodes are used
    154      */
     124    @Override
    155125    protected void getSettings(boolean virtual) {
     126        super.getSettings(virtual);
    156127        MapPaintSettings settings = MapPaintSettings.INSTANCE;
    157128        showDirectionArrow = settings.isShowDirectionArrow();
    158129        showOnewayArrow = settings.isShowOnewayArrow();
     
    166137        fillUnselectedNode = settings.isFillUnselectedNode();
    167138        fillConnectionNode = settings.isFillConnectionNode();
    168139        fillTaggedNode = settings.isFillTaggedNode();
    169         virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
    170         virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
    171         segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
    172         getColors();
    173140
    174141        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    175142                Main.pref.getBoolean("mappaint.wireframe.use-antialiasing", false) ?
     
    223190                osm.visit(this);
    224191            }
    225192        }
    226         drawVirtualNodes(data.searchWays(bbox), data.getHighlightedVirtualNodes());
     193        drawVirtualNodes(data, bbox);
    227194
    228195        // draw highlighted way segments over the already drawn ways. Otherwise each
    229196        // way would have to be checked if it contains a way segment to highlight when
    230197        // in most of the cases there won't be more than one segment. Since the wireframe
    231198        // renderer does not feature any transparency there should be no visual difference.
    232         for(final WaySegment wseg : data.getHighlightedWaySegments()) {
     199        for (final WaySegment wseg : data.getHighlightedWaySegments()) {
    233200            drawSegment(nc.getPoint(wseg.getFirstNode()), nc.getPoint(wseg.getSecondNode()), highlightColor, false);
    234201        }
    235202        displaySegments();
     
    295262    }
    296263
    297264    /**
    298      * Checks if a way segemnt is large enough for additional information display.
    299      *
    300      * @param p1 First point of the way segment.
    301      * @param p2 Second point of the way segment.
    302      * @param space The free space to check against.
    303      * @return <code>true</code> if segment is larger than required space
    304      */
    305     public static boolean isLargeSegment(Point2D p1, Point2D p2, int space)
    306     {
    307         double xd = Math.abs(p1.getX()-p2.getX());
    308         double yd = Math.abs(p1.getY()-p2.getY());
    309         return (xd+yd > space);
    310     }
    311 
    312     /**
    313      * Draws virtual nodes.
    314      *
    315      * @param ways The ways to draw nodes for.
    316      * @param highlightVirtualNodes Way segements, where nodesshould be highlighted.
    317      */
    318     public void drawVirtualNodes(Collection<Way> ways, Collection<WaySegment> highlightVirtualNodes) {
    319         if (virtualNodeSize == 0)
    320             return;
    321         // print normal virtual nodes
    322         GeneralPath path = new GeneralPath();
    323         for (Way osm : ways) {
    324             if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) {
    325                 visitVirtual(path, osm);
    326             }
    327         }
    328         g.setColor(nodeColor);
    329         g.draw(path);
    330         // print highlighted virtual nodes. Since only the color changes, simply
    331         // drawing them over the existing ones works fine (at least in their current
    332         // simple style)
    333         path = new GeneralPath();
    334         for (WaySegment wseg: highlightVirtualNodes){
    335             if (wseg.way.isUsable() && !wseg.way.isDisabled()) {
    336                 visitVirtual(path, wseg.toWay());
    337             }
    338         }
    339         g.setColor(highlightColor);
    340         g.draw(path);
    341     }
    342 
    343     /**
    344      * Creates path for drawing virtual nodes for one way.
    345      *
    346      * @param path The path to append drawing to.
    347      * @param w The ways to draw node for.
    348      */
    349     public void visitVirtual(GeneralPath path, Way w) {
    350         Iterator<Node> it = w.getNodes().iterator();
    351         if (it.hasNext()) {
    352             Point lastP = nc.getPoint(it.next());
    353             while(it.hasNext())
    354             {
    355                 Point p = nc.getPoint(it.next());
    356                 if(isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
    357                 {
    358                     int x = (p.x+lastP.x)/2;
    359                     int y = (p.y+lastP.y)/2;
    360                     path.moveTo(x-virtualNodeSize, y);
    361                     path.lineTo(x+virtualNodeSize, y);
    362                     path.moveTo(x, y-virtualNodeSize);
    363                     path.lineTo(x, y+virtualNodeSize);
    364                 }
    365                 lastP = p;
    366             }
    367         }
    368     }
    369 
    370     /**
    371265     * Draw a line for all way segments.
    372266     * @param w The way to draw.
    373267     */
     
    405299                drawSegment(lastP, p, wayColor,
    406300                        showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
    407301                if (showOrderNumber && !isInactiveMode) {
    408                     drawOrderNumber(lastP, p, orderNumber);
     302                    drawOrderNumber(lastP, p, orderNumber, g.getColor());
    409303                }
    410304                lastP = p;
    411305            }
     
    472366    @Override
    473367    public void visit(Changeset cs) {/* ignore */}
    474368
    475     /**
    476      * Draw an number of the order of the two consecutive nodes within the
    477      * parents way
    478      *
    479      * @param p1 First point of the way segment.
    480      * @param p2 Second point of the way segment.
    481      * @param orderNumber The number of the segment in the way.
    482      */
    483     protected void drawOrderNumber(Point p1, Point p2, int orderNumber) {
    484         if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
    485             String on = Integer.toString(orderNumber);
    486             int strlen = on.length();
    487             int x = (p1.x+p2.x)/2 - 4*strlen;
    488             int y = (p1.y+p2.y)/2 + 4;
    489 
    490             if(virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace))
    491             {
    492                 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
    493             }
    494 
    495             displaySegments(); /* draw nodes on top! */
    496             Color c = g.getColor();
    497             g.setColor(backgroundColor);
    498             g.fillRect(x-1, y-12, 8*strlen+1, 14);
    499             g.setColor(c);
    500             g.drawString(on, x, y);
    501         }
    502     }
    503 
    504     /**
    505      * Draw the node as small rectangle with the given color.
    506      *
    507      * @param n     The node to draw.
    508      * @param color The color of the node.
    509      */
     369    @Override
    510370    public void drawNode(Node n, Color color, int size, boolean fill) {
    511371        if (size > 1) {
    512372            int radius = size / 2;
     
    571431    }
    572432
    573433    /**
    574      * Checks if segment is visible in display.
    575      *
    576      * @param p1 First point of the way segment.
    577      * @param p2 Second point of the way segment.
    578      * @return <code>true</code> if segment is visible.
    579      */
    580     protected boolean isSegmentVisible(Point p1, Point p2) {
    581         if ((p1.x < 0) && (p2.x < 0)) return false;
    582         if ((p1.y < 0) && (p2.y < 0)) return false;
    583         if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
    584         if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
    585         return true;
    586     }
    587 
    588     /**
    589434     * Checks if a polygon is visible in display.
    590435     *
    591436     * @param polygon The polygon to check.
  • core/src/org/openstreetmap/josm/gui/mappaint/AreaElemStyle.java

     
    1010import org.openstreetmap.josm.data.osm.Relation;
    1111import org.openstreetmap.josm.data.osm.Way;
    1212import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    13 import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
     13import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
    1414import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
    1515import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
    1616import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    7777    }
    7878
    7979    @Override
    80     public void paintPrimitive(OsmPrimitive osm, MapPaintSettings paintSettings, MapPainter painter, boolean selected, boolean member) {
     80    public void paintPrimitive(OsmPrimitive osm, MapPaintSettings paintSettings, StyledMapRenderer painter, boolean selected, boolean member) {
    8181        if (osm instanceof Way)
    8282        {
    8383            Color myColor = color;
  • core/src/org/openstreetmap/josm/gui/mappaint/BoxTextElemStyle.java

     
    99import org.openstreetmap.josm.data.osm.Node;
    1010import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1111import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    12 import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
     12import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
    1313import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
    1414import org.openstreetmap.josm.tools.CheckParameterUtil;
    1515
     
    180180    }
    181181
    182182    @Override
    183     public void paintPrimitive(OsmPrimitive osm, MapPaintSettings settings, MapPainter painter, boolean selected, boolean member) {
     183    public void paintPrimitive(OsmPrimitive osm, MapPaintSettings settings, StyledMapRenderer painter, boolean selected, boolean member) {
    184184        if (osm instanceof Node) {
    185185            painter.drawBoxText((Node) osm, this);
    186186        }
  • core/src/org/openstreetmap/josm/gui/mappaint/ElemStyle.java

     
    1010import org.openstreetmap.josm.Main;
    1111import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1212import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    13 import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
     13import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
    1414import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat;
    1515
    1616abstract public class ElemStyle implements StyleKeys {
     
    4343     * @param selected true, if primitive is selected
    4444     * @param member true, if primitive is not selected and member of a selected relation
    4545     */
    46     public abstract void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, MapPainter painter, boolean selected, boolean member);
     46    public abstract void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, boolean selected, boolean member);
    4747
    4848    public boolean isProperLineStyle() {
    4949        return false;
  • core/src/org/openstreetmap/josm/gui/mappaint/LineElemStyle.java

     
    1111import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1212import org.openstreetmap.josm.data.osm.Way;
    1313import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    14 import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
     14import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
    1515import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
    1616import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat;
    1717import org.openstreetmap.josm.tools.Utils;
     
    265265    }
    266266
    267267    @Override
    268     public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, MapPainter painter, boolean selected, boolean member) {
     268    public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, boolean selected, boolean member) {
    269269        Way w = (Way)primitive;
    270270        /* show direction arrows, if draw.segment.relevant_directions_only is not set,
    271271        the way is tagged with a direction key
  • core/src/org/openstreetmap/josm/gui/mappaint/LinePatternElemStyle.java

     
    44import org.openstreetmap.josm.data.osm.OsmPrimitive;
    55import org.openstreetmap.josm.data.osm.Way;
    66import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    7 import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
     7import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
    88import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
    99
    1010/**
     
    3030    }
    3131
    3232    @Override
    33     public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, MapPainter painter, boolean selected, boolean member) {
     33    public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, boolean selected, boolean member) {
    3434        Way w = (Way)primitive;
    3535        painter.drawLinePattern(w, pattern.getImage());
    3636    }
  • core/src/org/openstreetmap/josm/gui/mappaint/LineTextElemStyle.java

     
    44import org.openstreetmap.josm.data.osm.OsmPrimitive;
    55import org.openstreetmap.josm.data.osm.Way;
    66import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    7 import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
     7import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
    88import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
    99import org.openstreetmap.josm.tools.Utils;
    1010
     
    3030    }
    3131
    3232    @Override
    33     public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, MapPainter painter, boolean selected, boolean member) {
     33    public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, boolean selected, boolean member) {
    3434        Way w = (Way)primitive;
    3535        painter.drawTextOnPath(w, text);
    3636    }
  • core/src/org/openstreetmap/josm/gui/mappaint/NodeElemStyle.java

     
    1313import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1414import org.openstreetmap.josm.data.osm.Relation;
    1515import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    16 import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
     16import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
    1717import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.BoxProvider;
    1818import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.SimpleBoxProvider;
    1919import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
     
    229229    }
    230230
    231231    @Override
    232     public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings settings, MapPainter painter, boolean selected, boolean member) {
     232    public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings settings, StyledMapRenderer painter, boolean selected, boolean member) {
    233233        if (primitive instanceof Node) {
    234234            Node n = (Node) primitive;
    235235            if (mapImage != null && painter.isShowIcons()) {