Changeset 6919 in josm


Ignore:
Timestamp:
2014-03-21T15:39:16+01:00 (5 years ago)
Author:
Don-vip
Message:

fix #8431, fix #9839 - fix issues with "align nodes in circle" action (patch by Balaitous)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/AlignInCircleAction.java

    r6892 r6919  
    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;
     
    3735 * @author Petr Dlouhý
    3836 * @author Teemu Koskinen
     37 * @author Alain Delplanque
    3938 */
    4039public final class AlignInCircleAction extends JosmAction {
     
    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) {
     
    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()];
     
    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
     
    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
    199 
    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());
     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.
     187
     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;
     
    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
     
    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                     }
    300                 }
    301                 i = j; // Update start point for next iteration
    302             }
     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;
     247                }
     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                }
     252            }
     253            i = j; // Update start point for next iteration
    303254        }
    304255       
    305256        Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Circle"), cmds));
    306257        Main.map.repaint();
     258    }
     259
     260    /**
     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;
    307303    }
    308304
     
    349345    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
    350346        setEnabled(selection != null && !selection.isEmpty());
    351     }
    352 
    353     /**
    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;
    367347    }
    368348
Note: See TracChangeset for help on using the changeset viewer.