Ticket #6694: AngleSnappingv3.patch

File AngleSnappingv3.patch, 26.9 KB (added by akks, 12 years 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}