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

Last change on this file since 941 was 941, checked in by framm, 16 years ago

+ fixed Osm->GPX conversion to retain timestamps.

  • Property svn:eol-style set to native
File size: 14.0 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui.layer;
4
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.Color;
10import java.awt.Component;
11import java.awt.Graphics;
12import java.awt.GridBagLayout;
13import java.awt.Point;
14import java.awt.event.ActionEvent;
15import java.io.File;
16import java.util.Collection;
17import java.util.HashSet;
18import java.util.Iterator;
19import java.util.LinkedList;
20import java.util.Set;
21import java.util.ArrayList;
22
23import javax.swing.AbstractAction;
24import javax.swing.Icon;
25import javax.swing.JLabel;
26import javax.swing.JMenuItem;
27import javax.swing.JOptionPane;
28import javax.swing.JPanel;
29import javax.swing.JSeparator;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.actions.GpxExportAction;
33import org.openstreetmap.josm.actions.RenameLayerAction;
34import org.openstreetmap.josm.actions.SaveAction;
35import org.openstreetmap.josm.actions.SaveAsAction;
36import org.openstreetmap.josm.command.Command;
37import org.openstreetmap.josm.data.Preferences;
38import org.openstreetmap.josm.data.coor.EastNorth;
39import org.openstreetmap.josm.data.coor.LatLon;
40import org.openstreetmap.josm.data.osm.DataSet;
41import org.openstreetmap.josm.data.osm.DataSource;
42import org.openstreetmap.josm.data.osm.Relation;
43import org.openstreetmap.josm.data.osm.Node;
44import org.openstreetmap.josm.data.osm.OsmPrimitive;
45import org.openstreetmap.josm.data.osm.Way;
46import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
47import org.openstreetmap.josm.data.osm.visitor.MapPaintVisitor;
48import org.openstreetmap.josm.data.osm.visitor.MergeVisitor;
49import org.openstreetmap.josm.data.osm.visitor.SimplePaintVisitor;
50import org.openstreetmap.josm.data.osm.visitor.Visitor;
51import org.openstreetmap.josm.data.gpx.GpxData;
52import org.openstreetmap.josm.data.gpx.GpxTrack;
53import org.openstreetmap.josm.data.gpx.WayPoint;
54import org.openstreetmap.josm.gui.MapView;
55import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
56import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
57import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
58import org.openstreetmap.josm.tools.GBC;
59import org.openstreetmap.josm.tools.ImageProvider;
60
61/**
62 * A layer holding data from a specific dataset.
63 * The data can be fully edited.
64 *
65 * @author imi
66 */
67public class OsmDataLayer extends Layer {
68
69 public final static class DataCountVisitor implements Visitor {
70 public final int[] normal = new int[3];
71 public final int[] deleted = new int[3];
72 public final String[] names = {"node", "way", "relation"};
73
74 private void inc(final OsmPrimitive osm, final int i) {
75 normal[i]++;
76 if (osm.deleted)
77 deleted[i]++;
78 }
79
80 public void visit(final Node n) {
81 inc(n, 0);
82 }
83
84 public void visit(final Way w) {
85 inc(w, 1);
86 }
87 public void visit(final Relation w) {
88 inc(w, 2);
89 }
90 }
91
92 public interface ModifiedChangedListener {
93 void modifiedChanged(boolean value, OsmDataLayer source);
94 }
95 public interface CommandQueueListener {
96 void commandChanged(int queueSize, int redoSize);
97 }
98
99 /**
100 * @deprecated Use Main.main.undoRedo.add(...) instead.
101 */
102 @Deprecated public void add(final Command c) {
103 Main.main.undoRedo.add(c);
104 }
105
106 /**
107 * The data behind this layer.
108 */
109 public final DataSet data;
110
111 /**
112 * Whether the data of this layer was modified during the session.
113 */
114 private boolean modified = false;
115 /**
116 * Whether the data was modified due an upload of the data to the server.
117 */
118 public boolean uploadedModified = false;
119
120 public final LinkedList<ModifiedChangedListener> listenerModified = new LinkedList<ModifiedChangedListener>();
121 public final LinkedList<DataChangeListener> listenerDataChanged = new LinkedList<DataChangeListener>();
122
123 private SimplePaintVisitor wireframeMapPainter = new SimplePaintVisitor();
124 private MapPaintVisitor standardMapPainter = new MapPaintVisitor();
125
126 /**
127 * Construct a OsmDataLayer.
128 */
129 public OsmDataLayer(final DataSet data, final String name, final File associatedFile) {
130 super(name);
131 this.data = data;
132 this.associatedFile = associatedFile;
133 }
134
135 /**
136 * TODO: @return Return a dynamic drawn icon of the map data. The icon is
137 * updated by a background thread to not disturb the running programm.
138 */
139 @Override public Icon getIcon() {
140 return ImageProvider.get("layer", "osmdata_small");
141 }
142
143 /**
144 * Draw all primitives in this layer but do not draw modified ones (they
145 * are drawn by the edit layer).
146 * Draw nodes last to overlap the ways they belong to.
147 */
148 @Override public void paint(final Graphics g, final MapView mv) {
149 boolean inactive = Main.map.mapView.getActiveLayer() != this && Main.pref.getBoolean("draw.data.inactive_color", true);
150 boolean virtual = !inactive && Main.map.mapView.useVirtualNodes();
151 if (Main.pref.getBoolean("draw.data.downloaded_area", true)) {
152 // FIXME this is inefficient; instead a proper polygon has to be built, and instead
153 // of drawing the outline, the outlying areas should perhaps be shaded.
154 for (DataSource src : data.dataSources) {
155 if (src.bounds != null && !src.bounds.min.equals(src.bounds.max)) {
156 EastNorth en1 = Main.proj.latlon2eastNorth(src.bounds.min);
157 EastNorth en2 = Main.proj.latlon2eastNorth(src.bounds.max);
158 Point p1 = mv.getPoint(en1);
159 Point p2 = mv.getPoint(en2);
160 Color color = inactive ? Main.pref.getColor(marktr("inactive"), Color.DARK_GRAY) :
161 Main.pref.getColor(marktr("downloaded Area"), Color.YELLOW);
162 g.setColor(color);
163 g.drawRect(Math.min(p1.x,p2.x), Math.min(p1.y, p2.y), Math.abs(p2.x-p1.x), Math.abs(p2.y-p1.y));
164 }
165 }
166 }
167
168 if (Main.pref.getBoolean("draw.wireframe")) {
169 wireframeMapPainter.setGraphics(g);
170 wireframeMapPainter.setNavigatableComponent(mv);
171 wireframeMapPainter.inactive = inactive;
172 wireframeMapPainter.visitAll(data, virtual);
173 }
174 else
175 {
176 standardMapPainter.setGraphics(g);
177 standardMapPainter.setNavigatableComponent(mv);
178 standardMapPainter.inactive = inactive;
179 standardMapPainter.visitAll(data, virtual);
180 }
181 Main.map.conflictDialog.paintConflicts(g, mv);
182 }
183
184 @Override public String getToolTipText() {
185 String tool = "";
186 tool += undeletedSize(data.nodes)+" "+trn("node", "nodes", undeletedSize(data.nodes))+", ";
187 tool += undeletedSize(data.ways)+" "+trn("way", "ways", undeletedSize(data.ways));
188 if (associatedFile != null)
189 tool = "<html>"+tool+"<br>"+associatedFile.getPath()+"</html>";
190 return tool;
191 }
192
193 @Override public void mergeFrom(final Layer from) {
194 final MergeVisitor visitor = new MergeVisitor(data,((OsmDataLayer)from).data);
195 for (final OsmPrimitive osm : ((OsmDataLayer)from).data.allPrimitives())
196 osm.visit(visitor);
197 visitor.fixReferences();
198
199 // copy the merged layer's data source info
200 for (DataSource src : ((OsmDataLayer)from).data.dataSources)
201 data.dataSources.add(src);
202 fireDataChange();
203 // repaint to make sure new data is displayed properly.
204 Main.map.mapView.repaint();
205
206 if (visitor.conflicts.isEmpty())
207 return;
208 final ConflictDialog dlg = Main.map.conflictDialog;
209 dlg.add(visitor.conflicts);
210 JOptionPane.showMessageDialog(Main.parent,tr("There were conflicts during import."));
211 if (!dlg.isVisible())
212 dlg.action.actionPerformed(new ActionEvent(this, 0, ""));
213 }
214
215 @Override public boolean isMergable(final Layer other) {
216 return other instanceof OsmDataLayer;
217 }
218
219 @Override public void visitBoundingBox(final BoundingXYVisitor v) {
220 for (final Node n : data.nodes)
221 if (!n.deleted && !n.incomplete)
222 v.visit(n);
223 }
224
225 /**
226 * Clean out the data behind the layer. This means clearing the redo/undo lists,
227 * really deleting all deleted objects and reset the modified flags. This is done
228 * after a successfull upload.
229 *
230 * @param processed A list of all objects that were actually uploaded.
231 * May be <code>null</code>, which means nothing has been uploaded but
232 * saved to disk instead. Note that an empty collection for "processed"
233 * means that an upload has been attempted but failed.
234 */
235 public void cleanData(final Collection<OsmPrimitive> processed, boolean dataAdded) {
236
237 // return immediately if an upload attempt failed
238 if (processed != null && processed.isEmpty() && !dataAdded)
239 return;
240
241 Main.main.undoRedo.clean();
242
243 // if uploaded, clean the modified flags as well
244 if (processed != null) {
245 final Set<OsmPrimitive> processedSet = new HashSet<OsmPrimitive>(processed);
246 for (final Iterator<Node> it = data.nodes.iterator(); it.hasNext();)
247 cleanIterator(it, processedSet);
248 for (final Iterator<Way> it = data.ways.iterator(); it.hasNext();)
249 cleanIterator(it, processedSet);
250 for (final Iterator<Relation> it = data.relations.iterator(); it.hasNext();)
251 cleanIterator(it, processedSet);
252 }
253
254 // update the modified flag
255 if (associatedFile != null && processed != null && !dataAdded)
256 return; // do nothing when uploading non-harmful changes.
257
258 // modified if server changed the data (esp. the id).
259 uploadedModified = associatedFile != null && processed != null && dataAdded;
260 setModified(uploadedModified);
261 }
262
263 /**
264 * Clean the modified flag for the given iterator over a collection if it is in the
265 * list of processed entries.
266 *
267 * @param it The iterator to change the modified and remove the items if deleted.
268 * @param processed A list of all objects that have been successfully progressed.
269 * If the object in the iterator is not in the list, nothing will be changed on it.
270 */
271 private void cleanIterator(final Iterator<? extends OsmPrimitive> it, final Collection<OsmPrimitive> processed) {
272 final OsmPrimitive osm = it.next();
273 if (!processed.remove(osm))
274 return;
275 osm.modified = false;
276 if (osm.deleted)
277 it.remove();
278 }
279
280 public boolean isModified() {
281 return modified;
282 }
283
284 public void setModified(final boolean modified) {
285 if (modified == this.modified)
286 return;
287 this.modified = modified;
288 for (final ModifiedChangedListener l : listenerModified)
289 l.modifiedChanged(modified, this);
290 }
291
292 /**
293 * @return The number of not-deleted primitives in the list.
294 */
295 private int undeletedSize(final Collection<? extends OsmPrimitive> list) {
296 int size = 0;
297 for (final OsmPrimitive osm : list)
298 if (!osm.deleted)
299 size++;
300 return size;
301 }
302
303 @Override public Object getInfoComponent() {
304 final DataCountVisitor counter = new DataCountVisitor();
305 for (final OsmPrimitive osm : data.allPrimitives())
306 osm.visit(counter);
307 final JPanel p = new JPanel(new GridBagLayout());
308 p.add(new JLabel(tr("{0} consists of:", name)), GBC.eol());
309 for (int i = 0; i < counter.normal.length; ++i) {
310 String s = counter.normal[i]+" "+trn(counter.names[i],counter.names[i]+"s",counter.normal[i]);
311 if (counter.deleted[i] > 0)
312 s += tr(" ({0} deleted.)",counter.deleted[i]);
313 p.add(new JLabel(s, ImageProvider.get("data", counter.names[i]), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
314 }
315 return p;
316 }
317
318 @Override public Component[] getMenuEntries() {
319 if (Main.applet) {
320 return new Component[]{
321 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
322 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
323 new JSeparator(),
324 new JMenuItem(new RenameLayerAction(associatedFile, this)),
325 new JSeparator(),
326 new JMenuItem(new LayerListPopup.InfoAction(this))};
327 }
328 return new Component[]{
329 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
330 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
331 new JSeparator(),
332 new JMenuItem(new SaveAction(this)),
333 new JMenuItem(new SaveAsAction(this)),
334 new JMenuItem(new GpxExportAction(this)),
335 new JMenuItem(new ConvertToGpxLayerAction()),
336 new JSeparator(),
337 new JMenuItem(new RenameLayerAction(associatedFile, this)),
338 new JSeparator(),
339 new JMenuItem(new LayerListPopup.InfoAction(this))};
340 }
341
342 public void fireDataChange() {
343 for (DataChangeListener dcl : listenerDataChanged) {
344 dcl.dataChanged(this);
345 }
346 }
347
348 public static GpxData toGpxData(DataSet data) {
349 GpxData gpxData = new GpxData();
350 HashSet<Node> doneNodes = new HashSet<Node>();
351 for (Way w : data.ways) {
352 if (w.incomplete || w.deleted) continue;
353 GpxTrack trk = new GpxTrack();
354 gpxData.tracks.add(trk);
355
356 if (w.get("name") != null)
357 trk.attr.put("name", w.get("name"));
358
359 ArrayList<WayPoint> trkseg = null;
360 for (Node n : w.nodes) {
361 if (n.incomplete || n.deleted) {
362 trkseg = null;
363 continue;
364 }
365 if (trkseg == null) {
366 trkseg = new ArrayList<WayPoint>();
367 trk.trackSegs.add(trkseg);
368 }
369 if (!n.tagged) {
370 doneNodes.add(n);
371 }
372 WayPoint wpt = new WayPoint(n.coor);
373 if (n.timestamp != null)
374 {
375 wpt.attr.put("time", n.timestamp);
376 wpt.setTime();
377 }
378 trkseg.add(wpt);
379 }
380 }
381
382 // what is this loop meant to do? it creates waypoints but never
383 // records them?
384 for (Node n : data.nodes) {
385 if (n.incomplete || n.deleted || doneNodes.contains(n)) continue;
386 WayPoint wpt = new WayPoint(n.coor);
387 if (n.timestamp != null) {
388 wpt.attr.put("time", n.timestamp);
389 wpt.setTime();
390 }
391 if (n.keys != null && n.keys.containsKey("name")) {
392 wpt.attr.put("name", n.keys.get("name"));
393 }
394 }
395 return gpxData;
396 }
397
398 public GpxData toGpxData() {
399 return toGpxData(data);
400 }
401
402 public class ConvertToGpxLayerAction extends AbstractAction {
403 public ConvertToGpxLayerAction() {
404 super(tr("Convert to GPX layer"), ImageProvider.get("converttogpx"));
405 }
406 public void actionPerformed(ActionEvent e) {
407 Main.main.addLayer(new GpxLayer(toGpxData(), tr("Converted from: {0}", name)));
408 Main.main.removeLayer(OsmDataLayer.this);
409 }
410 }
411
412 public boolean containsPoint(LatLon coor)
413 {
414 // we'll assume that if this has no data sources
415 // that it also has no borders
416 if (this.data.dataSources.isEmpty())
417 return true;
418
419 boolean layer_bounds_point = false;
420 for (DataSource src : this.data.dataSources) {
421 if (src.bounds.contains(coor)) {
422 layer_bounds_point = true;
423 break;
424 }
425 }
426 return layer_bounds_point;
427 }
428}
Note: See TracBrowser for help on using the repository browser.