Ticket #1621: josm-1054-add-ortho.patch

File josm-1054-add-ortho.patch, 13.2 KB (added by hkucharek, 17 years ago)

Patch for orthonormalization action

  • C:/eclipse.workspace/JOSM/src/org/openstreetmap/josm/actions/AlignOrthogonallyAction.java

     
     1// License: GPL. See LICENSE file for details.
     2//
     3package org.openstreetmap.josm.actions;
     4
     5import static org.openstreetmap.josm.tools.I18n.tr;
     6
     7import java.awt.event.ActionEvent;
     8import java.awt.event.KeyEvent;
     9import java.util.Collection;
     10import java.util.LinkedList;
     11
     12import javax.swing.JOptionPane;
     13
     14import org.openstreetmap.josm.Main;
     15import org.openstreetmap.josm.command.AddCommand;
     16import org.openstreetmap.josm.command.Command;
     17import org.openstreetmap.josm.command.MoveCommand;
     18import org.openstreetmap.josm.command.SequenceCommand;
     19import org.openstreetmap.josm.data.coor.EastNorth;
     20import org.openstreetmap.josm.data.osm.Node;
     21import org.openstreetmap.josm.data.osm.OsmPrimitive;
     22import org.openstreetmap.josm.data.osm.Way;
     23import org.openstreetmap.josm.tools.ShortCut;
     24
     25/**
     26 * Align edges of a way so all angles are right angles.
     27 *
     28 * 1. Find orientation of all edges
     29 * 2. Compute main orientation, weighted by length of edge, normalized to angles between 0 and pi/2
     30 * 3. Rotate every edge around its center to align with main orientation or perpendicular to it
     31 * 4. Compute new intersection points of two adjascent edges
     32 * 5. Move nodes to these points
     33 */
     34public final class AlignOrthogonallyAction extends JosmAction {
     35
     36        public AlignOrthogonallyAction() {
     37        super(tr("Align Nodes to make shape orthogonally"), "alignortho", tr("Move the selected nodes so all angles are orthogonally."),
     38                ShortCut.registerShortCut("tools:alignortho", tr("Tool: {0}", tr("Align orthonormal")), KeyEvent.VK_T, ShortCut.GROUP_EDIT), true);
     39        }
     40
     41        public void actionPerformed(ActionEvent e) {
     42       
     43                Collection<OsmPrimitive> sel = Main.ds.getSelected();
     44       
     45        // Check the selection if it is suitible for the orthogonalization
     46                for (OsmPrimitive osm : sel) {
     47            // Check if only ways are in the collection
     48                    if (!(osm instanceof Way)) {
     49                JOptionPane.showMessageDialog(Main.parent, tr("Selection must consist only of ways."));
     50                        return;
     51            }
     52           
     53            // Check if every way is made of at least four segments and closed
     54            Way way = (Way)osm;
     55            if ((way.nodes.size() < 5) || (!way.nodes.get(0).equals(way.nodes.get(way.nodes.size() - 1)))) {
     56                JOptionPane.showMessageDialog(Main.parent, tr("Please select closed way(s) of at least four nodes."));
     57                return;
     58            }
     59           
     60            // Check if every edge in the way is a definite edge of at least 45 degrees of direction change
     61            // Otherwise, two segments could be turned into same direction and intersection would fail.
     62            // Or changes of shape would be too serious.
     63            for (int i1=0; i1 < way.nodes.size()-1; i1++) {   
     64               int i2 = (i1+1) % (way.nodes.size()-1);
     65               int i3 = (i1+2) % (way.nodes.size()-1);
     66               double angle1  =Math.abs(way.nodes.get(i1).eastNorth.heading(way.nodes.get(i2).eastNorth));
     67               double angle2 = Math.abs(way.nodes.get(i2).eastNorth.heading(way.nodes.get(i3).eastNorth));
     68               double delta = Math.abs(angle2 - angle1);
     69               while(delta > Math.PI) delta -= Math.PI;
     70               if(delta < Math.PI/4) {
     71                   JOptionPane.showMessageDialog(Main.parent, tr("Please select ways with edges close to right angles."));
     72                   return;
     73               }
     74            }
     75        }
     76       
     77        // Now all checks are done and we can now do the neccessary computations
     78        // From here it is assumed that the above checks hold
     79        Collection<Command> cmds = new LinkedList<Command>();
     80
     81        // First, compute the weighted average of the headings of all segments
     82        double sum_weighted_headings = 0.0;
     83        double sum_weights = 0.0;
     84        for (OsmPrimitive osm : sel) {
     85            Way way = (Way)osm;
     86            int nodes = way.nodes.size();
     87                int sides = nodes - 1;           
     88                // to find orientation of all segments, compute weighted average of all segment's headings
     89            // all headings are mapped into [0, 3*4*PI) by PI/2 rotations so both main orientations are mapped into one
     90            // the headings are weighted by the length of the segment establishing it, so a longer segment, that is more
     91            // likely to have the correct orientation, has more influence in the computing than a short segment, that is easier to misalign.
     92                for (int i=0; i < sides; i++) {
     93                double heading;       
     94                double weight;
     95                heading = way.nodes.get(i).eastNorth.heading(way.nodes.get(i+1).eastNorth);
     96                //Put into [0, PI/4) to find main direction
     97                while(heading > Math.PI/4) heading -= Math.PI/2;
     98                weight = way.nodes.get(i).eastNorth.distance(way.nodes.get(i+1).eastNorth);
     99                sum_weighted_headings += heading*weight;
     100                        sum_weights += weight;
     101                }
     102         }
     103        double avg_heading = sum_weighted_headings/sum_weights;           
     104       
     105        for (OsmPrimitive osm : sel) { 
     106            Way myWay = (Way)osm;
     107            int nodes = myWay.nodes.size();
     108            int sides = nodes - 1;
     109           
     110            // Copy necessary data into a more suitable data structure
     111            EastNorth en[] = new EastNorth[sides];
     112            for (int i=0; i < sides; i++) {   
     113                en[i] = new EastNorth(myWay.nodes.get(i).eastNorth.east(), myWay.nodes.get(i).eastNorth.north());
     114            }
     115 
     116            for (int i=0; i < sides; i++) {
     117                // Compute handy indices of three nodes to be used in one loop iteration.
     118                // We use segments (i1,i2) and (i2,i3), align them and compute the new
     119                // position of the i2-node as the intersection of the realigned (i1,i2), (i2,i3) segments
     120               
     121                // Compute handy indices so we don't have to deal with index-wrap-around all the time
     122                int i1 = i;
     123                int i2 = (i+1)%sides;
     124                int i3 = (i+2)%sides;
     125                double heading1, heading2;
     126                double delta1, delta2;
     127                // compute neccessary rotation of first segment to align it with main orientation
     128                heading1 = en[i1].heading(en[i2]);
     129                //Put into [-PI/4, PI/4) because we want a minimum of rotation so we don't swap node positions
     130                while(heading1 - avg_heading > Math.PI/4) heading1 -= Math.PI/2;
     131                while(heading1 - avg_heading < -Math.PI/4) heading1 += Math.PI/2;
     132                delta1 = avg_heading - heading1;
     133                // compute neccessary rotation of second segment to align it with main orientation
     134                heading2 = en[i2].heading(en[i3]);
     135                //Put into [-PI/4, PI/4) because we want a minimum of rotation so we don't swap node positions
     136                while(heading2 - avg_heading > Math.PI/4) heading2 -= Math.PI/2;
     137                while(heading2 - avg_heading < -Math.PI/4) heading2 += Math.PI/2;
     138                delta2 = avg_heading - heading2;
     139                // To align a segment, rotate around its center
     140                EastNorth pivot1 = new EastNorth((en[i1].east()+en[i2].east())/2, (en[i1].north()+en[i2].north())/2);
     141                EastNorth A=en[i1].rotate(pivot1, delta1);
     142                EastNorth B=en[i2].rotate(pivot1, delta1);
     143                EastNorth pivot2 = new EastNorth((en[i2].east()+en[i3].east())/2, (en[i2].north()+en[i3].north())/2);
     144                EastNorth C=en[i2].rotate(pivot2, delta2);
     145                EastNorth D=en[i3].rotate(pivot2, delta2);
     146
     147                // compute intersection of segments
     148                double u=det(B.east() - A.east(), B.north() - A.north(), C.east() - D.east(), C.north() - D.north());
     149               
     150                // Check for parallel segments and do nothing if they are
     151                // In practice this will probably only happen when a way has been duplicated
     152               
     153                if (u == 0) continue;
     154               
     155                // q is a number between 0 and 1
     156                // It is the point in the segment where the intersection occurs
     157                // if the segment is scaled to lenght 1
     158               
     159                double q = det(B.north() - C.north(), B.east() - C.east(), D.north() - C.north(), D.east() - C.east()) / u;
     160                EastNorth intersection = new EastNorth(
     161                        B.east() + q * (A.east() - B.east()),
     162                        B.north() + q * (A.north() - B.north()));
     163   
     164                Node n = myWay.nodes.get(i1);
     165                double dx = intersection.east()-n.eastNorth.east();
     166                double dy = intersection.north()-n.eastNorth.north();
     167                cmds.add(new MoveCommand(n, dx, dy));       
     168            }     
     169        }
     170       
     171                Main.main.undoRedo.add(new SequenceCommand(tr("Align Segments orthogonally"), cmds));
     172                Main.map.repaint();
     173        }
     174   
     175    static double det(double a, double b, double c, double d)
     176    {
     177        return a * d - b * c;
     178    }
     179
     180}
  • C:/eclipse.workspace/JOSM/src/org/openstreetmap/josm/gui/MainMenu.java

    Property changes on: C:\eclipse.workspace\JOSM\src\org\openstreetmap\josm\actions\AlignOrthogonallyAction.java
    ___________________________________________________________________
    Name: svn:eol-style
       + native
    
     
    1919import org.openstreetmap.josm.actions.AlignInCircleAction;
    2020import org.openstreetmap.josm.actions.AlignInLineAction;
    2121import org.openstreetmap.josm.actions.AlignInRectangleAction;
     22import org.openstreetmap.josm.actions.AlignOrthogonallyAction;
    2223import org.openstreetmap.josm.actions.AutoScaleAction;
    2324import org.openstreetmap.josm.actions.CombineWayAction;
    2425import org.openstreetmap.josm.actions.CopyAction;
     
    106107        public final JosmAction alignInCircle = new AlignInCircleAction();
    107108        public final JosmAction alignInLine = new AlignInLineAction();
    108109        public final JosmAction alignInRect = new AlignInRectangleAction();
     110    public final JosmAction alignOrtho = new AlignOrthogonallyAction();
    109111        public final JosmAction createCircle = new CreateCircleAction();
    110112        public final JosmAction mergeNodes = new MergeNodesAction();
    111113        public final JosmAction joinNodeWay = new JoinNodeWayAction();
     
    223225                add(toolsMenu, alignInCircle);
    224226                add(toolsMenu, alignInLine);
    225227                add(toolsMenu, alignInRect);
     228                add(toolsMenu, alignOrtho);
    226229                toolsMenu.addSeparator();
    227230                add(toolsMenu, createCircle);
    228231                toolsMenu.addSeparator();
  • C:/eclipse.workspace/JOSM/src/org/openstreetmap/josm/data/coor/EastNorth.java

     
    2929        public EastNorth interpolate(EastNorth en2, double proportion) {
    3030                return new EastNorth(this.x + proportion * (en2.x - this.x),this.y + proportion * (en2.y - this.y));
    3131        }
     32   
     33    /**
     34     * Returns the heading, in radians, that you have to use to get from
     35     * this EastNorth to another. Heading is mapped into [0, 2pi)
     36     *
     37     * @param other the "destination" position
     38     * @return heading
     39     */
     40    public double heading(EastNorth other) {
     41        double hd = Math.atan2(other.east() - east(), other.north() - north());
     42        if(hd < 0) hd = 2 * Math.PI + hd;
     43        return hd;       
     44    }
     45   
     46    public EastNorth sub(EastNorth en) {
     47        return new EastNorth(en.east() - east(), en.north() - north());
     48    }
     49 
     50    /**
     51     * Returns an EastNorth representing the this EastNorth rotatedaround
     52     * a given EastNorth by a given angle
     53     * @param pivot the center of the rotation
     54     * @param angle the angle of the rotation
     55     * @return EastNorth rotated object
     56     */
     57    public EastNorth rotate(EastNorth pivot, double angle)
     58    {
     59        double cosPhi = Math.cos(angle);
     60        double sinPhi = Math.sin(angle);
     61        double x = east() - pivot.east();
     62        double y = north() - pivot.north();
     63        double nx =  cosPhi * x + sinPhi * y + pivot.east();
     64        double ny = -sinPhi * x + cosPhi * y + pivot.north();
     65        return new EastNorth(nx, ny);
     66    }
    3267       
    3368        @Override public String toString() {
    3469                return "EastNorth[e="+x+", n="+y+"]";