001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.HashSet; 009import java.util.List; 010import java.util.Set; 011 012import org.openstreetmap.josm.data.osm.Node; 013import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 014import org.openstreetmap.josm.data.osm.Relation; 015import org.openstreetmap.josm.data.osm.RelationMember; 016import org.openstreetmap.josm.data.validation.Severity; 017import org.openstreetmap.josm.data.validation.Test; 018import org.openstreetmap.josm.data.validation.TestError; 019import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType; 020import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionTypeCalculator; 021 022/** 023 * Tests for <a href="https://wiki.openstreetmap.org/wiki/Proposed_features/Public_Transport">public transport routes</a>. 024 */ 025public class PublicTransportRouteTest extends Test { 026 027 private final WayConnectionTypeCalculator connectionTypeCalculator = new WayConnectionTypeCalculator(); 028 029 /** 030 * Constructs a new {@code PublicTransportRouteTest}. 031 */ 032 public PublicTransportRouteTest() { 033 super(tr("Public Transport Route")); 034 } 035 036 @Override 037 public void visit(Relation r) { 038 final boolean skip = r.hasIncompleteMembers() 039 || !r.hasTag("type", "route") 040 || !r.hasKey("route") 041 || !r.hasTag("public_transport:version", "2"); 042 if (skip) { 043 return; 044 } 045 046 final List<RelationMember> membersToCheck = new ArrayList<>(); 047 final Set<Node> routeNodes = new HashSet<>(); 048 for (RelationMember member : r.getMembers()) { 049 if (member.hasRole("forward", "backward")) { 050 errors.add(new TestError(this, Severity.WARNING, tr("Route relation contains a ''{0}'' role", "forward/backward"), 3601, r)); 051 return; 052 } else if (member.hasRole("") && OsmPrimitiveType.WAY.equals(member.getType())) { 053 membersToCheck.add(member); 054 routeNodes.addAll(member.getWay().getNodes()); 055 } 056 } 057 if (membersToCheck.isEmpty()) { 058 return; 059 } 060 061 final List<WayConnectionType> links = connectionTypeCalculator.updateLinks(membersToCheck); 062 for (int i = 0; i < links.size(); i++) { 063 final WayConnectionType link = links.get(i); 064 final boolean hasError = !(i == 0 || link.linkPrev) 065 || !(i == links.size() - 1 || link.linkNext) 066 || link.direction == null 067 || WayConnectionType.Direction.NONE.equals(link.direction); 068 if (hasError) { 069 errors.add(new TestError(this, Severity.WARNING, tr("Route relation contains a gap"), 3602, r)); 070 return; 071 } 072 } 073 074 for (RelationMember member : r.getMembers()) { 075 if (member.hasRole("stop", "stop_exit_only", "stop_entry_only") 076 && OsmPrimitiveType.NODE.equals(member.getType()) 077 && !routeNodes.contains(member.getNode())) { 078 errors.add(new TestError(this, Severity.WARNING, 079 tr("Stop position not part of route"), 3603, Arrays.asList(member.getMember(), r))); 080 } 081 } 082 } 083}