source: josm/trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java@ 15497

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

see #16796 - fix most of checkstyle/pmd/findbugs violations

  • Property svn:eol-style set to native
File size: 15.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.markerlayer;
3
4import java.awt.AlphaComposite;
5import java.awt.Color;
6import java.awt.Graphics;
7import java.awt.Graphics2D;
8import java.awt.Point;
9import java.awt.event.ActionEvent;
10import java.awt.image.BufferedImage;
11import java.io.File;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.HashMap;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Map;
18
19import javax.swing.ImageIcon;
20
21import org.openstreetmap.josm.data.Preferences;
22import org.openstreetmap.josm.data.coor.CachedLatLon;
23import org.openstreetmap.josm.data.coor.EastNorth;
24import org.openstreetmap.josm.data.coor.ILatLon;
25import org.openstreetmap.josm.data.coor.LatLon;
26import org.openstreetmap.josm.data.gpx.GpxConstants;
27import org.openstreetmap.josm.data.gpx.WayPoint;
28import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
29import org.openstreetmap.josm.gui.MapView;
30import org.openstreetmap.josm.gui.layer.GpxLayer;
31import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
32import org.openstreetmap.josm.tools.ImageProvider;
33import org.openstreetmap.josm.tools.Logging;
34import org.openstreetmap.josm.tools.template_engine.ParseError;
35import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
36import org.openstreetmap.josm.tools.template_engine.TemplateEntry;
37import org.openstreetmap.josm.tools.template_engine.TemplateParser;
38
39/**
40 * Basic marker class. Requires a position, and supports
41 * a custom icon and a name.
42 *
43 * This class is also used to create appropriate Marker-type objects
44 * when waypoints are imported.
45 *
46 * It hosts a public list object, named makers, containing implementations of
47 * the MarkerMaker interface. Whenever a Marker needs to be created, each
48 * object in makers is called with the waypoint parameters (Lat/Lon and tag
49 * data), and the first one to return a Marker object wins.
50 *
51 * By default, one the list contains one default "Maker" implementation that
52 * will create AudioMarkers for supported audio files, ImageMarkers for supported image
53 * files, and WebMarkers for everything else. (The creation of a WebMarker will
54 * fail if there's no valid URL in the <link> tag, so it might still make sense
55 * to add Makers for such waypoints at the end of the list.)
56 *
57 * The default implementation only looks at the value of the <link> tag inside
58 * the <wpt> tag of the GPX file.
59 *
60 * <h2>HowTo implement a new Marker</h2>
61 * <ul>
62 * <li> Subclass Marker or ButtonMarker and override <code>containsPoint</code>
63 * if you like to respond to user clicks</li>
64 * <li> Override paint, if you want a custom marker look (not "a label and a symbol")</li>
65 * <li> Implement MarkerCreator to return a new instance of your marker class</li>
66 * <li> In you plugin constructor, add an instance of your MarkerCreator
67 * implementation either on top or bottom of Marker.markerProducers.
68 * Add at top, if your marker should overwrite an current marker or at bottom
69 * if you only add a new marker style.</li>
70 * </ul>
71 *
72 * @author Frederik Ramm
73 */
74public class Marker implements TemplateEngineDataProvider, ILatLon {
75
76 /**
77 * Plugins can add their Marker creation stuff at the bottom or top of this list
78 * (depending on whether they want to override default behaviour or just add new stuff).
79 */
80 private static final List<MarkerProducers> markerProducers = new LinkedList<>();
81
82 // Add one Marker specifying the default behaviour.
83 static {
84 Marker.markerProducers.add(new DefaultMarkerProducers());
85 }
86
87 /**
88 * Add a new marker producers at the end of the JOSM list.
89 * @param mp a new marker producers
90 * @since 11850
91 */
92 public static void appendMarkerProducer(MarkerProducers mp) {
93 markerProducers.add(mp);
94 }
95
96 /**
97 * Add a new marker producers at the beginning of the JOSM list.
98 * @param mp a new marker producers
99 * @since 11850
100 */
101 public static void prependMarkerProducer(MarkerProducers mp) {
102 markerProducers.add(0, mp);
103 }
104
105 /**
106 * Returns an object of class Marker or one of its subclasses
107 * created from the parameters given.
108 *
109 * @param wpt waypoint data for marker
110 * @param relativePath An path to use for constructing relative URLs or
111 * <code>null</code> for no relative URLs
112 * @param parentLayer the <code>MarkerLayer</code> that will contain the created <code>Marker</code>
113 * @param time time of the marker in seconds since epoch
114 * @param offset double in seconds as the time offset of this marker from
115 * the GPX file from which it was derived (if any).
116 * @return a new Marker object
117 */
118 public static Collection<Marker> createMarkers(WayPoint wpt, File relativePath, MarkerLayer parentLayer, double time, double offset) {
119 for (MarkerProducers maker : Marker.markerProducers) {
120 final Collection<Marker> markers = maker.createMarkers(wpt, relativePath, parentLayer, time, offset);
121 if (markers != null)
122 return markers;
123 }
124 return null;
125 }
126
127 public static final String MARKER_OFFSET = "waypointOffset";
128 public static final String MARKER_FORMATTED_OFFSET = "formattedWaypointOffset";
129
130 public static final String LABEL_PATTERN_AUTO = "?{ '{name} ({desc})' | '{name} ({cmt})' | '{name}' | '{desc}' | '{cmt}' }";
131 public static final String LABEL_PATTERN_NAME = "{name}";
132 public static final String LABEL_PATTERN_DESC = "{desc}";
133
134 private final TemplateEngineDataProvider dataProvider;
135 private final String text;
136
137 protected final ImageIcon symbol;
138 private BufferedImage redSymbol;
139 public final MarkerLayer parentLayer;
140 /** Absolute time of marker in seconds since epoch */
141 public double time;
142 /** Time offset in seconds from the gpx point from which it was derived, may be adjusted later to sync with other data, so not final */
143 public double offset;
144
145 private String cachedText;
146 private static Map<GpxLayer, String> cachedTemplates = new HashMap<>();
147 private String cachedDefaultTemplate;
148
149 private CachedLatLon coor;
150
151 private boolean erroneous;
152
153 public Marker(LatLon ll, TemplateEngineDataProvider dataProvider, String iconName, MarkerLayer parentLayer,
154 double time, double offset) {
155 this(ll, dataProvider, null, iconName, parentLayer, time, offset);
156 }
157
158 public Marker(LatLon ll, String text, String iconName, MarkerLayer parentLayer, double time, double offset) {
159 this(ll, null, text, iconName, parentLayer, time, offset);
160 }
161
162 private Marker(LatLon ll, TemplateEngineDataProvider dataProvider, String text, String iconName, MarkerLayer parentLayer,
163 double time, double offset) {
164 setCoor(ll);
165
166 this.offset = offset;
167 this.time = time;
168 /* tell icon checking that we expect these names to exist */
169 // /* ICON(markers/) */"Bridge"
170 // /* ICON(markers/) */"Crossing"
171 this.symbol = iconName != null ? ImageProvider.getIfAvailable("markers", iconName) : null;
172 this.parentLayer = parentLayer;
173
174 this.dataProvider = dataProvider;
175 this.text = text;
176
177 Preferences.main().addKeyPreferenceChangeListener("draw.rawgps." + getTextTemplateKey(), l -> updateText());
178 }
179
180 /**
181 * Convert Marker to WayPoint so it can be exported to a GPX file.
182 *
183 * Override in subclasses to add all necessary attributes.
184 *
185 * @return the corresponding WayPoint with all relevant attributes
186 */
187 public WayPoint convertToWayPoint() {
188 WayPoint wpt = new WayPoint(getCoor());
189 wpt.setTimeInMillis((long) (time * 1000));
190 if (text != null) {
191 wpt.getExtensions().add("josm", "text", text);
192 } else if (dataProvider != null) {
193 for (String key : dataProvider.getTemplateKeys()) {
194 Object value = dataProvider.getTemplateValue(key, false);
195 if (value != null && GpxConstants.WPT_KEYS.contains(key)) {
196 wpt.put(key, value);
197 }
198 }
199 }
200 return wpt;
201 }
202
203 /**
204 * Sets the marker's coordinates.
205 * @param coor The marker's coordinates (lat/lon)
206 */
207 public final void setCoor(LatLon coor) {
208 this.coor = new CachedLatLon(coor);
209 }
210
211 /**
212 * Returns the marker's coordinates.
213 * @return The marker's coordinates (lat/lon)
214 */
215 public final LatLon getCoor() {
216 return coor;
217 }
218
219 /**
220 * Sets the marker's projected coordinates.
221 * @param eastNorth The marker's projected coordinates (easting/northing)
222 */
223 public final void setEastNorth(EastNorth eastNorth) {
224 this.coor = new CachedLatLon(eastNorth);
225 }
226
227 /**
228 * @since 12725
229 */
230 @Override
231 public double lon() {
232 return coor == null ? Double.NaN : coor.lon();
233 }
234
235 /**
236 * @since 12725
237 */
238 @Override
239 public double lat() {
240 return coor == null ? Double.NaN : coor.lat();
241 }
242
243 /**
244 * Checks whether the marker display area contains the given point.
245 * Markers not interested in mouse clicks may always return false.
246 *
247 * @param p The point to check
248 * @return <code>true</code> if the marker "hotspot" contains the point.
249 */
250 public boolean containsPoint(Point p) {
251 return false;
252 }
253
254 /**
255 * Called when the mouse is clicked in the marker's hotspot. Never
256 * called for markers which always return false from containsPoint.
257 *
258 * @param ev A dummy ActionEvent
259 */
260 public void actionPerformed(ActionEvent ev) {
261 // Do nothing
262 }
263
264 /**
265 * Paints the marker.
266 * @param g graphics context
267 * @param mv map view
268 * @param mousePressed true if the left mouse button is pressed
269 * @param showTextOrIcon true if text and icon shall be drawn
270 */
271 public void paint(Graphics g, MapView mv, boolean mousePressed, boolean showTextOrIcon) {
272 Point screen = mv.getPoint(this);
273 if (symbol != null && showTextOrIcon) {
274 paintIcon(mv, g, screen.x-symbol.getIconWidth()/2, screen.y-symbol.getIconHeight()/2);
275 } else {
276 g.drawLine(screen.x-2, screen.y-2, screen.x+2, screen.y+2);
277 g.drawLine(screen.x+2, screen.y-2, screen.x-2, screen.y+2);
278 }
279
280 String labelText = getText();
281 if (!labelText.isEmpty() && showTextOrIcon) {
282 g.drawString(labelText, screen.x+4, screen.y+2);
283 }
284 }
285
286 protected void paintIcon(MapView mv, Graphics g, int x, int y) {
287 if (!erroneous) {
288 symbol.paintIcon(mv, g, x, y);
289 } else {
290 if (redSymbol == null) {
291 int width = symbol.getIconWidth();
292 int height = symbol.getIconHeight();
293
294 redSymbol = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
295 Graphics2D gbi = redSymbol.createGraphics();
296 gbi.drawImage(symbol.getImage(), 0, 0, null);
297 gbi.setColor(Color.RED);
298 gbi.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.666f));
299 gbi.fillRect(0, 0, width, height);
300 gbi.dispose();
301 }
302 g.drawImage(redSymbol, x, y, mv);
303 }
304 }
305
306 protected String getTextTemplateKey() {
307 return "markers.pattern";
308 }
309
310 private String getTextTemplate() {
311 String tmpl;
312 if (cachedTemplates.containsKey(parentLayer.fromLayer)) {
313 tmpl = cachedTemplates.get(parentLayer.fromLayer);
314 } else {
315 tmpl = GPXSettingsPanel.getLayerPref(parentLayer.fromLayer, getTextTemplateKey());
316 cachedTemplates.put(parentLayer.fromLayer, tmpl);
317 }
318 return tmpl;
319 }
320
321 private String getDefaultTextTemplate() {
322 if (cachedDefaultTemplate == null) {
323 cachedDefaultTemplate = GPXSettingsPanel.getLayerPref(null, getTextTemplateKey());
324 }
325 return cachedDefaultTemplate;
326 }
327
328 /**
329 * Returns the Text which should be displayed, depending on chosen preference
330 * @return Text of the label
331 */
332 public String getText() {
333 if (text != null) {
334 return text;
335 } else if (cachedText == null) {
336 TemplateEntry template;
337 String templateString = getTextTemplate();
338 try {
339 template = new TemplateParser(templateString).parse();
340 } catch (ParseError e) {
341 Logging.debug(e);
342 String def = getDefaultTextTemplate();
343 Logging.warn("Unable to parse template engine pattern ''{0}'' for property {1}. Using default (''{2}'') instead",
344 templateString, getTextTemplateKey(), def);
345 try {
346 template = new TemplateParser(def).parse();
347 } catch (ParseError e1) {
348 Logging.error(e1);
349 cachedText = "";
350 return "";
351 }
352 }
353 StringBuilder sb = new StringBuilder();
354 template.appendText(sb, this);
355 cachedText = sb.toString();
356
357 }
358 return cachedText;
359 }
360
361 /**
362 * Called when the template changes
363 */
364 public void updateText() {
365 cachedText = null;
366 cachedDefaultTemplate = null;
367 cachedTemplates = new HashMap<>();
368 }
369
370 @Override
371 public Collection<String> getTemplateKeys() {
372 Collection<String> result;
373 if (dataProvider != null) {
374 result = dataProvider.getTemplateKeys();
375 } else {
376 result = new ArrayList<>();
377 }
378 result.add(MARKER_FORMATTED_OFFSET);
379 result.add(MARKER_OFFSET);
380 return result;
381 }
382
383 private String formatOffset() {
384 int wholeSeconds = (int) (offset + 0.5);
385 if (wholeSeconds < 60)
386 return Integer.toString(wholeSeconds);
387 else if (wholeSeconds < 3600)
388 return String.format("%d:%02d", wholeSeconds / 60, wholeSeconds % 60);
389 else
390 return String.format("%d:%02d:%02d", wholeSeconds / 3600, (wholeSeconds % 3600)/60, wholeSeconds % 60);
391 }
392
393 @Override
394 public Object getTemplateValue(String name, boolean special) {
395 if (MARKER_FORMATTED_OFFSET.equals(name))
396 return formatOffset();
397 else if (MARKER_OFFSET.equals(name))
398 return offset;
399 else if (dataProvider != null)
400 return dataProvider.getTemplateValue(name, special);
401 else
402 return null;
403 }
404
405 @Override
406 public boolean evaluateCondition(Match condition) {
407 throw new UnsupportedOperationException();
408 }
409
410 /**
411 * Determines if this marker is erroneous.
412 * @return {@code true} if this markers has any kind of error, {@code false} otherwise
413 * @since 6299
414 */
415 public final boolean isErroneous() {
416 return erroneous;
417 }
418
419 /**
420 * Sets this marker erroneous or not.
421 * @param erroneous {@code true} if this markers has any kind of error, {@code false} otherwise
422 * @since 6299
423 */
424 public final void setErroneous(boolean erroneous) {
425 this.erroneous = erroneous;
426 if (!erroneous) {
427 redSymbol = null;
428 }
429 }
430}
Note: See TracBrowser for help on using the repository browser.