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

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

fix #12633 - Make layer order more flexible by letting each layer decide where it wants to be placed (patch by michael2402)

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