Ignore:
Timestamp:
2014-03-24T23:46:39+01:00 (10 years ago)
Author:
Don-vip
Message:

fix #8431 - improve "align in circle" action (patch by Balaitous):

  • by default nodes are evenly distributed
  • to prevent evenly distribution of a node, user have to explicitly select it
File:
1 edited

Legend:

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

    r6919 r6933  
    1010import java.util.Collection;
    1111import java.util.Collections;
     12import java.util.HashSet;
    1213import java.util.LinkedList;
    1314import java.util.List;
     
    100101    }
    101102
     103   
     104    /**
     105     * Perform AlignInCircle action.
     106     *
     107     * A fixed node is a node for which it is forbidden to change the angle relative to center of the circle.
     108     * All other nodes are uniformly distributed.
     109     *
     110     * Case 1: One unclosed way.
     111     * --> allow action, and align selected way nodes
     112     * If nodes contained by this way are selected, there are fix.
     113     * If nodes outside from the way are selected there are ignored.
     114     *
     115     * Case 2: One or more ways are selected and can be joined into a polygon
     116     * --> allow action, and align selected ways nodes
     117     * If 1 node outside of way is selected, it became center
     118     * If 1 node outside and 1 node inside are selected there define center and radius
     119     * If no outside node and 2 inside nodes are selected those 2 nodes define diameter
     120     * In all other cases outside nodes are ignored
     121     * In all cases, selected nodes are fix, nodes with more than one referrers are fix
     122     * (first referrer is the selected way)
     123     *
     124     * Case 3: Only nodes are selected
     125     * --> Align these nodes, all are fix
     126     */
    102127    @Override
    103128    public void actionPerformed(ActionEvent e) {
     
    107132        Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
    108133        List<Node> nodes = new LinkedList<Node>();
     134        // fixNodes: All nodes for which the angle relative to center should not be modified
     135        HashSet<Node> fixNodes = new HashSet<Node>();
    109136        List<Way> ways = new LinkedList<Way>();
    110137        EastNorth center = null;
    111138        double radius = 0;
    112         boolean isPolygon = false;
    113139       
    114140        for (OsmPrimitive osm : sel) {
     
    120146        }
    121147
    122         // special case if no single nodes are selected and exactly one way is:
    123         // then use the way's nodes
    124         if ((nodes.size() <= 2) && checkWaysArePolygon(ways)) {
    125             // some more special combinations:
    126             // When is selected node that is part of the way, then make a regular polygon, selected
    127             // node doesn't move.
    128             // I haven't got better idea, how to activate that function.
    129             //
    130             // When one way and one node is selected, set center to position of that node.
    131             // When one more node, part of the way, is selected, set the radius equal to the
    132             // distance between two nodes.
    133             if (nodes.size() >= 1) {
    134                 boolean[] isContained = new boolean[nodes.size()];
    135                 for(int i = 0; i < nodes.size(); i++) {
    136                     Node n = nodes.get(i);
    137                     isContained[i] = false;
    138                     for(Way way: ways)
    139                         if(way.containsNode(n)) {
    140                             isContained[i] = true;
    141                             break;
    142                         }
    143                 }
    144                 if(nodes.size() == 1) {
    145                     if(!isContained[0])
    146                         center = nodes.get(0).getEastNorth();
    147                 } else {
    148                     if(!isContained[0] && !isContained[1]) {
    149                         // 2 nodes outside of way, can't choose one as center
    150                         new Notification(
    151                                 tr("Please select only one node as center."))
    152                                 .setIcon(JOptionPane.INFORMATION_MESSAGE)
    153                                 .setDuration(Notification.TIME_SHORT)
    154                                 .show();
    155                         return;
    156                     } else if (!isContained[0] || !isContained[1]) {
    157                         // 1 node inside and 1 outside, outside is center, inside node define radius
    158                         center = nodes.get(isContained[0] ? 1 : 0).getEastNorth();
    159                         radius = distance(nodes.get(0).getEastNorth(), nodes.get(1).getEastNorth());
    160                     } else {
    161                         // 2 nodes inside, define diameter
    162                         EastNorth en0 = nodes.get(0).getEastNorth();
    163                         EastNorth en1 = nodes.get(1).getEastNorth();
    164                         center = new EastNorth((en0.east() + en1.east()) / 2, (en0.north() + en1.north()) / 2);
    165                         radius = distance(en0, en1) / 2;
     148        if (ways.size() == 1 && ways.get(0).firstNode() != ways.get(0).lastNode()) {
     149            // Case 1
     150            Way w = ways.get(0);
     151            fixNodes.add(w.firstNode());
     152            fixNodes.add(w.lastNode());
     153            fixNodes.addAll(nodes);
     154            fixNodes.addAll(collectNodesWithExternReferers(ways));
     155            // Temporary closed way used to reorder nodes
     156            Way closedWay = new Way(w);
     157            closedWay.addNode(w.firstNode());
     158            ArrayList<Way> usedWays = new ArrayList<Way>(1);
     159            usedWays.add(closedWay);
     160            nodes = collectNodesAnticlockwise(usedWays);
     161        } else if (!ways.isEmpty() && checkWaysArePolygon(ways)) {
     162            // Case 2
     163            ArrayList<Node> inside = new ArrayList<Node>();
     164            ArrayList<Node> outside = new ArrayList<Node>();
     165           
     166            for(Node n: nodes) {
     167                boolean isInside = false;
     168                for(Way w: ways) {
     169                    if(w.getNodes().contains(n)) {
     170                        isInside = true;
     171                        break;
    166172                    }
    167173                }
    168             }
     174                if(isInside)
     175                    inside.add(n);
     176                else
     177                    outside.add(n);
     178            }
     179           
     180            if(outside.size() == 1 && inside.isEmpty()) {
     181                center = outside.get(0).getEastNorth();
     182            } else if(outside.size() == 1 && inside.size() == 1) {
     183                center = outside.get(0).getEastNorth();
     184                radius = distance(center, inside.get(0).getEastNorth());
     185            } else if(inside.size() == 2 && outside.isEmpty()) {
     186                // 2 nodes inside, define diameter
     187                EastNorth en0 = inside.get(0).getEastNorth();
     188                EastNorth en1 = inside.get(1).getEastNorth();
     189                center = new EastNorth((en0.east() + en1.east()) / 2, (en0.north() + en1.north()) / 2);
     190                radius = distance(en0, en1) / 2;
     191            }
     192           
     193            fixNodes.addAll(inside);
     194            fixNodes.addAll(collectNodesWithExternReferers(ways));
    169195            nodes = collectNodesAnticlockwise(ways);
    170             isPolygon = true;
    171         }
    172 
    173         if (nodes.size() < 4) {
     196            if (nodes.size() < 4) {
     197                new Notification(
     198                        tr("Not enough nodes in selected ways."))
     199                .setIcon(JOptionPane.INFORMATION_MESSAGE)
     200                .setDuration(Notification.TIME_SHORT)
     201                .show();
     202                return;
     203            }
     204        } else if (ways.isEmpty() && nodes.size() > 3) {
     205            // Case 3
     206            fixNodes.addAll(nodes);
     207            // No need to reorder nodes since all are fix
     208        } else {
     209            // Invalid action
    174210            new Notification(
    175211                    tr("Please select at least four nodes."))
     
    185221        }
    186222        // Node "center" now is central to all selected nodes.
    187 
    188         if (!isPolygon) {
    189             // Then reorder them based on heading from the center point
    190             List<Node> newNodes = new LinkedList<Node>();
    191             while (!nodes.isEmpty()) {
    192                 double maxHeading = -1.0;
    193                 Node maxNode = null;
    194                 for (Node n : nodes) {
    195                     double heading = center.heading(n.getEastNorth());
    196                     if (heading > maxHeading) {
    197                         maxHeading = heading;
    198                         maxNode = n;
    199                     }
    200                 }
    201                 newNodes.add(maxNode);
    202                 nodes.remove(maxNode);
    203             }
    204             nodes = newNodes;
    205         }
    206223   
    207224        // Now calculate the average distance to each node from the
    208         // centre. This method is ok as long as distances are short
     225        // center. This method is ok as long as distances are short
    209226        // relative to the distance from the N or S poles.
    210227        if (radius == 0) {
     
    225242        int startPosition = 0;
    226243        for(startPosition = 0; startPosition < nodeCount; startPosition++)
    227             if(isFixNode(nodes.get(startPosition % nodeCount), sel)) break;
     244            if(fixNodes.contains(nodes.get(startPosition % nodeCount))) break;
    228245        int i = startPosition; // Start position for current arc
    229246        int j; // End position for current arc
    230247        while(i < startPosition + nodeCount) {
    231248            for(j = i + 1; j < startPosition + nodeCount; j++)
    232                 if(isFixNode(nodes.get(j % nodeCount), sel)) break;
     249                if(fixNodes.contains(nodes.get(j % nodeCount))) break;
    233250            Node first = nodes.get(i % nodeCount);
    234251            PolarCoor pcFirst = new PolarCoor(first.getEastNorth(), center, 0);
     
    258275    }
    259276
     277    /**
     278     * Collect all nodes with more than one referrer.
     279     * @param ways Ways from witch nodes are selected
     280     * @return List of nodes with more than one referrer
     281     */
     282    private List<Node> collectNodesWithExternReferers(List<Way> ways) {
     283        ArrayList<Node> withReferrers = new ArrayList<Node>();
     284        for(Way w: ways)
     285            for(Node n: w.getNodes())
     286                if(n.getReferrers().size() > 1)
     287                    withReferrers.add(n);
     288        return withReferrers;
     289    }
     290   
    260291    /**
    261292     * Assuming all ways can be joined into polygon, create an ordered list of node.
     
    324355    }
    325356   
    326     /**
    327      * Test if angle of a node can be change.
    328      * @param n Node
    329      * @param sel Selection which action is apply
    330      * @return true is this node does't have a fix angle
    331      */
    332     private boolean isFixNode(Node n, Collection<OsmPrimitive> sel) {
    333         List<OsmPrimitive> referrers = n.getReferrers();
    334         if(referrers.isEmpty()) return false;
    335         if(sel.contains(n) || referrers.size() > 1 || !sel.contains(referrers.get(0))) return true;
    336         return false;
    337     }
    338    
    339357    @Override
    340358    protected void updateEnabledState() {
Note: See TracChangeset for help on using the changeset viewer.