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