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