Ticket #8431: bug8431.patch

File bug8431.patch, 11.9 KB (added by Balaitous, 12 years ago)

patch from r6892 Order nodes anticlockwise before align in circle, cleanup old code for regular polygon (replaced by regular arc since r6892)

  • src/org/openstreetmap/josm/actions/AlignInCircleAction.java

     
    66
    77import java.awt.event.ActionEvent;
    88import java.awt.event.KeyEvent;
    9 import java.math.BigDecimal;
    10 import java.math.MathContext;
     9import java.util.ArrayList;
    1110import java.util.Collection;
    12 import java.util.HashSet;
     11import java.util.Collections;
    1312import java.util.LinkedList;
    1413import java.util.List;
    15 import java.util.Set;
    1614
    1715import javax.swing.JOptionPane;
    1816
     
    3634 * @author Matthew Newton
    3735 * @author Petr Dlouhý
    3836 * @author Teemu Koskinen
     37 * @author Alain Delplanque
    3938 */
    4039public final class AlignInCircleAction extends JosmAction {
    4140
     
    110109        List<Way> ways = new LinkedList<Way>();
    111110        EastNorth center = null;
    112111        double radius = 0;
    113         boolean regular = false;
    114 
     112        boolean isPolygon = false;
     113       
    115114        for (OsmPrimitive osm : sel) {
    116115            if (osm instanceof Node) {
    117116                nodes.add((Node) osm);
     
    131130            // When one way and one node is selected, set center to position of that node.
    132131            // When one more node, part of the way, is selected, set the radius equal to the
    133132            // distance between two nodes.
    134             if (nodes.size() == 1 && ways.size() == 1) {
    135                 // Regular polygons are allowed only if there is just one way
    136                 // Should be remove regular are now default for all nodes with no more than 1 referrer.
    137                 Way way = ways.get(0);
    138                 if (nodes.size() == 1 && way.containsNode(nodes.get(0)) && allowRegularPolygon(way.getNodes()))
    139                     regular = true;
    140             }
    141133            if (nodes.size() >= 1) {
    142134                boolean[] isContained = new boolean[nodes.size()];
    143135                for(int i = 0; i < nodes.size(); i++) {
     
    174166                    }
    175167                }
    176168            }
    177             nodes.clear();
    178 
    179             for(Way way: ways)
    180                 for (Node n : way.getNodes()) {
    181                     if (!nodes.contains(n)) {
    182                         nodes.add(n);
    183                     }
    184                 }
     169            nodes = collectNodesAnticlockwise(ways);
     170            isPolygon = true;
    185171        }
    186172
    187173        if (nodes.size() < 4) {
     
    193179            return;
    194180        }
    195181
    196         // Reorder the nodes if they didn't come from a single way
    197         if (ways.size() != 1) {
    198             // First calculate the average point
     182        if (center == null) {
     183            // Compute the centroid of nodes
     184            center = Geometry.getCentroid(nodes);
     185        }
     186        // Node "center" now is central to all selected nodes.
    199187
    200             BigDecimal east = BigDecimal.ZERO;
    201             BigDecimal north = BigDecimal.ZERO;
    202 
    203             for (Node n : nodes) {
    204                 BigDecimal x = new BigDecimal(n.getEastNorth().east());
    205                 BigDecimal y = new BigDecimal(n.getEastNorth().north());
    206                 east = east.add(x, MathContext.DECIMAL128);
    207                 north = north.add(y, MathContext.DECIMAL128);
    208             }
    209             BigDecimal nodesSize = new BigDecimal(nodes.size());
    210             east = east.divide(nodesSize, MathContext.DECIMAL128);
    211             north = north.divide(nodesSize, MathContext.DECIMAL128);
    212 
    213             EastNorth average = new EastNorth(east.doubleValue(), north.doubleValue());
     188        if (!isPolygon) {
     189            // Then reorder them based on heading from the center point
    214190            List<Node> newNodes = new LinkedList<Node>();
    215 
    216             // Then reorder them based on heading from the average point
    217191            while (!nodes.isEmpty()) {
    218192                double maxHeading = -1.0;
    219193                Node maxNode = null;
    220194                for (Node n : nodes) {
    221                     double heading = average.heading(n.getEastNorth());
     195                    double heading = center.heading(n.getEastNorth());
    222196                    if (heading > maxHeading) {
    223197                        maxHeading = heading;
    224198                        maxNode = n;
     
    227201                newNodes.add(maxNode);
    228202                nodes.remove(maxNode);
    229203            }
    230 
    231204            nodes = newNodes;
    232205        }
    233 
    234         if (center == null) {
    235             // Compute the centroid of nodes
    236             center = Geometry.getCentroid(nodes);
    237         }
    238         // Node "center" now is central to all selected nodes.
    239 
     206   
    240207        // Now calculate the average distance to each node from the
    241208        // centre. This method is ok as long as distances are short
    242209        // relative to the distance from the N or S poles.
     
    251218
    252219        Collection<Command> cmds = new LinkedList<Command>();
    253220
    254         PolarCoor pc;
    255 
    256         if (regular) { // Make a regular polygon
    257             double angle = Math.PI * 2 / nodes.size();
    258             pc = new PolarCoor(nodes.get(0).getEastNorth(), center, 0);
    259 
    260             if (pc.angle > (new PolarCoor(nodes.get(1).getEastNorth(), center, 0).angle)) {
    261                 angle *= -1;
    262             }
    263 
    264             pc.radius = radius;
    265             for (Node n : nodes) {
    266                 EastNorth no = pc.toEastNorth();
    267                 cmds.add(new MoveCommand(n, no.east() - n.getEastNorth().east(), no.north() - n.getEastNorth().north()));
    268                 pc.angle += angle;
    269             }
    270         } else { // Move each node to that distance from the center.
    271             int nodeCount = nodes.size();
    272             // Search first fixed node
    273             int startPosition = 0;
    274             for(startPosition = 0; startPosition < nodeCount; startPosition++)
    275                 if(isFixNode(nodes.get(startPosition % nodeCount), sel)) break;
    276             int i = startPosition; // Start position for current arc
    277             int j; // End position for current arc
    278             while(i < startPosition + nodeCount) {
    279                 for(j = i + 1; j < startPosition + nodeCount; j++)
    280                     if(isFixNode(nodes.get(j % nodeCount), sel)) break;
    281                 Node first = nodes.get(i % nodeCount);
    282                 PolarCoor pcFirst = new PolarCoor(first.getEastNorth(), center, 0);
    283                 pcFirst.radius = radius;
    284                 cmds.add(pcFirst.createMoveCommand(first));
    285                 if(j > i + 1) {
    286                     double delta;
    287                     if(j == i + nodeCount) {
    288                         delta = 2 * Math.PI / nodeCount;
    289                     } else {
    290                         PolarCoor pcLast = new PolarCoor(nodes.get(j % nodeCount).getEastNorth(), center, 0);
    291                         delta = pcLast.angle - pcFirst.angle;
    292                         if(delta < 0) // Assume each PolarCoor.angle is in range ]-pi; pi]
    293                             delta +=  2*Math.PI;
    294                         delta /= j - i;
    295                     }
    296                     for(int k = i+1; k < j; k++) {
    297                         PolarCoor p = new PolarCoor(radius, pcFirst.angle + (k-i)*delta, center, 0);
    298                         cmds.add(p.createMoveCommand(nodes.get(k % nodeCount)));
    299                     }
     221        // Move each node to that distance from the center.
     222        // Nodes that are not "fix" will be adjust making regular arcs.
     223        int nodeCount = nodes.size();
     224        // Search first fixed node
     225        int startPosition = 0;
     226        for(startPosition = 0; startPosition < nodeCount; startPosition++)
     227            if(isFixNode(nodes.get(startPosition % nodeCount), sel)) break;
     228        int i = startPosition; // Start position for current arc
     229        int j; // End position for current arc
     230        while(i < startPosition + nodeCount) {
     231            for(j = i + 1; j < startPosition + nodeCount; j++)
     232                if(isFixNode(nodes.get(j % nodeCount), sel)) break;
     233            Node first = nodes.get(i % nodeCount);
     234            PolarCoor pcFirst = new PolarCoor(first.getEastNorth(), center, 0);
     235            pcFirst.radius = radius;
     236            cmds.add(pcFirst.createMoveCommand(first));
     237            if(j > i + 1) {
     238                double delta;
     239                if(j == i + nodeCount) {
     240                    delta = 2 * Math.PI / nodeCount;
     241                } else {
     242                    PolarCoor pcLast = new PolarCoor(nodes.get(j % nodeCount).getEastNorth(), center, 0);
     243                    delta = pcLast.angle - pcFirst.angle;
     244                    if(delta < 0) // Assume each PolarCoor.angle is in range ]-pi; pi]
     245                        delta +=  2*Math.PI;
     246                    delta /= j - i;
    300247                }
    301                 i = j; // Update start point for next iteration
     248                for(int k = i+1; k < j; k++) {
     249                    PolarCoor p = new PolarCoor(radius, pcFirst.angle + (k-i)*delta, center, 0);
     250                    cmds.add(p.createMoveCommand(nodes.get(k % nodeCount)));
     251                }
    302252            }
     253            i = j; // Update start point for next iteration
    303254        }
    304255       
    305256        Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Circle"), cmds));
     
    307258    }
    308259
    309260    /**
     261     * Assuming all ways can be joined into polygon, create an ordered list of node.
     262     * @param ways List of ways to be joined
     263     * @return Nodes anticlockwise ordered
     264     */
     265    private List<Node> collectNodesAnticlockwise(List<Way> ways) {
     266        ArrayList<Node> nodes = new ArrayList<Node>();
     267        Node firstNode = ways.get(0).firstNode();
     268        Node lastNode = null;
     269        Way lastWay = null;
     270        while(firstNode != lastNode) {
     271            if(lastNode == null) lastNode = firstNode;
     272            for(Way way: ways) {
     273                if(way == lastWay) continue;
     274                if(way.firstNode() == lastNode) {
     275                    List<Node> wayNodes = way.getNodes();
     276                    for(int i = 0; i < wayNodes.size() - 1; i++)
     277                        nodes.add(wayNodes.get(i));
     278                    lastNode = way.lastNode();
     279                    lastWay = way;
     280                    break;
     281                }
     282                if(way.lastNode() == lastNode) {
     283                    List<Node> wayNodes = way.getNodes();
     284                    for(int i = wayNodes.size() - 1; i > 0; i--)
     285                        nodes.add(wayNodes.get(i));
     286                    lastNode = way.firstNode();
     287                    lastWay = way;
     288                    break;
     289                }
     290            }
     291        }
     292        // Check if nodes are in anticlockwise order
     293        int nc = nodes.size();
     294        double area = 0;
     295        for(int i = 0; i < nc; i++) {
     296            EastNorth p1 = nodes.get(i).getEastNorth();
     297            EastNorth p2 = nodes.get((i+1) % nc).getEastNorth();
     298            area += p1.east()*p2.north() - p2.east()*p1.north();
     299        }
     300        if(area < 0)
     301            Collections.reverse(nodes);
     302        return nodes;
     303    }
     304
     305    /**
    310306     * Check if one or more nodes are outside of download area
    311307     * @param nodes Nodes to check
    312308     * @return true if action can be done
     
    351347    }
    352348
    353349    /**
    354      * Determines if a regular polygon is allowed to be created with the given nodes collection.
    355      * @param nodes The nodes collection to check.
    356      * @return true if all nodes in the given collection are referred by the same object, and no other one (see #8431)
    357      */
    358     protected static boolean allowRegularPolygon(Collection<Node> nodes) {
    359         Set<OsmPrimitive> allReferrers = new HashSet<OsmPrimitive>();
    360         for (Node n : nodes) {
    361             List<OsmPrimitive> referrers = n.getReferrers();
    362             if (referrers.size() > 1 || (allReferrers.addAll(referrers) && allReferrers.size() > 1)) {
    363                 return false;
    364             }
    365         }
    366         return true;
    367     }
    368 
    369     /**
    370350     * Determines if ways can be joined into a polygon.
    371351     * @param ways The ways collection to check
    372352     * @return true if all ways can be joined into a polygon