Ticket #6694: AngleSnappingv3.patch

File AngleSnappingv3.patch, 26.9 KB (added by akks, 17 months ago)
  • src/org/openstreetmap/josm/actions/mapmode/DrawAction.java

     
    1010import java.awt.Cursor; 
    1111import java.awt.Graphics2D; 
    1212import java.awt.Point; 
     13import java.awt.Stroke; 
    1314import java.awt.Toolkit; 
    1415import java.awt.event.AWTEventListener; 
    1516import java.awt.event.ActionEvent; 
     
    1819import java.awt.event.MouseEvent; 
    1920import java.awt.geom.GeneralPath; 
    2021import java.util.ArrayList; 
     22import java.util.Arrays; 
    2123import java.util.Collection; 
    2224import java.util.Collections; 
    2325import java.util.HashMap; 
     
    7779    private Color selectedColor; 
    7880 
    7981    private Node currentBaseNode; 
     82    private Node previousNode; 
    8083    private EastNorth currentMouseEastNorth; 
    8184 
     85    private SnapHelper snapHelper = new SnapHelper(); 
     86 
    8287    private Shortcut extraShortcut; 
    8388    private Shortcut backspaceShortcut; 
     89     
     90     
     91    boolean snapOn; 
    8492             
    8593    public DrawAction(MapFrame mapFrame) { 
    8694        super(tr("Draw"), "node/autonode", tr("Draw nodes"), 
     
    116124        Main.map.mapView.repaint(); 
    117125    } 
    118126 
    119     /** 
    120      * Takes the data from computeHelperLine to determine which ways/nodes should be highlighted 
    121      * (if feature enabled). Also sets the target cursor if appropriate. 
    122      */ 
    123     private void addHighlighting() { 
    124         removeHighlighting(); 
    125         // if ctrl key is held ("no join"), don't highlight anything 
    126         if (ctrl) { 
    127             Main.map.mapView.setNewCursor(cursor, this); 
    128             return; 
    129         } 
    130  
    131         // This happens when nothing is selected, but we still want to highlight the "target node" 
    132         if (mouseOnExistingNode == null && getCurrentDataSet().getSelected().size() == 0 
    133                 && mousePos != null) { 
    134             mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate); 
    135         } 
    136  
    137         if (mouseOnExistingNode != null) { 
    138             Main.map.mapView.setNewCursor(cursorJoinNode, this); 
    139             // We also need this list for the statusbar help text 
    140             oldHighlights.add(mouseOnExistingNode); 
    141             if(drawTargetHighlight) { 
    142                 mouseOnExistingNode.setHighlighted(true); 
    143             } 
    144             return; 
    145         } 
    146  
    147         // Insert the node into all the nearby way segments 
    148         if (mouseOnExistingWays.size() == 0) { 
    149             Main.map.mapView.setNewCursor(cursor, this); 
    150             return; 
    151         } 
    152  
    153         Main.map.mapView.setNewCursor(cursorJoinWay, this); 
    154  
    155         // We also need this list for the statusbar help text 
    156         oldHighlights.addAll(mouseOnExistingWays); 
    157         if (!drawTargetHighlight) return; 
    158         for (Way w : mouseOnExistingWays) { 
    159             w.setHighlighted(true); 
    160         } 
    161     } 
    162  
    163     /** 
    164      * Removes target highlighting from primitives 
    165      */ 
    166     private void removeHighlighting() { 
    167         for(OsmPrimitive prim : oldHighlights) { 
    168             prim.setHighlighted(false); 
    169         } 
    170         oldHighlights = new HashSet<OsmPrimitive>(); 
    171     } 
    172  
    173127    @Override public void enterMode() { 
    174128        if (!isEnabled()) 
    175129            return; 
     
    178132        drawHelperLine = Main.pref.getBoolean("draw.helper-line", true); 
    179133        drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true); 
    180134        wayIsFinished = false; 
     135        snapHelper.init(); 
    181136         
    182137        backspaceShortcut = Shortcut.registerShortcut("mapmode:backspace", tr("Backspace in Add mode"), KeyEvent.VK_BACK_SPACE, Shortcut.GROUP_EDIT); 
    183138        Main.registerActionShortcut(new BackSpaceAction(), backspaceShortcut); 
     
    224179    public void eventDispatched(AWTEvent event) { 
    225180        if(Main.map == null || Main.map.mapView == null || !Main.map.mapView.isActiveLayerDrawable()) 
    226181            return; 
     182        if (event instanceof KeyEvent) { 
     183                KeyEvent ke = (KeyEvent) event; 
     184                if (ke.getKeyCode() == KeyEvent.VK_TAB &&  
     185                    ke.getID()==KeyEvent.KEY_PRESSED) { 
     186                    snapHelper.nextSnapMode(); 
     187                } 
     188        } //  toggle angle snapping 
    227189        updateKeyModifiers((InputEvent) event); 
    228190        computeHelperLine(); 
    229191        addHighlighting(); 
     
    257219        lastUsedNode = null; 
    258220        wayIsFinished = true; 
    259221        Main.map.selectSelectTool(true); 
    260  
     222        snapHelper.noSnapNow(); 
     223     
    261224        // Redraw to remove the helper line stub 
    262225        computeHelperLine(); 
    263226        removeHighlighting(); 
     
    271234     * If in nodeway mode, insert the node into the way. 
    272235     */ 
    273236    @Override public void mouseReleased(MouseEvent e) { 
     237        if (e.getButton() == MouseEvent.BUTTON3) { 
     238            WaySegment seg = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate); 
     239            snapHelper.fixToSegment(seg); 
     240            return; 
     241        } 
    274242        if (e.getButton() != MouseEvent.BUTTON1) 
    275243            return; 
    276244        if(!Main.map.mapView.isActiveLayerDrawable()) 
     
    308276            n = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate); 
    309277        } 
    310278 
    311         if (n != null) { 
     279        if (n != null && !snapHelper.isActive()) { 
    312280            // user clicked on node 
    313281            if (selection.isEmpty() || wayIsFinished) { 
    314282                // select the clicked node and do nothing else 
     
    327295                return; 
    328296            } 
    329297        } else { 
    330             // no node found in clicked area 
    331             n = new Node(Main.map.mapView.getLatLon(e.getX(), e.getY())); 
     298            EastNorth newEN; 
     299            if (n!=null) { 
     300                // project found node to snapping line 
     301                newEN = snapHelper.getSnapPoint(n.getEastNorth());  
     302            } else { // n==null 
     303                // no node found in clicked area 
     304                EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY()); 
     305                newEN = snapOn ? snapHelper.getSnapPoint(mouseEN) : mouseEN; 
     306            } 
     307            snapHelper.unsetFixedMode(); 
     308            n=new Node(newEN); //create node at clicked point 
    332309            if (n.getCoor().isOutSideWorld()) { 
    333310                JOptionPane.showMessageDialog( 
    334311                        Main.parent, 
     
    343320            cmds.add(new AddCommand(n)); 
    344321 
    345322            if (!ctrl) { 
    346                 // Insert the node into all the nearby way segments 
    347                 List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(e.getPoint(), OsmPrimitive.isSelectablePredicate); 
    348                 Map<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>(); 
    349                 for (WaySegment ws : wss) { 
    350                     List<Integer> is; 
    351                     if (insertPoints.containsKey(ws.way)) { 
    352                         is = insertPoints.get(ws.way); 
    353                     } else { 
    354                         is = new ArrayList<Integer>(); 
    355                         insertPoints.put(ws.way, is); 
    356                     } 
    357  
    358                     is.add(ws.lowerIndex); 
    359                 } 
    360  
    361                 Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>(); 
    362  
    363                 for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) { 
    364                     Way w = insertPoint.getKey(); 
    365                     List<Integer> is = insertPoint.getValue(); 
    366  
    367                     Way wnew = new Way(w); 
    368  
    369                     pruneSuccsAndReverse(is); 
    370                     for (int i : is) { 
    371                         segSet.add( 
    372                                 Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1)))); 
    373                     } 
    374                     for (int i : is) { 
    375                         wnew.addNode(i + 1, n); 
    376                     } 
    377  
    378                     // If ALT is pressed, a new way should be created and that new way should get 
    379                     // selected. This works everytime unless the ways the nodes get inserted into 
    380                     // are already selected. This is the case when creating a self-overlapping way 
    381                     // but pressing ALT prevents this. Therefore we must de-select the way manually 
    382                     // here so /only/ the new way will be selected after this method finishes. 
    383                     if(alt) { 
    384                         newSelection.add(insertPoint.getKey()); 
    385                     } 
    386  
    387                     cmds.add(new ChangeCommand(insertPoint.getKey(), wnew)); 
    388                     replacedWays.add(insertPoint.getKey()); 
    389                     reuseWays.add(wnew); 
    390                 } 
    391  
    392                 adjustNode(segSet, n); 
    393             } 
     323                    // Insert the node into all the nearby way segments 
     324                    List<WaySegment> wss = Main.map.mapView.getNearestWaySegments( 
     325                            Main.map.mapView.getPoint(n), OsmPrimitive.isSelectablePredicate); 
     326                    insertNodeIntoAllNearbySegments(wss, n, newSelection, cmds, replacedWays, reuseWays); 
     327                    }     
    394328        } 
    395  
     329        // now "n" is newly created or reused node that shoud be added to some way 
     330         
    396331        // This part decides whether or not a "segment" (i.e. a connection) is made to an 
    397332        // existing node. 
    398333 
     
    542477        removeHighlighting(); 
    543478        redrawIfRequired(); 
    544479    } 
     480     
     481    private void insertNodeIntoAllNearbySegments(List<WaySegment> wss, Node n, Collection<OsmPrimitive> newSelection, Collection<Command> cmds, ArrayList<Way> replacedWays, ArrayList<Way> reuseWays) { 
     482        Map<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>(); 
     483        for (WaySegment ws : wss) { 
     484            List<Integer> is; 
     485            if (insertPoints.containsKey(ws.way)) { 
     486                is = insertPoints.get(ws.way); 
     487            } else { 
     488                is = new ArrayList<Integer>(); 
     489                insertPoints.put(ws.way, is); 
     490            } 
    545491 
     492            is.add(ws.lowerIndex); 
     493        } 
     494 
     495        Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>(); 
     496 
     497        for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) { 
     498            Way w = insertPoint.getKey(); 
     499            List<Integer> is = insertPoint.getValue(); 
     500 
     501            Way wnew = new Way(w); 
     502 
     503            pruneSuccsAndReverse(is); 
     504            for (int i : is) { 
     505                segSet.add( 
     506                        Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1)))); 
     507            } 
     508            for (int i : is) { 
     509                wnew.addNode(i + 1, n); 
     510            } 
     511 
     512            // If ALT is pressed, a new way should be created and that new way should get 
     513            // selected. This works everytime unless the ways the nodes get inserted into 
     514            // are already selected. This is the case when creating a self-overlapping way 
     515            // but pressing ALT prevents this. Therefore we must de-select the way manually 
     516            // here so /only/ the new way will be selected after this method finishes. 
     517            if(alt) { 
     518                newSelection.add(insertPoint.getKey()); 
     519            } 
     520 
     521            cmds.add(new ChangeCommand(insertPoint.getKey(), wnew)); 
     522            replacedWays.add(insertPoint.getKey()); 
     523            reuseWays.add(wnew); 
     524        } 
     525 
     526        adjustNode(segSet, n); 
     527    } 
     528 
     529 
    546530    /** 
    547531     * Prevent creation of ways that look like this: <----> 
    548532     * This happens if users want to draw a no-exit-sideway from the main way like this: 
     
    641625 
    642626        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected(); 
    643627 
    644         Node selectedNode = null; 
    645         Way selectedWay = null; 
    646628        Node currentMouseNode = null; 
    647629        mouseOnExistingNode = null; 
    648630        mouseOnExistingWays = new HashSet<Way>(); 
     
    674656            currentMouseEastNorth = mv.getEastNorth(mousePos.x, mousePos.y); 
    675657        } 
    676658 
     659        determineCurrentBaseNodeAndPreviousNode(selection); 
     660        if (previousNode == null) snapHelper.noSnapNow(); 
     661         
     662        if (currentBaseNode == null || currentBaseNode == currentMouseNode) 
     663            return; // Don't create zero length way segments. 
     664 
     665        // find out the distance, in metres, between the base point and the mouse cursor 
     666        LatLon mouseLatLon = mv.getProjection().eastNorth2latlon(currentMouseEastNorth); 
     667        distance = currentBaseNode.getCoor().greatCircleDistance(mouseLatLon); 
     668 
     669        double hdg = Math.toDegrees(currentBaseNode.getEastNorth() 
     670                .heading(currentMouseEastNorth)); 
     671        if (previousNode != null) { 
     672            angle = hdg - Math.toDegrees(previousNode.getEastNorth() 
     673                    .heading(currentBaseNode.getEastNorth())); 
     674            angle += angle < 0 ? 360 : 0; 
     675        } 
     676         
     677        if (snapOn) snapHelper.checkAngleSnapping(currentMouseEastNorth,angle); 
     678         
     679        Main.map.statusLine.setAngle(angle); 
     680        Main.map.statusLine.setHeading(hdg); 
     681        Main.map.statusLine.setDist(distance); 
     682        // Now done in redrawIfRequired() 
     683        //updateStatusLine(); 
     684    } 
     685     
     686     
     687    /**  
     688     * Helper function that sets fields currentBaseNode and previousNode  
     689     * @param selection  
     690     * uses also lastUsedNode field 
     691     */ 
     692    private void determineCurrentBaseNodeAndPreviousNode(Collection<OsmPrimitive>  selection) { 
     693        Node selectedNode = null; 
     694        Way selectedWay = null; 
    677695        for (OsmPrimitive p : selection) { 
    678696            if (p instanceof Node) { 
    679697                if (selectedNode != null) return; 
     
    683701                selectedWay = (Way) p; 
    684702            } 
    685703        } 
     704        // we are here, if not more than 1 way or node is selected, 
    686705 
    687706        // the node from which we make a connection 
    688707        currentBaseNode = null; 
    689         Node previousNode = null; 
     708        previousNode = null; 
    690709 
    691710        if (selectedNode == null) { 
    692711            if (selectedWay == null) 
     
    700719        } else if (selectedWay == null) { 
    701720            currentBaseNode = selectedNode; 
    702721        } else if (!selectedWay.isDeleted()) { // fix #7118 
    703             if (selectedNode == selectedWay.getNode(0) || selectedNode == selectedWay.getNode(selectedWay.getNodesCount()-1)) { 
     722            if (selectedNode == selectedWay.getNode(0)){ 
    704723                currentBaseNode = selectedNode; 
     724                if (selectedWay.getNodesCount()>1) previousNode = selectedWay.getNode(1); 
    705725            } 
     726            if (selectedNode == selectedWay.lastNode()) { 
     727                currentBaseNode = selectedNode; 
     728                if (selectedWay.getNodesCount()>1)  
     729                    previousNode = selectedWay.getNode(selectedWay.getNodesCount()-2); 
     730            } 
    706731        } 
     732    } 
    707733 
    708         if (currentBaseNode == null || currentBaseNode == currentMouseNode) 
    709             return; // Don't create zero length way segments. 
    710734 
    711         // find out the distance, in metres, between the base point and the mouse cursor 
    712         LatLon mouseLatLon = mv.getProjection().eastNorth2latlon(currentMouseEastNorth); 
    713         distance = currentBaseNode.getCoor().greatCircleDistance(mouseLatLon); 
    714  
    715         double hdg = Math.toDegrees(currentBaseNode.getEastNorth() 
    716                 .heading(currentMouseEastNorth)); 
    717         if (previousNode != null) { 
    718             angle = hdg - Math.toDegrees(previousNode.getEastNorth() 
    719                     .heading(currentBaseNode.getEastNorth())); 
    720             angle += angle < 0 ? 360 : 0; 
    721         } 
    722  
    723         Main.map.statusLine.setAngle(angle); 
    724         Main.map.statusLine.setHeading(hdg); 
    725         Main.map.statusLine.setDist(distance); 
    726         // Now done in redrawIfRequired() 
    727         //updateStatusLine(); 
    728     } 
    729  
    730735    /** 
    731736     * Repaint on mouse exit so that the helper line goes away. 
    732737     */ 
     
    734739        if(!Main.map.mapView.isActiveLayerDrawable()) 
    735740            return; 
    736741        mousePos = e.getPoint(); 
     742        snapHelper.noSnapNow(); 
    737743        Main.map.mapView.repaint(); 
    738744    } 
    739745 
     
    741747     * @return If the node is the end of exactly one way, return this. 
    742748     *  <code>null</code> otherwise. 
    743749     */ 
    744     public Way getWayForNode(Node n) { 
     750    public static Way getWayForNode(Node n) { 
    745751        Way way = null; 
    746752        for (Way w : Utils.filteredCollection(n.getReferrers(), Way.class)) { 
    747753            if (!w.isUsable() || w.getNodesCount() < 1) { 
     
    852858    static double det(double a, double b, double c, double d) { 
    853859        return a * d - b * c; 
    854860    } 
     861/** 
     862     * Takes the data from computeHelperLine to determine which ways/nodes should be highlighted 
     863     * (if feature enabled). Also sets the target cursor if appropriate. 
     864     */ 
     865    private void addHighlighting() { 
     866        removeHighlighting(); 
     867        // if ctrl key is held ("no join"), don't highlight anything 
     868        if (ctrl) { 
     869            Main.map.mapView.setNewCursor(cursor, this); 
     870            return; 
     871        } 
    855872 
     873        // This happens when nothing is selected, but we still want to highlight the "target node" 
     874        if (mouseOnExistingNode == null && getCurrentDataSet().getSelected().size() == 0 
     875                && mousePos != null) { 
     876            mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate); 
     877        } 
     878 
     879        if (mouseOnExistingNode != null) { 
     880            Main.map.mapView.setNewCursor(cursorJoinNode, this); 
     881            // We also need this list for the statusbar help text 
     882            oldHighlights.add(mouseOnExistingNode); 
     883            if(drawTargetHighlight) { 
     884                mouseOnExistingNode.setHighlighted(true); 
     885        } 
     886            return; 
     887        } 
     888 
     889        // Insert the node into all the nearby way segments 
     890        if (mouseOnExistingWays.size() == 0) { 
     891            Main.map.mapView.setNewCursor(cursor, this); 
     892            return; 
     893    } 
     894 
     895        Main.map.mapView.setNewCursor(cursorJoinWay, this); 
     896 
     897        // We also need this list for the statusbar help text 
     898        oldHighlights.addAll(mouseOnExistingWays); 
     899        if (!drawTargetHighlight) return; 
     900        for (Way w : mouseOnExistingWays) { 
     901            w.setHighlighted(true); 
     902        } 
     903    } 
     904 
     905    /** 
     906     * Removes target highlighting from primitives 
     907     */ 
     908    private void removeHighlighting() { 
     909        for(OsmPrimitive prim : oldHighlights) { 
     910            prim.setHighlighted(false); 
     911        } 
     912        oldHighlights = new HashSet<OsmPrimitive>(); 
     913    } 
     914     
    856915    public void paint(Graphics2D g, MapView mv, Bounds box) { 
    857         if (!drawHelperLine || wayIsFinished || shift) return; 
    858  
    859916        // sanity checks 
    860917        if (Main.map.mapView == null) return; 
    861918        if (mousePos == null) return; 
     
    865922 
    866923        // don't draw line if mouse is outside window 
    867924        if (!Main.map.mapView.getBounds().contains(mousePos)) return; 
    868  
     925         
    869926        Graphics2D g2 = g; 
    870         g2.setColor(selectedColor); 
    871         g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); 
     927        if (snapOn) snapHelper.draw(g2,mv); 
     928        if (!drawHelperLine || wayIsFinished || shift) return; 
     929         
     930        if (!snapHelper.isActive()) { // else use color and stoke from  snapHelper.draw 
     931            g2.setColor(selectedColor); 
     932            g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); 
     933        } 
    872934        GeneralPath b = new GeneralPath(); 
    873935        Point p1=mv.getPoint(currentBaseNode); 
    874936        Point p2=mv.getPoint(currentMouseEastNorth); 
     
    9751037        super.destroy(); 
    9761038        Main.unregisterActionShortcut(extraShortcut); 
    9771039    } 
    978      
     1040 
    9791041    public static class BackSpaceAction extends AbstractAction { 
    9801042 
    9811043        @Override 
     
    10011063    } 
    10021064    } 
    10031065 
     1066    private class SnapHelper { 
     1067        private boolean active; // snapping is activa for current mouse position 
     1068        private boolean fixed; // snap angle is fixed 
     1069        private boolean absoluteFix; // snap angle is absolute 
     1070        EastNorth dir2; 
     1071        EastNorth projected; 
     1072        String labelText; 
     1073        double lastAngle; 
     1074                 
     1075        double snapAngles[];  
     1076        double snapAngleTolerance;  
     1077         
     1078        double pe,pn; // (pe,pn) - direction of snapping line 
     1079        double e0,n0; // (e0,n0) - origin of snapping line 
     1080         
     1081        final String fixFmt="%d "+tr("FIX"); 
     1082        Color snapHelperColor; 
     1083        private Stroke normalStroke; 
     1084        private Stroke helperStroke; 
     1085 
     1086        private  void init() { 
     1087            snapOn=false; 
     1088            fixed=false; absoluteFix=false; 
     1089             
     1090            Collection<String> angles = Main.pref.getCollection("draw.anglesnap.angles", Arrays.asList("0","90","270")); 
     1091            snapAngles = new double[angles.size()]; 
     1092            int i=0; 
     1093            for (String s: angles) { 
     1094                try { 
     1095                    snapAngles[i] = Double.parseDouble(s); 
     1096                } catch (NumberFormatException e) { 
     1097                    System.err.println("Warning: incorrect number in draw.anglesnap.angles preferences: "+s); 
     1098                    snapAngles[i]=0; 
     1099                }  
     1100                i++; 
     1101            } 
     1102            snapAngleTolerance = Main.pref.getDouble("draw.anglesnap.tolerance", 10.0); 
     1103 
     1104            normalStroke = new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 
     1105            snapHelperColor = Main.pref.getColor("draw.anglesnap.color", Color.ORANGE); 
     1106             
     1107            float dash1[] = { 4.0f }; 
     1108            helperStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, 
     1109                         BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f); 
     1110 
     1111        } 
     1112         
     1113        private void noSnapNow() { 
     1114            active=false;  
     1115            dir2=null; projected=null; 
     1116            labelText=null; 
     1117        } 
     1118 
     1119        private void draw(Graphics2D g2, MapView mv) { 
     1120            if (!active) return; 
     1121            Point p1=mv.getPoint(currentBaseNode); 
     1122            Point p2=mv.getPoint(dir2); 
     1123            Point p3=mv.getPoint(projected); 
     1124            GeneralPath b; 
     1125             
     1126            g2.setColor(snapHelperColor); 
     1127            g2.setStroke(helperStroke); 
     1128             
     1129            b = new GeneralPath(); 
     1130            if (absoluteFix) { 
     1131                b.moveTo(p2.x,p2.y);  
     1132                b.lineTo(2*p1.x-p2.x,2*p1.y-p2.y); // bi-directional line 
     1133            } else { 
     1134                b.moveTo(p2.x,p2.y); 
     1135                b.lineTo(p3.x,p3.y); 
     1136            }  
     1137            g2.draw(b); 
     1138 
     1139            g2.setColor(selectedColor); 
     1140            g2.setStroke(normalStroke); 
     1141            b = new GeneralPath(); 
     1142            b.moveTo(p1.x,p1.y);  
     1143            b.lineTo(p3.x,p3.y); 
     1144            g2.draw(b); 
     1145             
     1146            g2.drawString(labelText, p3.x-5, p3.y+20); 
     1147            g2.setStroke(normalStroke); 
     1148            g2.drawOval(p3.x-5, p3.y-5, 10, 10); // projected point 
     1149             
     1150            g2.setColor(snapHelperColor); 
     1151            g2.setStroke(helperStroke); 
     1152             
     1153        } 
     1154         
     1155        private double getAngleDelta(double a, double b) { 
     1156            double delta = Math.abs(a-b); 
     1157            if (delta>180) return 360-delta; else return delta; 
     1158        } 
     1159 
     1160        /* If mouse position is close to line at 15-30-45-... angle, remembers this direction 
     1161         */ 
     1162        private void checkAngleSnapping(EastNorth currentEN, double angle) { 
     1163            if (previousNode==null) return; 
     1164             
     1165            double nearestAngle; 
     1166            if (fixed) { 
     1167                nearestAngle = lastAngle; // if direction is fixed 
     1168                active=true; 
     1169            } else {  
     1170                nearestAngle = getNearestAngle(angle); 
     1171                lastAngle = nearestAngle; 
     1172                active = Math.abs(nearestAngle-180)>1e-3 && getAngleDelta(nearestAngle,angle)<snapAngleTolerance; 
     1173            } 
     1174             
     1175            if (active) { 
     1176                double de,dn,l, phi; 
     1177 
     1178                EastNorth prev = previousNode.getEastNorth(); 
     1179                EastNorth p0 = currentBaseNode.getEastNorth(); 
     1180                 
     1181                e0=p0.east(); n0=p0.north(); 
     1182 
     1183                if (fixed) { 
     1184                    if (absoluteFix) labelText = "="; 
     1185                                else labelText = String.format(fixFmt, (int) nearestAngle); 
     1186                } else labelText = String.format("%d", (int) nearestAngle); 
     1187                 
     1188                if (absoluteFix) { 
     1189                    de=0; dn=1;  
     1190                } else { 
     1191                    de = e0-prev.east(); 
     1192                    dn = n0-prev.north(); 
     1193                    l=Math.hypot(de, dn); 
     1194                    de/=l; dn/=l; 
     1195                } 
     1196                 
     1197                phi=nearestAngle*Math.PI/180; 
     1198                // (pe,pn) - direction of snapping line 
     1199                pe = de*Math.cos(phi) + dn*Math.sin(phi);   
     1200                pn = -de*Math.sin(phi) + dn*Math.cos(phi); 
     1201                double scale = 20*Main.map.mapView.getDist100Pixel(); 
     1202                dir2 = new EastNorth( e0+scale*pe, n0+scale*pn); 
     1203                getSnapPoint(currentEN);  
     1204           } else { 
     1205                noSnapNow(); 
     1206           } 
     1207        } 
     1208         
     1209        private EastNorth getSnapPoint(EastNorth p) { 
     1210            if (!active) return p; 
     1211            double de=p.east()-e0; 
     1212            double dn=p.north()-n0; 
     1213            double l = de*pe+dn*pn; 
     1214            if (!absoluteFix && l<1e-5) {active=false; return p; } //  do not go backward! 
     1215            return projected = new EastNorth(e0+l*pe, n0+l*pn); 
     1216        } 
     1217 
     1218        private void fixDirection() { 
     1219            if (active) { 
     1220                fixed=true; 
     1221            } 
     1222        } 
     1223         
     1224        private void unsetFixedMode() { 
     1225            fixed=false; absoluteFix=false; 
     1226            lastAngle=0; 
     1227            active=false; 
     1228        } 
     1229         
     1230        private void nextSnapMode() { 
     1231            if (snapOn) { 
     1232                if (fixed) { snapOn=false; unsetFixedMode(); } 
     1233                else fixDirection(); 
     1234            } else { 
     1235                snapOn=true; 
     1236                unsetFixedMode(); 
     1237            } 
     1238        } 
     1239 
     1240        private boolean isActive() { 
     1241            return active; 
     1242        } 
     1243 
     1244        private double getNearestAngle(double angle) { 
     1245            double delta,minDelta=1e5, bestAngle=0.0; 
     1246            for (int i=0; i<snapAngles.length; i++) { 
     1247                delta = getAngleDelta(angle,snapAngles[i]); 
     1248                if (delta<minDelta) { 
     1249                    minDelta=delta; 
     1250                    bestAngle=snapAngles[i]; 
     1251                } 
     1252            } 
     1253            if (Math.abs(bestAngle-360)<1e-3) bestAngle=0; 
     1254            return bestAngle; 
     1255        } 
     1256 
     1257        private void fixToSegment(WaySegment seg) { 
     1258            if (seg==null) return; 
     1259            double hdg = seg.getFirstNode().getEastNorth().heading(seg.getSecondNode().getEastNorth()); 
     1260            hdg=Math.toDegrees(hdg); 
     1261            if (hdg<0) hdg+=360; 
     1262            if (hdg>360) hdg=hdg-360; 
     1263            System.out.println("fix to segment "+hdg); 
     1264             
     1265            fixed=true; 
     1266            absoluteFix=true; 
     1267            lastAngle=hdg; 
     1268        } 
     1269 
     1270         
     1271    } 
    10041272}