// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.pt_assistant.validation;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import javax.swing.SwingUtilities;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.AutoScaleAction;
import org.openstreetmap.josm.command.ChangeCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.validation.Severity;
import org.openstreetmap.josm.data.validation.Test;
import org.openstreetmap.josm.data.validation.TestError;
import org.openstreetmap.josm.data.validation.TestError.Builder;
import org.openstreetmap.josm.gui.Notification;
import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor;
import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.plugins.pt_assistant.PTAssistantPlugin;
import org.openstreetmap.josm.plugins.pt_assistant.data.PTRouteDataManager;
import org.openstreetmap.josm.plugins.pt_assistant.data.PTRouteSegment;
import org.openstreetmap.josm.plugins.pt_assistant.data.PTStop;
import org.openstreetmap.josm.plugins.pt_assistant.data.PTWay;
import org.openstreetmap.josm.plugins.pt_assistant.gui.PTAssistantLayer;
import org.openstreetmap.josm.plugins.pt_assistant.utils.StopToWayAssigner;
/**
* Performs tests of a route at the level of route segments (the stop-by-stop
* approach).
*
* @author darya
*
*/
public class SegmentChecker extends Checker {
/* PTRouteSegments that have been validated and are correct */
private static List<PTRouteSegment> correctSegments = new ArrayList<>();
/* PTRouteSegments that are wrong, stored in case the user calls the fix */
protected static HashMap<TestError, PTRouteSegment> wrongSegments = new HashMap<>();
protected static HashMap<Builder, PTRouteSegment> wrongSegmentBuilders = new HashMap<>();
/* Manager of the PTStops and PTWays of the current route */
private PTRouteDataManager manager;
/* Assigns PTStops to nearest PTWays and stores that correspondence */
private StopToWayAssigner assigner;
public SegmentChecker(Relation relation, Test test) {
super(relation, test);
this.manager = new PTRouteDataManager(relation);
for (RelationMember rm : manager.getFailedMembers()) {
List<Relation> primitives = new ArrayList<>(1);
primitives.add(relation);
List<OsmPrimitive> highlighted = new ArrayList<>(1);
highlighted.add(rm.getMember());
Builder builder = TestError.builder(this.test, Severity.WARNING,
PTAssistantValidatorTest.ERROR_CODE_RELAITON_MEMBER_ROLES);
builder.message(tr("PT: Relation member roles do not match tags"));
builder.primitives(primitives);
builder.highlight(highlighted);
TestError e = builder.build();
this.errors.add(e);
}
this.assigner = new StopToWayAssigner(manager.getPTWays());
}
/**
* Returns the number of route segments that have been already successfully
* verified
*
* @return the number of route segments
*/
public static int getCorrectSegmentCount() {
return correctSegments.size();
}
/**
* Adds the given correct segment to the list of correct segments without
* checking its correctness
*
* @param segment
* to add to the list of correct segments
*/
public static synchronized void addCorrectSegment(PTRouteSegment segment) {
for (PTRouteSegment correctSegment : correctSegments) {
if (correctSegment.equalsRouteSegment(segment)) {
return;
}
}
correctSegments.add(segment);
}
/**
* Used for unit tests
*
* @param error
* test error
* @return wrong route segment
*/
protected static PTRouteSegment getWrongSegment(TestError error) {
return wrongSegments.get(error);
}
public void performFirstStopTest() {
performEndStopTest(manager.getFirstStop());
}
public void performLastStopTest() {
performEndStopTest(manager.getLastStop());
}
private void performEndStopTest(PTStop endStop) {
if (endStop == null) {
return;
}
/*
* This test checks: (1) that a stop position exists; (2) that it is the
* first or last node of its parent ways which belong to this route.
*/
if (endStop.getStopPosition() == null) {
List<Node> potentialStopPositionList = endStop.findPotentialStopPositions();
List<Node> stopPositionsOfThisRoute = new ArrayList<>();
boolean containsAtLeastOneStopPositionAsFirstOrLastNode = false;
for (Node potentialStopPosition : potentialStopPositionList) {
int belongsToWay = belongsToAWayOfThisRoute(potentialStopPosition);
if (belongsToWay == 0) {
stopPositionsOfThisRoute.add(potentialStopPosition);
containsAtLeastOneStopPositionAsFirstOrLastNode = true;
}
if (belongsToWay == 1) {
stopPositionsOfThisRoute.add(potentialStopPosition);
}
}
if (stopPositionsOfThisRoute.isEmpty()) {
List<Relation> primitives = new ArrayList<>(1);
primitives.add(relation);
List<OsmPrimitive> highlighted = new ArrayList<>(1);
highlighted.add(endStop.getPlatform());
Builder builder = TestError.builder(this.test, Severity.WARNING,
PTAssistantValidatorTest.ERROR_CODE_END_STOP);
builder.message(tr("PT: Route should start and end with a stop_position"));
builder.primitives(primitives);
builder.highlight(highlighted);
TestError e = builder.build();
this.errors.add(e);
return;
}
if (stopPositionsOfThisRoute.size() == 1) {
endStop.setStopPosition(stopPositionsOfThisRoute.get(0));
}
// At this point, there is at least one stop_position for this
// endStop:
if (!containsAtLeastOneStopPositionAsFirstOrLastNode) {
List<Relation> primitives = new ArrayList<>(1);
primitives.add(relation);
List<OsmPrimitive> highlighted = new ArrayList<>();
highlighted.addAll(stopPositionsOfThisRoute);
Builder builder = TestError.builder(this.test, Severity.WARNING,
PTAssistantValidatorTest.ERROR_CODE_SPLIT_WAY);
builder.message(tr("PT: First or last way needs to be split"));
builder.primitives(primitives);
builder.highlight(highlighted);
TestError e = builder.build();
this.errors.add(e);
}
} else {
// if the stop_position is known:
int belongsToWay = this.belongsToAWayOfThisRoute(endStop.getStopPosition());
if (belongsToWay == 1) {
List<Relation> primitives = new ArrayList<>(1);
primitives.add(relation);
List<OsmPrimitive> highlighted = new ArrayList<>();
highlighted.add(endStop.getStopPosition());
Builder builder = TestError.builder(this.test, Severity.WARNING,
PTAssistantValidatorTest.ERROR_CODE_SPLIT_WAY);
builder.message(tr("PT: First or last way needs to be split"));
builder.primitives(primitives);
builder.highlight(highlighted);
TestError e = builder.build();
this.errors.add(e);
}
}
}
/**
* Checks if the given node belongs to the ways of this route.
*
* @param node
* Node to be checked
* @return 1 if belongs only as an inner node, 0 if belongs as a first or
* last node for at least one way, -1 if does not belong to any way.
*/
private int belongsToAWayOfThisRoute(Node node) {
boolean contains = false;
List<PTWay> ptways = manager.getPTWays();
for (PTWay ptway : ptways) {
List<Way> ways = ptway.getWays();
for (Way way : ways) {
if (way.containsNode(node)) {
if (way.firstNode().equals(node) || way.lastNode().equals(node)) {
return 0;
}
contains = true;
}
}
}
if (contains) {
return 1;
}
return -1;
}
public void performStopNotServedTest() {
for (PTStop stop : manager.getPTStops()) {
Way way = assigner.get(stop);
if (way == null) {
createStopError(stop);
}
}
}
/**
* Performs the stop-by-stop test by visiting each segment between two
* consecutive stops and checking if the ways between them are correct
*/
public void performStopByStopTest() {
if (manager.getPTStopCount() < 2) {
return;
}
List<OsmPrimitive> lastCreatedBuilderHighlighted = null;
// Check each route segment:
for (int i = 1; i < manager.getPTStopCount(); i++) {
PTStop startStop = manager.getPTStops().get(i - 1);
PTStop endStop = manager.getPTStops().get(i);
Way startWay = assigner.get(startStop);
Way endWay = assigner.get(endStop);
if (startWay == null || endWay == null || (startWay == endWay && startWay == manager.getLastWay())) {
continue;
}
List<PTWay> segmentWays = manager.getPTWaysBetween(startWay, endWay);
Node firstNode = findFirstNodeOfRouteSegmentInDirectionOfTravel(segmentWays.get(0));
if (firstNode == null) {
// check if this error has just been reported:
if (wrongSegmentBuilders.isEmpty() && lastCreatedBuilderHighlighted != null && lastCreatedBuilderHighlighted.size() == 1
&& lastCreatedBuilderHighlighted.get(0) == startWay) {
// do nothing, this error has already been reported in
// the previous route segment
} else {
List<Relation> primitives = new ArrayList<>(1);
primitives.add(relation);
List<OsmPrimitive> highlighted = new ArrayList<>();
highlighted.add(startWay);
Builder builder = TestError.builder(this.test, Severity.WARNING,
PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP);
builder.primitives(primitives);
builder.highlight(highlighted);
PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
wrongSegmentBuilders.put(builder, routeSegment);
}
continue;
}
PTWay wronglySortedPtway = existingWaySortingIsWrong(segmentWays.get(0), firstNode,
segmentWays.get(segmentWays.size() - 1));
if (wronglySortedPtway == null) { // i.e. if the sorting is correct:
PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
addCorrectSegment(routeSegment);
} else { // i.e. if the sorting is wrong:
PTRouteSegment routeSegment = new PTRouteSegment(startStop, endStop, segmentWays, relation);
// TestError error = this.errors.get(this.errors.size() - 1);
// wrongSegments.put(error, routeSegment);
List<Relation> primitives = new ArrayList<>(1);
primitives.add(relation);
List<OsmPrimitive> highlighted = new ArrayList<>();
highlighted.addAll(wronglySortedPtway.getWays());
Builder builder = TestError.builder(this.test, Severity.WARNING,
PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP);
builder.primitives(primitives);
builder.highlight(highlighted);
lastCreatedBuilderHighlighted = highlighted;
wrongSegmentBuilders.put(builder, routeSegment);
}
}
}
/**
* Creates a TestError and adds it to the list of errors for a stop that is
* not served.
*
* @param stop
* stop
*/
private void createStopError(PTStop stop) {
List<Relation> primitives = new ArrayList<>(1);
primitives.add(relation);
List<OsmPrimitive> highlighted = new ArrayList<>();
OsmPrimitive stopPrimitive = stop.getPlatform();
if (stopPrimitive == null) {
stopPrimitive = stop.getStopPosition();
}
highlighted.add(stopPrimitive);
Builder builder = TestError.builder(this.test, Severity.WARNING,
PTAssistantValidatorTest.ERROR_CODE_STOP_NOT_SERVED);
builder.message(tr("PT: Stop not served"));
builder.primitives(primitives);
builder.highlight(highlighted);
TestError e = builder.build();
this.errors.add(e);
}
private Node findFirstNodeOfRouteSegmentInDirectionOfTravel(PTWay startWay) {
// 1) at first check if one of the first or last node of the first ptway
// is a deadend node:
Node[] startWayEndnodes = startWay.getEndNodes();
if (isDeadendNode(startWayEndnodes[0])) {
return startWayEndnodes[0];
}
if (isDeadendNode(startWayEndnodes[1])) {
return startWayEndnodes[1];
}
// 2) failing that, check which node this startWay shares with the
// following way:
PTWay nextWay = manager.getNextPTWay(startWay);
if (nextWay == null) {
return null;
}
PTWay wayAfterNext = manager.getNextPTWay(nextWay);
Node[] nextWayEndnodes = nextWay.getEndNodes();
if ((startWayEndnodes[0] == nextWayEndnodes[0] && startWayEndnodes[1] == nextWayEndnodes[1])
|| (startWayEndnodes[0] == nextWayEndnodes[1] && startWayEndnodes[1] == nextWayEndnodes[0])) {
// if this is a split roundabout:
Node[] wayAfterNextEndnodes = wayAfterNext.getEndNodes();
if (startWayEndnodes[0] == wayAfterNextEndnodes[0] || startWayEndnodes[0] == wayAfterNextEndnodes[1]) {
return startWayEndnodes[0];
}
if (startWayEndnodes[1] == wayAfterNextEndnodes[0] || startWayEndnodes[1] == wayAfterNextEndnodes[1]) {
return startWayEndnodes[1];
}
}
if (startWayEndnodes[0] == nextWayEndnodes[0] || startWayEndnodes[0] == nextWayEndnodes[1]) {
return startWayEndnodes[1];
}
if (startWayEndnodes[1] == nextWayEndnodes[0] || startWayEndnodes[1] == nextWayEndnodes[1]) {
return startWayEndnodes[0];
}
return null;
}
private boolean isDeadendNode(Node node) {
int count = 0;
for (PTWay ptway : manager.getPTWays()) {
List<Way> ways = ptway.getWays();
for (Way way : ways) {
if (way.firstNode() == node || way.lastNode() == node) {
count++;
}
}
}
return count == 1;
}
/**
* Finds the deadend node closest to the given node represented by its
* coordinates
*
* @param coord
* coordinates of the givenn node
* @param deadendNodes
* dead end nodes
* @return the closest deadend node
*/
@SuppressWarnings("unused")
private Node findClosestDeadendNode(LatLon coord, List<Node> deadendNodes) {
Node closestDeadendNode = null;
double minSqDistance = Double.MAX_VALUE;
for (Node deadendNode : deadendNodes) {
double distanceSq = coord.distanceSq(deadendNode.getCoor());
if (distanceSq < minSqDistance) {
minSqDistance = distanceSq;
closestDeadendNode = deadendNode;
}
}
return closestDeadendNode;
}
/**
* Checks if the existing sorting of the given route segment is correct
*
* @param start
* PTWay assigned to the first stop of the segment
* @param startWayPreviousNodeInDirectionOfTravel
* Node if the start way which is furthest away from the rest of
* the route
* @param end
* PTWay assigned to the end stop of the segment
* @return null if the sorting is correct, or the wrongly sorted PTWay
* otherwise.
*/
private PTWay existingWaySortingIsWrong(PTWay start, Node startWayPreviousNodeInDirectionOfTravel, PTWay end) {
if (start == end) {
// if both PTStops are on the same PTWay
// return true;
return null;
}
PTWay current = start;
Node currentNode = startWayPreviousNodeInDirectionOfTravel;
while (!current.equals(end)) {
// "equals" is used here instead of "==" because when the same way
// is passed multiple times by the bus, the algorithm should stop no
// matter which of the geometrically equal PTWays it finds
PTWay nextPTWayAccortingToExistingSorting = manager.getNextPTWay(current);
// if current contains an unsplit roundabout:
if (current.containsUnsplitRoundabout()) {
currentNode = manager.getCommonNode(current, nextPTWayAccortingToExistingSorting);
if (currentNode == null) {
return current;
}
} else {
// if this is a regular way, not an unsplit roundabout
// find the next node in direction of travel (which is part of
// the PTWay start):
currentNode = getOppositeEndNode(current, currentNode);
List<PTWay> nextWaysInDirectionOfTravel = this.findNextPTWaysInDirectionOfTravel(current, currentNode);
if (!nextWaysInDirectionOfTravel.contains(nextPTWayAccortingToExistingSorting)) {
return current;
}
}
current = nextPTWayAccortingToExistingSorting;
}
return null;
}
/**
* Will return the same node if the way is an unsplit roundabout
*
* @param way
* way
* @param node
* node
* @return the same node if the way is an unsplit roundabout
*/
private Node getOppositeEndNode(Way way, Node node) {
if (node == way.firstNode()) {
return way.lastNode();
}
if (node == way.lastNode()) {
return way.firstNode();
}
return null;
}
/**
* Does not work correctly for unsplit roundabouts
*
* @param ptway
* way
* @param node
* node
* @return node
*/
private Node getOppositeEndNode(PTWay ptway, Node node) {
if (ptway.isWay()) {
return getOppositeEndNode(ptway.getWays().get(0), node);
}
Way firstWay = ptway.getWays().get(0);
Way lastWay = ptway.getWays().get(ptway.getWays().size() - 1);
Node oppositeNode = node;
if (firstWay.firstNode() == node || firstWay.lastNode() == node) {
for (int i = 0; i < ptway.getWays().size(); i++) {
oppositeNode = getOppositeEndNode(ptway.getWays().get(i), oppositeNode);
}
return oppositeNode;
} else if (lastWay.firstNode() == node || lastWay.lastNode() == node) {
for (int i = ptway.getWays().size() - 1; i >= 0; i--) {
oppositeNode = getOppositeEndNode(ptway.getWays().get(i), oppositeNode);
}
return oppositeNode;
}
return null;
}
/**
* Finds the next ways for the route stop-by-stop parsing procedure
*
* @param currentWay
* current way
* @param nextNodeInDirectionOfTravel
* next node in direction of travel
* @return the next ways for the route stop-by-stop parsing procedure
*/
private List<PTWay> findNextPTWaysInDirectionOfTravel(PTWay currentWay, Node nextNodeInDirectionOfTravel) {
List<PTWay> nextPtways = new ArrayList<>();
List<PTWay> ptways = manager.getPTWays();
for (PTWay ptway : ptways) {
if (ptway != currentWay) {
for (Way way : ptway.getWays()) {
if (way.containsNode(nextNodeInDirectionOfTravel)) {
nextPtways.add(ptway);
}
}
}
}
return nextPtways;
}
protected static boolean isFixable(TestError testError) {
/*-
* When is an error fixable (outdated)?
* - if there is a correct segment
* - if it can be fixed by sorting
* - if the route is compete even without some ways
* - if simple routing closes the gap
*/
if (testError.getCode() == PTAssistantValidatorTest.ERROR_CODE_STOP_BY_STOP) {
return true;
}
return false;
}
@SuppressWarnings("unused")
private static boolean isFixableByUsingCorrectSegment(TestError testError) {
PTRouteSegment wrongSegment = wrongSegments.get(testError);
PTRouteSegment correctSegment = null;
for (PTRouteSegment segment : correctSegments) {
if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
&& wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
correctSegment = segment;
break;
}
}
return correctSegment != null;
}
@SuppressWarnings("unused")
private static boolean isFixableBySortingAndRemoval(TestError testError) {
PTRouteSegment wrongSegment = wrongSegments.get(testError);
List<List<PTWay>> fixVariants = wrongSegment.getFixVariants();
if (!fixVariants.isEmpty()) {
return true;
}
return false;
}
/**
* Finds fixes using sorting and removal.
*/
protected void findFixes() {
for (Builder builder : wrongSegmentBuilders.keySet()) {
if (wrongSegmentBuilders.get(builder).getRelation() == this.relation) {
findFix(builder);
}
}
}
/**
* Modifies the error messages of the stop-by-stop test errors depending on how many fixes each of them has.
*/
protected static void modifyStopByStopErrorMessages() {
for (Entry<Builder, PTRouteSegment> entry : SegmentChecker.wrongSegmentBuilders.entrySet()) {
// change the error code based on the availability of fixes:
Builder builder = entry.getKey();
PTRouteSegment wrongSegment = entry.getValue();
List<PTRouteSegment> correctSegmentsForThisError = new ArrayList<>();
for (PTRouteSegment segment : correctSegments) {
if (wrongSegment.getFirstWay().getId() == segment.getFirstWay().getId()
&& wrongSegment.getLastWay().getId() == segment.getLastWay().getId()) {
correctSegmentsForThisError.add(segment);
}
}
int numberOfFixes = correctSegmentsForThisError.size();
if (numberOfFixes == 0) {
numberOfFixes = wrongSegment.getFixVariants().size();
}
if (numberOfFixes == 0) {
for (PTRouteSegment segment : correctSegments) {
if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
&& wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
correctSegmentsForThisError.add(segment);
}
}
numberOfFixes = correctSegmentsForThisError.size();
}
// change the error message:
if (numberOfFixes == 0) {
builder.message(tr("PT: Problem in the route segment with no automatic fix"));
} else if (numberOfFixes == 1) {
builder.message(tr("PT: Problem in the route segment with one automatic fix"));
} else {
builder.message("PT: Problem in the route segment with several automatic fixes");
}
}
}
/**
* This method assumes that the first and the second ways of the route
* segment are correctly connected. If they are not, the error will be
* marked as not fixable.
*
* @param testError
* test error
*/
private void findFix(Builder builder) {
PTRouteSegment wrongSegment = wrongSegmentBuilders.get(builder);
PTWay startPTWay = wrongSegment.getFirstPTWay();
PTWay endPTWay = wrongSegment.getLastPTWay();
Node previousNode = findFirstNodeOfRouteSegmentInDirectionOfTravel(startPTWay);
if (previousNode == null) {
return;
}
List<List<PTWay>> initialFixes = new ArrayList<>();
List<PTWay> initialFix = new ArrayList<>();
initialFix.add(startPTWay);
initialFixes.add(initialFix);
List<List<PTWay>> allFixes = findWaysForFix(initialFixes, initialFix, previousNode, endPTWay);
for (List<PTWay> fix : allFixes) {
if (!fix.isEmpty() && fix.get(fix.size() - 1).equals(endPTWay)) {
wrongSegment.addFixVariant(fix);
}
}
}
/**
* Recursive method to parse the route segment
*
* @param allFixes
* all fixes
* @param currentFix
* current fix
* @param previousNode
* previous node
* @param endWay
* end way
* @return list of list of ways
*/
private List<List<PTWay>> findWaysForFix(List<List<PTWay>> allFixes, List<PTWay> currentFix, Node previousNode,
PTWay endWay) {
PTWay currentWay = currentFix.get(currentFix.size() - 1);
Node nextNode = getOppositeEndNode(currentWay, previousNode);
List<PTWay> nextWays = this.findNextPTWaysInDirectionOfTravel(currentWay, nextNode);
if (nextWays.size() > 1) {
for (int i = 1; i < nextWays.size(); i++) {
List<PTWay> newFix = new ArrayList<>();
newFix.addAll(currentFix);
newFix.add(nextWays.get(i));
allFixes.add(newFix);
if (!nextWays.get(i).equals(endWay) && !currentFix.contains(nextWays.get(i))) {
allFixes = findWaysForFix(allFixes, newFix, nextNode, endWay);
}
}
}
if (!nextWays.isEmpty()) {
boolean contains = currentFix.contains(nextWays.get(0));
currentFix.add(nextWays.get(0));
if (!nextWays.get(0).equals(endWay) && !contains) {
allFixes = findWaysForFix(allFixes, currentFix, nextNode, endWay);
}
}
return allFixes;
}
/**
* Fixes the error by first searching in the list of correct segments and
* then trying to sort and remove existing route relation members
*
* @param testError
* test error
* @return fix command
*/
protected static Command fixError(TestError testError) {
// if fix options for another route are displayed in the pt_assistant
// layer, clear them:
((PTAssistantValidatorTest) testError.getTester()).clearFixVariants();
PTRouteSegment wrongSegment = wrongSegments.get(testError);
// 1) try to fix by using the correct segment:
List<PTRouteSegment> correctSegmentsForThisError = new ArrayList<>();
for (PTRouteSegment segment : correctSegments) {
if (wrongSegment.getFirstWay().getId() == segment.getFirstWay().getId()
&& wrongSegment.getLastWay().getId() == segment.getLastWay().getId()) {
correctSegmentsForThisError.add(segment);
}
}
// if no correct segment found, apply less strict criteria to look for
// one:
if (correctSegmentsForThisError.isEmpty() && wrongSegment.getFixVariants().isEmpty()) {
for (PTRouteSegment segment : correctSegments) {
if (wrongSegment.getFirstStop().equalsStop(segment.getFirstStop())
&& wrongSegment.getLastStop().equalsStop(segment.getLastStop())) {
correctSegmentsForThisError.add(segment);
}
}
if (!correctSegmentsForThisError.isEmpty()) {
// display the notification:
if (SwingUtilities.isEventDispatchThread()) {
Notification notification = new Notification(
tr("Warning: the diplayed fix variants are based on less strict criteria"));
notification.show();
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Notification notification = new Notification(
tr("Warning: the diplayed fix variants are based on less strict criteria"));
notification.show();
}
});
}
}
}
if (!correctSegmentsForThisError.isEmpty()) {
if (correctSegmentsForThisError.size() > 1) {
List<List<PTWay>> fixVariants = new ArrayList<>();
for (PTRouteSegment segment : correctSegmentsForThisError) {
fixVariants.add(segment.getPTWays());
}
displayFixVariants(fixVariants, testError);
return null;
}
PTAssistantPlugin.setLastFix(correctSegmentsForThisError.get(0));
return carryOutSingleFix(testError, correctSegmentsForThisError.get(0).getPTWays());
} else if (!wrongSegment.getFixVariants().isEmpty()) {
// 2) try to fix using the sorting and removal of existing ways
// of the wrong segment:
if (wrongSegment.getFixVariants().size() > 1) {
displayFixVariants(wrongSegment.getFixVariants(), testError);
return null;
}
PTAssistantPlugin.setLastFix(new PTRouteSegment(wrongSegment.getFirstStop(), wrongSegment.getLastStop(),
wrongSegment.getFixVariants().get(0), (Relation) testError.getPrimitives().iterator().next()));
return carryOutSingleFix(testError, wrongSegment.getFixVariants().get(0));
}
// if there is no fix:
return fixErrorByZooming(testError);
}
/**
* This is largely a copy of the displayFixVariants() method, adapted for
* use with the key listener
*
* @param fixVariants
* fix variants
* @param testError
* test error
*/
private static void displayFixVariants(List<List<PTWay>> fixVariants, TestError testError) {
// find the letters of the fix variants:
char alphabet = 'A';
final List<Character> allowedCharacters = new ArrayList<>();
for (int i = 0; i < fixVariants.size(); i++) {
allowedCharacters.add(alphabet);
alphabet++;
}
// zoom to problem:
final Collection<OsmPrimitive> waysToZoom = new ArrayList<>();
for (Object highlightedPrimitive : testError.getHighlighted()) {
waysToZoom.add((OsmPrimitive) highlightedPrimitive);
}
if (SwingUtilities.isEventDispatchThread()) {
AutoScaleAction.zoomTo(waysToZoom);
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
AutoScaleAction.zoomTo(waysToZoom);
}
});
}
// display the fix variants:
final PTAssistantValidatorTest test = (PTAssistantValidatorTest) testError.getTester();
test.addFixVariants(fixVariants);
PTAssistantLayer.getLayer().repaint((Relation) testError.getPrimitives().iterator().next());
// prepare the variables for the key listener:
final TestError testErrorParameter = testError;
// // add the key listener:
Main.map.mapView.requestFocus();
Main.map.mapView.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
public void keyPressed(KeyEvent e) {
Character typedKey = e.getKeyChar();
Character typedKeyUpperCase = typedKey.toString().toUpperCase().toCharArray()[0];
if (allowedCharacters.contains(typedKeyUpperCase)) {
Main.map.mapView.removeKeyListener(this);
List<PTWay> selectedFix = test.getFixVariant(typedKeyUpperCase);
test.clearFixVariants();
carryOutSelectedFix(testErrorParameter, selectedFix);
}
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
Main.map.mapView.removeKeyListener(this);
test.clearFixVariants();
}
}
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
});
// display the notification:
if (SwingUtilities.isEventDispatchThread()) {
Notification notification = new Notification(
tr("Type letter to select the fix variant or press Escape for no fix"));
notification.show();
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Notification notification = new Notification(
tr("Type letter to select the fix variant or press Escape for no fix"));
notification.show();
}
});
}
}
/**
* Carries out the fix (i.e. modifies the route) after the user has picked
* the fix from several fix variants.
*
* @param testError
* test error to be fixed
* @param fix
* the fix variant to be adopted
*/
private static void carryOutSelectedFix(TestError testError, List<PTWay> fix) {
// modify the route:
Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
Relation modifiedRelation = new Relation(originalRelation);
modifiedRelation.setMembers(getModifiedRelationMembers(testError, fix));
ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
Main.main.undoRedo.addNoRedraw(changeCommand);
Main.main.undoRedo.afterAdd();
PTRouteSegment wrongSegment = wrongSegments.get(testError);
wrongSegments.remove(testError);
wrongSegment.setPTWays(fix);
addCorrectSegment(wrongSegment);
PTAssistantPlugin.setLastFixNoGui(wrongSegment);
// get ways for the fix:
List<Way> primitives = new ArrayList<>();
for (PTWay ptway : fix) {
primitives.addAll(ptway.getWays());
}
// get layer:
OsmDataLayer layer = null;
List<OsmDataLayer> listOfLayers = Main.getLayerManager().getLayersOfType(OsmDataLayer.class);
for (OsmDataLayer osmDataLayer : listOfLayers) {
if (osmDataLayer.data == originalRelation.getDataSet()) {
layer = osmDataLayer;
break;
}
}
// create editor:
GenericRelationEditor editor = (GenericRelationEditor) RelationEditor.getEditor(layer, originalRelation,
originalRelation.getMembersFor(primitives));
// open editor:
editor.setVisible(true);
}
/**
* Carries out the fix (i.e. modifies the route) when there is only one fix
* variant.
*
* @param testError
* test error
* @param fix
* fix
*/
private static Command carryOutSingleFix(TestError testError, List<PTWay> fix) {
// Zoom to the problematic ways:
final Collection<OsmPrimitive> waysToZoom = new ArrayList<>();
for (Object highlightedPrimitive : testError.getHighlighted()) {
waysToZoom.add((OsmPrimitive) highlightedPrimitive);
}
if (SwingUtilities.isEventDispatchThread()) {
AutoScaleAction.zoomTo(waysToZoom);
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
AutoScaleAction.zoomTo(waysToZoom);
}
});
}
// wait:
synchronized (SegmentChecker.class) {
try {
SegmentChecker.class.wait(1500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// modify the route:
Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
Relation modifiedRelation = new Relation(originalRelation);
modifiedRelation.setMembers(getModifiedRelationMembers(testError, fix));
wrongSegments.remove(testError);
ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
return changeCommand;
}
/**
* Returns a list of the modified relation members. This list can be used by
* the calling method (relation.setMemers()) to modify the modify the route
* relation. The route relation is not modified by this method. The lists of
* wrong and correct segments are not updated.
*
* @param testError
* test error to be fixed
* @param fix
* the fix variant to be adopted
* @return List of modified relation members to be applied to the route
* relation
*/
private static List<RelationMember> getModifiedRelationMembers(TestError testError, List<PTWay> fix) {
PTRouteSegment wrongSegment = wrongSegments.get(testError);
Relation originalRelation = (Relation) testError.getPrimitives().iterator().next();
// copy stops first:
List<RelationMember> modifiedRelationMembers = listStopMembers(originalRelation);
// copy PTWays last:
List<RelationMember> waysOfOriginalRelation = listNotStopMembers(originalRelation);
for (int i = 0; i < waysOfOriginalRelation.size(); i++) {
if (waysOfOriginalRelation.get(i).getWay() == wrongSegment.getPTWays().get(0).getWays().get(0)) {
modifiedRelationMembers.addAll(fix);
i = i + wrongSegment.getPTWays().size() - 1;
} else {
modifiedRelationMembers.add(waysOfOriginalRelation.get(i));
}
}
return modifiedRelationMembers;
}
public static void carryOutRepeatLastFix(PTRouteSegment segment) {
List<TestError> wrongSegmentsToRemove = new ArrayList<>();
// find all wrong ways that have the same segment:
for (TestError testError : wrongSegments.keySet()) {
PTRouteSegment wrongSegment = wrongSegments.get(testError);
if (wrongSegment.getFirstWay() == segment.getFirstWay()
&& wrongSegment.getLastWay() == segment.getLastWay()) {
// modify the route:
Relation originalRelation = wrongSegment.getRelation();
Relation modifiedRelation = new Relation(originalRelation);
modifiedRelation.setMembers(getModifiedRelationMembers(testError, segment.getPTWays()));
ChangeCommand changeCommand = new ChangeCommand(originalRelation, modifiedRelation);
Main.main.undoRedo.addNoRedraw(changeCommand);
Main.main.undoRedo.afterAdd();
wrongSegmentsToRemove.add(testError);
}
}
// update the errors displayed in the validator dialog:
List<TestError> modifiedValidatorTestErrors = new ArrayList<>();
for (TestError validatorTestError : Main.map.validatorDialog.tree.getErrors()) {
if (!wrongSegmentsToRemove.contains(validatorTestError)) {
modifiedValidatorTestErrors.add(validatorTestError);
}
}
Main.map.validatorDialog.tree.setErrors(modifiedValidatorTestErrors);
// update wrong segments:
for (TestError testError : wrongSegmentsToRemove) {
wrongSegments.remove(testError);
}
}
/**
* Resets the static list variables (used for unit tests and in Test.startTest() method)
*/
protected static void reset() {
correctSegments.clear();
wrongSegments.clear();
wrongSegmentBuilders.clear();
}
}