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
RevLine 
[541]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;
[444]11import java.util.Collection;
[3396]12import java.util.HashMap;
[541]13import java.util.LinkedList;
[3396]14import java.util.Map;
[541]15
16import javax.swing.Icon;
17
18import org.openstreetmap.josm.data.coor.EastNorth;
19import org.openstreetmap.josm.data.coor.LatLon;
[1601]20import org.openstreetmap.josm.data.gpx.GpxData;
[444]21import org.openstreetmap.josm.data.gpx.GpxLink;
22import org.openstreetmap.josm.data.gpx.WayPoint;
[3396]23import org.openstreetmap.josm.data.preferences.IntegerProperty;
[4126]24import org.openstreetmap.josm.data.projection.Projections;
[541]25import org.openstreetmap.josm.gui.MapView;
26import org.openstreetmap.josm.tools.ImageProvider;
27
28/**
[1169]29 * Basic marker class. Requires a position, and supports
[541]30 * a custom icon and a name.
[1169]31 *
[541]32 * This class is also used to create appropriate Marker-type objects
33 * when waypoints are imported.
[1169]34 *
[541]35 * It hosts a public list object, named makers, containing implementations of
[1169]36 * the MarkerMaker interface. Whenever a Marker needs to be created, each
[541]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.
[1169]39 *
[541]40 * By default, one the list contains one default "Maker" implementation that
[1169]41 * will create AudioMarkers for .wav files, ImageMarkers for .png/.jpg/.jpeg
[541]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.)
[1169]45 *
[541]46 * The default implementation only looks at the value of the <link> tag inside
47 * the <wpt> tag of the GPX file.
[1169]48 *
[541]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>
[1169]60 *
[541]61 * @author Frederik Ramm <frederik@remote.org>
62 */
63public class Marker implements ActionListener {
[1169]64 public final String text;
[3396]65 public final Map<String,String> textMap = new HashMap<String,String>();
[1169]66 public final Icon symbol;
67 public final MarkerLayer parentLayer;
[1724]68 public double time; /* absolute time of marker since epoch */
[1169]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 */
[541]71
[4126]72 private LatLon coor;
[1724]73
74 public final void setCoor(LatLon coor) {
[4126]75 this.coor = new LatLon(coor);
[1724]76 }
77
78 public final LatLon getCoor() {
79 return coor;
80 }
81
82 public final void setEastNorth(EastNorth eastNorth) {
[4126]83 this.coor = Projections.inverseProject(eastNorth);
[1724]84 }
85
86 public final EastNorth getEastNorth() {
[4126]87 return Projections.project(this.coor);
[1724]88 }
89
[1169]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>();
[541]96
[3396]97 private static final IntegerProperty PROP_LABEL = new IntegerProperty("draw.rawgps.layer.wpt", 0 );
98 private static final String[] labelAttributes = new String[] {"name", "desc"};
[3386]99
[1169]100 // Add one Maker specifying the default behaviour.
101 static {
102 Marker.markerProducers.add(new MarkerProducers() {
[1911]103 @SuppressWarnings("unchecked")
[1169]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 {
[1601]109 for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get(GpxData.META_LINKS)) {
[1169]110 uri = oneLink.uri;
111 break;
112 }
113 } catch (Exception ex) {}
[541]114
[1169]115 // Try a relative file:// url, if the link is not in an URL-compatible form
[1911]116 if (relativePath != null && uri != null && !isWellFormedAddress(uri)) {
[576]117 uri = new File(relativePath.getParentFile(), uri).toURI().toString();
[1911]118 }
[541]119
[3396]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));
[3386]124 }
[541]125 }
[1169]126
[3374]127 if (uri == null) {
128 String symbolName = wpt.getString("symbol");
129 if (symbolName == null) {
130 symbolName = wpt.getString("sym");
131 }
[3396]132 return new Marker(wpt.getCoor(), nameDesc, symbolName, parentLayer, time, offset);
[3374]133 }
[547]134 else if (uri.endsWith(".wav"))
[3396]135 return AudioMarker.create(wpt.getCoor(), getText(nameDesc), uri, parentLayer, time, offset);
[541]136 else if (uri.endsWith(".png") || uri.endsWith(".jpg") || uri.endsWith(".jpeg") || uri.endsWith(".gif"))
[1724]137 return ImageMarker.create(wpt.getCoor(), uri, parentLayer, time, offset);
[1169]138 else
[1724]139 return WebMarker.create(wpt.getCoor(), uri, parentLayer, time, offset);
[1169]140 }
[541]141
[1169]142 private boolean isWellFormedAddress(String link) {
143 try {
144 new URL(link);
145 return true;
146 } catch (MalformedURLException x) {
147 return false;
148 }
[541]149 }
[1169]150 });
151 }
[541]152
[1169]153 public Marker(LatLon ll, String text, String iconName, MarkerLayer parentLayer, double time, double offset) {
[1724]154 setCoor(ll);
[3396]155 if (text == null || text.length() == 0) {
156 this.text = null;
157 }
158 else {
159 this.text = text;
160 }
[1169]161 this.offset = offset;
162 this.time = time;
[2811]163 this.symbol = ImageProvider.getIfAvailable("markers",iconName);
[1169]164 this.parentLayer = parentLayer;
165 }
[541]166
[3396]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 }
[3530]173
[3396]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
[1169]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 }
[541]191
[1169]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 }
[541]200
[1169]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 */
[3237]207 public void paint(Graphics g, MapView mv, boolean mousePressed, boolean showTextOrIcon) {
[1724]208 Point screen = mv.getPoint(getEastNorth());
[3237]209 if (symbol != null && showTextOrIcon) {
[1169]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 }
[541]215
[3396]216 String labelText = getText();
217 if ((labelText != null) && showTextOrIcon) {
218 g.drawString(labelText, screen.x+4, screen.y+2);
[1911]219 }
[1169]220 }
[541]221
[1169]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) {
[3396]253 AudioMarker audioMarker = AudioMarker.create(getCoor(), this.getText(), uri, this.parentLayer, this.time, this.offset);
[1169]254 return audioMarker;
255 }
[3396]256
257 /**
258 * Returns the Text which should be displayed, depending on chosen preference
259 * @return Text
260 */
261 public String getText() {
[4126]262 if (this.text != null )
[3396]263 return this.text;
[4126]264 else
[3396]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 }
[541]331}
Note: See TracBrowser for help on using the repository browser.