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

Last change on this file since 4126 was 4126, checked in by bastiK, 13 years ago

memory optimizations for Node & WayPoint (Patch by Gubaer, modified)

The field 'proj' in CachedLatLon is a waste of memory. For the 2 classes where this has the greatest impact, the cache for the projected coordinates is replaced by 2 simple double fields (east & north). On projection change, they have to be invalidated explicitly. This is handled by the DataSet & the GpxLayer.

  • Property svn:eol-style set to native
File size: 12.1 KB
Line 
1// License: GPL. Copyright 2008 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.layer.markerlayer;
3
4import java.awt.Graphics;
5import java.awt.Point;
6import java.awt.event.ActionEvent;
7import java.awt.event.ActionListener;
8import java.io.File;
9import java.net.MalformedURLException;
10import java.net.URL;
11import java.util.Collection;
12import java.util.HashMap;
13import java.util.LinkedList;
14import java.util.Map;
15
16import javax.swing.Icon;
17
18import org.openstreetmap.josm.data.coor.EastNorth;
19import org.openstreetmap.josm.data.coor.LatLon;
20import org.openstreetmap.josm.data.gpx.GpxData;
21import org.openstreetmap.josm.data.gpx.GpxLink;
22import org.openstreetmap.josm.data.gpx.WayPoint;
23import org.openstreetmap.josm.data.preferences.IntegerProperty;
24import org.openstreetmap.josm.data.projection.Projections;
25import org.openstreetmap.josm.gui.MapView;
26import org.openstreetmap.josm.tools.ImageProvider;
27
28/**
29 * Basic marker class. Requires a position, and supports
30 * a custom icon and a name.
31 *
32 * This class is also used to create appropriate Marker-type objects
33 * when waypoints are imported.
34 *
35 * It hosts a public list object, named makers, containing implementations of
36 * the MarkerMaker interface. Whenever a Marker needs to be created, each
37 * object in makers is called with the waypoint parameters (Lat/Lon and tag
38 * data), and the first one to return a Marker object wins.
39 *
40 * By default, one the list contains one default "Maker" implementation that
41 * will create AudioMarkers for .wav files, ImageMarkers for .png/.jpg/.jpeg
42 * files, and WebMarkers for everything else. (The creation of a WebMarker will
43 * fail if there's no vaild URL in the <link> tag, so it might still make sense
44 * to add Makers for such waypoints at the end of the list.)
45 *
46 * The default implementation only looks at the value of the <link> tag inside
47 * the <wpt> tag of the GPX file.
48 *
49 * <h2>HowTo implement a new Marker</h2>
50 * <ul>
51 * <li> Subclass Marker or ButtonMarker and override <code>containsPoint</code>
52 * if you like to respond to user clicks</li>
53 * <li> Override paint, if you want a custom marker look (not "a label and a symbol")</li>
54 * <li> Implement MarkerCreator to return a new instance of your marker class</li>
55 * <li> In you plugin constructor, add an instance of your MarkerCreator
56 * implementation either on top or bottom of Marker.markerProducers.
57 * Add at top, if your marker should overwrite an current marker or at bottom
58 * if you only add a new marker style.</li>
59 * </ul>
60 *
61 * @author Frederik Ramm <frederik@remote.org>
62 */
63public class Marker implements ActionListener {
64 public final String text;
65 public final Map<String,String> textMap = new HashMap<String,String>();
66 public final Icon symbol;
67 public final MarkerLayer parentLayer;
68 public double time; /* absolute time of marker since epoch */
69 public double offset; /* time offset in seconds from the gpx point from which it was derived,
70 may be adjusted later to sync with other data, so not final */
71
72 private LatLon coor;
73
74 public final void setCoor(LatLon coor) {
75 this.coor = new LatLon(coor);
76 }
77
78 public final LatLon getCoor() {
79 return coor;
80 }
81
82 public final void setEastNorth(EastNorth eastNorth) {
83 this.coor = Projections.inverseProject(eastNorth);
84 }
85
86 public final EastNorth getEastNorth() {
87 return Projections.project(this.coor);
88 }
89
90 /**
91 * Plugins can add their Marker creation stuff at the bottom or top of this list
92 * (depending on whether they want to override default behaviour or just add new
93 * stuff).
94 */
95 public static LinkedList<MarkerProducers> markerProducers = new LinkedList<MarkerProducers>();
96
97 private static final IntegerProperty PROP_LABEL = new IntegerProperty("draw.rawgps.layer.wpt", 0 );
98 private static final String[] labelAttributes = new String[] {"name", "desc"};
99
100 // Add one Maker specifying the default behaviour.
101 static {
102 Marker.markerProducers.add(new MarkerProducers() {
103 @SuppressWarnings("unchecked")
104 public Marker createMarker(WayPoint wpt, File relativePath, MarkerLayer parentLayer, double time, double offset) {
105 String uri = null;
106 // cheapest way to check whether "link" object exists and is a non-empty
107 // collection of GpxLink objects...
108 try {
109 for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get(GpxData.META_LINKS)) {
110 uri = oneLink.uri;
111 break;
112 }
113 } catch (Exception ex) {}
114
115 // Try a relative file:// url, if the link is not in an URL-compatible form
116 if (relativePath != null && uri != null && !isWellFormedAddress(uri)) {
117 uri = new File(relativePath.getParentFile(), uri).toURI().toString();
118 }
119
120 Map<String,String> nameDesc = new HashMap<String,String>();
121 for(String attribute : labelAttributes) {
122 if (wpt.attr.containsKey(attribute)) {
123 nameDesc.put(attribute, wpt.getString(attribute));
124 }
125 }
126
127 if (uri == null) {
128 String symbolName = wpt.getString("symbol");
129 if (symbolName == null) {
130 symbolName = wpt.getString("sym");
131 }
132 return new Marker(wpt.getCoor(), nameDesc, symbolName, parentLayer, time, offset);
133 }
134 else if (uri.endsWith(".wav"))
135 return AudioMarker.create(wpt.getCoor(), getText(nameDesc), uri, parentLayer, time, offset);
136 else if (uri.endsWith(".png") || uri.endsWith(".jpg") || uri.endsWith(".jpeg") || uri.endsWith(".gif"))
137 return ImageMarker.create(wpt.getCoor(), uri, parentLayer, time, offset);
138 else
139 return WebMarker.create(wpt.getCoor(), uri, parentLayer, time, offset);
140 }
141
142 private boolean isWellFormedAddress(String link) {
143 try {
144 new URL(link);
145 return true;
146 } catch (MalformedURLException x) {
147 return false;
148 }
149 }
150 });
151 }
152
153 public Marker(LatLon ll, String text, String iconName, MarkerLayer parentLayer, double time, double offset) {
154 setCoor(ll);
155 if (text == null || text.length() == 0) {
156 this.text = null;
157 }
158 else {
159 this.text = text;
160 }
161 this.offset = offset;
162 this.time = time;
163 this.symbol = ImageProvider.getIfAvailable("markers",iconName);
164 this.parentLayer = parentLayer;
165 }
166
167 public Marker(LatLon ll, Map<String,String> textMap, String iconName, MarkerLayer parentLayer, double time, double offset) {
168 setCoor(ll);
169 if (textMap != null) {
170 this.textMap.clear();
171 this.textMap.putAll(textMap);
172 }
173
174 this.text = null;
175 this.offset = offset;
176 this.time = time;
177 this.symbol = ImageProvider.getIfAvailable("markers",iconName);
178 this.parentLayer = parentLayer;
179 }
180
181 /**
182 * Checks whether the marker display area contains the given point.
183 * Markers not interested in mouse clicks may always return false.
184 *
185 * @param p The point to check
186 * @return <code>true</code> if the marker "hotspot" contains the point.
187 */
188 public boolean containsPoint(Point p) {
189 return false;
190 }
191
192 /**
193 * Called when the mouse is clicked in the marker's hotspot. Never
194 * called for markers which always return false from containsPoint.
195 *
196 * @param ev A dummy ActionEvent
197 */
198 public void actionPerformed(ActionEvent ev) {
199 }
200
201 /**
202 * Paints the marker.
203 * @param g graphics context
204 * @param mv map view
205 * @param mousePressed true if the left mouse button is pressed
206 */
207 public void paint(Graphics g, MapView mv, boolean mousePressed, boolean showTextOrIcon) {
208 Point screen = mv.getPoint(getEastNorth());
209 if (symbol != null && showTextOrIcon) {
210 symbol.paintIcon(mv, g, screen.x-symbol.getIconWidth()/2, screen.y-symbol.getIconHeight()/2);
211 } else {
212 g.drawLine(screen.x-2, screen.y-2, screen.x+2, screen.y+2);
213 g.drawLine(screen.x+2, screen.y-2, screen.x-2, screen.y+2);
214 }
215
216 String labelText = getText();
217 if ((labelText != null) && showTextOrIcon) {
218 g.drawString(labelText, screen.x+4, screen.y+2);
219 }
220 }
221
222 /**
223 * Returns an object of class Marker or one of its subclasses
224 * created from the parameters given.
225 *
226 * @param wpt waypoint data for marker
227 * @param relativePath An path to use for constructing relative URLs or
228 * <code>null</code> for no relative URLs
229 * @param offset double in seconds as the time offset of this marker from
230 * the GPX file from which it was derived (if any).
231 * @return a new Marker object
232 */
233 public static Marker createMarker(WayPoint wpt, File relativePath, MarkerLayer parentLayer, double time, double offset) {
234 for (MarkerProducers maker : Marker.markerProducers) {
235 Marker marker = maker.createMarker(wpt, relativePath, parentLayer, time, offset);
236 if (marker != null)
237 return marker;
238 }
239 return null;
240 }
241
242 /**
243 * Returns an AudioMarker derived from this Marker and the provided uri
244 * Subclasses of specific marker types override this to return null as they can't
245 * be turned into AudioMarkers. This includes AudioMarkers themselves, as they
246 * already have audio.
247 *
248 * @param uri uri of wave file
249 * @return AudioMarker
250 */
251
252 public AudioMarker audioMarkerFromMarker(String uri) {
253 AudioMarker audioMarker = AudioMarker.create(getCoor(), this.getText(), uri, this.parentLayer, this.time, this.offset);
254 return audioMarker;
255 }
256
257 /**
258 * Returns the Text which should be displayed, depending on chosen preference
259 * @return Text
260 */
261 public String getText() {
262 if (this.text != null )
263 return this.text;
264 else
265 return getText(this.textMap);
266 }
267
268 /**
269 * Returns the Text which should be displayed, depending on chosen preference.
270 * The possible attributes are read from textMap.
271 *
272 * @param textMap A map with available texts/attributes
273 * @return Text
274 */
275 private static String getText(Map<String,String> textMap) {
276 String text = "";
277
278 if (textMap != null && !textMap.isEmpty()) {
279 switch(PROP_LABEL.get())
280 {
281 // name
282 case 1:
283 {
284 if (textMap.containsKey("name")) {
285 text = textMap.get("name");
286 }
287 break;
288 }
289
290 // desc
291 case 2:
292 {
293 if (textMap.containsKey("desc")) {
294 text = textMap.get("desc");
295 }
296 break;
297 }
298
299 // auto
300 case 0:
301 // both
302 case 3:
303 {
304 if (textMap.containsKey("name")) {
305 text = textMap.get("name");
306
307 if (textMap.containsKey("desc")) {
308 if (PROP_LABEL.get() != 0 || !text.equals(textMap.get("desc"))) {
309 text += " - " + textMap.get("desc");
310 }
311 }
312 }
313 else if (textMap.containsKey("desc")) {
314 text = textMap.get("desc");
315 }
316 break;
317 }
318
319 // none
320 case 4:
321 default:
322 {
323 text = "";
324 break;
325 }
326 }
327 }
328
329 return text;
330 }
331}
Note: See TracBrowser for help on using the repository browser.