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;
+ }
+}