diff --git a/config/configuration.json b/config/configuration.json index e40780491..df364154c 100644 --- a/config/configuration.json +++ b/config/configuration.json @@ -1048,6 +1048,14 @@ "instruction": "Please see instructions per feature for actions to take." } }, + "UnknownHighwayTagCheck": { + "challenge": { + "description": "highway tag is unknown and needs to be fixed.", + "blurb": "unknown highway tags.", + "instruction": "please change the highway tag to a known tag.", + "difficulty": "EASY" + } + }, "UnusualLayerTagsCheck": { "challenge": { "description": "Tunnels (negative), junctions (zero) and bridges (zero or positive) should have meaningful layer tags attached to them. A missing layer tag implies layer value 0. If there is an explicit layer tag, then it must be between -5 and 5.", diff --git a/docs/available_checks.md b/docs/available_checks.md index d602597ae..b09620079 100644 --- a/docs/available_checks.md +++ b/docs/available_checks.md @@ -90,6 +90,7 @@ This document is a list of tables with a description and link to documentation f | [StreetNameIntegersOnlyCheck](checks/streetNameIntegersOnlyCheck.md) | The purpose of this check is to identify streets whose names contain integers only. | | [TollValidationCheck](checks/tollValidationCheck) | The purpose of this check is to identify ways that need to have their toll tags investigated/added/removed. | [TunnelBridgeHeightLimitCheck](checks/tunnelBridgeHeightLimitCheck.md) | The purpose of this check is to identify roads with limited vertical clearance which do not have a maxheight tag. | +| [UnknownHighwayTagCheck](checks/unknownHighwayTagCheck.md) | This check attempts to flag all highway tags that are unknown to the [osm wiki page](https://wiki.openstreetmap.org/wiki/Key:highway). | | [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. | | [SourceMaxspeedCheck](checks/sourceMaxspeedCheck.md) | The purpose of this check is to identify elements that have a source:maxspeed tag that does not follow the tagging rules. | diff --git a/docs/checks/unknownHighwayTagCheck.md b/docs/checks/unknownHighwayTagCheck.md new file mode 100644 index 000000000..98d7a03b1 --- /dev/null +++ b/docs/checks/unknownHighwayTagCheck.md @@ -0,0 +1,14 @@ +# UnknownHighwayTagCheck + +#### Description +This check attempts to flag all highway tags that are unknown to the [osm wiki page](https://wiki.openstreetmap.org/wiki/Key:highway). + +#### Live Examples + +Highway tag does not exist on the OSM Wiki page. +1. [Node:313559095](https://www.openstreetmap.org/node/313559095) has a highway tag that is unknown (“highway=priority”) + +#### Code Review +This check evaluates [Nodes](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Node.java) and +[Edges](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/geography/atlas/items/Edge.java), and [HighwayTags](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/tags/HighwayTag.java), +it attempts to find items that contain unknown highway tags. \ No newline at end of file diff --git a/src/main/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheck.java b/src/main/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheck.java new file mode 100644 index 000000000..6a148b166 --- /dev/null +++ b/src/main/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheck.java @@ -0,0 +1,121 @@ +package org.openstreetmap.atlas.checks.validation.tag; + +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.base.BaseCheck; +import org.openstreetmap.atlas.checks.flag.CheckFlag; +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.geography.atlas.walker.OsmWayWalker; +import org.openstreetmap.atlas.tags.HighwayTag; +import org.openstreetmap.atlas.utilities.configuration.Configuration; + +/** + * This check flags Edges and Nodes that have highway tag values that are not contained within the + * set of known highway tags on the OSM Wiki page - https://wiki.openstreetmap.org/wiki/Key:highway + * + * @author v-garei + */ + +public class UnknownHighwayTagCheck extends BaseCheck +{ + private static final long serialVersionUID = -7273798399961675550L; + private static final String NODE_WITH_WAY_TAG_INSTRUCTIONS = "This Node's highway tag only belongs on a Way, please update this Node's tag with only Node related tags. List of tags can be found at https://wiki.openstreetmap.org/wiki/Key:highway"; + private static final String WAY_WITH_NODE_TAG_INSTRUCTIONS = "This Way's highway tag only belongs on a Node, please update this Way's tag with only Way related tags. List of tags can be found at https://wiki.openstreetmap.org/wiki/Key:highway"; + private static final String UNKNOWN_HIGHWAY_TAG_INSTRUCTIONS = "Please update highway tag to a known value."; + private static final List FALLBACK_INSTRUCTIONS = Arrays.asList( + NODE_WITH_WAY_TAG_INSTRUCTIONS, WAY_WITH_NODE_TAG_INSTRUCTIONS, + UNKNOWN_HIGHWAY_TAG_INSTRUCTIONS); + private static final Set allHighwayTags = Arrays.stream(HighwayTag.values()) + .map(Enum::toString).collect(Collectors.toSet()); + + /** + * The default constructor that must be supplied. The Atlas Checks framework will generate the + * checks with this constructor, supplying a configuration that can be used to adjust any + * parameters that the check uses during operation. + * + * @param configuration + * the JSON configuration for this check + */ + public UnknownHighwayTagCheck(final Configuration configuration) + { + super(configuration); + } + + /** + * 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()) || object instanceof Node) + && !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) + { + markAsFlagged(object.getOsmIdentifier()); + final Optional objectHighwayTag = object.getTag(HighwayTag.KEY); + + if (object instanceof Node && HighwayTag.isWayOnlyTag(object)) + { + return Optional.of(this.createFlag(object, + this.getLocalizedInstruction(0, object.getOsmIdentifier()))); + } + + if (object instanceof Edge && HighwayTag.isNodeOnlyTag(object)) + { + final Edge edgeInQuestion = ((Edge) object).getMainEdge(); + return Optional.of(this.createFlag(new OsmWayWalker(edgeInQuestion).collectEdges(), + this.getLocalizedInstruction(1, object.getOsmIdentifier()))); + } + + if (objectHighwayTag.isPresent() + && !this.isKnownHighwayTag(objectHighwayTag.get().toUpperCase())) + { + if (object instanceof Edge) + { + final Edge edgeInQuestion = ((Edge) object).getMainEdge(); + return Optional.of(this.createFlag(new OsmWayWalker(edgeInQuestion).collectEdges(), + this.getLocalizedInstruction(2, object.getOsmIdentifier()))); + } + return Optional.of(this.createFlag(object, + this.getLocalizedInstruction(2, object.getOsmIdentifier()))); + } + return Optional.empty(); + } + + @Override + protected List getFallbackInstructions() + { + return FALLBACK_INSTRUCTIONS; + } + + /** + * @param highwayTag + * object highway tag + * @return boolean for if the object's highway tag is contained within the known set of OSM + * highway tags. + */ + private boolean isKnownHighwayTag(final String highwayTag) + { + return allHighwayTags.contains(highwayTag); + } +} diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheckTest.java b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheckTest.java new file mode 100644 index 000000000..90f3ffa11 --- /dev/null +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheckTest.java @@ -0,0 +1,66 @@ +package org.openstreetmap.atlas.checks.validation.tag; + +import org.junit.Rule; +import org.junit.Test; +import org.openstreetmap.atlas.checks.configuration.ConfigurationResolver; +import org.openstreetmap.atlas.checks.validation.verifier.ConsumerBasedExpectedCheckVerifier; + +/** + * Tests for {@link UnknownHighwayTagCheck} + * + * @author v-garei + */ +public class UnknownHighwayTagCheckTest +{ + @Rule + public UnknownHighwayTagCheckTestRule setup = new UnknownHighwayTagCheckTestRule(); + + @Rule + public ConsumerBasedExpectedCheckVerifier verifier = new ConsumerBasedExpectedCheckVerifier(); + + private final UnknownHighwayTagCheck check = new UnknownHighwayTagCheck(ConfigurationResolver + .inlineConfiguration("{\"UnknownHighwayTagCheck\": " + "{" + "}}")); + + @Test + public void falsePositiveKnownHighwayTagOnEdge() + { + this.verifier.actual(this.setup.falsePositiveKnownHighwayTagOnEdge(), this.check); + this.verifier.verifyEmpty(); + } + + @Test + public void falsePositiveKnownHighwayTagOnNode() + { + this.verifier.actual(this.setup.falsePositiveKnownHighwayTagOnNode(), this.check); + this.verifier.verifyEmpty(); + } + + @Test + public void truePositiveEdgeTagOnNode() + { + this.verifier.actual(this.setup.truePositiveEdgeTagOnNode(), this.check); + this.verifier.verifyExpectedSize(1); + } + + @Test + public void truePositiveNodeTagOnEdge() + { + this.verifier.actual(this.setup.truePositiveNodeTagOnEdge(), this.check); + this.verifier.verifyExpectedSize(1); + } + + @Test + public void truePositiveUnknownHighwayTagOnEdge() + { + this.verifier.actual(this.setup.truePositiveUnknownHighwayTagOnEdge(), this.check); + this.verifier.verifyExpectedSize(1); + } + + @Test + public void truePositiveUnknownHighwayTagOnNode() + { + this.verifier.actual(this.setup.truePositiveUnknownHighwayTagOnNode(), this.check); + this.verifier.verifyExpectedSize(1); + } + +} diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheckTestRule.java b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheckTestRule.java new file mode 100644 index 000000000..38b2a52cb --- /dev/null +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/UnknownHighwayTagCheckTestRule.java @@ -0,0 +1,89 @@ +package org.openstreetmap.atlas.checks.validation.tag; + +import org.openstreetmap.atlas.geography.atlas.Atlas; +import org.openstreetmap.atlas.utilities.testing.CoreTestRule; +import org.openstreetmap.atlas.utilities.testing.TestAtlas; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Edge; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Loc; +import org.openstreetmap.atlas.utilities.testing.TestAtlas.Node; + +/** + * Tests for {@link UnknownHighwayTagCheck} + * + * @author v-garei + */ +public class UnknownHighwayTagCheckTestRule extends CoreTestRule +{ + + private static final String WAY1_NODE1 = "40.9130354, 29.4700719"; + private static final String WAY1_NODE2 = "40.9123887, 29.4698597"; + + @TestAtlas(nodes = { @Node(coordinates = @Loc(value = WAY1_NODE1)), + @Node(coordinates = @Loc(value = WAY1_NODE2)) }, edges = { + @Edge(id = "1000001", coordinates = { @Loc(value = WAY1_NODE1), + @Loc(value = WAY1_NODE2) }, tags = { "highway=trunk" }) }) + private Atlas falsePositiveKnownHighwayTagOnEdge; + + @TestAtlas(nodes = { + @Node(coordinates = @Loc(value = WAY1_NODE1), tags = { "highway=bus_stop" }), + @Node(coordinates = @Loc(value = WAY1_NODE2)) }, edges = { + @Edge(id = "2000001", coordinates = { @Loc(value = WAY1_NODE1), + @Loc(value = WAY1_NODE2) }) }) + private Atlas falsePositiveKnownHighwayTagOnNode; + + @TestAtlas(nodes = { @Node(id = "2000001", coordinates = @Loc(value = WAY1_NODE1)), + @Node(id = "3000001", coordinates = @Loc(value = WAY1_NODE2), tags = { + "highway=trunk" }) }, edges = { + @Edge(id = "4000001", coordinates = { @Loc(value = WAY1_NODE1), + @Loc(value = WAY1_NODE2) }) }) + private Atlas truePositiveEdgeTagOnNode; + + @TestAtlas(nodes = { @Node(coordinates = @Loc(value = WAY1_NODE1)), + @Node(coordinates = @Loc(value = WAY1_NODE2)) }, edges = { + @Edge(id = "5000001", coordinates = { @Loc(value = WAY1_NODE1), + @Loc(value = WAY1_NODE2) }, tags = { "highway=bus_stop" }) }) + private Atlas truePositiveNodeTagOnEdge; + + @TestAtlas(nodes = { @Node(coordinates = @Loc(value = WAY1_NODE1)), + @Node(coordinates = @Loc(value = WAY1_NODE2)) }, edges = { + @Edge(id = "3000001", coordinates = { @Loc(value = WAY1_NODE1), + @Loc(value = WAY1_NODE2) }, tags = { "highway=unknown" }) }) + private Atlas truePositiveUnknownHighwayTagOnEdge; + + @TestAtlas(nodes = { + @Node(coordinates = @Loc(value = WAY1_NODE1), tags = { "highway=unknown" }), + @Node(coordinates = @Loc(value = WAY1_NODE2)) }, edges = { + @Edge(id = "6000001", coordinates = { @Loc(value = WAY1_NODE1), + @Loc(value = WAY1_NODE2) }) }) + private Atlas truePositiveUnknownHighwayTagOnNode; + + public Atlas falsePositiveKnownHighwayTagOnEdge() + { + return this.falsePositiveKnownHighwayTagOnEdge; + } + + public Atlas falsePositiveKnownHighwayTagOnNode() + { + return this.falsePositiveKnownHighwayTagOnNode; + } + + public Atlas truePositiveEdgeTagOnNode() + { + return this.truePositiveEdgeTagOnNode; + } + + public Atlas truePositiveNodeTagOnEdge() + { + return this.truePositiveNodeTagOnEdge; + } + + public Atlas truePositiveUnknownHighwayTagOnEdge() + { + return this.truePositiveUnknownHighwayTagOnEdge; + } + + public Atlas truePositiveUnknownHighwayTagOnNode() + { + return this.truePositiveUnknownHighwayTagOnNode; + } +}