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

Last change on this file since 12574 was 12291, checked in by Don-vip, 7 years ago

sonar - squid:S1319 - Declarations should use Java collection interfaces such as "List" rather than specific implementation classes such as "ArrayList"

  • Property svn:eol-style set to native
File size: 42.0 KB
RevLine 
[8378]1// License: GPL. For details, see LICENSE file.
[444]2package org.openstreetmap.josm.gui.layer;
3
[2381]4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
[786]5import static org.openstreetmap.josm.tools.I18n.marktr;
[444]6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
[1082]9import java.awt.AlphaComposite;
[444]10import java.awt.Color;
[1082]11import java.awt.Composite;
[1078]12import java.awt.Graphics2D;
[10205]13import java.awt.GraphicsEnvironment;
[444]14import java.awt.GridBagLayout;
[1078]15import java.awt.Rectangle;
16import java.awt.TexturePaint;
[444]17import java.awt.event.ActionEvent;
[1078]18import java.awt.geom.Area;
[11633]19import java.awt.geom.Path2D;
[10806]20import java.awt.geom.Rectangle2D;
[1078]21import java.awt.image.BufferedImage;
[444]22import java.io.File;
[1523]23import java.util.ArrayList;
[5025]24import java.util.Arrays;
[444]25import java.util.Collection;
[7414]26import java.util.Collections;
[2907]27import java.util.HashMap;
[444]28import java.util.HashSet;
[9668]29import java.util.LinkedHashMap;
[3669]30import java.util.List;
[2907]31import java.util.Map;
[8338]32import java.util.Set;
[5519]33import java.util.concurrent.CopyOnWriteArrayList;
[11033]34import java.util.concurrent.atomic.AtomicInteger;
[8956]35import java.util.regex.Pattern;
[444]36
37import javax.swing.AbstractAction;
[3408]38import javax.swing.Action;
[444]39import javax.swing.Icon;
40import javax.swing.JLabel;
41import javax.swing.JOptionPane;
42import javax.swing.JPanel;
[2500]43import javax.swing.JScrollPane;
[444]44
45import org.openstreetmap.josm.Main;
[5025]46import org.openstreetmap.josm.actions.ExpertToggleAction;
[444]47import org.openstreetmap.josm.actions.RenameLayerAction;
[5025]48import org.openstreetmap.josm.actions.ToggleUploadDiscouragedLayerAction;
[7358]49import org.openstreetmap.josm.data.APIDataSet;
[2450]50import org.openstreetmap.josm.data.Bounds;
[7575]51import org.openstreetmap.josm.data.DataSource;
[10371]52import org.openstreetmap.josm.data.ProjectionBounds;
[1750]53import org.openstreetmap.josm.data.conflict.Conflict;
54import org.openstreetmap.josm.data.conflict.ConflictCollection;
[10806]55import org.openstreetmap.josm.data.coor.EastNorth;
[628]56import org.openstreetmap.josm.data.coor.LatLon;
[7518]57import org.openstreetmap.josm.data.gpx.GpxConstants;
[1523]58import org.openstreetmap.josm.data.gpx.GpxData;
[7518]59import org.openstreetmap.josm.data.gpx.GpxLink;
[2907]60import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
[1523]61import org.openstreetmap.josm.data.gpx.WayPoint;
[3965]62import org.openstreetmap.josm.data.osm.DataIntegrityProblemException;
[12152]63import org.openstreetmap.josm.data.osm.DataSelectionListener;
[444]64import org.openstreetmap.josm.data.osm.DataSet;
[11709]65import org.openstreetmap.josm.data.osm.DataSet.UploadPolicy;
[2434]66import org.openstreetmap.josm.data.osm.DataSetMerger;
[2500]67import org.openstreetmap.josm.data.osm.DatasetConsistencyTest;
[12014]68import org.openstreetmap.josm.data.osm.HighlightUpdateListener;
[4534]69import org.openstreetmap.josm.data.osm.IPrimitive;
[444]70import org.openstreetmap.josm.data.osm.Node;
71import org.openstreetmap.josm.data.osm.OsmPrimitive;
[8807]72import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator;
[1523]73import org.openstreetmap.josm.data.osm.Relation;
[444]74import org.openstreetmap.josm.data.osm.Way;
[3116]75import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
76import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
77import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter.Listener;
[1523]78import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
[444]79import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
[4087]80import org.openstreetmap.josm.data.osm.visitor.paint.MapRendererFactory;
81import org.openstreetmap.josm.data.osm.visitor.paint.Rendering;
[4623]82import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
[10853]83import org.openstreetmap.josm.data.preferences.ColorProperty;
[9668]84import org.openstreetmap.josm.data.preferences.IntegerProperty;
[9721]85import org.openstreetmap.josm.data.preferences.StringProperty;
[4126]86import org.openstreetmap.josm.data.projection.Projection;
[3669]87import org.openstreetmap.josm.data.validation.TestError;
[5459]88import org.openstreetmap.josm.gui.ExtendedDialog;
[444]89import org.openstreetmap.josm.gui.MapView;
[10806]90import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
[444]91import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
92import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
[7358]93import org.openstreetmap.josm.gui.io.AbstractIOTask;
94import org.openstreetmap.josm.gui.io.AbstractUploadDialog;
95import org.openstreetmap.josm.gui.io.UploadDialog;
96import org.openstreetmap.josm.gui.io.UploadLayerTask;
[8805]97import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
[4684]98import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
99import org.openstreetmap.josm.gui.progress.ProgressMonitor;
[7204]100import org.openstreetmap.josm.gui.util.GuiHelper;
[9670]101import org.openstreetmap.josm.gui.widgets.FileChooserManager;
[5886]102import org.openstreetmap.josm.gui.widgets.JosmTextArea;
[9670]103import org.openstreetmap.josm.io.OsmImporter;
[11033]104import org.openstreetmap.josm.tools.AlphanumComparator;
[8910]105import org.openstreetmap.josm.tools.CheckParameterUtil;
[444]106import org.openstreetmap.josm.tools.GBC;
[8323]107import org.openstreetmap.josm.tools.ImageOverlay;
[444]108import org.openstreetmap.josm.tools.ImageProvider;
[8323]109import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
[7299]110import org.openstreetmap.josm.tools.date.DateUtils;
[444]111
112/**
[5275]113 * A layer that holds OSM data from a specific dataset.
[444]114 * The data can be fully edited.
[1169]115 *
[444]116 * @author imi
[7414]117 * @since 17
[444]118 */
[12152]119public class OsmDataLayer extends AbstractModifiableLayer implements Listener, DataSelectionListener, HighlightUpdateListener {
[10806]120 private static final int HATCHED_SIZE = 15;
[7414]121 /** Property used to know if this layer has to be saved on disk */
[6889]122 public static final String REQUIRES_SAVE_TO_DISK_PROP = OsmDataLayer.class.getName() + ".requiresSaveToDisk";
[7414]123 /** Property used to know if this layer has to be uploaded */
[6889]124 public static final String REQUIRES_UPLOAD_TO_SERVER_PROP = OsmDataLayer.class.getName() + ".requiresUploadToServer";
[444]125
[8840]126 private boolean requiresSaveToFile;
127 private boolean requiresUploadToServer;
[2025]128
[6436]129 /**
130 * List of validation errors in this layer.
131 * @since 3669
132 */
[7005]133 public final List<TestError> validationErrors = new ArrayList<>();
[3669]134
[12115]135 /**
136 * The default number of relations in the recent relations cache.
137 * @see #getRecentRelations()
138 */
[9668]139 public static final int DEFAULT_RECENT_RELATIONS_NUMBER = 20;
[12115]140 /**
141 * The number of relations to use in the recent relations cache.
142 * @see #getRecentRelations()
143 */
[9668]144 public static final IntegerProperty PROPERTY_RECENT_RELATIONS_NUMBER = new IntegerProperty("properties.last-closed-relations-size",
145 DEFAULT_RECENT_RELATIONS_NUMBER);
[12115]146 /**
147 * The extension that should be used when saving the OSM file.
148 */
[9721]149 public static final StringProperty PROPERTY_SAVE_EXTENSION = new StringProperty("save.extension.osm", "osm");
[9668]150
[10853]151 private static final ColorProperty PROPERTY_BACKGROUND_COLOR = new ColorProperty(marktr("background"), Color.BLACK);
152 private static final ColorProperty PROPERTY_OUTSIDE_COLOR = new ColorProperty(marktr("outside downloaded area"), Color.YELLOW);
[9721]153
[9668]154 /** List of recent relations */
[11366]155 private final Map<Relation, Void> recentRelations = new LruCache(PROPERTY_RECENT_RELATIONS_NUMBER.get()+1);
[9668]156
157 /**
158 * Returns list of recently closed relations or null if none.
[9679]159 * @return list of recently closed relations or <code>null</code> if none
[12291]160 * @since 12291 (signature)
[9679]161 * @since 9668
[9668]162 */
[12291]163 public List<Relation> getRecentRelations() {
[9971]164 ArrayList<Relation> list = new ArrayList<>(recentRelations.keySet());
[9668]165 Collections.reverse(list);
166 return list;
167 }
168
[9678]169 /**
[9679]170 * Adds recently closed relation.
171 * @param relation new entry for the list of recently closed relations
[12116]172 * @see #PROPERTY_RECENT_RELATIONS_NUMBER
[9679]173 * @since 9668
[9678]174 */
[9668]175 public void setRecentRelation(Relation relation) {
176 recentRelations.put(relation, null);
[10280]177 if (Main.map != null && Main.map.relationListDialog != null) {
178 Main.map.relationListDialog.enableRecentRelations();
179 }
[9668]180 }
181
[9678]182 /**
183 * Remove relation from list of recent relations.
184 * @param relation relation to remove
[9679]185 * @since 9668
[9678]186 */
[9668]187 public void removeRecentRelation(Relation relation) {
188 recentRelations.remove(relation);
[10280]189 if (Main.map != null && Main.map.relationListDialog != null) {
190 Main.map.relationListDialog.enableRecentRelations();
191 }
[9668]192 }
193
[2025]194 protected void setRequiresSaveToFile(boolean newValue) {
195 boolean oldValue = requiresSaveToFile;
196 requiresSaveToFile = newValue;
197 if (oldValue != newValue) {
198 propertyChangeSupport.firePropertyChange(REQUIRES_SAVE_TO_DISK_PROP, oldValue, newValue);
199 }
200 }
201
202 protected void setRequiresUploadToServer(boolean newValue) {
203 boolean oldValue = requiresUploadToServer;
204 requiresUploadToServer = newValue;
205 if (oldValue != newValue) {
206 propertyChangeSupport.firePropertyChange(REQUIRES_UPLOAD_TO_SERVER_PROP, oldValue, newValue);
207 }
208 }
209
[1750]210 /** the global counter for created data layers */
[11033]211 private static final AtomicInteger dataLayerCounter = new AtomicInteger();
[1750]212
213 /**
214 * Replies a new unique name for a data layer
[1843]215 *
[1750]216 * @return a new unique name for a data layer
217 */
[6889]218 public static String createNewName() {
[11033]219 return createLayerName(dataLayerCounter.incrementAndGet());
[1750]220 }
221
[11033]222 static String createLayerName(Object arg) {
223 return tr("Data Layer {0}", arg);
224 }
225
[11366]226 static final class LruCache extends LinkedHashMap<Relation, Void> {
227 LruCache(int initialCapacity) {
228 super(initialCapacity, 1.1f, true);
229 }
230
231 @Override
232 protected boolean removeEldestEntry(Map.Entry<Relation, Void> eldest) {
233 return size() > PROPERTY_RECENT_RELATIONS_NUMBER.get();
234 }
235 }
236
[12115]237 /**
238 * A listener that counts the number of primitives it encounters
239 */
[6889]240 public static final class DataCountVisitor extends AbstractVisitor {
[12115]241 /**
242 * Nodes that have been visited
243 */
[2386]244 public int nodes;
[12115]245 /**
246 * Ways that have been visited
247 */
[2386]248 public int ways;
[12115]249 /**
250 * Relations that have been visited
251 */
[2386]252 public int relations;
[12115]253 /**
254 * Deleted nodes that have been visited
255 */
[2386]256 public int deletedNodes;
[12115]257 /**
258 * Deleted ways that have been visited
259 */
[2386]260 public int deletedWays;
[12115]261 /**
262 * Deleted relations that have been visited
263 */
[2386]264 public int deletedRelations;
[444]265
[6084]266 @Override
[2386]267 public void visit(final Node n) {
268 nodes++;
269 if (n.isDeleted()) {
270 deletedNodes++;
[1670]271 }
[1078]272 }
[444]273
[6084]274 @Override
[2386]275 public void visit(final Way w) {
276 ways++;
277 if (w.isDeleted()) {
278 deletedWays++;
279 }
[1078]280 }
[444]281
[6084]282 @Override
[2386]283 public void visit(final Relation r) {
284 relations++;
285 if (r.isDeleted()) {
286 deletedRelations++;
287 }
[1078]288 }
289 }
[444]290
[12115]291 /**
292 * A listener that gets notified of command queue (undo/redo) size changes.
293 */
[10600]294 @FunctionalInterface
[1078]295 public interface CommandQueueListener {
[12115]296 /**
297 * Notifies the listener about the new queue size
298 * @param queueSize Undo stack size
299 * @param redoSize Redo stack size
300 */
[1078]301 void commandChanged(int queueSize, int redoSize);
302 }
[6070]303
[5519]304 /**
305 * Listener called when a state of this layer has changed.
[10600]306 * @since 10600 (functional interface)
[5519]307 */
[10600]308 @FunctionalInterface
[5519]309 public interface LayerStateChangeListener {
310 /**
311 * Notifies that the "upload discouraged" (upload=no) state has changed.
312 * @param layer The layer that has been modified
313 * @param newValue The new value of the state
314 */
315 void uploadDiscouragedChanged(OsmDataLayer layer, boolean newValue);
316 }
[6070]317
[7005]318 private final CopyOnWriteArrayList<LayerStateChangeListener> layerStateChangeListeners = new CopyOnWriteArrayList<>();
[6070]319
[5519]320 /**
321 * Adds a layer state change listener
322 *
323 * @param listener the listener. Ignored if null or already registered.
324 * @since 5519
325 */
326 public void addLayerStateChangeListener(LayerStateChangeListener listener) {
327 if (listener != null) {
328 layerStateChangeListeners.addIfAbsent(listener);
329 }
330 }
[6070]331
[5519]332 /**
[10340]333 * Removes a layer state change listener
[5519]334 *
335 * @param listener the listener. Ignored if null or already registered.
[10340]336 * @since 10340
337 */
338 public void removeLayerStateChangeListener(LayerStateChangeListener listener) {
[5519]339 layerStateChangeListeners.remove(listener);
340 }
[444]341
[1078]342 /**
343 * The data behind this layer.
344 */
345 public final DataSet data;
[444]346
[1078]347 /**
[1750]348 * the collection of conflicts detected in this layer
349 */
[9078]350 private final ConflictCollection conflicts;
[1750]351
[2985]352 /**
[10806]353 * a texture for non-downloaded area
[1078]354 */
[10806]355 private static volatile BufferedImage hatched;
[1169]356
[1082]357 static {
358 createHatchTexture();
359 }
360
[7414]361 /**
362 * Replies background color for downloaded areas.
363 * @return background color for downloaded areas. Black by default
364 */
[3233]365 public static Color getBackgroundColor() {
[10853]366 return PROPERTY_BACKGROUND_COLOR.get();
[3233]367 }
368
[7414]369 /**
370 * Replies background color for non-downloaded areas.
371 * @return background color for non-downloaded areas. Yellow by default
372 */
[3233]373 public static Color getOutsideColor() {
[10853]374 return PROPERTY_OUTSIDE_COLOR.get();
[3233]375 }
376
[1078]377 /**
[1082]378 * Initialize the hatch pattern used to paint the non-downloaded area
[1078]379 */
[1082]380 public static void createHatchTexture() {
[10806]381 BufferedImage bi = new BufferedImage(HATCHED_SIZE, HATCHED_SIZE, BufferedImage.TYPE_INT_ARGB);
[1078]382 Graphics2D big = bi.createGraphics();
[3233]383 big.setColor(getBackgroundColor());
[1082]384 Composite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
385 big.setComposite(comp);
[10806]386 big.fillRect(0, 0, HATCHED_SIZE, HATCHED_SIZE);
[3233]387 big.setColor(getOutsideColor());
[10344]388 big.drawLine(-1, 6, 6, -1);
389 big.drawLine(4, 16, 16, 4);
[10806]390 hatched = bi;
[1078]391 }
[444]392
[1078]393 /**
[7414]394 * Construct a new {@code OsmDataLayer}.
395 * @param data OSM data
396 * @param name Layer name
397 * @param associatedFile Associated .osm file (can be null)
[1082]398 */
399 public OsmDataLayer(final DataSet data, final String name, final File associatedFile) {
400 super(name);
[8910]401 CheckParameterUtil.ensureParameterNotNull(data, "data");
[1082]402 this.data = data;
[1646]403 this.setAssociatedFile(associatedFile);
[1750]404 conflicts = new ConflictCollection();
[3116]405 data.addDataSetListener(new DataSetListenerAdapter(this));
[4623]406 data.addDataSetListener(MultipolygonCache.getInstance());
[12014]407 data.addHighlightUpdateListener(this);
[12152]408 data.addSelectionListener(this);
[11277]409 if (name != null && name.startsWith(createLayerName("")) && Character.isDigit(
410 (name.substring(createLayerName("").length()) + "XX" /*avoid StringIndexOutOfBoundsException*/).charAt(1))) {
[11033]411 while (AlphanumComparator.getInstance().compare(createLayerName(dataLayerCounter), name) < 0) {
[11261]412 final int i = dataLayerCounter.incrementAndGet();
413 if (i > 1_000_000) {
414 break; // to avoid looping in unforeseen case
415 }
[11033]416 }
417 }
[1082]418 }
419
[8323]420 /**
421 * Return the image provider to get the base icon
422 * @return image provider class which can be modified
423 * @since 8323
424 */
425 protected ImageProvider getBaseIconProvider() {
426 return new ImageProvider("layer", "osmdata_small");
[5025]427 }
[6070]428
[8323]429 @Override
430 public Icon getIcon() {
431 ImageProvider base = getBaseIconProvider().setMaxSize(ImageSizes.LAYER);
[11709]432 if (isUploadDiscouraged() || data.getUploadPolicy() == UploadPolicy.BLOCKED) {
[8323]433 base.addOverlay(new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0));
[5025]434 }
[8323]435 return base.get();
[1078]436 }
[444]437
[1078]438 /**
439 * Draw all primitives in this layer but do not draw modified ones (they
440 * are drawn by the edit layer).
441 * Draw nodes last to overlap the ways they belong to.
442 */
[2450]443 @Override public void paint(final Graphics2D g, final MapView mv, Bounds box) {
[10395]444 boolean active = mv.getLayerManager().getActiveLayer() == this;
[1083]445 boolean inactive = !active && Main.pref.getBoolean("draw.data.inactive_color", true);
[1823]446 boolean virtual = !inactive && mv.isVirtualNodesEnabled();
[1169]447
448 // draw the hatched area for non-downloaded region. only draw if we're the active
[1083]449 // and bounds are defined; don't draw for inactive layers or loaded GPX files etc
[11627]450 if (active && Main.pref.getBoolean("draw.data.downloaded_area", true) && !data.getDataSources().isEmpty()) {
[1078]451 // initialize area with current viewport
[1823]452 Rectangle b = mv.getBounds();
[1169]453 // on some platforms viewport bounds seem to be offset from the left,
[1083]454 // over-grow it just to be sure
455 b.grow(100, 100);
[11633]456 Path2D p = new Path2D.Double();
[1169]457
[11633]458 // combine successively downloaded areas
[4087]459 for (Bounds bounds : data.getDataSourceBounds()) {
460 if (bounds.isCollapsed()) {
461 continue;
[1078]462 }
[11633]463 p.append(mv.getState().getArea(bounds), false);
[1078]464 }
[11633]465 // subtract combined areas
466 Area a = new Area(b);
467 a.subtract(new Area(p));
[1169]468
[1078]469 // paint remainder
[10806]470 MapViewPoint anchor = mv.getState().getPointFor(new EastNorth(0, 0));
471 Rectangle2D anchorRect = new Rectangle2D.Double(anchor.getInView().getX() % HATCHED_SIZE,
472 anchor.getInView().getY() % HATCHED_SIZE, HATCHED_SIZE, HATCHED_SIZE);
473 g.setPaint(new TexturePaint(hatched, anchorRect));
[6501]474 g.fill(a);
[1078]475 }
[1169]476
[4087]477 Rendering painter = MapRendererFactory.getInstance().createActiveRenderer(g, mv, inactive);
478 painter.render(data, virtual, box);
[1078]479 Main.map.conflictDialog.paintConflicts(g, mv);
480 }
[444]481
[1078]482 @Override public String getToolTipText() {
[12153]483 DataCountVisitor counter = new DataCountVisitor();
484 for (final OsmPrimitive osm : data.allPrimitives()) {
485 osm.accept(counter);
486 }
487 int nodes = counter.nodes - counter.deletedNodes;
488 int ways = counter.ways - counter.deletedWays;
489 int rels = counter.relations - counter.deletedRelations;
[2386]490
[12160]491 StringBuilder tooltip = new StringBuilder("<html>")
492 .append(trn("{0} node", "{0} nodes", nodes, nodes))
493 .append("<br>")
494 .append(trn("{0} way", "{0} ways", ways, ways))
495 .append("<br>")
496 .append(trn("{0} relation", "{0} relations", rels, rels));
[2386]497
[1646]498 File f = getAssociatedFile();
[1670]499 if (f != null) {
[12160]500 tooltip.append("<br>").append(f.getPath());
[1670]501 }
[12153]502 tooltip.append("</html>");
503 return tooltip.toString();
[1078]504 }
[444]505
[1078]506 @Override public void mergeFrom(final Layer from) {
[4684]507 final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Merging layers"));
508 monitor.setCancelable(false);
[8510]509 if (from instanceof OsmDataLayer && ((OsmDataLayer) from).isUploadDiscouraged()) {
[5233]510 setUploadDiscouraged(true);
511 }
[8510]512 mergeFrom(((OsmDataLayer) from).data, monitor);
[4684]513 monitor.close();
[1670]514 }
515
516 /**
517 * merges the primitives in dataset <code>from</code> into the dataset of
518 * this layer
[1677]519 *
[1670]520 * @param from the source data set
521 */
522 public void mergeFrom(final DataSet from) {
[4684]523 mergeFrom(from, null);
524 }
[6070]525
[4684]526 /**
[9674]527 * merges the primitives in dataset <code>from</code> into the dataset of this layer
[4684]528 *
529 * @param from the source data set
[5869]530 * @param progressMonitor the progress monitor, can be {@code null}
[4684]531 */
532 public void mergeFrom(final DataSet from, ProgressMonitor progressMonitor) {
[8510]533 final DataSetMerger visitor = new DataSetMerger(data, from);
[3965]534 try {
[4684]535 visitor.merge(progressMonitor);
[3965]536 } catch (DataIntegrityProblemException e) {
[9674]537 Main.error(e);
[3965]538 JOptionPane.showMessageDialog(
539 Main.parent,
[5059]540 e.getHtmlMessage() != null ? e.getHtmlMessage() : e.getMessage(),
[3965]541 tr("Error"),
542 JOptionPane.ERROR_MESSAGE
543 );
544 return;
545 }
546
[1558]547 Area a = data.getDataSourceArea();
[1670]548
[6296]549 // copy the merged layer's data source info.
550 // only add source rectangles if they are not contained in the layer already.
[11627]551 for (DataSource src : from.getDataSources()) {
[1670]552 if (a == null || !a.contains(src.bounds.asRect())) {
[11627]553 data.addDataSource(src);
[1670]554 }
[1558]555 }
[1670]556
[7013]557 // copy the merged layer's API version
[2396]558 if (data.getVersion() == null) {
559 data.setVersion(from.getVersion());
[1523]560 }
[1169]561
[1750]562 int numNewConflicts = 0;
[2198]563 for (Conflict<?> c : visitor.getConflicts()) {
[1750]564 if (!conflicts.hasConflict(c)) {
565 numNewConflicts++;
566 conflicts.add(c);
567 }
[1670]568 }
[2198]569 // repaint to make sure new data is displayed properly.
[12176]570 invalidate();
[5869]571 // warn about new conflicts
572 if (numNewConflicts > 0 && Main.map != null && Main.map.conflictDialog != null) {
573 Main.map.conflictDialog.warnNumNewConflicts(numNewConflicts);
574 }
[2198]575 }
576
[8510]577 @Override
578 public boolean isMergable(final Layer other) {
[8855]579 // allow merging between normal layers and discouraged layers with a warning (see #7684)
580 return other instanceof OsmDataLayer;
[1078]581 }
[444]582
[8510]583 @Override
584 public void visitBoundingBox(final BoundingXYVisitor v) {
[2381]585 for (final Node n: data.getNodes()) {
[2120]586 if (n.isUsable()) {
[1078]587 v.visit(n);
[1670]588 }
[2381]589 }
[1078]590 }
[444]591
[1078]592 /**
593 * Clean out the data behind the layer. This means clearing the redo/undo lists,
[1894]594 * really deleting all deleted objects and reset the modified flags. This should
595 * be done after an upload, even after a partial upload.
[1169]596 *
597 * @param processed A list of all objects that were actually uploaded.
[1894]598 * May be <code>null</code>, which means nothing has been uploaded
[1078]599 */
[10205]600 public void cleanupAfterUpload(final Collection<? extends IPrimitive> processed) {
[1078]601 // return immediately if an upload attempt failed
[1894]602 if (processed == null || processed.isEmpty())
[1078]603 return;
[1169]604
[1894]605 Main.main.undoRedo.clean(this);
[444]606
[1078]607 // if uploaded, clean the modified flags as well
[3426]608 data.cleanupDeletedPrimitives();
[9941]609 data.beginUpdate();
610 try {
611 for (OsmPrimitive p: data.allPrimitives()) {
612 if (processed.contains(p)) {
613 p.setModified(false);
614 }
[3206]615 }
[9941]616 } finally {
617 data.endUpdate();
[1078]618 }
619 }
[444]620
[8510]621 @Override
622 public Object getInfoComponent() {
[1078]623 final DataCountVisitor counter = new DataCountVisitor();
[1670]624 for (final OsmPrimitive osm : data.allPrimitives()) {
[6009]625 osm.accept(counter);
[1670]626 }
[1078]627 final JPanel p = new JPanel(new GridBagLayout());
[2386]628
629 String nodeText = trn("{0} node", "{0} nodes", counter.nodes, counter.nodes);
[2388]630 if (counter.deletedNodes > 0) {
[8846]631 nodeText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedNodes, counter.deletedNodes)+')';
[2388]632 }
[2386]633
634 String wayText = trn("{0} way", "{0} ways", counter.ways, counter.ways);
[2388]635 if (counter.deletedWays > 0) {
[8846]636 wayText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedWays, counter.deletedWays)+')';
[2388]637 }
[2386]638
639 String relationText = trn("{0} relation", "{0} relations", counter.relations, counter.relations);
[2388]640 if (counter.deletedRelations > 0) {
[8846]641 relationText += " ("+trn("{0} deleted", "{0} deleted", counter.deletedRelations, counter.deletedRelations)+')';
[2388]642 }
[2386]643
[1890]644 p.add(new JLabel(tr("{0} consists of:", getName())), GBC.eol());
[8510]645 p.add(new JLabel(nodeText, ImageProvider.get("data", "node"), JLabel.HORIZONTAL), GBC.eop().insets(15, 0, 0, 0));
646 p.add(new JLabel(wayText, ImageProvider.get("data", "way"), JLabel.HORIZONTAL), GBC.eop().insets(15, 0, 0, 0));
647 p.add(new JLabel(relationText, ImageProvider.get("data", "relation"), JLabel.HORIZONTAL), GBC.eop().insets(15, 0, 0, 0));
648 p.add(new JLabel(tr("API version: {0}", (data.getVersion() != null) ? data.getVersion() : tr("unset"))),
649 GBC.eop().insets(15, 0, 0, 0));
[5025]650 if (isUploadDiscouraged()) {
[8510]651 p.add(new JLabel(tr("Upload is discouraged")), GBC.eop().insets(15, 0, 0, 0));
[5025]652 }
[11709]653 if (data.getUploadPolicy() == UploadPolicy.BLOCKED) {
654 p.add(new JLabel(tr("Upload is blocked")), GBC.eop().insets(15, 0, 0, 0));
655 }
[1523]656
[1078]657 return p;
658 }
[444]659
[3408]660 @Override public Action[] getMenuEntries() {
[7005]661 List<Action> actions = new ArrayList<>();
[12279]662 actions.addAll(Arrays.asList(
[3408]663 LayerListDialog.getInstance().createActivateLayerAction(this),
664 LayerListDialog.getInstance().createShowHideLayerAction(),
665 LayerListDialog.getInstance().createDeleteLayerAction(),
666 SeparatorLayerAction.INSTANCE,
667 LayerListDialog.getInstance().createMergeLayerAction(this),
[8728]668 LayerListDialog.getInstance().createDuplicateLayerAction(this),
[3408]669 new LayerSaveAction(this),
[12279]670 new LayerSaveAsAction(this)));
[6039]671 if (ExpertToggleAction.isExpert()) {
[12279]672 actions.addAll(Arrays.asList(
[6039]673 new LayerGpxExportAction(this),
[12279]674 new ConvertToGpxLayerAction()));
[6039]675 }
[12279]676 actions.addAll(Arrays.asList(
[3408]677 SeparatorLayerAction.INSTANCE,
[12279]678 new RenameLayerAction(getAssociatedFile(), this)));
[9873]679 if (ExpertToggleAction.isExpert()) {
[5025]680 actions.add(new ToggleUploadDiscouragedLayerAction(this));
681 }
[12279]682 actions.addAll(Arrays.asList(
[3408]683 new ConsistencyTestAction(),
684 SeparatorLayerAction.INSTANCE,
[12279]685 new LayerListPopup.InfoAction(this)));
[6083]686 return actions.toArray(new Action[actions.size()]);
[1078]687 }
[444]688
[7414]689 /**
690 * Converts given OSM dataset to GPX data.
691 * @param data OSM dataset
692 * @param file output .gpx file
693 * @return GPX data
694 */
[1508]695 public static GpxData toGpxData(DataSet data, File file) {
[1078]696 GpxData gpxData = new GpxData();
[1508]697 gpxData.storageFile = file;
[8338]698 Set<Node> doneNodes = new HashSet<>();
[7414]699 waysToGpxData(data.getWays(), gpxData, doneNodes);
700 nodesToGpxData(data.getNodes(), gpxData, doneNodes);
701 return gpxData;
702 }
703
[8338]704 private static void waysToGpxData(Collection<Way> ways, GpxData gpxData, Set<Node> doneNodes) {
[8807]705 /* When the dataset has been obtained from a gpx layer and now is being converted back,
706 * the ways have negative ids. The first created way corresponds to the first gpx segment,
707 * and has the highest id (i.e., closest to zero).
708 * Thus, sorting by OsmPrimitive#getUniqueId gives the original order.
709 * (Only works if the data layer has not been saved to and been loaded from an osm file before.)
710 */
[11177]711 ways.stream()
712 .sorted(OsmPrimitiveComparator.comparingUniqueId().reversed())
713 .forEachOrdered(w -> {
[2120]714 if (!w.isUsable()) {
[11177]715 return;
[1670]716 }
[7005]717 Collection<Collection<WayPoint>> trk = new ArrayList<>();
718 Map<String, Object> trkAttr = new HashMap<>();
[508]719
[11608]720 String name = w.get("name");
721 if (name != null) {
722 trkAttr.put("name", name);
[1670]723 }
[444]724
[3669]725 List<WayPoint> trkseg = null;
[1898]726 for (Node n : w.getNodes()) {
[2120]727 if (!n.isUsable()) {
[1078]728 trkseg = null;
729 continue;
730 }
731 if (trkseg == null) {
[7005]732 trkseg = new ArrayList<>();
[2907]733 trk.add(trkseg);
[1078]734 }
[1499]735 if (!n.isTagged()) {
[1078]736 doneNodes.add(n);
737 }
[8158]738 trkseg.add(nodeToWayPoint(n));
[1078]739 }
[2907]740
[12156]741 gpxData.addTrack(new ImmutableGpxTrack(trk, trkAttr));
[11177]742 });
[7414]743 }
[1169]744
[8158]745 private static WayPoint nodeToWayPoint(Node n) {
746 WayPoint wpt = new WayPoint(n.getCoor());
[7518]747
[8158]748 // Position info
[7518]749
[8158]750 addDoubleIfPresent(wpt, n, GpxConstants.PT_ELE);
[7518]751
[8158]752 if (!n.isTimestampEmpty()) {
[8565]753 wpt.put("time", DateUtils.fromTimestamp(n.getRawTimestamp()));
[8158]754 wpt.setTime();
755 }
[7518]756
[8158]757 addDoubleIfPresent(wpt, n, GpxConstants.PT_MAGVAR);
758 addDoubleIfPresent(wpt, n, GpxConstants.PT_GEOIDHEIGHT);
[7518]759
[8158]760 // Description info
[7518]761
[8158]762 addStringIfPresent(wpt, n, GpxConstants.GPX_NAME);
763 addStringIfPresent(wpt, n, GpxConstants.GPX_DESC, "description");
764 addStringIfPresent(wpt, n, GpxConstants.GPX_CMT, "comment");
765 addStringIfPresent(wpt, n, GpxConstants.GPX_SRC, "source", "source:position");
[7518]766
[8158]767 Collection<GpxLink> links = new ArrayList<>();
768 for (String key : new String[]{"link", "url", "website", "contact:website"}) {
769 String value = n.get(key);
770 if (value != null) {
771 links.add(new GpxLink(value));
[4087]772 }
[8158]773 }
774 wpt.put(GpxConstants.META_LINKS, links);
[4046]775
[8158]776 addStringIfPresent(wpt, n, GpxConstants.PT_SYM, "wpt_symbol");
777 addStringIfPresent(wpt, n, GpxConstants.PT_TYPE);
[7518]778
[8158]779 // Accuracy info
780 addStringIfPresent(wpt, n, GpxConstants.PT_FIX, "gps:fix");
781 addIntegerIfPresent(wpt, n, GpxConstants.PT_SAT, "gps:sat");
782 addDoubleIfPresent(wpt, n, GpxConstants.PT_HDOP, "gps:hdop");
783 addDoubleIfPresent(wpt, n, GpxConstants.PT_VDOP, "gps:vdop");
784 addDoubleIfPresent(wpt, n, GpxConstants.PT_PDOP, "gps:pdop");
785 addDoubleIfPresent(wpt, n, GpxConstants.PT_AGEOFDGPSDATA, "gps:ageofdgpsdata");
786 addIntegerIfPresent(wpt, n, GpxConstants.PT_DGPSID, "gps:dgpsid");
[7518]787
[8158]788 return wpt;
789 }
790
[8338]791 private static void nodesToGpxData(Collection<Node> nodes, GpxData gpxData, Set<Node> doneNodes) {
[8158]792 List<Node> sortedNodes = new ArrayList<>(nodes);
793 sortedNodes.removeAll(doneNodes);
794 Collections.sort(sortedNodes);
795 for (Node n : sortedNodes) {
796 if (n.isIncomplete() || n.isDeleted()) {
797 continue;
798 }
799 gpxData.waypoints.add(nodeToWayPoint(n));
[1078]800 }
801 }
[444]802
[11747]803 private static void addIntegerIfPresent(WayPoint wpt, OsmPrimitive p, String gpxKey, String... osmKeys) {
[8338]804 List<String> possibleKeys = new ArrayList<>(Arrays.asList(osmKeys));
[7518]805 possibleKeys.add(0, gpxKey);
806 for (String key : possibleKeys) {
807 String value = p.get(key);
808 if (value != null) {
809 try {
810 int i = Integer.parseInt(value);
811 // Sanity checks
812 if ((!GpxConstants.PT_SAT.equals(gpxKey) || i >= 0) &&
813 (!GpxConstants.PT_DGPSID.equals(gpxKey) || (0 <= i && i <= 1023))) {
814 wpt.put(gpxKey, value);
815 break;
816 }
817 } catch (NumberFormatException e) {
[10626]818 Main.trace(e);
[7518]819 }
820 }
821 }
822 }
823
[11747]824 private static void addDoubleIfPresent(WayPoint wpt, OsmPrimitive p, String gpxKey, String... osmKeys) {
[8338]825 List<String> possibleKeys = new ArrayList<>(Arrays.asList(osmKeys));
[7518]826 possibleKeys.add(0, gpxKey);
827 for (String key : possibleKeys) {
828 String value = p.get(key);
829 if (value != null) {
830 try {
831 double d = Double.parseDouble(value);
832 // Sanity checks
833 if (!GpxConstants.PT_MAGVAR.equals(gpxKey) || (0.0 <= d && d < 360.0)) {
834 wpt.put(gpxKey, value);
835 break;
836 }
837 } catch (NumberFormatException e) {
[10626]838 Main.trace(e);
[7518]839 }
840 }
841 }
842 }
843
[11747]844 private static void addStringIfPresent(WayPoint wpt, OsmPrimitive p, String gpxKey, String... osmKeys) {
[8338]845 List<String> possibleKeys = new ArrayList<>(Arrays.asList(osmKeys));
[7518]846 possibleKeys.add(0, gpxKey);
847 for (String key : possibleKeys) {
848 String value = p.get(key);
[9674]849 // Sanity checks
850 if (value != null && (!GpxConstants.PT_FIX.equals(gpxKey) || GpxConstants.FIX_VALUES.contains(value))) {
851 wpt.put(gpxKey, value);
852 break;
[7518]853 }
854 }
855 }
856
[7414]857 /**
858 * Converts OSM data behind this layer to GPX data.
859 * @return GPX data
860 */
[1078]861 public GpxData toGpxData() {
[1646]862 return toGpxData(data, getAssociatedFile());
[1078]863 }
[628]864
[7414]865 /**
866 * Action that converts this OSM layer to a GPX layer.
867 */
[1078]868 public class ConvertToGpxLayerAction extends AbstractAction {
[7414]869 /**
870 * Constructs a new {@code ConvertToGpxLayerAction}.
871 */
[1078]872 public ConvertToGpxLayerAction() {
873 super(tr("Convert to GPX layer"), ImageProvider.get("converttogpx"));
[4701]874 putValue("help", ht("/Action/ConvertToGpxLayer"));
[1078]875 }
[8510]876
[6084]877 @Override
[1078]878 public void actionPerformed(ActionEvent e) {
[9674]879 final GpxData gpxData = toGpxData();
880 final GpxLayer gpxLayer = new GpxLayer(gpxData, tr("Converted from: {0}", getName()));
[9481]881 if (getAssociatedFile() != null) {
[10300]882 String filename = getAssociatedFile().getName().replaceAll(Pattern.quote(".gpx.osm") + '$', "") + ".gpx";
[9481]883 gpxLayer.setAssociatedFile(new File(getAssociatedFile().getParentFile(), filename));
884 }
[10436]885 Main.getLayerManager().addLayer(gpxLayer);
[9674]886 if (Main.pref.getBoolean("marker.makeautomarkers", true) && !gpxData.waypoints.isEmpty()) {
[10436]887 Main.getLayerManager().addLayer(new MarkerLayer(gpxData, tr("Converted from: {0}", getName()), null, gpxLayer));
[8805]888 }
[10436]889 Main.getLayerManager().removeLayer(OsmDataLayer.this);
[1078]890 }
891 }
892
[7414]893 /**
894 * Determines if this layer contains data at the given coordinate.
895 * @param coor the coordinate
896 * @return {@code true} if data sources bounding boxes contain {@code coor}
897 */
[1523]898 public boolean containsPoint(LatLon coor) {
[628]899 // we'll assume that if this has no data sources
900 // that it also has no borders
[11627]901 if (this.data.getDataSources().isEmpty())
[628]902 return true;
903
[9674]904 boolean layerBoundsPoint = false;
[11627]905 for (DataSource src : this.data.getDataSources()) {
[628]906 if (src.bounds.contains(coor)) {
[9674]907 layerBoundsPoint = true;
[628]908 break;
909 }
910 }
[9674]911 return layerBoundsPoint;
[628]912 }
[1750]913
914 /**
[7414]915 * Replies the set of conflicts currently managed in this layer.
[1823]916 *
[1750]917 * @return the set of conflicts currently managed in this layer
918 */
919 public ConflictCollection getConflicts() {
920 return conflicts;
921 }
[2025]922
[7358]923 @Override
[9751]924 public boolean isUploadable() {
[11709]925 return data.getUploadPolicy() != UploadPolicy.BLOCKED;
[9751]926 }
927
928 @Override
[2025]929 public boolean requiresUploadToServer() {
[11709]930 return isUploadable() && requiresUploadToServer;
[2025]931 }
932
[7358]933 @Override
[2025]934 public boolean requiresSaveToFile() {
935 return getAssociatedFile() != null && requiresSaveToFile;
936 }
937
[5459]938 @Override
[2025]939 public void onPostLoadFromFile() {
940 setRequiresSaveToFile(false);
[7358]941 setRequiresUploadToServer(isModified());
[10467]942 invalidate();
[2025]943 }
944
[7414]945 /**
946 * Actions run after data has been downloaded to this layer.
947 */
[2434]948 public void onPostDownloadFromServer() {
949 setRequiresSaveToFile(true);
[7358]950 setRequiresUploadToServer(isModified());
[10467]951 invalidate();
[2434]952 }
953
[3116]954 @Override
[2025]955 public void onPostSaveToFile() {
956 setRequiresSaveToFile(false);
[7358]957 setRequiresUploadToServer(isModified());
[2025]958 }
959
[7358]960 @Override
[2025]961 public void onPostUploadToServer() {
[7358]962 setRequiresUploadToServer(isModified());
[2025]963 // keep requiresSaveToDisk unchanged
964 }
[2500]965
966 private class ConsistencyTestAction extends AbstractAction {
967
[8836]968 ConsistencyTestAction() {
[2500]969 super(tr("Dataset consistency test"));
970 }
971
[6084]972 @Override
[2500]973 public void actionPerformed(ActionEvent e) {
974 String result = DatasetConsistencyTest.runTests(data);
[8394]975 if (result.isEmpty()) {
[2500]976 JOptionPane.showMessageDialog(Main.parent, tr("No problems found"));
977 } else {
978 JPanel p = new JPanel(new GridBagLayout());
979 p.add(new JLabel(tr("Following problems found:")), GBC.eol());
[5886]980 JosmTextArea info = new JosmTextArea(result, 20, 60);
[2500]981 info.setCaretPosition(0);
982 info.setEditable(false);
983 p.add(new JScrollPane(info), GBC.eop());
984
985 JOptionPane.showMessageDialog(Main.parent, p, tr("Warning"), JOptionPane.WARNING_MESSAGE);
986 }
987 }
988 }
[3116]989
[3245]990 @Override
[12028]991 public synchronized void destroy() {
[10959]992 super.destroy();
[12152]993 data.removeSelectionListener(this);
[12014]994 data.removeHighlightUpdateListener(this);
[3245]995 }
996
[6084]997 @Override
[3116]998 public void processDatasetEvent(AbstractDatasetChangedEvent event) {
[10031]999 invalidate();
[3225]1000 setRequiresSaveToFile(true);
1001 setRequiresUploadToServer(true);
[3116]1002 }
1003
[6084]1004 @Override
[12152]1005 public void selectionChanged(SelectionChangeEvent event) {
[10031]1006 invalidate();
1007 }
1008
1009 @Override
[4126]1010 public void projectionChanged(Projection oldValue, Projection newValue) {
[7414]1011 // No reprojection required. The dataset itself is registered as projection
1012 // change listener and already got notified.
[4126]1013 }
[5025]1014
[11709]1015 /**
1016 * Determines if upload is being discouraged.
1017 * (i.e. this dataset contains private data which should not be uploaded)
1018 * @return {@code true} if upload is being discouraged, {@code false} otherwise
1019 */
[7358]1020 @Override
[5025]1021 public final boolean isUploadDiscouraged() {
[11709]1022 return data.getUploadPolicy() == UploadPolicy.DISCOURAGED;
[5025]1023 }
1024
[7414]1025 /**
1026 * Sets the "discouraged upload" flag.
[8540]1027 * @param uploadDiscouraged {@code true} if upload of data managed by this layer is discouraged.
1028 * This feature allows to use "private" data layers.
[7414]1029 */
[5025]1030 public final void setUploadDiscouraged(boolean uploadDiscouraged) {
[11709]1031 if (data.getUploadPolicy() != UploadPolicy.BLOCKED &&
[11710]1032 (uploadDiscouraged ^ isUploadDiscouraged())) {
[11709]1033 data.setUploadPolicy(uploadDiscouraged ? UploadPolicy.DISCOURAGED : UploadPolicy.NORMAL);
[5519]1034 for (LayerStateChangeListener l : layerStateChangeListeners) {
1035 l.uploadDiscouragedChanged(this, uploadDiscouraged);
1036 }
1037 }
[5025]1038 }
[5459]1039
1040 @Override
[7358]1041 public final boolean isModified() {
1042 return data.isModified();
1043 }
1044
1045 @Override
[5459]1046 public boolean isSavable() {
1047 return true; // With OsmExporter
1048 }
1049
1050 @Override
1051 public boolean checkSaveConditions() {
[10611]1052 if (isDataSetEmpty() && 1 != GuiHelper.runInEDTAndWaitAndReturn(() -> {
1053 if (GraphicsEnvironment.isHeadless()) {
1054 return 2;
[7204]1055 }
[12279]1056 return new ExtendedDialog(
[10611]1057 Main.parent,
1058 tr("Empty document"),
[12279]1059 tr("Save anyway"), tr("Cancel"))
1060 .setContent(tr("The document contains no data."))
1061 .setButtonIcons("save", "cancel")
1062 .showDialog().getValue();
[9674]1063 })) {
1064 return false;
[5459]1065 }
1066
[9674]1067 ConflictCollection conflictsCol = getConflicts();
[12281]1068 return conflictsCol == null || conflictsCol.isEmpty() || 1 == GuiHelper.runInEDTAndWaitAndReturn(() ->
1069 new ExtendedDialog(
[10611]1070 Main.parent,
1071 /* I18N: Display title of the window showing conflicts */
1072 tr("Conflicts"),
[12279]1073 tr("Reject Conflicts and Save"), tr("Cancel"))
1074 .setContent(
1075 tr("There are unresolved conflicts. Conflicts will not be saved and handled as if you rejected all. Continue?"))
1076 .setButtonIcons("save", "cancel")
[12281]1077 .showDialog().getValue()
1078 );
[5459]1079 }
[6070]1080
[5459]1081 /**
1082 * Check the data set if it would be empty on save. It is empty, if it contains
1083 * no objects (after all objects that are created and deleted without being
1084 * transferred to the server have been removed).
1085 *
1086 * @return <code>true</code>, if a save result in an empty data set.
1087 */
1088 private boolean isDataSetEmpty() {
1089 if (data != null) {
[8513]1090 for (OsmPrimitive osm : data.allNonDeletedPrimitives()) {
[5459]1091 if (!osm.isDeleted() || !osm.isNewOrUndeleted())
1092 return false;
[8513]1093 }
[5459]1094 }
1095 return true;
1096 }
1097
1098 @Override
1099 public File createAndOpenSaveFileChooser() {
[9721]1100 String extension = PROPERTY_SAVE_EXTENSION.get();
[9670]1101 File file = getAssociatedFile();
1102 if (file == null && isRenamed()) {
[11649]1103 StringBuilder filename = new StringBuilder(Main.pref.get("lastDirectory")).append('/').append(getName());
1104 if (!OsmImporter.FILE_FILTER.acceptName(filename.toString())) {
1105 filename.append('.').append(extension);
1106 }
1107 file = new File(filename.toString());
[9670]1108 }
1109 return new FileChooserManager()
1110 .title(tr("Save OSM file"))
1111 .extension(extension)
1112 .file(file)
[9714]1113 .allTypes(true)
[9670]1114 .getFileForSave();
[5459]1115 }
[7358]1116
1117 @Override
1118 public AbstractIOTask createUploadTask(final ProgressMonitor monitor) {
1119 UploadDialog dialog = UploadDialog.getUploadDialog();
1120 return new UploadLayerTask(
1121 dialog.getUploadStrategySpecification(),
1122 this,
1123 monitor,
1124 dialog.getChangeset());
1125 }
1126
1127 @Override
1128 public AbstractUploadDialog getUploadDialog() {
1129 UploadDialog dialog = UploadDialog.getUploadDialog();
1130 dialog.setUploadedPrimitives(new APIDataSet(data));
1131 return dialog;
1132 }
[10371]1133
1134 @Override
1135 public ProjectionBounds getViewProjectionBounds() {
1136 BoundingXYVisitor v = new BoundingXYVisitor();
1137 v.visit(data.getDataSourceBoundingBox());
1138 if (!v.hasExtend()) {
1139 v.computeBoundingBox(data.getNodes());
1140 }
1141 return v.getBounds();
1142 }
[12014]1143
1144 @Override
1145 public void highlightUpdated(HighlightUpdateEvent e) {
1146 invalidate();
1147 }
[444]1148}
Note: See TracBrowser for help on using the repository browser.