001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.geom.Area; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collection; 010import java.util.Collections; 011import java.util.HashMap; 012import java.util.HashSet; 013import java.util.Iterator; 014import java.util.LinkedHashSet; 015import java.util.LinkedList; 016import java.util.List; 017import java.util.Map; 018import java.util.Set; 019import java.util.concurrent.CopyOnWriteArrayList; 020import java.util.concurrent.locks.Lock; 021import java.util.concurrent.locks.ReadWriteLock; 022import java.util.concurrent.locks.ReentrantReadWriteLock; 023 024import org.openstreetmap.josm.Main; 025import org.openstreetmap.josm.data.Bounds; 026import org.openstreetmap.josm.data.SelectionChangedListener; 027import org.openstreetmap.josm.data.coor.EastNorth; 028import org.openstreetmap.josm.data.coor.LatLon; 029import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 030import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent; 031import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 032import org.openstreetmap.josm.data.osm.event.DataSetListener; 033import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 034import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 035import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 036import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 037import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 038import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 039import org.openstreetmap.josm.data.projection.Projection; 040import org.openstreetmap.josm.data.projection.ProjectionChangeListener; 041import org.openstreetmap.josm.gui.progress.ProgressMonitor; 042import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 043import org.openstreetmap.josm.tools.FilteredCollection; 044import org.openstreetmap.josm.tools.Predicate; 045import org.openstreetmap.josm.tools.SubclassFilteredCollection; 046import org.openstreetmap.josm.tools.Utils; 047 048/** 049 * DataSet is the data behind the application. It can consists of only a few points up to the whole 050 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc. 051 * 052 * Note that DataSet is not an osm-primitive and so has no key association but a few members to 053 * store some information. 054 * 055 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never 056 * lead to data corruption or ConccurentModificationException. However when for example one thread 057 * removes primitive and other thread try to add another primitive referring to the removed primitive, 058 * DataIntegrityException will occur. 059 * 060 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that 061 * Dataset will not change. Sample usage: 062 * <code> 063 * ds.getReadLock().lock(); 064 * try { 065 * // .. do something with dataset 066 * } finally { 067 * ds.getReadLock().unlock(); 068 * } 069 * </code> 070 * 071 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't 072 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance 073 * reasons - GUI can be updated after all changes are done. 074 * Sample usage: 075 * <code> 076 * ds.beginUpdate() 077 * try { 078 * // .. do modifications 079 * } finally { 080 * ds.endUpdate(); 081 * } 082 * </code> 083 * 084 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked 085 * automatically. 086 * 087 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for 088 * sample ticket 089 * 090 * @author imi 091 */ 092public final class DataSet implements Cloneable, ProjectionChangeListener { 093 094 /** 095 * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent) 096 */ 097 private static final int MAX_SINGLE_EVENTS = 30; 098 099 /** 100 * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent) 101 */ 102 private static final int MAX_EVENTS = 1000; 103 104 private Storage<OsmPrimitive> allPrimitives = new Storage<>(new Storage.PrimitiveIdHash(), true); 105 private Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new Storage.PrimitiveIdHash()); 106 private CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<>(); 107 108 // provide means to highlight map elements that are not osm primitives 109 private Collection<WaySegment> highlightedVirtualNodes = new LinkedList<>(); 110 private Collection<WaySegment> highlightedWaySegments = new LinkedList<>(); 111 112 // Number of open calls to beginUpdate 113 private int updateCount; 114 // Events that occurred while dataset was locked but should be fired after write lock is released 115 private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<>(); 116 117 private int highlightUpdateCount; 118 119 private boolean uploadDiscouraged = false; 120 121 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 122 private final Object selectionLock = new Object(); 123 124 public DataSet() { 125 /* 126 * Transparently register as projection change lister. No need to explicitly remove the 127 * the listener, projection change listeners are managed as WeakReferences. 128 */ 129 Main.addProjectionChangeListener(this); 130 } 131 132 public Lock getReadLock() { 133 return lock.readLock(); 134 } 135 136 /** 137 * This method can be used to detect changes in highlight state of primitives. If highlighting was changed 138 * then the method will return different number. 139 * @return the current highlight counter 140 */ 141 public int getHighlightUpdateCount() { 142 return highlightUpdateCount; 143 } 144 145 /** 146 * History of selections - shared by plugins and SelectionListDialog 147 */ 148 private final LinkedList<Collection<? extends OsmPrimitive>> selectionHistory = new LinkedList<>(); 149 150 /** 151 * Replies the history of JOSM selections 152 * 153 * @return list of history entries 154 */ 155 public LinkedList<Collection<? extends OsmPrimitive>> getSelectionHistory() { 156 return selectionHistory; 157 } 158 159 /** 160 * Clears selection history list 161 */ 162 public void clearSelectionHistory() { 163 selectionHistory.clear(); 164 } 165 166 /** 167 * Maintain a list of used tags for autocompletion 168 */ 169 private AutoCompletionManager autocomplete; 170 171 public AutoCompletionManager getAutoCompletionManager() { 172 if (autocomplete == null) { 173 autocomplete = new AutoCompletionManager(this); 174 addDataSetListener(autocomplete); 175 } 176 return autocomplete; 177 } 178 179 /** 180 * The API version that created this data set, if any. 181 */ 182 private String version; 183 184 /** 185 * Replies the API version this dataset was created from. May be null. 186 * 187 * @return the API version this dataset was created from. May be null. 188 */ 189 public String getVersion() { 190 return version; 191 } 192 193 /** 194 * Sets the API version this dataset was created from. 195 * 196 * @param version the API version, i.e. "0.6" 197 */ 198 public void setVersion(String version) { 199 this.version = version; 200 } 201 202 public final boolean isUploadDiscouraged() { 203 return uploadDiscouraged; 204 } 205 206 public final void setUploadDiscouraged(boolean uploadDiscouraged) { 207 this.uploadDiscouraged = uploadDiscouraged; 208 } 209 210 /* 211 * Holding bin for changeset tag information, to be applied when or if this is ever uploaded. 212 */ 213 private Map<String, String> changeSetTags = new HashMap<>(); 214 215 public Map<String, String> getChangeSetTags() { 216 return changeSetTags; 217 } 218 219 public void addChangeSetTag(String k, String v) { 220 this.changeSetTags.put(k,v); 221 } 222 223 /** 224 * All nodes goes here, even when included in other data (ways etc). This enables the instant 225 * conversion of the whole DataSet by iterating over this data structure. 226 */ 227 private QuadBuckets<Node> nodes = new QuadBuckets<>(); 228 229 private <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<OsmPrimitive> predicate) { 230 return new SubclassFilteredCollection<>(allPrimitives, predicate); 231 } 232 233 /** 234 * Replies an unmodifiable collection of nodes in this dataset 235 * 236 * @return an unmodifiable collection of nodes in this dataset 237 */ 238 public Collection<Node> getNodes() { 239 return getPrimitives(OsmPrimitive.nodePredicate); 240 } 241 242 public List<Node> searchNodes(BBox bbox) { 243 lock.readLock().lock(); 244 try { 245 return nodes.search(bbox); 246 } finally { 247 lock.readLock().unlock(); 248 } 249 } 250 251 /** 252 * All ways (Streets etc.) in the DataSet. 253 * 254 * The way nodes are stored only in the way list. 255 */ 256 private QuadBuckets<Way> ways = new QuadBuckets<>(); 257 258 /** 259 * Replies an unmodifiable collection of ways in this dataset 260 * 261 * @return an unmodifiable collection of ways in this dataset 262 */ 263 public Collection<Way> getWays() { 264 return getPrimitives(OsmPrimitive.wayPredicate); 265 } 266 267 public List<Way> searchWays(BBox bbox) { 268 lock.readLock().lock(); 269 try { 270 return ways.search(bbox); 271 } finally { 272 lock.readLock().unlock(); 273 } 274 } 275 276 /** 277 * All relations/relationships 278 */ 279 private Collection<Relation> relations = new ArrayList<>(); 280 281 /** 282 * Replies an unmodifiable collection of relations in this dataset 283 * 284 * @return an unmodifiable collection of relations in this dataset 285 */ 286 public Collection<Relation> getRelations() { 287 return getPrimitives(OsmPrimitive.relationPredicate); 288 } 289 290 public List<Relation> searchRelations(BBox bbox) { 291 lock.readLock().lock(); 292 try { 293 // QuadBuckets might be useful here (don't forget to do reindexing after some of rm is changed) 294 List<Relation> result = new ArrayList<>(); 295 for (Relation r: relations) { 296 if (r.getBBox().intersects(bbox)) { 297 result.add(r); 298 } 299 } 300 return result; 301 } finally { 302 lock.readLock().unlock(); 303 } 304 } 305 306 /** 307 * All data sources of this DataSet. 308 */ 309 public final Collection<DataSource> dataSources = new LinkedList<>(); 310 311 /** 312 * @return A collection containing all primitives of the dataset. Data are not ordered 313 */ 314 public Collection<OsmPrimitive> allPrimitives() { 315 return getPrimitives(OsmPrimitive.allPredicate); 316 } 317 318 /** 319 * @return A collection containing all not-deleted primitives (except keys). 320 */ 321 public Collection<OsmPrimitive> allNonDeletedPrimitives() { 322 return getPrimitives(OsmPrimitive.nonDeletedPredicate); 323 } 324 325 public Collection<OsmPrimitive> allNonDeletedCompletePrimitives() { 326 return getPrimitives(OsmPrimitive.nonDeletedCompletePredicate); 327 } 328 329 public Collection<OsmPrimitive> allNonDeletedPhysicalPrimitives() { 330 return getPrimitives(OsmPrimitive.nonDeletedPhysicalPredicate); 331 } 332 333 public Collection<OsmPrimitive> allModifiedPrimitives() { 334 return getPrimitives(OsmPrimitive.modifiedPredicate); 335 } 336 337 /** 338 * Adds a primitive to the dataset 339 * 340 * @param primitive the primitive. 341 */ 342 public void addPrimitive(OsmPrimitive primitive) { 343 beginUpdate(); 344 try { 345 if (getPrimitiveById(primitive) != null) 346 throw new DataIntegrityProblemException( 347 tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString())); 348 349 primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reinexRelation to work properly) 350 boolean success = false; 351 if (primitive instanceof Node) { 352 success = nodes.add((Node) primitive); 353 } else if (primitive instanceof Way) { 354 success = ways.add((Way) primitive); 355 } else if (primitive instanceof Relation) { 356 success = relations.add((Relation) primitive); 357 } 358 if (!success) 359 throw new RuntimeException("failed to add primitive: "+primitive); 360 allPrimitives.add(primitive); 361 primitive.setDataset(this); 362 firePrimitivesAdded(Collections.singletonList(primitive), false); 363 } finally { 364 endUpdate(); 365 } 366 } 367 368 /** 369 * Removes a primitive from the dataset. This method only removes the 370 * primitive form the respective collection of primitives managed 371 * by this dataset, i.e. from {@link #nodes}, {@link #ways}, or 372 * {@link #relations}. References from other primitives to this 373 * primitive are left unchanged. 374 * 375 * @param primitiveId the id of the primitive 376 */ 377 public void removePrimitive(PrimitiveId primitiveId) { 378 beginUpdate(); 379 try { 380 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId); 381 if (primitive == null) 382 return; 383 boolean success = false; 384 if (primitive instanceof Node) { 385 success = nodes.remove(primitive); 386 } else if (primitive instanceof Way) { 387 success = ways.remove(primitive); 388 } else if (primitive instanceof Relation) { 389 success = relations.remove(primitive); 390 } 391 if (!success) 392 throw new RuntimeException("failed to remove primitive: "+primitive); 393 synchronized (selectionLock) { 394 selectedPrimitives.remove(primitive); 395 selectionSnapshot = null; 396 } 397 allPrimitives.remove(primitive); 398 primitive.setDataset(null); 399 firePrimitivesRemoved(Collections.singletonList(primitive), false); 400 } finally { 401 endUpdate(); 402 } 403 } 404 405 /*--------------------------------------------------- 406 * SELECTION HANDLING 407 *---------------------------------------------------*/ 408 409 /** 410 * A list of listeners to selection changed events. The list is static, as listeners register 411 * themselves for any dataset selection changes that occur, regardless of the current active 412 * dataset. (However, the selection does only change in the active layer) 413 */ 414 private static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<>(); 415 416 public static void addSelectionListener(SelectionChangedListener listener) { 417 ((CopyOnWriteArrayList<SelectionChangedListener>)selListeners).addIfAbsent(listener); 418 } 419 420 public static void removeSelectionListener(SelectionChangedListener listener) { 421 selListeners.remove(listener); 422 } 423 424 /** 425 * Notifies all registered {@link SelectionChangedListener} about the current selection in 426 * this dataset. 427 * 428 */ 429 public void fireSelectionChanged(){ 430 Collection<? extends OsmPrimitive> currentSelection = getAllSelected(); 431 for (SelectionChangedListener l : selListeners) { 432 l.selectionChanged(currentSelection); 433 } 434 } 435 436 private Set<OsmPrimitive> selectedPrimitives = new LinkedHashSet<>(); 437 private Collection<OsmPrimitive> selectionSnapshot; 438 439 public Collection<OsmPrimitive> getSelectedNodesAndWays() { 440 return new FilteredCollection<>(getSelected(), new Predicate<OsmPrimitive>() { 441 @Override 442 public boolean evaluate(OsmPrimitive primitive) { 443 return primitive instanceof Node || primitive instanceof Way; 444 } 445 }); 446 } 447 448 /** 449 * returns an unmodifiable collection of *WaySegments* whose virtual 450 * nodes should be highlighted. WaySegments are used to avoid having 451 * to create a VirtualNode class that wouldn't have much purpose otherwise. 452 * 453 * @return unmodifiable collection of WaySegments 454 */ 455 public Collection<WaySegment> getHighlightedVirtualNodes() { 456 return Collections.unmodifiableCollection(highlightedVirtualNodes); 457 } 458 459 /** 460 * returns an unmodifiable collection of WaySegments that should be 461 * highlighted. 462 * 463 * @return unmodifiable collection of WaySegments 464 */ 465 public Collection<WaySegment> getHighlightedWaySegments() { 466 return Collections.unmodifiableCollection(highlightedWaySegments); 467 } 468 469 /** 470 * Replies an unmodifiable collection of primitives currently selected 471 * in this dataset, except deleted ones. May be empty, but not null. 472 * 473 * @return unmodifiable collection of primitives 474 */ 475 public Collection<OsmPrimitive> getSelected() { 476 return new SubclassFilteredCollection<>(getAllSelected(), OsmPrimitive.nonDeletedPredicate); 477 } 478 479 /** 480 * Replies an unmodifiable collection of primitives currently selected 481 * in this dataset, including deleted ones. May be empty, but not null. 482 * 483 * @return unmodifiable collection of primitives 484 */ 485 public Collection<OsmPrimitive> getAllSelected() { 486 Collection<OsmPrimitive> currentList; 487 synchronized (selectionLock) { 488 if (selectionSnapshot == null) { 489 selectionSnapshot = Collections.unmodifiableList(new ArrayList<>(selectedPrimitives)); 490 } 491 currentList = selectionSnapshot; 492 } 493 return currentList; 494 } 495 496 /** 497 * Return selected nodes. 498 */ 499 public Collection<Node> getSelectedNodes() { 500 return new SubclassFilteredCollection<>(getSelected(), OsmPrimitive.nodePredicate); 501 } 502 503 /** 504 * Return selected ways. 505 */ 506 public Collection<Way> getSelectedWays() { 507 return new SubclassFilteredCollection<>(getSelected(), OsmPrimitive.wayPredicate); 508 } 509 510 /** 511 * Return selected relations. 512 */ 513 public Collection<Relation> getSelectedRelations() { 514 return new SubclassFilteredCollection<>(getSelected(), OsmPrimitive.relationPredicate); 515 } 516 517 /** 518 * @return whether the selection is empty or not 519 */ 520 public boolean selectionEmpty() { 521 return selectedPrimitives.isEmpty(); 522 } 523 524 public boolean isSelected(OsmPrimitive osm) { 525 return selectedPrimitives.contains(osm); 526 } 527 528 public void toggleSelected(Collection<? extends PrimitiveId> osm) { 529 boolean changed = false; 530 synchronized (selectionLock) { 531 for (PrimitiveId o : osm) { 532 changed = changed | this.__toggleSelected(o); 533 } 534 if (changed) { 535 selectionSnapshot = null; 536 } 537 } 538 if (changed) { 539 fireSelectionChanged(); 540 } 541 } 542 public void toggleSelected(PrimitiveId... osm) { 543 toggleSelected(Arrays.asList(osm)); 544 } 545 private boolean __toggleSelected(PrimitiveId primitiveId) { 546 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId); 547 if (primitive == null) 548 return false; 549 if (!selectedPrimitives.remove(primitive)) { 550 selectedPrimitives.add(primitive); 551 } 552 selectionSnapshot = null; 553 return true; 554 } 555 556 /** 557 * set what virtual nodes should be highlighted. Requires a Collection of 558 * *WaySegments* to avoid a VirtualNode class that wouldn't have much use 559 * otherwise. 560 * @param waySegments Collection of way segments 561 */ 562 public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) { 563 if(highlightedVirtualNodes.isEmpty() && waySegments.isEmpty()) 564 return; 565 566 highlightedVirtualNodes = waySegments; 567 // can't use fireHighlightingChanged because it requires an OsmPrimitive 568 highlightUpdateCount++; 569 } 570 571 /** 572 * set what virtual ways should be highlighted. 573 * @param waySegments Collection of way segments 574 */ 575 public void setHighlightedWaySegments(Collection<WaySegment> waySegments) { 576 if(highlightedWaySegments.isEmpty() && waySegments.isEmpty()) 577 return; 578 579 highlightedWaySegments = waySegments; 580 // can't use fireHighlightingChanged because it requires an OsmPrimitive 581 highlightUpdateCount++; 582 } 583 584 /** 585 * Sets the current selection to the primitives in <code>selection</code>. 586 * Notifies all {@link SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true. 587 * 588 * @param selection the selection 589 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise 590 */ 591 public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) { 592 boolean changed; 593 synchronized (selectionLock) { 594 LinkedHashSet<OsmPrimitive> oldSelection = new LinkedHashSet<>(selectedPrimitives); 595 selectedPrimitives = new LinkedHashSet<>(); 596 addSelected(selection, false); 597 changed = !oldSelection.equals(selectedPrimitives); 598 if (changed) { 599 selectionSnapshot = null; 600 } 601 } 602 603 if (changed && fireSelectionChangeEvent) { 604 // If selection is not empty then event was already fired in addSelecteds 605 fireSelectionChanged(); 606 } 607 } 608 609 /** 610 * Sets the current selection to the primitives in <code>selection</code> 611 * and notifies all {@link SelectionChangedListener}. 612 * 613 * @param selection the selection 614 */ 615 public void setSelected(Collection<? extends PrimitiveId> selection) { 616 setSelected(selection, true /* fire selection change event */); 617 } 618 619 public void setSelected(PrimitiveId... osm) { 620 if (osm.length == 1 && osm[0] == null) { 621 setSelected(); 622 return; 623 } 624 List<PrimitiveId> list = Arrays.asList(osm); 625 setSelected(list); 626 } 627 628 /** 629 * Adds the primitives in <code>selection</code> to the current selection 630 * and notifies all {@link SelectionChangedListener}. 631 * 632 * @param selection the selection 633 */ 634 public void addSelected(Collection<? extends PrimitiveId> selection) { 635 addSelected(selection, true /* fire selection change event */); 636 } 637 638 public void addSelected(PrimitiveId... osm) { 639 addSelected(Arrays.asList(osm)); 640 } 641 642 /** 643 * Adds the primitives in <code>selection</code> to the current selection. 644 * Notifies all {@link SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true. 645 * 646 * @param selection the selection 647 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise 648 * @return if the selection was changed in the process 649 */ 650 private boolean addSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) { 651 boolean changed = false; 652 synchronized (selectionLock) { 653 for (PrimitiveId id: selection) { 654 OsmPrimitive primitive = getPrimitiveByIdChecked(id); 655 if (primitive != null) { 656 changed = changed | selectedPrimitives.add(primitive); 657 } 658 } 659 if (changed) { 660 selectionSnapshot = null; 661 } 662 } 663 if (fireSelectionChangeEvent && changed) { 664 fireSelectionChanged(); 665 } 666 return changed; 667 } 668 669 /** 670 * clear all highlights of virtual nodes 671 */ 672 public void clearHighlightedVirtualNodes() { 673 setHighlightedVirtualNodes(new ArrayList<WaySegment>()); 674 } 675 676 /** 677 * clear all highlights of way segments 678 */ 679 public void clearHighlightedWaySegments() { 680 setHighlightedWaySegments(new ArrayList<WaySegment>()); 681 } 682 683 /** 684 * Remove the selection from every value in the collection. 685 * @param osm The collection of ids to remove the selection from. 686 */ 687 public void clearSelection(PrimitiveId... osm) { 688 clearSelection(Arrays.asList(osm)); 689 } 690 public void clearSelection(Collection<? extends PrimitiveId> list) { 691 boolean changed = false; 692 synchronized (selectionLock) { 693 for (PrimitiveId id:list) { 694 OsmPrimitive primitive = getPrimitiveById(id); 695 if (primitive != null) { 696 changed = changed | selectedPrimitives.remove(primitive); 697 } 698 } 699 if (changed) { 700 selectionSnapshot = null; 701 } 702 } 703 if (changed) { 704 fireSelectionChanged(); 705 } 706 } 707 public void clearSelection() { 708 if (!selectedPrimitives.isEmpty()) { 709 synchronized (selectionLock) { 710 selectedPrimitives.clear(); 711 selectionSnapshot = null; 712 } 713 fireSelectionChanged(); 714 } 715 } 716 717 @Override public DataSet clone() { 718 getReadLock().lock(); 719 try { 720 DataSet ds = new DataSet(); 721 HashMap<OsmPrimitive, OsmPrimitive> primMap = new HashMap<>(); 722 for (Node n : nodes) { 723 Node newNode = new Node(n); 724 primMap.put(n, newNode); 725 ds.addPrimitive(newNode); 726 } 727 for (Way w : ways) { 728 Way newWay = new Way(w); 729 primMap.put(w, newWay); 730 List<Node> newNodes = new ArrayList<>(); 731 for (Node n: w.getNodes()) { 732 newNodes.add((Node)primMap.get(n)); 733 } 734 newWay.setNodes(newNodes); 735 ds.addPrimitive(newWay); 736 } 737 // Because relations can have other relations as members we first clone all relations 738 // and then get the cloned members 739 for (Relation r : relations) { 740 Relation newRelation = new Relation(r, r.isNew()); 741 newRelation.setMembers(null); 742 primMap.put(r, newRelation); 743 ds.addPrimitive(newRelation); 744 } 745 for (Relation r : relations) { 746 Relation newRelation = (Relation)primMap.get(r); 747 List<RelationMember> newMembers = new ArrayList<>(); 748 for (RelationMember rm: r.getMembers()) { 749 newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember()))); 750 } 751 newRelation.setMembers(newMembers); 752 } 753 for (DataSource source : dataSources) { 754 ds.dataSources.add(new DataSource(source.bounds, source.origin)); 755 } 756 ds.version = version; 757 return ds; 758 } finally { 759 getReadLock().unlock(); 760 } 761 } 762 763 /** 764 * Returns the total area of downloaded data (the "yellow rectangles"). 765 * @return Area object encompassing downloaded data. 766 */ 767 public Area getDataSourceArea() { 768 if (dataSources.isEmpty()) return null; 769 Area a = new Area(); 770 for (DataSource source : dataSources) { 771 // create area from data bounds 772 a.add(new Area(source.bounds.asRect())); 773 } 774 return a; 775 } 776 777 /** 778 * returns a primitive with a given id from the data set. null, if no such primitive 779 * exists 780 * 781 * @param id uniqueId of the primitive. Might be < 0 for newly created primitives 782 * @param type the type of the primitive. Must not be null. 783 * @return the primitive 784 * @exception NullPointerException thrown, if type is null 785 */ 786 public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) { 787 return getPrimitiveById(new SimplePrimitiveId(id, type)); 788 } 789 790 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) { 791 return primitivesMap.get(primitiveId); 792 } 793 794 /** 795 * Show message and stack trace in log in case primitive is not found 796 * @param primitiveId 797 * @return Primitive by id. 798 */ 799 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) { 800 OsmPrimitive result = getPrimitiveById(primitiveId); 801 if (result == null) { 802 Main.warn(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this " 803 + "at {2}. This is not a critical error, it should be safe to continue in your work.", 804 primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Main.getJOSMWebsite())); 805 Main.error(new Exception()); 806 } 807 808 return result; 809 } 810 811 private void deleteWay(Way way) { 812 way.setNodes(null); 813 way.setDeleted(true); 814 } 815 816 /** 817 * Removes all references from ways in this dataset to a particular node. 818 * 819 * @param node the node 820 * @return The set of ways that have been modified 821 */ 822 public Set<Way> unlinkNodeFromWays(Node node) { 823 Set<Way> result = new HashSet<>(); 824 beginUpdate(); 825 try { 826 for (Way way: ways) { 827 List<Node> wayNodes = way.getNodes(); 828 if (wayNodes.remove(node)) { 829 if (wayNodes.size() < 2) { 830 deleteWay(way); 831 } else { 832 way.setNodes(wayNodes); 833 } 834 result.add(way); 835 } 836 } 837 } finally { 838 endUpdate(); 839 } 840 return result; 841 } 842 843 /** 844 * removes all references from relations in this dataset to this primitive 845 * 846 * @param primitive the primitive 847 * @return The set of relations that have been modified 848 */ 849 public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) { 850 Set<Relation> result = new HashSet<>(); 851 beginUpdate(); 852 try { 853 for (Relation relation : relations) { 854 List<RelationMember> members = relation.getMembers(); 855 856 Iterator<RelationMember> it = members.iterator(); 857 boolean removed = false; 858 while(it.hasNext()) { 859 RelationMember member = it.next(); 860 if (member.getMember().equals(primitive)) { 861 it.remove(); 862 removed = true; 863 } 864 } 865 866 if (removed) { 867 relation.setMembers(members); 868 result.add(relation); 869 } 870 } 871 } finally { 872 endUpdate(); 873 } 874 return result; 875 } 876 877 /** 878 * Removes all references from other primitives to the referenced primitive. 879 * 880 * @param referencedPrimitive the referenced primitive 881 * @return The set of primitives that have been modified 882 */ 883 public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) { 884 Set<OsmPrimitive> result = new HashSet<>(); 885 beginUpdate(); 886 try { 887 if (referencedPrimitive instanceof Node) { 888 result.addAll(unlinkNodeFromWays((Node)referencedPrimitive)); 889 } 890 result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive)); 891 } finally { 892 endUpdate(); 893 } 894 return result; 895 } 896 897 /** 898 * Replies true if there is at least one primitive in this dataset with 899 * {@link OsmPrimitive#isModified()} == <code>true</code>. 900 * 901 * @return true if there is at least one primitive in this dataset with 902 * {@link OsmPrimitive#isModified()} == <code>true</code>. 903 */ 904 public boolean isModified() { 905 for (OsmPrimitive p: allPrimitives) { 906 if (p.isModified()) 907 return true; 908 } 909 return false; 910 } 911 912 private void reindexNode(Node node, LatLon newCoor, EastNorth eastNorth) { 913 if (!nodes.remove(node)) 914 throw new RuntimeException("Reindexing node failed to remove"); 915 node.setCoorInternal(newCoor, eastNorth); 916 if (!nodes.add(node)) 917 throw new RuntimeException("Reindexing node failed to add"); 918 for (OsmPrimitive primitive: node.getReferrers()) { 919 if (primitive instanceof Way) { 920 reindexWay((Way)primitive); 921 } else { 922 reindexRelation((Relation) primitive); 923 } 924 } 925 } 926 927 private void reindexWay(Way way) { 928 BBox before = way.getBBox(); 929 if (!ways.remove(way)) 930 throw new RuntimeException("Reindexing way failed to remove"); 931 way.updatePosition(); 932 if (!ways.add(way)) 933 throw new RuntimeException("Reindexing way failed to add"); 934 if (!way.getBBox().equals(before)) { 935 for (OsmPrimitive primitive: way.getReferrers()) { 936 reindexRelation((Relation)primitive); 937 } 938 } 939 } 940 941 private void reindexRelation(Relation relation) { 942 BBox before = relation.getBBox(); 943 relation.updatePosition(); 944 if (!before.equals(relation.getBBox())) { 945 for (OsmPrimitive primitive: relation.getReferrers()) { 946 reindexRelation((Relation) primitive); 947 } 948 } 949 } 950 951 public void addDataSetListener(DataSetListener dsl) { 952 listeners.addIfAbsent(dsl); 953 } 954 955 public void removeDataSetListener(DataSetListener dsl) { 956 listeners.remove(dsl); 957 } 958 959 /** 960 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}. 961 * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes 962 * <br> 963 * Typical usecase should look like this: 964 * <pre> 965 * ds.beginUpdate(); 966 * try { 967 * ... 968 * } finally { 969 * ds.endUpdate(); 970 * } 971 * </pre> 972 */ 973 public void beginUpdate() { 974 lock.writeLock().lock(); 975 updateCount++; 976 } 977 978 /** 979 * @see DataSet#beginUpdate() 980 */ 981 public void endUpdate() { 982 if (updateCount > 0) { 983 updateCount--; 984 if (updateCount == 0) { 985 List<AbstractDatasetChangedEvent> eventsCopy = new ArrayList<>(cachedEvents); 986 cachedEvents.clear(); 987 lock.writeLock().unlock(); 988 989 if (!eventsCopy.isEmpty()) { 990 lock.readLock().lock(); 991 try { 992 if (eventsCopy.size() < MAX_SINGLE_EVENTS) { 993 for (AbstractDatasetChangedEvent event: eventsCopy) { 994 fireEventToListeners(event); 995 } 996 } else if (eventsCopy.size() == MAX_EVENTS) { 997 fireEventToListeners(new DataChangedEvent(this)); 998 } else { 999 fireEventToListeners(new DataChangedEvent(this, eventsCopy)); 1000 } 1001 } finally { 1002 lock.readLock().unlock(); 1003 } 1004 } 1005 } else { 1006 lock.writeLock().unlock(); 1007 } 1008 1009 } else 1010 throw new AssertionError("endUpdate called without beginUpdate"); 1011 } 1012 1013 private void fireEventToListeners(AbstractDatasetChangedEvent event) { 1014 for (DataSetListener listener: listeners) { 1015 event.fire(listener); 1016 } 1017 } 1018 1019 private void fireEvent(AbstractDatasetChangedEvent event) { 1020 if (updateCount == 0) 1021 throw new AssertionError("dataset events can be fired only when dataset is locked"); 1022 if (cachedEvents.size() < MAX_EVENTS) { 1023 cachedEvents.add(event); 1024 } 1025 } 1026 1027 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) { 1028 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete)); 1029 } 1030 1031 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) { 1032 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete)); 1033 } 1034 1035 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) { 1036 fireEvent(new TagsChangedEvent(this, prim, originalKeys)); 1037 } 1038 1039 void fireRelationMembersChanged(Relation r) { 1040 reindexRelation(r); 1041 fireEvent(new RelationMembersChangedEvent(this, r)); 1042 } 1043 1044 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) { 1045 reindexNode(node, newCoor, eastNorth); 1046 fireEvent(new NodeMovedEvent(this, node)); 1047 } 1048 1049 void fireWayNodesChanged(Way way) { 1050 reindexWay(way); 1051 fireEvent(new WayNodesChangedEvent(this, way)); 1052 } 1053 1054 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) { 1055 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId)); 1056 } 1057 1058 void fireHighlightingChanged(OsmPrimitive primitive) { 1059 highlightUpdateCount++; 1060 } 1061 1062 /** 1063 * Invalidates the internal cache of projected east/north coordinates. 1064 * 1065 * This method can be invoked after the globally configured projection method 1066 * changed. 1067 */ 1068 public void invalidateEastNorthCache() { 1069 if (Main.getProjection() == null) return; // sanity check 1070 try { 1071 beginUpdate(); 1072 for (Node n: Utils.filteredCollection(allPrimitives, Node.class)) { 1073 n.invalidateEastNorthCache(); 1074 } 1075 } finally { 1076 endUpdate(); 1077 } 1078 } 1079 1080 public void cleanupDeletedPrimitives() { 1081 beginUpdate(); 1082 try { 1083 if (cleanupDeleted(nodes.iterator()) 1084 | cleanupDeleted(ways.iterator()) 1085 | cleanupDeleted(relations.iterator())) { 1086 fireSelectionChanged(); 1087 } 1088 } finally { 1089 endUpdate(); 1090 } 1091 } 1092 1093 private boolean cleanupDeleted(Iterator<? extends OsmPrimitive> it) { 1094 boolean changed = false; 1095 synchronized (selectionLock) { 1096 while (it.hasNext()) { 1097 OsmPrimitive primitive = it.next(); 1098 if (primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())) { 1099 selectedPrimitives.remove(primitive); 1100 selectionSnapshot = null; 1101 allPrimitives.remove(primitive); 1102 primitive.setDataset(null); 1103 changed = true; 1104 it.remove(); 1105 } 1106 } 1107 if (changed) { 1108 selectionSnapshot = null; 1109 } 1110 } 1111 return changed; 1112 } 1113 1114 /** 1115 * Removes all primitives from the dataset and resets the currently selected primitives 1116 * to the empty collection. Also notifies selection change listeners if necessary. 1117 * 1118 */ 1119 public void clear() { 1120 beginUpdate(); 1121 try { 1122 clearSelection(); 1123 for (OsmPrimitive primitive:allPrimitives) { 1124 primitive.setDataset(null); 1125 } 1126 nodes.clear(); 1127 ways.clear(); 1128 relations.clear(); 1129 allPrimitives.clear(); 1130 } finally { 1131 endUpdate(); 1132 } 1133 } 1134 1135 /** 1136 * Marks all "invisible" objects as deleted. These objects should be always marked as 1137 * deleted when downloaded from the server. They can be undeleted later if necessary. 1138 * 1139 */ 1140 public void deleteInvisible() { 1141 for (OsmPrimitive primitive:allPrimitives) { 1142 if (!primitive.isVisible()) { 1143 primitive.setDeleted(true); 1144 } 1145 } 1146 } 1147 1148 /** 1149 * <p>Replies the list of data source bounds.</p> 1150 * 1151 * <p>Dataset maintains a list of data sources which have been merged into the 1152 * data set. Each of these sources can optionally declare a bounding box of the 1153 * data it supplied to the dataset.</p> 1154 * 1155 * <p>This method replies the list of defined (non {@code null}) bounding boxes.</p> 1156 * 1157 * @return the list of data source bounds. An empty list, if no non-null data source 1158 * bounds are defined. 1159 */ 1160 public List<Bounds> getDataSourceBounds() { 1161 List<Bounds> ret = new ArrayList<>(dataSources.size()); 1162 for (DataSource ds : dataSources) { 1163 if (ds.bounds != null) { 1164 ret.add(ds.bounds); 1165 } 1166 } 1167 return ret; 1168 } 1169 1170 /** 1171 * Moves all primitives and datasources from DataSet "from" to this DataSet 1172 * @param from The source DataSet 1173 */ 1174 public void mergeFrom(DataSet from) { 1175 mergeFrom(from, null); 1176 } 1177 1178 /** 1179 * Moves all primitives and datasources from DataSet "from" to this DataSet 1180 * @param from The source DataSet 1181 */ 1182 public void mergeFrom(DataSet from, ProgressMonitor progressMonitor) { 1183 if (from != null) { 1184 new DataSetMerger(this, from).merge(progressMonitor); 1185 dataSources.addAll(from.dataSources); 1186 from.dataSources.clear(); 1187 } 1188 } 1189 1190 /* --------------------------------------------------------------------------------- */ 1191 /* interface ProjectionChangeListner */ 1192 /* --------------------------------------------------------------------------------- */ 1193 @Override 1194 public void projectionChanged(Projection oldValue, Projection newValue) { 1195 invalidateEastNorthCache(); 1196 } 1197}