1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.data.osm.visitor.paint;
|
---|
3 |
|
---|
4 | import java.awt.Color;
|
---|
5 | import java.awt.Graphics2D;
|
---|
6 | import java.awt.geom.GeneralPath;
|
---|
7 | import java.awt.geom.Path2D;
|
---|
8 | import java.awt.geom.Rectangle2D;
|
---|
9 | import java.util.Iterator;
|
---|
10 |
|
---|
11 | import org.openstreetmap.josm.data.osm.BBox;
|
---|
12 | import org.openstreetmap.josm.data.osm.INode;
|
---|
13 | import org.openstreetmap.josm.data.osm.IWay;
|
---|
14 | import org.openstreetmap.josm.data.osm.OsmData;
|
---|
15 | import org.openstreetmap.josm.data.osm.WaySegment;
|
---|
16 | import org.openstreetmap.josm.gui.MapViewState;
|
---|
17 | import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
|
---|
18 | import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
|
---|
19 | import org.openstreetmap.josm.gui.NavigatableComponent;
|
---|
20 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
21 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
---|
22 | import org.openstreetmap.josm.tools.Logging;
|
---|
23 |
|
---|
24 | /**
|
---|
25 | * <p>Abstract common superclass for {@link Rendering} implementations.</p>
|
---|
26 | * @since 4087
|
---|
27 | */
|
---|
28 | public abstract class AbstractMapRenderer implements Rendering {
|
---|
29 |
|
---|
30 | /** the graphics context to which the visitor renders OSM objects */
|
---|
31 | protected final Graphics2D g;
|
---|
32 | /** the map viewport - provides projection and hit detection functionality */
|
---|
33 | protected final NavigatableComponent nc;
|
---|
34 |
|
---|
35 | /**
|
---|
36 | * The {@link MapViewState} to use to convert between coordinates.
|
---|
37 | */
|
---|
38 | protected final MapViewState mapState;
|
---|
39 |
|
---|
40 | /** if true, the paint visitor shall render OSM objects such that they
|
---|
41 | * look inactive. Example: rendering of data in an inactive layer using light gray as color only. */
|
---|
42 | protected boolean isInactiveMode;
|
---|
43 | /** Color Preference for background */
|
---|
44 | protected Color backgroundColor;
|
---|
45 | /** Color Preference for inactive objects */
|
---|
46 | protected Color inactiveColor;
|
---|
47 | /** Color Preference for selected objects */
|
---|
48 | protected Color selectedColor;
|
---|
49 | /** Color Preference for members of selected relations */
|
---|
50 | protected Color relationSelectedColor;
|
---|
51 | /** Color Preference for nodes */
|
---|
52 | protected Color nodeColor;
|
---|
53 |
|
---|
54 | /** Color Preference for hightlighted objects */
|
---|
55 | protected Color highlightColor;
|
---|
56 | /** Preference: size of virtual nodes (0 displayes display) */
|
---|
57 | protected int virtualNodeSize;
|
---|
58 | /** Preference: minimum space (displayed way length) to display virtual nodes */
|
---|
59 | protected int virtualNodeSpace;
|
---|
60 |
|
---|
61 | /** Preference: minimum space (displayed way length) to display segment numbers */
|
---|
62 | protected int segmentNumberSpace;
|
---|
63 |
|
---|
64 | /** Performs slow operations by default. Can be disabled when fast partial rendering is required */
|
---|
65 | protected boolean doSlowOperations = true;
|
---|
66 |
|
---|
67 | /**
|
---|
68 | * <p>Creates an abstract paint visitor</p>
|
---|
69 | *
|
---|
70 | * @param g the graphics context. Must not be null.
|
---|
71 | * @param nc the map viewport. Must not be null.
|
---|
72 | * @param isInactiveMode if true, the paint visitor shall render OSM objects such that they
|
---|
73 | * look inactive. Example: rendering of data in an inactive layer using light gray as color only.
|
---|
74 | * @throws IllegalArgumentException if {@code g} is null
|
---|
75 | * @throws IllegalArgumentException if {@code nc} is null
|
---|
76 | */
|
---|
77 | public AbstractMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) {
|
---|
78 | CheckParameterUtil.ensureParameterNotNull(g);
|
---|
79 | CheckParameterUtil.ensureParameterNotNull(nc);
|
---|
80 | this.g = g;
|
---|
81 | this.nc = nc;
|
---|
82 | this.mapState = nc.getState();
|
---|
83 | this.isInactiveMode = isInactiveMode;
|
---|
84 | }
|
---|
85 |
|
---|
86 | /**
|
---|
87 | * Draw the node as small square with the given color.
|
---|
88 | *
|
---|
89 | * @param n The node to draw.
|
---|
90 | * @param color The color of the node.
|
---|
91 | * @param size size in pixels
|
---|
92 | * @param fill determines if the square mmust be filled
|
---|
93 | */
|
---|
94 | public abstract void drawNode(INode n, Color color, int size, boolean fill);
|
---|
95 |
|
---|
96 | /**
|
---|
97 | * Draw an number of the order of the two consecutive nodes within the
|
---|
98 | * parents way
|
---|
99 | *
|
---|
100 | * @param p1 First point of the way segment.
|
---|
101 | * @param p2 Second point of the way segment.
|
---|
102 | * @param orderNumber The number of the segment in the way.
|
---|
103 | * @param clr The color to use for drawing the text.
|
---|
104 | * @since 10827
|
---|
105 | */
|
---|
106 | protected void drawOrderNumber(MapViewPoint p1, MapViewPoint p2, int orderNumber, Color clr) {
|
---|
107 | if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
|
---|
108 | String on = Integer.toString(orderNumber);
|
---|
109 | int strlen = on.length();
|
---|
110 | double centerX = (p1.getInViewX()+p2.getInViewX())/2;
|
---|
111 | double centerY = (p1.getInViewY()+p2.getInViewY())/2;
|
---|
112 | double x = centerX - 4*strlen;
|
---|
113 | double y = centerY + 4;
|
---|
114 |
|
---|
115 | if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) {
|
---|
116 | y = centerY - virtualNodeSize - 3;
|
---|
117 | }
|
---|
118 |
|
---|
119 | g.setColor(backgroundColor);
|
---|
120 | g.fill(new Rectangle2D.Double(x-1, y-12, 8*strlen+1d, 14));
|
---|
121 | g.setColor(clr);
|
---|
122 | g.drawString(on, (int) x, (int) y);
|
---|
123 | }
|
---|
124 | }
|
---|
125 |
|
---|
126 | /**
|
---|
127 | * Draws virtual nodes.
|
---|
128 | *
|
---|
129 | * @param data The data set being rendered.
|
---|
130 | * @param bbox The bounding box being displayed.
|
---|
131 | * @since 13810 (signature)
|
---|
132 | */
|
---|
133 | public void drawVirtualNodes(OsmData<?, ?, ?, ?> data, BBox bbox) {
|
---|
134 | if (virtualNodeSize == 0 || data == null || bbox == null || data.isLocked())
|
---|
135 | return;
|
---|
136 | // print normal virtual nodes
|
---|
137 | GeneralPath path = new GeneralPath();
|
---|
138 | for (IWay<?> osm : data.searchWays(bbox)) {
|
---|
139 | if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) {
|
---|
140 | visitVirtual(path, osm);
|
---|
141 | }
|
---|
142 | }
|
---|
143 | g.setColor(nodeColor);
|
---|
144 | g.draw(path);
|
---|
145 | try {
|
---|
146 | // print highlighted virtual nodes. Since only the color changes, simply
|
---|
147 | // drawing them over the existing ones works fine (at least in their current simple style)
|
---|
148 | path = new GeneralPath();
|
---|
149 | for (WaySegment wseg: data.getHighlightedVirtualNodes()) {
|
---|
150 | if (wseg.way.isUsable() && !wseg.way.isDisabled()) {
|
---|
151 | visitVirtual(path, wseg.toWay());
|
---|
152 | }
|
---|
153 | }
|
---|
154 | g.setColor(highlightColor);
|
---|
155 | g.draw(path);
|
---|
156 | } catch (ArrayIndexOutOfBoundsException e) {
|
---|
157 | // Silently ignore any ArrayIndexOutOfBoundsException that may be raised
|
---|
158 | // if the way has changed while being rendered (fix #7979)
|
---|
159 | // TODO: proper solution ?
|
---|
160 | // Idea from bastiK:
|
---|
161 | // avoid the WaySegment class and add another data class with { Way way; Node firstNode, secondNode; int firstIdx; }.
|
---|
162 | // On read, it would first check, if the way still has firstIdx+2 nodes, then check if the corresponding way nodes are still
|
---|
163 | // the same and report changes in a more controlled manner.
|
---|
164 | Logging.trace(e);
|
---|
165 | }
|
---|
166 | }
|
---|
167 |
|
---|
168 | /**
|
---|
169 | * Reads the color definitions from preferences. This function is <code>public</code>, so that
|
---|
170 | * color names in preferences can be displayed even without calling the wireframe display before.
|
---|
171 | */
|
---|
172 | public void getColors() {
|
---|
173 | this.backgroundColor = PaintColors.BACKGROUND.get();
|
---|
174 | this.inactiveColor = PaintColors.INACTIVE.get();
|
---|
175 | this.selectedColor = PaintColors.SELECTED.get();
|
---|
176 | this.relationSelectedColor = PaintColors.RELATIONSELECTED.get();
|
---|
177 | this.nodeColor = PaintColors.NODE.get();
|
---|
178 | this.highlightColor = PaintColors.HIGHLIGHT.get();
|
---|
179 | }
|
---|
180 |
|
---|
181 | /**
|
---|
182 | * Reads all the settings from preferences. Calls the @{link #getColors}
|
---|
183 | * function.
|
---|
184 | *
|
---|
185 | * @param virtual <code>true</code> if virtual nodes are used
|
---|
186 | */
|
---|
187 | protected void getSettings(boolean virtual) {
|
---|
188 | this.virtualNodeSize = virtual ? Config.getPref().getInt("mappaint.node.virtual-size", 8) / 2 : 0;
|
---|
189 | this.virtualNodeSpace = Config.getPref().getInt("mappaint.node.virtual-space", 70);
|
---|
190 | this.segmentNumberSpace = Config.getPref().getInt("mappaint.segmentnumber.space", 40);
|
---|
191 | getColors();
|
---|
192 | }
|
---|
193 |
|
---|
194 | /**
|
---|
195 | * Checks if a way segemnt is large enough for additional information display.
|
---|
196 | *
|
---|
197 | * @param p1 First point of the way segment.
|
---|
198 | * @param p2 Second point of the way segment.
|
---|
199 | * @param space The free space to check against.
|
---|
200 | * @return <code>true</code> if segment is larger than required space
|
---|
201 | * @since 10827
|
---|
202 | */
|
---|
203 | public static boolean isLargeSegment(MapViewPoint p1, MapViewPoint p2, int space) {
|
---|
204 | return p1.oneNormInView(p2) > space;
|
---|
205 | }
|
---|
206 |
|
---|
207 | /**
|
---|
208 | * Checks if segment is visible in display.
|
---|
209 | *
|
---|
210 | * @param p1 First point of the way segment.
|
---|
211 | * @param p2 Second point of the way segment.
|
---|
212 | * @return <code>true</code> if segment may be visible.
|
---|
213 | * @since 10827
|
---|
214 | */
|
---|
215 | protected boolean isSegmentVisible(MapViewPoint p1, MapViewPoint p2) {
|
---|
216 | MapViewRectangle view = mapState.getViewArea();
|
---|
217 | // not outside in the same direction
|
---|
218 | return (p1.getOutsideRectangleFlags(view) & p2.getOutsideRectangleFlags(view)) == 0;
|
---|
219 | }
|
---|
220 |
|
---|
221 | /**
|
---|
222 | * Creates path for drawing virtual nodes for one way.
|
---|
223 | *
|
---|
224 | * @param path The path to append drawing to.
|
---|
225 | * @param w The ways to draw node for.
|
---|
226 | * @since 10827
|
---|
227 | * @since 13810 (signature)
|
---|
228 | */
|
---|
229 | public void visitVirtual(Path2D path, IWay<?> w) {
|
---|
230 | Iterator<? extends INode> it = w.getNodes().iterator();
|
---|
231 | MapViewPoint lastP = null;
|
---|
232 | while (it.hasNext()) {
|
---|
233 | INode n = it.next();
|
---|
234 | if (n.isLatLonKnown()) {
|
---|
235 | MapViewPoint p = mapState.getPointFor(n);
|
---|
236 | if (lastP != null && isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace)) {
|
---|
237 | double x = (p.getInViewX()+lastP.getInViewX())/2;
|
---|
238 | double y = (p.getInViewY()+lastP.getInViewY())/2;
|
---|
239 | path.moveTo(x-virtualNodeSize, y);
|
---|
240 | path.lineTo(x+virtualNodeSize, y);
|
---|
241 | path.moveTo(x, y-virtualNodeSize);
|
---|
242 | path.lineTo(x, y+virtualNodeSize);
|
---|
243 | }
|
---|
244 | lastP = p;
|
---|
245 | }
|
---|
246 | }
|
---|
247 | }
|
---|
248 |
|
---|
249 | /**
|
---|
250 | * Sets whether slow operations such as text rendering must be performed (true by default).
|
---|
251 | * @param enable whether slow operations such as text rendering must be performed
|
---|
252 | * @since 13987
|
---|
253 | */
|
---|
254 | public final void enableSlowOperations(boolean enable) {
|
---|
255 | doSlowOperations = enable;
|
---|
256 | }
|
---|
257 | }
|
---|