Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[new] Highway Intersection Check #643

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/available_checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,4 @@ This document is a list of tables with a description and link to documentation f
| [MalformedPolyLineCheck](checks/malformedPolyLineCheck.md) | This check identifies lines that have only one point, or none, and the ones that are too long. |
| [SelfIntersectingPolylineCheck](checks/selfIntersectingPolylineCheck.md) | The purpose of this check is to identify all edges/lines/areas in Atlas that have self-intersecting polylines, or geometries that intersects itself in some form. |
| [WaterWayCheck](checks/waterWayCheck.md) | This check finds closed waterways (circular water motion), waterways without a place for water to go (a "sink"), crossing waterways, and waterways that go uphill (requires elevation data). |
| [HighwayIntersectionCheck](checks/highwayIntersectionCheck.md) | The purpose of this check is to identify highways intersecting power or waterway objects invalidly. |
19 changes: 19 additions & 0 deletions docs/checks/highwayIntersectionCheck.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Highway Intersection Check

#### Description

The purpose of this check is to flag highways intersecting power or waterway objects.

*dam* and *weir* waterway objects are not flagged, these type of waterways can intersect with highways.

For waterway objects the intersection with a highway having *leisure=slipway* or *ford=yes* tag is allowed, and they are not flagged.

#### Configuration

There are no configurables for this check.

#### Code Review

#### More Information

Please see the source code for HighwayIntersectionCheck here: [HighwayIntersectionCheck](../../src/main/java/org/openstreetmap/atlas/checks/validation/intersections/HighwayIntersectionCheck.java)
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package org.openstreetmap.atlas.checks.validation.intersections;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.openstreetmap.atlas.checks.atlas.predicates.TagPredicates;
import org.openstreetmap.atlas.checks.atlas.predicates.TypePredicates;
import org.openstreetmap.atlas.checks.base.BaseCheck;
import org.openstreetmap.atlas.checks.flag.CheckFlag;
import org.openstreetmap.atlas.geography.Location;
import org.openstreetmap.atlas.geography.PolyLine;
import org.openstreetmap.atlas.geography.Rectangle;
import org.openstreetmap.atlas.geography.atlas.Atlas;
import org.openstreetmap.atlas.geography.atlas.items.AtlasObject;
import org.openstreetmap.atlas.geography.atlas.items.Edge;
import org.openstreetmap.atlas.tags.FordTag;
import org.openstreetmap.atlas.tags.HighwayTag;
import org.openstreetmap.atlas.tags.LeisureTag;
import org.openstreetmap.atlas.tags.WaterwayTag;
import org.openstreetmap.atlas.tags.annotations.validation.Validators;
import org.openstreetmap.atlas.utilities.collections.Iterables;
import org.openstreetmap.atlas.utilities.configuration.Configuration;

/**
* Flags waterway and power line edge items that are crossed by navigable edges (having way specific
* highway tag). If the way is a waterway and the crossing way has {@code ford=yes} or
* {@code leisure=slipway} tags, then the crossing is accepted. {@code dam} and {@code weir}
* waterways are not checked, those type of ways can cross other highways.
*
* @author pako.todea
*/
public class HighwayIntersectionCheck extends BaseCheck<Long>
{

private static final long serialVersionUID = 1L;
private static final String INSTRUCTION_FORMAT = "The water/powerline with id {0,number,#} has invalid crossings "
+ "with {1}. A navigable way can not cross a power line or a water.";
private static final String INVALID_EDGE_FORMAT = "Edge {0,number,#} is crossing invalidly with {1}.";
private static final List<String> FALLBACK_INSTRUCTIONS = Arrays.asList(INSTRUCTION_FORMAT,
INVALID_EDGE_FORMAT);

/**
* Checks whether the given {@link Edge}s cross each other.
*
* @param edge
* {@link Edge} being crossed
* @param crossingEdge
* Crossing {@link Edge}
* @param intersection
* Intersection {@link Location}
* @return {@code true} if given {@link Edge}s cross each other
*/
private static boolean isCross(final Edge edge, final Edge crossingEdge,
final Location intersection)
{
final PolyLine edgeAsPolyLine = edge.asPolyLine();
final PolyLine crossingEdgeAsPolyLine = crossingEdge.asPolyLine();
return edgeAsPolyLine.contains(intersection)
&& crossingEdgeAsPolyLine.contains(intersection);
}

public HighwayIntersectionCheck(final Configuration configuration)
{
super(configuration);
}

@Override
public boolean validCheckForObject(final AtlasObject object)
{
return TypePredicates.IS_EDGE.test(object)
&& (TagPredicates.IS_POWER_LINE.test(object) || this.isWaterwayToCheck(object));
}

@Override
protected Optional<CheckFlag> flag(final AtlasObject object)
{
final Edge edge = (Edge) object;
final Atlas atlas = edge.getAtlas();
final Rectangle edgeBounds = edge.bounds();

final Set<Edge> invalidIntersectingEdges = Iterables
.asList(atlas.edgesIntersecting(edgeBounds, HighwayTag::isWayOnlyTag)).stream()
.filter(crossingEdge -> TagPredicates.IS_POWER_LINE.test(edge)
|| !FordTag.isYes(crossingEdge))
.filter(crossingEdge -> TagPredicates.IS_POWER_LINE.test(edge)
|| !Validators.isOfType(crossingEdge, LeisureTag.class, LeisureTag.SLIPWAY))
.filter(crossingEdge -> crossingEdge.getIdentifier() != edge.getIdentifier())
.filter(crossingEdge -> !crossingEdge.isReversedEdge(edge))
.filter(crossingEdge -> this.getIntersection(edge, crossingEdge).stream()
.anyMatch(intersection -> isCross(edge, crossingEdge, intersection)))
.collect(Collectors.toSet());

if (!invalidIntersectingEdges.isEmpty())
{
return this.createHighwayIntersectionCheckFlag(edge, invalidIntersectingEdges);
}

return Optional.empty();
}

@Override
protected List<String> getFallbackInstructions()
{
return FALLBACK_INSTRUCTIONS;
}

/**
* Function that creates highway intersection check flag.
*
* @param edge
* Atlas object.
* @param crossingEdges
* collected edges for a given atlas object.
* @return newly created highway intersection check flag including crossing edges locations.
*/
private Optional<CheckFlag> createHighwayIntersectionCheckFlag(final Edge edge,
final Set<Edge> crossingEdges)
{
final CheckFlag newFlag = new CheckFlag(this.getTaskIdentifier(edge));
this.markAsFlagged(edge.getIdentifier());
final Set<Location> points = crossingEdges.stream()
.filter(crossEdge -> crossEdge.getIdentifier() != edge.getIdentifier())
.flatMap(crossEdge -> this.getIntersection(edge, crossEdge).stream())
.collect(Collectors.toSet());
newFlag.addInstruction(
this.getLocalizedInstruction(0, edge.getOsmIdentifier(), crossingEdges.stream()
.map(AtlasObject::getOsmIdentifier).collect(Collectors.toSet())));
newFlag.addPoints(points);
newFlag.addObject(edge);
return Optional.of(newFlag);
}

/**
* This function returns the set of intersection locations for the given edges.
*
* @param firstEdge
* the first Edge
* @param secondEdge
* the second edge
* @return set of intersection locations.
*/
private Set<Location> getIntersection(final Edge firstEdge, final Edge secondEdge)
{
final PolyLine firstEdgeAsPolyLine = firstEdge.asPolyLine();
final PolyLine secondEdgeAsPolyLine = secondEdge.asPolyLine();
return firstEdgeAsPolyLine.intersections(secondEdgeAsPolyLine);
}

/**
* Checks whether the given {@link AtlasObject} is a waterway to check.
*
* @param edge
* the {@link AtlasObject} to check
* @return true if the given {@link AtlasObject} should be ckecked
*/
private boolean isWaterwayToCheck(final AtlasObject edge)
{
boolean validForCheck = false;
if (Validators.hasValuesFor(edge, WaterwayTag.class))
{
final Optional<WaterwayTag> waterwayTagValue = WaterwayTag.get(edge);
if (waterwayTagValue.isPresent())
{
validForCheck = !(HighwayTag.highwayTag(edge).isPresent()
&& (waterwayTagValue.get().name().equalsIgnoreCase("dam")
|| waterwayTagValue.get().name().equalsIgnoreCase("weir")));
}
}
return validForCheck;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.openstreetmap.atlas.checks.validation.intersections;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.openstreetmap.atlas.checks.configuration.ConfigurationResolver;
import org.openstreetmap.atlas.checks.validation.verifier.ConsumerBasedExpectedCheckVerifier;
import org.openstreetmap.atlas.utilities.configuration.Configuration;

/**
* @author pako.todea
*/
public class HighwayIntersectionCheckTest
{

@Rule
public HighwayIntersectionTestCaseRule setup = new HighwayIntersectionTestCaseRule();

@Rule
public ConsumerBasedExpectedCheckVerifier verifier = new ConsumerBasedExpectedCheckVerifier();

private final Configuration configuration = ConfigurationResolver.emptyConfiguration();

@Test
public void testInvalidCrossingHighwayLeisureEdges()
{
this.verifier.actual(this.setup.invalidCrossingWaterwayLeisureEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size()));
this.verifier.verify(flag -> Assert.assertEquals(2, flag.getFlaggedObjects().size()));
}

@Test
public void testInvalidCrossingHighwayPowerLineEdges()
{
this.verifier.actual(this.setup.invalidCrossingHighwayPowerLineEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size()));
this.verifier.verify(flag -> Assert.assertEquals(2, flag.getFlaggedObjects().size()));
}

@Test
public void testInvalidCrossingHighwayWaterEdges()
{
this.verifier.actual(this.setup.invalidCrossingHighwayWaterEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size()));
this.verifier.verify(flag -> Assert.assertEquals(2, flag.getFlaggedObjects().size()));
}

@Test
public void testInvalidCrossingPowerLineFordYesEdges()
{
this.verifier.actual(this.setup.invalidCrossingPowerLineFordYesEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size()));
this.verifier.verify(flag -> Assert.assertEquals(2, flag.getFlaggedObjects().size()));
}

@Test
public void testInvalidCrossingPowerLineLeisureSlipwayEdges()
{
this.verifier.actual(this.setup.invalidCrossingPowerLineLeisureSlipwayEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size()));
this.verifier.verify(flag -> Assert.assertEquals(2, flag.getFlaggedObjects().size()));
}

@Test
public void testInvalidMultipleCrossingHighwayWaterEdges()
{
this.verifier.actual(this.setup.invalidMultipleCrossingHighwayWaterEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size()));
this.verifier.verify(flag -> Assert.assertEquals(3, flag.getFlaggedObjects().size()));
}

@Test
public void testNoCrossingHighwayPowerLineEdges()
{
this.verifier.actual(this.setup.noCrossingHighwayPowerLineEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.verifyEmpty();
}

@Test
public void testNoCrossingHighwayWaterEdges()
{
this.verifier.actual(this.setup.noCrossingHighwayWaterEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.verifyEmpty();
}

@Test
public void testValidCrossingHighwayFordYesEdges()
{
this.verifier.actual(this.setup.validCrossingWaterwayFordYesEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.verifyEmpty();
}

@Test
public void testValidCrossingHighwayLeisureSlipwayEdges()
{
this.verifier.actual(this.setup.validCrossingWaterwayLeisureSlipwayEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.verifyEmpty();
}

@Test
public void testValidCrossingHighwayWaterwayDamEdges()
{
this.verifier.actual(this.setup.validCrossingHighwayWaterwayDamEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.verifyEmpty();
}

@Test
public void testValidCrossingHighwayWaterwayWeirEdges()
{
this.verifier.actual(this.setup.validCrossingHighwayWaterwayWeirEdges(),
new HighwayIntersectionCheck(this.configuration));
this.verifier.verifyEmpty();
}
}
Loading