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

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