// This software is released into the Public Domain. See copying.txt for details.
package org.openstreetmap.osmosis.areafilter.v0_6;
import java.util.Iterator;
import java.util.Map;
import org.openstreetmap.osmosis.core.container.v0_6.BoundContainer;
import org.openstreetmap.osmosis.core.container.v0_6.EntityContainer;
import org.openstreetmap.osmosis.core.container.v0_6.EntityProcessor;
import org.openstreetmap.osmosis.core.container.v0_6.NodeContainer;
import org.openstreetmap.osmosis.core.container.v0_6.RelationContainer;
import org.openstreetmap.osmosis.core.container.v0_6.WayContainer;
import org.openstreetmap.osmosis.core.domain.v0_6.EntityType;
import org.openstreetmap.osmosis.core.domain.v0_6.Node;
import org.openstreetmap.osmosis.core.domain.v0_6.Relation;
import org.openstreetmap.osmosis.core.domain.v0_6.RelationMember;
import org.openstreetmap.osmosis.core.domain.v0_6.Way;
import org.openstreetmap.osmosis.core.domain.v0_6.WayNode;
import org.openstreetmap.osmosis.core.filter.common.IdTracker;
import org.openstreetmap.osmosis.core.filter.common.IdTrackerFactory;
import org.openstreetmap.osmosis.core.filter.common.IdTrackerType;
import org.openstreetmap.osmosis.core.lifecycle.ReleasableIterator;
import org.openstreetmap.osmosis.core.store.SimpleObjectStore;
import org.openstreetmap.osmosis.core.store.SingleClassObjectSerializationFactory;
import org.openstreetmap.osmosis.core.task.v0_6.Sink;
import org.openstreetmap.osmosis.core.task.v0_6.SinkSource;
/**
* A base class for all tasks filter entities within an area.
*
* @author Brett Henderson
* @author Karl Newman
*/
public abstract class AreaFilter implements SinkSource, EntityProcessor {
private Sink sink;
private IdTracker availableNodes; // Nodes within the area.
private IdTracker requiredNodes; // Nodes needed to complete referencing entities.
private IdTracker availableWays; // Ways within the area.
private IdTracker requiredWays; // Ways needed to complete referencing relations.
private IdTracker availableRelations; // Relations within the area.
private IdTracker requiredRelations; // Relations needed to complete referencing relations.
private boolean clipIncompleteEntities;
private boolean completeWays;
private boolean completeRelations;
private boolean storeEntities;
private boolean cascadingRelations;
private SimpleObjectStore<WayContainer> allWays;
private SimpleObjectStore<NodeContainer> allNodes;
// this duplicates as a container for held-back relations in the cascadingRelations case:
private SimpleObjectStore<RelationContainer> allRelations;
/**
* Creates a new instance.
*
* @param idTrackerType
* Defines the id tracker implementation to use.
* @param clipIncompleteEntities
* If true, entities referring to non-existent entities will be
* modified to ensure referential integrity. For example, ways
* will be modified to only include nodes inside the area.
* @param completeWays
* Include all nodes for ways which have at least one node inside
* the filtered area.
* @param completeRelations
* Include all relations referenced by other relations which have
* members inside the filtered area.
* @param cascadingRelations
* Make sure that a relation referencing a relation which is included
* will also be included.
*/
public AreaFilter(
IdTrackerType idTrackerType, boolean clipIncompleteEntities, boolean completeWays,
boolean completeRelations, boolean cascadingRelations) {
this.clipIncompleteEntities = clipIncompleteEntities;
// Allowing complete relations without complete ways is very difficult and not allowed for
// now.
this.completeWays = completeWays || completeRelations;
this.completeRelations = completeRelations;
// cascadingRelations is included for free with any of the complete options so you don't
// need it if those are set.
this.cascadingRelations = cascadingRelations && !completeRelations && !completeWays;
availableNodes = IdTrackerFactory.createInstance(idTrackerType);
requiredNodes = IdTrackerFactory.createInstance(idTrackerType);
availableWays = IdTrackerFactory.createInstance(idTrackerType);
requiredWays = IdTrackerFactory.createInstance(idTrackerType);
availableRelations = IdTrackerFactory.createInstance(idTrackerType);
requiredRelations = IdTrackerFactory.createInstance(idTrackerType);
// If either complete ways or complete relations are required, then all data must be stored
// during processing.
storeEntities = completeWays || completeRelations;
if (storeEntities) {
allNodes = new SimpleObjectStore<NodeContainer>(
new SingleClassObjectSerializationFactory(NodeContainer.class), "afn", true);
allWays = new SimpleObjectStore<WayContainer>(
new SingleClassObjectSerializationFactory(WayContainer.class), "afw", true);
allRelations =
new SimpleObjectStore<RelationContainer>(
new SingleClassObjectSerializationFactory(RelationContainer.class), "afr", true);
} else if (cascadingRelations) {
allRelations =
new SimpleObjectStore<RelationContainer>(
new SingleClassObjectSerializationFactory(RelationContainer.class), "afr", true);
}
}
/**
* {@inheritDoc}
*/
public void initialize(Map<String, Object> metaData) {
sink.initialize(metaData);
}
/**
* {@inheritDoc}
*/
public void process(EntityContainer entityContainer) {
// Ask the entity container to invoke the appropriate processing method
// for the entity type.
entityContainer.process(this);
}
/**
* {@inheritDoc}
*/
public void process(BoundContainer boundContainer) {
// By default, pass it on unchanged
sink.process(boundContainer);
}
/**
* Indicates if the node lies within the area required.
*
* @param node
* The node to be checked.
* @return True if the node lies within the area.
*/
protected abstract boolean isNodeWithinArea(Node node);
/**
* {@inheritDoc}
*/
public void process(NodeContainer container) {
Node node;
node = container.getEntity();
// Check if we're storing entities for later.
if (storeEntities) {
allNodes.add(container);
}
// Only add the node if it lies within the box boundaries.
if (isNodeWithinArea(node)) {
availableNodes.set(node.getId());
// If we're not storing entities, we pass it on immediately.
if (!storeEntities) {
emitNode(container);
}
}
}
/**
* {@inheritDoc}
*/
public void process(WayContainer container) {
Way way;
boolean inArea;
way = container.getEntity();
// Check if we're storing entities for later.
if (storeEntities) {
allWays.add(container);
}
// First look through all the nodes to see if any are within the filtered area
inArea = false;
for (WayNode nodeReference : way.getWayNodes()) {
if (availableNodes.get(nodeReference.getNodeId())) {
inArea = true;
break;
}
}
// If the way has at least one node in the filtered area.
if (inArea) {
availableWays.set(way.getId());
// If complete ways are desired, mark any unavailable nodes as required.
if (completeWays) {
for (WayNode nodeReference : way.getWayNodes()) {
long nodeId = nodeReference.getNodeId();
if (!availableNodes.get(nodeId)) {
requiredNodes.set(nodeId);
}
}
}
// If we're not storing entities, we pass it on immediately.
if (!storeEntities) {
emitWay(container);
}
}
}
/**
* {@inheritDoc}
*/
public void process(RelationContainer container) {
Relation relation;
boolean inArea;
boolean holdBackRelation;
relation = container.getEntity();
// First look through all the node and way members to see if any are within the filtered area
inArea = false;
holdBackRelation = false;
for (RelationMember member : relation.getMembers()) {
switch (member.getMemberType()) {
case Node:
inArea = availableNodes.get(member.getMemberId());
break;
case Way:
inArea = availableWays.get(member.getMemberId());
break;
case Relation:
inArea = availableRelations.get(member.getMemberId());
break;
default:
break;
}
if (inArea) {
break;
}
}
if (cascadingRelations) { // && referencesOtherRelation && (!inArea || clipIncompleteEntities)) {
holdBackRelation = true;
}
// Check if we're storing entities for later.
if (storeEntities || holdBackRelation) {
allRelations.add(container);
}
// If the relation has at least one member in the filtered area.
if (inArea) {
availableRelations.set(relation.getId());
// If we're not storing entities, we pass it on immediately.
if (!storeEntities && !holdBackRelation) {
emitRelation(container);
}
}
}
/**
* Sends a node to the sink. This will perform any necessary transformations on the node before
* sending it.
*
* @param nodeContainer
* Node to be sent.
*/
private void emitNode(NodeContainer nodeContainer) {
sink.process(nodeContainer);
}
/**
* Sends a way to the sink. This will perform any necessary transformations on the way before
* sending it.
*
* @param wayContainer
* Way to be sent.
*/
private void emitWay(WayContainer wayContainer) {
if (clipIncompleteEntities) {
WayContainer filteredWayContainer;
Way filteredWay;
filteredWayContainer = wayContainer.getWriteableInstance();
filteredWay = filteredWayContainer.getEntity();
// Remove node references for nodes that are unavailable.
for (Iterator<WayNode> i = filteredWay.getWayNodes().iterator(); i.hasNext();) {
WayNode nodeReference = i.next();
if (!availableNodes.get(nodeReference.getNodeId())) {
i.remove();
}
}
// Only add ways that contain nodes.
if (filteredWay.getWayNodes().size() > 0) {
sink.process(filteredWayContainer);
}
} else {
sink.process(wayContainer);
}
}
/**
* Sends a relation to the sink. This will perform any necessary transformations on the way before
* sending it.
*
* @param relationContainer
* Relation to be sent.
*/
private void emitRelation(RelationContainer relationContainer) {
if (clipIncompleteEntities) {
RelationContainer filteredRelationContainer;
Relation filteredRelation;
filteredRelationContainer = relationContainer.getWriteableInstance();
filteredRelation = filteredRelationContainer.getEntity();
// Remove members for entities that are unavailable.
for (Iterator<RelationMember> i = filteredRelation.getMembers().iterator(); i.hasNext();) {
RelationMember member = i.next();
EntityType memberType;
long memberId;
memberType = member.getMemberType();
memberId = member.getMemberId();
switch (memberType) {
case Node:
if (!availableNodes.get(memberId)) {
i.remove();
}
break;
case Way:
if (!availableWays.get(memberId)) {
i.remove();
}
break;
case Relation:
if (!availableRelations.get(memberId)) {
i.remove();
}
break;
default:
break;
}
}
// Only add relations that contain entities.
if (filteredRelation.getMembers().size() > 0) {
sink.process(filteredRelationContainer);
}
} else {
sink.process(relationContainer);
}
}
private boolean selectParentRelationsPass() {
try (ReleasableIterator<RelationContainer> i = allRelations.iterate()) {
int selectionCount;
selectionCount = 0;
while (i.hasNext()) {
Relation relation = i.next().getEntity();
long relationId = relation.getId();
// Ignore relations that have already been selected.
if (!availableRelations.get(relationId)) {
// This relation becomes an available relation if one of its member
// relations is also available.
for (RelationMember member : relation.getMembers()) {
if (member.getMemberType().equals(EntityType.Relation)) {
if (availableRelations.get(member.getMemberId())) {
availableRelations.set(relationId);
selectionCount++;
}
}
}
}
}
return selectionCount > 0;
}
}
/**
* Walk up the relation tree. This means iterating through relations until all parent relations
* of existing relations are marked in the available list. We may have to do this multiple times
* depending on the nesting level of relations.
*/
private void selectParentRelations() {
boolean selectionsMade;
do {
selectionsMade = selectParentRelationsPass();
} while (selectionsMade);
}
/**
* Select all relation members of type relation for existing selected relations. This may need
* to be called several times until all children are selected.
*
* @return True if additional selections were made an another pass is needed.
*/
private boolean selectChildRelationsPass() {
try (ReleasableIterator<RelationContainer> i = allRelations.iterate()) {
int selectionCount;
selectionCount = 0;
while (i.hasNext()) {
Relation relation = i.next().getEntity();
long relationId = relation.getId();
// Only examine available relations.
if (availableRelations.get(relationId)) {
// Select the child if it hasn't already been selected.
for (RelationMember member : relation.getMembers()) {
if (member.getMemberType().equals(EntityType.Relation)) {
long memberId = member.getMemberId();
if (!availableRelations.get(memberId)) {
availableRelations.set(memberId);
selectionCount++;
}
}
}
}
}
return selectionCount > 0;
}
}
/**
* Select all relation members of type node or way for existing selected relations.
*/
private void selectChildNonRelationsPass() {
try (ReleasableIterator<RelationContainer> i = allRelations.iterate()) {
while (i.hasNext()) {
Relation relation = i.next().getEntity();
long relationId = relation.getId();
// Only examine available relations.
if (availableRelations.get(relationId)) {
// Select the member if it hasn't already been selected.
for (RelationMember member : relation.getMembers()) {
switch (member.getMemberType()) {
case Node:
availableNodes.set(member.getMemberId());
break;
case Way:
availableWays.set(member.getMemberId());
break;
default:
break;
}
}
}
}
}
}
/**
* Select all nodes within already selected ways.
*/
private void selectWayNodes() {
try (ReleasableIterator<WayContainer> i = allWays.iterate()) {
while (i.hasNext()) {
Way way = i.next().getEntity();
long wayId = way.getId();
// Only examine available relations.
if (availableWays.get(wayId)) {
// Select all nodes within the way.
for (WayNode wayNode : way.getWayNodes()) {
availableNodes.set(wayNode.getNodeId());
}
}
}
}
}
private void buildCompleteRelations() {
boolean selectionsMade;
// Select all child relation members of type relation.
do {
selectionsMade = selectChildRelationsPass();
} while (selectionsMade);
// Select all child relation members of type way or node.
selectChildNonRelationsPass();
// Select all way nodes of existing nodes.
selectWayNodes();
}
private void pumpNodesToSink() {
try (ReleasableIterator<NodeContainer> i = allNodes.iterate()) {
while (i.hasNext()) {
NodeContainer nodeContainer = i.next();
if (availableNodes.get(nodeContainer.getEntity().getId())) {
emitNode(nodeContainer);
}
}
}
}
private void pumpWaysToSink() {
try (ReleasableIterator<WayContainer> i = allWays.iterate()) {
while (i.hasNext()) {
WayContainer wayContainer = i.next();
if (availableWays.get(wayContainer.getEntity().getId())) {
emitWay(wayContainer);
}
}
}
}
private void pumpRelationsToSink() {
try (ReleasableIterator<RelationContainer> i = allRelations.iterate()) {
while (i.hasNext()) {
RelationContainer relationContainer = i.next();
if (availableRelations.get(relationContainer.getEntity().getId())) {
emitRelation(relationContainer);
}
}
}
}
/**
* {@inheritDoc}
*/
public void complete() {
// If we've stored entities temporarily, we now need to forward the selected ones to the output.
if (storeEntities) {
// Select all parents of current relations.
selectParentRelations();
// Merge required ids into available ids.
availableNodes.setAll(requiredNodes);
availableWays.setAll(requiredWays);
availableRelations.setAll(requiredRelations);
requiredNodes = null;
requiredWays = null;
requiredRelations = null;
if (completeRelations) {
buildCompleteRelations();
}
// Send the selected entities to the output.
pumpNodesToSink();
pumpWaysToSink();
pumpRelationsToSink();
} else if (cascadingRelations) {
// Select all parents of current relations.
selectParentRelations();
availableRelations.setAll(requiredRelations);
// nodes, ways, and relations *not* referencing other relations will already have
// been written in this mode. we only pump the remaining ones, relations that
// reference other relations. this may result in an un-ordered relation stream.
pumpRelationsToSink();
}
sink.complete();
}
/**
* {@inheritDoc}
*/
public void close() {
if (allNodes != null) {
allNodes.close();
}
if (allWays != null) {
allWays.close();
}
if (allRelations != null) {
allRelations.close();
}
sink.close();
}
/**
* {@inheritDoc}
*/
public void setSink(Sink sink) {
this.sink = sink;
}
}