All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openstreetmap.atlas.checks.validation.linear.edges.SnakeRoadNetworkWalk Maven / Gradle / Ivy

package org.openstreetmap.atlas.checks.validation.linear.edges;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.openstreetmap.atlas.geography.Heading;
import org.openstreetmap.atlas.geography.atlas.items.AtlasObject;
import org.openstreetmap.atlas.geography.atlas.items.Edge;
import org.openstreetmap.atlas.geography.atlas.items.Node;
import org.openstreetmap.atlas.tags.names.NameTag;
import org.openstreetmap.atlas.tags.names.ReferenceTag;
import org.openstreetmap.atlas.utilities.collections.Iterables;
import org.openstreetmap.atlas.utilities.scalars.Angle;

/**
 * Keeps track of the network walk results for the {@link SnakeRoadCheck}
 *
 * @author mgostintsev
 */
public class SnakeRoadNetworkWalk
{
    private boolean isSnakeRoad;
    private final Optional roadName;
    private final Optional refTag;
    private long greatestEncounteredValence;
    private final TreeSet visitedEdges;
    private final Angle edgeHeadingDifferenceThreshold;

    // Keeps track of the directly connected edges to process. Call these friends.
    private final Queue directConnections;

    // Keeps track of the edges one layer away from the current edge to process. Call these friends
    // of friends.
    private final Set oneLayerRemovedConnections;

    protected SnakeRoadNetworkWalk(final Edge edge, final Angle threshold)
    {
        this.isSnakeRoad = false;
        this.roadName = edge.getTag(NameTag.KEY);
        this.refTag = edge.getTag(ReferenceTag.KEY);
        this.greatestEncounteredValence = 0;

        // We use a TreeSet here to order the edges by their Atlas identifier. This helps us easily
        // grab the first and last Edge making up the road and check connections for false
        // positives.
        this.visitedEdges = new TreeSet<>((one, two) ->
        {
            return Long.compare(one.getIdentifier(), two.getIdentifier());
        });
        this.visitedEdges.add(edge);
        this.directConnections = new LinkedList<>();
        this.oneLayerRemovedConnections = new HashSet<>();
        this.edgeHeadingDifferenceThreshold = threshold;
    }

    protected void addDirectConnections(final Set edges)
    {
        this.directConnections.addAll(edges);
    }

    /**
     * Checks if the difference in heading between the incoming and outgoing {@link Edge}s exceeds
     * the threshold
     *
     * @param incoming
     *            The incoming {@link Edge}
     * @param outgoing
     *            The outgoing {@link Edge}
     */
    protected void checkIfEdgeHeadingDifferenceExceedsThreshold(final Edge incoming,
            final Edge outgoing)
    {
        if (!this.isSnakeRoad())
        {
            final Optional incomingHeading = incoming.overallHeading();
            final Optional outgoingHeading = outgoing.overallHeading();
            if (incomingHeading.isPresent() && outgoingHeading.isPresent()
                    && incomingHeading.get().difference(outgoingHeading.get())
                            .isGreaterThanOrEqualTo(this.edgeHeadingDifferenceThreshold))
            {
                this.setSnakeRoadStatus(true);
            }
        }
    }

    protected void clearOneLayerRemovedConnections()
    {
        this.oneLayerRemovedConnections.clear();
    }

    /**
     * We filter false positives for two cases. 1) If the network walk has yielded a snake road with
     * a name, we check all of the connected roads to see if that name is shared across any of them.
     * If it is, highly likely that the snake road is a false positive, and we remove the snake road
     * designation. An example is when we flag a portion of highway that behaves as a snake road. 2)
     * We do the same for ref tags to avoid flagging section of highway that exhibit snake road
     * behavior.
     */
    protected void filterFalsePositives()
    {
        if (this.isSnakeRoad() && (this.hasRoadName() || this.hasRefTag()))
        {
            // Gather all connected edges for the first and last edge of this road
            final Set connections = new HashSet<>();
            connections.addAll(this.getMainEdgesForConnectedEdgesOfDifferentWays(
                    (Edge) this.getVisitedEdges().first()));
            connections.addAll(this.getMainEdgesForConnectedEdgesOfDifferentWays(
                    (Edge) this.getVisitedEdges().last()));

            // Check their connections for connected names and ref tags
            for (final Edge connection : connections)
            {
                final Optional connectionName = connection.getTag(NameTag.KEY);
                final Optional refTag = connection.getTag(ReferenceTag.KEY);
                if (connectionName.equals(this.getRoadName()) || refTag.equals(this.getRefTag()))
                {
                    this.setSnakeRoadStatus(false);
                    break;
                }
            }
        }
    }

    /**
     * Returns all connected main, non-visited {@link Edge}s that are a continuation of the same OSM
     * way
     *
     * @param edge
     *            the {@link Edge} from which we're seeking connections
     * @return a set of {@link Edge}s
     */
    protected Set getConnectedMainEdgeOfTheSameWay(final Edge edge)
    {
        return edge.connectedEdges().stream()
                .filter(connection -> connection.isMainEdge()
                        && connection.getOsmIdentifier() == edge.getOsmIdentifier()
                        && !this.visitedEdges.contains(connection))
                .collect(Collectors.toSet());
    }

    protected Queue getDirectConnections()
    {
        return this.directConnections;
    }

    protected long getGreatestEncounteredValence()
    {
        return this.greatestEncounteredValence;
    }

    protected Set getOneLayerRemovedConnections()
    {
        return this.oneLayerRemovedConnections;
    }

    protected TreeSet getVisitedEdges()
    {
        return this.visitedEdges;
    }

    protected boolean isSnakeRoad()
    {
        return this.isSnakeRoad;
    }

    protected void populateOneLayerRemovedConnections(final Set edges)
    {
        this.oneLayerRemovedConnections.addAll(edges);
    }

    /**
     * Adds the given {@link Edge} to the visited set and updates the greatest valence value
     *
     * @param comingFrom
     *            the {@link Edge} we are coming from
     * @param comingTo
     *            the {@link Edge} we are coming to
     */
    protected void visitEdge(final Edge comingFrom, final Edge comingTo)
    {
        this.visitedEdges.add(comingTo);
        this.setGreatestValence(comingTo.start());
        this.setGreatestValence(comingTo.end());
    }

    /**
     * Returns all connected main {@link Edge}s that are NOT part of the same way as the given
     * target {@link Edge}
     *
     * @param edge
     *            the target {@link Edge} for which we're seeking connections
     * @return the {@link Set} of {@link Edge}s we found
     */
    private Set getMainEdgesForConnectedEdgesOfDifferentWays(final Edge edge)
    {
        return Iterables.stream(edge.connectedEdges()).filter(candidate -> candidate.isMainEdge()
                && candidate.getOsmIdentifier() != edge.getOsmIdentifier()).collectToSet();
    }

    private Optional getRefTag()
    {
        return this.refTag;
    }

    private Optional getRoadName()
    {
        return this.roadName;
    }

    private boolean hasRefTag()
    {
        return this.refTag.isPresent();
    }

    private boolean hasRoadName()
    {
        return this.roadName.isPresent();
    }

    private void setGreatestValence(final Node node)
    {
        final long valence = node.valence();
        if (valence > this.greatestEncounteredValence)
        {
            this.greatestEncounteredValence = valence;
        }
    }

    private void setSnakeRoadStatus(final boolean value)
    {
        this.isSnakeRoad = value;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy