Ignore:
Timestamp:
2014-03-29T00:45:51+01:00 (6 years ago)
Author:
Don-vip
Message:

fix #5922, see #8431 - value of createcircle.nodecount not always applied (patch by Balaitous)

File:
1 edited

Legend:

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

    r6830 r6942  
    88import java.awt.event.KeyEvent;
    99import java.util.ArrayList;
     10import java.util.Arrays;
    1011import java.util.Collection;
     12import java.util.Comparator;
    1113import java.util.LinkedList;
    1214import java.util.List;
     
    1820import org.openstreetmap.josm.command.ChangeCommand;
    1921import org.openstreetmap.josm.command.Command;
    20 import org.openstreetmap.josm.command.DeleteCommand;
    2122import org.openstreetmap.josm.command.SequenceCommand;
    2223import org.openstreetmap.josm.data.coor.EastNorth;
     
    2627import org.openstreetmap.josm.data.osm.Way;
    2728import org.openstreetmap.josm.gui.Notification;
     29import org.openstreetmap.josm.tools.Geometry;
    2830import org.openstreetmap.josm.tools.Shortcut;
    2931
     
    3335 * - Useful for roundabouts
    3436 *
    35  * Note: If a way is selected, it is changed. If nodes are selected a new way is created.
    36  *       So if you've got a way with nodes it makes a difference between running this on the way or the nodes!
    37  *
     37 * Notes:
     38 *   * If a way is selected, it is changed. If nodes are selected a new way is created.
     39 *     So if you've got a way with nodes it makes a difference between running this on the way or the nodes!
     40 *   * The existing nodes are retained, and additional nodes are inserted regularly
     41 *     to achieve the desired number of nodes (`createcircle.nodecount`).
    3842 * BTW: Someone might want to implement projection corrections for this...
    3943 *
     
    4246 * @author Henry Loenwind
    4347 * @author Sebastian Masch
     48 * @author Alain Delplanque
    4449 */
    4550public final class CreateCircleAction extends JosmAction {
     
    5560    }
    5661
    57     private double calcang(double xc, double yc, double x, double y) {
    58         // calculate the angle from xc|yc to x|y
    59         if (xc == x && yc == y)
    60             return 0; // actually invalid, but we won't have this case in this context
    61         double yd = Math.abs(y - yc);
    62         if (yd == 0 && xc < x)
    63             return 0;
    64         if (yd == 0 && xc > x)
    65             return Math.PI;
    66         double xd = Math.abs(x - xc);
    67         double a = Math.atan2(xd, yd);
    68         if (y > yc) {
    69             a = Math.PI - a;
    70         }
    71         if (x < xc) {
    72             a = -a;
    73         }
    74         a = 1.5*Math.PI + a;
    75         if (a < 0) {
    76             a += 2*Math.PI;
    77         }
    78         if (a >= 2*Math.PI) {
    79             a -= 2*Math.PI;
    80         }
    81         return a;
    82     }
    83 
     62    /**
     63     * Distributes nodes according to the algorithm of election with largest remainder.
     64     * @param angles Array of PolarNode ordered by increasing angles
     65     * @param nodesCount Number of nodes to be distributed
     66     * @return Array of number of nodes to put in each arc
     67     */
     68    private int[] distributeNodes(PolarNode[] angles, int nodesCount) {
     69        int[] count = new int[angles.length];
     70        double[] width = new double[angles.length];
     71        double[] remainder = new double[angles.length];
     72        for(int i = 0; i < angles.length; i++) {
     73            width[i] = angles[(i+1) % angles.length].a - angles[i].a;
     74            if(width[i] < 0)
     75                width[i] += 2*Math.PI;
     76        }
     77        int assign = 0;
     78        for(int i = 0; i < angles.length; i++) {
     79            double part = width[i] / 2.0 / Math.PI * nodesCount;
     80            count[i] = (int) Math.floor(part);
     81            remainder[i] = part - count[i];
     82            assign += count[i];
     83        }
     84        while(assign < nodesCount) {
     85            int imax = 0;
     86            for(int i = 1; i < angles.length; i++)
     87                if(remainder[i] > remainder[imax])
     88                    imax = i;
     89            count[imax]++;
     90            remainder[imax] = 0;
     91            assign++;
     92        }
     93        return count;
     94    }
     95
     96    /**
     97     * Class designed to create a couple between a node and its angle relative to the center of the circle.
     98     */
     99    private class PolarNode {
     100        double a;
     101        Node node;
     102       
     103        PolarNode(EastNorth center, Node n) {
     104            EastNorth pt = n.getEastNorth();
     105            this.a = Math.atan2(pt.north() - center.north(), pt.east() - center.east());
     106            this.node = n;
     107        }
     108    }
     109
     110    /**
     111     * Comparator used to order PolarNode relative to their angle.
     112     */
     113    private class PolarNodeComparator implements Comparator<PolarNode> {
     114
     115        @Override
     116        public int compare(PolarNode pc1, PolarNode pc2) {
     117            if(pc1.a < pc2.a)
     118                return -1;
     119            else if(pc1.a == pc2.a)
     120                return 0;
     121            else
     122                return 1;
     123        }
     124    }
     125   
    84126    @Override
    85127    public void actionPerformed(ActionEvent e) {
     
    118160        }
    119161
     162        if (nodes.size() < 2 || nodes.size() > 3) {
     163            new Notification(
     164                    tr("Please select exactly two or three nodes or one way with exactly two or three nodes."))
     165                    .setIcon(JOptionPane.INFORMATION_MESSAGE)
     166                    .setDuration(Notification.TIME_LONG)
     167                    .show();
     168            return;
     169        }
     170
    120171        // now we can start doing things to OSM data
    121172        Collection<Command> cmds = new LinkedList<Command>();
    122 
     173        EastNorth center = null;
     174       
    123175        if (nodes.size() == 2) {
    124176            // diameter: two single nodes needed or a way with two nodes
    125 
    126177            Node   n1 = nodes.get(0);
    127178            double x1 = n1.getEastNorth().east();
     
    134185            double xc = 0.5 * (x1 + x2);
    135186            double yc = 0.5 * (y1 + y2);
    136 
    137             // calculate the radius (r)
    138             double r = Math.sqrt(Math.pow(xc-x1,2) + Math.pow(yc-y1,2));
    139 
    140             // find where to put the existing nodes
    141             double a1 = calcang(xc, yc, x1, y1);
    142             double a2 = calcang(xc, yc, x2, y2);
    143             if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
    144 
    145             // build a way for the circle
    146             List<Node> wayToAdd = new ArrayList<Node>(numberOfNodesInCircle + 1);
    147 
    148             for (int i = 1; i <= numberOfNodesInCircle; i++) {
    149                 double a = a2 + 2*Math.PI*(1.0 - i/(double)numberOfNodesInCircle); // "1-" to get it clock-wise
    150 
    151                 // insert existing nodes if they fit before this new node (999 means "already added this node")
    152                 if ((a1 < 999) && (a1 > a - 1E-9) && (a1 < a + 1E-9)) {
    153                     wayToAdd.add(n1);
    154                     a1 = 999;
    155                 }
    156                 else if ((a2 < 999) && (a2 > a - 1E-9) && (a2 < a + 1E-9)) {
    157                     wayToAdd.add(n2);
    158                     a2 = 999;
    159                 }
    160                 else {
    161                     // get the position of the new node and insert it
    162                     double x = xc + r*Math.cos(a);
    163                     double y = yc + r*Math.sin(a);
    164                     Node n = new Node(Main.getProjection().eastNorth2latlon(new EastNorth(x,y)));
    165                     wayToAdd.add(n);
    166                     cmds.add(new AddCommand(n));
    167                 }
    168             }
    169             wayToAdd.add(wayToAdd.get(0)); // close the circle
    170             if (existingWay == null) {
    171                 Way newWay = new Way();
    172                 newWay.setNodes(wayToAdd);
    173                 cmds.add(new AddCommand(newWay));
    174             } else {
    175                 Way newWay = new Way(existingWay);
    176                 newWay.setNodes(wayToAdd);
    177                 cmds.add(new ChangeCommand(existingWay, newWay));
    178             }
    179 
    180             // the first node may be unused/abandoned if createcircle.nodecount is odd
    181             if (a1 < 999) {
    182                 // if it is, delete it
    183                 List<OsmPrimitive> parents = n1.getReferrers();
    184                 if (parents.isEmpty() || ((parents.size() == 1) && (parents.contains(existingWay)))) {
    185                     cmds.add(new DeleteCommand(n1));
    186                 }
    187             }
    188 
     187            center = new EastNorth(xc, yc);
    189188        } else if (nodes.size() == 3) {
    190189            // triangle: three single nodes needed or a way with three nodes
    191 
    192             // let's get some shorter names
    193             Node   n1 = nodes.get(0);
    194             double x1 = n1.getEastNorth().east();
    195             double y1 = n1.getEastNorth().north();
    196             Node   n2 = nodes.get(1);
    197             double x2 = n2.getEastNorth().east();
    198             double y2 = n2.getEastNorth().north();
    199             Node   n3 = nodes.get(2);
    200             double x3 = n3.getEastNorth().east();
    201             double y3 = n3.getEastNorth().north();
    202 
    203             // calculate the center (xc/yc)
    204             double s = 0.5*((x2 - x3)*(x1 - x3) - (y2 - y3)*(y3 - y1));
    205             double sUnder = (x1 - x2)*(y3 - y1) - (y2 - y1)*(x1 - x3);
    206 
    207             if (sUnder == 0) {
     190            center = Geometry.getCenter(nodes);
     191            if (center == null) {
    208192                notifyNodesNotOnCircle();
    209193                return;
    210194            }
    211 
    212             s /= sUnder;
    213 
    214             double xc = 0.5*(x1 + x2) + s*(y2 - y1);
    215             double yc = 0.5*(y1 + y2) + s*(x1 - x2);
    216 
    217             // calculate the radius (r)
    218             double r = Math.sqrt(Math.pow(xc-x1,2) + Math.pow(yc-y1,2));
    219 
    220             // find where to put the existing nodes
    221             double a1 = calcang(xc, yc, x1, y1);
    222             double a2 = calcang(xc, yc, x2, y2);
    223             double a3 = calcang(xc, yc, x3, y3);
    224             if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
    225             if (a2 < a3) { double at = a2; Node nt = n2; a2 = a3; n2 = n3; a3 = at; n3 = nt; }
    226             if (a1 < a2) { double at = a1; Node nt = n1; a1 = a2; n1 = n2; a2 = at; n2 = nt; }
    227 
    228             // build a way for the circle
    229             List<Node> wayToAdd = new ArrayList<Node>();
    230             for (int i = 1; i <= numberOfNodesInCircle; i++) {
    231                 double a = 2*Math.PI*(1.0 - i/(double)numberOfNodesInCircle); // "1-" to get it clock-wise
    232                 // insert existing nodes if they fit before this new node (999 means "already added this node")
    233                 if (a1 < 999 && a1 > a) {
    234                     wayToAdd.add(n1);
    235                     a1 = 999;
    236                 }
    237                 if (a2 < 999 && a2 > a) {
    238                     wayToAdd.add(n2);
    239                     a2 = 999;
    240                 }
    241                 if (a3 < 999 && a3 > a) {
    242                     wayToAdd.add(n3);
    243                     a3 = 999;
    244                 }
    245                 // get the position of the new node and insert it
    246                 double x = xc + r*Math.cos(a);
    247                 double y = yc + r*Math.sin(a);
     195        }
     196
     197        // calculate the radius (r)
     198        EastNorth n1 = nodes.get(0).getEastNorth();
     199        double r = Math.sqrt(Math.pow(center.east()-n1.east(),2) +
     200                Math.pow(center.north()-n1.north(),2));
     201
     202        // Order nodes by angle
     203        PolarNode[] angles = new PolarNode[nodes.size()];
     204        for(int i = 0; i < nodes.size(); i++) {
     205            angles[i] = new PolarNode(center, nodes.get(i));
     206        }
     207        Arrays.sort(angles, new PolarNodeComparator());
     208        int[] count = distributeNodes(angles,
     209                numberOfNodesInCircle >= nodes.size() ? numberOfNodesInCircle - nodes.size() : 0);
     210
     211        // build a way for the circle
     212        List<Node> wayToAdd = new ArrayList<Node>();
     213        for(int i = 0; i < nodes.size(); i++) {
     214            wayToAdd.add(angles[i].node);
     215            double delta = angles[(i+1) % nodes.size()].a - angles[i].a;
     216            if(delta < 0)
     217                delta += 2*Math.PI;
     218            for(int j = 0; j < count[i]; j++) {
     219                double alpha = angles[i].a + (j+1)*delta/(count[i]+1);
     220                double x = center.east() + r*Math.cos(alpha);
     221                double y = center.north() + r*Math.sin(alpha);
    248222                LatLon ll = Main.getProjection().eastNorth2latlon(new EastNorth(x,y));
    249223                if (ll.isOutSideWorld()) {
     
    255229                cmds.add(new AddCommand(n));
    256230            }
    257             wayToAdd.add(wayToAdd.get(0)); // close the circle
    258             if (existingWay == null) {
    259                 Way newWay = new Way();
    260                 newWay.setNodes(wayToAdd);
    261                 cmds.add(new AddCommand(newWay));
    262             } else {
    263                 Way newWay = new Way(existingWay);
    264                 newWay.setNodes(wayToAdd);
    265                 cmds.add(new ChangeCommand(existingWay, newWay));
    266             }
    267 
     231        }
     232        wayToAdd.add(wayToAdd.get(0)); // close the circle
     233        if (existingWay == null) {
     234            Way newWay = new Way();
     235            newWay.setNodes(wayToAdd);
     236            cmds.add(new AddCommand(newWay));
    268237        } else {
    269             new Notification(
    270                     tr("Please select exactly two or three nodes or one way with exactly two or three nodes."))
    271                     .setIcon(JOptionPane.INFORMATION_MESSAGE)
    272                     .setDuration(Notification.TIME_LONG)
    273                     .show();
    274             return;
     238            Way newWay = new Way(existingWay);
     239            newWay.setNodes(wayToAdd);
     240            cmds.add(new ChangeCommand(existingWay, newWay));
    275241        }
    276242
Note: See TracChangeset for help on using the changeset viewer.