// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.validation.tests; import static org.openstreetmap.josm.tools.I18n.tr; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.validation.Severity; import org.openstreetmap.josm.data.validation.Test; import org.openstreetmap.josm.data.validation.TestError; import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType; import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator; /** * Tests for <a href="https://wiki.openstreetmap.org/wiki/Proposed_features/Public_Transport">public transport routes</a>. */ public class PublicTransportRouteTest extends Test { private final WayConnectionTypeCalculator connectionTypeCalculator = new WayConnectionTypeCalculator(); /** * Constructs a new {@code PublicTransportRouteTest}. */ public PublicTransportRouteTest() { super(tr("Public Transport Route")); } @Override public void visit(Relation r) { final boolean skip = r.hasIncompleteMembers() || !r.hasTag("type", "route") || !r.hasKey("route") || !r.hasTag("public_transport:version", "2"); if (skip) { return; } final List<RelationMember> membersToCheck = new ArrayList<>(); final Set<Node> routeNodes = new HashSet<>(); for (RelationMember member : r.getMembers()) { if (member.hasRole("forward", "backward")) { errors.add(TestError.builder(this, Severity.WARNING, 3601) .message(tr("Route relation contains a ''{0}'' role", "forward/backward")) .primitives(r) .build()); return; } else if (member.hasRole("") && OsmPrimitiveType.WAY.equals(member.getType())) { membersToCheck.add(member); routeNodes.addAll(member.getWay().getNodes()); } } if (membersToCheck.isEmpty()) { return; } final List<WayConnectionType> links = connectionTypeCalculator.updateLinks(membersToCheck); for (int i = 0; i < links.size(); i++) { final WayConnectionType link = links.get(i); final boolean hasError = !(i == 0 || link.linkPrev) || !(i == links.size() - 1 || link.linkNext) || link.direction == null || WayConnectionType.Direction.NONE.equals(link.direction); if (hasError) { errors.add(TestError.builder(this, Severity.WARNING, 3602) .message(tr("Route relation contains a gap")) .primitives(r) .build()); return; } } for (RelationMember member : r.getMembers()) { if (member.hasRole("stop", "stop_exit_only", "stop_entry_only") && OsmPrimitiveType.NODE.equals(member.getType()) && !routeNodes.contains(member.getNode())) { errors.add(TestError.builder(this, Severity.WARNING, 3603) .message(tr("Stop position not part of route")) .primitives(member.getMember(), r) .build()); } } } }