result = new HashSet<>();
beginUpdate();
try {
if (referencedPrimitive instanceof Node) {
result.addAll(unlinkNodeFromWays((Node) referencedPrimitive));
}
result.addAll(unlinkPrimitiveFromRelations(referencedPrimitive));
} finally {
endUpdate();
}
return result;
}
/**
* Replies true if there is at least one primitive in this dataset with
* {@link OsmPrimitive#isModified()} == true
.
*
* @return true if there is at least one primitive in this dataset with
* {@link OsmPrimitive#isModified()} == true
.
*/
public boolean isModified() {
for (OsmPrimitive p: allPrimitives) {
if (p.isModified())
return true;
}
return false;
}
private void reindexNode(Node node, LatLon newCoor, EastNorth eastNorth) {
if (!nodes.remove(node))
throw new JosmRuntimeException("Reindexing node failed to remove");
node.setCoorInternal(newCoor, eastNorth);
if (!nodes.add(node))
throw new JosmRuntimeException("Reindexing node failed to add");
for (OsmPrimitive primitive: node.getReferrers()) {
if (primitive instanceof Way) {
reindexWay((Way) primitive);
} else {
reindexRelation((Relation) primitive);
}
}
}
private void reindexWay(Way way) {
BBox before = way.getBBox();
if (!ways.remove(way))
throw new JosmRuntimeException("Reindexing way failed to remove");
way.updatePosition();
if (!ways.add(way))
throw new JosmRuntimeException("Reindexing way failed to add");
if (!way.getBBox().equals(before)) {
for (OsmPrimitive primitive: way.getReferrers()) {
reindexRelation((Relation) primitive);
}
}
}
private static void reindexRelation(Relation relation) {
BBox before = relation.getBBox();
relation.updatePosition();
if (!before.equals(relation.getBBox())) {
for (OsmPrimitive primitive: relation.getReferrers()) {
reindexRelation((Relation) primitive);
}
}
}
/**
* Adds a new data set listener.
* @param dsl The data set listener to add
*/
public void addDataSetListener(DataSetListener dsl) {
listeners.addIfAbsent(dsl);
}
/**
* Removes a data set listener.
* @param dsl The data set listener to remove
*/
public void removeDataSetListener(DataSetListener dsl) {
listeners.remove(dsl);
}
/**
* Can be called before bigger changes on dataset. Events are disabled until {@link #endUpdate()}.
* {@link DataSetListener#dataChanged(DataChangedEvent event)} event is triggered after end of changes
*
* Typical usecase should look like this:
*
* ds.beginUpdate();
* try {
* ...
* } finally {
* ds.endUpdate();
* }
*
*/
public void beginUpdate() {
lock.writeLock().lock();
updateCount++;
}
/**
* @see DataSet#beginUpdate()
*/
public void endUpdate() {
if (updateCount > 0) {
updateCount--;
List eventsToFire = Collections.emptyList();
if (updateCount == 0) {
eventsToFire = new ArrayList<>(cachedEvents);
cachedEvents.clear();
}
if (!eventsToFire.isEmpty()) {
lock.readLock().lock();
lock.writeLock().unlock();
try {
if (eventsToFire.size() < MAX_SINGLE_EVENTS) {
for (AbstractDatasetChangedEvent event: eventsToFire) {
fireEventToListeners(event);
}
} else if (eventsToFire.size() == MAX_EVENTS) {
fireEventToListeners(new DataChangedEvent(this));
} else {
fireEventToListeners(new DataChangedEvent(this, eventsToFire));
}
} finally {
lock.readLock().unlock();
}
} else {
lock.writeLock().unlock();
}
} else
throw new AssertionError("endUpdate called without beginUpdate");
}
private void fireEventToListeners(AbstractDatasetChangedEvent event) {
for (DataSetListener listener: listeners) {
event.fire(listener);
}
}
private void fireEvent(AbstractDatasetChangedEvent event) {
if (updateCount == 0)
throw new AssertionError("dataset events can be fired only when dataset is locked");
if (cachedEvents.size() < MAX_EVENTS) {
cachedEvents.add(event);
}
}
void firePrimitivesAdded(Collection extends OsmPrimitive> added, boolean wasIncomplete) {
fireEvent(new PrimitivesAddedEvent(this, added, wasIncomplete));
}
void firePrimitivesRemoved(Collection extends OsmPrimitive> removed, boolean wasComplete) {
fireEvent(new PrimitivesRemovedEvent(this, removed, wasComplete));
}
void fireTagsChanged(OsmPrimitive prim, Map originalKeys) {
fireEvent(new TagsChangedEvent(this, prim, originalKeys));
}
void fireRelationMembersChanged(Relation r) {
reindexRelation(r);
fireEvent(new RelationMembersChangedEvent(this, r));
}
void fireNodeMoved(Node node, LatLon newCoor, EastNorth eastNorth) {
reindexNode(node, newCoor, eastNorth);
fireEvent(new NodeMovedEvent(this, node));
}
void fireWayNodesChanged(Way way) {
reindexWay(way);
fireEvent(new WayNodesChangedEvent(this, way));
}
void fireChangesetIdChanged(OsmPrimitive primitive, int oldChangesetId, int newChangesetId) {
fireEvent(new ChangesetIdChangedEvent(this, Collections.singletonList(primitive), oldChangesetId, newChangesetId));
}
void firePrimitiveFlagsChanged(OsmPrimitive primitive) {
fireEvent(new PrimitiveFlagsChangedEvent(this, primitive));
}
void fireHighlightingChanged() {
highlightUpdateCount++;
}
/**
* Invalidates the internal cache of projected east/north coordinates.
*
* This method can be invoked after the globally configured projection method
* changed.
*/
public void invalidateEastNorthCache() {
if (Main.getProjection() == null) return; // sanity check
try {
beginUpdate();
for (Node n: Utils.filteredCollection(allPrimitives, Node.class)) {
n.invalidateEastNorthCache();
}
} finally {
endUpdate();
}
}
/**
* Cleanups all deleted primitives (really delete them from the dataset).
*/
public void cleanupDeletedPrimitives() {
beginUpdate();
try {
boolean changed = cleanupDeleted(nodes.iterator());
if (cleanupDeleted(ways.iterator())) {
changed = true;
}
if (cleanupDeleted(relations.iterator())) {
changed = true;
}
if (changed) {
fireSelectionChanged();
}
} finally {
endUpdate();
}
}
private boolean cleanupDeleted(Iterator extends OsmPrimitive> it) {
boolean changed = false;
synchronized (selectionLock) {
while (it.hasNext()) {
OsmPrimitive primitive = it.next();
if (primitive.isDeleted() && (!primitive.isVisible() || primitive.isNew())) {
selectedPrimitives.remove(primitive);
selectionSnapshot = null;
allPrimitives.remove(primitive);
primitive.setDataset(null);
changed = true;
it.remove();
}
}
if (changed) {
selectionSnapshot = null;
}
}
return changed;
}
/**
* Removes all primitives from the dataset and resets the currently selected primitives
* to the empty collection. Also notifies selection change listeners if necessary.
*
*/
public void clear() {
beginUpdate();
try {
clearSelection();
for (OsmPrimitive primitive:allPrimitives) {
primitive.setDataset(null);
}
nodes.clear();
ways.clear();
relations.clear();
allPrimitives.clear();
} finally {
endUpdate();
}
}
/**
* Marks all "invisible" objects as deleted. These objects should be always marked as
* deleted when downloaded from the server. They can be undeleted later if necessary.
*
*/
public void deleteInvisible() {
for (OsmPrimitive primitive:allPrimitives) {
if (!primitive.isVisible()) {
primitive.setDeleted(true);
}
}
}
/**
* Moves all primitives and datasources from DataSet "from" to this DataSet.
* @param from The source DataSet
*/
public void mergeFrom(DataSet from) {
mergeFrom(from, null);
}
/**
* Moves all primitives and datasources from DataSet "from" to this DataSet.
* @param from The source DataSet
* @param progressMonitor The progress monitor
*/
public void mergeFrom(DataSet from, ProgressMonitor progressMonitor) {
if (from != null) {
new DataSetMerger(this, from).merge(progressMonitor);
dataSources.addAll(from.dataSources);
from.dataSources.clear();
}
}
/* --------------------------------------------------------------------------------- */
/* interface ProjectionChangeListner */
/* --------------------------------------------------------------------------------- */
@Override
public void projectionChanged(Projection oldValue, Projection newValue) {
invalidateEastNorthCache();
}
public ProjectionBounds getDataSourceBoundingBox() {
BoundingXYVisitor bbox = new BoundingXYVisitor();
for (DataSource source : dataSources) {
bbox.visit(source.bounds);
}
if (bbox.hasExtend()) {
return bbox.getBounds();
}
return null;
}
}