001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm.visitor.paint; 003 004import java.awt.Color; 005import java.awt.Graphics2D; 006import java.awt.geom.GeneralPath; 007import java.awt.geom.Path2D; 008import java.awt.geom.Rectangle2D; 009import java.util.Iterator; 010 011import org.openstreetmap.josm.Main; 012import org.openstreetmap.josm.data.osm.BBox; 013import org.openstreetmap.josm.data.osm.DataSet; 014import org.openstreetmap.josm.data.osm.Node; 015import org.openstreetmap.josm.data.osm.Way; 016import org.openstreetmap.josm.data.osm.WaySegment; 017import org.openstreetmap.josm.gui.MapViewState; 018import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 019import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle; 020import org.openstreetmap.josm.gui.NavigatableComponent; 021import org.openstreetmap.josm.tools.CheckParameterUtil; 022 023/** 024 * <p>Abstract common superclass for {@link Rendering} implementations.</p> 025 * 026 */ 027public abstract class AbstractMapRenderer implements Rendering { 028 029 /** the graphics context to which the visitor renders OSM objects */ 030 protected Graphics2D g; 031 /** the map viewport - provides projection and hit detection functionality */ 032 protected NavigatableComponent nc; 033 034 /** 035 * The {@link MapViewState} to use to convert between coordinates. 036 */ 037 protected final MapViewState mapState; 038 039 /** if true, the paint visitor shall render OSM objects such that they 040 * look inactive. Example: rendering of data in an inactive layer using light gray as color only. */ 041 protected boolean isInactiveMode; 042 /** Color Preference for background */ 043 protected Color backgroundColor; 044 /** Color Preference for inactive objects */ 045 protected Color inactiveColor; 046 /** Color Preference for selected objects */ 047 protected Color selectedColor; 048 /** Color Preference for members of selected relations */ 049 protected Color relationSelectedColor; 050 /** Color Preference for nodes */ 051 protected Color nodeColor; 052 053 /** Color Preference for hightlighted objects */ 054 protected Color highlightColor; 055 /** Preference: size of virtual nodes (0 displayes display) */ 056 protected int virtualNodeSize; 057 /** Preference: minimum space (displayed way length) to display virtual nodes */ 058 protected int virtualNodeSpace; 059 060 /** Preference: minimum space (displayed way length) to display segment numbers */ 061 protected int segmentNumberSpace; 062 063 /** 064 * <p>Creates an abstract paint visitor</p> 065 * 066 * @param g the graphics context. Must not be null. 067 * @param nc the map viewport. Must not be null. 068 * @param isInactiveMode if true, the paint visitor shall render OSM objects such that they 069 * look inactive. Example: rendering of data in an inactive layer using light gray as color only. 070 * @throws IllegalArgumentException if {@code g} is null 071 * @throws IllegalArgumentException if {@code nc} is null 072 */ 073 public AbstractMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) { 074 CheckParameterUtil.ensureParameterNotNull(g); 075 CheckParameterUtil.ensureParameterNotNull(nc); 076 this.g = g; 077 this.nc = nc; 078 this.mapState = nc.getState(); 079 this.isInactiveMode = isInactiveMode; 080 } 081 082 /** 083 * Draw the node as small square with the given color. 084 * 085 * @param n The node to draw. 086 * @param color The color of the node. 087 * @param size size in pixels 088 * @param fill determines if the square mmust be filled 089 */ 090 public abstract void drawNode(Node n, Color color, int size, boolean fill); 091 092 /** 093 * Draw an number of the order of the two consecutive nodes within the 094 * parents way 095 * 096 * @param p1 First point of the way segment. 097 * @param p2 Second point of the way segment. 098 * @param orderNumber The number of the segment in the way. 099 * @param clr The color to use for drawing the text. 100 * @since 10827 101 */ 102 protected void drawOrderNumber(MapViewPoint p1, MapViewPoint p2, int orderNumber, Color clr) { 103 if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) { 104 String on = Integer.toString(orderNumber); 105 int strlen = on.length(); 106 double centerX = (p1.getInViewX()+p2.getInViewX())/2; 107 double centerY = (p1.getInViewY()+p2.getInViewY())/2; 108 double x = centerX - 4*strlen; 109 double y = centerY + 4; 110 111 if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) { 112 y = centerY - virtualNodeSize - 3; 113 } 114 115 g.setColor(backgroundColor); 116 g.fill(new Rectangle2D.Double(x-1, y-12, 8*strlen+1, 14)); 117 g.setColor(clr); 118 g.drawString(on, (int) x, (int) y); 119 } 120 } 121 122 /** 123 * Draws virtual nodes. 124 * 125 * @param data The data set being rendered. 126 * @param bbox The bounding box being displayed. 127 */ 128 public void drawVirtualNodes(DataSet data, BBox bbox) { 129 if (virtualNodeSize == 0 || data == null || bbox == null) 130 return; 131 // print normal virtual nodes 132 GeneralPath path = new GeneralPath(); 133 for (Way osm : data.searchWays(bbox)) { 134 if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) { 135 visitVirtual(path, osm); 136 } 137 } 138 g.setColor(nodeColor); 139 g.draw(path); 140 try { 141 // print highlighted virtual nodes. Since only the color changes, simply 142 // drawing them over the existing ones works fine (at least in their current simple style) 143 path = new GeneralPath(); 144 for (WaySegment wseg: data.getHighlightedVirtualNodes()) { 145 if (wseg.way.isUsable() && !wseg.way.isDisabled()) { 146 visitVirtual(path, wseg.toWay()); 147 } 148 } 149 g.setColor(highlightColor); 150 g.draw(path); 151 } catch (ArrayIndexOutOfBoundsException e) { 152 // Silently ignore any ArrayIndexOutOfBoundsException that may be raised 153 // if the way has changed while being rendered (fix #7979) 154 // TODO: proper solution ? 155 // Idea from bastiK: 156 // avoid the WaySegment class and add another data class with { Way way; Node firstNode, secondNode; int firstIdx; }. 157 // On read, it would first check, if the way still has firstIdx+2 nodes, then check if the corresponding way nodes are still 158 // the same and report changes in a more controlled manner. 159 Main.trace(e); 160 } 161 } 162 163 /** 164 * Reads the color definitions from preferences. This function is <code>public</code>, so that 165 * color names in preferences can be displayed even without calling the wireframe display before. 166 */ 167 public void getColors() { 168 this.backgroundColor = PaintColors.BACKGROUND.get(); 169 this.inactiveColor = PaintColors.INACTIVE.get(); 170 this.selectedColor = PaintColors.SELECTED.get(); 171 this.relationSelectedColor = PaintColors.RELATIONSELECTED.get(); 172 this.nodeColor = PaintColors.NODE.get(); 173 this.highlightColor = PaintColors.HIGHLIGHT.get(); 174 } 175 176 /** 177 * Reads all the settings from preferences. Calls the @{link #getColors} 178 * function. 179 * 180 * @param virtual <code>true</code> if virtual nodes are used 181 */ 182 protected void getSettings(boolean virtual) { 183 this.virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0; 184 this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70); 185 this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40); 186 getColors(); 187 } 188 189 /** 190 * Checks if a way segemnt is large enough for additional information display. 191 * 192 * @param p1 First point of the way segment. 193 * @param p2 Second point of the way segment. 194 * @param space The free space to check against. 195 * @return <code>true</code> if segment is larger than required space 196 * @since 10827 197 */ 198 public static boolean isLargeSegment(MapViewPoint p1, MapViewPoint p2, int space) { 199 return p1.oneNormInView(p2) > space; 200 } 201 202 /** 203 * Checks if segment is visible in display. 204 * 205 * @param p1 First point of the way segment. 206 * @param p2 Second point of the way segment. 207 * @return <code>true</code> if segment may be visible. 208 * @since 10827 209 */ 210 protected boolean isSegmentVisible(MapViewPoint p1, MapViewPoint p2) { 211 MapViewRectangle view = mapState.getViewArea(); 212 // not outside in the same direction 213 return (p1.getOutsideRectangleFlags(view) & p2.getOutsideRectangleFlags(view)) == 0; 214 } 215 216 /** 217 * Creates path for drawing virtual nodes for one way. 218 * 219 * @param path The path to append drawing to. 220 * @param w The ways to draw node for. 221 * @since 10827 222 */ 223 public void visitVirtual(Path2D path, Way w) { 224 Iterator<Node> it = w.getNodes().iterator(); 225 if (it.hasNext()) { 226 MapViewPoint lastP = mapState.getPointFor(it.next()); 227 while (it.hasNext()) { 228 MapViewPoint p = mapState.getPointFor(it.next()); 229 if (isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace)) { 230 double x = (p.getInViewX()+lastP.getInViewX())/2; 231 double y = (p.getInViewY()+lastP.getInViewY())/2; 232 path.moveTo(x-virtualNodeSize, y); 233 path.lineTo(x+virtualNodeSize, y); 234 path.moveTo(x, y-virtualNodeSize); 235 path.lineTo(x, y+virtualNodeSize); 236 } 237 lastP = p; 238 } 239 } 240 } 241}