Ignore:
Timestamp:
2008-11-20T19:33:47+01:00 (15 years ago)
Author:
framm
Message:
  • now really added the patch that *should* have been added in r1085.
File:
1 edited

Legend:

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

    r1085 r1089  
    4747        }
    4848
    49         public void actionPerformed(ActionEvent e) {
    50        
    51                 Collection<OsmPrimitive> sel = Main.ds.getSelected();
    52        
     49    public void actionPerformed(ActionEvent e) {
     50
     51        Collection<OsmPrimitive> sel = Main.ds.getSelected();
     52
    5353        ArrayList<Node> dirnodes = new ArrayList<Node>();
    54                
     54
    5555        // Check the selection if it is suitible for the orthogonalization
    56                 for (OsmPrimitive osm : sel) {
     56        for (OsmPrimitive osm : sel) {
    5757            // Check if not more than two nodes in the selection
    5858            if(osm instanceof Node) {
     
    6565            }
    6666            // Check if selection consists now only of ways
    67                     if (!(osm instanceof Way)) {
     67            if (!(osm instanceof Way)) {
    6868                JOptionPane.showMessageDialog(Main.parent, tr("Selection must consist only of ways."));
    69                         return;
     69                return;
    7070            }
    71            
     71
    7272            // Check if every way is made of at least four segments and closed
    7373            Way way = (Way)osm;
     
    7676                return;
    7777            }
    78            
     78
    7979            // Check if every edge in the way is a definite edge of at least 45 degrees of direction change
    8080            // Otherwise, two segments could be turned into same direction and intersection would fail.
    8181            // Or changes of shape would be too serious.
    82             for (int i1=0; i1 < way.nodes.size()-1; i1++) {   
    83                int i2 = (i1+1) % (way.nodes.size()-1);
    84                int i3 = (i1+2) % (way.nodes.size()-1);
    85                double angle1  =Math.abs(way.nodes.get(i1).eastNorth.heading(way.nodes.get(i2).eastNorth));
    86                double angle2 = Math.abs(way.nodes.get(i2).eastNorth.heading(way.nodes.get(i3).eastNorth));
    87                double delta = Math.abs(angle2 - angle1);
    88                while(delta > Math.PI) delta -= Math.PI;
    89                if(delta < Math.PI/4) {
    90                    JOptionPane.showMessageDialog(Main.parent, tr("Please select ways with almost right angles to orthogonalize."));
    91                    return;
    92                }
     82            for (int i1=0; i1 < way.nodes.size()-1; i1++) {
     83                int i2 = (i1+1) % (way.nodes.size()-1);
     84                int i3 = (i1+2) % (way.nodes.size()-1);
     85                double angle1  =Math.abs(way.nodes.get(i1).eastNorth.heading(way.nodes.get(i2).eastNorth));
     86                double angle2 = Math.abs(way.nodes.get(i2).eastNorth.heading(way.nodes.get(i3).eastNorth));
     87                double delta = Math.abs(angle2 - angle1);
     88                while(delta > Math.PI) delta -= Math.PI;
     89                if(delta < Math.PI/4) {
     90                    JOptionPane.showMessageDialog(Main.parent, tr("Please select ways with almost right angles to orthogonalize."));
     91                    return;
     92                }
    9393            }
    9494        }
     
    9696        if ("EPSG:4326".equals(Main.proj.toString())) {
    9797            String msg = tr("<html>You are using the EPSG:4326 projection which might lead<br>" +
    98                "to undesirable results when doing rectangular alignments.<br>" +
    99                "Change your projection to get rid of this warning.<br>" +
    100                "Do you want to continue?");
     98                    "to undesirable results when doing rectangular alignments.<br>" +
     99                    "Change your projection to get rid of this warning.<br>" +
     100                    "Do you want to continue?");
    101101
    102102            if (!DontShowAgainInfo.show("align_rectangular_4326", msg, false)) {
     
    108108            JOptionPane.showMessageDialog(Main.parent, tr("Only one node selected"));
    109109            return;
    110         } 
    111        
     110        }
     111
    112112        // Now all checks are done and we can now do the neccessary computations
    113113        // From here it is assumed that the above checks hold
    114114        Collection<Command> cmds = new LinkedList<Command>();
    115         double align_to_heading;
    116 
    117        
    118         if(dirnodes.size() == 2) { // When selection contained two nodes, use the nodes to compute a direction to align to
    119             double heading;       
    120             heading = dirnodes.get(0).eastNorth.heading(dirnodes.get(1).eastNorth);
    121             while(heading > Math.PI/4) heading -= Math.PI/2;
    122             align_to_heading=heading;
    123         } else {   // Otherwise compute the alignment direction from the ways in the collection
    124         // First, compute the weighted average of the headings of all segments
    125         double sum_weighted_headings = 0.0;
    126         double sum_weights = 0.0;
     115        double align_to_heading = 0.0;
     116        boolean use_dirnodes = false;
     117
     118        if(dirnodes.size() == 2) {
     119            // When selection contains two nodes, use the nodes to compute a direction
     120            // to align all ways to
     121            align_to_heading = normalize_angle(dirnodes.get(0).eastNorth.heading(dirnodes.get(1).eastNorth));
     122            use_dirnodes = true;
     123        }
     124
    127125        for (OsmPrimitive osm : sel) {
    128                 if(!(osm instanceof Way))
    129                     continue;
     126            if(!(osm instanceof Way))
     127                continue;
     128
    130129            Way way = (Way)osm;
    131130            int nodes = way.nodes.size();
    132                 int sides = nodes - 1;           
    133                         // To find orientation of all segments, compute weighted average of all segment's headings
    134             // all headings are mapped into [0, 3*4*PI) by PI/2 rotations so both main orientations are mapped into one
    135             // the headings are weighted by the length of the segment establishing it, so a longer segment, that is more
    136             // likely to have the correct orientation, has more influence in the computing than a short segment, that is easier to misalign.
    137                 for (int i=0; i < sides; i++) {
    138                 double heading;       
    139                 double weight;
    140                 heading = way.nodes.get(i).eastNorth.heading(way.nodes.get(i+1).eastNorth);
    141                 //Put into [0, PI/4) to find main direction
    142                 while(heading > Math.PI/4) heading -= Math.PI/2;
    143                 weight = way.nodes.get(i).eastNorth.distance(way.nodes.get(i+1).eastNorth);
    144                 sum_weighted_headings += heading*weight;
    145                         sum_weights += weight;
    146                 }
    147          }
    148             align_to_heading = sum_weighted_headings/sum_weights;         
    149         }
    150        
    151         for (OsmPrimitive osm : sel) { 
    152             if(!(osm instanceof Way))
    153                 continue;
    154             Way myWay = (Way)osm;
    155             int nodes = myWay.nodes.size();
    156131            int sides = nodes - 1;
    157            
    158132            // Copy necessary data into a more suitable data structure
    159133            EastNorth en[] = new EastNorth[sides];
    160             for (int i=0; i < sides; i++) {   
    161                 en[i] = new EastNorth(myWay.nodes.get(i).eastNorth.east(), myWay.nodes.get(i).eastNorth.north());
    162             }
    163  
     134            for (int i=0; i < sides; i++) {
     135                en[i] = new EastNorth(way.nodes.get(i).eastNorth.east(), way.nodes.get(i).eastNorth.north());
     136            }
     137
     138            if (! use_dirnodes) {
     139                // To find orientation of all segments, compute weighted average of all segment's headings
     140                // all headings are mapped into [-PI/4, PI/4] by PI/2 rotations so both main orientations are mapped into one
     141                // the headings are weighted by the length of the segment establishing it, so a longer segment, that is more
     142                // likely to have the correct orientation, has more influence in the computing than a short segment, that is easier to misalign.
     143                double headings[] = new double[sides];
     144                double weights[] = new double[sides];
     145                for (int i=0; i < sides; i++) {
     146                    headings[i] = normalize_angle(way.nodes.get(i).eastNorth.heading(way.nodes.get(i+1).eastNorth));
     147                    weights[i] = way.nodes.get(i).eastNorth.distance(way.nodes.get(i+1).eastNorth);
     148                }
     149
     150                // CAVEAT: for orientations near -PI/4 or PI/4 the mapping into ONE orientation fails
     151                //         resulting in a heading-difference between adjacent sides of almost PI/2
     152                //         and a totally wrong average
     153                // check for this (use PI/3 as arbitray limit) and rotate into ONE orientation
     154                double angle_diff_max = 0.0;
     155                for (int i=0; i < sides; i++) {
     156                    double diff = 0.0;
     157                    if (i == 0) {
     158                        diff = heading_diff(headings[i], headings[sides - 1]);
     159                    } else {
     160                        diff = heading_diff(headings[i], headings[i - 1]);
     161                    }
     162                    if (diff > angle_diff_max) angle_diff_max = diff;
     163                }
     164
     165                if (angle_diff_max > Math.PI/3) {
     166                    // rearrange headings: everything < 0 gets PI/2-rotated
     167                    for (int i=0; i < sides; i++) {
     168                        if (headings[i] < 0)
     169                            headings[i] += Math.PI/2;
     170                    }
     171                }
     172
     173                // TODO:
     174                // use angle_diff_max as an indicator that the way is already orthogonal
     175                // e.g. if angle_diff_max is less then Math.toRadians(0.5)
     176                // and do nothing in that case (?)
     177
     178                // Compute the weighted average of the headings of all segments
     179                double sum_weighted_headings = 0.0;
     180                double sum_weights = 0.0;
     181                for (int i=0; i < sides; i++) {
     182                    sum_weighted_headings += headings[i] * weights[i];
     183                    sum_weights += weights[i];
     184                }
     185                align_to_heading = normalize_angle(sum_weighted_headings/sum_weights);
     186            }
     187
     188
    164189            for (int i=0; i < sides; i++) {
    165190                // Compute handy indices of three nodes to be used in one loop iteration.
     
    173198                double delta1, delta2;
    174199                // Compute neccessary rotation of first segment to align it with main orientation
    175                 heading1 = en[i1].heading(en[i2]);
    176                 //Put into [-PI/4, PI/4) because we want a minimum of rotation so we don't swap node positions
    177                 while(heading1 - align_to_heading > Math.PI/4) heading1 -= Math.PI/2;
    178                 while(heading1 - align_to_heading < -Math.PI/4) heading1 += Math.PI/2;
     200                heading1 = normalize_angle(en[i1].heading(en[i2]), align_to_heading);
    179201                delta1 = align_to_heading - heading1;
    180202                // Compute neccessary rotation of second segment to align it with main orientation
    181                 heading2 = en[i2].heading(en[i3]);
    182                 //Put into [-PI/4, PI/4) because we want a minimum of rotation so we don't swap node positions
    183                 while(heading2 - align_to_heading > Math.PI/4) heading2 -= Math.PI/2;
    184                 while(heading2 - align_to_heading < -Math.PI/4) heading2 += Math.PI/2;
     203                heading2 = normalize_angle(en[i2].heading(en[i3]), align_to_heading);
    185204                delta2 = align_to_heading - heading2;
    186205                // To align a segment, rotate around its center
     
    194213                // compute intersection of segments
    195214                double u=det(B.east() - A.east(), B.north() - A.north(),
    196                     C.east() - D.east(), C.north() - D.north());
    197                
     215                        C.east() - D.east(), C.north() - D.north());
     216
    198217                // Check for parallel segments and do nothing if they are
    199218                // In practice this will probably only happen when a way has
    200219                // been duplicated
    201                
     220
    202221                if (u == 0) continue;
    203                
     222
    204223                // q is a number between 0 and 1
    205224                // It is the point in the segment where the intersection occurs
    206225                // if the segment is scaled to length 1
    207                
     226
    208227                double q = det(B.north() - C.north(), B.east() - C.east(),
    209                     D.north() - C.north(), D.east() - C.east()) / u;
     228                        D.north() - C.north(), D.east() - C.east()) / u;
    210229                EastNorth intersection = new EastNorth(
    211230                        B.east() + q * (A.east() - B.east()),
    212231                        B.north() + q * (A.north() - B.north()));
    213    
    214                 Node n = myWay.nodes.get(i2);
     232
     233                Node n = way.nodes.get(i2);
    215234
    216235                LatLon ill = Main.proj.eastNorth2latlon(intersection);
     
    218237                    double dx = intersection.east()-n.eastNorth.east();
    219238                    double dy = intersection.north()-n.eastNorth.north();
    220                     cmds.add(new MoveCommand(n, dx, dy));       
    221                 }
    222             }     
    223         }
    224        
     239                    cmds.add(new MoveCommand(n, dx, dy));
     240                }
     241            } 
     242        }
     243
    225244        if (cmds.size() > 0) {
    226245            Main.main.undoRedo.add(new SequenceCommand(tr("Orthogonalize"), cmds));
    227246            Main.map.repaint();
    228247        }
    229         }
    230    
     248    }
     249
    231250    static double det(double a, double b, double c, double d)
    232251    {
     
    234253    }
    235254
     255    static double normalize_angle(double h) {
     256        return normalize_angle(h, 0.0);
     257    }
     258    static double normalize_angle(double h, double align_to) {
     259        double llimit = -Math.PI/4;
     260        double ulimit = Math.PI/4;
     261        while (h - align_to > ulimit) h -= Math.PI/2;
     262        while (h - align_to < llimit) h += Math.PI/2;
     263
     264        return h;
     265    }
     266
     267    static double heading_diff(double h1, double h2) {
     268        double heading_delta = h1 > h2 ? h1 - h2 : h2 - h1;
     269        return heading_delta;
     270    }
    236271}
Note: See TracChangeset for help on using the changeset viewer.