Index: trunk/src/org/openstreetmap/josm/actions/AlignInLineAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/AlignInLineAction.java	(revision 6960)
+++ trunk/src/org/openstreetmap/josm/actions/AlignInLineAction.java	(revision 6961)
@@ -19,4 +19,5 @@
 import org.openstreetmap.josm.command.MoveCommand;
 import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -30,4 +31,9 @@
  * therefore need multiple nodes)
  *
+ * Case 1: Only ways selected, align each ways taking care of intersection.
+ * Case 2: Single node selected, align this node relative to the surrounding nodes.
+ * Case 3: Single node and ways selected, align this node relative to the surrounding nodes only parts of selected ways.
+ * Case 4: Only nodes selected, align these nodes respect to the line passing through the most distant nodes.
+ *
  * @author Matthew Newton
  */
@@ -41,4 +47,25 @@
                 Shortcut.registerShortcut("tools:alignline", tr("Tool: {0}", tr("Align Nodes in Line")), KeyEvent.VK_L, Shortcut.DIRECT), true);
         putValue("help", ht("/Action/AlignInLine"));
+    }
+
+    /**
+     * InvalidSelection exception has to be raised when action can't be perform
+     */
+    private class InvalidSelection extends Exception {
+
+        /**
+         * Create an InvalidSelection exception with default message
+         */
+        public InvalidSelection() {
+            super(tr("Please select at least three nodes."));
+        }
+
+        /**
+         * Create an InvalidSelection exception with specific message
+         * @param msg Message that will be display to the user
+         */
+        public InvalidSelection(String msg) {
+            super(msg);
+        }
     }
 
@@ -53,5 +80,5 @@
         if(resultOut.length < 2)
             throw new IllegalArgumentException();
-        
+
         Node nodea = null;
         Node nodeb = null;
@@ -100,37 +127,4 @@
     }
 
-    private void showWarning() {
-        showWarning(tr("Please select at least three nodes."));
-    }
-
-    private void showWarning(String msg) {
-        new Notification(msg)
-            .setIcon(JOptionPane.INFORMATION_MESSAGE)
-            .show();
-    }
-
-    private static int indexWrap(int size, int i) {
-        i = i % size; // -2 % 5 = -2, -7 % 5 = -2, -5 % 5 = 0
-        if (i < 0) {
-            i = size + i;
-        }
-        return i;
-    }
-    // get the node in w at index i relative to refI
-    private static Node getNodeRelative(Way w, int refI, int i) {
-        int absI = indexWrap(w.getNodesCount(), refI + i);
-        if(w.isClosed() && refI + i < 0) {
-            absI--;  // node duplicated in closed ways
-        }
-        return w.getNode(absI);
-    }
-
-    /**
-     * The general algorithm here is to find the two selected nodes
-     * that are furthest apart, and then to align all other selected
-     * nodes onto the straight line between these nodes.
-     */
-
-
     /**
      * Operation depends on the selected objects:
@@ -141,114 +135,71 @@
             return;
 
+        List<Node> selectedNodes = new ArrayList<Node>(getCurrentDataSet().getSelectedNodes());
+        List<Way> selectedWays = new ArrayList<Way>(getCurrentDataSet().getSelectedWays());
+
+        try {
+            Command cmd = null;
+            //// Decide what to align based on selection:
+
+            /// Only ways selected -> For each way align their nodes taking care of intersection
+            if(selectedNodes.isEmpty() && !selectedWays.isEmpty()) {
+                cmd = alignMultiWay(selectedWays);
+            }
+            /// Only 1 node selected -> align this node relative to referers way
+            else if(selectedNodes.size() == 1) {
+                Node selectedNode = selectedNodes.get(0);
+                List<Way> involvedWays = null;
+                if(selectedWays.isEmpty())
+                    /// No selected way, all way containing this node are used
+                    involvedWays = OsmPrimitive.getFilteredList(selectedNode.getReferrers(), Way.class);
+                else
+                    /// Selected way, use only these ways
+                    involvedWays = selectedWays;
+                List<Line> lines = getInvolvedLines(selectedNode, involvedWays);
+                if(lines.size() > 2 || lines.isEmpty())
+                    throw new InvalidSelection();
+                cmd = alignSingleNode(selectedNodes.get(0), lines);
+            }
+            /// More than 3 nodes selected -> align those nodes
+            else if(selectedNodes.size() >= 3) {
+                cmd = alignOnlyNodes(selectedNodes);
+            }
+            /// All others cases are invalid
+            else {
+                throw new InvalidSelection();
+            }
+
+            // Do it!
+            Main.main.undoRedo.add(cmd);
+            Main.map.repaint();
+
+        } catch (InvalidSelection except) {
+            new Notification(except.getMessage())
+                .setIcon(JOptionPane.INFORMATION_MESSAGE)
+                .show();
+        }
+    }
+
+    /**
+     * Align nodes in case that only nodes are selected
+     *
+     * The general algorithm here is to find the two selected nodes
+     * that are furthest apart, and then to align all other selected
+     * nodes onto the straight line between these nodes.
+
+     * @param nodes Nodes to be aligned
+     * @return Command that perform action
+     * @throws InvalidSelection
+     */
+    private Command alignOnlyNodes(List<Node> nodes) throws InvalidSelection {
         Node[] anchors = new Node[2]; // oh, java I love you so much..
-
-        List<Node> selectedNodes = new ArrayList<Node>(getCurrentDataSet().getSelectedNodes());
-        Collection<Way> selectedWays = getCurrentDataSet().getSelectedWays();
-        List<Node> nodes = new ArrayList<Node>();
-
-        //// Decide what to align based on selection:
-
-        /// Only ways selected -> For each way align their nodes taking care of intersection
-        if(selectedNodes.isEmpty() && !selectedWays.isEmpty()) {
-            alignMultiWay(selectedWays);
-            return;
-        }
-        /// More than 3 nodes selected -> align those nodes
-        else if(selectedNodes.size() >= 3) {
-            nodes.addAll(selectedNodes);
-            // use the nodes furthest apart as anchors
-            nodePairFurthestApart(nodes, anchors);
-        }
-        /// One node selected -> align that node to the relevant neighbors
-        else if (selectedNodes.size() == 1) {
-            Node n = selectedNodes.iterator().next();
-
-            Way w = null;
-            if(selectedWays.size() == 1) {
-                w = selectedWays.iterator().next();
-                if (!w.containsNode(n))
-                    // warning
-                    return;
-            } else {
-                List<Way> refWays = OsmPrimitive.getFilteredList(n.getReferrers(), Way.class);
-                if (refWays.size() == 1) { // node used in only one way
-                    w = refWays.iterator().next();
-                }
-            }
-            if (w == null || w.getNodesCount() < 3)
-                // warning, need at least 3 nodes
-                return;
-
-            // Find anchors
-            int nodeI = w.getNodes().indexOf(n);
-            // End-node in non-circular way selected: align this node with the two neighbors.
-            if ((nodeI == 0 || nodeI == w.getNodesCount()-1) && !w.isClosed()) {
-                int direction = nodeI == 0 ? 1 : -1;
-                anchors[0] = w.getNode(nodeI + direction);
-                anchors[1] = w.getNode(nodeI + direction*2);
-            } else {
-                // o---O---o
-                anchors[0] = getNodeRelative(w, nodeI, 1);
-                anchors[1] = getNodeRelative(w, nodeI, -1);
-            }
-            nodes.add(n);
-        }
-
-        if (anchors[0] == null || anchors[1] == null) {
-            showWarning();
-            return;
-        }
-
-
+        // use the nodes furthest apart as anchors
+        nodePairFurthestApart(nodes, anchors);
         Collection<Command> cmds = new ArrayList<Command>(nodes.size());
-
-        createAlignNodesCommands(anchors, nodes, cmds);
-
-        // Do it!
-        Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Line"), cmds));
-        Main.map.repaint();
-    }
-
-    private void createAlignNodesCommands(Node[] anchors, Collection<Node> nodes, Collection<Command> cmds) {
-        Node nodea = anchors[0];
-        Node nodeb = anchors[1];
-
-        // The anchors are aligned per definition
-        nodes.remove(nodea);
-        nodes.remove(nodeb);
-
-        // Find out co-ords of A and B
-        double ax = nodea.getEastNorth().east();
-        double ay = nodea.getEastNorth().north();
-        double bx = nodeb.getEastNorth().east();
-        double by = nodeb.getEastNorth().north();
-
-        // OK, for each node to move, work out where to move it!
-        for (Node n : nodes) {
-            // Get existing co-ords of node to move
-            double nx = n.getEastNorth().east();
-            double ny = n.getEastNorth().north();
-
-            if (ax == bx) {
-                // Special case if AB is vertical...
-                nx = ax;
-            } else if (ay == by) {
-                // ...or horizontal
-                ny = ay;
-            } else {
-                // Otherwise calculate position by solving y=mx+c
-                double m1 = (by - ay) / (bx - ax);
-                double c1 = ay - (ax * m1);
-                double m2 = (-1) / m1;
-                double c2 = n.getEastNorth().north() - (n.getEastNorth().east() * m2);
-
-                nx = (c2 - c1) / (m1 - m2);
-                ny = (m1 * nx) + c1;
-            }
-            double newX = nx - n.getEastNorth().east();
-            double newY = ny - n.getEastNorth().north();
-            // Add the command to move the node to its new position.
-            cmds.add(new MoveCommand(n, newX, newY));
-        }
+        Line line = new Line(anchors[0], anchors[1]);
+        for(Node node: nodes)
+            if(node != anchors[0] && node != anchors[1])
+                cmds.add(line.projectionCommand(node));
+        return new SequenceCommand(tr("Align Nodes in Line"), cmds);
     }
 
@@ -256,14 +207,14 @@
      * Align way in case of multiple way #6819
      * @param ways Collection of way to align
-     */
-    private void alignMultiWay(Collection<Way> ways) {
+     * @return Command that perform action
+     * @throws InvalidSelection
+     */
+    private Command alignMultiWay(Collection<Way> ways) throws InvalidSelection {
         // Collect all nodes and compute line equation
         HashSet<Node> nodes = new HashSet<Node>();
         HashMap<Way, Line> lines = new HashMap<Way, Line>();
         for(Way w: ways) {
-            if(w.firstNode() == w.lastNode()) {
-                showWarning(tr("Can not align a polygon. Abort."));
-                return;
-            }
+            if(w.firstNode() == w.lastNode())
+                throw new InvalidSelection(tr("Can not align a polygon. Abort."));
             nodes.addAll(w.getNodes());
             lines.put(w, new Line(w));
@@ -283,21 +234,87 @@
             else if(referers.size() == 2) {
                 Command cmd = lines.get(referers.get(0)).intersectionCommand(n, lines.get(referers.get(1)));
-                if(cmd == null) {
-                    showWarning(tr("Two parallels ways found. Abort."));
-                    return;
-                }
                 cmds.add(cmd);
             }
-            else {
-                showWarning(tr("Intersection of three or more ways can not be solved. Abort."));
-                return;
-            }
-        }
-        Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Line"), cmds));
-        Main.map.repaint();
-    }
-
-    /**
-     * Class that describe a line
+            else
+                throw new InvalidSelection(tr("Intersection of three or more ways can not be solved. Abort."));
+        }
+        return new SequenceCommand(tr("Align Nodes in Line"), cmds);
+    }
+
+    /**
+     * Get lines useful to do alignment of a single node
+     * @param node Node to be aligned
+     * @param refWays Ways where useful lines will be searched
+     * @return List of useful lines
+     * @throws InvalidSelection
+     */
+    private List<Line> getInvolvedLines(Node node, List<Way> refWays) throws InvalidSelection {
+        ArrayList<Line> lines = new ArrayList<Line>();
+        ArrayList<Node> neighbors = new ArrayList<Node>();
+        for(Way way: refWays) {
+            List<Node> nodes = way.getNodes();
+            neighbors.clear();
+            for(int i = 1; i < nodes.size()-1; i++)
+                if(nodes.get(i) == node) {
+                    System.out.printf("Find 2 neighbors\n");
+                    neighbors.add(nodes.get(i-1));
+                    neighbors.add(nodes.get(i+1));
+                    //lines.add(new Line(nodes.get(i-1), nodes.get(i+1)));
+                }
+            if(neighbors.size() == 0)
+                continue;
+            else if(neighbors.size() == 2)
+                // Non self crossing
+                lines.add(new Line(neighbors.get(0), neighbors.get(1)));
+            else if(neighbors.size() == 4) {
+                // Self crossing, have to make 2 lines with 4 neighbors
+                // see #9081 comment 6
+                EastNorth c = node.getEastNorth();
+                double[] angle = new double[4];
+                for(int i = 0; i < 4; i++) {
+                    EastNorth p = neighbors.get(i).getEastNorth();
+                    angle[i] = Math.atan2(p.north() - c.north(), p.east() - c.east());
+                }
+                double[] deltaAngle = new double[3];
+                for(int i = 0; i < 3; i++) {
+                    deltaAngle[i] = angle[i+1] - angle[0];
+                    if(deltaAngle[i] < 0)
+                        deltaAngle[i] += 2*Math.PI;
+                }
+                int nb = 0;
+                if(deltaAngle[1] < deltaAngle[0]) nb++;
+                if(deltaAngle[2] < deltaAngle[0]) nb++;
+                if(nb == 1) {
+                    // Align along [neighbors[0], neighbors[1]] and [neighbors[0], neighbors[2]]
+                    lines.add(new Line(neighbors.get(0), neighbors.get(1)));
+                    lines.add(new Line(neighbors.get(2), neighbors.get(3)));
+                } else {
+                    // Align along [neighbors[0], neighbors[2]] and [neighbors[1], neighbors[3]]
+                    lines.add(new Line(neighbors.get(0), neighbors.get(2)));
+                    lines.add(new Line(neighbors.get(1), neighbors.get(3)));
+                }
+            } else
+                throw new InvalidSelection();
+        }
+        return lines;
+    }
+
+    /**
+     * Align a single node relative to a set of lines #9081
+     * @param node Node to be aligned
+     * @param lines Lines to align node on
+     * @return Command that perform action
+     * @throws InvalidSelection
+     */
+    private Command alignSingleNode(Node node, List<Line> lines) throws InvalidSelection {
+        if(lines.size() == 1)
+            return lines.get(0).projectionCommand(node);
+        else if(lines.size() == 2)
+            return lines.get(0).intersectionCommand(node,  lines.get(1));
+        throw new InvalidSelection();
+    }
+
+    /**
+     * Class that represent a line
      */
     private class Line {
@@ -307,25 +324,27 @@
          * Such as a^2 + b^2 = 1, ie (-b, a) is a unit vector of line
          */
-        private double a, b, c; // Line equation ax+by+c=0
-        /**
-         * (xM, yM) are coordinate of a point of the line
-         */
-        private double xM, yM; // Coordinate of a point of the line 
-
-        /**
-         * Init a line equation from a way.
-         * @param way
-         */
-        public Line(Way way) {
-            xM = way.firstNode().getEastNorth().getX();
-            yM = way.firstNode().getEastNorth().getY();
-            double xB = way.lastNode().getEastNorth().getX();
-            double yB = way.lastNode().getEastNorth().getY();
+        private double a, b, c;
+        /**
+         * (xM, yM) are coordinates of a point of the line
+         */
+        private double xM, yM;
+
+        /**
+         * Init a line by 2 nodes.
+         * @param first On point of the line
+         * @param last Other point of the line
+         * @throws InvalidSelection
+         */
+        public Line(Node first, Node last) throws InvalidSelection {
+            xM = first.getEastNorth().getX();
+            yM = first.getEastNorth().getY();
+            double xB = last.getEastNorth().getX();
+            double yB = last.getEastNorth().getY();
             a = yB - yM;
             b = xM - xB;
             double norm = Math.sqrt(a*a + b*b);
-            if (norm == 0) {
-                norm = 1;
-            }
+            if (norm == 0)
+                // Nodes have same coordinates !
+                throw new InvalidSelection();
             a /= norm;
             b /= norm;
@@ -334,4 +353,13 @@
 
         /**
+         * Init a line equation from a way.
+         * @param way Use extremity of this way to compute line equation
+         * @throws InvalidSelection
+         */
+        public Line(Way way) throws InvalidSelection {
+            this(way.firstNode(), way.lastNode());
+        }
+
+        /**
          * Orthogonal projection of a node N along this line.
          * @param n Node to be projected
@@ -347,9 +375,12 @@
          * @param n Node to move to the intersection
          * @param other Second line for intersection
-         * @return The command that move the node or null if line are parallels
-         */
-        public Command intersectionCommand(Node n, Line other) {
+         * @return The command that move the node
+         * @throws InvalidSelection
+         */
+        public Command intersectionCommand(Node n, Line other) throws InvalidSelection {
             double d = this.a * other.b - other.a * this.b;
-            if(d == 0) return null;
+            if(Math.abs(d) < 10e-6)
+                // parallels lines
+                throw new InvalidSelection(tr("Two parallels ways found. Abort."));
             double x = (this.b * other.c - other.b * this.c) / d;
             double y = (other.a * this.c - this.a * other.c) / d;
