source: josm/trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java@ 11297

Last change on this file since 11297 was 10959, checked in by Don-vip, 8 years ago

fix #13507 - safer management of listeners for Layer color property

  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Dimension;
8import java.awt.Graphics2D;
9import java.io.File;
10import java.text.DateFormat;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.Date;
15import java.util.LinkedList;
16import java.util.List;
17
18import javax.swing.Action;
19import javax.swing.Icon;
20import javax.swing.JScrollPane;
21import javax.swing.SwingUtilities;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.actions.RenameLayerAction;
25import org.openstreetmap.josm.actions.SaveActionBase;
26import org.openstreetmap.josm.data.Bounds;
27import org.openstreetmap.josm.data.SystemOfMeasurement;
28import org.openstreetmap.josm.data.gpx.GpxConstants;
29import org.openstreetmap.josm.data.gpx.GpxData;
30import org.openstreetmap.josm.data.gpx.GpxTrack;
31import org.openstreetmap.josm.data.gpx.WayPoint;
32import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
33import org.openstreetmap.josm.data.preferences.ColorProperty;
34import org.openstreetmap.josm.data.projection.Projection;
35import org.openstreetmap.josm.gui.MapView;
36import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
37import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
38import org.openstreetmap.josm.gui.layer.gpx.ChooseTrackVisibilityAction;
39import org.openstreetmap.josm.gui.layer.gpx.ConvertToDataLayerAction;
40import org.openstreetmap.josm.gui.layer.gpx.CustomizeDrawingAction;
41import org.openstreetmap.josm.gui.layer.gpx.DownloadAlongTrackAction;
42import org.openstreetmap.josm.gui.layer.gpx.DownloadWmsAlongTrackAction;
43import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
44import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
45import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
46import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
47import org.openstreetmap.josm.gui.widgets.HtmlPanel;
48import org.openstreetmap.josm.io.GpxImporter;
49import org.openstreetmap.josm.tools.ImageProvider;
50import org.openstreetmap.josm.tools.date.DateUtils;
51
52public class GpxLayer extends Layer {
53
54 /** GPX data */
55 public GpxData data;
56 private final boolean isLocalFile;
57 // used by ChooseTrackVisibilityAction to determine which tracks to show/hide
58 public boolean[] trackVisibility = new boolean[0];
59
60 private final List<GpxTrack> lastTracks = new ArrayList<>(); // List of tracks at last paint
61 private int lastUpdateCount;
62
63 private final GpxDrawHelper drawHelper;
64
65 /**
66 * Constructs a new {@code GpxLayer} without name.
67 * @param d GPX data
68 */
69 public GpxLayer(GpxData d) {
70 this(d, null, false);
71 }
72
73 /**
74 * Constructs a new {@code GpxLayer} with a given name.
75 * @param d GPX data
76 * @param name layer name
77 */
78 public GpxLayer(GpxData d, String name) {
79 this(d, name, false);
80 }
81
82 /**
83 * Constructs a new {@code GpxLayer} with a given name, thah can be attached to a local file.
84 * @param d GPX data
85 * @param name layer name
86 * @param isLocal whether data is attached to a local file
87 */
88 public GpxLayer(GpxData d, String name, boolean isLocal) {
89 super(d.getString(GpxConstants.META_NAME));
90 data = d;
91 drawHelper = new GpxDrawHelper(data, getColorProperty());
92 SystemOfMeasurement.addSoMChangeListener(drawHelper);
93 ensureTrackVisibilityLength();
94 setName(name);
95 isLocalFile = isLocal;
96 }
97
98 @Override
99 protected ColorProperty getBaseColorProperty() {
100 return GpxDrawHelper.DEFAULT_COLOR;
101 }
102
103 /**
104 * Returns a human readable string that shows the timespan of the given track
105 * @param trk The GPX track for which timespan is displayed
106 * @return The timespan as a string
107 */
108 public static String getTimespanForTrack(GpxTrack trk) {
109 Date[] bounds = GpxData.getMinMaxTimeForTrack(trk);
110 String ts = "";
111 if (bounds != null) {
112 DateFormat df = DateUtils.getDateFormat(DateFormat.SHORT);
113 String earliestDate = df.format(bounds[0]);
114 String latestDate = df.format(bounds[1]);
115
116 if (earliestDate.equals(latestDate)) {
117 DateFormat tf = DateUtils.getTimeFormat(DateFormat.SHORT);
118 ts += earliestDate + ' ';
119 ts += tf.format(bounds[0]) + " - " + tf.format(bounds[1]);
120 } else {
121 DateFormat dtf = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM);
122 ts += dtf.format(bounds[0]) + " - " + dtf.format(bounds[1]);
123 }
124
125 int diff = (int) (bounds[1].getTime() - bounds[0].getTime()) / 1000;
126 ts += String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60);
127 }
128 return ts;
129 }
130
131 @Override
132 public Icon getIcon() {
133 return ImageProvider.get("layer", "gpx_small");
134 }
135
136 @Override
137 public Object getInfoComponent() {
138 StringBuilder info = new StringBuilder(48).append("<html>");
139
140 if (data.attr.containsKey("name")) {
141 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
142 }
143
144 if (data.attr.containsKey("desc")) {
145 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
146 }
147
148 if (!data.tracks.isEmpty()) {
149 info.append("<table><thead align='center'><tr><td colspan='5'>")
150 .append(trn("{0} track", "{0} tracks", data.tracks.size(), data.tracks.size()))
151 .append("</td></tr><tr align='center'><td>").append(tr("Name")).append("</td><td>")
152 .append(tr("Description")).append("</td><td>").append(tr("Timespan"))
153 .append("</td><td>").append(tr("Length")).append("</td><td>").append(tr("URL"))
154 .append("</td></tr></thead>");
155
156 for (GpxTrack trk : data.tracks) {
157 info.append("<tr><td>");
158 if (trk.getAttributes().containsKey(GpxConstants.GPX_NAME)) {
159 info.append(trk.get(GpxConstants.GPX_NAME));
160 }
161 info.append("</td><td>");
162 if (trk.getAttributes().containsKey(GpxConstants.GPX_DESC)) {
163 info.append(' ').append(trk.get(GpxConstants.GPX_DESC));
164 }
165 info.append("</td><td>");
166 info.append(getTimespanForTrack(trk));
167 info.append("</td><td>");
168 info.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length()));
169 info.append("</td><td>");
170 if (trk.getAttributes().containsKey("url")) {
171 info.append(trk.get("url"));
172 }
173 info.append("</td></tr>");
174 }
175 info.append("</table><br><br>");
176 }
177
178 info.append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))).append("<br>")
179 .append(trn("{0} route, ", "{0} routes, ", data.routes.size(), data.routes.size()))
180 .append(trn("{0} waypoint", "{0} waypoints", data.waypoints.size(), data.waypoints.size())).append("<br></html>");
181
182 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString()));
183 sp.setPreferredSize(new Dimension(sp.getPreferredSize().width+20, 370));
184 SwingUtilities.invokeLater(() -> sp.getVerticalScrollBar().setValue(0));
185 return sp;
186 }
187
188 @Override
189 public boolean isInfoResizable() {
190 return true;
191 }
192
193 @Override
194 public Action[] getMenuEntries() {
195 return new Action[] {
196 LayerListDialog.getInstance().createShowHideLayerAction(),
197 LayerListDialog.getInstance().createDeleteLayerAction(),
198 LayerListDialog.getInstance().createMergeLayerAction(this),
199 SeparatorLayerAction.INSTANCE,
200 new LayerSaveAction(this),
201 new LayerSaveAsAction(this),
202 new CustomizeColor(this),
203 new CustomizeDrawingAction(this),
204 new ImportImagesAction(this),
205 new ImportAudioAction(this),
206 new MarkersFromNamedPointsAction(this),
207 new ConvertToDataLayerAction.FromGpxLayer(this),
208 new DownloadAlongTrackAction(data),
209 new DownloadWmsAlongTrackAction(data),
210 SeparatorLayerAction.INSTANCE,
211 new ChooseTrackVisibilityAction(this),
212 new RenameLayerAction(getAssociatedFile(), this),
213 SeparatorLayerAction.INSTANCE,
214 new LayerListPopup.InfoAction(this) };
215 }
216
217 /**
218 * Determines if data is attached to a local file.
219 * @return {@code true} if data is attached to a local file, {@code false} otherwise
220 */
221 public boolean isLocalFile() {
222 return isLocalFile;
223 }
224
225 @Override
226 public String getToolTipText() {
227 StringBuilder info = new StringBuilder(48).append("<html>");
228
229 if (data.attr.containsKey(GpxConstants.META_NAME)) {
230 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
231 }
232
233 if (data.attr.containsKey(GpxConstants.META_DESC)) {
234 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
235 }
236
237 info.append(trn("{0} track, ", "{0} tracks, ", data.tracks.size(), data.tracks.size()))
238 .append(trn("{0} route, ", "{0} routes, ", data.routes.size(), data.routes.size()))
239 .append(trn("{0} waypoint", "{0} waypoints", data.waypoints.size(), data.waypoints.size())).append("<br>")
240 .append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length())))
241 .append("<br></html>");
242 return info.toString();
243 }
244
245 @Override
246 public boolean isMergable(Layer other) {
247 return other instanceof GpxLayer;
248 }
249
250 private int sumUpdateCount() {
251 int updateCount = 0;
252 for (GpxTrack track: data.tracks) {
253 updateCount += track.getUpdateCount();
254 }
255 return updateCount;
256 }
257
258 @Override
259 public boolean isChanged() {
260 if (data.tracks.equals(lastTracks))
261 return sumUpdateCount() != lastUpdateCount;
262 else
263 return true;
264 }
265
266 public void filterTracksByDate(Date fromDate, Date toDate, boolean showWithoutDate) {
267 int i = 0;
268 long from = fromDate.getTime();
269 long to = toDate.getTime();
270 for (GpxTrack trk : data.tracks) {
271 Date[] t = GpxData.getMinMaxTimeForTrack(trk);
272
273 if (t == null) continue;
274 long tm = t[1].getTime();
275 trackVisibility[i] = (tm == 0 && showWithoutDate) || (from <= tm && tm <= to);
276 i++;
277 }
278 }
279
280 @Override
281 public void mergeFrom(Layer from) {
282 data.mergeFrom(((GpxLayer) from).data);
283 drawHelper.dataChanged();
284 }
285
286 @Override
287 public void paint(Graphics2D g, MapView mv, Bounds box) {
288 lastUpdateCount = sumUpdateCount();
289 lastTracks.clear();
290 lastTracks.addAll(data.tracks);
291
292 List<WayPoint> visibleSegments = listVisibleSegments(box);
293 if (!visibleSegments.isEmpty()) {
294 drawHelper.readPreferences(getName());
295 drawHelper.drawAll(g, mv, visibleSegments);
296 if (Main.getLayerManager().getActiveLayer() == this) {
297 drawHelper.drawColorBar(g, mv);
298 }
299 }
300 }
301
302 private List<WayPoint> listVisibleSegments(Bounds box) {
303 WayPoint last = null;
304 LinkedList<WayPoint> visibleSegments = new LinkedList<>();
305
306 ensureTrackVisibilityLength();
307 for (Collection<WayPoint> segment : data.getLinesIterable(trackVisibility)) {
308
309 for (WayPoint pt : segment) {
310 Bounds b = new Bounds(pt.getCoor());
311 if (pt.drawLine && last != null) {
312 b.extend(last.getCoor());
313 }
314 if (b.intersects(box)) {
315 if (last != null && (visibleSegments.isEmpty()
316 || visibleSegments.getLast() != last)) {
317 if (last.drawLine) {
318 WayPoint l = new WayPoint(last);
319 l.drawLine = false;
320 visibleSegments.add(l);
321 } else {
322 visibleSegments.add(last);
323 }
324 }
325 visibleSegments.add(pt);
326 }
327 last = pt;
328 }
329 }
330 return visibleSegments;
331 }
332
333 @Override
334 public void visitBoundingBox(BoundingXYVisitor v) {
335 v.visit(data.recalculateBounds());
336 }
337
338 @Override
339 public File getAssociatedFile() {
340 return data.storageFile;
341 }
342
343 @Override
344 public void setAssociatedFile(File file) {
345 data.storageFile = file;
346 }
347
348 /** ensures the trackVisibility array has the correct length without losing data.
349 * additional entries are initialized to true;
350 */
351 private void ensureTrackVisibilityLength() {
352 final int l = data.tracks.size();
353 if (l == trackVisibility.length)
354 return;
355 final int m = Math.min(l, trackVisibility.length);
356 trackVisibility = Arrays.copyOf(trackVisibility, l);
357 for (int i = m; i < l; i++) {
358 trackVisibility[i] = true;
359 }
360 }
361
362 @Override
363 public void projectionChanged(Projection oldValue, Projection newValue) {
364 if (newValue == null) return;
365 data.resetEastNorthCache();
366 }
367
368 @Override
369 public boolean isSavable() {
370 return true; // With GpxExporter
371 }
372
373 @Override
374 public boolean checkSaveConditions() {
375 return data != null;
376 }
377
378 @Override
379 public File createAndOpenSaveFileChooser() {
380 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save GPX file"), GpxImporter.getFileFilter());
381 }
382
383 @Override
384 public LayerPositionStrategy getDefaultLayerPosition() {
385 return LayerPositionStrategy.AFTER_LAST_DATA_LAYER;
386 }
387
388 @Override
389 public void destroy() {
390 super.destroy();
391 SystemOfMeasurement.removeSoMChangeListener(drawHelper);
392 }
393}
Note: See TracBrowser for help on using the repository browser.