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

Last change on this file since 1180 was 1180, checked in by stoecker, 15 years ago

fixed bug #1871, removed all deprecations

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