From 6c987dd8bb0e87df4900e9a7a605f6a13ecfce7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81adys=C5=82aw=20W=C5=82odarski?= <69821285+ladwlo@users.noreply.github.com> Date: Wed, 7 Oct 2020 23:36:12 +0200 Subject: [PATCH] New check: TunnelBridgeHeightLimitCheck (#343) (#350) * New check: TunnelBridgeHeightLimitCheck * Replacing deprecated language * CheckStyle fixes * Making highway type filter configurable (code review remark) * CheckStyle * Flagging all Edges of a related OSM Way (code review remark) * Adding the tunnel/bridge maxheight check to the list of available checks --- config/configuration.json | 10 + docs/available_checks.md | 1 + docs/checks/tunnelBridgeHeightLimitCheck.md | 32 +++ .../tag/TunnelBridgeHeightLimitCheck.java | 164 +++++++++++++++ .../tag/TunnelBridgeHeightLimitCheckTest.java | 92 +++++++++ .../TunnelBridgeHeightLimitCheckTestRule.java | 193 ++++++++++++++++++ 6 files changed, 492 insertions(+) create mode 100644 docs/checks/tunnelBridgeHeightLimitCheck.md create mode 100644 src/main/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheck.java create mode 100644 src/test/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheckTest.java create mode 100644 src/test/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheckTestRule.java diff --git a/config/configuration.json b/config/configuration.json index 95cc58a56..72f1960b7 100644 --- a/config/configuration.json +++ b/config/configuration.json @@ -1120,5 +1120,15 @@ "difficulty": "NORMAL", "defaultPriority": "LOW" } + }, + "TunnelBridgeHeightLimitCheck": { + "highway.filter":"highway->motorway_link,trunk_link,primary,primary_link,secondary,secondary_link", + "challenge": { + "description": "Tunnels, covered roads and roads crossing with bridges should have a 'maxheight' or 'maxheight:physical' tags.", + "blurb": "Missing maxheight tag", + "instruction": "Open your favorite editor and check the instruction for the task instructions, then add missing tags to each of the flagged ways.", + "difficulty": "NORMAL", + "defaultPriority": "LOW" + } } } diff --git a/docs/available_checks.md b/docs/available_checks.md index fbda977d1..80f25c4a3 100644 --- a/docs/available_checks.md +++ b/docs/available_checks.md @@ -83,6 +83,7 @@ This document is a list of tables with a description and link to documentation f | [RoadNameSpellingConsistencyCheck](checks/RoadNameSpellingConsistencyCheck.md) | The purpose of this check is to identify road segments that have a name Tag with a different spelling from that of other segments of the same road. This check is primarily meant to catch small errors in spelling, such as a missing letter, letter accent mixups, or capitalization errors. | | ShortNameCheck | The short name check will validate that any and all names contain at least 2 letters in the name. | | [StreetNameIntegersOnlyCheck](checks/streetNameIntegersOnlyCheck.md) | The purpose of this check is to identify streets whose names contain integers only. | +| [TunnelBridgeHeightLimitCheck](checks/tunnelBridgeHeightLimitCheck.md) | The purpose of this check is to identify roads with limited vertical clearance which do not have a maxheight tag. | | [UnusualLayerTagsCheck](checks/unusualLayerTagsCheck.md) | The purpose of this check is to identify layer tag values when accompanied by invalid tunnel and bridge tags. | | [ConditionalRestrictionCheck](checks/conditionalRestrictionCheck.md) | The purpose of this check is to identify elements that have a :conditional tag that does not respect the established format. | diff --git a/docs/checks/tunnelBridgeHeightLimitCheck.md b/docs/checks/tunnelBridgeHeightLimitCheck.md new file mode 100644 index 000000000..28b82b311 --- /dev/null +++ b/docs/checks/tunnelBridgeHeightLimitCheck.md @@ -0,0 +1,32 @@ +# Tunnel/Bridge Height Limit Check + +#### Description + +This check identifies roads of limited vertical clearance (tunnels, covered roads, and roads crossed by bridges) that do not have 'maxheight' or 'maxheight:physical' tags set. +The valid highway classes for this check (configurable) are: +MOTORWAY_LINK, TRUNK_LINK, PRIMARY, PRIMARY_LINK, SECONDARY, SECONDARY_LINK + +This is a port of Osmose check #7130. + +#### Live Examples + +1. Way [id:17659494](https://www.openstreetmap.org/way/17659494) is a tunnel without maxheight tag. +2. Way [id:601626442](https://www.openstreetmap.org/way/601626442) is a covered road without maxheight tag. +3. Way [id:174319379](https://www.openstreetmap.org/way/174319379) passes under a pair of bridges: +[id:11778321](https://www.openstreetmap.org/way/11778321) and [id:11778325](https://www.openstreetmap.org/way/11778325). +But it does not have a maxheight tag. + +#### Code Review + +The check ensures that the Atlas object being evaluated is an [Edge](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Edge.java) +which is a _master_ edge (positive ID) and its related OSM Way ID has not yet been flagged. This is done in the validation step. +Once an edge is found to be valid, the verification step first checks (using standard Validators) if the edge is a _tunnel_ or a _covered_ road, +it has an expected _highway_ class and no _maxheight_ tags are present. The edge is then flagged. +If the edge has not been flagged, but it is tagged as a _bridge_, then any edges that intersect the bridge's bounding box are retrieved. +For each of those new target objects, the algorithm verifies the same criteria (the edge is a master edge, with OSM ID that has not yet been flagged, +it is of expected highway class and does not have maxheight tags). Additionally, it checks if the edge's polyline properly crosses the bridge's polyline, +to exclude any edges that might have been caught in the bounding box but do not actually intersect with the bridge, or only touch it at either end. +For each edge which passed through all these filters, a separate fixing instruction is added and a single flag is created. + +To learn more about the code, please look at the comments in the source code for the check. +[TunnelBridgeHeightLimitCheck.java](../../src/main/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheck.java) diff --git a/src/main/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheck.java b/src/main/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheck.java new file mode 100644 index 000000000..ee11232f5 --- /dev/null +++ b/src/main/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheck.java @@ -0,0 +1,164 @@ +package org.openstreetmap.atlas.checks.validation.tag; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.openstreetmap.atlas.checks.base.BaseCheck; +import org.openstreetmap.atlas.checks.flag.CheckFlag; +import org.openstreetmap.atlas.geography.PolyLine; +import org.openstreetmap.atlas.geography.atlas.items.AtlasObject; +import org.openstreetmap.atlas.geography.atlas.items.Edge; +import org.openstreetmap.atlas.geography.atlas.walker.OsmWayWalker; +import org.openstreetmap.atlas.tags.BridgeTag; +import org.openstreetmap.atlas.tags.CoveredTag; +import org.openstreetmap.atlas.tags.MaxHeightTag; +import org.openstreetmap.atlas.tags.TunnelTag; +import org.openstreetmap.atlas.tags.annotations.validation.Validators; +import org.openstreetmap.atlas.tags.filters.TaggableFilter; +import org.openstreetmap.atlas.utilities.collections.Iterables; +import org.openstreetmap.atlas.utilities.configuration.Configuration; + +/** + * Flags highways (of certain classes) which should have a 'maxheight' or 'maxheight:*' tag but do + * not have either. This is a port of Osmose check #7130.
+ * Target objects:
+ * 1. Tunnels
+ * 2. Covered ways
+ * 3. Ways passing under bridges
+ * Target highway classes (configurable):
+ * MOTORWAY_LINK, TRUNK_LINK, PRIMARY, PRIMARY_LINK, SECONDARY, SECONDARY_LINK + * + * @author ladwlo + */ +public class TunnelBridgeHeightLimitCheck extends BaseCheck +{ + + private static final long serialVersionUID = 7912181047816225229L; + + private static final String FALLBACK_INSTRUCTION_TEMPLATE = "Way {0,number,#} %s but vehicle height limit is not specified. Add a 'maxheight' or 'maxheight:physical' tag according to an existing legal or physical restriction."; + private static final int TUNNEL_CASE_INDEX = 0; + private static final int COVERED_CASE_INDEX = 1; + private static final int BRIDGE_CASE_INDEX = 2; + private static final List FALLBACK_CASES = Arrays.asList("is a tunnel", "is covered", + "passes under bridge ({1,number,#})"); + private static final List FALLBACK_INSTRUCTIONS = FALLBACK_CASES.stream() + .map(caseDescription -> String.format(FALLBACK_INSTRUCTION_TEMPLATE, caseDescription)) + .collect(Collectors.toList()); + private static final String MAXHEIGHT_PHYSICAL = "maxheight:physical"; + private static final String HIGHWAY_FILTER_DEFAULT = "highway->motorway_link,trunk_link,primary,primary_link,secondary,secondary_link"; + + private final TaggableFilter highwayFilter; + + /** + * Default constructor. + * + * @param configuration + * the JSON configuration for this check + */ + public TunnelBridgeHeightLimitCheck(final Configuration configuration) + { + super(configuration); + this.highwayFilter = configurationValue(configuration, "highway.filter", + HIGHWAY_FILTER_DEFAULT, TaggableFilter::forDefinition); + } + + /** + * This function will validate if the supplied atlas object is valid for the check. + * + * @param object + * the atlas object supplied by the Atlas-Checks framework for evaluation + * @return {@code true} if this object should be checked + */ + @Override + public boolean validCheckForObject(final AtlasObject object) + { + return object instanceof Edge && ((Edge) object).isMainEdge() + && !isFlagged(object.getOsmIdentifier()); + } + + /** + * This is the actual function that will check to see whether the object needs to be flagged. + * + * @param object + * the atlas object supplied by the Atlas-Checks framework for evaluation + * @return an optional {@link CheckFlag} object that + */ + @Override + protected Optional flag(final AtlasObject object) + { + // case 1 (tunnel) & 2 (covered highway) + if ((TunnelTag.isTunnel(object) || this.isCovered(object)) + && this.isHighwayWithoutMaxHeight(object)) + { + final Long osmId = object.getOsmIdentifier(); + markAsFlagged(osmId); + final Set edgesToFlag = new OsmWayWalker((Edge) object).collectEdges(); + final int instructionIndex = TunnelTag.isTunnel(object) ? TUNNEL_CASE_INDEX + : COVERED_CASE_INDEX; + final String instruction = getLocalizedInstruction(instructionIndex, osmId); + final CheckFlag flag = createFlag(edgesToFlag, instruction); + return Optional.of(flag); + } + // case 3 (road passing under bridge) + if (BridgeTag.isBridge(object)) + { + final Edge bridge = (Edge) object; + final PolyLine bridgeAsPolyLine = bridge.asPolyLine(); + final Set wayIdsToFlag = new HashSet<>(); + final Set edgesToFlag = new HashSet<>(); + Iterables.stream(bridge.getAtlas().edgesIntersecting(bridge.bounds())) + .filter(edge -> edge.isMainEdge() + && edge.getOsmIdentifier() != bridge.getOsmIdentifier() + && !isFlagged(edge.getOsmIdentifier()) + && this.isHighwayWithoutMaxHeight(edge) + && this.edgeCrossesBridge(edge.asPolyLine(), bridgeAsPolyLine)) + .forEach(edge -> + { + final long wayId = edge.getOsmIdentifier(); + markAsFlagged(wayId); + wayIdsToFlag.add(wayId); + edgesToFlag.addAll(new OsmWayWalker(edge).collectEdges()); + }); + if (!wayIdsToFlag.isEmpty()) + { + final CheckFlag checkFlag = new CheckFlag(getTaskIdentifier(bridge)); + wayIdsToFlag.forEach( + wayId -> checkFlag.addInstruction(getLocalizedInstruction(BRIDGE_CASE_INDEX, + wayId, bridge.getOsmIdentifier()))); + checkFlag.addObjects(edgesToFlag); + return Optional.of(checkFlag); + } + } + return Optional.empty(); + } + + @Override + protected List getFallbackInstructions() + { + return FALLBACK_INSTRUCTIONS; + } + + // check if the two polylines intersect at any location other than the bridge's endpoints + private boolean edgeCrossesBridge(final PolyLine edge, final PolyLine bridge) + { + return edge.intersections(bridge).stream() + .anyMatch(loc -> !loc.equals(bridge.first()) && !loc.equals(bridge.last())); + } + + private boolean isCovered(final AtlasObject object) + { + return Validators.isOfType(object, CoveredTag.class, CoveredTag.YES, CoveredTag.ARCADE, + CoveredTag.COLONNADE); + } + + private boolean isHighwayWithoutMaxHeight(final AtlasObject object) + { + return this.highwayFilter.test(object) + && !Validators.hasValuesFor(object, MaxHeightTag.class) + && object.getTag(MAXHEIGHT_PHYSICAL).isEmpty(); + } +} diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheckTest.java b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheckTest.java new file mode 100644 index 000000000..9f7ba41a0 --- /dev/null +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheckTest.java @@ -0,0 +1,92 @@ +package org.openstreetmap.atlas.checks.validation.tag; + +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; + +/** + * Test cases for {@link TunnelBridgeHeightLimitCheck} + * + * @author ladwlo + */ +public class TunnelBridgeHeightLimitCheckTest +{ + + @Rule + public TunnelBridgeHeightLimitCheckTestRule setup = new TunnelBridgeHeightLimitCheckTestRule(); + + @Rule + public ConsumerBasedExpectedCheckVerifier verifier = new ConsumerBasedExpectedCheckVerifier(); + + private final Configuration inlineConfiguration = ConfigurationResolver + .inlineConfiguration("{\"TunnelBridgeHeightLimitCheck\":{}}"); + + @Test + public void bidirectionalTunnelWithoutMaxHeightIsFlaggedOnce() + { + this.verifier.actual(this.setup.getBidirectionalTunnelWithoutMaxHeight(), + new TunnelBridgeHeightLimitCheck(this.inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void bridgeWithoutCrossingRoadsIsIgnored() + { + this.verifier.actual(this.setup.getBridgeWithNoCrossingRoads(), + new TunnelBridgeHeightLimitCheck(this.inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void coveredRoadWithoutMaxHeightSplitIntoTwoEdgesIsFlaggedOnce() + { + this.verifier.actual(this.setup.getCoveredRoadWithoutMaxHeightSplitIntoTwoEdges(), + new TunnelBridgeHeightLimitCheck(this.inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(1, flags.size())); + } + + @Test + public void lowClassTunnelWithoutMaxHeightIsIgnored() + { + this.verifier.actual(this.setup.getLowClassTunnelWithoutMaxHeight(), + new TunnelBridgeHeightLimitCheck(this.inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void roadWithoutMaxHeightPassingUnderBridgeIsFlagged() + { + this.verifier.actual(this.setup.getRoadsPassingUnderBridge(), + new TunnelBridgeHeightLimitCheck(this.inlineConfiguration)); + this.verifier.globallyVerify(flags -> + { + Assert.assertEquals(1, flags.size()); + // task is created for the expected bridge object + Assert.assertEquals("1000000001", flags.get(0).getIdentifier()); + // both edges of the flagged Way are included + Assert.assertEquals(2, flags.get(0).getFlaggedObjects().size()); + // both edges have the expected OSM ID + flags.get(0).getFlaggedObjects().forEach(object -> Assert.assertEquals("4000", + object.getProperties().get("osmIdentifier"))); + }); + } + + @Test + public void tunnelAndCoveredRoadWithMaxHeightAreIgnored() + { + this.verifier.actual(this.setup.getTunnelAndCoveredRoadWithMaxHeight(), + new TunnelBridgeHeightLimitCheck(this.inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } + + @Test + public void uncoveredRoadWithoutMaxHeightIsIgnored() + { + this.verifier.actual(this.setup.getUncoveredRoadWithoutMaxHeight(), + new TunnelBridgeHeightLimitCheck(this.inlineConfiguration)); + this.verifier.globallyVerify(flags -> Assert.assertEquals(0, flags.size())); + } +} diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheckTestRule.java b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheckTestRule.java new file mode 100644 index 000000000..80264051b --- /dev/null +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/TunnelBridgeHeightLimitCheckTestRule.java @@ -0,0 +1,193 @@ +package org.openstreetmap.atlas.checks.validation.tag; + +import static org.openstreetmap.atlas.utilities.testing.TestAtlas.Edge; +import static org.openstreetmap.atlas.utilities.testing.TestAtlas.Loc; +import static org.openstreetmap.atlas.utilities.testing.TestAtlas.Node; + +import org.openstreetmap.atlas.checks.base.checks.BaseTestRule; +import org.openstreetmap.atlas.geography.atlas.Atlas; +import org.openstreetmap.atlas.utilities.testing.TestAtlas; + +/** + * Test Atlases for {@link TunnelBridgeHeightLimitCheckTest} + * + * @author ladwlo + */ +public class TunnelBridgeHeightLimitCheckTestRule extends BaseTestRule +{ + + // bridge or standalone road/tunnel + private static final String LOC_1 = "47.0,-122.1"; + private static final String LOC_2 = "47.2,-122.1"; + // crossing edge + private static final String LOC_3 = "47.1,-122.0"; + private static final String LOC_4 = "47.1,-122.2"; + // edge with multiple crossings; way split into two edges + private static final String LOC_5 = "47.0,-122.0"; + private static final String LOC_6 = "47.1,-122.2"; + private static final String LOC_7 = "47.2,-122.0"; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = LOC_1)), + @Node(coordinates = @Loc(value = LOC_2)) }, + // edges + edges = { @Edge( + // master + id = "1000000001", coordinates = { @Loc(value = LOC_1), + @Loc(value = LOC_2) }, tags = { "highway=primary", "tunnel=yes" }), + @Edge( + // reversed + id = "-1000000001", coordinates = { @Loc(value = LOC_2), + @Loc(value = LOC_1) }, tags = { "highway=primary", + "tunnel=yes" }) }) + // positive case; should only flag master edge + private Atlas bidirectionalTunnelWithoutMaxHeight; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = LOC_1)), + @Node(coordinates = @Loc(value = LOC_2)), + @Node(coordinates = @Loc(value = LOC_5)), + @Node(coordinates = @Loc(value = LOC_7)) }, + // edges + edges = { + @Edge(id = "1000000001", coordinates = { @Loc(value = LOC_1), + @Loc(value = LOC_2) }, tags = { "highway=primary", "bridge=yes" }), + @Edge(id = "2000000001", coordinates = { @Loc(value = LOC_5), + @Loc(value = LOC_7) }, tags = { "highway=primary" }) }) + // negative case: no edges with violations + private Atlas bridgeWithNoCrossingRoads; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = LOC_5)), + @Node(coordinates = @Loc(value = LOC_6)), + @Node(coordinates = @Loc(value = LOC_7)) }, + // edges + edges = { @Edge( + // first part of way #1 + id = "1000000001", coordinates = { @Loc(value = LOC_5), + @Loc(value = LOC_6) }, tags = { "highway=primary", "covered=yes" }), + @Edge( + // second part of way #1 + id = "1000000002", coordinates = { @Loc(value = LOC_6), + @Loc(value = LOC_7) }, tags = { "highway=primary", + "covered=yes" }) }) + // positive case; should only flag given OSM ID once + private Atlas coveredRoadWithoutMaxHeightSplitIntoTwoEdges; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = LOC_1)), + @Node(coordinates = @Loc(value = LOC_2)) }, + // edges + edges = { @Edge(id = "1000000001", coordinates = { @Loc(value = LOC_1), + @Loc(value = LOC_2) }, tags = { "highway=tertiary", "tunnel=yes" }) }) + // negative case: highway class does not match + private Atlas lowClassTunnelWithoutMaxHeight; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = LOC_1)), + @Node(coordinates = @Loc(value = LOC_2)), + @Node(coordinates = @Loc(value = LOC_3)), + @Node(coordinates = @Loc(value = LOC_4)), + @Node(coordinates = @Loc(value = LOC_5)), + @Node(coordinates = @Loc(value = LOC_6)), + @Node(coordinates = @Loc(value = LOC_7)) }, + // edges + edges = { + @Edge(id = "1000000001", coordinates = { @Loc(value = LOC_1), + @Loc(value = LOC_2) }, tags = { "highway=primary", "bridge=yes" }), + // negative case: edge only touches the bridge (at one end) + @Edge(id = "2000000001", coordinates = { @Loc(value = LOC_1), + @Loc(value = LOC_5) }, tags = { "highway=primary" }), + // negative case: not a master edge + @Edge(id = "-2000000001", coordinates = { @Loc(value = LOC_5), + @Loc(value = LOC_1) }, tags = { "highway=primary" }), + // negative case: edge only touches the bridge (at the other end) + @Edge(id = "3000000001", coordinates = { @Loc(value = LOC_2), + @Loc(value = LOC_7) }, tags = { "highway=primary" }), + // positive case: edge crosses the bridge + @Edge(id = "4000000001", coordinates = { @Loc(value = LOC_5), + @Loc(value = LOC_6), + @Loc(value = LOC_7) }, tags = { "highway=primary" }), + // negative case: edge with the same OSM ID already flagged + @Edge(id = "4000000002", coordinates = { @Loc(value = LOC_7), + @Loc(value = LOC_6), + @Loc(value = LOC_5) }, tags = { "highway=primary" }), + // negative case: edge crossed the bridge but has a low class + @Edge(id = "5000000001", coordinates = { @Loc(value = LOC_5), + @Loc(value = LOC_6) }, tags = { "highway=tertiary" }), + // negative case: edge crosses the bridge but maxheight tag is present + @Edge(id = "6000000001", coordinates = { @Loc(value = LOC_6), + @Loc(value = LOC_7) }, tags = { "highway=primary", "maxheight=3.5" }) }) + // positive, should only flag one edge that matches all conditions + private Atlas roadsPassingUnderBridge; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = LOC_5)), + @Node(coordinates = @Loc(value = LOC_6)), + @Node(coordinates = @Loc(value = LOC_7)) }, + // edges + edges = { + @Edge(id = "1000000001", coordinates = { @Loc(value = LOC_5), + @Loc(value = LOC_6) }, tags = { "highway=primary", "tunnel=yes", + "maxheight=4.0" }), + @Edge(id = "2000000001", coordinates = { @Loc(value = LOC_6), + @Loc(value = LOC_7) }, tags = { "highway=primary", "covered=yes", + "maxheight:physical=3.8" }) }) + // negative case: has maxheight + private Atlas tunnelAndCoveredRoadWithMaxHeight; + + @TestAtlas( + // nodes + nodes = { @Node(coordinates = @Loc(value = LOC_5)), + @Node(coordinates = @Loc(value = LOC_6)), + @Node(coordinates = @Loc(value = LOC_7)) }, + // edges - two cases: one with covered=no and another without any covered tag + edges = { + @Edge(id = "1000000001", coordinates = { @Loc(value = LOC_5), + @Loc(value = LOC_6) }, tags = { "highway=primary", "covered=no" }), + @Edge(id = "2000000001", coordinates = { @Loc(value = LOC_6), + @Loc(value = LOC_7) }, tags = { "highway=primary" }) }) + // negative case: is not covered + private Atlas uncoveredRoadWithoutMaxHeight; + + public Atlas getBidirectionalTunnelWithoutMaxHeight() + { + return this.bidirectionalTunnelWithoutMaxHeight; + } + + public Atlas getBridgeWithNoCrossingRoads() + { + return this.bridgeWithNoCrossingRoads; + } + + public Atlas getCoveredRoadWithoutMaxHeightSplitIntoTwoEdges() + { + return this.coveredRoadWithoutMaxHeightSplitIntoTwoEdges; + } + + public Atlas getLowClassTunnelWithoutMaxHeight() + { + return this.lowClassTunnelWithoutMaxHeight; + } + + public Atlas getRoadsPassingUnderBridge() + { + return this.roadsPassingUnderBridge; + } + + public Atlas getTunnelAndCoveredRoadWithMaxHeight() + { + return this.tunnelAndCoveredRoadWithMaxHeight; + } + + public Atlas getUncoveredRoadWithoutMaxHeight() + { + return this.uncoveredRoadWithoutMaxHeight; + } +}