Ignore:
Timestamp:
2014-06-08T21:36:21+02:00 (10 years ago)
Author:
akks
Message:

Extrude mode: do not allow reversing the moved segment direction to avoid strange shapes, see #7991
can be disabled by extrude.dualalign.keep-segment-direction=false

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java

    r7217 r7223  
    3030import org.openstreetmap.josm.Main;
    3131import org.openstreetmap.josm.actions.JosmAction;
     32import org.openstreetmap.josm.actions.MergeNodesAction;
    3233import org.openstreetmap.josm.command.AddCommand;
    3334import org.openstreetmap.josm.command.ChangeCommand;
     
    7980    private boolean ignoreSharedNodes;
    8081
     82    private final boolean keepSegmentDirection;
     83   
    8184    /**
    8285     * drawing settings for helper lines
     
    217220                tr("Mode: {0}", tr("Extrude Dual alignment")), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE);
    218221        useRepeatedShortcut = Main.pref.getBoolean("extrude.dualalign.toggleOnRepeatedX", true);
     222        keepSegmentDirection = Main.pref.getBoolean("extrude.dualalign.keep-segment-direction", true);
    219223    }
    220224
     
    446450
    447451            EastNorth mouseEn = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
    448             EastNorth bestMovement = calculateBestMovement(mouseEn);
    449             EastNorth n1movedEn = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());
    450 
    451             // find out the movement distance, in metres
    452             double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(n1movedEn));
    453             Main.map.statusLine.setDist(distance);
    454             updateStatusLine();
    455 
     452            EastNorth bestMovement = calculateBestMovementAndNewNodes(mouseEn);
     453           
    456454            Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this);
    457455
    458456            if (dualAlignActive) {
    459                 calculateDualAlignNodesPositions(bestMovement);
    460 
    461457                if (mode == Mode.extrude || mode == Mode.create_new) {
    462458                    // nothing here
     
    478474                }
    479475            } else {
    480                 newN1en = n1movedEn;
    481                 newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());
    482 
    483476                if (mode == Mode.extrude || mode == Mode.create_new) {
    484477                    //nothing here
     
    595588    /**
    596589     * Does actual extrusion of {@link #selectedSegment}.
     590     * Uses {@link #initialN1en}, {@link #initialN2en} saved in calculatePossibleDirections* call
     591     * Uses {@link #newN1en}, {@link #newN2en} calculated by {@link #calculateBestMovementAndNewNodes}
    597592     */
    598593    private void performExtrusion() {
     
    610605        boolean segmentAngleZero = prevNode != null && Math.abs(Geometry.getCornerAngle(prevNode.getEastNorth(), initialN1en, newN1en)) < 1e-5;
    611606        boolean hasOtherWays = hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way);
    612 
     607        ArrayList<Node> changedNodes = new ArrayList<>();
    613608        if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) {
    614609            //move existing node
    615610            Node n1Old = selectedSegment.getFirstNode();
    616611            cmds.add(new MoveCommand(n1Old, Main.getProjection().eastNorth2latlon(newN1en)));
     612            changedNodes.add(n1Old);
    617613        } else if (ignoreSharedNodes && segmentAngleZero && !alwaysCreateNodes && hasOtherWays) {
    618614            // replace shared node with new one
     
    623619            wayWasModified = true;
    624620            cmds.add(new AddCommand(n1New));
     621            changedNodes.add(n1New);
    625622        } else {
    626623            //introduce new node
     
    630627            insertionPoint ++;
    631628            cmds.add(new AddCommand(n1New));
     629            changedNodes.add(n1New);
    632630        }
    633631
     
    642640            Node n2Old = selectedSegment.getSecondNode();
    643641            cmds.add(new MoveCommand(n2Old, Main.getProjection().eastNorth2latlon(newN2en)));
     642            changedNodes.add(n2Old);
    644643        } else if (ignoreSharedNodes && segmentAngleZero && !alwaysCreateNodes && hasOtherWays) {
    645644            // replace shared node with new one
     
    650649            wayWasModified = true;
    651650            cmds.add(new AddCommand(n2New));
     651            changedNodes.add(n2New);
    652652        } else {
    653653            //introduce new node
     
    657657            insertionPoint ++;
    658658            cmds.add(new AddCommand(n2New));
     659            changedNodes.add(n2New);
    659660        }
    660661
     
    670671        Command c = new SequenceCommand(tr("Extrude Way"), cmds);
    671672        Main.main.undoRedo.add(c);
     673        if (newN1en.distance(newN2en) < 1e-6) {
     674            // If the dual alignment created moved two nodes  to the same point, merge them
     675            Node targetNode = MergeNodesAction.selectTargetNode(changedNodes);
     676            Command mergeCmd = MergeNodesAction.mergeNodes(Main.main.getEditLayer(), changedNodes, targetNode, changedNodes.get(0));
     677            Main.main.undoRedo.add(mergeCmd);
     678        }
    672679    }
    673680
     
    859866            ), initialN2en,  nextNodeEn, false);
    860867    }
    861 
    862     /**
    863      * Calculates positions of new nodes, aligning them to neighboring segments.
    864      * @param movement movement to be used
    865      */
    866     private void calculateDualAlignNodesPositions(EastNorth movement) {
    867         // new positions of selected segment's nodes, without applying dual alignment
    868         EastNorth n1movedEn = new EastNorth(initialN1en.getX() + movement.getX(), initialN1en.getY() + movement.getY());
    869         EastNorth n2movedEn = new EastNorth(initialN2en.getX() + movement.getX(), initialN2en.getY() + movement.getY());
    870 
    871         // calculate intersections
    872         newN1en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment1.p1, dualAlignSegment1.p2);
    873         newN2en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment2.p1, dualAlignSegment2.p2);
     868   
     869    /**
     870     * Calculate newN1en, newN2en best suitable for given mouse coordinates
     871     * For dual align, calculates positions of new nodes, aligning them to neighboring segments.
     872     * Elsewhere, just adds the vetor returned by calculateBestMovement to {@link #initialN1en},  {@link #initialN2en}.
     873     * @return best movement vector
     874     */
     875    private EastNorth calculateBestMovementAndNewNodes(EastNorth mouseEn) {
     876        EastNorth bestMovement = calculateBestMovement(mouseEn);
     877        EastNorth n1movedEn = initialN1en.add(bestMovement), n2movedEn;
     878
     879        // find out the movement distance, in metres
     880        double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(n1movedEn));
     881        Main.map.statusLine.setDist(distance);
     882        updateStatusLine();
     883       
     884        if (dualAlignActive) {
     885            // new positions of selected segment's nodes, without applying dual alignment
     886            n1movedEn = initialN1en.add(bestMovement);
     887            n2movedEn = initialN2en.add(bestMovement);
     888
     889            // calculate intersections of parallel shifted segment and the adjacent lines
     890            newN1en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment1.p1, dualAlignSegment1.p2);
     891            newN2en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment2.p1, dualAlignSegment2.p2);
     892            if (newN1en == null || newN2en == null) return bestMovement;
     893            if (keepSegmentDirection && isOppositeDirection(newN1en, newN2en, initialN1en, initialN2en)) {
     894                EastNorth collapsedSegmentPosition = Geometry.getLineLineIntersection(dualAlignSegment1.p1, dualAlignSegment1.p2, dualAlignSegment2.p1, dualAlignSegment2.p2);
     895                newN1en = collapsedSegmentPosition;
     896                newN2en = collapsedSegmentPosition;
     897            }
     898        } else {
     899            newN1en = n1movedEn;
     900            newN2en = initialN2en.add(bestMovement);
     901        }
     902        return bestMovement;
    874903    }
    875904
     
    961990                    if (dualAlignActive) {
    962991                        // Draw reference ways
    963                         drawReferenceSegment(g2, mv, dualAlignSegment1.p1, dualAlignSegment1.p2);
    964                         drawReferenceSegment(g2, mv, dualAlignSegment2.p1, dualAlignSegment2.p2);
     992                        drawReferenceSegment(g2, mv, dualAlignSegment1);
     993                        drawReferenceSegment(g2, mv, dualAlignSegment2);
    965994                    } else if (activeMoveDirection != null) {
    966995                        // Draw reference way
    967                         drawReferenceSegment(g2, mv, activeMoveDirection.p1, activeMoveDirection.p2);
     996                        drawReferenceSegment(g2, mv, activeMoveDirection);
    968997
    969998                        // Draw right angle marker on first node position, only when moving at right angle
     
    9931022                    if (dualAlignActive) {
    9941023                        // Draw reference ways
    995                         drawReferenceSegment(g2, mv, dualAlignSegment1.p1, dualAlignSegment1.p2);
    996                         drawReferenceSegment(g2, mv, dualAlignSegment2.p1, dualAlignSegment2.p2);
     1024                        drawReferenceSegment(g2, mv, dualAlignSegment1);
     1025                        drawReferenceSegment(g2, mv, dualAlignSegment2);
    9971026                    } else if (activeMoveDirection != null) {
    9981027
     
    10351064        return normalUnitVector;
    10361065    }
     1066   
     1067    /**
     1068     * Returns true if from1-to1 and from2-to2 vertors directions are opposite
     1069     */
     1070    private boolean isOppositeDirection(EastNorth from1, EastNorth to1, EastNorth from2, EastNorth to2) {
     1071        return (from1.getX()-to1.getX())*(from2.getX()-to2.getX())
     1072              +(from1.getY()-to1.getY())*(from2.getY()-to2.getY()) < 0;
     1073    }
    10371074
    10381075    /**
     
    10701107     * @param p2en segment's second point
    10711108     */
    1072     private void drawReferenceSegment(Graphics2D g2, MapView mv, EastNorth p1en, EastNorth p2en)
     1109    private void drawReferenceSegment(Graphics2D g2, MapView mv, ReferenceSegment seg)
    10731110    {
    1074         Point p1 = mv.getPoint(p1en);
    1075         Point p2 = mv.getPoint(p2en);
     1111        Point p1 = mv.getPoint(seg.p1);
     1112        Point p2 = mv.getPoint(seg.p2);
    10761113        GeneralPath b = new GeneralPath();
    10771114        b.moveTo(p1.x, p1.y);
Note: See TracChangeset for help on using the changeset viewer.