source: osm/applications/editors/josm/plugins/terracer/src/terracer/TerracerAction.java@ 13559

Last change on this file since 13559 was 13559, checked in by zere, 16 years ago

Fixed bug when not checking the return value of a dialogue box.

File size: 5.5 KB
Line 
1/**
2 * Terracer: A JOSM Plugin for terraced houses.
3 *
4 * Copyright 2009 CloudMade Ltd.
5 *
6 * Released under the GPLv2, see LICENSE file for details.
7 */
8package terracer;
9
10import static org.openstreetmap.josm.tools.I18n.tr;
11
12import java.awt.event.ActionEvent;
13import java.awt.event.KeyEvent;
14import java.util.Collection;
15import java.util.LinkedList;
16
17import javax.swing.JOptionPane;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.actions.JosmAction;
21import org.openstreetmap.josm.command.AddCommand;
22import org.openstreetmap.josm.command.Command;
23import org.openstreetmap.josm.command.SequenceCommand;
24import org.openstreetmap.josm.data.coor.LatLon;
25import org.openstreetmap.josm.data.osm.Node;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.Way;
28import org.openstreetmap.josm.tools.Shortcut;
29
30/**
31 * Terraces a quadrilateral, closed way into a series of quadrilateral,
32 * closed ways.
33 *
34 * At present it only works on quadrilaterals, but there is no reason
35 * why it couldn't be extended to work with other shapes too. The
36 * algorithm employed is naive, but it works in the simple case.
37 *
38 * @author zere
39 */
40public final class TerracerAction extends JosmAction {
41
42 public TerracerAction() {
43 super(tr("Terrace a building"),
44 "terrace",
45 tr("Creates individual buildings from a long building."),
46 Shortcut.registerShortcut("tools:Terracer",
47 tr("Tool: {0}", tr("Terrace a building")),
48 KeyEvent.VK_T, Shortcut.GROUP_EDIT,
49 Shortcut.SHIFT_DEFAULT),
50 true);
51 }
52
53 /**
54 * Checks that the selection is OK. If not, displays error message. If so
55 * calls to terraceBuilding(), which does all the real work.
56 */
57 public void actionPerformed(ActionEvent e) {
58 Collection<OsmPrimitive> sel = Main.ds.getSelected();
59 boolean badSelect = false;
60
61 if (sel.size() == 1) {
62 OsmPrimitive prim = sel.iterator().next();
63
64 if (prim instanceof Way) {
65 Way way = (Way)prim;
66
67 if ((way.nodes.size() == 5) &&
68 way.isClosed()) {
69 // first ask the user how many buildings to terrace into
70 String answer =
71 JOptionPane.showInputDialog(
72 tr("How many buildings are in the terrace?"));
73
74 // if the answer was null then the user clicked "Cancel"
75 if (answer != null) {
76 int nb = Integer.parseInt(answer);
77 terraceBuilding(way, nb);
78 }
79 } else {
80 badSelect = true;
81 }
82 } else {
83 badSelect = true;
84 }
85 } else {
86 badSelect = true;
87 }
88
89 if (badSelect) {
90 JOptionPane.showMessageDialog(Main.parent,
91 tr("Select a single, closed way of four nodes."));
92 }
93 }
94
95 /**
96 * Terraces a single, closed, quadrilateral way.
97 *
98 * Any node must be adjacent to both a short and long edge, we naively
99 * choose the longest edge and its opposite and interpolate along them
100 * linearly to produce new nodes. Those nodes are then assembled into
101 * closed, quadrilateral ways and left in the selection.
102 *
103 * @param w The closed, quadrilateral way to terrace.
104 * @param nb The number of buildings to terrace into.
105 */
106 private void terraceBuilding(Way w, int nb) {
107
108 // now find which is the longest side connecting the first node
109 Node[] nodes = w.nodes.toArray(new Node[] {});
110 double side1 = nodes[0].coor.greatCircleDistance(nodes[1].coor);
111 double side2 = nodes[0].coor.greatCircleDistance(nodes[3].coor);
112
113 // new nodes array to hold all intermediate nodes
114 Node[][] new_nodes = new Node[2][nb + 1];
115
116 if (side1 > side2) {
117 new_nodes[0][0] = nodes[0];
118 new_nodes[0][nb] = nodes[1];
119 new_nodes[1][0] = nodes[3];
120 new_nodes[1][nb] = nodes[2];
121 } else {
122 new_nodes[0][0] = nodes[0];
123 new_nodes[0][nb] = nodes[3];
124 new_nodes[1][0] = nodes[1];
125 new_nodes[1][nb] = nodes[2];
126 }
127
128 Collection<Command> commands = new LinkedList<Command>();
129 Collection<Way> ways = new LinkedList<Way>();
130
131 // create intermediate nodes by interpolating.
132 for (int i = 1; i < nb; ++i) {
133 new_nodes[0][i] = interpolateNode(new_nodes[0][0], new_nodes[0][nb],
134 (double)(i)/(double)(nb));
135 new_nodes[1][i] = interpolateNode(new_nodes[1][0], new_nodes[1][nb],
136 (double)(i)/(double)(nb));
137 commands.add(new AddCommand(new_nodes[0][i]));
138 commands.add(new AddCommand(new_nodes[1][i]));
139 }
140
141 // assemble new quadrilateral, closed ways
142 for (int i = 0; i < nb; ++i) {
143 Way terr = new Way();
144 terr.addNode(new_nodes[0][i]);
145 terr.addNode(new_nodes[0][i+1]);
146 terr.addNode(new_nodes[1][i+1]);
147 terr.addNode(new_nodes[1][i]);
148 terr.addNode(new_nodes[0][i]);
149 ways.add(terr);
150 commands.add(new AddCommand(terr));
151 }
152
153 Main.main.undoRedo.add(new SequenceCommand(tr("Terrace"), commands));
154 Main.ds.setSelected(ways);
155 }
156
157 /**
158 * Creates a new node at the interpolated position between the argument
159 * nodes. Interpolates linearly in Lat/Lon coordinates.
160 *
161 * @param a First node, at which f=0.
162 * @param b Last node, at which f=1.
163 * @param f Fractional position between first and last nodes.
164 * @return A new node at the interpolated position.
165 */
166 private Node interpolateNode(Node a, Node b, double f) {
167 // this isn't quite right - we should probably be interpolating
168 // screen coordinates rather than lat/lon, but it doesn't seem to
169 // make a great deal of difference at the scale of most terraces.
170 Node n = new Node(new LatLon(
171 a.coor.lat() * (1.0 - f) + b.coor.lat() * f,
172 a.coor.lon() * (1.0 - f) + b.coor.lon() * f
173 ));
174 return n;
175 }
176}
Note: See TracBrowser for help on using the repository browser.