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