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

Last change on this file since 1523 was 1523, checked in by framm, 15 years ago
  • Major redesign of how JOSM talks to the OSM server. Connections now all go through a new OsmApi class that finds out which version the server uses. JOSM should now be able to handle 0.5 and 0.6 without configuration change. Config options osm-server.version and osm-server.additional-versions now obsolete. Handling of error and cancel situations might still need some improvement.
  • Property svn:eol-style set to native
File size: 17.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.ArrayList;
24import java.util.Collection;
25import java.util.HashSet;
26import java.util.Iterator;
27import java.util.LinkedList;
28import java.util.Set;
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.data.coor.EastNorth;
44import org.openstreetmap.josm.data.coor.LatLon;
45import org.openstreetmap.josm.data.gpx.GpxData;
46import org.openstreetmap.josm.data.gpx.GpxTrack;
47import org.openstreetmap.josm.data.gpx.WayPoint;
48import org.openstreetmap.josm.data.osm.DataSet;
49import org.openstreetmap.josm.data.osm.DataSource;
50import org.openstreetmap.josm.data.osm.Node;
51import org.openstreetmap.josm.data.osm.OsmPrimitive;
52import org.openstreetmap.josm.data.osm.Relation;
53import org.openstreetmap.josm.data.osm.Way;
54import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
55import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
56import org.openstreetmap.josm.data.osm.visitor.MapPaintVisitor;
57import org.openstreetmap.josm.data.osm.visitor.MergeVisitor;
58import org.openstreetmap.josm.data.osm.visitor.SimplePaintVisitor;
59import org.openstreetmap.josm.gui.MapView;
60import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
61import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
62import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
63import org.openstreetmap.josm.tools.DateUtils;
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 extends AbstractVisitor {
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 (data.version != null) tool += ", " + tr("version {0}", data.version);
218 if (associatedFile != null)
219 tool = "<html>"+tool+"<br>"+associatedFile.getPath()+"</html>";
220 return tool;
221 }
222
223 @Override public void mergeFrom(final Layer from) {
224 final MergeVisitor visitor = new MergeVisitor(data,((OsmDataLayer)from).data);
225 for (final OsmPrimitive osm : ((OsmDataLayer)from).data.allPrimitives()) {
226// i++;
227// if(i%100 == 0) {
228// double perc = (((double)i) / ((double)max) * 100.0);
229// System.out.format(" " + (int)perc + "%%");
230// }
231 osm.visit(visitor);
232 }
233 visitor.fixReferences();
234// System.out.println("");
235
236 // copy the merged layer's data source info
237 for (DataSource src : ((OsmDataLayer)from).data.dataSources)
238 data.dataSources.add(src);
239
240 // copy the merged layer's API version, downgrade if required
241 if (data.version == null) {
242 data.version = ((OsmDataLayer)from).data.version;
243 } else {
244 if ("0.5".equals(data.version) ^ "0.5".equals(((OsmDataLayer)from).data.version)) {
245 System.err.println("Warning: mixing 0.6 and 0.5 data results in version 0.5");
246 data.version = "0.5";
247 }
248 }
249 fireDataChange();
250 // repaint to make sure new data is displayed properly.
251 Main.map.mapView.repaint();
252
253 if (visitor.conflicts.isEmpty())
254 return;
255 final ConflictDialog dlg = Main.map.conflictDialog;
256 dlg.add(visitor.conflicts);
257 JOptionPane.showMessageDialog(Main.parent,tr("There were conflicts during import."));
258 if (!dlg.isVisible())
259 dlg.action.actionPerformed(new ActionEvent(this, 0, ""));
260 }
261
262 @Override public boolean isMergable(final Layer other) {
263 return other instanceof OsmDataLayer;
264 }
265
266 @Override public void visitBoundingBox(final BoundingXYVisitor v) {
267 for (final Node n : data.nodes)
268 if (!n.deleted && !n.incomplete)
269 v.visit(n);
270 }
271
272 /**
273 * Clean out the data behind the layer. This means clearing the redo/undo lists,
274 * really deleting all deleted objects and reset the modified flags. This is done
275 * after a successfull upload.
276 *
277 * @param processed A list of all objects that were actually uploaded.
278 * May be <code>null</code>, which means nothing has been uploaded but
279 * saved to disk instead. Note that an empty collection for "processed"
280 * means that an upload has been attempted but failed.
281 */
282 public void cleanData(final Collection<OsmPrimitive> processed, boolean dataAdded) {
283
284 // return immediately if an upload attempt failed
285 if (processed != null && processed.isEmpty() && !dataAdded)
286 return;
287
288 Main.main.undoRedo.clean();
289
290 // if uploaded, clean the modified flags as well
291 if (processed != null) {
292 final Set<OsmPrimitive> processedSet = new HashSet<OsmPrimitive>(processed);
293 for (final Iterator<Node> it = data.nodes.iterator(); it.hasNext();)
294 cleanIterator(it, processedSet);
295 for (final Iterator<Way> it = data.ways.iterator(); it.hasNext();)
296 cleanIterator(it, processedSet);
297 for (final Iterator<Relation> it = data.relations.iterator(); it.hasNext();)
298 cleanIterator(it, processedSet);
299 }
300
301 // update the modified flag
302 if (associatedFile != null && processed != null && !dataAdded)
303 return; // do nothing when uploading non-harmful changes.
304
305 // modified if server changed the data (esp. the id).
306 uploadedModified = associatedFile != null && processed != null && dataAdded;
307 setModified(uploadedModified);
308 }
309
310 /**
311 * Clean the modified flag for the given iterator over a collection if it is in the
312 * list of processed entries.
313 *
314 * @param it The iterator to change the modified and remove the items if deleted.
315 * @param processed A list of all objects that have been successfully progressed.
316 * If the object in the iterator is not in the list, nothing will be changed on it.
317 */
318 private void cleanIterator(final Iterator<? extends OsmPrimitive> it, final Collection<OsmPrimitive> processed) {
319 final OsmPrimitive osm = it.next();
320 if (!processed.remove(osm))
321 return;
322 osm.modified = false;
323 if (osm.deleted)
324 it.remove();
325 }
326
327 public boolean isModified() {
328 return modified;
329 }
330
331 public void setModified(final boolean modified) {
332 if (modified == this.modified)
333 return;
334 this.modified = modified;
335 for (final ModifiedChangedListener l : listenerModified)
336 l.modifiedChanged(modified, this);
337 }
338
339 /**
340 * @return The number of not-deleted primitives in the list.
341 */
342 private int undeletedSize(final Collection<? extends OsmPrimitive> list) {
343 int size = 0;
344 for (final OsmPrimitive osm : list)
345 if (!osm.deleted)
346 size++;
347 return size;
348 }
349
350 @Override public Object getInfoComponent() {
351 final DataCountVisitor counter = new DataCountVisitor();
352 for (final OsmPrimitive osm : data.allPrimitives())
353 osm.visit(counter);
354 final JPanel p = new JPanel(new GridBagLayout());
355 p.add(new JLabel(tr("{0} consists of:", name)), GBC.eol());
356 for (int i = 0; i < counter.normal.length; ++i) {
357 String s = counter.normal[i]+" "+trn(counter.names[i],counter.names[i]+"s",counter.normal[i]);
358 if (counter.deleted[i] > 0)
359 s += tr(" ({0} deleted.)",counter.deleted[i]);
360 p.add(new JLabel(s, ImageProvider.get("data", counter.names[i]), JLabel.HORIZONTAL), GBC.eop().insets(15,0,0,0));
361 }
362 p.add(new JLabel(tr("API version: {0}", (data.version != null) ? data.version : tr("unset"))));
363
364 return p;
365 }
366
367 @Override public Component[] getMenuEntries() {
368 if (Main.applet) {
369 return new Component[]{
370 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
371 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
372 new JSeparator(),
373 new JMenuItem(new RenameLayerAction(associatedFile, this)),
374 new JSeparator(),
375 new JMenuItem(new LayerListPopup.InfoAction(this))};
376 }
377 return new Component[]{
378 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
379 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
380 new JSeparator(),
381 new JMenuItem(new SaveAction(this)),
382 new JMenuItem(new SaveAsAction(this)),
383 new JMenuItem(new GpxExportAction(this)),
384 new JMenuItem(new ConvertToGpxLayerAction()),
385 new JSeparator(),
386 new JMenuItem(new RenameLayerAction(associatedFile, this)),
387 new JSeparator(),
388 new JMenuItem(new LayerListPopup.InfoAction(this))};
389 }
390
391 public void fireDataChange() {
392 for (DataChangeListener dcl : listenerDataChanged) {
393 dcl.dataChanged(this);
394 }
395 }
396
397 public static GpxData toGpxData(DataSet data, File file) {
398 GpxData gpxData = new GpxData();
399 gpxData.storageFile = file;
400 HashSet<Node> doneNodes = new HashSet<Node>();
401 for (Way w : data.ways) {
402 if (w.incomplete || w.deleted) continue;
403 GpxTrack trk = new GpxTrack();
404 gpxData.tracks.add(trk);
405
406 if (w.get("name") != null)
407 trk.attr.put("name", w.get("name"));
408
409 ArrayList<WayPoint> trkseg = null;
410 for (Node n : w.nodes) {
411 if (n.incomplete || n.deleted) {
412 trkseg = null;
413 continue;
414 }
415 if (trkseg == null) {
416 trkseg = new ArrayList<WayPoint>();
417 trk.trackSegs.add(trkseg);
418 }
419 if (!n.isTagged()) {
420 doneNodes.add(n);
421 }
422 WayPoint wpt = new WayPoint(n.coor);
423 if (!n.isTimestampEmpty())
424 {
425 wpt.attr.put("time", DateUtils.fromDate(n.getTimestamp()));
426 wpt.setTime();
427 }
428 trkseg.add(wpt);
429 }
430 }
431
432 // what is this loop meant to do? it creates waypoints but never
433 // records them?
434 for (Node n : data.nodes) {
435 if (n.incomplete || n.deleted || doneNodes.contains(n)) continue;
436 WayPoint wpt = new WayPoint(n.coor);
437 if (!n.isTimestampEmpty()) {
438 wpt.attr.put("time", DateUtils.fromDate(n.getTimestamp()));
439 wpt.setTime();
440 }
441 if (n.keys != null && n.keys.containsKey("name")) {
442 wpt.attr.put("name", n.keys.get("name"));
443 }
444 }
445 return gpxData;
446 }
447
448 public GpxData toGpxData() {
449 return toGpxData(data, associatedFile);
450 }
451
452 public class ConvertToGpxLayerAction extends AbstractAction {
453 public ConvertToGpxLayerAction() {
454 super(tr("Convert to GPX layer"), ImageProvider.get("converttogpx"));
455 }
456 public void actionPerformed(ActionEvent e) {
457 Main.main.addLayer(new GpxLayer(toGpxData(), tr("Converted from: {0}", name)));
458 Main.main.removeLayer(OsmDataLayer.this);
459 }
460 }
461
462 public boolean containsPoint(LatLon coor) {
463 // we'll assume that if this has no data sources
464 // that it also has no borders
465 if (this.data.dataSources.isEmpty())
466 return true;
467
468 boolean layer_bounds_point = false;
469 for (DataSource src : this.data.dataSources) {
470 if (src.bounds.contains(coor)) {
471 layer_bounds_point = true;
472 break;
473 }
474 }
475 return layer_bounds_point;
476 }
477}
Note: See TracBrowser for help on using the repository browser.