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

Last change on this file since 8818 was 8818, checked in by simon04, 9 years ago

fix #11925, see #11897 - Re-enable merging of gpx, marker, geoimage layers

Regression of r8728.

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