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

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