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