Changeset 33091 in osm


Ignore:
Timestamp:
2016-11-30T16:56:32+01:00 (8 years ago)
Author:
darya
Message:

fix #14015

Location:
applications/editors/josm/plugins/pt_assistant/src/org/openstreetmap/josm/plugins/pt_assistant/validation
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/pt_assistant/src/org/openstreetmap/josm/plugins/pt_assistant/validation/PTAssistantValidatorTest.java

    r33082 r33091  
    88import java.util.Collection;
    99import java.util.List;
     10import java.util.Map.Entry;
    1011
    1112import javax.swing.JOptionPane;
     
    3940import org.openstreetmap.josm.plugins.pt_assistant.utils.StopToWayAssigner;
    4041import org.openstreetmap.josm.plugins.pt_assistant.utils.StopUtils;
     42import org.openstreetmap.josm.tools.Utils;
    4143
    4244public class PTAssistantValidatorTest extends Test {
     
    406408        }
    407409    }
     410   
     411    /**
     412     * Overrides the superclass method
     413     */
     414    public void startTest() {
     415        super.startTest(progressMonitor);
     416        SegmentChecker.reset();
     417    }
     418   
     419    /**
     420     * Method is called after all primitives has been visited, overrides the method of the superclass.
     421     */
     422    public void endTest() {
     423       
     424        // modify the error messages for the stop-by-stop test:
     425        SegmentChecker.modifyStopByStopErrorMessages();
     426       
     427        // add the stop-by-stop errors with modified messages:
     428        for (Entry<Builder, PTRouteSegment> entry: SegmentChecker.wrongSegmentBuilders.entrySet()) {                   
     429                TestError error = entry.getKey().build();
     430                SegmentChecker.wrongSegments.put(error, entry.getValue());
     431                this.errors.add(error);
     432        }
     433       
     434        super.endTest();
     435
     436    }
    408437
    409438    /**
  • applications/editors/josm/plugins/pt_assistant/src/org/openstreetmap/josm/plugins/pt_assistant/validation/SegmentChecker.java

    r33082 r33091  
    1010import java.util.HashMap;
    1111import java.util.List;
     12import java.util.Map.Entry;
    1213
    1314import javax.swing.SwingUtilities;
     
    4849public class SegmentChecker extends Checker {
    4950
    50     /* PTRouteSegments that have been validated and are correct */
    51     private static List<PTRouteSegment> correctSegments = new ArrayList<>();
    52 
    53     /* PTRouteSegments that are wrong, stored in case the user calls the fix */
    54     private static HashMap<TestError, PTRouteSegment> wrongSegments = new HashMap<>();
    55 
    56     /* Manager of the PTStops and PTWays of the current route */
    57     private PTRouteDataManager manager;
    58 
    59     /* Assigns PTStops to nearest PTWays and stores that correspondence */
    60     private StopToWayAssigner assigner;
    61 
    62     public SegmentChecker(Relation relation, Test test) {
    63 
    64         super(relation, test);
    65 
    66         this.manager = new PTRouteDataManager(relation);
    67 
    68         for (RelationMember rm : manager.getFailedMembers()) {
    69             List<Relation> primitives = new ArrayList<>(1);
    70             primitives.add(relation);
    71             List<OsmPrimitive> highlighted = new ArrayList<>(1);
    72             highlighted.add(rm.getMember());
    73             Builder builder = TestError.builder(this.test, Severity.WARNING, PTAssistantValidatorTest.ERROR_CODE_RELAITON_MEMBER_ROLES);
    74             builder.message(tr("PT: Relation member roles do not match tags"));
    75             builder.primitives(primitives);
    76             builder.highlight(highlighted);
    77             TestError e = builder.build();
    78             this.errors.add(e);
    79         }
    80 
    81         this.assigner = new StopToWayAssigner(manager.getPTWays());
    82 
    83     }
    84 
    85     /**
    86      * Returns the number of route segments that have been already successfully
    87      * verified
    88      *
    89      * @return the number of route segments
    90      */
    91     public static int getCorrectSegmentCount() {
    92         return correctSegments.size();
    93     }
    94 
    95     /**
    96      * Adds the given correct segment to the list of correct segments without
    97      * checking its correctness
    98      *
    99      * @param segment
    100      *            to add to the list of correct segments
    101      */
    102     public static synchronized void addCorrectSegment(PTRouteSegment segment) {
    103         for (PTRouteSegment correctSegment : correctSegments) {
    104             if (correctSegment.equalsRouteSegment(segment)) {
    105                 return;
    106             }
    107         }
    108         correctSegments.add(segment);
    109     }
    110 
    111     /**
    112      * Used for unit tests
    113      * @param error test error
    114      * @return wrong route segment
    115      */
    116     protected static PTRouteSegment getWrongSegment(TestError error) {
    117         return wrongSegments.get(error);
    118     }
    119 
    120     public void performFirstStopTest() {
    121 
    122         performEndStopTest(manager.getFirstStop());
    123 
    124     }
    125 
    126     public void performLastStopTest() {
    127 
    128         performEndStopTest(manager.getLastStop());
    129 
    130     }
    131 
    132     private void performEndStopTest(PTStop endStop) {
    133 
    134         if (endStop == null) {
    135             return;
    136         }
    137 
    138         /*
    139          * This test checks: (1) that a stop position exists; (2) that it is the
    140          * first or last node of its parent ways which belong to this route.
    141          */
    142 
    143         if (endStop.getStopPosition() == null) {
    144 
    145             List<Node> potentialStopPositionList = endStop.findPotentialStopPositions();
    146             List<Node> stopPositionsOfThisRoute = new ArrayList<>();
    147             boolean containsAtLeastOneStopPositionAsFirstOrLastNode = false;
    148 
    149             for (Node potentialStopPosition : potentialStopPositionList) {
    150 
    151                 int belongsToWay = belongsToAWayOfThisRoute(potentialStopPosition);
    152 
    153                 if (belongsToWay == 0) {
    154                     stopPositionsOfThisRoute.add(potentialStopPosition);
    155                     containsAtLeastOneStopPositionAsFirstOrLastNode = true;
    156                 }
    157 
    158                 if (belongsToWay == 1) {
    159                     stopPositionsOfThisRoute.add(potentialStopPosition);
    160                 }
    161             }
    162 
    163             if (stopPositionsOfThisRoute.isEmpty()) {
    164                 List<Relation> primitives = new ArrayList<>(1);
    165                 primitives.add(relation);
    166                 List<OsmPrimitive> highlighted = new ArrayList<>(1);
    167                 highlighted.add(endStop.getPlatform());
    168                 Builder builder = TestError.builder(this.test, Severity.WARNING, PTAssistantValidatorTest.ERROR_CODE_END_STOP);
    169                 builder.message(tr("PT: Route should start and end with a stop_position"));
    170                 builder.primitives(primitives);
    171                 builder.highlight(highlighted);
    172                 TestError e = builder.build();
    173                 this.errors.add(e);
    174                 return;
    175             }
    176 
    177             if (stopPositionsOfThisRoute.size() == 1) {
    178                 endStop.setStopPosition(stopPositionsOfThisRoute.get(0));
    179             }
    180 
    181             // At this point, there is at least one stop_position for this
    182             // endStop:
    183             if (!containsAtLeastOneStopPositionAsFirstOrLastNode) {
    184                 List<Relation> primitives = new ArrayList<>(1);
    185                 primitives.add(relation);
    186                 List<OsmPrimitive> highlighted = new ArrayList<>();
    187                 highlighted.addAll(stopPositionsOfThisRoute);
    188 
    189                 Builder builder = TestError.builder(this.test, Severity.WARNING, PTAssistantValidatorTest.ERROR_CODE_SPLIT_WAY);
    190                 builder.message(tr("PT: First or last way needs to be split"));
    191                 builder.primitives(primitives);
    192                 builder.highlight(highlighted);
    193                 TestError e = builder.build();
    194                 this.errors.add(e);
    195             }
    196 
    197         } else {
    198 
    199             // if the stop_position is known:
    200             int belongsToWay = this.belongsToAWayOfThisRoute(endStop.getStopPosition());
    201 
    202             if (belongsToWay == 1) {
    203 
    204                 List<Relation> primitives = new ArrayList<>(1);
    205                 primitives.add(relation);
    206                 List<OsmPrimitive> highlighted = new ArrayList<>();
    207                 highlighted.add(endStop.getStopPosition());
    208                 Builder builder = TestError.builder(this.test, Severity.WARNING, PTAssistantValidatorTest.ERROR_CODE_SPLIT_WAY);
    209                 builder.message(tr("PT: First or last way needs to be split"));
    210                 builder.primitives(primitives);
    211                 builder.highlight(highlighted);
    212                 TestError e = builder.build();
    213                 this.errors.add(e);
    214             }
    215         }
    216 
    217     }
    218 
    219     /**
    220      * Checks if the given node belongs to the ways of this route.
    221      *
    222      * @param node
    223      *            Node to be checked
    224      * @return 1 if belongs only as an inner node, 0 if belongs as a first or
    225      *         last node for at least one way, -1 if does not belong to any way.
    226      */
    227     private int belongsToAWayOfThisRoute(Node node) {
    228 
    229         boolean contains = false;
    230 
    231         List<PTWay> ptways = manager.getPTWays();
    232         for (PTWay ptway : ptways) {
    233             List<Way> ways = ptway.getWays();
    234             for (Way way : ways) {
    235                 if (way.containsNode(node)) {
    236 
    237                     if (way.firstNode().equals(node) || way.lastNode().equals(node)) {
    238                         return 0;
    239                     }
    240 
    241                     contains = true;
    242                 }
    243             }
    244         }
    245 
    246         if (contains) {
    247             return 1;
    248         }
    249 
    250         return -1;
    251     }
    252 
    253     public void performStopNotServedTest() {
    254         for (PTStop stop : manager.getPTStops()) {
    255             Way way = assigner.get(stop);
    256             if (way == null) {
    257                 createStopError(stop);
    258             }
    259         }
    260     }
    261 
    262     /**
    263      * Performs the stop-by-stop test by visiting each segment between two
    264      * consecutive stops and checking if the ways between them are correct
    265      */
    266     public void performStopByStopTest() {
    267 
    268         if (manager.getPTStopCount() < 2) {
    269             return;
    270         }
    271 
    272         // Check each route segment:
    273         for (int i = 1; i < manager.getPTStopCount(); i++) {
    274 
    275             PTStop startStop = manager.getPTStops().get(i - 1);
    276             PTStop endStop = manager.getPTStops().get(i);
    277 
    278             Way startWay = assigner.get(startStop);
    279             Way endWay = assigner.get(endStop);
    280             if (startWay == null || endWay == null || (startWay == endWay && startWay == manager.getLastWay())) {
    281                 continue;
    282             }
    283 
    284             List<PTWay> segmentWays = manager.getPTWaysBetween(startWay, endWay);
    285 
    286             Node firstNode = findFirstNodeOfRouteSegmentInDirectionOfTravel(segmentWays.get(0));
    287             if (firstNode == null) {
    288                 // check if this error has just been reported:
    289                 if (!this.errors.isEmpty() && this.errors.get(this.errors.size() - 1).getHighlighted().size() == 1
    290                         && this.errors.get(this.errors.size() - 1).getHighlighted().iterator().next() == startWay) {
    291                     // do nothing, this error has already been reported in
    292                     // the previous route segment
    293                 } else {
    294                     List<Relation> primitives = new ArrayList<>(1);
    295                     primitives.add(relation);
    296                     List<OsmPrimitive> highlighted = new ArrayList<>();
    297                     highlighted.add(startWay);
    298                     Builder builder = TestError.builder(this.test, Severity.WARNING, PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP);
    299                     builder.message(tr("PT: Problem in the route segment"));
    300                     builder.primitives(primitives);
    301                     builder.highlight(highlighted);
    302                     TestError e = builder.build();
    303                     this.errors.add(e);
    304                     PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
    305                     wrongSegments.put(e, routeSegment);
    306                 }
    307                 continue;
    308             }
    309 
    310             boolean sortingCorrect = existingWaySortingIsCorrect(segmentWays.get(0), firstNode,
    311                     segmentWays.get(segmentWays.size() - 1));
    312             if (sortingCorrect) {
    313                 PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
    314                 addCorrectSegment(routeSegment);
    315             } else {
    316                 PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
    317                 TestError error = this.errors.get(this.errors.size() - 1);
    318                 wrongSegments.put(error, routeSegment);
    319             }
    320         }
    321     }
    322 
    323     /**
    324      * Creates a TestError and adds it to the list of errors for a stop that is
    325      * not served.
    326      *
    327      * @param stop stop
    328      */
    329     private void createStopError(PTStop stop) {
    330         List<Relation> primitives = new ArrayList<>(1);
    331         primitives.add(relation);
    332         List<OsmPrimitive> highlighted = new ArrayList<>();
    333         OsmPrimitive stopPrimitive = stop.getPlatform();
    334         if (stopPrimitive == null) {
    335             stopPrimitive = stop.getStopPosition();
    336         }
    337         highlighted.add(stopPrimitive);
    338         Builder builder = TestError.builder(this.test, Severity.WARNING, PTAssistantValidatorTest.ERROR_CODE_STOP_NOT_SERVED);
    339         builder.message(tr("PT: Stop not served"));
    340         builder.primitives(primitives);
    341         builder.highlight(highlighted);
    342         TestError e = builder.build();
    343         this.errors.add(e);
    344     }
    345 
    346     private Node findFirstNodeOfRouteSegmentInDirectionOfTravel(PTWay startWay) {
    347 
    348         // 1) at first check if one of the first or last node of the first ptway
    349         // is a deadend node:
    350         Node[] startWayEndnodes = startWay.getEndNodes();
    351         if (isDeadendNode(startWayEndnodes[0])) {
    352             return startWayEndnodes[0];
    353         }
    354         if (isDeadendNode(startWayEndnodes[1])) {
    355             return startWayEndnodes[1];
    356         }
    357 
    358         // 2) failing that, check which node this startWay shares with the
    359         // following way:
    360         PTWay nextWay = manager.getNextPTWay(startWay);
    361         if (nextWay == null) {
    362             return null;
    363         }
    364         PTWay wayAfterNext = manager.getNextPTWay(nextWay);
    365         Node[] nextWayEndnodes = nextWay.getEndNodes();
    366         if ((startWayEndnodes[0] == nextWayEndnodes[0] && startWayEndnodes[1] == nextWayEndnodes[1])
    367                 || (startWayEndnodes[0] == nextWayEndnodes[1] && startWayEndnodes[1] == nextWayEndnodes[0])) {
    368             // if this is a split roundabout:
    369             Node[] wayAfterNextEndnodes = wayAfterNext.getEndNodes();
    370             if (startWayEndnodes[0] == wayAfterNextEndnodes[0] || startWayEndnodes[0] == wayAfterNextEndnodes[1]) {
    371                 return startWayEndnodes[0];
    372             }
    373             if (startWayEndnodes[1] == wayAfterNextEndnodes[0] || startWayEndnodes[1] == wayAfterNextEndnodes[1]) {
    374                 return startWayEndnodes[1];
    375             }
    376         }
    377 
    378         if (startWayEndnodes[0] == nextWayEndnodes[0] || startWayEndnodes[0] == nextWayEndnodes[1]) {
    379             return startWayEndnodes[1];
    380         }
    381         if (startWayEndnodes[1] == nextWayEndnodes[0] || startWayEndnodes[1] == nextWayEndnodes[1]) {
    382             return startWayEndnodes[0];
    383         }
    384 
    385         return null;
    386 
    387     }
    388 
    389     private boolean isDeadendNode(Node node) {
    390         int count = 0;
    391         for (PTWay ptway : manager.getPTWays()) {
    392             List<Way> ways = ptway.getWays();
    393             for (Way way : ways) {
    394                 if (way.firstNode() == node || way.lastNode() == node) {
    395                     count++;
    396                 }
    397             }
    398         }
    399         return count == 1;
    400     }
    401 
    402     /**
    403      * Finds the deadend node closest to the given node represented by its
    404      * coordinates
    405      *
    406      * @param coord
    407      *            coordinates of the givenn node
    408      * @param deadendNodes dead end nodes
    409      * @return the closest deadend node
    410      */
    411     @SuppressWarnings("unused")
    412     private Node findClosestDeadendNode(LatLon coord, List<Node> deadendNodes) {
    413 
    414         Node closestDeadendNode = null;
    415         double minSqDistance = Double.MAX_VALUE;
    416         for (Node deadendNode : deadendNodes) {
    417             double distanceSq = coord.distanceSq(deadendNode.getCoor());
    418             if (distanceSq < minSqDistance) {
    419                 minSqDistance = distanceSq;
    420                 closestDeadendNode = deadendNode;
    421             }
    422         }
    423         return closestDeadendNode;
    424 
    425     }
    426 
    427     /**
    428      * Checks if the existing sorting of the given route segment is correct
    429      *
    430      * @param start
    431      *            PTWay assigned to the first stop of the segment
    432      * @param startWayPreviousNodeInDirectionOfTravel
    433      *            Node if the start way which is furthest away from the rest of
    434      *            the route
    435      * @param end
    436      *            PTWay assigned to the end stop of the segment
    437      * @return true if the sorting is correct, false otherwise.
    438      */
    439     private boolean existingWaySortingIsCorrect(PTWay start, Node startWayPreviousNodeInDirectionOfTravel, PTWay end) {
    440 
    441         if (start == end) {
    442             // if both PTStops are on the same PTWay
    443             return true;
    444         }
    445 
    446         PTWay current = start;
    447         Node currentNode = startWayPreviousNodeInDirectionOfTravel;
    448 
    449         while (!current.equals(end)) {
    450             // "equals" is used here instead of "==" because when the same way
    451             // is passed multiple times by the bus, the algorithm should stop no
    452             // matter which of the geometrically equal PTWays it finds
    453 
    454             PTWay nextPTWayAccortingToExistingSorting = manager.getNextPTWay(current);
    455 
    456             // if current contains an unsplit roundabout:
    457             if (current.containsUnsplitRoundabout()) {
    458                 currentNode = manager.getCommonNode(current, nextPTWayAccortingToExistingSorting);
    459                 if (currentNode == null) {
    460                     List<Relation> primitives = new ArrayList<>(1);
    461                     primitives.add(relation);
    462                     List<OsmPrimitive> highlighted = new ArrayList<>();
    463                     highlighted.addAll(current.getWays());
    464                     Builder builder = TestError.builder(this.test, Severity.WARNING, PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP);
    465                     builder.message(tr("PT: Problem in the route segment"));
    466                     builder.primitives(primitives);
    467                     builder.highlight(highlighted);
    468                     TestError e = builder.build();
    469                     this.errors.add(e);
    470                     return false;
    471                 }
    472             } else {
    473                 // if this is a regular way, not an unsplit roundabout
    474 
    475                 // find the next node in direction of travel (which is part of
    476                 // the PTWay start):
    477                 currentNode = getOppositeEndNode(current, currentNode);
    478 
    479                 List<PTWay> nextWaysInDirectionOfTravel = this.findNextPTWaysInDirectionOfTravel(current, currentNode);
    480 
    481                 if (!nextWaysInDirectionOfTravel.contains(nextPTWayAccortingToExistingSorting)) {
    482                     List<Relation> primitives = new ArrayList<>(1);
    483                     primitives.add(relation);
    484                     List<OsmPrimitive> highlighted = new ArrayList<>();
    485 
    486                     highlighted.addAll(current.getWays());
    487                    
    488                     Builder builder = TestError.builder(this.test, Severity.WARNING, PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP);
    489                     builder.message(tr("PT: Problem in the route segment"));
    490                     builder.primitives(primitives);
    491                     builder.highlight(highlighted);
    492                     TestError e = builder.build();
    493                     this.errors.add(e);
    494                     return false;
    495 
    496                 }
    497             }
    498 
    499             current = nextPTWayAccortingToExistingSorting;
    500 
    501         }
    502 
    503         return true;
    504     }
    505 
    506     /**
    507      * Will return the same node if the way is an unsplit roundabout
    508      *
    509      * @param way way
    510      * @param node node
    511      * @return the same node if the way is an unsplit roundabout
    512      */
    513     private Node getOppositeEndNode(Way way, Node node) {
    514 
    515         if (node == way.firstNode()) {
    516             return way.lastNode();
    517         }
    518 
    519         if (node == way.lastNode()) {
    520             return way.firstNode();
    521         }
    522 
    523         return null;
    524     }
    525 
    526     /**
    527      * Does not work correctly for unsplit roundabouts
    528      *
    529      * @param ptway way
    530      * @param node node
    531      * @return node
    532      */
    533     private Node getOppositeEndNode(PTWay ptway, Node node) {
    534         if (ptway.isWay()) {
    535             return getOppositeEndNode(ptway.getWays().get(0), node);
    536         }
    537 
    538         Way firstWay = ptway.getWays().get(0);
    539         Way lastWay = ptway.getWays().get(ptway.getWays().size() - 1);
    540         Node oppositeNode = node;
    541         if (firstWay.firstNode() == node || firstWay.lastNode() == node) {
    542             for (int i = 0; i < ptway.getWays().size(); i++) {
    543                 oppositeNode = getOppositeEndNode(ptway.getWays().get(i), oppositeNode);
    544             }
    545             return oppositeNode;
    546         } else if (lastWay.firstNode() == node || lastWay.lastNode() == node) {
    547             for (int i = ptway.getWays().size() - 1; i >= 0; i--) {
    548                 oppositeNode = getOppositeEndNode(ptway.getWays().get(i), oppositeNode);
    549             }
    550             return oppositeNode;
    551         }
    552 
    553         return null;
    554 
    555     }
    556 
    557     /**
    558      * Finds the next ways for the route stop-by-stop parsing procedure
    559      *
    560      * @param currentWay current way
    561      * @param nextNodeInDirectionOfTravel next node in direction of travel
    562      * @return the next ways for the route stop-by-stop parsing procedure
    563      */
    564     private List<PTWay> findNextPTWaysInDirectionOfTravel(PTWay currentWay, Node nextNodeInDirectionOfTravel) {
    565 
    566         List<PTWay> nextPtways = new ArrayList<>();
    567 
    568         List<PTWay> ptways = manager.getPTWays();
    569 
    570         for (PTWay ptway : ptways) {
    571 
    572             if (ptway != currentWay) {
    573                 for (Way way : ptway.getWays()) {
    574                     if (way.containsNode(nextNodeInDirectionOfTravel)) {
    575                         nextPtways.add(ptway);
    576                     }
    577                 }
    578             }
    579         }
    580 
    581         return nextPtways;
    582 
    583     }
    584 
    585     protected static boolean isFixable(TestError testError) {
    586 
    587         /*-
    588          * When is an error fixable (outdated)?
    589          * - if there is a correct segment
    590          * - if it can be fixed by sorting
    591          * - if the route is compete even without some ways
    592          * - if simple routing closes the gap
    593          */
    594 
    595         if (testError.getCode() == PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP) {
    596             return true;
    597         }
    598 
    599         return false;
    600 
    601     }
    602 
    603     @SuppressWarnings("unused")
    604     private static boolean isFixableByUsingCorrectSegment(TestError testError) {
    605         PTRouteSegment wrongSegment = wrongSegments.get(testError);
    606         PTRouteSegment correctSegment = null;
    607         for (PTRouteSegment segment : correctSegments) {
    608             if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
    609                     && wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
    610                 correctSegment = segment;
    611                 break;
    612             }
    613         }
    614         return correctSegment != null;
    615     }
    616 
    617     @SuppressWarnings("unused")
    618     private static boolean isFixableBySortingAndRemoval(TestError testError) {
    619         PTRouteSegment wrongSegment = wrongSegments.get(testError);
    620         List<List<PTWay>> fixVariants = wrongSegment.getFixVariants();
    621         if (!fixVariants.isEmpty()) {
    622             return true;
    623         }
    624         return false;
    625     }
    626 
    627     /**
    628      * Finds fixes using sorting and removal. Modifies the messages in the test
    629      * error according to the availability of automatic fixes.
    630      */
    631     protected void findFixes() {
    632 
    633         for (TestError error : wrongSegments.keySet()) {
    634             // look for fixes using sorting and removing:
    635             findFix(error);
    636 
    637             // change the error code based on the availability of fixes:
    638             PTRouteSegment wrongSegment = wrongSegments.get(error);
    639             List<PTRouteSegment> correctSegmentsForThisError = new ArrayList<>();
    640             for (PTRouteSegment segment : correctSegments) {
    641                 if (wrongSegment.getFirstWay().getId() == segment.getFirstWay().getId()
    642                         && wrongSegment.getLastWay().getId() == segment.getLastWay().getId()) {
    643                     correctSegmentsForThisError.add(segment);
    644                 }
    645             }
    646 
    647             int numberOfFixes = correctSegmentsForThisError.size();
    648 
    649             if (numberOfFixes == 0) {
    650                 numberOfFixes = wrongSegment.getFixVariants().size();
    651             }
    652             if (numberOfFixes == 0) {
    653                 for (PTRouteSegment segment : correctSegments) {
    654                     if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
    655                             && wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
    656                         correctSegmentsForThisError.add(segment);
    657                     }
    658                 }
    659                 numberOfFixes = correctSegmentsForThisError.size();
    660             }
    661 
    662             // change the error code:
    663             if (numberOfFixes == 0) {
    664                 error.setMessage(tr("PT: Problem in the route segment with no automatic fix"));
    665             } else if (numberOfFixes == 1) {
    666                 error.setMessage(tr("PT: Problem in the route segment with one automatic fix"));
    667             } else {
    668                 error.setMessage("PT: Problem in the route segment with several automatic fixes");
    669             }
    670         }
    671 
    672     }
    673 
    674     /**
    675      * This method assumes that the first and the second ways of the route
    676      * segment are correctly connected. If they are not, the error will be
    677      * marked as not fixable.
    678      *
    679      * @param testError test error
    680      */
    681     private void findFix(TestError testError) {
    682 
    683         PTRouteSegment wrongSegment = wrongSegments.get(testError);
    684         PTWay startPTWay = wrongSegment.getFirstPTWay();
    685         PTWay endPTWay = wrongSegment.getLastPTWay();
    686 
    687         Node previousNode = findFirstNodeOfRouteSegmentInDirectionOfTravel(startPTWay);
    688         if (previousNode == null) {
    689             return;
    690         }
    691 
    692         List<List<PTWay>> initialFixes = new ArrayList<>();
    693         List<PTWay> initialFix = new ArrayList<>();
    694         initialFix.add(startPTWay);
    695         initialFixes.add(initialFix);
    696 
    697         List<List<PTWay>> allFixes = findWaysForFix(initialFixes, initialFix, previousNode, endPTWay);
    698         for (List<PTWay> fix : allFixes) {
    699             if (!fix.isEmpty() && fix.get(fix.size() - 1).equals(endPTWay)) {
    700                 wrongSegment.addFixVariant(fix);
    701             }
    702         }
    703 
    704     }
    705 
    706     /**
    707      * Recursive method to parse the route segment
    708      *
    709      * @param allFixes all fixes
    710      * @param currentFix current fix
    711      * @param previousNode previous node
    712      * @param endWay end way
    713      * @return list of list of ways
    714      */
    715     private List<List<PTWay>> findWaysForFix(List<List<PTWay>> allFixes, List<PTWay> currentFix, Node previousNode,
    716             PTWay endWay) {
    717 
    718         PTWay currentWay = currentFix.get(currentFix.size() - 1);
    719         Node nextNode = getOppositeEndNode(currentWay, previousNode);
    720 
    721         List<PTWay> nextWays = this.findNextPTWaysInDirectionOfTravel(currentWay, nextNode);
    722 
    723         if (nextWays.size() > 1) {
    724             for (int i = 1; i < nextWays.size(); i++) {
    725                 List<PTWay> newFix = new ArrayList<>();
    726                 newFix.addAll(currentFix);
    727                 newFix.add(nextWays.get(i));
    728                 allFixes.add(newFix);
    729                 if (!nextWays.get(i).equals(endWay) && !currentFix.contains(nextWays.get(i))) {
    730                     allFixes = findWaysForFix(allFixes, newFix, nextNode, endWay);
    731                 }
    732             }
    733         }
    734 
    735         if (!nextWays.isEmpty()) {
    736             boolean contains = currentFix.contains(nextWays.get(0));
    737             currentFix.add(nextWays.get(0));
    738             if (!nextWays.get(0).equals(endWay) && !contains) {
    739                 allFixes = findWaysForFix(allFixes, currentFix, nextNode, endWay);
    740             }
    741         }
    742 
    743         return allFixes;
    744     }
    745 
    746     /**
    747      * Fixes the error by first searching in the list of correct segments and
    748      * then trying to sort and remove existing route relation members
    749      *
    750      * @param testError test error
    751      * @return fix command
    752      */
    753     protected static Command fixError(TestError testError) {
    754 
    755         // if fix options for another route are displayed in the pt_assistant
    756         // layer, clear them:
    757         ((PTAssistantValidatorTest) testError.getTester()).clearFixVariants();
    758 
    759         PTRouteSegment wrongSegment = wrongSegments.get(testError);
    760 
    761         // 1) try to fix by using the correct segment:
    762         List<PTRouteSegment> correctSegmentsForThisError = new ArrayList<>();
    763         for (PTRouteSegment segment : correctSegments) {
    764             if (wrongSegment.getFirstWay().getId() == segment.getFirstWay().getId()
    765                     && wrongSegment.getLastWay().getId() == segment.getLastWay().getId()) {
    766                 correctSegmentsForThisError.add(segment);
    767             }
    768         }
    769 
    770         // if no correct segment found, apply less strict criteria to look for
    771         // one:
    772         if (correctSegmentsForThisError.isEmpty() && wrongSegment.getFixVariants().isEmpty()) {
    773             for (PTRouteSegment segment : correctSegments) {
    774                 if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
    775                         && wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
    776                     correctSegmentsForThisError.add(segment);
    777                 }
    778             }
    779             if (!correctSegmentsForThisError.isEmpty()) {
    780                 // display the notification:
    781                 if (SwingUtilities.isEventDispatchThread()) {
    782                     Notification notification = new Notification(
    783                             tr("Warning: the diplayed fix variants are based on less strict criteria"));
    784                     notification.show();
    785                 } else {
    786                     SwingUtilities.invokeLater(new Runnable() {
    787                         @Override
    788                         public void run() {
    789                             Notification notification = new Notification(
    790                                     tr("Warning: the diplayed fix variants are based on less strict criteria"));
    791                             notification.show();
    792                         }
    793                     });
    794                 }
    795             }
    796         }
    797 
    798         if (!correctSegmentsForThisError.isEmpty()) {
    799 
    800             if (correctSegmentsForThisError.size() > 1) {
    801                 List<List<PTWay>> fixVariants = new ArrayList<>();
    802                 for (PTRouteSegment segment : correctSegmentsForThisError) {
    803                     fixVariants.add(segment.getPTWays());
    804                 }
    805                 displayFixVariants(fixVariants, testError);
    806                 return null;
    807             }
    808 
    809             PTAssistantPlugin.setLastFix(correctSegmentsForThisError.get(0));
    810             return carryOutSingleFix(testError, correctSegmentsForThisError.get(0).getPTWays());
    811 
    812         } else if (!wrongSegment.getFixVariants().isEmpty()) {
    813             // 2) try to fix using the sorting and removal of existing ways
    814             // of the wrong segment:
    815             if (wrongSegment.getFixVariants().size() > 1) {
    816                 displayFixVariants(wrongSegment.getFixVariants(), testError);
    817                 return null;
    818             }
    819 
    820             PTAssistantPlugin.setLastFix(new PTRouteSegment(wrongSegment.getFirstStop(),
    821                     wrongSegment.getLastStop(), wrongSegment.getFixVariants().get(0), (Relation) testError.getPrimitives().iterator().next()));
    822             return carryOutSingleFix(testError, wrongSegment.getFixVariants().get(0));
    823         }
    824 
    825         // if there is no fix:
    826         return fixErrorByZooming(testError);
    827 
    828     }
    829 
    830     /**
    831      * This is largely a copy of the displayFixVariants() method, adapted for
    832      * use with the key listener
    833      *
    834      * @param fixVariants fix variants
    835      * @param testError test error
    836      */
    837     private static void displayFixVariants(List<List<PTWay>> fixVariants, TestError testError) {
    838         // find the letters of the fix variants:
    839         char alphabet = 'A';
    840         final List<Character> allowedCharacters = new ArrayList<>();
    841         for (int i = 0; i < fixVariants.size(); i++) {
    842             allowedCharacters.add(alphabet);
    843             alphabet++;
    844         }
    845 
    846         // zoom to problem:
    847         final Collection<OsmPrimitive> waysToZoom = new ArrayList<>();
    848         for (Object highlightedPrimitive : testError.getHighlighted()) {
    849             waysToZoom.add((OsmPrimitive) highlightedPrimitive);
    850         }
    851         if (SwingUtilities.isEventDispatchThread()) {
    852             AutoScaleAction.zoomTo(waysToZoom);
    853         } else {
    854             SwingUtilities.invokeLater(new Runnable() {
    855                 @Override
    856                 public void run() {
    857                     AutoScaleAction.zoomTo(waysToZoom);
    858                 }
    859             });
    860         }
    861 
    862         // display the fix variants:
    863         final PTAssistantValidatorTest test = (PTAssistantValidatorTest) testError.getTester();
    864         test.addFixVariants(fixVariants);
    865         PTAssistantLayer.getLayer().repaint((Relation) testError.getPrimitives().iterator().next());
    866 
    867         // prepare the variables for the key listener:
    868         final TestError testErrorParameter = testError;
    869        
    870 //        // add the key listener:
    871         Main.map.mapView.requestFocus();
    872         Main.map.mapView.addKeyListener(new KeyListener() {
    873                
    874             public void keyTyped(KeyEvent e) {
    875                  //TODO Auto-generated method stub
    876             }
    877 
    878             public void keyPressed(KeyEvent e) {
    879                 Character typedKey = e.getKeyChar();
    880                 Character typedKeyUpperCase = typedKey.toString().toUpperCase().toCharArray()[0];
    881                 if (allowedCharacters.contains(typedKeyUpperCase)) {
    882                     Main.map.mapView.removeKeyListener(this);
    883                     List<PTWay> selectedFix = test.getFixVariant(typedKeyUpperCase);
    884                     test.clearFixVariants();
    885                     carryOutSelectedFix(testErrorParameter, selectedFix);
    886                 }
    887                 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
    888                     Main.map.mapView.removeKeyListener(this);
    889                     test.clearFixVariants();
    890                 }
    891             }
    892 
    893             public void keyReleased(KeyEvent e) {
    894                 // TODO Auto-generated method stub
    895             }
    896         });
    897 
    898         // display the notification:
    899         if (SwingUtilities.isEventDispatchThread()) {
    900             Notification notification = new Notification(
    901                     tr("Type letter to select the fix variant or press Escape for no fix"));
    902             notification.show();
    903         } else {
    904             SwingUtilities.invokeLater(new Runnable() {
    905                 @Override
    906                 public void run() {
    907                     Notification notification = new Notification(
    908                             tr("Type letter to select the fix variant or press Escape for no fix"));
    909                     notification.show();
    910                 }
    911             });
    912         }
    913     }
    914 
    915     /**
    916      * Carries out the fix (i.e. modifies the route) after the user has picked
    917      * the fix from several fix variants.
    918      *
    919      * @param testError
    920      *            test error to be fixed
    921      * @param fix
    922      *            the fix variant to be adopted
    923      */
    924     private static void carryOutSelectedFix(TestError testError, List<PTWay> fix) {
    925         // modify the route:
    926         Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
    927         Relation modifiedRelation = new Relation(originalRelation);
    928         modifiedRelation.setMembers(getModifiedRelationMembers(testError, fix));
    929         ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
    930         Main.main.undoRedo.addNoRedraw(changeCommand);
    931         Main.main.undoRedo.afterAdd();
    932         PTRouteSegment wrongSegment = wrongSegments.get(testError);
    933         wrongSegments.remove(testError);
    934         wrongSegment.setPTWays(fix);
    935         addCorrectSegment(wrongSegment);
    936         PTAssistantPlugin.setLastFixNoGui(wrongSegment);
    937 
    938         // get ways for the fix:
    939         List<Way> primitives = new ArrayList<>();
    940         for (PTWay ptway : fix) {
    941             primitives.addAll(ptway.getWays());
    942         }
    943 
    944         // get layer:
    945         OsmDataLayer layer = null;
    946         List<OsmDataLayer> listOfLayers = Main.getLayerManager().getLayersOfType(OsmDataLayer.class);
    947         for (OsmDataLayer osmDataLayer : listOfLayers) {
    948             if (osmDataLayer.data == originalRelation.getDataSet()) {
    949                 layer = osmDataLayer;
    950                 break;
    951             }
    952         }
    953 
    954         // create editor:
    955         GenericRelationEditor editor = (GenericRelationEditor) RelationEditor.getEditor(layer, originalRelation,
    956                 originalRelation.getMembersFor(primitives));
    957 
    958         // open editor:
    959         editor.setVisible(true);
    960 
    961     }
    962                
    963     /**
    964      * Carries out the fix (i.e. modifies the route) when there is only one fix
    965      * variant.
    966      *
    967      * @param testError test error
    968      * @param fix fix
    969      */
    970     private static Command carryOutSingleFix(TestError testError, List<PTWay> fix) {
    971         // Zoom to the problematic ways:
    972         final Collection<OsmPrimitive> waysToZoom = new ArrayList<>();
    973         for (Object highlightedPrimitive : testError.getHighlighted()) {
    974             waysToZoom.add((OsmPrimitive) highlightedPrimitive);
    975         }
    976         if (SwingUtilities.isEventDispatchThread()) {
    977             AutoScaleAction.zoomTo(waysToZoom);
    978         } else {
    979             SwingUtilities.invokeLater(new Runnable() {
    980                 @Override
    981                 public void run() {
    982                     AutoScaleAction.zoomTo(waysToZoom);
    983                 }
    984             });
    985         }
    986                
    987         // wait:
    988         synchronized (SegmentChecker.class) {
    989             try {
    990                 SegmentChecker.class.wait(1500);
    991             } catch (InterruptedException e) {
    992                 // TODO Auto-generated catch block
    993                 e.printStackTrace();
    994             }
    995         }
    996 
    997         // modify the route:
    998         Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
    999         Relation modifiedRelation = new Relation(originalRelation);
    1000         modifiedRelation.setMembers(getModifiedRelationMembers(testError, fix));
    1001         wrongSegments.remove(testError);
    1002         ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
    1003         return changeCommand;
    1004     }
    1005 
    1006     /**
    1007      * Returns a list of the modified relation members. This list can be used by
    1008      * the calling method (relation.setMemers()) to modify the modify the route
    1009      * relation. The route relation is not modified by this method. The lists of
    1010      * wrong and correct segments are not updated.
    1011      *
    1012      * @param testError
    1013      *            test error to be fixed
    1014      * @param fix
    1015      *            the fix variant to be adopted
    1016      * @return List of modified relation members to be applied to the route
    1017      *         relation
    1018      */
    1019     private static List<RelationMember> getModifiedRelationMembers(TestError testError, List<PTWay> fix) {
    1020         PTRouteSegment wrongSegment = wrongSegments.get(testError);
    1021         Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
    1022 
    1023         // copy stops first:
    1024         List<RelationMember> modifiedRelationMembers = listStopMembers(originalRelation);
    1025 
    1026         // copy PTWays last:
    1027         List<RelationMember> waysOfOriginalRelation = listNotStopMembers(originalRelation);
    1028         for (int i = 0; i < waysOfOriginalRelation.size(); i++) {
    1029             if (waysOfOriginalRelation.get(i).getWay() == wrongSegment.getPTWays().get(0).getWays().get(0)) {
    1030                 modifiedRelationMembers.addAll(fix);
    1031                 i = i + wrongSegment.getPTWays().size() - 1;
    1032             } else {
    1033                 modifiedRelationMembers.add(waysOfOriginalRelation.get(i));
    1034             }
    1035         }
    1036 
    1037         return modifiedRelationMembers;
    1038     }
    1039 
    1040     public static void carryOutRepeatLastFix(PTRouteSegment segment) {
    1041 
    1042         List<TestError> wrongSegmentsToRemove = new ArrayList<>();
    1043 
    1044         // find all wrong ways that have the same segment:
    1045         for (TestError testError: wrongSegments.keySet()) {
    1046             PTRouteSegment wrongSegment = wrongSegments.get(testError);
    1047             if (wrongSegment.getFirstWay() == segment.getFirstWay() && wrongSegment.getLastWay() == segment.getLastWay()) {
    1048                 // modify the route:
    1049                 Relation originalRelation = wrongSegment.getRelation();
    1050                 Relation modifiedRelation = new Relation(originalRelation);
    1051                 modifiedRelation.setMembers(getModifiedRelationMembers(testError, segment.getPTWays()));
    1052                 ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
    1053                 Main.main.undoRedo.addNoRedraw(changeCommand);
    1054                 Main.main.undoRedo.afterAdd();
    1055                 wrongSegmentsToRemove.add(testError);
    1056             }
    1057         }
    1058 
    1059         // update the errors displayed in the validator dialog:
    1060         List<TestError> modifiedValidatorTestErrors = new ArrayList<>();
    1061         for (TestError validatorTestError: Main.map.validatorDialog.tree.getErrors()) {
    1062             if (!wrongSegmentsToRemove.contains(validatorTestError)) {
    1063                 modifiedValidatorTestErrors.add(validatorTestError);
    1064             }
    1065         }
    1066         Main.map.validatorDialog.tree.setErrors(modifiedValidatorTestErrors);
    1067 
    1068         // update wrong segments:
    1069         for (TestError testError: wrongSegmentsToRemove) {
    1070             wrongSegments.remove(testError);
    1071         }
    1072 
    1073     }
    1074 
    1075     /**
    1076      * Resets the static list variables (used for unit testing)
    1077      */
    1078     protected static void reset() {
    1079         correctSegments.clear();
    1080         wrongSegments.clear();
    1081     }
     51        /* PTRouteSegments that have been validated and are correct */
     52        private static List<PTRouteSegment> correctSegments = new ArrayList<>();
     53
     54        /* PTRouteSegments that are wrong, stored in case the user calls the fix */
     55        protected static HashMap<TestError, PTRouteSegment> wrongSegments = new HashMap<>();
     56        protected static HashMap<Builder, PTRouteSegment> wrongSegmentBuilders = new HashMap<>();
     57
     58        /* Manager of the PTStops and PTWays of the current route */
     59        private PTRouteDataManager manager;
     60
     61        /* Assigns PTStops to nearest PTWays and stores that correspondence */
     62        private StopToWayAssigner assigner;
     63
     64        public SegmentChecker(Relation relation, Test test) {
     65
     66                super(relation, test);
     67
     68                this.manager = new PTRouteDataManager(relation);
     69
     70                for (RelationMember rm : manager.getFailedMembers()) {
     71                        List<Relation> primitives = new ArrayList<>(1);
     72                        primitives.add(relation);
     73                        List<OsmPrimitive> highlighted = new ArrayList<>(1);
     74                        highlighted.add(rm.getMember());
     75                        Builder builder = TestError.builder(this.test, Severity.WARNING,
     76                                        PTAssistantValidatorTest.ERROR_CODE_RELAITON_MEMBER_ROLES);
     77                        builder.message(tr("PT: Relation member roles do not match tags"));
     78                        builder.primitives(primitives);
     79                        builder.highlight(highlighted);
     80                        TestError e = builder.build();
     81                        this.errors.add(e);
     82                }
     83
     84                this.assigner = new StopToWayAssigner(manager.getPTWays());
     85
     86        }
     87
     88        /**
     89         * Returns the number of route segments that have been already successfully
     90         * verified
     91         *
     92         * @return the number of route segments
     93         */
     94        public static int getCorrectSegmentCount() {
     95                return correctSegments.size();
     96        }
     97
     98        /**
     99         * Adds the given correct segment to the list of correct segments without
     100         * checking its correctness
     101         *
     102         * @param segment
     103         *            to add to the list of correct segments
     104         */
     105        public static synchronized void addCorrectSegment(PTRouteSegment segment) {
     106                for (PTRouteSegment correctSegment : correctSegments) {
     107                        if (correctSegment.equalsRouteSegment(segment)) {
     108                                return;
     109                        }
     110                }
     111                correctSegments.add(segment);
     112        }
     113
     114        /**
     115         * Used for unit tests
     116         *
     117         * @param error
     118         *            test error
     119         * @return wrong route segment
     120         */
     121        protected static PTRouteSegment getWrongSegment(TestError error) {
     122                return wrongSegments.get(error);
     123        }
     124
     125        public void performFirstStopTest() {
     126
     127                performEndStopTest(manager.getFirstStop());
     128
     129        }
     130
     131        public void performLastStopTest() {
     132
     133                performEndStopTest(manager.getLastStop());
     134
     135        }
     136
     137        private void performEndStopTest(PTStop endStop) {
     138
     139                if (endStop == null) {
     140                        return;
     141                }
     142
     143                /*
     144                 * This test checks: (1) that a stop position exists; (2) that it is the
     145                 * first or last node of its parent ways which belong to this route.
     146                 */
     147
     148                if (endStop.getStopPosition() == null) {
     149
     150                        List<Node> potentialStopPositionList = endStop.findPotentialStopPositions();
     151                        List<Node> stopPositionsOfThisRoute = new ArrayList<>();
     152                        boolean containsAtLeastOneStopPositionAsFirstOrLastNode = false;
     153
     154                        for (Node potentialStopPosition : potentialStopPositionList) {
     155
     156                                int belongsToWay = belongsToAWayOfThisRoute(potentialStopPosition);
     157
     158                                if (belongsToWay == 0) {
     159                                        stopPositionsOfThisRoute.add(potentialStopPosition);
     160                                        containsAtLeastOneStopPositionAsFirstOrLastNode = true;
     161                                }
     162
     163                                if (belongsToWay == 1) {
     164                                        stopPositionsOfThisRoute.add(potentialStopPosition);
     165                                }
     166                        }
     167
     168                        if (stopPositionsOfThisRoute.isEmpty()) {
     169                                List<Relation> primitives = new ArrayList<>(1);
     170                                primitives.add(relation);
     171                                List<OsmPrimitive> highlighted = new ArrayList<>(1);
     172                                highlighted.add(endStop.getPlatform());
     173                                Builder builder = TestError.builder(this.test, Severity.WARNING,
     174                                                PTAssistantValidatorTest.ERROR_CODE_END_STOP);
     175                                builder.message(tr("PT: Route should start and end with a stop_position"));
     176                                builder.primitives(primitives);
     177                                builder.highlight(highlighted);
     178                                TestError e = builder.build();
     179                                this.errors.add(e);
     180                                return;
     181                        }
     182
     183                        if (stopPositionsOfThisRoute.size() == 1) {
     184                                endStop.setStopPosition(stopPositionsOfThisRoute.get(0));
     185                        }
     186
     187                        // At this point, there is at least one stop_position for this
     188                        // endStop:
     189                        if (!containsAtLeastOneStopPositionAsFirstOrLastNode) {
     190                                List<Relation> primitives = new ArrayList<>(1);
     191                                primitives.add(relation);
     192                                List<OsmPrimitive> highlighted = new ArrayList<>();
     193                                highlighted.addAll(stopPositionsOfThisRoute);
     194
     195                                Builder builder = TestError.builder(this.test, Severity.WARNING,
     196                                                PTAssistantValidatorTest.ERROR_CODE_SPLIT_WAY);
     197                                builder.message(tr("PT: First or last way needs to be split"));
     198                                builder.primitives(primitives);
     199                                builder.highlight(highlighted);
     200                                TestError e = builder.build();
     201                                this.errors.add(e);
     202                        }
     203
     204                } else {
     205
     206                        // if the stop_position is known:
     207                        int belongsToWay = this.belongsToAWayOfThisRoute(endStop.getStopPosition());
     208
     209                        if (belongsToWay == 1) {
     210
     211                                List<Relation> primitives = new ArrayList<>(1);
     212                                primitives.add(relation);
     213                                List<OsmPrimitive> highlighted = new ArrayList<>();
     214                                highlighted.add(endStop.getStopPosition());
     215                                Builder builder = TestError.builder(this.test, Severity.WARNING,
     216                                                PTAssistantValidatorTest.ERROR_CODE_SPLIT_WAY);
     217                                builder.message(tr("PT: First or last way needs to be split"));
     218                                builder.primitives(primitives);
     219                                builder.highlight(highlighted);
     220                                TestError e = builder.build();
     221                                this.errors.add(e);
     222                        }
     223                }
     224
     225        }
     226
     227        /**
     228         * Checks if the given node belongs to the ways of this route.
     229         *
     230         * @param node
     231         *            Node to be checked
     232         * @return 1 if belongs only as an inner node, 0 if belongs as a first or
     233         *         last node for at least one way, -1 if does not belong to any way.
     234         */
     235        private int belongsToAWayOfThisRoute(Node node) {
     236
     237                boolean contains = false;
     238
     239                List<PTWay> ptways = manager.getPTWays();
     240                for (PTWay ptway : ptways) {
     241                        List<Way> ways = ptway.getWays();
     242                        for (Way way : ways) {
     243                                if (way.containsNode(node)) {
     244
     245                                        if (way.firstNode().equals(node) || way.lastNode().equals(node)) {
     246                                                return 0;
     247                                        }
     248
     249                                        contains = true;
     250                                }
     251                        }
     252                }
     253
     254                if (contains) {
     255                        return 1;
     256                }
     257
     258                return -1;
     259        }
     260
     261        public void performStopNotServedTest() {
     262                for (PTStop stop : manager.getPTStops()) {
     263                        Way way = assigner.get(stop);
     264                        if (way == null) {
     265                                createStopError(stop);
     266                        }
     267                }
     268        }
     269
     270        /**
     271         * Performs the stop-by-stop test by visiting each segment between two
     272         * consecutive stops and checking if the ways between them are correct
     273         */
     274        public void performStopByStopTest() {
     275
     276                if (manager.getPTStopCount() < 2) {
     277                        return;
     278                }
     279
     280                List<OsmPrimitive> lastCreatedBuilderHighlighted = null;
     281
     282                // Check each route segment:
     283                for (int i = 1; i < manager.getPTStopCount(); i++) {
     284
     285                        PTStop startStop = manager.getPTStops().get(i - 1);
     286                        PTStop endStop = manager.getPTStops().get(i);
     287
     288                        Way startWay = assigner.get(startStop);
     289                        Way endWay = assigner.get(endStop);
     290                        if (startWay == null || endWay == null || (startWay == endWay && startWay == manager.getLastWay())) {
     291                                continue;
     292                        }
     293
     294                        List<PTWay> segmentWays = manager.getPTWaysBetween(startWay, endWay);
     295
     296                        Node firstNode = findFirstNodeOfRouteSegmentInDirectionOfTravel(segmentWays.get(0));
     297
     298                        // get last added TestErrorBuilder:
     299
     300                        if (firstNode == null) {
     301                                // check if this error has just been reported:
     302                                if (wrongSegmentBuilders.isEmpty() && lastCreatedBuilderHighlighted.size() == 1
     303                                                && lastCreatedBuilderHighlighted.get(0) == startWay) {
     304                                        // do nothing, this error has already been reported in
     305                                        // the previous route segment
     306                                } else {
     307                                        List<Relation> primitives = new ArrayList<>(1);
     308                                        primitives.add(relation);
     309                                        List<OsmPrimitive> highlighted = new ArrayList<>();
     310                                        highlighted.add(startWay);
     311                                        Builder builder = TestError.builder(this.test, Severity.WARNING,
     312                                                        PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP);
     313                                        builder.primitives(primitives);
     314                                        builder.highlight(highlighted);
     315                                        PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
     316                                        wrongSegmentBuilders.put(builder, routeSegment);
     317                                }
     318                                continue;
     319                        }
     320
     321                        PTWay wronglySortedPtway = existingWaySortingIsWrong(segmentWays.get(0), firstNode,
     322                                        segmentWays.get(segmentWays.size() - 1));
     323                        if (wronglySortedPtway == null) { // i.e. if the sorting is correct:
     324                                PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
     325                                addCorrectSegment(routeSegment);
     326                        } else { // i.e. if the sorting is wrong:
     327                                PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
     328                                // TestError error = this.errors.get(this.errors.size() - 1);
     329                                // wrongSegments.put(error, routeSegment);
     330
     331                                List<Relation> primitives = new ArrayList<>(1);
     332                                primitives.add(relation);
     333                                List<OsmPrimitive> highlighted = new ArrayList<>();
     334                                highlighted.addAll(wronglySortedPtway.getWays());
     335                                Builder builder = TestError.builder(this.test, Severity.WARNING,
     336                                                PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP);
     337                                builder.primitives(primitives);
     338                                builder.highlight(highlighted);
     339                                lastCreatedBuilderHighlighted = highlighted;
     340                                wrongSegmentBuilders.put(builder, routeSegment);
     341                        }
     342                }
     343        }
     344
     345        /**
     346         * Creates a TestError and adds it to the list of errors for a stop that is
     347         * not served.
     348         *
     349         * @param stop
     350         *            stop
     351         */
     352        private void createStopError(PTStop stop) {
     353                List<Relation> primitives = new ArrayList<>(1);
     354                primitives.add(relation);
     355                List<OsmPrimitive> highlighted = new ArrayList<>();
     356                OsmPrimitive stopPrimitive = stop.getPlatform();
     357                if (stopPrimitive == null) {
     358                        stopPrimitive = stop.getStopPosition();
     359                }
     360                highlighted.add(stopPrimitive);
     361                Builder builder = TestError.builder(this.test, Severity.WARNING,
     362                                PTAssistantValidatorTest.ERROR_CODE_STOP_NOT_SERVED);
     363                builder.message(tr("PT: Stop not served"));
     364                builder.primitives(primitives);
     365                builder.highlight(highlighted);
     366                TestError e = builder.build();
     367                this.errors.add(e);
     368        }
     369
     370        private Node findFirstNodeOfRouteSegmentInDirectionOfTravel(PTWay startWay) {
     371
     372                // 1) at first check if one of the first or last node of the first ptway
     373                // is a deadend node:
     374                Node[] startWayEndnodes = startWay.getEndNodes();
     375                if (isDeadendNode(startWayEndnodes[0])) {
     376                        return startWayEndnodes[0];
     377                }
     378                if (isDeadendNode(startWayEndnodes[1])) {
     379                        return startWayEndnodes[1];
     380                }
     381
     382                // 2) failing that, check which node this startWay shares with the
     383                // following way:
     384                PTWay nextWay = manager.getNextPTWay(startWay);
     385                if (nextWay == null) {
     386                        return null;
     387                }
     388                PTWay wayAfterNext = manager.getNextPTWay(nextWay);
     389                Node[] nextWayEndnodes = nextWay.getEndNodes();
     390                if ((startWayEndnodes[0] == nextWayEndnodes[0] && startWayEndnodes[1] == nextWayEndnodes[1])
     391                                || (startWayEndnodes[0] == nextWayEndnodes[1] && startWayEndnodes[1] == nextWayEndnodes[0])) {
     392                        // if this is a split roundabout:
     393                        Node[] wayAfterNextEndnodes = wayAfterNext.getEndNodes();
     394                        if (startWayEndnodes[0] == wayAfterNextEndnodes[0] || startWayEndnodes[0] == wayAfterNextEndnodes[1]) {
     395                                return startWayEndnodes[0];
     396                        }
     397                        if (startWayEndnodes[1] == wayAfterNextEndnodes[0] || startWayEndnodes[1] == wayAfterNextEndnodes[1]) {
     398                                return startWayEndnodes[1];
     399                        }
     400                }
     401
     402                if (startWayEndnodes[0] == nextWayEndnodes[0] || startWayEndnodes[0] == nextWayEndnodes[1]) {
     403                        return startWayEndnodes[1];
     404                }
     405                if (startWayEndnodes[1] == nextWayEndnodes[0] || startWayEndnodes[1] == nextWayEndnodes[1]) {
     406                        return startWayEndnodes[0];
     407                }
     408
     409                return null;
     410
     411        }
     412
     413        private boolean isDeadendNode(Node node) {
     414                int count = 0;
     415                for (PTWay ptway : manager.getPTWays()) {
     416                        List<Way> ways = ptway.getWays();
     417                        for (Way way : ways) {
     418                                if (way.firstNode() == node || way.lastNode() == node) {
     419                                        count++;
     420                                }
     421                        }
     422                }
     423                return count == 1;
     424        }
     425
     426        /**
     427         * Finds the deadend node closest to the given node represented by its
     428         * coordinates
     429         *
     430         * @param coord
     431         *            coordinates of the givenn node
     432         * @param deadendNodes
     433         *            dead end nodes
     434         * @return the closest deadend node
     435         */
     436        @SuppressWarnings("unused")
     437        private Node findClosestDeadendNode(LatLon coord, List<Node> deadendNodes) {
     438
     439                Node closestDeadendNode = null;
     440                double minSqDistance = Double.MAX_VALUE;
     441                for (Node deadendNode : deadendNodes) {
     442                        double distanceSq = coord.distanceSq(deadendNode.getCoor());
     443                        if (distanceSq < minSqDistance) {
     444                                minSqDistance = distanceSq;
     445                                closestDeadendNode = deadendNode;
     446                        }
     447                }
     448                return closestDeadendNode;
     449
     450        }
     451
     452        /**
     453         * Checks if the existing sorting of the given route segment is correct
     454         *
     455         * @param start
     456         *            PTWay assigned to the first stop of the segment
     457         * @param startWayPreviousNodeInDirectionOfTravel
     458         *            Node if the start way which is furthest away from the rest of
     459         *            the route
     460         * @param end
     461         *            PTWay assigned to the end stop of the segment
     462         * @return null if the sorting is correct, or the wrongly sorted PTWay
     463         *         otherwise.
     464         */
     465        private PTWay existingWaySortingIsWrong(PTWay start, Node startWayPreviousNodeInDirectionOfTravel, PTWay end) {
     466
     467                if (start == end) {
     468                        // if both PTStops are on the same PTWay
     469                        // return true;
     470                        return null;
     471                }
     472
     473                PTWay current = start;
     474                Node currentNode = startWayPreviousNodeInDirectionOfTravel;
     475
     476                while (!current.equals(end)) {
     477                        // "equals" is used here instead of "==" because when the same way
     478                        // is passed multiple times by the bus, the algorithm should stop no
     479                        // matter which of the geometrically equal PTWays it finds
     480
     481                        PTWay nextPTWayAccortingToExistingSorting = manager.getNextPTWay(current);
     482
     483                        // if current contains an unsplit roundabout:
     484                        if (current.containsUnsplitRoundabout()) {
     485                                currentNode = manager.getCommonNode(current, nextPTWayAccortingToExistingSorting);
     486                                if (currentNode == null) {
     487
     488                                        return current;
     489
     490                                }
     491                        } else {
     492                                // if this is a regular way, not an unsplit roundabout
     493
     494                                // find the next node in direction of travel (which is part of
     495                                // the PTWay start):
     496                                currentNode = getOppositeEndNode(current, currentNode);
     497
     498                                List<PTWay> nextWaysInDirectionOfTravel = this.findNextPTWaysInDirectionOfTravel(current, currentNode);
     499
     500                                if (!nextWaysInDirectionOfTravel.contains(nextPTWayAccortingToExistingSorting)) {
     501                                        return current;
     502
     503                                }
     504                        }
     505
     506                        current = nextPTWayAccortingToExistingSorting;
     507
     508                }
     509
     510                return null;
     511        }
     512
     513        /**
     514         * Will return the same node if the way is an unsplit roundabout
     515         *
     516         * @param way
     517         *            way
     518         * @param node
     519         *            node
     520         * @return the same node if the way is an unsplit roundabout
     521         */
     522        private Node getOppositeEndNode(Way way, Node node) {
     523
     524                if (node == way.firstNode()) {
     525                        return way.lastNode();
     526                }
     527
     528                if (node == way.lastNode()) {
     529                        return way.firstNode();
     530                }
     531
     532                return null;
     533        }
     534
     535        /**
     536         * Does not work correctly for unsplit roundabouts
     537         *
     538         * @param ptway
     539         *            way
     540         * @param node
     541         *            node
     542         * @return node
     543         */
     544        private Node getOppositeEndNode(PTWay ptway, Node node) {
     545                if (ptway.isWay()) {
     546                        return getOppositeEndNode(ptway.getWays().get(0), node);
     547                }
     548
     549                Way firstWay = ptway.getWays().get(0);
     550                Way lastWay = ptway.getWays().get(ptway.getWays().size() - 1);
     551                Node oppositeNode = node;
     552                if (firstWay.firstNode() == node || firstWay.lastNode() == node) {
     553                        for (int i = 0; i < ptway.getWays().size(); i++) {
     554                                oppositeNode = getOppositeEndNode(ptway.getWays().get(i), oppositeNode);
     555                        }
     556                        return oppositeNode;
     557                } else if (lastWay.firstNode() == node || lastWay.lastNode() == node) {
     558                        for (int i = ptway.getWays().size() - 1; i >= 0; i--) {
     559                                oppositeNode = getOppositeEndNode(ptway.getWays().get(i), oppositeNode);
     560                        }
     561                        return oppositeNode;
     562                }
     563
     564                return null;
     565
     566        }
     567
     568        /**
     569         * Finds the next ways for the route stop-by-stop parsing procedure
     570         *
     571         * @param currentWay
     572         *            current way
     573         * @param nextNodeInDirectionOfTravel
     574         *            next node in direction of travel
     575         * @return the next ways for the route stop-by-stop parsing procedure
     576         */
     577        private List<PTWay> findNextPTWaysInDirectionOfTravel(PTWay currentWay, Node nextNodeInDirectionOfTravel) {
     578
     579                List<PTWay> nextPtways = new ArrayList<>();
     580
     581                List<PTWay> ptways = manager.getPTWays();
     582
     583                for (PTWay ptway : ptways) {
     584
     585                        if (ptway != currentWay) {
     586                                for (Way way : ptway.getWays()) {
     587                                        if (way.containsNode(nextNodeInDirectionOfTravel)) {
     588                                                nextPtways.add(ptway);
     589                                        }
     590                                }
     591                        }
     592                }
     593
     594                return nextPtways;
     595
     596        }
     597
     598        protected static boolean isFixable(TestError testError) {
     599
     600                /*-
     601                 * When is an error fixable (outdated)?
     602                 * - if there is a correct segment
     603                 * - if it can be fixed by sorting
     604                 * - if the route is compete even without some ways
     605                 * - if simple routing closes the gap
     606                 */
     607
     608                if (testError.getCode() == PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP) {
     609                        return true;
     610                }
     611
     612                return false;
     613
     614        }
     615
     616        @SuppressWarnings("unused")
     617        private static boolean isFixableByUsingCorrectSegment(TestError testError) {
     618                PTRouteSegment wrongSegment = wrongSegments.get(testError);
     619                PTRouteSegment correctSegment = null;
     620                for (PTRouteSegment segment : correctSegments) {
     621                        if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
     622                                        && wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
     623                                correctSegment = segment;
     624                                break;
     625                        }
     626                }
     627                return correctSegment != null;
     628        }
     629
     630        @SuppressWarnings("unused")
     631        private static boolean isFixableBySortingAndRemoval(TestError testError) {
     632                PTRouteSegment wrongSegment = wrongSegments.get(testError);
     633                List<List<PTWay>> fixVariants = wrongSegment.getFixVariants();
     634                if (!fixVariants.isEmpty()) {
     635                        return true;
     636                }
     637                return false;
     638        }
     639
     640        /**
     641         * Finds fixes using sorting and removal.
     642         */
     643        protected void findFixes() {
     644
     645                for (Builder builder : wrongSegmentBuilders.keySet()) {
     646
     647                        if (wrongSegmentBuilders.get(builder).getRelation() == this.relation) {
     648
     649                                findFix(builder);
     650
     651                        }
     652                }
     653
     654        }
     655
     656        /**
     657         * Modifies the error messages of the stop-by-stop test errors depending on how many fixes each of them has.
     658         */
     659        protected static void modifyStopByStopErrorMessages() {
     660
     661                for (Entry<Builder, PTRouteSegment> entry : SegmentChecker.wrongSegmentBuilders.entrySet()) {
     662
     663                        // change the error code based on the availability of fixes:
     664                        Builder builder = entry.getKey();
     665                        PTRouteSegment wrongSegment = entry.getValue();
     666                        List<PTRouteSegment> correctSegmentsForThisError = new ArrayList<>();
     667                        for (PTRouteSegment segment : correctSegments) {
     668                                if (wrongSegment.getFirstWay().getId() == segment.getFirstWay().getId()
     669                                                && wrongSegment.getLastWay().getId() == segment.getLastWay().getId()) {
     670                                        correctSegmentsForThisError.add(segment);
     671                                }
     672                        }
     673
     674                        int numberOfFixes = correctSegmentsForThisError.size();
     675
     676                        if (numberOfFixes == 0) {
     677                                numberOfFixes = wrongSegment.getFixVariants().size();
     678                        }
     679                        if (numberOfFixes == 0) {
     680                                for (PTRouteSegment segment : correctSegments) {
     681                                        if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
     682                                                        && wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
     683                                                correctSegmentsForThisError.add(segment);
     684                                        }
     685                                }
     686                                numberOfFixes = correctSegmentsForThisError.size();
     687                        }
     688
     689                        // change the error message:
     690                        if (numberOfFixes == 0) {
     691                                builder.message(tr("PT: Problem in the route segment with no automatic fix"));
     692                        } else if (numberOfFixes == 1) {
     693                                builder.message(tr("PT: Problem in the route segment with one automatic fix"));
     694                        } else {
     695                                builder.message("PT: Problem in the route segment with several automatic fixes");
     696                        }
     697
     698                }
     699
     700        }
     701
     702        /**
     703         * This method assumes that the first and the second ways of the route
     704         * segment are correctly connected. If they are not, the error will be
     705         * marked as not fixable.
     706         *
     707         * @param testError
     708         *            test error
     709         */
     710        private void findFix(Builder builder) {
     711
     712                PTRouteSegment wrongSegment = wrongSegmentBuilders.get(builder);
     713                PTWay startPTWay = wrongSegment.getFirstPTWay();
     714                PTWay endPTWay = wrongSegment.getLastPTWay();
     715
     716                Node previousNode = findFirstNodeOfRouteSegmentInDirectionOfTravel(startPTWay);
     717                if (previousNode == null) {
     718                        return;
     719                }
     720
     721                List<List<PTWay>> initialFixes = new ArrayList<>();
     722                List<PTWay> initialFix = new ArrayList<>();
     723                initialFix.add(startPTWay);
     724                initialFixes.add(initialFix);
     725
     726                List<List<PTWay>> allFixes = findWaysForFix(initialFixes, initialFix, previousNode, endPTWay);
     727                for (List<PTWay> fix : allFixes) {
     728                        if (!fix.isEmpty() && fix.get(fix.size() - 1).equals(endPTWay)) {
     729                                wrongSegment.addFixVariant(fix);
     730                        }
     731                }
     732
     733        }
     734
     735        /**
     736         * Recursive method to parse the route segment
     737         *
     738         * @param allFixes
     739         *            all fixes
     740         * @param currentFix
     741         *            current fix
     742         * @param previousNode
     743         *            previous node
     744         * @param endWay
     745         *            end way
     746         * @return list of list of ways
     747         */
     748        private List<List<PTWay>> findWaysForFix(List<List<PTWay>> allFixes, List<PTWay> currentFix, Node previousNode,
     749                        PTWay endWay) {
     750
     751                PTWay currentWay = currentFix.get(currentFix.size() - 1);
     752                Node nextNode = getOppositeEndNode(currentWay, previousNode);
     753
     754                List<PTWay> nextWays = this.findNextPTWaysInDirectionOfTravel(currentWay, nextNode);
     755
     756                if (nextWays.size() > 1) {
     757                        for (int i = 1; i < nextWays.size(); i++) {
     758                                List<PTWay> newFix = new ArrayList<>();
     759                                newFix.addAll(currentFix);
     760                                newFix.add(nextWays.get(i));
     761                                allFixes.add(newFix);
     762                                if (!nextWays.get(i).equals(endWay) && !currentFix.contains(nextWays.get(i))) {
     763                                        allFixes = findWaysForFix(allFixes, newFix, nextNode, endWay);
     764                                }
     765                        }
     766                }
     767
     768                if (!nextWays.isEmpty()) {
     769                        boolean contains = currentFix.contains(nextWays.get(0));
     770                        currentFix.add(nextWays.get(0));
     771                        if (!nextWays.get(0).equals(endWay) && !contains) {
     772                                allFixes = findWaysForFix(allFixes, currentFix, nextNode, endWay);
     773                        }
     774                }
     775
     776                return allFixes;
     777        }
     778
     779        /**
     780         * Fixes the error by first searching in the list of correct segments and
     781         * then trying to sort and remove existing route relation members
     782         *
     783         * @param testError
     784         *            test error
     785         * @return fix command
     786         */
     787        protected static Command fixError(TestError testError) {
     788
     789                // if fix options for another route are displayed in the pt_assistant
     790                // layer, clear them:
     791                ((PTAssistantValidatorTest) testError.getTester()).clearFixVariants();
     792
     793                PTRouteSegment wrongSegment = wrongSegments.get(testError);
     794
     795                // 1) try to fix by using the correct segment:
     796                List<PTRouteSegment> correctSegmentsForThisError = new ArrayList<>();
     797                for (PTRouteSegment segment : correctSegments) {
     798                        if (wrongSegment.getFirstWay().getId() == segment.getFirstWay().getId()
     799                                        && wrongSegment.getLastWay().getId() == segment.getLastWay().getId()) {
     800                                correctSegmentsForThisError.add(segment);
     801                        }
     802                }
     803
     804                // if no correct segment found, apply less strict criteria to look for
     805                // one:
     806                if (correctSegmentsForThisError.isEmpty() && wrongSegment.getFixVariants().isEmpty()) {
     807                        for (PTRouteSegment segment : correctSegments) {
     808                                if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
     809                                                && wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
     810                                        correctSegmentsForThisError.add(segment);
     811                                }
     812                        }
     813                        if (!correctSegmentsForThisError.isEmpty()) {
     814                                // display the notification:
     815                                if (SwingUtilities.isEventDispatchThread()) {
     816                                        Notification notification = new Notification(
     817                                                        tr("Warning: the diplayed fix variants are based on less strict criteria"));
     818                                        notification.show();
     819                                } else {
     820                                        SwingUtilities.invokeLater(new Runnable() {
     821                                                @Override
     822                                                public void run() {
     823                                                        Notification notification = new Notification(
     824                                                                        tr("Warning: the diplayed fix variants are based on less strict criteria"));
     825                                                        notification.show();
     826                                                }
     827                                        });
     828                                }
     829                        }
     830                }
     831
     832                if (!correctSegmentsForThisError.isEmpty()) {
     833
     834                        if (correctSegmentsForThisError.size() > 1) {
     835                                List<List<PTWay>> fixVariants = new ArrayList<>();
     836                                for (PTRouteSegment segment : correctSegmentsForThisError) {
     837                                        fixVariants.add(segment.getPTWays());
     838                                }
     839                                displayFixVariants(fixVariants, testError);
     840                                return null;
     841                        }
     842
     843                        PTAssistantPlugin.setLastFix(correctSegmentsForThisError.get(0));
     844                        return carryOutSingleFix(testError, correctSegmentsForThisError.get(0).getPTWays());
     845
     846                } else if (!wrongSegment.getFixVariants().isEmpty()) {
     847                        // 2) try to fix using the sorting and removal of existing ways
     848                        // of the wrong segment:
     849                        if (wrongSegment.getFixVariants().size() > 1) {
     850                                displayFixVariants(wrongSegment.getFixVariants(), testError);
     851                                return null;
     852                        }
     853
     854                        PTAssistantPlugin.setLastFix(new PTRouteSegment(wrongSegment.getFirstStop(), wrongSegment.getLastStop(),
     855                                        wrongSegment.getFixVariants().get(0), (Relation) testError.getPrimitives().iterator().next()));
     856                        return carryOutSingleFix(testError, wrongSegment.getFixVariants().get(0));
     857                }
     858
     859                // if there is no fix:
     860                return fixErrorByZooming(testError);
     861
     862        }
     863
     864        /**
     865         * This is largely a copy of the displayFixVariants() method, adapted for
     866         * use with the key listener
     867         *
     868         * @param fixVariants
     869         *            fix variants
     870         * @param testError
     871         *            test error
     872         */
     873        private static void displayFixVariants(List<List<PTWay>> fixVariants, TestError testError) {
     874                // find the letters of the fix variants:
     875                char alphabet = 'A';
     876                final List<Character> allowedCharacters = new ArrayList<>();
     877                for (int i = 0; i < fixVariants.size(); i++) {
     878                        allowedCharacters.add(alphabet);
     879                        alphabet++;
     880                }
     881
     882                // zoom to problem:
     883                final Collection<OsmPrimitive> waysToZoom = new ArrayList<>();
     884                for (Object highlightedPrimitive : testError.getHighlighted()) {
     885                        waysToZoom.add((OsmPrimitive) highlightedPrimitive);
     886                }
     887                if (SwingUtilities.isEventDispatchThread()) {
     888                        AutoScaleAction.zoomTo(waysToZoom);
     889                } else {
     890                        SwingUtilities.invokeLater(new Runnable() {
     891                                @Override
     892                                public void run() {
     893                                        AutoScaleAction.zoomTo(waysToZoom);
     894                                }
     895                        });
     896                }
     897
     898                // display the fix variants:
     899                final PTAssistantValidatorTest test = (PTAssistantValidatorTest) testError.getTester();
     900                test.addFixVariants(fixVariants);
     901                PTAssistantLayer.getLayer().repaint((Relation) testError.getPrimitives().iterator().next());
     902
     903                // prepare the variables for the key listener:
     904                final TestError testErrorParameter = testError;
     905
     906                // // add the key listener:
     907                Main.map.mapView.requestFocus();
     908                Main.map.mapView.addKeyListener(new KeyListener() {
     909
     910                        public void keyTyped(KeyEvent e) {
     911                                // TODO Auto-generated method stub
     912                        }
     913
     914                        public void keyPressed(KeyEvent e) {
     915                                Character typedKey = e.getKeyChar();
     916                                Character typedKeyUpperCase = typedKey.toString().toUpperCase().toCharArray()[0];
     917                                if (allowedCharacters.contains(typedKeyUpperCase)) {
     918                                        Main.map.mapView.removeKeyListener(this);
     919                                        List<PTWay> selectedFix = test.getFixVariant(typedKeyUpperCase);
     920                                        test.clearFixVariants();
     921                                        carryOutSelectedFix(testErrorParameter, selectedFix);
     922                                }
     923                                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
     924                                        Main.map.mapView.removeKeyListener(this);
     925                                        test.clearFixVariants();
     926                                }
     927                        }
     928
     929                        public void keyReleased(KeyEvent e) {
     930                                // TODO Auto-generated method stub
     931                        }
     932                });
     933
     934                // display the notification:
     935                if (SwingUtilities.isEventDispatchThread()) {
     936                        Notification notification = new Notification(
     937                                        tr("Type letter to select the fix variant or press Escape for no fix"));
     938                        notification.show();
     939                } else {
     940                        SwingUtilities.invokeLater(new Runnable() {
     941                                @Override
     942                                public void run() {
     943                                        Notification notification = new Notification(
     944                                                        tr("Type letter to select the fix variant or press Escape for no fix"));
     945                                        notification.show();
     946                                }
     947                        });
     948                }
     949        }
     950
     951        /**
     952         * Carries out the fix (i.e. modifies the route) after the user has picked
     953         * the fix from several fix variants.
     954         *
     955         * @param testError
     956         *            test error to be fixed
     957         * @param fix
     958         *            the fix variant to be adopted
     959         */
     960        private static void carryOutSelectedFix(TestError testError, List<PTWay> fix) {
     961                // modify the route:
     962                Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
     963                Relation modifiedRelation = new Relation(originalRelation);
     964                modifiedRelation.setMembers(getModifiedRelationMembers(testError, fix));
     965                ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
     966                Main.main.undoRedo.addNoRedraw(changeCommand);
     967                Main.main.undoRedo.afterAdd();
     968                PTRouteSegment wrongSegment = wrongSegments.get(testError);
     969                wrongSegments.remove(testError);
     970                wrongSegment.setPTWays(fix);
     971                addCorrectSegment(wrongSegment);
     972                PTAssistantPlugin.setLastFixNoGui(wrongSegment);
     973
     974                // get ways for the fix:
     975                List<Way> primitives = new ArrayList<>();
     976                for (PTWay ptway : fix) {
     977                        primitives.addAll(ptway.getWays());
     978                }
     979
     980                // get layer:
     981                OsmDataLayer layer = null;
     982                List<OsmDataLayer> listOfLayers = Main.getLayerManager().getLayersOfType(OsmDataLayer.class);
     983                for (OsmDataLayer osmDataLayer : listOfLayers) {
     984                        if (osmDataLayer.data == originalRelation.getDataSet()) {
     985                                layer = osmDataLayer;
     986                                break;
     987                        }
     988                }
     989
     990                // create editor:
     991                GenericRelationEditor editor = (GenericRelationEditor) RelationEditor.getEditor(layer, originalRelation,
     992                                originalRelation.getMembersFor(primitives));
     993
     994                // open editor:
     995                editor.setVisible(true);
     996
     997        }
     998
     999        /**
     1000         * Carries out the fix (i.e. modifies the route) when there is only one fix
     1001         * variant.
     1002         *
     1003         * @param testError
     1004         *            test error
     1005         * @param fix
     1006         *            fix
     1007         */
     1008        private static Command carryOutSingleFix(TestError testError, List<PTWay> fix) {
     1009                // Zoom to the problematic ways:
     1010                final Collection<OsmPrimitive> waysToZoom = new ArrayList<>();
     1011                for (Object highlightedPrimitive : testError.getHighlighted()) {
     1012                        waysToZoom.add((OsmPrimitive) highlightedPrimitive);
     1013                }
     1014                if (SwingUtilities.isEventDispatchThread()) {
     1015                        AutoScaleAction.zoomTo(waysToZoom);
     1016                } else {
     1017                        SwingUtilities.invokeLater(new Runnable() {
     1018                                @Override
     1019                                public void run() {
     1020                                        AutoScaleAction.zoomTo(waysToZoom);
     1021                                }
     1022                        });
     1023                }
     1024
     1025                // wait:
     1026                synchronized (SegmentChecker.class) {
     1027                        try {
     1028                                SegmentChecker.class.wait(1500);
     1029                        } catch (InterruptedException e) {
     1030                                // TODO Auto-generated catch block
     1031                                e.printStackTrace();
     1032                        }
     1033                }
     1034
     1035                // modify the route:
     1036                Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
     1037                Relation modifiedRelation = new Relation(originalRelation);
     1038                modifiedRelation.setMembers(getModifiedRelationMembers(testError, fix));
     1039                wrongSegments.remove(testError);
     1040                ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
     1041                return changeCommand;
     1042        }
     1043
     1044        /**
     1045         * Returns a list of the modified relation members. This list can be used by
     1046         * the calling method (relation.setMemers()) to modify the modify the route
     1047         * relation. The route relation is not modified by this method. The lists of
     1048         * wrong and correct segments are not updated.
     1049         *
     1050         * @param testError
     1051         *            test error to be fixed
     1052         * @param fix
     1053         *            the fix variant to be adopted
     1054         * @return List of modified relation members to be applied to the route
     1055         *         relation
     1056         */
     1057        private static List<RelationMember> getModifiedRelationMembers(TestError testError, List<PTWay> fix) {
     1058                PTRouteSegment wrongSegment = wrongSegments.get(testError);
     1059                Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
     1060
     1061                // copy stops first:
     1062                List<RelationMember> modifiedRelationMembers = listStopMembers(originalRelation);
     1063
     1064                // copy PTWays last:
     1065                List<RelationMember> waysOfOriginalRelation = listNotStopMembers(originalRelation);
     1066                for (int i = 0; i < waysOfOriginalRelation.size(); i++) {
     1067                        if (waysOfOriginalRelation.get(i).getWay() == wrongSegment.getPTWays().get(0).getWays().get(0)) {
     1068                                modifiedRelationMembers.addAll(fix);
     1069                                i = i + wrongSegment.getPTWays().size() - 1;
     1070                        } else {
     1071                                modifiedRelationMembers.add(waysOfOriginalRelation.get(i));
     1072                        }
     1073                }
     1074
     1075                return modifiedRelationMembers;
     1076        }
     1077
     1078        public static void carryOutRepeatLastFix(PTRouteSegment segment) {
     1079
     1080                List<TestError> wrongSegmentsToRemove = new ArrayList<>();
     1081
     1082                // find all wrong ways that have the same segment:
     1083                for (TestError testError : wrongSegments.keySet()) {
     1084                        PTRouteSegment wrongSegment = wrongSegments.get(testError);
     1085                        if (wrongSegment.getFirstWay() == segment.getFirstWay()
     1086                                        && wrongSegment.getLastWay() == segment.getLastWay()) {
     1087                                // modify the route:
     1088                                Relation originalRelation = wrongSegment.getRelation();
     1089                                Relation modifiedRelation = new Relation(originalRelation);
     1090                                modifiedRelation.setMembers(getModifiedRelationMembers(testError, segment.getPTWays()));
     1091                                ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
     1092                                Main.main.undoRedo.addNoRedraw(changeCommand);
     1093                                Main.main.undoRedo.afterAdd();
     1094                                wrongSegmentsToRemove.add(testError);
     1095                        }
     1096                }
     1097
     1098                // update the errors displayed in the validator dialog:
     1099                List<TestError> modifiedValidatorTestErrors = new ArrayList<>();
     1100                for (TestError validatorTestError : Main.map.validatorDialog.tree.getErrors()) {
     1101                        if (!wrongSegmentsToRemove.contains(validatorTestError)) {
     1102                                modifiedValidatorTestErrors.add(validatorTestError);
     1103                        }
     1104                }
     1105                Main.map.validatorDialog.tree.setErrors(modifiedValidatorTestErrors);
     1106
     1107                // update wrong segments:
     1108                for (TestError testError : wrongSegmentsToRemove) {
     1109                        wrongSegments.remove(testError);
     1110                }
     1111
     1112        }
     1113
     1114        /**
     1115         * Resets the static list variables (used for unit tests and in Test.startTest() method)
     1116         */
     1117        protected static void reset() {
     1118                correctSegments.clear();
     1119                wrongSegments.clear();
     1120                wrongSegmentBuilders.clear();
     1121        }
    10821122
    10831123}
Note: See TracChangeset for help on using the changeset viewer.