| | 406 | |
| | 407 | Remove extra ways resulting from Potlatch way split operations under turn restriction relations |
| | 408 | |
| | 409 | {{{#!python |
| | 410 | |
| | 411 | #!/bin/jython |
| | 412 | |
| | 413 | ''' |
| | 414 | RepairTurns.py - Remove extra ways resulting from Potlatch way split operations under turn restriction relations |
| | 415 | |
| | 416 | This code is released under the GNU General |
| | 417 | Public License v2 or later. |
| | 418 | |
| | 419 | The GPL v3 is accessible here: |
| | 420 | http://www.gnu.org/licenses/gpl.html |
| | 421 | |
| | 422 | The GPL v2 is accessible here: |
| | 423 | http://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
| | 424 | |
| | 425 | It comes with no warranty whatsoever. |
| | 426 | |
| | 427 | This code Loops through selected turn restriction relations, trying to remove ways split from originally the same way (with to / from roles) under turn restriction relations which should no longer remain as members of these relations, as a result of a Potlatch issue: https://trac.openstreetmap.org/ticket/3254 |
| | 428 | |
| | 429 | Only work for turn restrictions with one via node |
| | 430 | |
| | 431 | e.g. Original : from: Way1, via: Node, to:Way2 |
| | 432 | Split : from: Way1a, from: Way1b, via: Node, to: Way2 |
| | 433 | After running : from: Way1b, via: Node, to: Way2 |
| | 434 | |
| | 435 | This code illustrates how to use Jython to: |
| | 436 | * process selected items |
| | 437 | * download missing primitives in the same thread (blocking) |
| | 438 | * process, validate and remove members from relations |
| | 439 | |
| | 440 | ''' |
| | 441 | from javax.swing import JOptionPane |
| | 442 | from org.openstreetmap.josm import Main |
| | 443 | import org.openstreetmap.josm.data.osm.Node as Node |
| | 444 | import org.openstreetmap.josm.data.osm.Way as Way |
| | 445 | import org.openstreetmap.josm.data.osm.Relation as Relation |
| | 446 | import org.openstreetmap.josm.data.osm.TagCollection as TagCollection |
| | 447 | import org.openstreetmap.josm.data.osm.DataSet as DataSet |
| | 448 | import org.openstreetmap.josm.data.osm.RelationMember as RelationMember |
| | 449 | import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask as DownloadRelationMemberTask |
| | 450 | import org.openstreetmap.josm.io.MultiFetchServerObjectReader as MultiFetchServerObjectReader |
| | 451 | import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor as PleaseWaitProgressMonitor |
| | 452 | import org.openstreetmap.josm.command as Command |
| | 453 | |
| | 454 | class RestrictionError(Exception): |
| | 455 | pass |
| | 456 | |
| | 457 | def getMapView(): |
| | 458 | if Main.main and Main.main.map: |
| | 459 | return Main.main.map.mapView |
| | 460 | else: |
| | 461 | return None |
| | 462 | |
| | 463 | def getMembers (restriction): |
| | 464 | memberlist = dict() |
| | 465 | for member in restriction.getMembers(): |
| | 466 | memberRole = member.getRole() |
| | 467 | if memberRole == "via": |
| | 468 | if member.isNode() and not "via" in memberlist: |
| | 469 | memberlist[memberRole] = [member] |
| | 470 | else: |
| | 471 | raise RestrictionError, "More than one via role or via not a node" |
| | 472 | elif memberRole in ("from", "to"): |
| | 473 | if member.isWay(): |
| | 474 | try: |
| | 475 | memberlist[memberRole].append (member) |
| | 476 | except KeyError: |
| | 477 | memberlist[memberRole] = [member] |
| | 478 | else: |
| | 479 | raise RestrictionError, "From or to role not a way" |
| | 480 | else: |
| | 481 | raise RestrictionError, "Unknown role " + memberRole |
| | 482 | |
| | 483 | if len(memberlist.keys())<3: |
| | 484 | raise RestrictionError, "Some roles missing: Only " + ",".join (memberlist.keys()) + " found" |
| | 485 | return memberlist |
| | 486 | |
| | 487 | def downloadPrimitives (primitives, editlayer): |
| | 488 | """ |
| | 489 | Download a list of primitives from server, and merge into editlayer. Blocking. |
| | 490 | """ |
| | 491 | monitor = PleaseWaitProgressMonitor() |
| | 492 | monitor.showForegroundDialog() |
| | 493 | |
| | 494 | print "Downloading" |
| | 495 | try: |
| | 496 | objectReader = MultiFetchServerObjectReader().append (primitives) |
| | 497 | dataSet = objectReader.parseOsm (monitor) |
| | 498 | editlayer.mergeFrom (dataSet) |
| | 499 | editlayer.onPostDownloadFromServer() |
| | 500 | if (not objectReader.getMissingPrimitives().isEmpty()) : |
| | 501 | raise RestrictionError, "Unable to download missing primitives" |
| | 502 | print "Download completed" |
| | 503 | finally: |
| | 504 | monitor.close() |
| | 505 | |
| | 506 | def checkIfConnected (node, way, reverse): |
| | 507 | """ |
| | 508 | Return (connected, next node to compare (i.e. other end of the node) if true) |
| | 509 | """ |
| | 510 | if (way.isOneway() != 0 and reverse): |
| | 511 | return node == way.lastNode(True), way.firstNode(True) # True: auto process case whan isOneway == -1 |
| | 512 | if (way.isOneway() != 0): # not reverse |
| | 513 | return node == way.firstNode(True), way.lastNode(True) |
| | 514 | if node == way.firstNode(): |
| | 515 | return True, way.lastNode() |
| | 516 | if node == way.lastNode(): |
| | 517 | return True, way.firstNode() |
| | 518 | return False, node |
| | 519 | |
| | 520 | |
| | 521 | def repairrestriction (relation, memberlist, editLayer): |
| | 522 | """ |
| | 523 | Download missing members if needed, get list of members to remove, and remove them if no error raised during checking |
| | 524 | """ |
| | 525 | incompleteMembers = relation.getIncompleteMembers() |
| | 526 | if incompleteMembers: |
| | 527 | downloadPrimitives (incompleteMembers, editLayer); |
| | 528 | |
| | 529 | fromRemovalList = []; toRemovalList = [] |
| | 530 | |
| | 531 | if len (memberlist["from"]) >= 1: |
| | 532 | currnode = memberlist["via"][0].getNode() |
| | 533 | firstMember = True |
| | 534 | failed = False; |
| | 535 | # trace "from" members to confirm if split from one way |
| | 536 | for member in reversed(memberlist['from']): |
| | 537 | connected, currnode = checkIfConnected (currnode, member.getWay(), True) |
| | 538 | if not connected: |
| | 539 | if not firstMember: |
| | 540 | raise RestrictionError, "from ways not continuous from via node" |
| | 541 | failed = True |
| | 542 | break |
| | 543 | if not firstMember: |
| | 544 | fromRemovalList.append (member) |
| | 545 | else: |
| | 546 | firstMember = False |
| | 547 | |
| | 548 | if failed: # Second attempt in case sequence reversed when split |
| | 549 | currnode = memberlist["via"][0].getNode() |
| | 550 | for member in memberlist['from']: |
| | 551 | connected, currnode = checkIfConnected (currnode, member.getWay(), True) |
| | 552 | if not connected: |
| | 553 | raise RestrictionError, "from ways not continuous from via node" |
| | 554 | if not firstMember: |
| | 555 | fromRemovalList.append (member) |
| | 556 | else: |
| | 557 | firstMember = False |
| | 558 | |
| | 559 | if len (memberlist["to"]) >= 1: |
| | 560 | currnode = memberlist["via"][0].getNode() |
| | 561 | firstMember = True |
| | 562 | failed = False |
| | 563 | # trace "to" members to confirm if split from one way |
| | 564 | for member in memberlist['to']: |
| | 565 | connected, currnode = checkIfConnected (currnode, member.getWay(), False) |
| | 566 | if not connected: |
| | 567 | if not firstMember: |
| | 568 | raise RestrictionError, "to ways not continuous from via node" |
| | 569 | failed = True |
| | 570 | break |
| | 571 | if not firstMember: |
| | 572 | toRemovalList.append (member) |
| | 573 | else: |
| | 574 | firstMember = False |
| | 575 | |
| | 576 | if failed: # Second attempt in case sequence reversed when split |
| | 577 | currnode = memberlist["via"][0].getNode() |
| | 578 | for member in reversed(memberlist['to']): |
| | 579 | connected, currnode = checkIfConnected (currnode, member.getWay(), False) |
| | 580 | if not connected: |
| | 581 | raise RestrictionError, "to ways not continuous from via node" |
| | 582 | if not firstMember: |
| | 583 | toRemovalList.append (member) |
| | 584 | else: |
| | 585 | firstMember = False |
| | 586 | |
| | 587 | # to remove ways in fromRemovalList, toRemovalList |
| | 588 | newRelation = Relation(relation) |
| | 589 | waysToRemove = set() |
| | 590 | for removalList in [fromRemovalList, toRemovalList]: |
| | 591 | waysToRemove |= set ([m.getWay() for m in removalList]) |
| | 592 | newRelation.removeMembersFor (waysToRemove) |
| | 593 | print "Remove way(s) id:" + ",".join ([str(w.getId()) for w in waysToRemove]) |
| | 594 | return Command.ChangeCommand (relation, newRelation) |
| | 595 | |
| | 596 | |
| | 597 | validrestrictiontypes = ('only_straight_on', 'only_right_turn', 'only_left_turn', 'no_right_turn', 'no_left_turn', 'no_straight_on', 'no_u_turn') |
| | 598 | mv = getMapView() |
| | 599 | if mv and mv.editLayer and mv.editLayer.data: |
| | 600 | selectedRelations = mv.editLayer.data.getSelectedRelations() |
| | 601 | |
| | 602 | if not(selectedRelations): |
| | 603 | JOptionPane.showMessageDialog(Main.parent, "Please select one or more relations") |
| | 604 | else: |
| | 605 | commandsList = [] |
| | 606 | for relation in selectedRelations: |
| | 607 | if relation.get('type') == "restriction" and relation.get('restriction') in validrestrictiontypes: |
| | 608 | try: |
| | 609 | memberlist = getMembers (relation) |
| | 610 | #print "".join(v * len(memberlist[v]) for v in memberlist.keys()) |
| | 611 | if (len (memberlist["from"])) > 1 or (len (memberlist["to"])) > 1 : |
| | 612 | # Repair attempt |
| | 613 | print "Attempt repair", |
| | 614 | print "relation id: " + str(relation.getId()) |
| | 615 | command = repairrestriction (relation, memberlist, mv.editLayer) |
| | 616 | print "Success" |
| | 617 | commandsList.append (command) |
| | 618 | except RestrictionError, e: |
| | 619 | print str(e), "; relation id: "+ str(relation.getId()) |
| | 620 | |
| | 621 | |
| | 622 | Main.main.undoRedo.add(Command.SequenceCommand("Repair turn restrictions", commandsList)) |
| | 623 | commandsList=[] |
| | 624 | }}} |