Skip to content

Commit e484e56

Browse files
author
taylor.smock
committed
Fix #22504: Circularize multiple selected ways (patch by qeef, modified)
git-svn-id: https://josm.openstreetmap.de/svn/trunk@18615 0c6e7542-c601-0410-84e7-c038aed88b3b
1 parent d0dcfb2 commit e484e56

File tree

4 files changed

+272
-0
lines changed

4 files changed

+272
-0
lines changed

src/org/openstreetmap/josm/actions/AlignInCircleAction.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ public void actionPerformed(ActionEvent e) {
149149
* <p>
150150
* Case 3: Only nodes are selected
151151
* --&gt; Align these nodes, all are fix
152+
* <p>
153+
* Case 4: Circularize selected ways
154+
* --&gt; Circularize each way of the selection.
152155
* @param ds data set in which the command operates
153156
* @return the resulting command to execute to perform action, or null if nothing was changed
154157
* @throws InvalidSelection if selection cannot be used
@@ -226,6 +229,43 @@ public static Command buildCommand(DataSet ds) throws InvalidSelection {
226229
}
227230
fixNodes.addAll(onWay);
228231
nodes = collectNodesAnticlockwise(ways);
232+
} else if (!ways.isEmpty() && selectedNodes.isEmpty()) {
233+
List<Command> rcmds = new LinkedList<>();
234+
for (Way w : ways) {
235+
if (!w.isDeleted() && w.isArea()) {
236+
List<Node> wnodes = w.getNodes();
237+
wnodes.remove(wnodes.size() - 1);
238+
if (validateGeometry(wnodes)) {
239+
center = Geometry.getCenter(wnodes);
240+
}
241+
if (center == null) {
242+
continue;
243+
}
244+
boolean skipThisWay = false;
245+
radius = 0;
246+
for (Node n : wnodes) {
247+
if (!n.isLatLonKnown()) {
248+
skipThisWay = true;
249+
break;
250+
} else {
251+
radius += center.distance(n.getEastNorth());
252+
}
253+
}
254+
if (skipThisWay) {
255+
continue;
256+
} else {
257+
radius /= wnodes.size();
258+
}
259+
Command c = moveNodesCommand(wnodes, Collections.emptySet(), center, radius);
260+
if (c != null) {
261+
rcmds.add(c);
262+
}
263+
}
264+
}
265+
if (rcmds.isEmpty()) {
266+
throw new InvalidSelection();
267+
}
268+
return new SequenceCommand(tr("Align each Way in Circle"), rcmds);
229269
} else {
230270
throw new InvalidSelection();
231271
}
@@ -258,7 +298,23 @@ public static Command buildCommand(DataSet ds) throws InvalidSelection {
258298
}
259299
radius = radius / nodes.size();
260300
}
301+
return moveNodesCommand(nodes, fixNodes, center, radius);
302+
}
261303

304+
/**
305+
* Move each node of the list of nodes to be arranged in a circle.
306+
* @param nodes The list of nodes to be moved.
307+
* @param fixNodes The list of nodes that must not be moved.
308+
* @param center A center of the circle formed by the nodes.
309+
* @param radius A radius of the circle formed by the nodes.
310+
* @return the command that arranges the nodes in a circle.
311+
* @since 18615
312+
*/
313+
public static Command moveNodesCommand(
314+
List<Node> nodes,
315+
Set<Node> fixNodes,
316+
EastNorth center,
317+
double radius) {
262318
List<Command> cmds = new LinkedList<>();
263319

264320
// Move each node to that distance from the center.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<osm version='0.6' generator='JOSM'>
3+
<node id='-201' action='modify' visible='true' lat='8.18118921225' lon='32.15483196352' />
4+
<node id='-202' action='modify' visible='true' lat='8.18119339651' lon='32.15483624212' />
5+
<node id='-203' action='modify' visible='true' lat='8.1811987769' lon='32.15483881688' />
6+
<node id='-204' action='modify' visible='true' lat='8.18120470447' lon='32.15483937724' />
7+
<node id='-205' action='modify' visible='true' lat='8.18121046427' lon='32.15483785561' />
8+
<node id='-206' action='modify' visible='true' lat='8.18121536158' lon='32.15483443553' />
9+
<node id='-207' action='modify' visible='true' lat='8.18121880571' lon='32.15482952951' />
10+
<node id='-208' action='modify' visible='true' lat='8.18122038125' lon='32.15482372927' />
11+
<node id='-209' action='modify' visible='true' lat='8.18121989817' lon='32.15481773443' />
12+
<node id='-210' action='modify' visible='true' lat='8.18121741473' lon='32.15481226805' />
13+
<node id='-211' action='modify' visible='true' lat='8.18121323048' lon='32.15480798944' />
14+
<node id='-212' action='modify' visible='true' lat='8.18120785009' lon='32.15480541468' />
15+
<node id='-213' action='modify' visible='true' lat='8.18120192252' lon='32.15480485432' />
16+
<node id='-214' action='modify' visible='true' lat='8.18119616272' lon='32.15480637595' />
17+
<node id='-215' action='modify' visible='true' lat='8.18119126541' lon='32.15480979603' />
18+
<node id='-216' action='modify' visible='true' lat='8.18118782128' lon='32.15481470206' />
19+
<node id='-217' action='modify' visible='true' lat='8.18118624574' lon='32.15482050229' />
20+
<node id='-218' action='modify' visible='true' lat='8.18118672882' lon='32.15482649713' />
21+
<node id='-101' action='modify' visible='true' lat='8.18124542497' lon='32.15480713489' />
22+
<node id='-102' action='modify' visible='true' lat='8.18125347379' lon='32.15481337498' />
23+
<node id='-103' action='modify' visible='true' lat='8.18126361593' lon='32.1548136437' />
24+
<node id='-104' action='modify' visible='true' lat='8.18127197743' lon='32.1548078384' />
25+
<node id='-105' action='modify' visible='true' lat='8.18127536449' lon='32.15479817652' />
26+
<node id='-106' action='modify' visible='true' lat='8.18127248336' lon='32.15478834856' />
27+
<node id='-107' action='modify' visible='true' lat='8.18126443454' lon='32.15478210847' />
28+
<node id='-108' action='modify' visible='true' lat='8.18125429241' lon='32.15478183976' />
29+
<node id='-109' action='modify' visible='true' lat='8.1812459309' lon='32.15478764505' />
30+
<node id='-110' action='modify' visible='true' lat='8.18124254385' lon='32.15479730693' />
31+
<way id='-200' action='modify' visible='true'>
32+
<nd ref='-201' />
33+
<nd ref='-202' />
34+
<nd ref='-203' />
35+
<nd ref='-204' />
36+
<nd ref='-205' />
37+
<nd ref='-206' />
38+
<nd ref='-207' />
39+
<nd ref='-208' />
40+
<nd ref='-209' />
41+
<nd ref='-210' />
42+
<nd ref='-211' />
43+
<nd ref='-212' />
44+
<nd ref='-213' />
45+
<nd ref='-214' />
46+
<nd ref='-215' />
47+
<nd ref='-216' />
48+
<nd ref='-217' />
49+
<nd ref='-218' />
50+
<nd ref='-201' />
51+
<tag k='building' v='yes' />
52+
<tag k='test' v='second' />
53+
</way>
54+
<way id='-100' action='modify' visible='true'>
55+
<nd ref='-101' />
56+
<nd ref='-102' />
57+
<nd ref='-103' />
58+
<nd ref='-104' />
59+
<nd ref='-105' />
60+
<nd ref='-106' />
61+
<nd ref='-107' />
62+
<nd ref='-108' />
63+
<nd ref='-109' />
64+
<nd ref='-110' />
65+
<nd ref='-101' />
66+
<tag k='building' v='yes' />
67+
<tag k='test' v='first' />
68+
</way>
69+
</osm>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<osm version='0.6' generator='JOSM'>
3+
<node id='-201' action='modify' visible='true' lat='8.18118921225' lon='32.15483196352' />
4+
<node id='-202' action='modify' visible='true' lat='8.18118681129' lon='32.1548268042' />
5+
<node id='-203' action='modify' visible='true' lat='8.18118619859' lon='32.15482113682' />
6+
<node id='-204' action='modify' visible='true' lat='8.18118744056' lon='32.15481557553' />
7+
<node id='-205' action='modify' visible='true' lat='8.18119040259' lon='32.15481072297' />
8+
<node id='-206' action='modify' visible='true' lat='8.18119476373' lon='32.154807105' />
9+
<node id='-207' action='modify' visible='true' lat='8.18120005137' lon='32.15480511368' />
10+
<node id='-208' action='modify' visible='true' lat='8.1812056925' lon='32.15480496481' />
11+
<node id='-209' action='modify' visible='true' lat='8.18121107584' lon='32.1548066745' />
12+
<node id='-210' action='modify' visible='true' lat='8.18121561801' lon='32.1548100575' />
13+
<node id='-211' action='modify' visible='true' lat='8.1812188268' lon='32.1548147472' />
14+
<node id='-212' action='modify' visible='true' lat='8.18122035447' lon='32.15482023541' />
15+
<node id='-213' action='modify' visible='true' lat='8.18122003548' lon='32.15482592737' />
16+
<node id='-214' action='modify' visible='true' lat='8.18121790441' lon='32.1548312063' />
17+
<node id='-215' action='modify' visible='true' lat='8.18120930109' lon='32.15483834355' />
18+
<node id='-216' action='modify' visible='true' lat='8.18120376113' lon='32.15483942844' />
19+
<node id='-217' action='modify' visible='true' lat='8.18119817269' lon='32.15483863724' />
20+
<node id='-218' action='modify' visible='true' lat='8.18119314131' lon='32.15483605568' />
21+
<node id='-101' action='modify' visible='true' lat='8.18124560591' lon='32.15480700927' />
22+
<node id='-102' action='modify' visible='true' lat='8.18125370049' lon='32.15481306718' />
23+
<node id='-103' action='modify' visible='true' lat='8.18126443735' lon='32.15481416717' />
24+
<node id='-104' action='modify' visible='true' lat='8.18127197775' lon='32.15480725579' />
25+
<node id='-105' action='modify' visible='true' lat='8.18127517917' lon='32.15479760626' />
26+
<node id='-106' action='modify' visible='true' lat='8.18127215504' lon='32.15478789853' />
27+
<node id='-107' action='modify' visible='true' lat='8.18126406047' lon='32.15478184063' />
28+
<node id='-108' action='modify' visible='true' lat='8.18125398734' lon='32.15478174647' />
29+
<node id='-109' action='modify' visible='true' lat='8.1812457832' lon='32.15478765201' />
30+
<node id='-110' action='modify' visible='true' lat='8.18124258179' lon='32.15479730155' />
31+
<way id='-200' action='modify' visible='true'>
32+
<nd ref='-201' />
33+
<nd ref='-202' />
34+
<nd ref='-203' />
35+
<nd ref='-204' />
36+
<nd ref='-205' />
37+
<nd ref='-206' />
38+
<nd ref='-207' />
39+
<nd ref='-208' />
40+
<nd ref='-209' />
41+
<nd ref='-210' />
42+
<nd ref='-211' />
43+
<nd ref='-212' />
44+
<nd ref='-213' />
45+
<nd ref='-214' />
46+
<nd ref='-215' />
47+
<nd ref='-216' />
48+
<nd ref='-217' />
49+
<nd ref='-218' />
50+
<nd ref='-201' />
51+
<tag k='building' v='yes' />
52+
<tag k='test' v='second' />
53+
</way>
54+
<way id='-100' action='modify' visible='true'>
55+
<nd ref='-101' />
56+
<nd ref='-102' />
57+
<nd ref='-103' />
58+
<nd ref='-104' />
59+
<nd ref='-105' />
60+
<nd ref='-106' />
61+
<nd ref='-107' />
62+
<nd ref='-108' />
63+
<nd ref='-109' />
64+
<nd ref='-110' />
65+
<nd ref='-101' />
66+
<tag k='building' v='yes' />
67+
<tag k='test' v='first' />
68+
</way>
69+
</osm>

test/unit/org/openstreetmap/josm/actions/AlignInCircleActionTest.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
// License: GPL. For details, see LICENSE file.
22
package org.openstreetmap.josm.actions;
33

4+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
45
import static org.junit.jupiter.api.Assertions.assertEquals;
56
import static org.junit.jupiter.api.Assertions.assertFalse;
67
import static org.junit.jupiter.api.Assertions.assertNotNull;
78
import static org.junit.jupiter.api.Assertions.assertNull;
89
import static org.junit.jupiter.api.Assertions.assertTrue;
10+
import static org.junit.jupiter.api.Assertions.fail;
911

12+
import java.io.IOException;
13+
import java.io.InputStream;
1014
import java.nio.file.Files;
1115
import java.nio.file.Paths;
1216
import java.util.Set;
@@ -17,10 +21,13 @@
1721
import org.openstreetmap.josm.TestUtils;
1822
import org.openstreetmap.josm.actions.AlignInCircleAction.InvalidSelection;
1923
import org.openstreetmap.josm.command.Command;
24+
import org.openstreetmap.josm.data.coor.ILatLon;
2025
import org.openstreetmap.josm.data.osm.DataSet;
2126
import org.openstreetmap.josm.data.osm.Node;
2227
import org.openstreetmap.josm.data.osm.OsmPrimitive;
2328
import org.openstreetmap.josm.data.osm.Way;
29+
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
30+
import org.openstreetmap.josm.io.IllegalDataException;
2431
import org.openstreetmap.josm.io.OsmReader;
2532
import org.openstreetmap.josm.testutils.JOSMTestRules;
2633
import org.opentest4j.AssertionFailedError;
@@ -181,4 +188,75 @@ void testSelectionEvaluation() throws Exception {
181188
}
182189
}
183190
}
191+
192+
/**
193+
* Test case: Circularize a batch of (two) buildings.
194+
* @throws IOException if the test file could not be read
195+
* @throws IllegalDataException if the test file has been corrupted
196+
*/
197+
@Test
198+
void testMultipleWaysSelected() throws IOException, IllegalDataException {
199+
final DataSet before;
200+
try (InputStream fis = Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleBuildingsBefore.osm"))) {
201+
before = OsmReader.parseDataSet(fis, NullProgressMonitor.INSTANCE);
202+
}
203+
204+
Way firstBefore = null;
205+
Way secondBefore = null;
206+
207+
for (Way w : before.getWays()) {
208+
if ("first".equals(w.get("test"))) {
209+
firstBefore = w;
210+
} else if ("second".equals(w.get("test"))) {
211+
secondBefore = w;
212+
} else {
213+
fail("There should only be \"first\" or \"second\" values in the key \"test\"");
214+
}
215+
}
216+
217+
assertNotNull(firstBefore);
218+
assertNotNull(secondBefore);
219+
220+
before.clearSelection();
221+
before.addSelected(firstBefore);
222+
before.addSelected(secondBefore);
223+
224+
Command c = assertDoesNotThrow(() -> AlignInCircleAction.buildCommand(before));
225+
c.executeCommand();
226+
227+
final DataSet after;
228+
try (InputStream fis = Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleBuildingsAfter.osm"))) {
229+
after = OsmReader.parseDataSet(fis, NullProgressMonitor.INSTANCE);
230+
}
231+
Way firstAfter = null;
232+
Way secondAfter = null;
233+
234+
for (Way w : after.getWays()) {
235+
if ("first".equals(w.get("test"))) {
236+
firstAfter = w;
237+
} else if ("second".equals(w.get("test"))) {
238+
secondAfter = w;
239+
} else {
240+
fail("There should only be \"first\" or \"second\" values in the key \"test\"");
241+
}
242+
}
243+
244+
assertNotNull(firstAfter);
245+
assertEquals(firstAfter.getNodesCount(), firstBefore.getNodesCount());
246+
for (int i = 0; i < firstAfter.getNodesCount(); i++) {
247+
Node bn = firstBefore.getNode(i);
248+
Node an = firstAfter.getNode(i);
249+
assertEquals(bn.lat(), an.lat(), ILatLon.MAX_SERVER_PRECISION);
250+
assertEquals(bn.lon(), an.lon(), ILatLon.MAX_SERVER_PRECISION);
251+
}
252+
253+
assertNotNull(secondAfter);
254+
assertEquals(secondAfter.getNodesCount(), secondBefore.getNodesCount());
255+
for (int i = 0; i < secondAfter.getNodesCount(); i++) {
256+
Node bn = secondBefore.getNode(i);
257+
Node an = secondAfter.getNode(i);
258+
assertEquals(bn.lat(), an.lat(), ILatLon.MAX_SERVER_PRECISION);
259+
assertEquals(bn.lon(), an.lon(), ILatLon.MAX_SERVER_PRECISION);
260+
}
261+
}
184262
}

0 commit comments

Comments
 (0)