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