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 thrown if {@code g} is null 063 * @throws IllegalArgumentException thrown if {@code nc} is null 064 */ 065 public AbstractMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) throws IllegalArgumentException{ 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 */ 091 protected void drawOrderNumber(Point p1, Point p2, int orderNumber, Color clr) { 092 if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) { 093 String on = Integer.toString(orderNumber); 094 int strlen = on.length(); 095 int x = (p1.x+p2.x)/2 - 4*strlen; 096 int y = (p1.y+p2.y)/2 + 4; 097 098 if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) { 099 y = (p1.y+p2.y)/2 - virtualNodeSize - 3; 100 } 101 102 g.setColor(backgroundColor); 103 g.fillRect(x-1, y-12, 8*strlen+1, 14); 104 g.setColor(clr); 105 g.drawString(on, x, y); 106 } 107 } 108 109 /** 110 * Draws virtual nodes. 111 * 112 * @param data The data set being rendered. 113 * @param bbox The bounding box being displayed. 114 */ 115 public void drawVirtualNodes(DataSet data, BBox bbox) { 116 if (virtualNodeSize == 0 || data == null || bbox == null) 117 return; 118 // print normal virtual nodes 119 GeneralPath path = new GeneralPath(); 120 for (Way osm : data.searchWays(bbox)) { 121 if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) { 122 visitVirtual(path, osm); 123 } 124 } 125 g.setColor(nodeColor); 126 g.draw(path); 127 try { 128 // print highlighted virtual nodes. Since only the color changes, simply 129 // drawing them over the existing ones works fine (at least in their current 130 // 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: avoid the WaySegment class and add another data class with { Way way; Node firstNode, secondNode; int firstIdx; }. 144 // On read, it would first check, if the way still has firstIdx+2 nodes, then check if the corresponding way nodes are still the same 145 // and report changes in a more controlled manner. 146 } 147 } 148 149 /** 150 * Reads the color definitions from preferences. This function is <code>public</code>, so that 151 * color names in preferences can be displayed even without calling the wireframe display before. 152 */ 153 public void getColors() { 154 this.backgroundColor = PaintColors.BACKGROUND.get(); 155 this.inactiveColor = PaintColors.INACTIVE.get(); 156 this.selectedColor = PaintColors.SELECTED.get(); 157 this.relationSelectedColor = PaintColors.RELATIONSELECTED.get(); 158 this.nodeColor = PaintColors.NODE.get(); 159 this.highlightColor = PaintColors.HIGHLIGHT.get(); 160 } 161 162 /** 163 * Reads all the settings from preferences. Calls the @{link #getColors} 164 * function. 165 * 166 * @param virtual <code>true</code> if virtual nodes are used 167 */ 168 protected void getSettings(boolean virtual) { 169 this.virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0; 170 this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70); 171 this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40); 172 getColors(); 173 } 174 175 /** 176 * Checks if a way segemnt is large enough for additional information display. 177 * 178 * @param p1 First point of the way segment. 179 * @param p2 Second point of the way segment. 180 * @param space The free space to check against. 181 * @return <code>true</code> if segment is larger than required space 182 */ 183 public static boolean isLargeSegment(Point2D p1, Point2D p2, int space) { 184 double xd = Math.abs(p1.getX()-p2.getX()); 185 double yd = Math.abs(p1.getY()-p2.getY()); 186 return (xd+yd > space); 187 } 188 189 /** 190 * Checks if segment is visible in display. 191 * 192 * @param p1 First point of the way segment. 193 * @param p2 Second point of the way segment. 194 * @return <code>true</code> if segment is visible. 195 */ 196 protected boolean isSegmentVisible(Point p1, Point p2) { 197 if ((p1.x < 0) && (p2.x < 0)) return false; 198 if ((p1.y < 0) && (p2.y < 0)) return false; 199 if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false; 200 if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false; 201 return true; 202 } 203 204 /** 205 * Creates path for drawing virtual nodes for one way. 206 * 207 * @param path The path to append drawing to. 208 * @param w The ways to draw node for. 209 */ 210 public void visitVirtual(GeneralPath path, Way w) { 211 Iterator<Node> it = w.getNodes().iterator(); 212 if (it.hasNext()) { 213 Point lastP = nc.getPoint(it.next()); 214 while (it.hasNext()) 215 { 216 Point p = nc.getPoint(it.next()); 217 if (isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace)) 218 { 219 int x = (p.x+lastP.x)/2; 220 int y = (p.y+lastP.y)/2; 221 path.moveTo(x-virtualNodeSize, y); 222 path.lineTo(x+virtualNodeSize, y); 223 path.moveTo(x, y-virtualNodeSize); 224 path.lineTo(x, y+virtualNodeSize); 225 } 226 lastP = p; 227 } 228 } 229 } 230}