Changeset 3557 in josm


Ignore:
Timestamp:
2010-09-23T15:02:05+02:00 (11 years ago)
Author:
bastiK
Message:

applied #5427 (patch by extropy) - Extrude leaves unnecessary nodes and 'antennae' segments when extruding again in a direction

Location:
trunk/src/org/openstreetmap/josm
Files:
2 edited

Legend:

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

    r3530 r3557  
    1818import java.awt.geom.NoninvertibleTransformException;
    1919import java.awt.geom.Point2D;
     20import java.util.ArrayList;
    2021import java.util.Collection;
    2122import java.util.LinkedList;
     23import java.util.List;
    2224
    2325import org.openstreetmap.josm.Main;
     
    4850
    4951    enum Mode { extrude, translate, select }
     52
    5053    private Mode mode = Mode.select;
     54
     55    /**
     56     * If true, when extruding create new node even if segments prallel.
     57     */
     58    private boolean alwaysCreateNodes = false;
    5159    private long mouseDownTime = 0;
    5260    private WaySegment selectedSegment = null;
     
    5462
    5563    /**
     64     * Possible directions to move to.
     65     */
     66    private List<EastNorth> possibleMoveDirections;
     67
     68    /**
     69     * The direction that is currently active.
     70     */
     71    private EastNorth activeMoveDirection;
     72
     73    /**
    5674     * The old cursor before the user pressed the mouse button.
    5775     */
     
    7694    private EastNorth newN1en;
    7795    private EastNorth newN2en;
    78     /**
    79      * This is to work around some deficiencies in MoveCommand when translating
    80      */
    81     private EastNorth lastTranslatedN1en;
     96
     97    /**
     98     * the command that performed last move.
     99     */
     100    private MoveCommand moveCommand;
     101
    82102    /**
    83103     * Create a new SelectAction
     
    94114    }
    95115
    96     private static Cursor getCursor(String name, String mod, int def) {
    97         try {
    98             return ImageProvider.getCursor(name, mod);
    99         } catch (Exception e) {
    100         }
    101         return Cursor.getPredefinedCursor(def);
    102     }
    103 
    104     private void setCursor(Cursor c) {
    105         if (oldCursor == null) {
    106             oldCursor = Main.map.mapView.getCursor();
    107             Main.map.mapView.setCursor(c);
    108         }
    109     }
    110 
    111     private void restoreCursor() {
    112         if (oldCursor != null) {
    113             Main.map.mapView.setCursor(oldCursor);
    114             oldCursor = null;
    115         }
     116    @Override public String getModeHelpText() {
     117        if (mode == Mode.translate)
     118            return tr("Move a segment along its normal, then release the mouse button.");
     119        else if (mode == Mode.extrude)
     120            return tr("Draw a rectangle of the desired size, then release the mouse button.");
     121        else
     122            return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal.");
     123    }
     124
     125    @Override public boolean layerIsSupported(Layer l) {
     126        return l instanceof OsmDataLayer;
    116127    }
    117128
     
    123134
    124135    @Override public void exitMode() {
    125         super.exitMode();
    126136        Main.map.mapView.removeMouseListener(this);
    127137        Main.map.mapView.removeMouseMotionListener(this);
    128138        Main.map.mapView.removeTemporaryLayer(this);
     139        super.exitMode();
     140    }
     141
     142    /**
     143     * If the left mouse button is pressed over a segment, switch
     144     * to either extrude or translate mode depending on whether ctrl is held.
     145     */
     146    @Override public void mousePressed(MouseEvent e) {
     147        if(!Main.map.mapView.isActiveLayerVisible())
     148            return;
     149        if (!(Boolean)this.getValue("active"))
     150            return;
     151        if (e.getButton() != MouseEvent.BUTTON1)
     152            return;
     153
     154        selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
     155
     156        if (selectedSegment == null) {
     157            // If nothing gets caught, stay in select mode
     158        } else {
     159            // Otherwise switch to another mode
     160
     161            if ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0) {
     162                mode = Mode.translate;
     163            } else {
     164                mode = Mode.extrude;
     165                getCurrentDataSet().setSelected(selectedSegment.way);
     166                alwaysCreateNodes = ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0);
     167            }
     168
     169            // remember initial positions for segment nodes.
     170            initialN1en = selectedSegment.getFirstNode().getEastNorth();
     171            initialN2en = selectedSegment.getSecondNode().getEastNorth();
     172
     173            //gather possible move directions - perpendicular to the selected segment and parallel to neighbor segments
     174            possibleMoveDirections = new ArrayList<EastNorth>();
     175            possibleMoveDirections.add(new EastNorth(
     176                    initialN1en.getY() - initialN2en.getY(),
     177                    initialN2en.getX() - initialN1en.getX()));
     178
     179            //add directions parallel to neighbor segments
     180
     181            Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
     182            if (prevNode != null) {
     183                EastNorth en = prevNode.getEastNorth();
     184                possibleMoveDirections.add(new EastNorth(
     185                        initialN1en.getX() - en.getX(),
     186                        initialN1en.getY() - en.getY()));
     187            }
     188
     189            Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
     190            if (nextNode != null) {
     191                EastNorth en = nextNode.getEastNorth();
     192                possibleMoveDirections.add(new EastNorth(
     193                        initialN2en.getX() - en.getX(),
     194                        initialN2en.getY() - en.getY()));
     195            }
     196           
     197            // Signifies that nothing has happened yet
     198            newN1en = null;
     199            newN2en = null;
     200            moveCommand = null;
     201
     202            Main.map.mapView.addTemporaryLayer(this);
     203
     204            updateStatusLine();
     205            Main.map.mapView.repaint();
     206
     207            // Make note of time pressed
     208            mouseDownTime = System.currentTimeMillis();
     209
     210            // Make note of mouse position
     211            initialMousePos = e.getPoint();
     212        }
    129213    }
    130214
     
    137221
    138222        // do not count anything as a drag if it lasts less than 100 milliseconds.
    139         if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay) return;
     223        if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)
     224            return;
    140225
    141226        if (mode == Mode.select) {
    142227            // Just sit tight and wait for mouse to be released.
    143228        } else {
    144             Node nd1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
    145             Node nd2 = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1);
    146 
    147             EastNorth en1 = nd1.getEastNorth();
    148             EastNorth en2 = nd2.getEastNorth();
    149             EastNorth en3 = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
    150 
    151             double u = ((en3.east() - en1.east()) * (en2.east() - en1.east()) +
    152                     (en3.north() - en1.north()) * (en2.north() - en1.north())) /
    153                     en2.distanceSq(en1);
    154             // the point on the segment from which the distance to mouse pos is shortest
    155             EastNorth base = new EastNorth(en1.east() + u * (en2.east() - en1.east()),
    156                     en1.north() + u * (en2.north() - en1.north()));
    157 
    158             // find out the distance, in metres, between the base point and the mouse cursor
    159             double distance = Main.proj.eastNorth2latlon(base).greatCircleDistance(Main.proj.eastNorth2latlon(en3));
     229            //move and extrude mode - move the selected segment
     230
     231            EastNorth initialMouseEn = Main.map.mapView.getEastNorth(initialMousePos.x, initialMousePos.y);
     232            EastNorth mouseEn = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
     233            EastNorth mouseMovement = new EastNorth(mouseEn.getX() - initialMouseEn.getX(), mouseEn.getY() - initialMouseEn.getY());
     234
     235            double bestDistance = Double.POSITIVE_INFINITY;
     236            EastNorth bestMovement = null;
     237            activeMoveDirection = null;
     238
     239            //find the best movement direction and vector
     240            for (EastNorth direction: possibleMoveDirections) {
     241                EastNorth movement = calculateSegmentOffset(initialN1en, initialN2en, direction , mouseEn);
     242                if (movement == null) {
     243                    //if direction parallel to segment.
     244                    continue;
     245                }
     246
     247                double distanceFromMouseMovement = movement.distance(mouseMovement);
     248                if (bestDistance > distanceFromMouseMovement) {
     249                    bestDistance = distanceFromMouseMovement;
     250                    activeMoveDirection = direction;
     251                    bestMovement = movement;
     252                }
     253            }
     254
     255            newN1en = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());
     256            newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());
     257
     258            // find out the movement distance, in metres
     259            double distance = Main.proj.eastNorth2latlon(initialN1en).greatCircleDistance(Main.proj.eastNorth2latlon(newN1en));
    160260            Main.map.statusLine.setDist(distance);
    161261            updateStatusLine();
    162262
    163             // compute vertical and horizontal components.
    164             double xoff = en3.east() - base.east();
    165             double yoff = en3.north() - base.north();
    166 
    167             newN1en = new EastNorth(en1.getX() + xoff, en1.getY() + yoff);
    168             newN2en = new EastNorth(en2.getX() + xoff, en2.getY() + yoff);
    169 
    170             // find out the distance, in metres, between the initial position of N1 and the new one.
    171             Main.map.statusLine.setDist(Main.proj.eastNorth2latlon(initialN1en).greatCircleDistance(Main.proj.eastNorth2latlon(newN1en)));
     263            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
     264
     265            if (mode == Mode.extrude) {
     266                //nothing here
     267            } else if (mode == Mode.translate) {
     268                //move nodes to new position
     269                if (moveCommand == null) {
     270                    //make a new move command
     271                    Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
     272                    nodelist.add(selectedSegment.getFirstNode());
     273                    nodelist.add(selectedSegment.getSecondNode());
     274                    moveCommand = new MoveCommand(nodelist, bestMovement.getX(), bestMovement.getY());
     275                    Main.main.undoRedo.add(moveCommand);
     276                } else {
     277                    //reuse existing move command
     278                    moveCommand.undoCommand();
     279                    moveCommand.moveAgain(bestMovement.getX(), bestMovement.getY());
     280                }
     281            }
     282
     283            Main.map.mapView.repaint();
     284        }
     285    }
     286
     287    /**
     288     * Do anything that needs to be done, then switch back to select mode
     289     */
     290    @Override public void mouseReleased(MouseEvent e) {
     291
     292        if(!Main.map.mapView.isActiveLayerVisible())
     293            return;
     294
     295        if (mode == Mode.select) {
     296            // Nothing to be done
     297        } else {
     298            if (mode == Mode.extrude) {
     299                if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) {
     300                    // create extrusion
     301
     302                    Collection<Command> cmds = new LinkedList<Command>();
     303                    Way wnew = new Way(selectedSegment.way);
     304                    int insertionPoint = selectedSegment.lowerIndex + 1;
     305
     306                    //find if the new points overlap existing segments (in case of 90 degree angles)
     307                    Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
     308                    boolean nodeOverlapsSegment = prevNode != null && pointsColinear(prevNode.getEastNorth(), initialN1en, newN1en);
     309
     310                    if (nodeOverlapsSegment && !alwaysCreateNodes) {
     311                        //move existing node
     312                        Node n1Old = selectedSegment.getFirstNode();
     313                        cmds.add(new MoveCommand(n1Old, Main.proj.eastNorth2latlon(newN1en)));
     314                    } else {
     315                        //introduce new node
     316                        Node n1New = new Node(Main.proj.eastNorth2latlon(newN1en));
     317                        wnew.addNode(insertionPoint, n1New);
     318                        insertionPoint ++;
     319                        cmds.add(new AddCommand(n1New));
     320                    }
     321
     322                    //find if the new points overlap existing segments (in case of 90 degree angles)
     323                    Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
     324                    nodeOverlapsSegment = nextNode != null && pointsColinear(nextNode.getEastNorth(), initialN2en, newN2en);
     325
     326                    if (nodeOverlapsSegment && !alwaysCreateNodes) {
     327                        //move existing node
     328                        Node n2Old = selectedSegment.getSecondNode();
     329                        cmds.add(new MoveCommand(n2Old, Main.proj.eastNorth2latlon(newN2en)));
     330                    } else {
     331                        //introduce new node
     332                        Node n2New = new Node(Main.proj.eastNorth2latlon(newN2en));
     333                        wnew.addNode(insertionPoint, n2New);
     334                        insertionPoint ++;
     335                        cmds.add(new AddCommand(n2New));
     336                    }
     337
     338                    //the way was a single segment, close the way
     339                    if (wnew.getNodesCount() == 4) {
     340                        wnew.addNode(selectedSegment.getFirstNode());
     341                    }
     342
     343                    cmds.add(new ChangeCommand(selectedSegment.way, wnew));
     344                    Command c = new SequenceCommand(tr("Extrude Way"), cmds);
     345                    Main.main.undoRedo.add(c);
     346                }
     347            } else if (mode == Mode.translate) {
     348                //Commit translate
     349                //the move command is already committed in mouseDragged
     350            }
     351
     352            // Switch back into select mode
     353            restoreCursor();
     354            Main.map.mapView.removeTemporaryLayer(this);
     355            selectedSegment = null;
     356            moveCommand = null;
     357            mode = Mode.select;
     358
    172359            updateStatusLine();
    173 
    174             setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    175 
    176             if (mode == Mode.extrude) {
    177 
    178             } else if (mode == Mode.translate) {
    179                 Command c = !Main.main.undoRedo.commands.isEmpty()
    180                 ? Main.main.undoRedo.commands.getLast() : null;
    181                 if (c instanceof SequenceCommand) {
    182                     c = ((SequenceCommand)c).getLastCommand();
    183                 }
    184 
    185                 Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
    186                 Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
    187 
    188                 EastNorth difference = new EastNorth(newN1en.getX()-lastTranslatedN1en.getX(), newN1en.getY()-lastTranslatedN1en.getY());
    189 
    190                 // Better way of testing list equality non-order-sensitively?
    191                 if (c instanceof MoveCommand
    192                         && ((MoveCommand)c).getParticipatingPrimitives().contains(n1)
    193                         && ((MoveCommand)c).getParticipatingPrimitives().contains(n2)
    194                         && ((MoveCommand)c).getParticipatingPrimitives().size() == 2) {
    195                     // MoveCommand doesn't let us know how much it has already moved the selection
    196                     // so we have to do some ugly record-keeping.
    197                     ((MoveCommand)c).moveAgain(difference.getX(), difference.getY());
    198                     lastTranslatedN1en = newN1en;
    199                 } else {
    200                     Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
    201                     nodelist.add(n1);
    202                     nodelist.add(n2);
    203                     Main.main.undoRedo.add(c = new MoveCommand(nodelist, difference.getX(), difference.getY()));
    204                     lastTranslatedN1en = newN1en;
    205                 }
    206             }
    207360            Main.map.mapView.repaint();
    208361        }
    209362    }
    210363
    211     /**
    212      * Create a new Line that extends off the edge of the viewport in one direction
    213      * @param start The start point of the line
    214      * @param unitvector A unit vector denoting the direction of the line
    215      * @param g the Graphics2D object  it will be used on
    216      */
    217     static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) {
    218         Rectangle bounds = g.getDeviceConfiguration().getBounds();
    219         try {
    220             AffineTransform invtrans = g.getTransform().createInverse();
    221             Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null);
    222             Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null);
    223 
    224             // Here we should end up with a gross overestimate of the maximum viewport diagonal in what
    225             // Graphics2D calls 'user space'. Essentially a manhattan distance of manhattan distances.
    226             // This can be used as a safe length of line to generate which will always go off-viewport.
    227             double linelength = Math.abs(widthpoint.getX()) + Math.abs(widthpoint.getY()) + Math.abs(heightpoint.getX()) + Math.abs(heightpoint.getY());
    228 
    229             return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength)));
    230         }
    231         catch (NoninvertibleTransformException e) {
    232             return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * 10) , start.getY() + (unitvector.getY() * 10)));
    233         }
     364    /***
     365     * This method calculates offset amount by witch to move the given segment perpendicularly for it to be in line with mouse position.
     366     * @param segmentP1
     367     * @param segmentP2
     368     * @param targetPos
     369     * @return offset amount of P1 and P2.
     370     */
     371    private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2 ,EastNorth moveDirection , EastNorth targetPos)
     372    {
     373        EastNorth intersectionPoint = getLineLineIntersection(
     374                segmentP1,
     375                segmentP2,
     376                targetPos,
     377                new EastNorth(targetPos.getX() + moveDirection.getX(), targetPos.getY() + moveDirection.getY()));
     378
     379        if (intersectionPoint == null)
     380            return null;
     381        else
     382            //return distance form base to target position
     383            return new EastNorth(targetPos.getX() - intersectionPoint.getX(), targetPos.getY() - intersectionPoint.getY());
     384    }
     385
     386    /**
     387     * Finds the intersection of two lines of inifinite length
     388     * @return EastNorth null if no intersection was found, the Lon coordinates of the intersection otherwise
     389     */
     390    static public EastNorth getLineLineIntersection(
     391            EastNorth p1, EastNorth p2,
     392            EastNorth p3, EastNorth p4) {
     393
     394        // Convert line from (point, point) form to ax+by=c
     395        double a1 = p2.getY() - p1.getY();
     396        double b1 = p1.getX() - p2.getX();
     397        double c1 = p2.getX() * p1.getY() - p1.getX() * p2.getY();
     398
     399        double a2 = p4.getY() - p3.getY();
     400        double b2 = p3.getX() - p4.getX();
     401        double c2 = p4.getX() * p3.getY() - p3.getX() * p4.getY();
     402
     403        // Solve the equations
     404        double det = a1*b2 - a2*b1;
     405        if(det == 0) return null; // Lines are parallel
     406
     407        return new EastNorth(
     408                (b1*c2 - b2*c1)/det,
     409                (a2*c1 - a1*c2)/det);
     410    }
     411
     412    /**
     413     * Returns true if all points are on the same line.
     414     */
     415    private static boolean pointsColinear(EastNorth p1, EastNorth p2, EastNorth p3) {
     416
     417        //the simple dumb way of triangle side lengths.
     418        double distance1 = p1.distance(p2);
     419        double distance2 = p1.distance(p3);
     420        double distance3 = p2.distance(p3);
     421
     422        //sort so that distance 1 is the greatest
     423        if (distance1 < distance2) {
     424            double temp = distance1;
     425            distance1 = distance2;
     426            distance2 = temp;
     427        }
     428
     429        if (distance1 < distance3) {
     430            double temp = distance1;
     431            distance1 = distance3;
     432            distance3 = temp;
     433        }
     434
     435        //test with some treshold
     436        double difference = distance1 - distance2 - distance3;
     437
     438        return (Math.abs(difference) < 1e-15);
     439
     440    }
     441
     442    /**
     443     * Gets a node from selected way before given index.
     444     * @param index  index of current node
     445     * @return previous node or null if there are no nodes there.
     446     */
     447    private Node getPreviousNode(int index) {
     448        if (index > 0)
     449            return selectedSegment.way.getNode(index - 1);
     450        else if (selectedSegment.way.isClosed())
     451            return selectedSegment.way.getNode(selectedSegment.way.getNodesCount() - 2);
     452        else
     453            return null;
     454    }
     455
     456    /**
     457     * Gets a node from selected way before given index.
     458     * @param index index of current node
     459     * @return next node or null if there are no nodes there.
     460     */
     461    private Node getNextNode(int index) {
     462        int count = selectedSegment.way.getNodesCount();
     463        if (index <  count - 1)
     464            return selectedSegment.way.getNode(index + 1);
     465        else if (selectedSegment.way.isClosed())
     466            return selectedSegment.way.getNode(1);
     467        else
     468            return null;
    234469    }
    235470
     
    264499                    g2.draw(oldline);
    265500
    266                     EastNorth segmentVector = new EastNorth(initialN2en.getX()-initialN1en.getX(), initialN2en.getY()-initialN1en.getY());
    267 
    268                     double fac = 1.0 / Math.hypot(segmentVector.getX(), segmentVector.getY());
    269                     // swap coords to get normal, mult by factor to get unit vector.
    270                     EastNorth normalUnitVector = new EastNorth(segmentVector.getY() * fac, segmentVector.getX() * fac);
    271 
    272                     // Draw a guideline along the normal.
    273                     Line2D normline;
    274                     Point2D centerpoint = new Point2D.Double((p1.getX()+p2.getX())*0.5, (p1.getY()+p2.getY())*0.5);
    275                     EastNorth drawnorm;
    276                     // Check to see if our new N1 is in a positive direction with respect to the normalUnitVector.
    277                     // Even if the x component is zero, we should still be able to discern using +0.0 and -0.0
    278                     if (newN1en == null || (newN1en.getX() > initialN1en.getX() == normalUnitVector.getX() > -0.0)) {
    279                         drawnorm = normalUnitVector;
    280                     } else {
    281                         // If not, use a sign-flipped version of the normalUnitVector.
    282                         drawnorm = new EastNorth(-normalUnitVector.getX(), -normalUnitVector.getY());
     501                    if (activeMoveDirection != null) {
     502
     503                        double fac = 1.0 / activeMoveDirection.distance(0,0);
     504                        // mult by factor to get unit vector.
     505                        EastNorth normalUnitVector = new EastNorth(activeMoveDirection.getX() * fac, activeMoveDirection.getY() * fac);
     506
     507                        // Check to see if our new N1 is in a positive direction with respect to the normalUnitVector.
     508                        // Even if the x component is zero, we should still be able to discern using +0.0 and -0.0
     509                        if (newN1en != null && (newN1en.getX() > initialN1en.getX() != normalUnitVector.getX() > -0.0)) {
     510                            // If not, use a sign-flipped version of the normalUnitVector.
     511                            normalUnitVector = new EastNorth(-normalUnitVector.getX(), -normalUnitVector.getY());
     512                        }
     513
     514                        //HACK: swap Y, because the target pixels are top down, but EastNorth is bottom-up.
     515                        //This is normally done by MapView.getPoint, but it does not work on vectors.
     516                        normalUnitVector.setLocation(normalUnitVector.getX(), -normalUnitVector.getY());
     517
     518                        // Draw a guideline along the normal.
     519                        Line2D normline;
     520                        Point2D centerpoint = new Point2D.Double((p1.getX()+p2.getX())*0.5, (p1.getY()+p2.getY())*0.5);
     521                        normline = createSemiInfiniteLine(centerpoint, normalUnitVector, g2);
     522                        g2.draw(normline);
     523
     524                        // Draw right angle marker on initial position, only when moving at right angle
     525                        if (activeMoveDirection == possibleMoveDirections.get(0)) {
     526                            // EastNorth units per pixel
     527                            double factor = 1.0/g2.getTransform().getScaleX();
     528
     529                            double raoffsetx = 8.0*factor*normalUnitVector.getX();
     530                            double raoffsety = 8.0*factor*normalUnitVector.getY();
     531                            Point2D ra1 = new Point2D.Double(centerpoint.getX()+raoffsetx, centerpoint.getY()+raoffsety);
     532                            Point2D ra3 = new Point2D.Double(centerpoint.getX()-raoffsety, centerpoint.getY()+raoffsetx);
     533                            Point2D ra2 = new Point2D.Double(ra1.getX()-raoffsety, ra1.getY()+raoffsetx);
     534                            GeneralPath ra = new GeneralPath();
     535                            ra.moveTo((float)ra1.getX(), (float)ra1.getY());
     536                            ra.lineTo((float)ra2.getX(), (float)ra2.getY());
     537                            ra.lineTo((float)ra3.getX(), (float)ra3.getY());
     538                            g2.draw(ra);
     539                        }
    283540                    }
    284                     normline = createSemiInfiniteLine(centerpoint, drawnorm, g2);
    285                     g2.draw(normline);
    286 
    287                     // EastNorth units per pixel
    288                     double factor = 1.0/g2.getTransform().getScaleX();
    289 
    290                     // Draw right angle marker on initial position.
    291                     double raoffsetx = 8.0*factor*drawnorm.getX();
    292                     double raoffsety = 8.0*factor*drawnorm.getY();
    293                     Point2D ra1 = new Point2D.Double(centerpoint.getX()+raoffsetx, centerpoint.getY()+raoffsety);
    294                     Point2D ra3 = new Point2D.Double(centerpoint.getX()-raoffsety, centerpoint.getY()+raoffsetx);
    295                     Point2D ra2 = new Point2D.Double(ra1.getX()-raoffsety, ra1.getY()+raoffsetx);
    296                     GeneralPath ra = new GeneralPath();
    297                     ra.moveTo((float)ra1.getX(), (float)ra1.getY());
    298                     ra.lineTo((float)ra2.getX(), (float)ra2.getY());
    299                     ra.lineTo((float)ra3.getX(), (float)ra3.getY());
    300                     g2.draw(ra);
    301541                }
    302542            }
     
    305545
    306546    /**
    307      * If the left mouse button is pressed over a segment, switch
    308      * to either extrude or translate mode depending on whether ctrl is held.
    309      */
    310     @Override public void mousePressed(MouseEvent e) {
    311         if(!Main.map.mapView.isActiveLayerVisible())
    312             return;
    313         if (!(Boolean)this.getValue("active")) return;
    314         if (e.getButton() != MouseEvent.BUTTON1)
    315             return;
    316         // boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    317         // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
    318         // boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
    319 
    320         selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
    321 
    322         if (selectedSegment == null) {
    323             // If nothing gets caught, stay in select mode
    324         } else {
    325             // Otherwise switch to another mode
    326 
    327             // For extrusion, these positions are actually never changed,
    328             // but keeping note of this anyway allows us to not continually
    329             // look it up and also allows us to unify code with the translate mode
    330             initialN1en = selectedSegment.way.getNode(selectedSegment.lowerIndex).getEastNorth();
    331             initialN2en = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1).getEastNorth();
    332 
    333             // Signifies that nothing has happened yet
    334             newN1en = null;
    335             newN2en = null;
    336 
    337             Main.map.mapView.addTemporaryLayer(this);
    338 
    339             updateStatusLine();
    340             Main.map.mapView.repaint();
    341 
    342             // Make note of time pressed
    343             mouseDownTime = System.currentTimeMillis();
    344 
    345             // Make note of mouse position
    346             initialMousePos = e.getPoint();
    347 
    348             // Switch mode.
    349             if ( (e.getModifiers() & ActionEvent.CTRL_MASK) != 0 ) {
    350                 mode = Mode.translate;
    351                 lastTranslatedN1en = initialN1en;
    352             } else {
    353                 mode = Mode.extrude;
    354                 getCurrentDataSet().setSelected(selectedSegment.way);
    355             }
    356         }
    357     }
    358 
    359     /**
    360      * Do anything that needs to be done, then switch back to select mode
    361      */
    362     @Override public void mouseReleased(MouseEvent e) {
    363 
    364         if(!Main.map.mapView.isActiveLayerVisible())
    365             return;
    366 
    367         if (mode == Mode.select) {
    368             // Nothing to be done
    369         } else {
    370             if (mode == Mode.extrude) {
    371                 if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) {
    372                     // Commit extrusion
    373 
    374                     Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
    375                     //Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
    376                     Node n3 = new Node(Main.proj.eastNorth2latlon(newN2en));
    377                     Node n4 = new Node(Main.proj.eastNorth2latlon(newN1en));
    378                     Way wnew = new Way(selectedSegment.way);
    379                     wnew.addNode(selectedSegment.lowerIndex+1, n3);
    380                     wnew.addNode(selectedSegment.lowerIndex+1, n4);
    381                     if (wnew.getNodesCount() == 4) {
    382                         wnew.addNode(n1);
    383                     }
    384                     Collection<Command> cmds = new LinkedList<Command>();
    385                     cmds.add(new AddCommand(n4));
    386                     cmds.add(new AddCommand(n3));
    387                     cmds.add(new ChangeCommand(selectedSegment.way, wnew));
    388                     Command c = new SequenceCommand(tr("Extrude Way"), cmds);
    389                     Main.main.undoRedo.add(c);
    390                 }
    391             } else if (mode == Mode.translate) {
    392                 // I don't think there's anything to do
    393             }
    394 
    395             // Switch back into select mode
    396             restoreCursor();
    397             Main.map.mapView.removeTemporaryLayer(this);
    398             selectedSegment = null;
    399             mode = Mode.select;
    400 
    401             updateStatusLine();
    402             Main.map.mapView.repaint();
    403         }
    404     }
    405 
    406     @Override public String getModeHelpText() {
    407         if (mode == Mode.translate)
    408             return tr("Move a segment along its normal, then release the mouse button.");
    409         else if (mode == Mode.extrude)
    410             return tr("Draw a rectangle of the desired size, then release the mouse button.");
    411         else
    412             return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal.");
    413     }
    414 
    415     @Override public boolean layerIsSupported(Layer l) {
    416         return l instanceof OsmDataLayer;
     547     * Create a new Line that extends off the edge of the viewport in one direction
     548     * @param start The start point of the line
     549     * @param unitvector A unit vector denoting the direction of the line
     550     * @param g the Graphics2D object  it will be used on
     551     */
     552    static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) {
     553        Rectangle bounds = g.getDeviceConfiguration().getBounds();
     554        try {
     555            AffineTransform invtrans = g.getTransform().createInverse();
     556            Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null);
     557            Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null);
     558
     559            // Here we should end up with a gross overestimate of the maximum viewport diagonal in what
     560            // Graphics2D calls 'user space'. Essentially a manhattan distance of manhattan distances.
     561            // This can be used as a safe length of line to generate which will always go off-viewport.
     562            double linelength = Math.abs(widthpoint.getX()) + Math.abs(widthpoint.getY()) + Math.abs(heightpoint.getX()) + Math.abs(heightpoint.getY());
     563
     564            return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength)));
     565        }
     566        catch (NoninvertibleTransformException e) {
     567            return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * 10) , start.getY() + (unitvector.getY() * 10)));
     568        }
     569    }
     570
     571    private static Cursor getCursor(String name, String mod, int def) {
     572        try {
     573            return ImageProvider.getCursor(name, mod);
     574        } catch (Exception e) {
     575        }
     576        return Cursor.getPredefinedCursor(def);
     577    }
     578
     579    private void setCursor(Cursor c) {
     580        if (oldCursor == null) {
     581            oldCursor = Main.map.mapView.getCursor();
     582            Main.map.mapView.setCursor(c);
     583        }
     584    }
     585
     586    private void restoreCursor() {
     587        if (oldCursor != null) {
     588            Main.map.mapView.setCursor(oldCursor);
     589            oldCursor = null;
     590        }
    417591    }
    418592}
  • trunk/src/org/openstreetmap/josm/data/osm/WaySegment.java

    r1169 r3557  
    2222    }
    2323
     24    public Node getFirstNode(){
     25        return way.getNode(lowerIndex);
     26    }
     27
     28    public Node getSecondNode(){
     29        return way.getNode(lowerIndex + 1);
     30    }
     31
    2432    @Override public boolean equals(Object o) {
    2533        return o != null && o instanceof WaySegment
Note: See TracChangeset for help on using the changeset viewer.