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

Last change on this file since 8554 was 8554, checked in by bastiK, 9 years ago

applied #11634 - Moved global SOM management out of NavigationComponent to SystemOfMeasurement class (patch by michael2402)

  • 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.NavigatableComponent;
37import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
38import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
39import org.openstreetmap.josm.gui.layer.gpx.ChooseTrackVisibilityAction;
40import org.openstreetmap.josm.gui.layer.gpx.ConvertToDataLayerAction;
41import org.openstreetmap.josm.gui.layer.gpx.CustomizeDrawingAction;
42import org.openstreetmap.josm.gui.layer.gpx.DownloadAlongTrackAction;
43import org.openstreetmap.josm.gui.layer.gpx.DownloadWmsAlongTrackAction;
44import org.openstreetmap.josm.gui.layer.gpx.GpxDrawHelper;
45import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
46import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
47import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
48import org.openstreetmap.josm.gui.widgets.HtmlPanel;
49import org.openstreetmap.josm.io.GpxImporter;
50import org.openstreetmap.josm.tools.ImageProvider;
51import org.openstreetmap.josm.tools.date.DateUtils;
52
53public class GpxLayer extends Layer {
54
55 public GpxData data;
56 private 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 public GpxLayer(GpxData d) {
66 super(d.getString(GpxConstants.META_NAME));
67 data = d;
68 drawHelper = new GpxDrawHelper(data);
69 ensureTrackVisibilityLength();
70 }
71
72 public GpxLayer(GpxData d, String name) {
73 this(d);
74 this.setName(name);
75 }
76
77 public GpxLayer(GpxData d, String name, boolean isLocal) {
78 this(d);
79 this.setName(name);
80 this.isLocalFile = isLocal;
81 }
82
83 @Override
84 public Color getColor(boolean ignoreCustom) {
85 return drawHelper.getColor(getName(), ignoreCustom);
86 }
87
88 /**
89 * Returns a human readable string that shows the timespan of the given track
90 * @param trk The GPX track for which timespan is displayed
91 * @return The timespan as a string
92 */
93 public static String getTimespanForTrack(GpxTrack trk) {
94 Date[] bounds = GpxData.getMinMaxTimeForTrack(trk);
95 String ts = "";
96 if (bounds != null) {
97 DateFormat df = DateUtils.getDateFormat(DateFormat.SHORT);
98 String earliestDate = df.format(bounds[0]);
99 String latestDate = df.format(bounds[1]);
100
101 if (earliestDate.equals(latestDate)) {
102 DateFormat tf = DateUtils.getTimeFormat(DateFormat.SHORT);
103 ts += earliestDate + " ";
104 ts += tf.format(bounds[0]) + " - " + tf.format(bounds[1]);
105 } else {
106 DateFormat dtf = DateUtils.getDateTimeFormat(DateFormat.SHORT, DateFormat.MEDIUM);
107 ts += dtf.format(bounds[0]) + " - " + dtf.format(bounds[1]);
108 }
109
110 int diff = (int) (bounds[1].getTime() - bounds[0].getTime()) / 1000;
111 ts += String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60);
112 }
113 return ts;
114 }
115
116 @Override
117 public Icon getIcon() {
118 return ImageProvider.get("layer", "gpx_small");
119 }
120
121 @Override
122 public Object getInfoComponent() {
123 StringBuilder info = new StringBuilder(48);
124
125 if (data.attr.containsKey("name")) {
126 info.append(tr("Name: {0}", data.get(GpxConstants.META_NAME))).append("<br>");
127 }
128
129 if (data.attr.containsKey("desc")) {
130 info.append(tr("Description: {0}", data.get(GpxConstants.META_DESC))).append("<br>");
131 }
132
133 if (!data.tracks.isEmpty()) {
134 info.append("<table><thead align='center'><tr><td colspan='5'>")
135 .append(trn("{0} track", "{0} tracks", data.tracks.size(), data.tracks.size()))
136 .append("</td></tr><tr align='center'><td>").append(tr("Name")).append("</td><td>")
137 .append(tr("Description")).append("</td><td>").append(tr("Timespan"))
138 .append("</td><td>").append(tr("Length")).append("</td><td>").append(tr("URL"))
139 .append("</td></tr></thead>");
140
141 for (GpxTrack trk : data.tracks) {
142 info.append("<tr><td>");
143 if (trk.getAttributes().containsKey(GpxConstants.GPX_NAME)) {
144 info.append(trk.get(GpxConstants.GPX_NAME));
145 }
146 info.append("</td><td>");
147 if (trk.getAttributes().containsKey(GpxConstants.GPX_DESC)) {
148 info.append(' ').append(trk.get(GpxConstants.GPX_DESC));
149 }
150 info.append("</td><td>");
151 info.append(getTimespanForTrack(trk));
152 info.append("</td><td>");
153 info.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length()));
154 info.append("</td><td>");
155 if (trk.getAttributes().containsKey("url")) {
156 info.append(trk.get("url"));
157 }
158 info.append("</td></tr>");
159 }
160 info.append("</table><br><br>");
161 }
162
163 info.append(tr("Length: {0}", SystemOfMeasurement.getSystemOfMeasurement().getDistText(data.length()))).append("<br>")
164 .append(trn("{0} route, ", "{0} routes, ", data.routes.size(), data.routes.size())).append(
165 trn("{0} waypoint", "{0} waypoints", data.waypoints.size(), data.waypoints.size())).append("<br>");
166
167 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString()));
168 sp.setPreferredSize(new Dimension(sp.getPreferredSize().width+20, 370));
169 SwingUtilities.invokeLater(new Runnable() {
170 @Override
171 public void run() {
172 sp.getVerticalScrollBar().setValue(0);
173 }
174 });
175 return sp;
176 }
177
178 @Override
179 public boolean isInfoResizable() {
180 return true;
181 }
182
183 @Override
184 public Action[] getMenuEntries() {
185 return new Action[] {
186 LayerListDialog.getInstance().createShowHideLayerAction(),
187 LayerListDialog.getInstance().createDeleteLayerAction(),
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(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.