Ticket #9081: bug9081.patch

File bug9081.patch, 11.7 KB (added by Balaitous, 6 years ago)
  • src/org/openstreetmap/josm/actions/AlignInLineAction.java

     
    2828 * Aligns all selected nodes into a straight line (useful for
    2929 * roads that should be straight, but have side roads and
    3030 * therefore need multiple nodes)
     31 *
     32 * Case 1: Only ways selected, align each ways taking care of intersection.
     33 * Case 2: Single node selected, align this node relative to the surrounding nodes.
     34 * Case 3: Single node and ways selected, align this node relative to the surrounding nodes only parts of selected ways.
     35 * Case 4: Only nodes selected, align these nodes respect to the line passing through the most distant nodes.
    3136 *
    3237 * @author Matthew Newton
    3338 */
     
    109114            .show();
    110115    }
    111116
    112     private static int indexWrap(int size, int i) {
    113         i = i % size; // -2 % 5 = -2, -7 % 5 = -2, -5 % 5 = 0
    114         if (i < 0) {
    115             i = size + i;
    116         }
    117         return i;
    118     }
    119     // get the node in w at index i relative to refI
    120     private static Node getNodeRelative(Way w, int refI, int i) {
    121         int absI = indexWrap(w.getNodesCount(), refI + i);
    122         if(w.isClosed() && refI + i < 0) {
    123             absI--;  // node duplicated in closed ways
    124         }
    125         return w.getNode(absI);
    126     }
    127 
    128117    /**
    129118     * The general algorithm here is to find the two selected nodes
    130119     * that are furthest apart, and then to align all other selected
     
    140129        if (!isEnabled())
    141130            return;
    142131
    143         Node[] anchors = new Node[2]; // oh, java I love you so much..
    144 
    145132        List<Node> selectedNodes = new ArrayList<Node>(getCurrentDataSet().getSelectedNodes());
    146133        Collection<Way> selectedWays = getCurrentDataSet().getSelectedWays();
    147         List<Node> nodes = new ArrayList<Node>();
     134        Command cmd = null;
    148135
    149136        //// Decide what to align based on selection:
    150137
    151138        /// Only ways selected -> For each way align their nodes taking care of intersection
    152139        if(selectedNodes.isEmpty() && !selectedWays.isEmpty()) {
    153             alignMultiWay(selectedWays);
    154             return;
     140            cmd = alignMultiWay(selectedWays);
    155141        }
    156         /// More than 3 nodes selected -> align those nodes
    157         else if(selectedNodes.size() >= 3) {
    158             nodes.addAll(selectedNodes);
    159             // use the nodes furthest apart as anchors
    160             nodePairFurthestApart(nodes, anchors);
    161         }
    162         /// One node selected -> align that node to the relevant neighbors
    163         else if (selectedNodes.size() == 1) {
    164             Node n = selectedNodes.iterator().next();
    165 
    166             Way w = null;
    167             if(selectedWays.size() == 1) {
    168                 w = selectedWays.iterator().next();
    169                 if (!w.containsNode(n))
    170                     // warning
    171                     return;
    172             } else {
    173                 List<Way> refWays = OsmPrimitive.getFilteredList(n.getReferrers(), Way.class);
    174                 if (refWays.size() == 1) { // node used in only one way
    175                     w = refWays.iterator().next();
    176                 }
    177             }
    178             if (w == null || w.getNodesCount() < 3)
    179                 // warning, need at least 3 nodes
     142        /// Only 1 node selected -> align this node relative to referers way
     143        else if(selectedNodes.size() == 1) {
     144            Node selectedNode = selectedNodes.get(0);
     145            List<Way> involvedWays = null;
     146            if(selectedWays.isEmpty())
     147                /// No selected way, all way containing this node are used
     148                involvedWays = OsmPrimitive.getFilteredList(selectedNode.getReferrers(), Way.class);
     149            else
     150                /// Selected way, use only these ways
     151                involvedWays = new ArrayList<Way>(selectedWays);
     152            List<Line> lines = getInvolvedLines(selectedNode, involvedWays);
     153            if(lines.size() > 2 || lines.isEmpty()) {
     154                showWarning();
    180155                return;
    181 
    182             // Find anchors
    183             int nodeI = w.getNodes().indexOf(n);
    184             // End-node in non-circular way selected: align this node with the two neighbors.
    185             if ((nodeI == 0 || nodeI == w.getNodesCount()-1) && !w.isClosed()) {
    186                 int direction = nodeI == 0 ? 1 : -1;
    187                 anchors[0] = w.getNode(nodeI + direction);
    188                 anchors[1] = w.getNode(nodeI + direction*2);
    189             } else {
    190                 // o---O---o
    191                 anchors[0] = getNodeRelative(w, nodeI, 1);
    192                 anchors[1] = getNodeRelative(w, nodeI, -1);
    193156            }
    194             nodes.add(n);
     157            cmd = alignSingleNode(selectedNodes.get(0), lines);
    195158        }
    196 
    197         if (anchors[0] == null || anchors[1] == null) {
    198             showWarning();
    199             return;
     159        /// More than 3 nodes selected -> align those nodes
     160        else if(selectedNodes.size() >= 3) {
     161            cmd = alignOnlyNodes(selectedNodes);
    200162        }
    201163
    202 
    203         Collection<Command> cmds = new ArrayList<Command>(nodes.size());
    204 
    205         createAlignNodesCommands(anchors, nodes, cmds);
    206 
    207164        // Do it!
    208         Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Line"), cmds));
    209         Main.map.repaint();
    210     }
    211 
    212     private void createAlignNodesCommands(Node[] anchors, Collection<Node> nodes, Collection<Command> cmds) {
    213         Node nodea = anchors[0];
    214         Node nodeb = anchors[1];
    215 
    216         // The anchors are aligned per definition
    217         nodes.remove(nodea);
    218         nodes.remove(nodeb);
    219 
    220         // Find out co-ords of A and B
    221         double ax = nodea.getEastNorth().east();
    222         double ay = nodea.getEastNorth().north();
    223         double bx = nodeb.getEastNorth().east();
    224         double by = nodeb.getEastNorth().north();
    225 
    226         // OK, for each node to move, work out where to move it!
    227         for (Node n : nodes) {
    228             // Get existing co-ords of node to move
    229             double nx = n.getEastNorth().east();
    230             double ny = n.getEastNorth().north();
    231 
    232             if (ax == bx) {
    233                 // Special case if AB is vertical...
    234                 nx = ax;
    235             } else if (ay == by) {
    236                 // ...or horizontal
    237                 ny = ay;
    238             } else {
    239                 // Otherwise calculate position by solving y=mx+c
    240                 double m1 = (by - ay) / (bx - ax);
    241                 double c1 = ay - (ax * m1);
    242                 double m2 = (-1) / m1;
    243                 double c2 = n.getEastNorth().north() - (n.getEastNorth().east() * m2);
    244 
    245                 nx = (c2 - c1) / (m1 - m2);
    246                 ny = (m1 * nx) + c1;
    247             }
    248             double newX = nx - n.getEastNorth().east();
    249             double newY = ny - n.getEastNorth().north();
    250             // Add the command to move the node to its new position.
    251             cmds.add(new MoveCommand(n, newX, newY));
     165        if(cmd != null) {
     166            Main.main.undoRedo.add(cmd);
     167            Main.map.repaint();
     168        } else {
     169            showWarning();
    252170        }
    253171    }
    254172
    255173    /**
     174     * Align nodes in case that only nodes are selected
     175     * @param nodes Nodes to be aligned
     176     * @return Command that perform action
     177     */
     178    private Command alignOnlyNodes(List<Node> nodes) {
     179        Node[] anchors = new Node[2]; // oh, java I love you so much..
     180        // use the nodes furthest apart as anchors
     181        nodePairFurthestApart(nodes, anchors);
     182        Collection<Command> cmds = new ArrayList<Command>(nodes.size());
     183        Line line = new Line(anchors[0], anchors[1]);
     184        for(Node node: nodes)
     185            if(node != anchors[0] && node != anchors[1])
     186                cmds.add(line.projectionCommand(node));
     187        return new SequenceCommand(tr("Align Nodes in Line"), cmds);
     188    }
     189   
     190    /**
    256191     * Align way in case of multiple way #6819
    257192     * @param ways Collection of way to align
     193     * @return Command that perform action
    258194     */
    259     private void alignMultiWay(Collection<Way> ways) {
     195    private Command alignMultiWay(Collection<Way> ways) {
    260196        // Collect all nodes and compute line equation
    261197        HashSet<Node> nodes = new HashSet<Node>();
    262198        HashMap<Way, Line> lines = new HashMap<Way, Line>();
    263199        for(Way w: ways) {
    264200            if(w.firstNode() == w.lastNode()) {
    265201                showWarning(tr("Can not align a polygon. Abort."));
    266                 return;
     202                return null;
    267203            }
    268204            nodes.addAll(w.getNodes());
    269205            lines.put(w, new Line(w));
     
    284220                Command cmd = lines.get(referers.get(0)).intersectionCommand(n, lines.get(referers.get(1)));
    285221                if(cmd == null) {
    286222                    showWarning(tr("Two parallels ways found. Abort."));
    287                     return;
     223                    return null;
    288224                }
    289225                cmds.add(cmd);
    290226            }
    291227            else {
    292228                showWarning(tr("Intersection of three or more ways can not be solved. Abort."));
    293                 return;
     229                return null;
    294230            }
    295231        }
    296         Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Line"), cmds));
    297         Main.map.repaint();
     232        return new SequenceCommand(tr("Align Nodes in Line"), cmds);
    298233    }
    299234
    300235    /**
     236     * Get lines useful to do alignment of a single node
     237     * @param node Node to be aligned
     238     * @param refWays Ways where useful lines will be searched
     239     * @return List of useful lines
     240     */
     241    private List<Line> getInvolvedLines(Node node, List<Way> refWays) {
     242        ArrayList<Line> lines = new ArrayList<Line>();
     243        for(Way way: refWays) {
     244            List<Node> nodes = way.getNodes();
     245            for(int i = 1; i < nodes.size()-1; i++)
     246                if(nodes.get(i) == node)
     247                    lines.add(new Line(nodes.get(i-1), nodes.get(i+1)));
     248        }
     249        return lines;
     250    }
     251
     252    /**
     253     * Align a single node relative to a set of lines #9081
     254     * @param node Node to be aligned
     255     * @param lines Lines to align node on
     256     * @return Command that perform action
     257     */
     258    private Command alignSingleNode(Node node, List<Line> lines) {
     259        if(lines.size() == 1)
     260            return lines.get(0).projectionCommand(node);
     261        else if(lines.size() == 2)
     262            return lines.get(0).intersectionCommand(node,  lines.get(1));
     263        return null;
     264    }
     265
     266    /**
    301267     * Class that describe a line
    302268     */
    303269    private class Line {
     
    312278         */
    313279        private double xM, yM; // Coordinate of a point of the line
    314280
    315         /**
    316          * Init a line equation from a way.
    317          * @param way
    318          */
    319         public Line(Way way) {
    320             xM = way.firstNode().getEastNorth().getX();
    321             yM = way.firstNode().getEastNorth().getY();
    322             double xB = way.lastNode().getEastNorth().getX();
    323             double yB = way.lastNode().getEastNorth().getY();
     281        public Line(Node first, Node last) {
     282            xM = first.getEastNorth().getX();
     283            yM = first.getEastNorth().getY();
     284            double xB = last.getEastNorth().getX();
     285            double yB = last.getEastNorth().getY();
    324286            a = yB - yM;
    325287            b = xM - xB;
    326288            double norm = Math.sqrt(a*a + b*b);
     
    331293            b /= norm;
    332294            c = -(a*xM + b*yM);
    333295        }
     296       
     297        /**
     298         * Init a line equation from a way using its extremities.
     299         * @param way
     300         */
     301        public Line(Way way) {
     302            this(way.firstNode(), way.lastNode());
     303        }
    334304
    335305        /**
    336306         * Orthogonal projection of a node N along this line.