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

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

see #12731 - checkstyle/sonar

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