source: josm/trunk/src/org/openstreetmap/josm/actions/AlignInRectangleAction.java@ 1023

Last change on this file since 1023 was 1023, checked in by stoecker, 16 years ago

close bug #1622. Keyboard shortcuts and specific OS handling

  • Property svn:eol-style set to native
File size: 5.3 KB
Line 
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.Command;
16import org.openstreetmap.josm.command.MoveCommand;
17import org.openstreetmap.josm.command.SequenceCommand;
18import org.openstreetmap.josm.data.coor.EastNorth;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Way;
22import org.openstreetmap.josm.tools.ShortCut;
23
24/**
25 * Aligns all selected nodes within a rectangle.
26 *
27 * There are many ways this could be done, for example:
28 * - find smallest rectangle to contain all points (rectangular hull) OR
29 * - find largest rectangle to fit inside OR
30 * - find both and compute the average
31 *
32 * Also, it would be possible to let the user specify more input, e.g.
33 * two nodes that should remain where they are.
34 *
35 * This method uses the following algorithm:
36 * 1. compute "heading" of all four edges
37 * 2. select the edge that is oriented closest to the average of all headings
38 * @author Frederik Ramm <frederik@remote.org>
39 */
40public final class AlignInRectangleAction extends JosmAction {
41
42 public AlignInRectangleAction() {
43 super(tr("Align Nodes in Rectangle"), "alignrect", tr("Move the selected nodes into a rectangle."),
44 ShortCut.registerShortCut("tools:alignrect", tr("Tool: Align in rectangle"), KeyEvent.VK_Q, ShortCut.GROUP_EDIT), true);
45 }
46
47 public void actionPerformed(ActionEvent e) {
48 Collection<OsmPrimitive> sel = Main.ds.getSelected();
49 Way myWay = null;
50 if (sel.size() == 1)
51 for (OsmPrimitive osm : sel)
52 if (osm instanceof Way)
53 myWay = (Way) osm;
54
55 if ((myWay == null) || (myWay.nodes.size() != 5) || (!myWay.nodes.get(0).equals(myWay.nodes.get(4)))) {
56 JOptionPane.showMessageDialog(Main.parent, tr("Please select one circular way of exactly four nodes."));
57 return;
58 }
59
60 // find orientation of all four edges, compute average
61 double avg_angle = 0;
62 double angle[] = new double[4];
63 EastNorth en[] = new EastNorth[5];
64 for (int i=0; i<5; i++) en[i] = new EastNorth(myWay.nodes.get(i).eastNorth.east(), myWay.nodes.get(i).eastNorth.north());
65 for (int i=0; i<4; i++) {
66 angle[i] = Math.asin((en[i+1].north()-en[i].north())/en[i+1].distance(en[i])) + 2 * Math.PI;
67 while(angle[i] > Math.PI/4) angle[i] -= Math.PI/2;
68 avg_angle += angle[i];
69 }
70 avg_angle /= 4;
71
72 // select edge that is closest to average, and use it as the base for the following
73 double best_dist = 0;
74 int base = 0;
75 for (int i=0; i<4; i++)
76 {
77 if ((i==0)||(Math.abs(angle[i]-avg_angle))<best_dist)
78 {
79 best_dist = Math.abs(angle[i]-avg_angle);
80 base = i;
81 }
82 }
83
84
85 // nice symbolic names for the nodes we're working with
86 EastNorth begin = en[base]; // first node of base segment
87 EastNorth end = en[(base+1)%4]; // second node of base segment
88 EastNorth next = en[(base+2)%4]; // node following the second node of the base seg
89 EastNorth prev= en[(base+3)%4]; // node before the first node of the base seg
90
91 // find a parallel to the base segment
92 double base_slope = (end.north() - begin.north()) / (end.east() - begin.east());
93 // base intercept of parallels that go through "next" and "prev" points
94 double b1 = next.north() - base_slope * next.east();
95 double b2 = prev.north() - base_slope * prev.east();
96 // average of both
97 double opposite_b = (b1+b2)/2;
98
99 // find the point on the base segment from which distance to "next" is shortest
100 double u = ((next.east()-begin.east())*(end.east()-begin.east()) + (next.north()-begin.north())*(end.north()-begin.north()))/end.distanceSq(begin);
101 EastNorth end2 = new EastNorth(begin.east()+u*(end.east()-begin.east()), begin.north()+u*(end.north()-begin.north()));
102
103 // same for "prev"
104 u = ((prev.east()-begin.east())*(end.east()-begin.east()) + (prev.north()-begin.north())*(end.north()-begin.north()))/end.distanceSq(begin);
105 EastNorth begin2 = new EastNorth(begin.east()+u*(end.east()-begin.east()), begin.north()+u*(end.north()-begin.north()));
106
107 // new "begin" and "end" points are halfway between their old position and
108 // the base points found above
109 end = new EastNorth((end2.east()+end.east())/2, (end2.north()+end.north())/2);
110 begin = new EastNorth((begin2.east()+begin.east())/2, (begin2.north()+begin.north())/2);
111
112 double other_slope = -1 / base_slope;
113 double next_b = end.north() - other_slope * end.east();
114 double prev_b = begin.north() - other_slope * begin.east();
115
116 double x = (opposite_b-next_b)/(other_slope-base_slope);
117 double y = opposite_b + base_slope * x;
118 next = new EastNorth(x, y);
119
120 x = (opposite_b-prev_b)/(other_slope-base_slope);
121 y = opposite_b + base_slope * x;
122 prev = new EastNorth(x, y);
123
124 Collection<Command> cmds = new LinkedList<Command>();
125 for (int i=0; i<4; i++) {
126 Node n = myWay.nodes.get(i);
127 EastNorth ref = (i == base) ? begin : (i == (base+1)%4) ? end : (i==(base+2)%4) ? next : prev;
128 double dx = ref.east()-n.eastNorth.east();
129 double dy = ref.north()-n.eastNorth.north();
130 cmds.add(new MoveCommand(n, dx, dy));
131 }
132
133 Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Rectangle"), cmds));
134 Main.map.repaint();
135 }
136}
Note: See TracBrowser for help on using the repository browser.