source: josm/trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java@ 3128

Last change on this file since 3128 was 3118, checked in by jttt, 14 years ago

Handle changes in viewID in MapView instead of OsmDataLayer

  • Property svn:eol-style set to native
File size: 29.1 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui.layer;
4
5import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
6import static org.openstreetmap.josm.tools.I18n.marktr;
7import static org.openstreetmap.josm.tools.I18n.tr;
8import static org.openstreetmap.josm.tools.I18n.trn;
9
10import java.awt.AlphaComposite;
11import java.awt.Color;
12import java.awt.Component;
13import java.awt.Composite;
14import java.awt.Graphics2D;
15import java.awt.GridBagLayout;
16import java.awt.Point;
17import java.awt.Rectangle;
18import java.awt.TexturePaint;
19import java.awt.event.ActionEvent;
20import java.awt.geom.Area;
21import java.awt.image.BufferedImage;
22import java.io.File;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.Iterator;
28import java.util.LinkedList;
29import java.util.Map;
30import java.util.Set;
31
32import javax.swing.AbstractAction;
33import javax.swing.Icon;
34import javax.swing.JLabel;
35import javax.swing.JMenuItem;
36import javax.swing.JOptionPane;
37import javax.swing.JPanel;
38import javax.swing.JScrollPane;
39import javax.swing.JSeparator;
40import javax.swing.JTextArea;
41
42import org.openstreetmap.josm.Main;
43import org.openstreetmap.josm.actions.RenameLayerAction;
44import org.openstreetmap.josm.command.PurgePrimitivesCommand;
45import org.openstreetmap.josm.data.Bounds;
46import org.openstreetmap.josm.data.SelectionChangedListener;
47import org.openstreetmap.josm.data.conflict.Conflict;
48import org.openstreetmap.josm.data.conflict.ConflictCollection;
49import org.openstreetmap.josm.data.coor.EastNorth;
50import org.openstreetmap.josm.data.coor.LatLon;
51import org.openstreetmap.josm.data.gpx.GpxData;
52import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
53import org.openstreetmap.josm.data.gpx.WayPoint;
54import org.openstreetmap.josm.data.osm.DataSet;
55import org.openstreetmap.josm.data.osm.DataSetMerger;
56import org.openstreetmap.josm.data.osm.DataSource;
57import org.openstreetmap.josm.data.osm.DatasetConsistencyTest;
58import org.openstreetmap.josm.data.osm.Node;
59import org.openstreetmap.josm.data.osm.OsmPrimitive;
60import org.openstreetmap.josm.data.osm.Relation;
61import org.openstreetmap.josm.data.osm.Way;
62import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
63import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
64import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter.Listener;
65import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
66import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
67import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintVisitor;
68import org.openstreetmap.josm.data.osm.visitor.paint.PaintVisitor;
69import org.openstreetmap.josm.data.osm.visitor.paint.SimplePaintVisitor;
70import org.openstreetmap.josm.gui.HelpAwareOptionPane;
71import org.openstreetmap.josm.gui.MapView;
72import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
73import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
74import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
75import org.openstreetmap.josm.tools.DateUtils;
76import org.openstreetmap.josm.tools.GBC;
77import org.openstreetmap.josm.tools.ImageProvider;
78
79/**
80 * A layer holding data from a specific dataset.
81 * The data can be fully edited.
82 *
83 * @author imi
84 */
85public class OsmDataLayer extends Layer implements Listener, SelectionChangedListener {
86 static public final String REQUIRES_SAVE_TO_DISK_PROP = OsmDataLayer.class.getName() + ".requiresSaveToDisk";
87 static public final String REQUIRES_UPLOAD_TO_SERVER_PROP = OsmDataLayer.class.getName() + ".requiresUploadToServer";
88
89 private boolean requiresSaveToFile = false;
90 private boolean requiresUploadToServer = false;
91 private boolean isChanged = true;
92 private int highlightUpdateCount;
93
94 protected void setRequiresSaveToFile(boolean newValue) {
95 boolean oldValue = requiresSaveToFile;
96 requiresSaveToFile = newValue;
97 if (oldValue != newValue) {
98 propertyChangeSupport.firePropertyChange(REQUIRES_SAVE_TO_DISK_PROP, oldValue, newValue);
99 }
100 }
101
102 protected void setRequiresUploadToServer(boolean newValue) {
103 boolean oldValue = requiresUploadToServer;
104 requiresUploadToServer = newValue;
105 if (oldValue != newValue) {
106 propertyChangeSupport.firePropertyChange(REQUIRES_UPLOAD_TO_SERVER_PROP, oldValue, newValue);
107 }
108 }
109
110 /** the global counter for created data layers */
111 static private int dataLayerCounter = 0;
112
113 /**
114 * Replies a new unique name for a data layer
115 *
116 * @return a new unique name for a data layer
117 */
118 static public String createNewName() {
119 dataLayerCounter++;
120 return tr("Data Layer {0}", dataLayerCounter);
121 }
122
123 public final static class DataCountVisitor extends AbstractVisitor {
124 public int nodes;
125 public int ways;
126 public int relations;
127 public int deletedNodes;
128 public int deletedWays;
129 public int deletedRelations;
130
131 public void visit(final Node n) {
132 nodes++;
133 if (n.isDeleted()) {
134 deletedNodes++;
135 }
136 }
137
138 public void visit(final Way w) {
139 ways++;
140 if (w.isDeleted()) {
141 deletedWays++;
142 }
143 }
144
145 public void visit(final Relation r) {
146 relations++;
147 if (r.isDeleted()) {
148 deletedRelations++;
149 }
150 }
151 }
152
153 public interface CommandQueueListener {
154 void commandChanged(int queueSize, int redoSize);
155 }
156
157 /**
158 * The data behind this layer.
159 */
160 public final DataSet data;
161
162 /**
163 * the collection of conflicts detected in this layer
164 */
165 private ConflictCollection conflicts;
166
167 /**
168 * @deprecated Use {@link DataSet#addDataSetListener(org.openstreetmap.josm.data.osm.event.DataSetListener)} instead
169 * @see DataSetListener, DatasetEventManager
170 */
171 @Deprecated
172 public final LinkedList<DataChangeListener> listenerDataChanged = new LinkedList<DataChangeListener>();
173
174 /**
175 * a paint texture for non-downloaded area
176 */
177 private static TexturePaint hatched;
178
179 static {
180 createHatchTexture();
181 }
182
183 /**
184 * Initialize the hatch pattern used to paint the non-downloaded area
185 */
186 public static void createHatchTexture() {
187 BufferedImage bi = new BufferedImage(15, 15, BufferedImage.TYPE_INT_ARGB);
188 Graphics2D big = bi.createGraphics();
189 big.setColor(Main.pref.getColor(marktr("background"), Color.BLACK));
190 Composite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
191 big.setComposite(comp);
192 big.fillRect(0,0,15,15);
193 big.setColor(Main.pref.getColor(marktr("outside downloaded area"), Color.YELLOW));
194 big.drawLine(0,15,15,0);
195 Rectangle r = new Rectangle(0, 0, 15,15);
196 hatched = new TexturePaint(bi, r);
197 }
198
199 /**
200 * Construct a OsmDataLayer.
201 */
202 public OsmDataLayer(final DataSet data, final String name, final File associatedFile) {
203 super(name);
204 this.data = data;
205 this.setAssociatedFile(associatedFile);
206 conflicts = new ConflictCollection();
207 data.addDataSetListener(new DataSetListenerAdapter(this));
208 DataSet.selListeners.add(this);
209 }
210
211 /**
212 * TODO: @return Return a dynamic drawn icon of the map data. The icon is
213 * updated by a background thread to not disturb the running programm.
214 */
215 @Override public Icon getIcon() {
216 return ImageProvider.get("layer", "osmdata_small");
217 }
218
219 /**
220 * Draw all primitives in this layer but do not draw modified ones (they
221 * are drawn by the edit layer).
222 * Draw nodes last to overlap the ways they belong to.
223 */
224 @Override public void paint(final Graphics2D g, final MapView mv, Bounds box) {
225 isChanged = false;
226 highlightUpdateCount = data.getHighlightUpdateCount();
227
228 boolean active = mv.getActiveLayer() == this;
229 boolean inactive = !active && Main.pref.getBoolean("draw.data.inactive_color", true);
230 boolean virtual = !inactive && mv.isVirtualNodesEnabled();
231
232 // draw the hatched area for non-downloaded region. only draw if we're the active
233 // and bounds are defined; don't draw for inactive layers or loaded GPX files etc
234 if (active && Main.pref.getBoolean("draw.data.downloaded_area", true) && !data.dataSources.isEmpty()) {
235 // initialize area with current viewport
236 Rectangle b = mv.getBounds();
237 // on some platforms viewport bounds seem to be offset from the left,
238 // over-grow it just to be sure
239 b.grow(100, 100);
240 Area a = new Area(b);
241
242 // now succesively subtract downloaded areas
243 for (DataSource src : data.dataSources) {
244 if (src.bounds != null && !src.bounds.getMin().equals(src.bounds.getMax())) {
245 EastNorth en1 = mv.getProjection().latlon2eastNorth(src.bounds.getMin());
246 EastNorth en2 = mv.getProjection().latlon2eastNorth(src.bounds.getMax());
247 Point p1 = mv.getPoint(en1);
248 Point p2 = mv.getPoint(en2);
249 Rectangle r = new Rectangle(Math.min(p1.x, p2.x),Math.min(p1.y, p2.y),Math.abs(p2.x-p1.x),Math.abs(p2.y-p1.y));
250 a.subtract(new Area(r));
251 }
252 }
253
254 // paint remainder
255 g.setPaint(hatched);
256 g.fill(a);
257 }
258
259 PaintVisitor painter;
260 if (Main.pref.getBoolean("draw.wireframe")) {
261 painter = new SimplePaintVisitor();
262 } else {
263 painter = new MapPaintVisitor();
264 }
265 painter.setGraphics(g);
266 painter.setNavigatableComponent(mv);
267 painter.setInactive(inactive);
268 painter.visitAll(data, virtual, box);
269 Main.map.conflictDialog.paintConflicts(g, mv);
270 }
271
272 @Override public String getToolTipText() {
273 int nodes = undeletedSize(data.getNodes());
274 int ways = undeletedSize(data.getWays());
275
276 String tool = trn("{0} node", "{0} nodes", nodes, nodes)+", ";
277 tool += trn("{0} way", "{0} ways", ways, ways);
278
279 if (data.getVersion() != null) {
280 tool += ", " + tr("version {0}", data.getVersion());
281 }
282 File f = getAssociatedFile();
283 if (f != null) {
284 tool = "<html>"+tool+"<br>"+f.getPath()+"</html>";
285 }
286 return tool;
287 }
288
289 @Override public void mergeFrom(final Layer from) {
290 mergeFrom(((OsmDataLayer)from).data);
291 }
292
293 /**
294 * merges the primitives in dataset <code>from</code> into the dataset of
295 * this layer
296 *
297 * @param from the source data set
298 */
299 public void mergeFrom(final DataSet from) {
300 final DataSetMerger visitor = new DataSetMerger(data,from);
301 visitor.merge();
302
303 Area a = data.getDataSourceArea();
304
305 // copy the merged layer's data source info;
306 // only add source rectangles if they are not contained in the
307 // layer already.
308 for (DataSource src : from.dataSources) {
309 if (a == null || !a.contains(src.bounds.asRect())) {
310 data.dataSources.add(src);
311 }
312 }
313
314 // copy the merged layer's API version, downgrade if required
315 if (data.getVersion() == null) {
316 data.setVersion(from.getVersion());
317 } else if ("0.5".equals(data.getVersion()) ^ "0.5".equals(from.getVersion())) {
318 System.err.println(tr("Warning: mixing 0.6 and 0.5 data results in version 0.5"));
319 data.setVersion("0.5");
320 }
321
322 int numNewConflicts = 0;
323 for (Conflict<?> c : visitor.getConflicts()) {
324 if (!conflicts.hasConflict(c)) {
325 numNewConflicts++;
326 conflicts.add(c);
327 }
328 }
329 PurgePrimitivesCommand cmd = buildPurgeCommand();
330 if (cmd != null) {
331 Main.main.undoRedo.add(cmd);
332 }
333 fireDataChange();
334 // repaint to make sure new data is displayed properly.
335 Main.map.mapView.repaint();
336 warnNumNewConflicts(
337 numNewConflicts,
338 cmd == null ? 0 : cmd.getPurgedPrimitives().size()
339 );
340 }
341
342 /**
343 * Warns the user about the number of detected conflicts
344 *
345 * @param numNewConflicts the number of detected conflicts
346 * @param numPurgedPrimitives the number of automatically purged objects
347 */
348 protected void warnNumNewConflicts(int numNewConflicts, int numPurgedPrimitives) {
349 if (numNewConflicts == 0 && numPurgedPrimitives == 0) return;
350
351 String msg1 = trn(
352 "There was {0} conflict detected.",
353 "There were {0} conflicts detected.",
354 numNewConflicts,
355 numNewConflicts
356 );
357 String msg2 = trn(
358 "{0} conflict has been <strong>resolved automatically</strong> by purging {0} object<br>from the local dataset because it is deleted on the server.",
359 "{0} conflicts have been <strong>resolved automatically</strong> by purging {0} objects<br> from the local dataset because they are deleted on the server.",
360 numPurgedPrimitives,
361 numPurgedPrimitives
362 );
363 int numRemainingConflicts = numNewConflicts - numPurgedPrimitives;
364 String msg3 = "";
365 if (numRemainingConflicts >0) {
366 msg3 = trn(
367 "{0} conflict remains to be resolved.<br><br>Please open the Conflict List Dialog and manually resolve it.",
368 "{0} conflicts remain to be resolved.<br><br>Please open the Conflict List Dialog and manually resolve them.",
369 numRemainingConflicts,
370 numRemainingConflicts
371 );
372 }
373
374 StringBuffer sb = new StringBuffer();
375 sb.append("<html>").append(msg1);
376 if (numPurgedPrimitives > 0) {
377 sb.append("<br>").append(msg2);
378 }
379 if (numRemainingConflicts > 0) {
380 sb.append("<br>").append(msg3);
381 }
382 sb.append("</html>");
383 if (numNewConflicts > 0) {
384 ButtonSpec[] options = new ButtonSpec[] {
385 new ButtonSpec(
386 tr("OK"),
387 ImageProvider.get("ok"),
388 tr("Click to close this dialog and continue editing"),
389 null /* no specific help */
390 )
391 };
392 HelpAwareOptionPane.showOptionDialog(
393 Main.parent,
394 sb.toString(),
395 tr("Conflicts detected"),
396 JOptionPane.WARNING_MESSAGE,
397 null, /* no icon */
398 options,
399 options[0],
400 ht("/Concepts/Conflict#WarningAboutDetectedConflicts")
401 );
402 Main.map.conflictDialog.unfurlDialog();
403 Main.map.repaint();
404 }
405 }
406
407 /**
408 * Builds the purge command for primitives which can be purged automatically
409 * from the local dataset because they've been deleted on the
410 * server.
411 *
412 * @return the purge command. <code>null</code> if no primitives have to
413 * be purged
414 */
415 protected PurgePrimitivesCommand buildPurgeCommand() {
416 ArrayList<OsmPrimitive> toPurge = new ArrayList<OsmPrimitive>();
417 conflictLoop:
418 for (Conflict<?> c: conflicts) {
419 if (c.getMy().isDeleted() && !c.getTheir().isVisible()) {
420 // Local and server version of the primitive are deleted. We
421 // can purge it from the local dataset.
422 //
423 toPurge.add(c.getMy());
424 } else if (!c.getMy().isModified() && ! c.getTheir().isVisible()) {
425 // We purge deleted *ways* and *relations* automatically if they are
426 // deleted on the server and if they aren't modified in the local
427 // dataset.
428 //
429 if (c.getMy() instanceof Way || c.getMy() instanceof Relation) {
430 toPurge.add(c.getMy());
431 continue conflictLoop;
432 }
433 // We only purge nodes if they aren't part of a modified way.
434 // Otherwise the number of nodes of a modified way could drop
435 // below 2 and we would lose the modified data when the way
436 // gets purged.
437 //
438 for (OsmPrimitive parent: c.getMy().getReferrers()) {
439 if (parent.isModified() && parent instanceof Way) {
440 continue conflictLoop;
441 }
442 }
443 toPurge.add(c.getMy());
444 }
445 }
446 if (toPurge.isEmpty()) return null;
447 PurgePrimitivesCommand cmd = new PurgePrimitivesCommand(this, toPurge);
448 return cmd;
449 }
450
451 @Override public boolean isMergable(final Layer other) {
452 return other instanceof OsmDataLayer;
453 }
454
455 @Override public void visitBoundingBox(final BoundingXYVisitor v) {
456 for (final Node n: data.getNodes()) {
457 if (n.isUsable()) {
458 v.visit(n);
459 }
460 }
461 }
462
463 /**
464 * Clean out the data behind the layer. This means clearing the redo/undo lists,
465 * really deleting all deleted objects and reset the modified flags. This should
466 * be done after an upload, even after a partial upload.
467 *
468 * @param processed A list of all objects that were actually uploaded.
469 * May be <code>null</code>, which means nothing has been uploaded
470 */
471 public void cleanupAfterUpload(final Collection<OsmPrimitive> processed) {
472 // return immediately if an upload attempt failed
473 if (processed == null || processed.isEmpty())
474 return;
475
476 Main.main.undoRedo.clean(this);
477
478 // if uploaded, clean the modified flags as well
479 final Set<OsmPrimitive> processedSet = new HashSet<OsmPrimitive>(processed);
480 data.clenupDeletedPrimitives();
481 for (final Iterator<Node> it = data.getNodes().iterator(); it.hasNext();) {
482 cleanIterator(it, processedSet);
483 }
484 for (final Iterator<Way> it = data.getWays().iterator(); it.hasNext();) {
485 cleanIterator(it, processedSet);
486 }
487 for (final Iterator<Relation> it = data.getRelations().iterator(); it.hasNext();) {
488 cleanIterator(it, processedSet);
489 }
490 }
491
492 /**
493 * Clean the modified flag for the given iterator over a collection if it is in the
494 * list of processed entries.
495 *
496 * @param it The iterator to change the modified and remove the items if deleted.
497 * @param processed A list of all objects that have been successfully progressed.
498 * If the object in the iterator is not in the list, nothing will be changed on it.
499 */
500 private void cleanIterator(final Iterator<? extends OsmPrimitive> it, final Collection<OsmPrimitive> processed) {
501 final OsmPrimitive osm = it.next();
502 if (!processed.remove(osm))
503 return;
504 osm.setModified(false);
505 }
506
507 /**
508 * @return The number of not-deleted and visible primitives in the list.
509 */
510 private int undeletedSize(final Collection<? extends OsmPrimitive> list) {
511 int size = 0;
512 for (final OsmPrimitive osm : list)
513 if (!osm.isDeleted() && osm.isVisible()) {
514 size++;
515 }
516 return size;
517 }
518
519 @Override public Object getInfoComponent() {
520 final DataCountVisitor counter = new DataCountVisitor();
521 for (final OsmPrimitive osm : data.allPrimitives()) {
522 osm.visit(counter);
523 }
524 final JPanel p = new JPanel(new GridBagLayout());
525
526 String nodeText = trn("{0} node", "{0} nodes", counter.nodes, counter.nodes);
527 if (counter.deletedNodes > 0) {
528 nodeText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedNodes, counter.deletedNodes)+")";
529 }
530
531 String wayText = trn("{0} way", "{0} ways", counter.ways, counter.ways);
532 if (counter.deletedWays > 0) {
533 wayText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedWays, counter.deletedWays)+")";
534 }
535
536 String relationText = trn("{0} relation", "{0} relations", counter.relations, counter.relations);
537 if (counter.deletedRelations > 0) {
538 relationText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedRelations, counter.deletedRelations)+")";
539 }
540
541 p.add(new JLabel(tr("{0} consists of:", getName())), GBC.eol());
542 p.add(new JLabel(nodeText, ImageProvider.get("data", "node"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
543 p.add(new JLabel(wayText, ImageProvider.get("data", "way"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
544 p.add(new JLabel(relationText, ImageProvider.get("data", "relation"), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
545 p.add(new JLabel(tr("API version: {0}", (data.getVersion() != null) ? data.getVersion() : tr("unset"))));
546
547 return p;
548 }
549
550 @Override public Component[] getMenuEntries() {
551 if (Main.applet)
552 return new Component[]{
553 new JMenuItem(LayerListDialog.getInstance().createActivateLayerAction(this)),
554 new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
555 new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
556 new JSeparator(),
557 new JMenuItem(LayerListDialog.getInstance().createMergeLayerAction(this)),
558 new JSeparator(),
559 new JMenuItem(new RenameLayerAction(getAssociatedFile(), this)),
560 new JMenuItem(new ConsistencyTestAction()),
561 new JSeparator(),
562 new JMenuItem(new LayerListPopup.InfoAction(this))};
563 return new Component[]{
564 new JMenuItem(LayerListDialog.getInstance().createActivateLayerAction(this)),
565 new JMenuItem(LayerListDialog.getInstance().createShowHideLayerAction(this)),
566 new JMenuItem(LayerListDialog.getInstance().createDeleteLayerAction(this)),
567 new JSeparator(),
568 new JMenuItem(LayerListDialog.getInstance().createMergeLayerAction(this)),
569 new JMenuItem(new LayerSaveAction(this)),
570 new JMenuItem(new LayerSaveAsAction(this)),
571 new JMenuItem(new LayerGpxExportAction(this)),
572 new JMenuItem(new ConvertToGpxLayerAction()),
573 new JSeparator(),
574 new JMenuItem(new RenameLayerAction(getAssociatedFile(), this)),
575 new JMenuItem(new ConsistencyTestAction()),
576 new JSeparator(),
577 new JMenuItem(new LayerListPopup.InfoAction(this))};
578 }
579
580 public void fireDataChange() {
581 setRequiresSaveToFile(true);
582 setRequiresUploadToServer(true);
583 for (DataChangeListener dcl : listenerDataChanged) {
584 dcl.dataChanged(this);
585 }
586 }
587
588 public static GpxData toGpxData(DataSet data, File file) {
589 GpxData gpxData = new GpxData();
590 gpxData.storageFile = file;
591 HashSet<Node> doneNodes = new HashSet<Node>();
592 for (Way w : data.getWays()) {
593 if (!w.isUsable()) {
594 continue;
595 }
596 Collection<Collection<WayPoint>> trk = new ArrayList<Collection<WayPoint>>();
597 Map<String, Object> trkAttr = new HashMap<String, Object>();
598
599 if (w.get("name") != null) {
600 trkAttr.put("name", w.get("name"));
601 }
602
603 ArrayList<WayPoint> trkseg = null;
604 for (Node n : w.getNodes()) {
605 if (!n.isUsable()) {
606 trkseg = null;
607 continue;
608 }
609 if (trkseg == null) {
610 trkseg = new ArrayList<WayPoint>();
611 trk.add(trkseg);
612 }
613 if (!n.isTagged()) {
614 doneNodes.add(n);
615 }
616 WayPoint wpt = new WayPoint(n.getCoor());
617 if (!n.isTimestampEmpty()) {
618 wpt.attr.put("time", DateUtils.fromDate(n.getTimestamp()));
619 wpt.setTime();
620 }
621 trkseg.add(wpt);
622 }
623
624 gpxData.tracks.add(new ImmutableGpxTrack(trk, trkAttr));
625 }
626
627 // what is this loop meant to do? it creates waypoints but never
628 // records them?
629 for (Node n : data.getNodes()) {
630 if (n.isIncomplete() || n.isDeleted() || doneNodes.contains(n)) {
631 continue;
632 }
633 WayPoint wpt = new WayPoint(n.getCoor());
634 if (!n.isTimestampEmpty()) {
635 wpt.attr.put("time", DateUtils.fromDate(n.getTimestamp()));
636 wpt.setTime();
637 }
638 String name = n.get("name");
639 if (name != null) {
640 wpt.attr.put("name", name);
641 }
642 }
643 return gpxData;
644 }
645
646 public GpxData toGpxData() {
647 return toGpxData(data, getAssociatedFile());
648 }
649
650 public class ConvertToGpxLayerAction extends AbstractAction {
651 public ConvertToGpxLayerAction() {
652 super(tr("Convert to GPX layer"), ImageProvider.get("converttogpx"));
653 }
654 public void actionPerformed(ActionEvent e) {
655 Main.main.addLayer(new GpxLayer(toGpxData(), tr("Converted from: {0}", getName())));
656 Main.main.removeLayer(OsmDataLayer.this);
657 }
658 }
659
660 public boolean containsPoint(LatLon coor) {
661 // we'll assume that if this has no data sources
662 // that it also has no borders
663 if (this.data.dataSources.isEmpty())
664 return true;
665
666 boolean layer_bounds_point = false;
667 for (DataSource src : this.data.dataSources) {
668 if (src.bounds.contains(coor)) {
669 layer_bounds_point = true;
670 break;
671 }
672 }
673 return layer_bounds_point;
674 }
675
676 /**
677 * replies the set of conflicts currently managed in this layer
678 *
679 * @return the set of conflicts currently managed in this layer
680 */
681 public ConflictCollection getConflicts() {
682 return conflicts;
683 }
684
685 /**
686 * Replies true if the data managed by this layer needs to be uploaded to
687 * the server because it contains at least one modified primitive.
688 *
689 * @return true if the data managed by this layer needs to be uploaded to
690 * the server because it contains at least one modified primitive; false,
691 * otherwise
692 */
693 public boolean requiresUploadToServer() {
694 return requiresUploadToServer;
695 }
696
697 /**
698 * Replies true if the data managed by this layer needs to be saved to
699 * a file. Only replies true if a file is assigned to this layer and
700 * if the data managed by this layer has been modified since the last
701 * save operation to the file.
702 *
703 * @return true if the data managed by this layer needs to be saved to
704 * a file
705 */
706 public boolean requiresSaveToFile() {
707 return getAssociatedFile() != null && requiresSaveToFile;
708 }
709
710 /**
711 * Initializes the layer after a successful load of OSM data from a file
712 *
713 */
714 public void onPostLoadFromFile() {
715 setRequiresSaveToFile(false);
716 setRequiresUploadToServer(data.isModified());
717 }
718
719 public void onPostDownloadFromServer() {
720 setRequiresSaveToFile(true);
721 setRequiresUploadToServer(data.isModified());
722 }
723
724 @Override
725 public boolean isChanged() {
726 return isChanged || highlightUpdateCount != data.getHighlightUpdateCount();
727 }
728
729 /**
730 * Initializes the layer after a successful save of OSM data to a file
731 *
732 */
733 public void onPostSaveToFile() {
734 setRequiresSaveToFile(false);
735 setRequiresUploadToServer(data.isModified());
736 }
737
738 /**
739 * Initializes the layer after a successful upload to the server
740 *
741 */
742 public void onPostUploadToServer() {
743 setRequiresUploadToServer(data.isModified());
744 // keep requiresSaveToDisk unchanged
745 }
746
747 private class ConsistencyTestAction extends AbstractAction {
748
749 public ConsistencyTestAction() {
750 super(tr("Dataset consistency test"));
751 }
752
753 public void actionPerformed(ActionEvent e) {
754 String result = DatasetConsistencyTest.runTests(data);
755 if (result.length() == 0) {
756 JOptionPane.showMessageDialog(Main.parent, tr("No problems found"));
757 } else {
758 JPanel p = new JPanel(new GridBagLayout());
759 p.add(new JLabel(tr("Following problems found:")), GBC.eol());
760 JTextArea info = new JTextArea(result, 20, 60);
761 info.setCaretPosition(0);
762 info.setEditable(false);
763 p.add(new JScrollPane(info), GBC.eop());
764
765 JOptionPane.showMessageDialog(Main.parent, p, tr("Warning"), JOptionPane.WARNING_MESSAGE);
766 }
767 }
768
769 }
770
771 public void processDatasetEvent(AbstractDatasetChangedEvent event) {
772 isChanged = true;
773 }
774
775 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
776 isChanged = true;
777 }
778}
Note: See TracBrowser for help on using the repository browser.