source: josm/trunk/test/unit/org/openstreetmap/josm/actions/JoinAreasActionTest.java@ 16048

Last change on this file since 16048 was 16048, checked in by GerdP, 4 years ago

fix #18744: "Join Overlapping Areas" shows confusing dialogs when merging inner and outer ways of a multipolygon
Refuse to join ways when one is an outer way and at least one is an inner way of the same multipolygon relation

  • Property svn:eol-style set to native
File size: 11.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.junit.Assert.assertEquals;
5import static org.junit.Assert.assertTrue;
6
7import java.io.IOException;
8import java.io.InputStream;
9import java.nio.file.Files;
10import java.nio.file.Paths;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.HashSet;
14import java.util.Objects;
15import java.util.Set;
16
17import org.junit.Rule;
18import org.junit.Test;
19import org.openstreetmap.josm.TestUtils;
20import org.openstreetmap.josm.actions.search.SearchAction;
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.IPrimitive;
23import org.openstreetmap.josm.data.osm.Node;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.Relation;
26import org.openstreetmap.josm.data.osm.RelationMember;
27import org.openstreetmap.josm.data.osm.Way;
28import org.openstreetmap.josm.data.osm.search.SearchMode;
29import org.openstreetmap.josm.gui.MainApplication;
30import org.openstreetmap.josm.gui.layer.Layer;
31import org.openstreetmap.josm.gui.layer.OsmDataLayer;
32import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
33import org.openstreetmap.josm.io.IllegalDataException;
34import org.openstreetmap.josm.io.OsmReader;
35import org.openstreetmap.josm.testutils.JOSMTestRules;
36import org.openstreetmap.josm.tools.MultiMap;
37import org.openstreetmap.josm.tools.Utils;
38
39import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
40
41/**
42 * Unit tests of {@link JoinAreasAction} class.
43 */
44public class JoinAreasActionTest {
45
46 /**
47 * Setup test.
48 */
49 @Rule
50 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
51 public JOSMTestRules test = new JOSMTestRules().main().projection().preferences();
52
53 /**
54 * Non-regression test for bug #9599.
55 * @throws IOException if any I/O error occurs
56 * @throws IllegalDataException if OSM parsing fails
57 */
58 @Test
59 public void testTicket9599() throws IOException, IllegalDataException {
60 try (InputStream is = TestUtils.getRegressionDataStream(9599, "ex5.osm")) {
61 DataSet ds = OsmReader.parseDataSet(is, null);
62 Layer layer = new OsmDataLayer(ds, null, null);
63 MainApplication.getLayerManager().addLayer(layer);
64 try {
65 new JoinAreasAction(false).join(ds.getWays());
66 Collection<IPrimitive> found = SearchAction.searchAndReturn("type:way", SearchMode.replace);
67 assertEquals(1, found.size());
68 assertEquals(257786939, found.iterator().next().getUniqueId());
69 } finally {
70 // Ensure we clean the place before leaving, even if test fails.
71 MainApplication.getLayerManager().removeLayer(layer);
72 }
73 }
74 }
75
76 /**
77 * Non-regression test for bug #9599.
78 * @throws IOException if any I/O error occurs
79 * @throws IllegalDataException if OSM parsing fails
80 */
81 @Test
82 public void testTicket9599Simple() throws IOException, IllegalDataException {
83 try (InputStream is = TestUtils.getRegressionDataStream(9599, "three_old.osm")) {
84 DataSet ds = OsmReader.parseDataSet(is, null);
85 Layer layer = new OsmDataLayer(ds, null, null);
86 MainApplication.getLayerManager().addLayer(layer);
87 try {
88 new JoinAreasAction(false).join(ds.getWays());
89 Collection<IPrimitive> found = SearchAction.searchAndReturn("type:way", SearchMode.replace);
90 assertEquals(1, found.size());
91 assertEquals(31567319, found.iterator().next().getUniqueId());
92 } finally {
93 // Ensure we clean the place before leaving, even if test fails.
94 MainApplication.getLayerManager().removeLayer(layer);
95 }
96 }
97 }
98
99 /**
100 * Non-regression test for bug #10511.
101 * @throws IOException if any I/O error occurs
102 * @throws IllegalDataException if OSM parsing fails
103 */
104 @Test
105 public void testTicket10511() throws IOException, IllegalDataException {
106 try (InputStream is = TestUtils.getRegressionDataStream(10511, "10511_mini.osm")) {
107 DataSet ds = OsmReader.parseDataSet(is, null);
108 Layer layer = new OsmDataLayer(ds, null, null);
109 MainApplication.getLayerManager().addLayer(layer);
110 try {
111 new JoinAreasAction(false).join(ds.getWays());
112 Collection<IPrimitive> found = SearchAction.searchAndReturn("type:way", SearchMode.replace);
113 assertEquals(1, found.size());
114 } finally {
115 // Ensure we clean the place before leaving, even if test fails.
116 MainApplication.getLayerManager().removeLayer(layer);
117 }
118 }
119 }
120
121 /**
122 * Non-regression test for bug #11992.
123 * @throws IOException if any I/O error occurs
124 * @throws IllegalDataException if OSM parsing fails
125 */
126 @Test
127 public void testTicket11992() throws IOException, IllegalDataException {
128 try (InputStream is = TestUtils.getRegressionDataStream(11992, "shapes.osm")) {
129 DataSet ds = OsmReader.parseDataSet(is, null);
130 assertEquals(10, ds.getWays().size());
131 Layer layer = new OsmDataLayer(ds, null, null);
132 MainApplication.getLayerManager().addLayer(layer);
133 for (String ref : new String[]{"A", "B", "C", "D", "E"}) {
134 System.out.print("Joining ways " + ref);
135 Collection<IPrimitive> found = SearchAction.searchAndReturn("type:way ref="+ref, SearchMode.replace);
136 assertEquals(2, found.size());
137
138 MainApplication.getMenu().joinAreas.join(Utils.filteredCollection(found, Way.class));
139
140 Collection<IPrimitive> found2 = SearchAction.searchAndReturn("type:way ref="+ref, SearchMode.replace);
141 assertEquals(1, found2.size());
142 System.out.println(" ==> OK");
143 }
144 }
145 }
146
147 /**
148 * Non-regression test for bug #18744.
149 * @throws IOException if any I/O error occurs
150 * @throws IllegalDataException if OSM parsing fails
151 */
152 @Test
153 public void testTicket18744() throws IOException, IllegalDataException {
154 try (InputStream is = TestUtils.getRegressionDataStream(18744, "18744-sample.osm")) {
155 DataSet ds = OsmReader.parseDataSet(is, null);
156 Layer layer = new OsmDataLayer(ds, null, null);
157 MainApplication.getLayerManager().addLayer(layer);
158 try {
159 assertEquals(3, ds.getWays().size());
160 new JoinAreasAction(false).join(ds.getWays());
161 // join should not have changed anything
162 assertEquals(3, ds.getWays().size());
163 } finally {
164 // Ensure we clean the place before leaving, even if test fails.
165 MainApplication.getLayerManager().removeLayer(layer);
166 }
167 }
168 }
169
170
171 /**
172 * Non-regression test which checks example files in nodist/data
173 * @throws Exception if an error occurs
174 */
175 @Test
176 @SuppressWarnings({ "rawtypes", "unchecked" })
177 public void testExamples() throws Exception {
178 DataSet dsToJoin, dsExpected;
179 try (InputStream is = Files.newInputStream(Paths.get("nodist/data/Join_Areas_Tests.osm"))) {
180 dsToJoin = OsmReader.parseDataSet(is, NullProgressMonitor.INSTANCE);
181 }
182 try (InputStream is = Files.newInputStream(Paths.get("nodist/data/Join_Areas_Tests_joined.osm"))) {
183 dsExpected = OsmReader.parseDataSet(is, NullProgressMonitor.INSTANCE);
184 }
185
186 // set current edit layer
187 MainApplication.getLayerManager().addLayer(new OsmDataLayer(dsToJoin, "join", null));
188
189 Collection<OsmPrimitive> testPrims = dsToJoin.getPrimitives(osm -> osm.get("test") != null);
190 MultiMap<String, OsmPrimitive> tests = new MultiMap<>();
191 for (OsmPrimitive testPrim : testPrims) {
192 tests.put(testPrim.get("test"), testPrim);
193 }
194 for (String test : tests.keySet()) {
195 Collection<OsmPrimitive> primitives = tests.get(test);
196 for (OsmPrimitive osm : primitives) {
197 assertTrue(test + "; expected way, but got: " + osm, osm instanceof Way);
198 }
199 new JoinAreasAction(false).join((Collection) primitives);
200 Collection<OsmPrimitive> joinedCol = dsToJoin.getPrimitives(osm -> !osm.isDeleted() && Objects.equals(osm.get("test"), test));
201 assertEquals("in test " + test + ":", 1, joinedCol.size());
202 Collection<OsmPrimitive> expectedCol = dsExpected.getPrimitives(osm -> !osm.isDeleted() && Objects.equals(osm.get("test"), test));
203 assertEquals("in test " + test + ":", 1, expectedCol.size());
204 OsmPrimitive osmJoined = joinedCol.iterator().next();
205 OsmPrimitive osmExpected = expectedCol.iterator().next();
206 assertTrue("difference in test " + test, isSemanticallyEqual(osmExpected, osmJoined));
207 }
208 }
209
210 /**
211 * Check if 2 primitives are semantically equal as result of a join areas
212 * operation.
213 * @param osm1 first primitive
214 * @param osm2 second primitive
215 * @return true if both primitives are semantically equal
216 */
217 private boolean isSemanticallyEqual(OsmPrimitive osm1, OsmPrimitive osm2) {
218 if (osm1 instanceof Node && osm2 instanceof Node)
219 return isSemanticallyEqualNode((Node) osm1, (Node) osm2);
220 if (osm1 instanceof Way && osm2 instanceof Way)
221 return isSemanticallyEqualWay((Way) osm1, (Way) osm2);
222 if (osm1 instanceof Relation && osm2 instanceof Relation)
223 return isSemanticallyEqualRelation((Relation) osm1, (Relation) osm2);
224 return false;
225 }
226
227 private boolean isSemanticallyEqualRelation(Relation r1, Relation r2) {
228 if (!r1.getKeys().equals(r2.getKeys())) return false;
229 if (r1.getMembersCount() != r2.getMembersCount()) return false;
230 Set<RelationMember> matchCandidates = new HashSet<>(r2.getMembers());
231 for (RelationMember rm : r1.getMembers()) {
232 RelationMember matched = null;
233 for (RelationMember cand : matchCandidates) {
234 if (!rm.getRole().equals(cand.getRole())) continue;
235 if (!isSemanticallyEqual(rm.getMember(), cand.getMember())) continue;
236 matched = cand;
237 break;
238 }
239 if (matched == null) return false;
240 matchCandidates.remove(matched);
241 }
242 return true;
243 }
244
245 private boolean isSemanticallyEqualWay(Way w1, Way w2) {
246 if (!w1.isClosed() || !w2.isClosed()) throw new UnsupportedOperationException();
247 if (!w1.getKeys().equals(w2.getKeys())) return false;
248 if (w1.getNodesCount() != w2.getNodesCount()) return false;
249 int n = w1.getNodesCount() - 1;
250 for (int dir : Arrays.asList(1, -1)) {
251 for (int i = 0; i < n; i++) {
252 boolean different = false;
253 for (int j = 0; j < n; j++) {
254 Node n1 = w1.getNode(j);
255 Node n2 = w2.getNode(Utils.mod(i + dir*j, n));
256 if (!isSemanticallyEqualNode(n1, n2)) {
257 different = true;
258 break;
259 }
260 }
261 if (!different)
262 return true;
263 }
264 }
265 return false;
266 }
267
268 private boolean isSemanticallyEqualNode(Node n1, Node n2) {
269 return n1.hasEqualSemanticAttributes(n2);
270 }
271}
Note: See TracBrowser for help on using the repository browser.