source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java@ 12756

Last change on this file since 12756 was 12756, checked in by Don-vip, 7 years ago

see #15229 - see #15182 - move RotationAngle from gui.util to tools

  • Property svn:eol-style set to native
File size: 16.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.styleelement;
3
4import java.awt.BasicStroke;
5import java.awt.Color;
6import java.awt.Rectangle;
7import java.awt.Stroke;
8import java.util.Objects;
9import java.util.Optional;
10import java.util.stream.IntStream;
11
12import org.openstreetmap.josm.Main;
13import org.openstreetmap.josm.data.osm.Node;
14import org.openstreetmap.josm.data.osm.OsmPrimitive;
15import org.openstreetmap.josm.data.osm.Relation;
16import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
17import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
18import org.openstreetmap.josm.gui.draw.SymbolShape;
19import org.openstreetmap.josm.gui.mappaint.Cascade;
20import org.openstreetmap.josm.gui.mappaint.Environment;
21import org.openstreetmap.josm.gui.mappaint.Keyword;
22import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
23import org.openstreetmap.josm.gui.mappaint.MultiCascade;
24import org.openstreetmap.josm.gui.mappaint.StyleElementList;
25import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.BoxProvider;
26import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement.SimpleBoxProvider;
27import org.openstreetmap.josm.tools.CheckParameterUtil;
28import org.openstreetmap.josm.tools.Logging;
29import org.openstreetmap.josm.tools.RotationAngle;
30import org.openstreetmap.josm.tools.Utils;
31
32/**
33 * applies for Nodes and turn restriction relations
34 */
35public class NodeElement extends StyleElement {
36 /**
37 * The image that is used to display this node. May be <code>null</code>
38 */
39 public final MapImage mapImage;
40 /**
41 * The angle that is used to rotate {@link #mapImage}. May be <code>null</code> to indicate no rotation.
42 */
43 public final RotationAngle mapImageAngle;
44 /**
45 * The symbol that should be used for drawing this node.
46 */
47 public final Symbol symbol;
48
49 private static final String[] ICON_KEYS = {ICON_IMAGE, ICON_WIDTH, ICON_HEIGHT, ICON_OPACITY, ICON_OFFSET_X, ICON_OFFSET_Y};
50
51 /**
52 * The style used for simple nodes
53 */
54 public static final NodeElement SIMPLE_NODE_ELEMSTYLE;
55 /**
56 * A box provider that provides the size of a simple node
57 */
58 public static final BoxProvider SIMPLE_NODE_ELEMSTYLE_BOXPROVIDER;
59 static {
60 MultiCascade mc = new MultiCascade();
61 mc.getOrCreateCascade("default");
62 SIMPLE_NODE_ELEMSTYLE = create(new Environment(null, mc, "default", null), 4.1f, true);
63 if (SIMPLE_NODE_ELEMSTYLE == null) throw new AssertionError();
64 SIMPLE_NODE_ELEMSTYLE_BOXPROVIDER = SIMPLE_NODE_ELEMSTYLE.getBoxProvider();
65 }
66
67 /**
68 * The default styles that are used for nodes.
69 * @see #SIMPLE_NODE_ELEMSTYLE
70 */
71 public static final StyleElementList DEFAULT_NODE_STYLELIST = new StyleElementList(NodeElement.SIMPLE_NODE_ELEMSTYLE);
72 /**
73 * The default styles that are used for nodes with text.
74 */
75 public static final StyleElementList DEFAULT_NODE_STYLELIST_TEXT = new StyleElementList(NodeElement.SIMPLE_NODE_ELEMSTYLE,
76 BoxTextElement.SIMPLE_NODE_TEXT_ELEMSTYLE);
77
78 protected NodeElement(Cascade c, MapImage mapImage, Symbol symbol, float defaultMajorZindex, RotationAngle rotationAngle) {
79 super(c, defaultMajorZindex);
80 this.mapImage = mapImage;
81 this.symbol = symbol;
82 this.mapImageAngle = Objects.requireNonNull(rotationAngle, "rotationAngle");
83 }
84
85 /**
86 * Creates a new node element for the given Environment
87 * @param env The environment
88 * @return The node element style or <code>null</code> if the node should not be painted.
89 */
90 public static NodeElement create(Environment env) {
91 return create(env, 4f, false);
92 }
93
94 private static NodeElement create(Environment env, float defaultMajorZindex, boolean allowDefault) {
95 MapImage mapImage = createIcon(env);
96 Symbol symbol = null;
97 if (mapImage == null) {
98 symbol = createSymbol(env);
99 }
100
101 // optimization: if we neither have a symbol, nor a mapImage
102 // we don't have to check for the remaining style properties and we don't
103 // have to allocate a node element style.
104 if (!allowDefault && symbol == null && mapImage == null) return null;
105
106 Cascade c = env.mc.getCascade(env.layer);
107 RotationAngle rotationAngle = createRotationAngle(env);
108 return new NodeElement(c, mapImage, symbol, defaultMajorZindex, rotationAngle);
109 }
110
111 /**
112 * Reads the icon-rotation property and creates a rotation angle from it.
113 * @param env The environment
114 * @return The angle
115 * @since 11670
116 */
117 public static RotationAngle createRotationAngle(Environment env) {
118 Cascade c = env.mc.getCascade(env.layer);
119
120 RotationAngle rotationAngle = RotationAngle.NO_ROTATION;
121 final Float angle = c.get(ICON_ROTATION, null, Float.class, true);
122 if (angle != null) {
123 rotationAngle = RotationAngle.buildStaticRotation(angle);
124 } else {
125 final Keyword rotationKW = c.get(ICON_ROTATION, null, Keyword.class);
126 if (rotationKW != null) {
127 if ("way".equals(rotationKW.val)) {
128 rotationAngle = RotationAngle.buildWayDirectionRotation();
129 } else {
130 try {
131 rotationAngle = RotationAngle.buildStaticRotation(rotationKW.val);
132 } catch (IllegalArgumentException ignore) {
133 Logging.trace(ignore);
134 }
135 }
136 }
137 }
138 return rotationAngle;
139 }
140
141 /**
142 * Create a map icon for the environment using the default keys.
143 * @param env The environment to read the icon form
144 * @return The icon or <code>null</code> if no icon is defined
145 * @since 11670
146 */
147 public static MapImage createIcon(final Environment env) {
148 return createIcon(env, ICON_KEYS);
149 }
150
151 /**
152 * Create a map icon for the environment.
153 * @param env The environment to read the icon form
154 * @param keys The keys, indexed by the ICON_..._IDX constants.
155 * @return The icon or <code>null</code> if no icon is defined
156 */
157 public static MapImage createIcon(final Environment env, final String... keys) {
158 CheckParameterUtil.ensureParameterNotNull(env, "env");
159 CheckParameterUtil.ensureParameterNotNull(keys, "keys");
160
161 Cascade c = env.mc.getCascade(env.layer);
162
163 final IconReference iconRef = c.get(keys[ICON_IMAGE_IDX], null, IconReference.class, true);
164 if (iconRef == null)
165 return null;
166
167 Cascade cDef = env.mc.getCascade("default");
168
169 Float widthOnDefault = cDef.get(keys[ICON_WIDTH_IDX], null, Float.class);
170 if (widthOnDefault != null && widthOnDefault <= 0) {
171 widthOnDefault = null;
172 }
173 Float widthF = getWidth(c, keys[ICON_WIDTH_IDX], widthOnDefault);
174
175 Float heightOnDefault = cDef.get(keys[ICON_HEIGHT_IDX], null, Float.class);
176 if (heightOnDefault != null && heightOnDefault <= 0) {
177 heightOnDefault = null;
178 }
179 Float heightF = getWidth(c, keys[ICON_HEIGHT_IDX], heightOnDefault);
180
181 int width = widthF == null ? -1 : Math.round(widthF);
182 int height = heightF == null ? -1 : Math.round(heightF);
183
184 float offsetXF = 0f;
185 float offsetYF = 0f;
186 if (keys[ICON_OFFSET_X_IDX] != null) {
187 offsetXF = c.get(keys[ICON_OFFSET_X_IDX], 0f, Float.class);
188 offsetYF = c.get(keys[ICON_OFFSET_Y_IDX], 0f, Float.class);
189 }
190
191 final MapImage mapImage = new MapImage(iconRef.iconName, iconRef.source);
192
193 mapImage.width = width;
194 mapImage.height = height;
195 mapImage.offsetX = Math.round(offsetXF);
196 mapImage.offsetY = Math.round(offsetYF);
197
198 mapImage.alpha = Utils.clamp(Main.pref.getInteger("mappaint.icon-image-alpha", 255), 0, 255);
199 Integer pAlpha = Utils.colorFloat2int(c.get(keys[ICON_OPACITY_IDX], null, float.class));
200 if (pAlpha != null) {
201 mapImage.alpha = pAlpha;
202 }
203 return mapImage;
204 }
205
206 /**
207 * Create a symbol for the environment
208 * @param env The environment to read the icon form
209 * @return The symbol.
210 */
211 private static Symbol createSymbol(Environment env) {
212 Cascade c = env.mc.getCascade(env.layer);
213
214 Keyword shapeKW = c.get("symbol-shape", null, Keyword.class);
215 if (shapeKW == null)
216 return null;
217 Optional<SymbolShape> shape = SymbolShape.forName(shapeKW.val);
218 if (!shape.isPresent()) {
219 return null;
220 }
221
222 Cascade cDef = env.mc.getCascade("default");
223 Float sizeOnDefault = cDef.get("symbol-size", null, Float.class);
224 if (sizeOnDefault != null && sizeOnDefault <= 0) {
225 sizeOnDefault = null;
226 }
227 Float size = Optional.ofNullable(getWidth(c, "symbol-size", sizeOnDefault)).orElse(10f);
228 if (size <= 0)
229 return null;
230
231 Float strokeWidthOnDefault = getWidth(cDef, "symbol-stroke-width", null);
232 Float strokeWidth = getWidth(c, "symbol-stroke-width", strokeWidthOnDefault);
233
234 Color strokeColor = c.get("symbol-stroke-color", null, Color.class);
235
236 if (strokeWidth == null && strokeColor != null) {
237 strokeWidth = 1f;
238 } else if (strokeWidth != null && strokeColor == null) {
239 strokeColor = Color.ORANGE;
240 }
241
242 Stroke stroke = null;
243 if (strokeColor != null && strokeWidth != null) {
244 Integer strokeAlpha = Utils.colorFloat2int(c.get("symbol-stroke-opacity", null, Float.class));
245 if (strokeAlpha != null) {
246 strokeColor = new Color(strokeColor.getRed(), strokeColor.getGreen(),
247 strokeColor.getBlue(), strokeAlpha);
248 }
249 stroke = new BasicStroke(strokeWidth);
250 }
251
252 Color fillColor = c.get("symbol-fill-color", null, Color.class);
253 if (stroke == null && fillColor == null) {
254 fillColor = Color.BLUE;
255 }
256
257 if (fillColor != null) {
258 Integer fillAlpha = Utils.colorFloat2int(c.get("symbol-fill-opacity", null, Float.class));
259 if (fillAlpha != null) {
260 fillColor = new Color(fillColor.getRed(), fillColor.getGreen(),
261 fillColor.getBlue(), fillAlpha);
262 }
263 }
264
265 return new Symbol(shape.get(), Math.round(size), stroke, strokeColor, fillColor);
266 }
267
268 @Override
269 public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings settings, StyledMapRenderer painter,
270 boolean selected, boolean outermember, boolean member) {
271 if (primitive instanceof Node) {
272 Node n = (Node) primitive;
273 if (mapImage != null && painter.isShowIcons()) {
274 painter.drawNodeIcon(n, mapImage, painter.isInactiveMode() || n.isDisabled(), selected, member,
275 mapImageAngle == null ? 0.0 : mapImageAngle.getRotationAngle(primitive));
276 } else if (symbol != null) {
277 paintWithSymbol(settings, painter, selected, member, n);
278 } else {
279 Color color;
280 boolean isConnection = n.isConnectionNode();
281
282 if (painter.isInactiveMode() || n.isDisabled()) {
283 color = settings.getInactiveColor();
284 } else if (selected) {
285 color = settings.getSelectedColor();
286 } else if (member) {
287 color = settings.getRelationSelectedColor();
288 } else if (isConnection) {
289 if (n.isTagged()) {
290 color = settings.getTaggedConnectionColor();
291 } else {
292 color = settings.getConnectionColor();
293 }
294 } else {
295 if (n.isTagged()) {
296 color = settings.getTaggedColor();
297 } else {
298 color = settings.getNodeColor();
299 }
300 }
301
302 final int size = max(
303 selected ? settings.getSelectedNodeSize() : 0,
304 n.isTagged() ? settings.getTaggedNodeSize() : 0,
305 isConnection ? settings.getConnectionNodeSize() : 0,
306 settings.getUnselectedNodeSize());
307
308 final boolean fill = (selected && settings.isFillSelectedNode()) ||
309 (n.isTagged() && settings.isFillTaggedNode()) ||
310 (isConnection && settings.isFillConnectionNode()) ||
311 settings.isFillUnselectedNode();
312
313 painter.drawNode(n, color, size, fill);
314
315 }
316 } else if (primitive instanceof Relation && mapImage != null) {
317 painter.drawRestriction((Relation) primitive, mapImage, painter.isInactiveMode() || primitive.isDisabled());
318 }
319 }
320
321 private void paintWithSymbol(MapPaintSettings settings, StyledMapRenderer painter, boolean selected, boolean member,
322 Node n) {
323 Color fillColor = symbol.fillColor;
324 if (fillColor != null) {
325 if (painter.isInactiveMode() || n.isDisabled()) {
326 fillColor = settings.getInactiveColor();
327 } else if (defaultSelectedHandling && selected) {
328 fillColor = settings.getSelectedColor(fillColor.getAlpha());
329 } else if (member) {
330 fillColor = settings.getRelationSelectedColor(fillColor.getAlpha());
331 }
332 }
333 Color strokeColor = symbol.strokeColor;
334 if (strokeColor != null) {
335 if (painter.isInactiveMode() || n.isDisabled()) {
336 strokeColor = settings.getInactiveColor();
337 } else if (defaultSelectedHandling && selected) {
338 strokeColor = settings.getSelectedColor(strokeColor.getAlpha());
339 } else if (member) {
340 strokeColor = settings.getRelationSelectedColor(strokeColor.getAlpha());
341 }
342 }
343 painter.drawNodeSymbol(n, symbol, fillColor, strokeColor);
344 }
345
346 /**
347 * Gets the selection box for this element.
348 * @return The selection box as {@link BoxProvider} object.
349 */
350 public BoxProvider getBoxProvider() {
351 if (mapImage != null)
352 return mapImage.getBoxProvider();
353 else if (symbol != null)
354 return new SimpleBoxProvider(new Rectangle(-symbol.size/2, -symbol.size/2, symbol.size, symbol.size));
355 else {
356 // This is only executed once, so no performance concerns.
357 // However, it would be better, if the settings could be changed at runtime.
358 int size = max(
359 Main.pref.getInteger("mappaint.node.selected-size", 5),
360 Main.pref.getInteger("mappaint.node.unselected-size", 3),
361 Main.pref.getInteger("mappaint.node.connection-size", 5),
362 Main.pref.getInteger("mappaint.node.tagged-size", 3)
363 );
364 return new SimpleBoxProvider(new Rectangle(-size/2, -size/2, size, size));
365 }
366 }
367
368 private static int max(int... elements) {
369 return IntStream.of(elements).max().orElseThrow(IllegalStateException::new);
370 }
371
372 @Override
373 public int hashCode() {
374 return Objects.hash(super.hashCode(), mapImage, mapImageAngle, symbol);
375 }
376
377 @Override
378 public boolean equals(Object obj) {
379 if (this == obj) return true;
380 if (obj == null || getClass() != obj.getClass()) return false;
381 if (!super.equals(obj)) return false;
382 NodeElement that = (NodeElement) obj;
383 return Objects.equals(mapImage, that.mapImage) &&
384 Objects.equals(mapImageAngle, that.mapImageAngle) &&
385 Objects.equals(symbol, that.symbol);
386 }
387
388 @Override
389 public String toString() {
390 StringBuilder s = new StringBuilder(64).append("NodeElement{").append(super.toString());
391 if (mapImage != null) {
392 s.append(" icon=[" + mapImage + ']');
393 }
394 if (mapImage != null && mapImageAngle != null) {
395 s.append(" mapImageAngle=[" + mapImageAngle + ']');
396 }
397 if (symbol != null) {
398 s.append(" symbol=[" + symbol + ']');
399 }
400 s.append('}');
401 return s.toString();
402 }
403}
Note: See TracBrowser for help on using the repository browser.