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