wiki:Help/Plugin/Scripting/Python/RCN_Route_Validator

Version 5 (modified by Polyglot, 12 years ago) ( diff )

This version now also checks rwn and rhn relations for numbered node networks.

#!/bin/jython
'''
CheckRouteOrNetworkOrCollectionOfRoutes.jy
- Validation of a rXn route relation

This code is released under the GNU General
Public License v2 or later.

The GPL v3 is accessible here:
http://www.gnu.org/licenses/gpl.html

The GPL v2 is accessible here:
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html

It comes with no warranty whatsoever.

This code illustrates how to use Jython (Python in the scripting plugin of JOSM) to:
* loop over all members of a route relation
* find out whether the member is a node, a way or a relation
* add/change properties of a relation
* remove properties of a relation
* add members to a relation
* remove members from a relation
* sort all members backwards

* How to set an element selected

'''
from javax.swing import JOptionPane
from org.openstreetmap.josm import Main
import org.openstreetmap.josm.command as Command
import org.openstreetmap.josm.data.osm.Node as Node
import org.openstreetmap.josm.data.osm.Way as Way
import org.openstreetmap.josm.data.osm.Relation as Relation
import org.openstreetmap.josm.data.osm.TagCollection as TagCollection
import org.openstreetmap.josm.data.osm.DataSet as DataSet
import org.openstreetmap.josm.data.osm.RelationMember as RelationMember
import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask as DownloadRelationMemberTask
import org.openstreetmap.josm.actions.DownloadReferrersAction as DownloadReferrersAction
import org.openstreetmap.josm.actions.AutoScaleAction as AutoScaleAction
import re, time
import codecs


# the file name where the report for the wiki goes, needs to be configurable
wikiReportFN = 'C:/wikiReport.txt'
# comment to disable side effect
sideEffects = {
    'addRouteToNetwork': None,
    #'removeNameRefKey_xx-yyAndcreated_by': None, 
    #'modifyNoteTo_xx-yy': None,
    #'flipOrderOfMembers': None, # such that ways go from lower rXn_ref to higher rXn_ref
    #'sortRouteRelations': None,
    #'sortNetworkRelations': None, # not implemented yet
    #'removeNodesFromRoutes': None,
    #'addNodes2Routes': None,
    #'selectObjects': None,
    #'zoomToSelection': None,
    #'downloadReferrersForNodes': None, # This will download all ways and relations for nodes with an rXn_ref
    #'downloadReferrersForWays': None, # This will download all relations for ways that have an endnode with an rXn_ref
    #'downloadIncompleteMembers': None,
    'createWikiReport': None,
    #'reportWhenWaysAreUsedInAnotherRelationToo': None, # not implemented yet
    #'addFixmeTODOtags': None, # This will add todo tags to the data containing problems,
                              # the intention is that these tags are removed before uploading,
                              # once the reported problems are resolved
    #'createGarminGPX': None, # not implemented yet
    #'checkOneWays = False, # not implemented yet
    }

logVerbosity = 20
'''
10: only report problems that require attention
20: report on collection
30: report on network nodes
40: report on which routes are being checked
50: report everything
'''

def getMapView():
    if Main.main and Main.main.map:
        return Main.main.map.mapView
    else:
        return None

def sortRouteRelation(route, nodesDict, beginAndEndnodes):
    # TODO This can still be improved a lot. Now it aborts at the first sign of probable failure.
    # TODO It sorts the simple cases though (i.e. the ones with no forward/backward roles which actually are continuous)
    nextNode = None
    routeToReturn = Relation(route)
    # Which node has the lower rXn_ref?
    lowestNodeInt = 10000; lowPos = 0
    for nodeTuple in beginAndEndnodes:
        if nodeTuple[0] < lowestNodeInt:
            lowestNodeInt, nextNode = nodeTuple
    if not(nextNode):
        return route

    '''
    node1: way1
    node2: way1,way2
    node3: way2,way3,way4
    node4: way3,way5
    node5: way5,way6
    node6:
    node8: way8, way9
    node9: way7,way9,way10
    node10: way10, way11
    w1,w2,w3,w5,w6,w7,w4,w8,w9,w10'''
    # At this point nextNode contains the unique ID of the node with the lower rXn_ref
    members=nodesDict[nextNode]
    processedMembers = {}; inBranch = False
    #print len(nodesDict)
    #print dir(route)
    while lowPos<route.getMembersCount():
        #print 'in loop', routeToReturn
        if len(members) == 1:
            member = members[0]
        elif len(members) == 3:
            # road is splitting or recombining
            if inBranch:
                inBranch = False
            else:
                inBranch = True
                # road is splitting, which is the member we need next?
                # probably the one not going against a oneway restriction
                for member in members:
                    if member in processedMembers: continue
                    way = member.getWay()
                    oneway = way.get('oneway')
                    if oneway:
                        oneway = oneway.lower()
                    else:
                        oneway = ''
                    if oneway and not(oneway == 'no'):
                        ob=way.get('oneway:bicycle')
                        if ob:
                            ob = ob.lower()
                        else:
                            ob = ''
                        bo=way.get('bicycle:oneway')
                        if bo:
                            bo = bo.lower()
                        else:
                            bo = ''
                        co=way.get('cycleway_opposite')
                        if co:
                            co = co.lower()
                        else:
                            co = ''
                        onewayForBicycle = oneway in ['yes', '1', 'true', '-1'] and not(ob in ['yes', '1', 'true'] or 
                                                                                        bo in ['yes', '1', 'true'] or 
                                                                                        co in ['yes', '1', 'true'])
                        if oneway == '-1': pass # TODO this also needs to be taken in consideration
                    if way.getNode(0).getUniqueId() == nextNode:
                        break
        else:
            member = members[1]
            if member in processedMembers: member = members[0]
        processedMembers[member] = True
        way = member.getWay()
        routeToReturn.removeMembersFor(way)
        print 'lowPos',lowPos
        routeToReturn.addMember(lowPos,member)
        #routeToReturn.setModified(True)
        currentNode = nextNode
        nextNode = way.getNode(way.nodesCount-1).getUniqueId()
        if currentNode == nextNode:
            nextNode = way.getNode(0).getUniqueId()
        if nextNode in nodesDict:
            members=nodesDict[nextNode]
        else:
            break
        del nodesDict[nextNode]
        #print len(nodesDict)
        if len(nodesDict)<3: break
        lowPos += 1
    print 'before return', routeToReturn
    return routeToReturn

def checkForContinuity(membersList):
    listIsContinuous = True; prev_endnodes = []
    for member in membersList:
        if member.isWay():
            way = member.getWay()
            #print dir(way)
            if logVerbosity > 49:
                print way.getKeys()
                print way.get('name')
                print way.nodesCount-1, way
            if way.get('junction') == 'roundabout':
                endnodes = way.getNodes()
            else:
                if way.nodesCount:
                    endnodes = [way.getNode(0), way.getNode(way.nodesCount-1)]
                else:
                    endnodes = []
            foundTheNode = False
            for endnode in endnodes:
                if logVerbosity > 49: print endnode, prev_endnodes
                if endnode in prev_endnodes:
                    foundTheNode = True
            if prev_endnodes and not(foundTheNode):
                listIsContinuous = False
                if logVerbosity > 49:
                    print way
                    print endnodes
                    print prev_endnodes
                break
            prev_endnodes = endnodes
    return listIsContinuous

def checkRxNroute(route, networklevel, aDownloadWasNeeded):
    if aDownloadWasNeeded:
        return None, False, ''
    if 'removeNameRefKey_xx-yyAndcreated_by' in sideEffects:
        commandsList = []
        reNumberDashNumber = re.compile(r'\d+\s-\s\d+')
        newRelation = Relation(route)
        relationChanged = False
        created_by = route.get('created_by')
        if created_by:
            print 'removing created_by'
            newRelation.remove('created_by')
            relationChanged = True
        name = route.get('name')
        if name:
            #if reNumberDashNumber.match(name):
            print 'removing name'
            newRelation.remove('name')
            relationChanged = True
        else:
            name = ''
        ref = route.get('ref')
        if ref:
            if reNumberDashNumber.match(ref):
                print 'removing ref when it is of the form ##-##'
                newRelation.remove('ref')
                relationChanged = True
        else:
            ref = ''
        if relationChanged:
            commandsList.append(Command.ChangeCommand(route, newRelation))
            
            Main.main.undoRedo.add(Command.SequenceCommand("Removing name and/or ref and/or created_by" + name + '/' + ref, commandsList))
            commandsList = []

    fixme = route.get('fixme')
    rXn_refs = []; actual_rXn_refs = []; route_relation_names = []
    memberslist = []; waymembersinlist = 0; roleInNetwork = ''
    sameNodeNumberRepeated = False
    allWaysgoingFromLowerToHigher = []; allWaysgoingFromHigherToLower = []; branch = None
    allWaysgoingFromLowerToHigherBeyondEndOfRoute = []; allWaysgoingFromHigherToLowerBeyondEndOfRoute = []
    nodesWithrXn_ref = {}; likelyCandidateNodesForActualStartOfRoute = {}
    tentacle = 0; tentacles = {}; routeHasStarted = False; high_rXn_refEncountered = 0
    rXn_refAsInt = None
    # tentacles is a nested list of all the segments of ways wich are present before the actual start of the route
    # sequence, UID of node with rXn_ref, list with ways, reference to next tentacle sequence
    # 1st level: rXn_refs
    # 2nd level: rXn_ref, tentacle sequence, ways, next tentacle
    # {40: [[1, [5n], 2],
    #       [2, [3n], 3],
    #       [3, [3n, 5n, 7n, 8n], 0],
    #       [4, [4n], 0]
    #      ],
    #  58: [[1, [2n], 0],
    #       [2, [7n], 1]
    #      ]
    # }
    newRelation = Relation(route); commandslist = []; i=0; roundabout = []

    if 'sortRouteRelations' in sideEffects: nodesForSorting = {}; beginAndEndNodes = []
    routeMembers = route.getMembers()
    modified = False

    for member in routeMembers:
        memberslist.append(member)
        if member.isWay():
            waymembersinlist += 1
            if high_rXn_refEncountered: high_rXn_refEncountered += 1
            role = member.getRole()
            # make a Python list out of the members, in case we want to simply flip all of them
            # this would happen if we encounter the high rXn_ref before the low rXn_ref
            way = member.getWay()
            if logVerbosity > 49: print way.get('name')
            wayNodes = way.getNodes()

            notfoundyet = True
            if logVerbosity > 49: print wayNodes
            if role in ['forward']:
                lastNode=wayNodes[-1]
            else:
                lastNode=wayNodes[0]
            lastNodeUid = lastNode.getUniqueId()
            if lastNodeUid in likelyCandidateNodesForActualStartOfRoute:
                likelyCandidateNodesForActualStartOfRoute[lastNodeUid] += 1
            else:
                likelyCandidateNodesForActualStartOfRoute[lastNodeUid] = 1
            
            for endnode in wayNodes:
                endnodeId = endnode.getUniqueId()
                if 'sortRouteRelations' in sideEffects:
                    if not(endnodeId in nodesForSorting):
                        nodesForSorting[endnodeId] = []
                    nodesForSorting[endnodeId].append(member)

                rXn_ref = endnode.get(networklevel + '_ref')
                if rXn_ref:
                    rXn_ref_digits = ''
                    for character in rXn_ref:
                        try:
                            int(character)
                            rXn_ref_digits += character
                        except: pass
                    if rXn_ref_digits and rXn_ref_digits==rXn_ref:
                        rXn_refAsInt = int(rXn_ref_digits)
                    else:
                        rXn_refAsInt = -1

                        print rXn_ref
                    if 'sortRouteRelations' in sideEffects: beginAndEndNodes.append([rXn_refAsInt, endnodeId])
                    # keep track of distinct rXn_ref numbers in a list
                    if not(rXn_refAsInt in rXn_refs):
                        if len(rXn_refs): high_rXn_refEncountered += 1; tentacle = 0
                        rXn_refs.append(rXn_refAsInt)
                        if rXn_refAsInt == -1:
                            actual_rXn_refs.append(rXn_ref)
                        else:
                            actual_rXn_refs.append(str(rXn_refAsInt).zfill(2))
                    else:
                        sameNodeNumberRepeated = True
                    # keep track of node IDs with an rXn_ref on them
                    if not(rXn_refAsInt in nodesWithrXn_ref):
                        nodesWithrXn_ref[rXn_refAsInt] = []
                    nodesWithrXn_ref[rXn_refAsInt].append(endnode)
                    referrersForNode = endnode.getReferrers(); networkNotFoundForNode = True
                    for referrer in referrersForNode:
                        if referrer.getType() is dummy_relation.getType():
                            # This node belongs to a relation
                            if referrer.get('type') in ['network'] and referrer.get('network') in [networklevel]:
                                # and we have a winner
                                networkNotFoundForNode = False
                                relname=referrer.get('name')
                                if relname:
                                    route_relation_names.append(relname)
                                break
                    if networkNotFoundForNode:
                        route_relation_names.append('Node not assigned to network yet')
                        if 'selectObjects' in sideEffects: Main.main.getCurrentDataSet().setSelected(endnode)
                        if 'zoomToSelection' in sideEffects: AutoScaleAction.zoomToSelection()
                        if 'downloadReferrersForNodes' in sideEffects:
                            aDownloadWasNeeded = True
                            print "Downloading referrers for ", endnode.get(networklevel + '_ref'), ' ', wayNodes
                            DownloadReferrersAction.downloadReferrers(mv.editLayer, wayNodes)
                            return None, False, ''
                        else:
                            print "Would download referrers for ", endnode.get(networklevel + '_ref'), ' ', wayNodes
                            
            # *** Now let's process the ways *** 
            # build at least 2 structures to help check for continuity on ways
            # possibly there are 'tentacles' before the start or after the end when there are split network nodes
            if logVerbosity > 49:
                print 'L2H', allWaysgoingFromLowerToHigher
                print 'H2L', allWaysgoingFromHigherToLower
                print 'L2H', allWaysgoingFromLowerToHigherBeyondEndOfRoute
                print 'H2L', allWaysgoingFromHigherToLowerBeyondEndOfRoute
            
            if i==0:
                lastNodesBeforeSplit = [wayNodes]
            else:
                if not(routeHasStarted):
                    if role:
                        if role in ['forward']:
                            firstNode=wayNodes[0]
                        else:
                            firstNode=wayNodes[-1]
                        firstNodeUid = firstNode.getUniqueId()
                        print firstNodeUid, firstNodeUid in likelyCandidateNodesForActualStartOfRoute
                        if firstNodeUid in likelyCandidateNodesForActualStartOfRoute: print likelyCandidateNodesForActualStartOfRoute[firstNodeUid]
                        print likelyCandidateNodesForActualStartOfRoute
                        if firstNodeUid in likelyCandidateNodesForActualStartOfRoute and likelyCandidateNodesForActualStartOfRoute[firstNodeUid]>1:
                            routeHasStarted = True
                        if firstNode.get(networklevel + '_ref'):
                            if logVerbosity > 49: print 'rXn_ref', firstNode.get(networklevel + '_ref'), 'found in ', firstNode.getUniqueId()
                            # This is not the first member anymore and the first node has an rXn_ref and the member has a role,
                            # so we have to assign what we thought was the start of the route to one of our tentacles
                            tentacle +=1; reference = 0; interestingNodeId = None; tentacledescription = []
                            if allWaysgoingFromLowerToHigher:
                                prevRole = allWaysgoingFromLowerToHigher[-1].getRole()
                                if logVerbosity > 49: print 'prevRoleL2H', prevRole
                                if prevRole in ['forward']:
                                    interestingNodeId = allWaysgoingFromLowerToHigher[-1].getWay().getNodes()[-1].getUniqueId()
                                elif prevRole in ['backward']:
                                    interestingNodeId = allWaysgoingFromLowerToHigher[-1].getWay().getNodes()[0].getUniqueId()
                            elif allWaysgoingFromHigherToLower:
                                prevRole = allWaysgoingFromHigherToLower[-1].getRole()
                                if logVerbosity > 49: print 'prevRoleH2L', prevRole
                                if prevRole in ['forward']:
                                    interestingNodeId = allWaysgoingFromHigherToLower[-1].getWay().getNodes()[0].getUniqueId()
                                elif prevRole in ['backward']:
                                    interestingNodeId = allWaysgoingFromHigherToLower[-1].getWay().getNodes()[-1].getUniqueId()
                            if interestingNodeId and firstNode.getUniqueId() == interestingNodeId:
                                reference = tentacle+1
                            if allWaysgoingFromLowerToHigher:
                                tentacledescription = [tentacle, allWaysgoingFromLowerToHigher, reference]
                            elif allWaysgoingFromHigherToLower:
                                tentacledescription = [tentacle, allWaysgoingFromHigherToLower, reference]
                            allWaysgoingFromLowerToHigher = []; allWaysgoingFromHigherToLower = []; branch = None
                            if tentacledescription:
                                if rXn_refAsInt and not(rXn_refAsInt in tentacles):
                                    tentacles[rXn_refAsInt] = []
                                tentacles[rXn_refAsInt].append(tentacledescription)
                    else:
                        # no role on way means this is the actual start of our route
                        # so, all that went into allWaysgoingFromLowerToHigher etc has to go to the last tentacle
                        routeHasStarted = True
                        if allWaysgoingFromLowerToHigher or allWaysgoingFromHigherToLower:
                            tentacle +=1
                            if allWaysgoingFromHigherToLower:
                                allWaysgoingFromLowerToHigher # .append(allWaysgoingFromHigherToLower)
                            tentacledescription = [tentacle, allWaysgoingFromLowerToHigher, 0]
                            # allWaysgoingFromHigherToLower was assigned the start of the route in mistake in this case
                            # it should have gone to both 'branches', so we create a shallow copy
                            allWaysgoingFromLowerToHigher = allWaysgoingFromHigherToLower[:]; branch = None
                            if not(rXn_refAsInt in tentacles):
                                tentacles[rXn_refAsInt] = []
                            tentacles[rXn_refAsInt].append(tentacledescription)
                if high_rXn_refEncountered > 1:
                    # We're past the first high rXn_ref, time to disover more tentacles
                    if role:
                        if role in ['forward']:
                            lastNode=wayNodes[-1]
                        else:
                            lastNode=wayNodes[0]
                        if lastNode.get(networklevel + '_ref'):
                            print 'rXn_ref', lastNode.get(networklevel + '_ref'), 'found in ', lastNode.getUniqueId()
                            tentacle +=1; reference = 0; interestingNodeId = None; tentacledescription = []
                            if interestingNodeId and lastNode.getUniqueId() == interestingNodeId:
                                reference = tentacle+1
                            if allWaysgoingFromLowerToHigher:
                                tentacledescription = [tentacle, allWaysgoingFromLowerToHigherBeyondEndOfRoute, reference]
                            elif allWaysgoingFromHigherToLower:
                                tentacledescription = [tentacle, allWaysgoingFromHigherToLowerBeyondEndOfRoute, reference]
                            allWaysgoingFromLowerToHigherBeyondEndOfRoute = []; allWaysgoingFromHigherToLowerBeyondEndOfRoute = []; branch = None
                            if tentacledescription:
                                if rXn_refAsInt and not(rXn_refAsInt in tentacles):
                                    tentacles[rXn_refAsInt] = []
                                tentacles[rXn_refAsInt].append(tentacledescription)
            if logVerbosity > 49: print tentacle, repr(tentacles)
                            
            if role and role in ['forward', 'backward']:
                # if there is a role, it might be part of the route proper when it starts or ends in a fork
                # this is what we suppose, if we encounter another node with a low rXn_ref, we add what we already had
                # to the tentacles structure and clear fromLowToHigh
                if not(branch):                    
                    if wayNodes:
                        if role in ['forward']:
                            lastNodesBeforeSplit = [wayNodes[0]]
                        else:
                            lastNodesBeforeSplit = [wayNodes[-1]]
                    else:
                        return '', True, '|align="right" | way has no nodes||align="right" | {{BrowseRoute|' + str(route.getId()) + '}}||align="right" needs to be downloaded first\n'
                        if logVerbosity > 49: print waynodes
                if logVerbosity > 29:
                    print 'lastNodesBeforeSplit', lastNodesBeforeSplit
                    print way.getNodesCount(), 'startnode', wayNodes[0], 'endnode', wayNodes[-1]
                    print 'roundabout', roundabout
                    print wayNodes[-1].get(networklevel + '_ref'),not(member.getUniqueId() == routeMembers[-1].getUniqueId())
                if role in ['forward'] and (wayNodes[-1] in lastNodesBeforeSplit or wayNodes[-1] in roundabout):
                    branch = 'HigherToLower'
                elif role in ['backward'] and wayNodes[0] in lastNodesBeforeSplit:
                    branch = 'HigherToLower'
                    
                elif branch == 'LowerToHigher' and not(allWaysgoingFromHigherToLower) and wayNodes[-1].get(networklevel + '_ref') and not(member.getUniqueId() == routeMembers[-1].getUniqueId()):
                    # This is for when the route starts forked from a different rXn node (split node situation), we don't want it to kick in for the last member of the route
                    branch = 'HigherToLower'
                elif not(branch == 'HigherToLower'):
                    branch = 'LowerToHigher'
                print branch
                if high_rXn_refEncountered < 2:
                    if branch == 'LowerToHigher':
                        allWaysgoingFromLowerToHigher.append(member)
                    elif branch == 'HigherToLower':
                        allWaysgoingFromHigherToLower.append(member)
                else:
                    if branch == 'LowerToHigher':
                        allWaysgoingFromLowerToHigherBeyondEndOfRoute.append(member)
                    elif branch == 'HigherToLower':
                        allWaysgoingFromHigherToLowerBeyondEndOfRoute.append(member)
            else:
                branch = None; roundabout = []
                routeHasStarted = True
                if high_rXn_refEncountered < 2:
                    allWaysgoingFromLowerToHigher.append(member)
                    allWaysgoingFromHigherToLower.append(member)
                else:
                    allWaysgoingFromLowerToHigherBeyondEndOfRoute.append(member)
                    allWaysgoingFromHigherToLowerBeyondEndOfRoute.append(member)
            if way.get('junction') == 'roundabout':
                roundabout = way.getNodes()
        i+=1
    modified = False
    if 'removeNodesFromRoutes' in sideEffects:
        newRelation = Relation(route)
        for member in routeMembers:
            if member.isNode():
                node = member.getNode()
                newRelation.removeMembersFor(node)
                modified = True
    if modified:
        commandsList.append(Command.ChangeCommand(route, newRelation))
        Main.main.undoRedo.add(Command.SequenceCommand("Removing nodes with rXn_ref", commandsList))
        commandsList = []
    modified = False
    if 'addNodes2Routes' in sideEffects:
        newRelation = Relation(route)
        # (re)add nodes to route
        lowrXn_ref2BDone = True; lowPos = 0
        for nodeNumber in sorted(nodesWithrXn_ref.keys()):
            for node in nodesWithrXn_ref[nodeNumber]:
               newRelation.removeMembersFor(node)
               newMember = RelationMember("",node)
               if lowrXn_ref2BDone:
                   newRelation.addMember(lowPos, newMember)
                   lowPos += 1
               else:
                   newRelation.addMember(newRelation.getMembersCount(), newMember)
            lowrXn_ref2BDone = False; modified = True

    if modified:
        commandsList.append(Command.ChangeCommand(route, newRelation))
        Main.main.undoRedo.add(Command.SequenceCommand("Adding nodes with rXn_ref", commandsList))
        commandsList = []

    continuous_forward = True; continuous_backward = True
    if len(allWaysgoingFromLowerToHigher) > 1:
        continuous_forward = checkForContinuity(allWaysgoingFromLowerToHigher)
        if 'sortRouteRelations' in sideEffects and not(continuous_forward) and not(fixme):
            sortedRelation = sortRouteRelation(route, nodesForSorting, beginAndEndNodes)

            if logVerbosity>49:
                print route
                print sortedRelation

            allWaysgoingFromLowerToHigher = []; allWaysgoingFromHigherToLower = []
            routeMembers = sortedRelation.getMembers(); i=0
            for member in routeMembers:
                if member.isWay():
                    role = member.getRole()
                    if i==0:
                        lastNodesBeforeSplit = [wayNodes] # why nodes in plural? situations occur where the branch starts on a roundabout
                    if role and role in ['forward', 'backward']:
                        if not(branch):
                            if role in ['forward']:
                                lastNodesBeforeSplit = [wayNodes[0]]
                            else:
                                lastNodesBeforeSplit = [wayNodes[-1]]
                        if logVerbosity > 29:
                            print 'wayNodes', wayNodes
                            print 'lastNodesBeforeSplit', lastNodesBeforeSplit
                            print way.getNodesCount(), 'startnode', wayNodes[0], 'endnode', wayNodes[-1]
                            print 'roundabout', roundabout

                        if role in ['forward'] and (wayNodes[-1] in lastNodesBeforeSplit or wayNodes[-1] in roundabout):
                            branch = 'HigherToLower'
                        elif role in ['backward'] and wayNodes[0] in lastNodesBeforeSplit:
                            branch = 'HigherToLower'
                        elif wayNodes[-1].get(networklevel + '_ref') and not(member.getUniqueId() == routeMembers[-1].getUniqueId()): # not(allWaysgoingFromHigherToLower) and ## this last part is probably not necessary anymore
                            # This is for when the route starts forked from a different rXn node (split node situation), we don't want it to kick in for the last member of the route
                            branch = 'HigherToLower'
                        elif not(branch == 'HigherToLower'):
                            branch = 'LowerToHigher'
                        print branch
                        if branch == 'LowerToHigher':
                            allWaysgoingFromLowerToHigher.append(member)
                        elif branch == 'HigherToLower':
                            allWaysgoingFromHigherToLower.append(member)
                    else:
                        branch = None; roundabout = []
                        allWaysgoingFromLowerToHigher.append(member)
                        allWaysgoingFromHigherToLower.append(member)
                    if way.get('junction') == 'roundabout':
                        roundabout = way.getNodes()
                i+=1
            continuous_forward = checkForContinuity(allWaysgoingFromLowerToHigher)
            if continuous_forward:
                # we only want to store the sorted relation, when sorting actually succeeded in getting it continuous
                commandsList.append(Command.ChangeCommand(route, sortedRelation))
                Main.main.undoRedo.add(Command.SequenceCommand("Sorted route relation", commandsList))            
    else:
        # a route relation with only one way member
        continuous_forward = True
    if len(allWaysgoingFromHigherToLower) > 1:
        continuous_backward = checkForContinuity(reversed(allWaysgoingFromHigherToLower))
    else:
        # a route relation with only one way member
        continuous_backward = True
    print continuous_forward, continuous_backward

    for rXn_nodeTentacles in tentacles:
        for seq, tentacleMember, next in tentacles[rXn_nodeTentacles]:
            print rXn_nodeTentacles, ' ', seq, ' ', next, ' ', checkForContinuity(tentacleMember)
            
    # Drawing conclusions about rXn_refs
    fixmetodo = ''
    if logVerbosity > 39: print rXn_refs
    if sameNodeNumberRepeated:
        rXn_refs.append(rXn_refs[0])
    newNote = note = repr(rXn_refs)
    sortedRelation = route
    if len(rXn_refs) > 1:
        relationChanged = False

        if rXn_refs[0] > rXn_refs[1]:
            rXn_refs.sort()
            if waymembersinlist>1 and 'flipOrderOfMembers' in sideEffects:
                for member in reversed(memberslist):
                    sortedRelation.addMember( sortedRelation.getMembersCount(), member)
                    sortedRelation.removeMember (0)
                commandsList.append(Command.ChangeCommand(route, sortedRelation))
                Main.main.undoRedo.add(Command.SequenceCommand("Flipping order of members", commandsList))
                commandsList = []
        note = route.get('note')
        if not(-1 in rXn_refs):
            rXn_refs.sort()
            newNote = str(rXn_refs[0]).zfill(2) + '-' + str(rXn_refs[-1]).zfill(2)
        else:
            newNote = ''
            for ref in actual_rXn_refs:
               newNote+=ref + '-'
            newNote = newNote[:-1]
        if logVerbosity > 49: print note, newNote
        if 'modifyNoteTo_xx-yy' in sideEffects:
            if (not(note) or note != newNote) and rXn_refs[0] != rXn_refs[-1]:
                newRelation = Relation(route)
                #print newRelation.getUniqueId()
                #print route.getUniqueId()
                if not(note): note = 'nothing'
                newRelation.put('note', newNote)
                relationChanged = True
                commandsList.append(Command.ChangeCommand(route, newRelation))
            
                Main.main.undoRedo.add(Command.SequenceCommand("Changing note from " + note + ' to ' + newNote, commandsList))
                commandsList = []

        if len(route_relation_names) > 1 and route_relation_names[0] != route_relation_names[1]:
            # print 'This is probably a CONNECTION to another network'
            if logVerbosity > 9: print newNote, route_relation_names
            roleInNetwork = 'connection'
        wikiEN = ''
    else:
        if logVerbosity > 9: print 'less than 2 end nodes with '+networklevel+'_ref found for route', newNote
        wikiEN = 'style="color:red" | ' + repr(rXn_refs) + ' - ?'
        if 'addFixmeTODOtags' in sideEffects and not(route.get(networklevel+':external_connection')) and not(route.get('roundtrip')):
            newRelation = Relation(route)
            #print newRelation.getUniqueId()
            #print route.getUniqueId()
            fixmetodo = 'less than 2 end nodes with '+networklevel+'_ref found for route' + newNote + ' ' + repr(rXn_refs) + ' ' + note
            newRelation.put('fixmetodo', fixmetodo)
            relationChanged = True
            commandsList.append(Command.ChangeCommand(route, newRelation))
        
            Main.main.undoRedo.add(Command.SequenceCommand("report that only one '+networklevel+'_ref was found " + note + ' to ' + newNote, commandsList))
            commandsList = []
    if fixme and not(continuous_forward or continuous_backward):
        if logVerbosity > 9: print 'FIXME flag is INCOMPLETE for route', newNote
    if continuous_forward:
        wikiCF = 'align="right" | continuous'
        if logVerbosity > 29: print 'route is continous in the forward direction'
    else:
        wikiCF = 'align="right" style="color:red" | NOT continuous'
        if logVerbosity > 9: print 'route ',newNote,' is NOT CONTINUOUS going from xx to yy; xx<yy'
        if logVerbosity > 49: print 'L2H:', allWaysgoingFromLowerToHigher
        if not(fixme) and 'addFixmeTODOtags' in sideEffects:
            newRelation = Relation(route)
            #print newRelation.getUniqueId()
            #print route.getUniqueId()
            fixmetodo += ' ' + newNote + 'is not continuous going from xx to yy; xx<yy'
            newRelation.put('fixmetodo', fixmetodo)
            relationChanged = True
            commandsList.append(Command.ChangeCommand(route, newRelation))
            Main.main.undoRedo.add(Command.SequenceCommand("report that  " + note + ' is not continuous going from xx to yy; xx<yy' , commandsList))
            commandsList = []
    if continuous_backward:
        wikiCB = 'align="right" | continuous'
        if logVerbosity > 29: print 'route is continous in the backward direction'
    else:
        wikiCB = 'align="right" style="color:red" | NOT continuous'
        if logVerbosity > 9: print 'route ',newNote,' is NOT CONTINUOUS coming back from yy to xx; xx<yy'
        if logVerbosity > 49: print 'H2L: ', allWaysgoingFromHigherToLower
        if not(fixme) and 'addFixmeTODOtags' in sideEffects:
            newRelation = Relation(route)
            #print newRelation.getUniqueId()
            #print route.getUniqueId()
            fixmetodo += ' ' + newNote + 'is not continuous coming back from yy to xx; xx<yy'
            newRelation.put('fixmetodo', fixmetodo)
            relationChanged = True
            commandsList.append(Command.ChangeCommand(route, newRelation))
        
            Main.main.undoRedo.add(Command.SequenceCommand("report that  " + note + ' is not continuous coming back from yy to xx; xx<yy' , commandsList))
            commandsList = []
    print
    if fixme:
        wikiFM = 'style="color:red" | ' + fixme
    else:
        wikiFM = ' | '
    if fixme or not(continuous_forward) or not(continuous_backward) or (len(rXn_refs)<2 and not(route.get(networklevel+':external_connection'))):
        problem = 'yes'
    else:
        problem = ''

    print 'PROBLEM:', problem
    return str(roleInNetwork), problem, '|align="right" | ' + str(note) + '||align="right" | {{BrowseRoute|' + str(route.getId()) + '}}||align="right" ' + str(wikiFM) + ' ||' + str(wikiCF) + ' ||' + str(wikiCB)  + ' ||' + str(wikiEN) + '\n'

def sortNetwork(network):
    # While making a list of all the nodes in the relation with an rXn_ref, find the node with the lowest rXn_ref in the network, not necessarily 01 and bring it to the first position
    # Look at the ways connecting to this node and their parent route relations with network=rXn
    # For all these relations, make a list of all the rXn_ref numbers, they lead to and remember the UIDs of these nodes and keep a ref to the relations
    # Split this list in two, one for internal routes, another for route relations connecting to a foreign network (so the ones with a connection role)
    # Sort both lists and bring the relations in positions following the first node
    # Do the same for all the relations in the second list
    # Then add the lowest rXn_ref number and remove it from the general todo list of rXn_ref nodes that were encountered in internal route relations
    name = network.get('name')
    if logVerbosity>19:
        print '********************************************'
        print name
        print '********************************************'
    lowestrXn_refInNetwork=9999; listOfNodeMembersSeen = []
    dictionaryWithAllNodesBelongingToThisNetwork = {}; dictionaryWithAllNodesBelongingToNeighbouringNetworks = {}
    ListOfInternalRoutesSeen = []; ListOfExternalRoutesSeen = []; dictionaryLinkingRoutesToNotes = {}
    members = network.getMembers()
    # Find a member node with the lowestrXn_refInNetwork and bring it to the front if such node is not there yet
    firstMember = members[0]
    if firstMember.isRelation() or firstMember.isNode() and int(firstMember.getNode().get(networklevel + '_ref')) != lowestrXn_refInNetwork:
        memberNode = None
        for networkMember in members:
            if networkMember.isNode():
                memberNode = networkMember.getNode()
                if int(memberNode.get(networklevel + '_ref')) == 1: break
        if memberNode:
            network.removeMembersFor(memberNode)
            network.addMember(0, networkMember)
    
    for networkMember in network.getMembers():
        if networkMember.isNode():
            node = networkMember.getNode()
            rXn_ref = node.get(networklevel + '_ref')
            if int(rXn_ref) == 1:
                referrersForNode = node.getReferrers()
                for referrer in referrersForNode:
                    if referrer.getType() is dummy_way.getType():
                        referrersForWay = referrer.getReferrers()
                        for wayReferrer in referrersForWay:
                            if wayReferrer.getType() is dummy_relation.getType():
                                aRelation = wayReferrer
                                if aRelation.get('type') == 'route' and aRelation.get('network') ==networklevel and not(aRelation.get('ref')):
                                    rXnNetworkCountForNode+=1
                                    if aRelation.hasIncompleteMembers():
                                        name = aRelation.get('name')
                                        if not(name): name = ''
                                        note = aRelation.get('note')
                                        if not(note): note = ''
                                        networkname = aRelation.get('network')
                                        if not(networkname): networkname = ''
                                        if 'downloadIncompleteMembers' in sideEffects:
                                            aDownloadWasNeeded = True
                                            print 'Downloading incomplete members for', note
                                            DownloadRelationMemberTask.run(DownloadRelationMemberTask(aRelation, aRelation.getIncompleteMembers(), mv.editLayer ))
                                            time.sleep(1)
                                            continue
                                        else:
                                            JOptionPane.showMessageDialog(Main.parent, 'Please download all incomplete members of the route relations first: ' + networkname + ' ' + note + ' ' + name)
                                            break
                                networkMembersList = []
                                for subMember in network.getMembers():
                                    if subMember.isRelation():
                                        routeRelation = subMember.getRelation()
                                        networkMembersList.append(routeRelation.getUniqueId())
                                routeId = aRelation.getUniqueId()
                                if rXnRelationWeWantToProcess and not(routeId in networkMembersList):
                                    roleFromRouteCheck, problemReported, wikiReport = checkRCNroute(aRelation, aDownloadWasNeeded)
                                    if problemReported and 'createWikiReport' in sideEffects:
                                        wikiReportOnRelations += wikiReport + '\n|-\n'
                                    if 'addRouteToNetwork' in sideEffects and not(aDownloadWasNeeded):
                                        newRelation = Relation(network)
                                        newmember = RelationMember(roleFromRouteCheck, aRelation)
                                        if logVerbosity > 9: print newRelation.getMembersCount(), ' ',i, ' ', newmember
                                        newRelation.addMember( i, newmember)
                                        commandsList = []
                                        commandsList.append(Command.ChangeCommand(network, newRelation))
                                        note = aRelation.get('note')
                                        if not(note):
                                            note = ''
                                        Main.main.undoRedo.add(Command.SequenceCommand("Adding " + note + " to network", commandsList))
                                        commandsList = []
                                        if logVerbosity > 9: print 'Added newly found RCN route relation to network: ', note
                                        i+=1
 

def checkNetwork(network, networklevel, aDownloadWasNeeded):
    name = network.get('name')
    if logVerbosity>19:
        print '********************************************'
        print name
        print '********************************************'
    if 'createWikiReport' in sideEffects:
        wikiReportOnNodes = ('\n==' + name + '==\n{| class="wikitable" align="left" style="margin:0 0 2em 2em;"\n|-\n|+' + name +
                             '\n|-\n!style="width:2.5em" | Node\n!Link\n! # Roads\n! # Relations\n|-\n')
        wikiReportOnRelations = ('{| class="wikitable" align="left" style="margin:0 0 2em 2em;"\n|-\n|+' + name +
                                 '\n|-\n!style="width:2.5em" | note\n!link\n!fixme\n! forward\n! backward\n! end nodes\n|-\n')
    i=1
    for networkMember in network.getMembers():
        if networkMember.isRelation():
            routeRelation = networkMember.getRelation()
            if routeRelation.get('network') ==networklevel:
                if routeRelation.hasIncompleteMembers():
                    name = routeRelation.get('name')
                    if not(name): name = ''
                    note = routeRelation.get('note')
                    if not(note): note = ''
                    kindOfNetwork = routeRelation.get('network')
                    if not(kindOfNetwork): kindOfNetwork = ''
                    if 'downloadIncompleteMembers' in sideEffects:
                        aDownloadWasNeeded = True
                        print 'Downloading incomplete members for', note
                        DownloadRelationMemberTask.run(DownloadRelationMemberTask(routeRelation, routeRelation.getIncompleteMembers(), mv.editLayer ))
                        time.sleep(1)
                        continue
                    else:
                        JOptionPane.showMessageDialog(Main.parent, 'Please download all incomplete members of the route relations first: ' + kindOfNetwork + ' ' + note + ' ' + name)
                        break
                roleFromRouteCheck, problemReported, wikiReport = checkRxNroute(routeRelation, networklevel, aDownloadWasNeeded)
                if problemReported and 'createWikiReport' in sideEffects:
                    wikiReportOnRelations += wikiReport + '\n|-\n'
                role = networkMember.getRole()
                if logVerbosity > 29 and role != roleFromRouteCheck:
                    print
                    print 'Role in network is ', role
                    print 'Maybe this should be: ', roleFromRouteCheck
        if networkMember.isNode():
            node = networkMember.getNode()
            rXn_ref = node.get(networklevel + '_ref')
            expected_rXn_route_relations = node.get('expected_' + networklevel + '_route_relations')
            if expected_rXn_route_relations:
                expected_rXn_route_relations = int(expected_rXn_route_relations)
            else:
                expected_rXn_route_relations = 3
            if rXn_ref:
                if logVerbosity > 39: print rXn_ref
                referrersForNode = node.getReferrers()
                networkNotFoundForNode = True
                for referrer in referrersForNode:
                    if referrer.getType() is dummy_relation.getType():
                        if referrer.get('type') in ['network'] and referrer.get('network') in ['rcn','rwn','rhn']:
                            networkNotFoundForNode = False
                            break
                if networkNotFoundForNode and 'downloadReferrersNodes' in sideEffects:
                    aDownloadWasNeeded = True
                    if 'selectObjects' in sideEffects: Main.main.getCurrentDataSet().setSelected(node)
                    if 'zoomToSelection' in sideEffects: AutoScaleAction.zoomToSelection()
                    #selectedNode = mv.editLayer.data.getSelected()
                    print 'Downloading referrers for ', node.get(networklevel + '_ref')
                    DownloadReferrersAction.downloadReferrers(mv.editLayer, node)

                rXnNetworkCountForNode = roads = 0
                for referrer in referrersForNode:
                    if referrer.getType() is dummy_way.getType():
                        if referrer.get('highway'): roads += 1
                        referrersForWay = referrer.getReferrers()
                        if len(referrersForWay) < 1 and 'downloadReferrersForWays' in sideEffects:
                            aDownloadWasNeeded = True
                            if 'selectObjects' in sideEffects: Main.main.getCurrentDataSet().setSelected(referrer)
                            if 'zoomToSelection' in sideEffects: AutoScaleAction.zoomToSelection()
                            print 'Downloading referrers for ', referrer.get('name') , ' ', rXn_ref
                            DownloadReferrersAction.downloadReferrers(mv.editLayer, referrer)

                        for wayReferrer in referrersForWay:
                            if wayReferrer.getType() is dummy_relation.getType():
                                aRelation = wayReferrer
                                rXnRelationWeWantToProcess = False # We need this again further on, when deciding whether or not to add this relation to the network
                                if aRelation.get('type') == 'route' and aRelation.get('network') ==networklevel and not(aRelation.get('ref')): # in ['RUR','NRR','NRW','2LR','3LR','KAI','EHR','DFR','WBR','Nk']):
                                    # The check on the ref is for Germany, where many rXn networks run crisscross through another.
                                    # We wouldn't want to get entangled in that mess.
                                    # The list is continuously getting longer, of course. But it's the best we can do here
                                    rXnRelationWeWantToProcess = True
                                    rXnNetworkCountForNode+=1
                                    if aRelation.hasIncompleteMembers():
                                        name = aRelation.get('name')
                                        if not(name): name = ''
                                        note = aRelation.get('note')
                                        if not(note): note = ''
                                        kindOfNetwork = aRelation.get('network')
                                        if not(kindOfNetwork): kindOfNetwork = ''
                                        if 'downloadIncompleteMembers' in sideEffects:
                                            aDownloadWasNeeded = True
                                            print 'Downloading incomplete members for', note
                                            DownloadRelationMemberTask.run(DownloadRelationMemberTask(aRelation, aRelation.getIncompleteMembers(), mv.editLayer ))
                                            time.sleep(1)
                                            continue
                                        else:
                                            JOptionPane.showMessageDialog(Main.parent, 'Please download all incomplete members of the route relations first: ' + kindOfNetwork + ' ' + note + ' ' + name)
                                            break
                                networkMembersList = []
                                # print network
                                for subMember in network.getMembers():
                                    if subMember.isRelation():
                                        routeRelation = subMember.getRelation()
                                        networkMembersList.append(routeRelation.getUniqueId())
                                routeId = aRelation.getUniqueId()
                                if rXnRelationWeWantToProcess and not(routeId in networkMembersList):
                                    roleFromRouteCheck, problemReported, wikiReport = checkRxNroute(aRelation, networklevel, aDownloadWasNeeded)
                                    if problemReported and 'createWikiReport' in sideEffects:
                                        wikiReportOnRelations += wikiReport + '\n|-\n'
                                    if 'addRouteToNetwork' in sideEffects and not(aDownloadWasNeeded):
                                        newRelation = Relation(network)
                                        newmember = RelationMember(roleFromRouteCheck, aRelation)
                                        if logVerbosity > 9: print newRelation.getMembersCount(), ' ',i, ' ', newmember
                                        newRelation.addMember( i, newmember)
                                        commandsList = []
                                        commandsList.append(Command.ChangeCommand(network, newRelation))
                                        note = aRelation.get('note')
                                        if not(note):
                                            note = ''
                                        Main.main.undoRedo.add(Command.SequenceCommand("Adding " + note + " to network", commandsList))
                                        commandsList = []
                                        if logVerbosity > 9: print 'Added newly found RCN route relation to network: ', note
                                        i+=1
                if rXnNetworkCountForNode < expected_rXn_route_relations:
                    if logVerbosity > 9: print 'Node ', rXn_ref, ' only has ', rXnNetworkCountForNode, ' rXn routes connected to it'
                    if 'createWikiReport' in sideEffects: wikiReportOnNodes += '|align="right" | ' + rXn_ref + '||align="right" | {{Node|' + str(node.getId()) + '}}||align="right" |' + str(roads) + ' ||align="right" style="color:red" | ' + str(rXnNetworkCountForNode) + '\n|-\n'

        i+=1
    if logVerbosity > 19: print
    if 'createWikiReport' in sideEffects:
        fh = codecs.open(wikiReportFN, 'a', encoding="UTF-8")
        wikiReportOnNodes += ('|-\n|}\n')
        wikiReportOnRelations += ('|-\n|}\n<br style="clear:left;" />\n')
        fh.write(wikiReportOnNodes)
        fh.write(wikiReportOnRelations)
        fh.close()


aDownloadWasNeeded = False
'''
Since Downloading referrers or missing members happens asynchronously in a separate worker thread
the script can run in three modes

1. No downloads allowed/offline run; output mentions that data was incomplete in its reports.
2. Download run; When incomplete items are encountered, they are scheduled to be downloaded. From then on, no more quality checks are performed on the data.
   All hierarchies are still checked, looking for more incomplete data for which more downloads need to be scheduled.
3. Normal run; All data is available and proper reporting can be performed.
'''

dummy_way = Way()
dummy_relation = Relation()

mv = getMapView()
if mv and mv.editLayer and mv.editLayer.data:
    selectedRelations = mv.editLayer.data.getSelectedRelations()

    if not(selectedRelations):
        JOptionPane.showMessageDialog(Main.parent, "Please select a route, network or collection relation")
    else:
        if 'createWikiReport' in sideEffects:
            fh = codecs.open(wikiReportFN, 'w', encoding="UTF-8")
            fh.close()
        for relation in selectedRelations:
            if logVerbosity> 49: print relation
            if relation.hasIncompleteMembers():
                if 'downloadIncompleteMembers' in sideEffects:
                    aDownloadWasNeeded = True
                    print 'Downloading referrers for ', str(relation.get('name')), ' ', str(relation.get('note'))
                    DownloadRelationMemberTask.run(DownloadRelationMemberTask(relation, relation.getIncompleteMembers(), mv.editLayer ))
                    continue
                else:
                    JOptionPane.showMessageDialog(Main.parent, 'Please download all incomplete member of the relations first')
                    exit()
            networklevel=relation.get('network')
            if networklevel in ('rcn','rwn','rhn'):
                relationType = relation.get('type')
                if relationType == 'route':
                    checkRxNroute(relation, networklevel, aDownloadWasNeeded)
                elif relationType == 'network':
                    checkNetwork(relation, networklevel, aDownloadWasNeeded)
                elif relationType == 'collection':
                    for collectionMember in relation.getMembers():
                        if collectionMember.isRelation():
                            networkRelation = collectionMember.getRelation()
                            if networkRelation.hasIncompleteMembers():
                                name = networkRelation.get('name')
                                if not(name): name = ''
                                networkname = networkRelation.get('network')
                                if not(networkname): networkname = ''
                                if 'downloadIncompleteMembers' in sideEffects:
                                    aDownloadWasNeeded = True
                                    print 'Downloading referrers for ', name
                                    DownloadRelationMemberTask.run(DownloadRelationMemberTask(networkRelation, networkRelation.getIncompleteMembers(), mv.editLayer ))
                                    continue
                                else:
                                    JOptionPane.showMessageDialog(Main.parent, 'Please download all incomplete members of the network relations first:' + networkname + ' ' + name)
                                    break
                            checkNetwork(networkRelation, networklevel, aDownloadWasNeeded)
        if aDownloadWasNeeded:
            JOptionPane.showMessageDialog(Main.parent, 'There was incomplete data and downloading mode was initiated,\nNo further quality checks were performed.\nPlease run the script again when all downloads have completed')


Note: See TracWiki for help on using the wiki.