source: josm/trunk/src/org/openstreetmap/josm/data/osm/DataSet.java@ 12054

Last change on this file since 12054 was 12049, checked in by michael2402, 7 years ago

Move quad bucket store of dataset to separate class.

  • Property svn:eol-style set to native
File size: 46.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Area;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.Iterator;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.Objects;
17import java.util.Set;
18import java.util.concurrent.CopyOnWriteArrayList;
19import java.util.concurrent.locks.Lock;
20import java.util.concurrent.locks.ReadWriteLock;
21import java.util.concurrent.locks.ReentrantReadWriteLock;
22import java.util.function.Function;
23import java.util.function.Predicate;
24import java.util.stream.Collectors;
25import java.util.stream.Stream;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.Bounds;
29import org.openstreetmap.josm.data.Data;
30import org.openstreetmap.josm.data.DataSource;
31import org.openstreetmap.josm.data.ProjectionBounds;
32import org.openstreetmap.josm.data.SelectionChangedListener;
33import org.openstreetmap.josm.data.coor.EastNorth;
34import org.openstreetmap.josm.data.coor.LatLon;
35import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionAddEvent;
36import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionChangeEvent;
37import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionRemoveEvent;
38import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionReplaceEvent;
39import org.openstreetmap.josm.data.osm.DataSelectionListener.SelectionToggleEvent;
40import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
41import org.openstreetmap.josm.data.osm.event.ChangesetIdChangedEvent;
42import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
43import org.openstreetmap.josm.data.osm.event.DataSetListener;
44import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
45import org.openstreetmap.josm.data.osm.event.PrimitiveFlagsChangedEvent;
46import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
47import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
48import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
49import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
50import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
51import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
52import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
53import org.openstreetmap.josm.data.projection.Projection;
54import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
55import org.openstreetmap.josm.gui.progress.ProgressMonitor;
56import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
57import org.openstreetmap.josm.tools.ListenerList;
58import org.openstreetmap.josm.tools.SubclassFilteredCollection;
59import org.openstreetmap.josm.tools.Utils;
60
61/**
62 * DataSet is the data behind the application. It can consists of only a few points up to the whole
63 * osm database. DataSet's can be merged together, saved, (up/down/disk)loaded etc.
64 *
65 * Note that DataSet is not an osm-primitive and so has no key association but a few members to
66 * store some information.
67 *
68 * Dataset is threadsafe - accessing Dataset simultaneously from different threads should never
69 * lead to data corruption or ConcurrentModificationException. However when for example one thread
70 * removes primitive and other thread try to add another primitive referring to the removed primitive,
71 * DataIntegrityException will occur.
72 *
73 * To prevent such situations, read/write lock is provided. While read lock is used, it's guaranteed that
74 * Dataset will not change. Sample usage:
75 * <code>
76 * ds.getReadLock().lock();
77 * try {
78 * // .. do something with dataset
79 * } finally {
80 * ds.getReadLock().unlock();
81 * }
82 * </code>
83 *
84 * Write lock should be used in case of bulk operations. In addition to ensuring that other threads can't
85 * use dataset in the middle of modifications it also stops sending of dataset events. That's good for performance
86 * reasons - GUI can be updated after all changes are done.
87 * Sample usage:
88 * <code>
89 * ds.beginUpdate()
90 * try {
91 * // .. do modifications
92 * } finally {
93 * ds.endUpdate();
94 * }
95 * </code>
96 *
97 * Note that it is not necessary to call beginUpdate/endUpdate for every dataset modification - dataset will get locked
98 * automatically.
99 *
100 * Note that locks cannot be upgraded - if one threads use read lock and and then write lock, dead lock will occur - see #5814 for
101 * sample ticket
102 *
103 * @author imi
104 */
105public final class DataSet extends QuadBucketPrimitiveStore implements Data, ProjectionChangeListener {
106
107 /**
108 * Upload policy.
109 *
110 * Determines if upload to the OSM server is intended, discouraged, or
111 * disabled / blocked.
112 */
113 public enum UploadPolicy {
114 /**
115 * Normal dataset, upload intended.
116 */
117 NORMAL("true"),
118 /**
119 * Upload discouraged, for example when using or distributing a private dataset.
120 */
121 DISCOURAGED("false"),
122 /**
123 * Upload blocked.
124 * Upload options completely disabled. Intended for special cases
125 * where a warning dialog is not enough, see #12731.
126 *
127 * For the user, it shouldn't be too easy to disable this flag.
128 */
129 BLOCKED("never");
130
131 final String xmlFlag;
132
133 UploadPolicy(String xmlFlag) {
134 this.xmlFlag = xmlFlag;
135 }
136
137 /**
138 * Get the corresponding value of the <code>upload='...'</code> XML-attribute
139 * in the .osm file.
140 * @return value of the <code>upload</code> attribute
141 */
142 public String getXmlFlag() {
143 return xmlFlag;
144 }
145 }
146
147 /**
148 * Maximum number of events that can be fired between beginUpdate/endUpdate to be send as single events (ie without DatasetChangedEvent)
149 */
150 private static final int MAX_SINGLE_EVENTS = 30;
151
152 /**
153 * Maximum number of events to kept between beginUpdate/endUpdate. When more events are created, that simple DatasetChangedEvent is sent)
154 */
155 private static final int MAX_EVENTS = 1000;
156
157 private final Storage<OsmPrimitive> allPrimitives = new Storage<>(new Storage.PrimitiveIdHash(), true);
158 private final Map<PrimitiveId, OsmPrimitive> primitivesMap = allPrimitives.foreignKey(new Storage.PrimitiveIdHash());
159 private final CopyOnWriteArrayList<DataSetListener> listeners = new CopyOnWriteArrayList<>();
160
161 // provide means to highlight map elements that are not osm primitives
162 private Collection<WaySegment> highlightedVirtualNodes = new LinkedList<>();
163 private Collection<WaySegment> highlightedWaySegments = new LinkedList<>();
164 private final ListenerList<HighlightUpdateListener> highlightUpdateListeners = ListenerList.create();
165
166 // Number of open calls to beginUpdate
167 private int updateCount;
168 // Events that occurred while dataset was locked but should be fired after write lock is released
169 private final List<AbstractDatasetChangedEvent> cachedEvents = new ArrayList<>();
170
171 private UploadPolicy uploadPolicy;
172
173 private final ReadWriteLock lock = new ReentrantReadWriteLock();
174
175 /**
176 * The mutex lock that is used to synchronize selection changes.
177 */
178 private final Object selectionLock = new Object();
179 /**
180 * The current selected primitives. This is always a unmodifiable set.
181 */
182 private Set<OsmPrimitive> currentSelectedPrimitives = Collections.emptySet();
183
184 /**
185 * A list of listeners that listen to selection changes on this layer.
186 */
187 private final ListenerList<DataSelectionListener> selectionListeners = ListenerList.create();
188
189 private Area cachedDataSourceArea;
190 private List<Bounds> cachedDataSourceBounds;
191
192 /**
193 * All data sources of this DataSet.
194 */
195 private final Collection<DataSource> dataSources = new LinkedList<>();
196
197 /**
198 * Constructs a new {@code DataSet}.
199 */
200 public DataSet() {
201 // Transparently register as projection change listener. No need to explicitly remove
202 // the listener, projection change listeners are managed as WeakReferences.
203 Main.addProjectionChangeListener(this);
204 addSelectionListener((DataSelectionListener) e -> fireDreprecatedSelectionChange(e.getSelection()));
205 }
206
207 /**
208 * Creates a new {@link DataSet}.
209 * @param copyFrom An other {@link DataSet} to copy the contents of this dataset from.
210 * @since 10346
211 */
212 public DataSet(DataSet copyFrom) {
213 this();
214 copyFrom.getReadLock().lock();
215 try {
216 Map<OsmPrimitive, OsmPrimitive> primMap = new HashMap<>();
217 for (Node n : copyFrom.getNodes()) {
218 Node newNode = new Node(n);
219 primMap.put(n, newNode);
220 addPrimitive(newNode);
221 }
222 for (Way w : copyFrom.getWays()) {
223 Way newWay = new Way(w);
224 primMap.put(w, newWay);
225 List<Node> newNodes = new ArrayList<>();
226 for (Node n: w.getNodes()) {
227 newNodes.add((Node) primMap.get(n));
228 }
229 newWay.setNodes(newNodes);
230 addPrimitive(newWay);
231 }
232 // Because relations can have other relations as members we first clone all relations
233 // and then get the cloned members
234 Collection<Relation> relations = copyFrom.getRelations();
235 for (Relation r : relations) {
236 Relation newRelation = new Relation(r, r.isNew());
237 newRelation.setMembers(null);
238 primMap.put(r, newRelation);
239 addPrimitive(newRelation);
240 }
241 for (Relation r : relations) {
242 Relation newRelation = (Relation) primMap.get(r);
243 List<RelationMember> newMembers = new ArrayList<>();
244 for (RelationMember rm: r.getMembers()) {
245 newMembers.add(new RelationMember(rm.getRole(), primMap.get(rm.getMember())));
246 }
247 newRelation.setMembers(newMembers);
248 }
249 for (DataSource source : copyFrom.dataSources) {
250 dataSources.add(new DataSource(source));
251 }
252 version = copyFrom.version;
253 } finally {
254 copyFrom.getReadLock().unlock();
255 }
256 }
257
258 /**
259 * Adds a new data source.
260 * @param source data source to add
261 * @return {@code true} if the collection changed as a result of the call
262 * @since 11626
263 */
264 public synchronized boolean addDataSource(DataSource source) {
265 return addDataSources(Collections.singleton(source));
266 }
267
268 /**
269 * Adds new data sources.
270 * @param sources data sources to add
271 * @return {@code true} if the collection changed as a result of the call
272 * @since 11626
273 */
274 public synchronized boolean addDataSources(Collection<DataSource> sources) {
275 boolean changed = dataSources.addAll(sources);
276 if (changed) {
277 cachedDataSourceArea = null;
278 cachedDataSourceBounds = null;
279 }
280 return changed;
281 }
282
283 /**
284 * Returns the lock used for reading.
285 * @return the lock used for reading
286 */
287 public Lock getReadLock() {
288 return lock.readLock();
289 }
290
291 /**
292 * History of selections - shared by plugins and SelectionListDialog
293 */
294 private final LinkedList<Collection<? extends OsmPrimitive>> selectionHistory = new LinkedList<>();
295
296 /**
297 * Replies the history of JOSM selections
298 *
299 * @return list of history entries
300 */
301 public LinkedList<Collection<? extends OsmPrimitive>> getSelectionHistory() {
302 return selectionHistory;
303 }
304
305 /**
306 * Clears selection history list
307 */
308 public void clearSelectionHistory() {
309 selectionHistory.clear();
310 }
311
312 /**
313 * Maintains a list of used tags for autocompletion.
314 */
315 private AutoCompletionManager autocomplete;
316
317 /**
318 * Returns the autocompletion manager, which maintains a list of used tags for autocompletion.
319 * @return the autocompletion manager
320 */
321 public AutoCompletionManager getAutoCompletionManager() {
322 if (autocomplete == null) {
323 autocomplete = new AutoCompletionManager(this);
324 addDataSetListener(autocomplete);
325 }
326 return autocomplete;
327 }
328
329 /**
330 * The API version that created this data set, if any.
331 */
332 private String version;
333
334 /**
335 * Replies the API version this dataset was created from. May be null.
336 *
337 * @return the API version this dataset was created from. May be null.
338 */
339 public String getVersion() {
340 return version;
341 }
342
343 /**
344 * Sets the API version this dataset was created from.
345 *
346 * @param version the API version, i.e. "0.6"
347 */
348 public void setVersion(String version) {
349 this.version = version;
350 }
351
352 /**
353 * Determines if upload is being discouraged.
354 * (i.e. this dataset contains private data which should not be uploaded)
355 * @return {@code true} if upload is being discouraged, {@code false} otherwise
356 * @see #setUploadDiscouraged
357 * @deprecated use {@link #getUploadPolicy()}
358 */
359 @Deprecated
360 public boolean isUploadDiscouraged() {
361 return uploadPolicy == UploadPolicy.DISCOURAGED || uploadPolicy == UploadPolicy.BLOCKED;
362 }
363
364 /**
365 * Sets the "upload discouraged" flag.
366 * @param uploadDiscouraged {@code true} if this dataset contains private data which should not be uploaded
367 * @see #isUploadDiscouraged
368 * @deprecated use {@link #setUploadPolicy(UploadPolicy)}
369 */
370 @Deprecated
371 public void setUploadDiscouraged(boolean uploadDiscouraged) {
372 if (uploadPolicy != UploadPolicy.BLOCKED) {
373 this.uploadPolicy = uploadDiscouraged ? UploadPolicy.DISCOURAGED : UploadPolicy.NORMAL;
374 }
375 }
376
377 /**
378 * Get the upload policy.
379 * @return the upload policy
380 * @see #setUploadPolicy(UploadPolicy)
381 */
382 public UploadPolicy getUploadPolicy() {
383 return this.uploadPolicy;
384 }
385
386 /**
387 * Sets the upload policy.
388 * @param uploadPolicy the upload policy
389 * @see #getUploadPolicy()
390 */
391 public void setUploadPolicy(UploadPolicy uploadPolicy) {
392 this.uploadPolicy = uploadPolicy;
393 }
394
395 /**
396 * Holding bin for changeset tag information, to be applied when or if this is ever uploaded.
397 */
398 private final Map<String, String> changeSetTags = new HashMap<>();
399
400 /**
401 * Replies the set of changeset tags to be applied when or if this is ever uploaded.
402 * @return the set of changeset tags
403 * @see #addChangeSetTag
404 */
405 public Map<String, String> getChangeSetTags() {
406 return changeSetTags;
407 }
408
409 /**
410 * Adds a new changeset tag.
411 * @param k Key
412 * @param v Value
413 * @see #getChangeSetTags
414 */
415 public void addChangeSetTag(String k, String v) {
416 this.changeSetTags.put(k, v);
417 }
418
419 /**
420 * Gets a filtered collection of primitives matching the given predicate.
421 * @param <T> The primitive type.
422 * @param predicate The predicate to match
423 * @return The list of primtives.
424 * @since 10590
425 */
426 public <T extends OsmPrimitive> Collection<T> getPrimitives(Predicate<? super OsmPrimitive> predicate) {
427 return new SubclassFilteredCollection<>(allPrimitives, predicate);
428 }
429
430 /**
431 * Replies an unmodifiable collection of nodes in this dataset
432 *
433 * @return an unmodifiable collection of nodes in this dataset
434 */
435 public Collection<Node> getNodes() {
436 return getPrimitives(Node.class::isInstance);
437 }
438
439 @Override
440 public List<Node> searchNodes(BBox bbox) {
441 lock.readLock().lock();
442 try {
443 return super.searchNodes(bbox);
444 } finally {
445 lock.readLock().unlock();
446 }
447 }
448
449 /**
450 * Replies an unmodifiable collection of ways in this dataset
451 *
452 * @return an unmodifiable collection of ways in this dataset
453 */
454 public Collection<Way> getWays() {
455 return getPrimitives(Way.class::isInstance);
456 }
457
458 @Override
459 public List<Way> searchWays(BBox bbox) {
460 lock.readLock().lock();
461 try {
462 return super.searchWays(bbox);
463 } finally {
464 lock.readLock().unlock();
465 }
466 }
467
468 /**
469 * Searches for relations in the given bounding box.
470 * @param bbox the bounding box
471 * @return List of relations in the given bbox. Can be empty but not null
472 */
473 @Override
474 public List<Relation> searchRelations(BBox bbox) {
475 lock.readLock().lock();
476 try {
477 return super.searchRelations(bbox);
478 } finally {
479 lock.readLock().unlock();
480 }
481 }
482
483 /**
484 * Replies an unmodifiable collection of relations in this dataset
485 *
486 * @return an unmodifiable collection of relations in this dataset
487 */
488 public Collection<Relation> getRelations() {
489 return getPrimitives(Relation.class::isInstance);
490 }
491
492 /**
493 * Returns a collection containing all primitives of the dataset.
494 * @return A collection containing all primitives of the dataset. Data is not ordered
495 */
496 public Collection<OsmPrimitive> allPrimitives() {
497 return getPrimitives(o -> true);
498 }
499
500 /**
501 * Returns a collection containing all not-deleted primitives.
502 * @return A collection containing all not-deleted primitives.
503 * @see OsmPrimitive#isDeleted
504 */
505 public Collection<OsmPrimitive> allNonDeletedPrimitives() {
506 return getPrimitives(p -> !p.isDeleted());
507 }
508
509 /**
510 * Returns a collection containing all not-deleted complete primitives.
511 * @return A collection containing all not-deleted complete primitives.
512 * @see OsmPrimitive#isDeleted
513 * @see OsmPrimitive#isIncomplete
514 */
515 public Collection<OsmPrimitive> allNonDeletedCompletePrimitives() {
516 return getPrimitives(primitive -> !primitive.isDeleted() && !primitive.isIncomplete());
517 }
518
519 /**
520 * Returns a collection containing all not-deleted complete physical primitives.
521 * @return A collection containing all not-deleted complete physical primitives (nodes and ways).
522 * @see OsmPrimitive#isDeleted
523 * @see OsmPrimitive#isIncomplete
524 */
525 public Collection<OsmPrimitive> allNonDeletedPhysicalPrimitives() {
526 return getPrimitives(primitive -> !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation));
527 }
528
529 /**
530 * Returns a collection containing all modified primitives.
531 * @return A collection containing all modified primitives.
532 * @see OsmPrimitive#isModified
533 */
534 public Collection<OsmPrimitive> allModifiedPrimitives() {
535 return getPrimitives(OsmPrimitive::isModified);
536 }
537
538 /**
539 * Adds a primitive to the dataset.
540 *
541 * @param primitive the primitive.
542 */
543 @Override
544 public void addPrimitive(OsmPrimitive primitive) {
545 Objects.requireNonNull(primitive, "primitive");
546 beginUpdate();
547 try {
548 if (getPrimitiveById(primitive) != null)
549 throw new DataIntegrityProblemException(
550 tr("Unable to add primitive {0} to the dataset because it is already included", primitive.toString()));
551
552 allPrimitives.add(primitive);
553 primitive.setDataset(this);
554 primitive.updatePosition(); // Set cached bbox for way and relation (required for reindexWay and reindexRelation to work properly)
555 super.addPrimitive(primitive);
556 firePrimitivesAdded(Collections.singletonList(primitive), false);
557 } finally {
558 endUpdate();
559 }
560 }
561
562 /**
563 * Removes a primitive from the dataset. This method only removes the
564 * primitive form the respective collection of primitives managed
565 * by this dataset, i.e. from {@link #nodes}, {@link #ways}, or
566 * {@link #relations}. References from other primitives to this
567 * primitive are left unchanged.
568 *
569 * @param primitiveId the id of the primitive
570 */
571 public void removePrimitive(PrimitiveId primitiveId) {
572 beginUpdate();
573 try {
574 OsmPrimitive primitive = getPrimitiveByIdChecked(primitiveId);
575 if (primitive == null)
576 return;
577 super.removePrimitive(primitive);
578 clearSelection(primitiveId);
579 allPrimitives.remove(primitive);
580 primitive.setDataset(null);
581 firePrimitivesRemoved(Collections.singletonList(primitive), false);
582 } finally {
583 endUpdate();
584 }
585 }
586
587 /*---------------------------------------------------
588 * SELECTION HANDLING
589 *---------------------------------------------------*/
590
591 /**
592 * Add a listener that listens to selection changes in this specific data set.
593 * @param listener The listener.
594 * @see #removeSelectionListener(DataSelectionListener)
595 * @see SelectionEventManager#addSelectionListener(SelectionChangedListener,
596 * org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode)
597 * To add a global listener.
598 */
599 public void addSelectionListener(DataSelectionListener listener) {
600 selectionListeners.addListener(listener);
601 }
602
603 /**
604 * Remove a listener that listens to selection changes in this specific data set.
605 * @param listener The listener.
606 * @see #addSelectionListener(DataSelectionListener)
607 */
608 public void removeSelectionListener(DataSelectionListener listener) {
609 selectionListeners.removeListener(listener);
610 }
611
612 /*---------------------------------------------------
613 * OLD SELECTION HANDLING
614 *---------------------------------------------------*/
615
616 /**
617 * A list of listeners to selection changed events. The list is static, as listeners register
618 * themselves for any dataset selection changes that occur, regardless of the current active
619 * dataset. (However, the selection does only change in the active layer)
620 */
621 private static final Collection<SelectionChangedListener> selListeners = new CopyOnWriteArrayList<>();
622
623 /**
624 * Adds a new selection listener.
625 * @param listener The selection listener to add
626 * @see #addSelectionListener(DataSelectionListener)
627 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
628 */
629 public static void addSelectionListener(SelectionChangedListener listener) {
630 ((CopyOnWriteArrayList<SelectionChangedListener>) selListeners).addIfAbsent(listener);
631 }
632
633 /**
634 * Removes a selection listener.
635 * @param listener The selection listener to remove
636 * @see #removeSelectionListener(DataSelectionListener)
637 * @see SelectionEventManager#removeSelectionListener(SelectionChangedListener)
638 */
639 public static void removeSelectionListener(SelectionChangedListener listener) {
640 selListeners.remove(listener);
641 }
642
643 /**
644 * Notifies all registered {@link SelectionChangedListener} about the current selection in
645 * this dataset.
646 * @deprecated You should never need to do this from the outside.
647 */
648 @Deprecated
649 public void fireSelectionChanged() {
650 fireDreprecatedSelectionChange(getAllSelected());
651 }
652
653 private static void fireDreprecatedSelectionChange(Collection<? extends OsmPrimitive> currentSelection) {
654 for (SelectionChangedListener l : selListeners) {
655 l.selectionChanged(currentSelection);
656 }
657 }
658
659 /**
660 * Returns selected nodes and ways.
661 * @return selected nodes and ways
662 */
663 public Collection<OsmPrimitive> getSelectedNodesAndWays() {
664 return new SubclassFilteredCollection<>(getSelected(), primitive -> primitive instanceof Node || primitive instanceof Way);
665 }
666
667 /**
668 * Returns an unmodifiable collection of *WaySegments* whose virtual
669 * nodes should be highlighted. WaySegments are used to avoid having
670 * to create a VirtualNode class that wouldn't have much purpose otherwise.
671 *
672 * @return unmodifiable collection of WaySegments
673 */
674 public Collection<WaySegment> getHighlightedVirtualNodes() {
675 return Collections.unmodifiableCollection(highlightedVirtualNodes);
676 }
677
678 /**
679 * Returns an unmodifiable collection of WaySegments that should be highlighted.
680 *
681 * @return unmodifiable collection of WaySegments
682 */
683 public Collection<WaySegment> getHighlightedWaySegments() {
684 return Collections.unmodifiableCollection(highlightedWaySegments);
685 }
686
687 /**
688 * Adds a listener that gets notified whenever way segment / virtual nodes highlights change.
689 * @param listener The Listener
690 * @since 12014
691 */
692 public void addHighlightUpdateListener(HighlightUpdateListener listener) {
693 highlightUpdateListeners.addListener(listener);
694 }
695
696 /**
697 * Removes a listener that was added with {@link #addHighlightUpdateListener(HighlightUpdateListener)}
698 * @param listener The Listener
699 * @since 12014
700 */
701 public void removeHighlightUpdateListener(HighlightUpdateListener listener) {
702 highlightUpdateListeners.removeListener(listener);
703 }
704
705 /**
706 * Replies an unmodifiable collection of primitives currently selected
707 * in this dataset, except deleted ones. May be empty, but not null.
708 *
709 * @return unmodifiable collection of primitives
710 */
711 public Collection<OsmPrimitive> getSelected() {
712 return new SubclassFilteredCollection<>(getAllSelected(), p -> !p.isDeleted());
713 }
714
715 /**
716 * Replies an unmodifiable collection of primitives currently selected
717 * in this dataset, including deleted ones. May be empty, but not null.
718 *
719 * @return unmodifiable collection of primitives
720 */
721 public Collection<OsmPrimitive> getAllSelected() {
722 return currentSelectedPrimitives;
723 }
724
725 /**
726 * Returns selected nodes.
727 * @return selected nodes
728 */
729 public Collection<Node> getSelectedNodes() {
730 return new SubclassFilteredCollection<>(getSelected(), Node.class::isInstance);
731 }
732
733 /**
734 * Returns selected ways.
735 * @return selected ways
736 */
737 public Collection<Way> getSelectedWays() {
738 return new SubclassFilteredCollection<>(getSelected(), Way.class::isInstance);
739 }
740
741 /**
742 * Returns selected relations.
743 * @return selected relations
744 */
745 public Collection<Relation> getSelectedRelations() {
746 return new SubclassFilteredCollection<>(getSelected(), Relation.class::isInstance);
747 }
748
749 /**
750 * Determines whether the selection is empty or not
751 * @return whether the selection is empty or not
752 */
753 public boolean selectionEmpty() {
754 return currentSelectedPrimitives.isEmpty();
755 }
756
757 /**
758 * Determines whether the given primitive is selected or not
759 * @param osm the primitive
760 * @return whether {@code osm} is selected or not
761 */
762 public boolean isSelected(OsmPrimitive osm) {
763 return currentSelectedPrimitives.contains(osm);
764 }
765
766 /**
767 * set what virtual nodes should be highlighted. Requires a Collection of
768 * *WaySegments* to avoid a VirtualNode class that wouldn't have much use
769 * otherwise.
770 * @param waySegments Collection of way segments
771 */
772 public void setHighlightedVirtualNodes(Collection<WaySegment> waySegments) {
773 if (highlightedVirtualNodes.isEmpty() && waySegments.isEmpty())
774 return;
775
776 highlightedVirtualNodes = waySegments;
777 fireHighlightingChanged();
778 }
779
780 /**
781 * set what virtual ways should be highlighted.
782 * @param waySegments Collection of way segments
783 */
784 public void setHighlightedWaySegments(Collection<WaySegment> waySegments) {
785 if (highlightedWaySegments.isEmpty() && waySegments.isEmpty())
786 return;
787
788 highlightedWaySegments = waySegments;
789 fireHighlightingChanged();
790 }
791
792 /**
793 * Sets the current selection to the primitives in <code>selection</code>.
794 * Notifies all {@link SelectionChangedListener} if <code>fireSelectionChangeEvent</code> is true.
795 *
796 * @param selection the selection
797 * @param fireSelectionChangeEvent true, if the selection change listeners are to be notified; false, otherwise
798 * @deprecated Use {@link #setSelected(Collection)} instead. To bee removed end of 2017. Does not seem to be used by plugins.
799 */
800 @Deprecated
801 public void setSelected(Collection<? extends PrimitiveId> selection, boolean fireSelectionChangeEvent) {
802 setSelected(selection);
803 }
804
805 /**
806 * Sets the current selection to the primitives in <code>selection</code>
807 * and notifies all {@link SelectionChangedListener}.
808 *
809 * @param selection the selection
810 */
811 public void setSelected(Collection<? extends PrimitiveId> selection) {
812 setSelected(selection.stream());
813 }
814
815 /**
816 * Sets the current selection to the primitives in <code>osm</code>
817 * and notifies all {@link SelectionChangedListener}.
818 *
819 * @param osm the primitives to set. <code>null</code> values are ignored for now, but this may be removed in the future.
820 */
821 public void setSelected(PrimitiveId... osm) {
822 setSelected(Stream.of(osm).filter(Objects::nonNull));
823 }
824
825 private void setSelected(Stream<? extends PrimitiveId> stream) {
826 doSelectionChange(old -> new SelectionReplaceEvent(this, old,
827 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
828 }
829
830 /**
831 * Adds the primitives in <code>selection</code> to the current selection
832 * and notifies all {@link SelectionChangedListener}.
833 *
834 * @param selection the selection
835 */
836 public void addSelected(Collection<? extends PrimitiveId> selection) {
837 addSelected(selection.stream());
838 }
839
840 /**
841 * Adds the primitives in <code>osm</code> to the current selection
842 * and notifies all {@link SelectionChangedListener}.
843 *
844 * @param osm the primitives to add
845 */
846 public void addSelected(PrimitiveId... osm) {
847 addSelected(Stream.of(osm));
848 }
849
850 private void addSelected(Stream<? extends PrimitiveId> stream) {
851 doSelectionChange(old -> new SelectionAddEvent(this, old,
852 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
853 }
854
855 /**
856 * Removes the selection from every value in the collection.
857 * @param osm The collection of ids to remove the selection from.
858 */
859 public void clearSelection(PrimitiveId... osm) {
860 clearSelection(Stream.of(osm));
861 }
862
863 /**
864 * Removes the selection from every value in the collection.
865 * @param list The collection of ids to remove the selection from.
866 */
867 public void clearSelection(Collection<? extends PrimitiveId> list) {
868 clearSelection(list.stream());
869 }
870
871 /**
872 * Clears the current selection.
873 */
874 public void clearSelection() {
875 setSelected(Stream.empty());
876 }
877
878 private void clearSelection(Stream<? extends PrimitiveId> stream) {
879 doSelectionChange(old -> new SelectionRemoveEvent(this, old,
880 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
881 }
882
883 /**
884 * Toggles the selected state of the given collection of primitives.
885 * @param osm The primitives to toggle
886 */
887 public void toggleSelected(Collection<? extends PrimitiveId> osm) {
888 toggleSelected(osm.stream());
889 }
890
891 /**
892 * Toggles the selected state of the given collection of primitives.
893 * @param osm The primitives to toggle
894 */
895 public void toggleSelected(PrimitiveId... osm) {
896 toggleSelected(Stream.of(osm));
897 }
898
899 private void toggleSelected(Stream<? extends PrimitiveId> stream) {
900 doSelectionChange(old -> new SelectionToggleEvent(this, old,
901 stream.map(this::getPrimitiveByIdChecked).filter(Objects::nonNull)));
902 }
903
904 /**
905 * Do a selection change.
906 * <p>
907 * This is the only method that changes the current selection state.
908 * @param command A generator that generates the {@link SelectionChangeEvent} for the given base set of currently selected primitives.
909 * @return true iff the command did change the selection.
910 * @since 12048
911 */
912 private boolean doSelectionChange(Function<Set<OsmPrimitive>, SelectionChangeEvent> command) {
913 lock.readLock().lock();
914 try {
915 synchronized (selectionLock) {
916 SelectionChangeEvent event = command.apply(currentSelectedPrimitives);
917 if (event.isNop()) {
918 return false;
919 }
920 currentSelectedPrimitives = event.getSelection();
921 selectionListeners.fireEvent(l -> l.selectionChanged(event));
922 return true;
923 }
924 } finally {
925 lock.readLock().unlock();
926 }
927 }
928
929 /**
930 * clear all highlights of virtual nodes
931 */
932 public void clearHighlightedVirtualNodes() {
933 setHighlightedVirtualNodes(new ArrayList<WaySegment>());
934 }
935
936 /**
937 * clear all highlights of way segments
938 */
939 public void clearHighlightedWaySegments() {
940 setHighlightedWaySegments(new ArrayList<WaySegment>());
941 }
942
943 @Override
944 public synchronized Area getDataSourceArea() {
945 if (cachedDataSourceArea == null) {
946 cachedDataSourceArea = Data.super.getDataSourceArea();
947 }
948 return cachedDataSourceArea;
949 }
950
951 @Override
952 public synchronized List<Bounds> getDataSourceBounds() {
953 if (cachedDataSourceBounds == null) {
954 cachedDataSourceBounds = Data.super.getDataSourceBounds();
955 }
956 return Collections.unmodifiableList(cachedDataSourceBounds);
957 }
958
959 @Override
960 public synchronized Collection<DataSource> getDataSources() {
961 return Collections.unmodifiableCollection(dataSources);
962 }
963
964 /**
965 * Returns a primitive with a given id from the data set. null, if no such primitive exists
966 *
967 * @param id uniqueId of the primitive. Might be &lt; 0 for newly created primitives
968 * @param type the type of the primitive. Must not be null.
969 * @return the primitive
970 * @throws NullPointerException if type is null
971 */
972 public OsmPrimitive getPrimitiveById(long id, OsmPrimitiveType type) {
973 return getPrimitiveById(new SimplePrimitiveId(id, type));
974 }
975
976 /**
977 * Returns a primitive with a given id from the data set. null, if no such primitive exists
978 *
979 * @param primitiveId type and uniqueId of the primitive. Might be &lt; 0 for newly created primitives
980 * @return the primitive
981 */
982 public OsmPrimitive getPrimitiveById(PrimitiveId primitiveId) {
983 return primitiveId != null ? primitivesMap.get(primitiveId) : null;
984 }
985
986 /**
987 * Show message and stack trace in log in case primitive is not found
988 * @param primitiveId primitive id to look for
989 * @return Primitive by id.
990 */
991 private OsmPrimitive getPrimitiveByIdChecked(PrimitiveId primitiveId) {
992 OsmPrimitive result = getPrimitiveById(primitiveId);
993 if (result == null && primitiveId != null) {
994 Main.warn(tr("JOSM expected to find primitive [{0} {1}] in dataset but it is not there. Please report this "
995 + "at {2}. This is not a critical error, it should be safe to continue in your work.",
996 primitiveId.getType(), Long.toString(primitiveId.getUniqueId()), Main.getJOSMWebsite()));
997 Main.error(new Exception());
998 }
999
1000 return result;
1001 }
1002
1003 private static void deleteWay(Way way) {
1004 way.setNodes(null);
1005 way.setDeleted(true);
1006 }
1007
1008 /**
1009 * Removes all references from ways in this dataset to a particular node.
1010 *
1011 * @param node the node
1012 * @return The set of ways that have been modified
1013 */
1014 public Set<Way> unlinkNodeFromWays(Node node) {
1015 Set<Way> result = new HashSet<>();
1016 beginUpdate();
1017 try {
1018 for (Way way : OsmPrimitive.getFilteredList(node.getReferrers(), Way.class)) {
1019 List<Node> wayNodes = way.getNodes();
1020 if (wayNodes.remove(node)) {
1021 if (wayNodes.size() < 2) {
1022 deleteWay(way);
1023 } else {
1024 way.setNodes(wayNodes);
1025 }
1026 result.add(way);
1027 }
1028 }
1029 } finally {
1030 endUpdate();
1031 }
1032 return result;
1033 }
1034
1035 /**
1036 * removes all references from relations in this dataset to this primitive
1037 *
1038 * @param primitive the primitive
1039 * @return The set of relations that have been modified
1040 */
1041 public Set<Relation> unlinkPrimitiveFromRelations(OsmPrimitive primitive) {
1042 Set<Relation> result = new HashSet<>();
1043 beginUpdate();
1044 try {
1045 for (Relation relation : getRelations()) {
1046 List<RelationMember> members = relation.getMembers();
1047
1048 Iterator<RelationMember> it = members.iterator();
1049 boolean removed = false;
1050 while (it.hasNext()) {
1051 RelationMember member = it.next();
1052 if (member.getMember().equals(primitive)) {
1053 it.remove();
1054 removed = true;
1055 }
1056 }
1057
1058 if (removed) {
1059 relation.setMembers(members);
1060 result.add(relation);
1061 }
1062 }
1063 } finally {
1064 endUpdate();
1065 }
1066 return result;
1067 }
1068
1069 /**
1070 * Removes all references from other primitives to the referenced primitive.
1071 *
1072 * @param referencedPrimitive the referenced primitive
1073 * @return The set of primitives that have been modified
1074 */
1075 public Set<OsmPrimitive> unlinkReferencesToPrimitive(OsmPrimitive referencedPrimitive) {
1076 Set<OsmPrimitive> result = new HashSet<>();
1077 beginUpdate();
1078 try {
1079 if (referencedPrimitive instanceof Node) {
1080 result.addAll(unlinkNodeFromWays((Node) referencedPrimitive));
1081 }
1082 result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive));
1083 } finally {
1084 endUpdate();
1085 }
1086 return result;
1087 }
1088
1089 /**
1090 * Replies true if there is at least one primitive in this dataset with
1091 * {@link OsmPrimitive#isModified()} == <code>true</code>.
1092 *
1093 * @return true if there is at least one primitive in this dataset with
1094 * {@link OsmPrimitive#isModified()} == <code>true</code>.
1095 */
1096 public boolean isModified() {
1097 for (OsmPrimitive p: allPrimitives) {
1098 if (p.isModified())
1099 return true;
1100 }
1101 return false;
1102 }
1103
1104 /**
1105 * Adds a new data set listener.
1106 * @param dsl The data set listener to add
1107 */
1108 public void addDataSetListener(DataSetListener dsl) {
1109 listeners.addIfAbsent(dsl);
1110 }
1111
1112 /**
1113 * Removes a data set listener.
1114 * @param dsl The data set listener to remove
1115 */
1116 public void removeDataSetListener(DataSetListener dsl) {
1117 listeners.remove(dsl);
1118 }
1119
1120 /**
1121 * Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
1122 * {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes
1123 * <br>
1124 * Typical usecase should look like this:
1125 * <pre>
1126 * ds.beginUpdate();
1127 * try {
1128 * ...
1129 * } finally {
1130 * ds.endUpdate();
1131 * }
1132 * </pre>
1133 */
1134 public void beginUpdate() {
1135 lock.writeLock().lock();
1136 updateCount++;
1137 }
1138
1139 /**
1140 * @see DataSet#beginUpdate()
1141 */
1142 public void endUpdate() {
1143 if (updateCount > 0) {
1144 updateCount--;
1145 List<AbstractDatasetChangedEvent> eventsToFire = Collections.emptyList();
1146 if (updateCount == 0) {
1147 eventsToFire = new ArrayList<>(cachedEvents);
1148 cachedEvents.clear();
1149 }
1150
1151 if (!eventsToFire.isEmpty()) {
1152 lock.readLock().lock();
1153 lock.writeLock().unlock();
1154 try {
1155 if (eventsToFire.size() < MAX_SINGLE_EVENTS) {
1156 for (AbstractDatasetChangedEvent event: eventsToFire) {
1157 fireEventToListeners(event);
1158 }
1159 } else if (eventsToFire.size() == MAX_EVENTS) {
1160 fireEventToListeners(new DataChangedEvent(this));
1161 } else {
1162 fireEventToListeners(new DataChangedEvent(this, eventsToFire));
1163 }
1164 } finally {
1165 lock.readLock().unlock();
1166 }
1167 } else {
1168 lock.writeLock().unlock();
1169 }
1170
1171 } else
1172 throw new AssertionError("endUpdate called without beginUpdate");
1173 }
1174
1175 private void fireEventToListeners(AbstractDatasetChangedEvent event) {
1176 for (DataSetListener listener: listeners) {
1177 event.fire(listener);
1178 }
1179 }
1180
1181 private void fireEvent(AbstractDatasetChangedEvent event) {
1182 if (updateCount == 0)
1183 throw new AssertionError("dataset events can be fired only when dataset is locked");
1184 if (cachedEvents.size() < MAX_EVENTS) {
1185 cachedEvents.add(event);
1186 }
1187 }
1188
1189 void firePrimitivesAdded(Collection<? extends OsmPrimitive> added, boolean wasIncomplete) {
1190 fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
1191 }
1192
1193 void firePrimitivesRemoved(Collection<? extends OsmPrimitive> removed, boolean wasComplete) {
1194 fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
1195 }
1196
1197 void fireTagsChanged(OsmPrimitive prim, Map<String, String> originalKeys) {
1198 fireEvent(new TagsChangedEvent(this, prim, originalKeys));
1199 }
1200
1201 void fireRelationMembersChanged(Relation r) {
1202 reindexRelation(r);
1203 fireEvent(new RelationMembersChangedEvent(this, r));
1204 }
1205
1206 void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
1207 reindexNode(node, newCoor, eastNorth);
1208 fireEvent(new NodeMovedEvent(this, node));
1209 }
1210
1211 void fireWayNodesChanged(Way way) {
1212 reindexWay(way);
1213 fireEvent(new WayNodesChangedEvent(this, way));
1214 }
1215
1216 void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
1217 fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
1218 }
1219
1220 void firePrimitiveFlagsChanged(OsmPrimitive primitive) {
1221 fireEvent(new PrimitiveFlagsChangedEvent(this, primitive));
1222 }
1223
1224 void fireHighlightingChanged() {
1225 HighlightUpdateListener.HighlightUpdateEvent e = new HighlightUpdateListener.HighlightUpdateEvent(this);
1226 highlightUpdateListeners.fireEvent(l -> l.highlightUpdated(e));
1227 }
1228
1229 /**
1230 * Invalidates the internal cache of projected east/north coordinates.
1231 *
1232 * This method can be invoked after the globally configured projection method
1233 * changed.
1234 */
1235 public void invalidateEastNorthCache() {
1236 if (Main.getProjection() == null) return; // sanity check
1237 try {
1238 beginUpdate();
1239 for (Node n: Utils.filteredCollection(allPrimitives, Node.class)) {
1240 n.invalidateEastNorthCache();
1241 }
1242 } finally {
1243 endUpdate();
1244 }
1245 }
1246
1247 /**
1248 * Cleanups all deleted primitives (really delete them from the dataset).
1249 */
1250 public void cleanupDeletedPrimitives() {
1251 beginUpdate();
1252 try {
1253 cleanupDeleted(Stream.concat(
1254 getNodes().stream(), Stream.concat(getNodes().stream(), getNodes().stream())));
1255 } finally {
1256 endUpdate();
1257 }
1258 }
1259
1260 private void cleanupDeleted(Stream<? extends OsmPrimitive> it) {
1261 it.filter(primitive -> primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew()))
1262 .collect(Collectors.toList())
1263 .forEach(primitive -> this.removePrimitive(primitive.getPrimitiveId()));
1264
1265 }
1266
1267 /**
1268 * Removes all primitives from the dataset and resets the currently selected primitives
1269 * to the empty collection. Also notifies selection change listeners if necessary.
1270 */
1271 @Override
1272 public void clear() {
1273 beginUpdate();
1274 try {
1275 clearSelection();
1276 for (OsmPrimitive primitive:allPrimitives) {
1277 primitive.setDataset(null);
1278 }
1279 super.clear();
1280 allPrimitives.clear();
1281 } finally {
1282 endUpdate();
1283 }
1284 }
1285
1286 /**
1287 * Marks all "invisible" objects as deleted. These objects should be always marked as
1288 * deleted when downloaded from the server. They can be undeleted later if necessary.
1289 *
1290 */
1291 public void deleteInvisible() {
1292 for (OsmPrimitive primitive:allPrimitives) {
1293 if (!primitive.isVisible()) {
1294 primitive.setDeleted(true);
1295 }
1296 }
1297 }
1298
1299 /**
1300 * Moves all primitives and datasources from DataSet "from" to this DataSet.
1301 * @param from The source DataSet
1302 */
1303 public void mergeFrom(DataSet from) {
1304 mergeFrom(from, null);
1305 }
1306
1307 /**
1308 * Moves all primitives and datasources from DataSet "from" to this DataSet.
1309 * @param from The source DataSet
1310 * @param progressMonitor The progress monitor
1311 */
1312 public synchronized void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
1313 if (from != null) {
1314 new DataSetMerger(this, from).merge(progressMonitor);
1315 synchronized (from) {
1316 if (!from.dataSources.isEmpty()) {
1317 if (dataSources.addAll(from.dataSources)) {
1318 cachedDataSourceArea = null;
1319 cachedDataSourceBounds = null;
1320 }
1321 from.dataSources.clear();
1322 from.cachedDataSourceArea = null;
1323 from.cachedDataSourceBounds = null;
1324 }
1325 }
1326 }
1327 }
1328
1329 /* --------------------------------------------------------------------------------- */
1330 /* interface ProjectionChangeListner */
1331 /* --------------------------------------------------------------------------------- */
1332 @Override
1333 public void projectionChanged(Projection oldValue, Projection newValue) {
1334 invalidateEastNorthCache();
1335 }
1336
1337 /**
1338 * Returns the data sources bounding box.
1339 * @return the data sources bounding box
1340 */
1341 public synchronized ProjectionBounds getDataSourceBoundingBox() {
1342 BoundingXYVisitor bbox = new BoundingXYVisitor();
1343 for (DataSource source : dataSources) {
1344 bbox.visit(source.bounds);
1345 }
1346 if (bbox.hasExtend()) {
1347 return bbox.getBounds();
1348 }
1349 return null;
1350 }
1351}
Note: See TracBrowser for help on using the repository browser.