Ticket #17898: 17898.2.patch
| File 17898.2.patch, 26.4 KB (added by , 6 years ago) |
|---|
-
src/org/openstreetmap/josm/actions/SplitWayAction.java
25 25 import javax.swing.JPanel; 26 26 import javax.swing.ListSelectionModel; 27 27 28 import org.openstreetmap.josm.command.MissingDataHelper; 28 29 import org.openstreetmap.josm.command.SplitWayCommand; 29 30 import org.openstreetmap.josm.data.UndoRedoHandler; 30 31 import org.openstreetmap.josm.data.osm.DataSet; … … 293 294 wayToKeep, 294 295 newWays, 295 296 !isMapModeDraw ? newSelection : null, 296 SplitWayCommand.WhenRelationOrderUncertain.ASK_USER_FOR_CONSENT_TO_DOWNLOAD297 MissingDataHelper.WhenRelationOrderUncertain.ASK_USER_FOR_CONSENT_TO_DOWNLOAD 297 298 ); 298 299 299 300 splitWayCommand.ifPresent(result -> { -
src/org/openstreetmap/josm/command/MissingDataHelper.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.command; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import javax.swing.JOptionPane; 7 8 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 9 import org.openstreetmap.josm.gui.MainApplication; 10 import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 11 12 /** 13 * Methods for actions which may corrupt relations. They should make sure that possible membership in relations is known 14 * and that relations are not corrupted by the modification of the member. 15 * @since xxx 16 */ 17 public class MissingDataHelper { 18 19 private MissingDataHelper() { 20 // Hide default constructor for utils classes 21 } 22 23 /** 24 * What to do when the action might modify a member of a relation and either the parents 25 * of the object are not known for certain or the action might need to add members in a specific order and that 26 * order cannot be determined with the available information. 27 * This can be used for unit tests. 28 */ 29 public enum WhenRelationOrderUncertain { 30 /** 31 * Ask the user to consent to downloading the missing information. The user can abort the operation or choose to 32 * proceed without downloading anything. 33 */ 34 ASK_USER_FOR_CONSENT_TO_DOWNLOAD, 35 /** 36 * If needed information is missing, abort the action. 37 */ 38 ABORT, 39 /** 40 * Perform the action even when needed information is missing, without downloading anything and risk to corrupt relations. 41 */ 42 PERFORM_ANYWAY, 43 /** 44 * If needed information is missing, automatically download these without prompting the user. 45 */ 46 DOWNLOAD_MISSING 47 } 48 49 /** 50 * What to do if needed data is missing. 51 */ 52 public enum MissingDataStrategy { 53 /** continue, download needed data */ 54 GO_AHEAD_WITH_DOWNLOADS, 55 /** continue, don't download needed data and risk to break relations */ 56 GO_AHEAD_WITHOUT_DOWNLOADS, 57 /** aboort action */ 58 USER_ABORTED 59 } 60 61 /** 62 * Check given preference key and optionally show dialog to ask user for confirmation regarding the download of missing data. 63 * @param whenRelationOrderUncertain What to do when the action might corrupt relations and additional data is needed 64 * @param preferenceKey the preference key which is used to store the user decision in case the answer is "Yes, download missing data" 65 * @return the strategy to use 66 */ 67 public static MissingDataStrategy getUserDecision(WhenRelationOrderUncertain whenRelationOrderUncertain, String preferenceKey) { 68 switch (whenRelationOrderUncertain) { 69 case ASK_USER_FOR_CONSENT_TO_DOWNLOAD: 70 // Only ask the user about downloading missing data when they haven't consented to this before. 71 if (ConditionalOptionPaneUtil.getDialogReturnValue(preferenceKey) == Integer.MAX_VALUE) { 72 // User has previously told us downloading missing relation members is fine. 73 return MissingDataStrategy.GO_AHEAD_WITH_DOWNLOADS; 74 } 75 // Ask the user. 76 return offerToDownloadMissingDataIfNeeded(preferenceKey); 77 case PERFORM_ANYWAY: 78 return MissingDataStrategy.GO_AHEAD_WITHOUT_DOWNLOADS; 79 case DOWNLOAD_MISSING: 80 return MissingDataStrategy.GO_AHEAD_WITH_DOWNLOADS; 81 case ABORT: 82 default: 83 return MissingDataStrategy.USER_ABORTED; 84 } 85 } 86 87 static MissingDataStrategy offerToDownloadMissingDataIfNeeded(String preferenceKey) { 88 89 JMultilineLabel msg = new JMultilineLabel(tr("Relations might be corrupted by splitting the way. " 90 + "Download missing information from Server?")); 91 92 String[] options = { 93 tr("Yes, download missing data"), 94 tr("No, abort the split operation"), 95 tr("No, perform the split without downloading") 96 }; 97 98 msg.setMaxWidth(600); 99 100 int ret = JOptionPane.showOptionDialog( 101 MainApplication.getMainFrame(), 102 msg, 103 tr("Download missing information about relations?"), 104 JOptionPane.OK_CANCEL_OPTION, 105 JOptionPane.QUESTION_MESSAGE, 106 null, 107 options, 108 options[0] 109 ); 110 111 switch (ret) { 112 case JOptionPane.OK_OPTION: 113 // Ask the user if they want to do this automatically from now on. We only ask this for the download 114 // action, because automatically cancelling is confusing (the user can't tell why this happened), and 115 // automatically performing the split without downloading missing parent relations is 116 // likely to break relations. 117 ConditionalOptionPaneUtil.showMessageDialog( 118 preferenceKey, 119 MainApplication.getMainFrame(), 120 tr("Missing information about relations will be downloaded. " 121 + "Should this be done automatically from now on?"), 122 tr("Download missing information about relations"), 123 JOptionPane.INFORMATION_MESSAGE 124 ); 125 return MissingDataStrategy.GO_AHEAD_WITH_DOWNLOADS; 126 case JOptionPane.CANCEL_OPTION: 127 return MissingDataStrategy.GO_AHEAD_WITHOUT_DOWNLOADS; 128 default: 129 return MissingDataStrategy.USER_ABORTED; 130 } 131 } 132 133 } -
src/org/openstreetmap/josm/command/SplitWayCommand.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.command; 3 3 4 import static org.openstreetmap.josm.command.SplitWayCommand.MissingMemberStrategy.GO_AHEAD_WITHOUT_DOWNLOADS;5 import static org.openstreetmap.josm.command.SplitWayCommand.MissingMemberStrategy.GO_AHEAD_WITH_DOWNLOADS;6 import static org.openstreetmap.josm.command.SplitWayCommand.MissingMemberStrategy.USER_ABORTED;7 import static org.openstreetmap.josm.command.SplitWayCommand.WhenRelationOrderUncertain.ASK_USER_FOR_CONSENT_TO_DOWNLOAD;8 4 import static org.openstreetmap.josm.tools.I18n.tr; 9 5 import static org.openstreetmap.josm.tools.I18n.trn; 10 6 … … 24 20 import java.util.Set; 25 21 import java.util.function.Consumer; 26 22 27 import javax.swing.JOptionPane;28 23 import org.openstreetmap.josm.command.MissingDataHelper.MissingDataStrategy; 24 import org.openstreetmap.josm.command.MissingDataHelper.WhenRelationOrderUncertain; 29 25 import org.openstreetmap.josm.data.osm.DataSet; 30 26 import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 31 27 import org.openstreetmap.josm.data.osm.Node; … … 34 30 import org.openstreetmap.josm.data.osm.Relation; 35 31 import org.openstreetmap.josm.data.osm.RelationMember; 36 32 import org.openstreetmap.josm.data.osm.Way; 37 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;38 33 import org.openstreetmap.josm.gui.ExceptionDialogUtil; 39 34 import org.openstreetmap.josm.gui.MainApplication; 40 35 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 41 import org.openstreetmap.josm.gui.widgets.JMultilineLabel;42 36 import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 37 import org.openstreetmap.josm.io.OsmServerBackreferenceReader; 43 38 import org.openstreetmap.josm.io.OsmTransferException; 44 39 import org.openstreetmap.josm.spi.preferences.Config; 45 40 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 55 50 public class SplitWayCommand extends SequenceCommand { 56 51 57 52 private static volatile Consumer<String> warningNotifier = Logging::warn; 58 private static final String DOWNLOAD_MISSING_PREF_KEY = "split_way_download_missing_ members";53 private static final String DOWNLOAD_MISSING_PREF_KEY = "split_way_download_missing_data"; 59 54 60 55 private static final class RelationInformation { 61 56 boolean warnme; … … 285 280 286 281 // This method could be refactored to use an Optional in the future, but would need to be deprecated first 287 282 // to phase out use by plugins. 288 return splitWay(way, wayChunks, selection, splitStrategy, ASK_USER_FOR_CONSENT_TO_DOWNLOAD).orElse(null); 283 return splitWay(way, wayChunks, selection, splitStrategy, 284 WhenRelationOrderUncertain.ASK_USER_FOR_CONSENT_TO_DOWNLOAD).orElse(null); 289 285 } 290 286 291 287 /** … … 344 340 List<Way> newWays, 345 341 List<OsmPrimitive> newSelection, 346 342 WhenRelationOrderUncertain whenRelationOrderUncertain) { 347 if (whenRelationOrderUncertain == null) whenRelationOrderUncertain = ASK_USER_FOR_CONSENT_TO_DOWNLOAD;343 if (whenRelationOrderUncertain == null) whenRelationOrderUncertain = WhenRelationOrderUncertain.ASK_USER_FOR_CONSENT_TO_DOWNLOAD; 348 344 349 345 final int indexOfWayToKeep = newWays.indexOf(wayToKeep); 350 346 newWays.remove(wayToKeep); 351 347 348 MissingDataStrategy missingDataStrategy = null; 349 if (!way.isNew() && (way.getDataSet().getDataSourceBounds().isEmpty() 350 || way.getNodes().stream().allMatch(Node::isOutsideDownloadArea))) { 351 missingDataStrategy = MissingDataHelper.getUserDecision(whenRelationOrderUncertain, DOWNLOAD_MISSING_PREF_KEY); 352 switch (missingDataStrategy) { 353 case GO_AHEAD_WITH_DOWNLOADS: 354 try { 355 downloadParents(way); 356 } catch (OsmTransferException e) { 357 ExceptionDialogUtil.explainException(e); 358 return Optional.empty(); 359 } 360 break; 361 case GO_AHEAD_WITHOUT_DOWNLOADS: 362 // Proceed with the split with the information we have. 363 // This can mean that we break relations. 364 break; 365 case USER_ABORTED: 366 default: 367 return Optional.empty(); 368 } 369 } 370 352 371 // Figure out the order of relation members (if any). 353 372 Analysis analysis = analyseSplit(way, wayToKeep, newWays, indexOfWayToKeep); 354 373 … … 380 399 } 381 400 } 382 401 383 MissingMemberStrategy missingMemberStrategy;384 402 if (relationsNeedingMoreMembers.isEmpty()) { 385 403 // The split can be performed without any extra downloads. 386 missing MemberStrategy =GO_AHEAD_WITHOUT_DOWNLOADS;404 missingDataStrategy = MissingDataStrategy.GO_AHEAD_WITHOUT_DOWNLOADS; 387 405 } else { 388 switch (whenRelationOrderUncertain) { 389 case ASK_USER_FOR_CONSENT_TO_DOWNLOAD: 390 // If the analysis shows that for some relations missing members should be downloaded, offer the user the 391 // chance to consent to this. 392 393 // Only ask the user about downloading missing members when they haven't consented to this before. 394 if (ConditionalOptionPaneUtil.getDialogReturnValue(DOWNLOAD_MISSING_PREF_KEY) == Integer.MAX_VALUE) { 395 // User has previously told us downloading missing relation members is fine. 396 missingMemberStrategy = GO_AHEAD_WITH_DOWNLOADS; 397 } else { 398 // Ask the user. 399 missingMemberStrategy = offerToDownloadMissingMembersIfNeeded(analysis, relationsNeedingMoreMembers); 400 } 401 break; 402 case SPLIT_ANYWAY: 403 missingMemberStrategy = GO_AHEAD_WITHOUT_DOWNLOADS; 404 break; 405 case DOWNLOAD_MISSING_MEMBERS: 406 missingMemberStrategy = GO_AHEAD_WITH_DOWNLOADS; 407 break; 408 case ABORT: 409 default: 410 missingMemberStrategy = USER_ABORTED; 411 break; 406 if (missingDataStrategy == null) { 407 missingDataStrategy = MissingDataHelper.getUserDecision(whenRelationOrderUncertain, DOWNLOAD_MISSING_PREF_KEY); 412 408 } 413 409 } 414 415 switch (missingMemberStrategy) { 416 case GO_AHEAD_WITH_DOWNLOADS: 417 try { 418 downloadMissingMembers(incompleteMembers); 419 } catch (OsmTransferException e) { 420 ExceptionDialogUtil.explainException(e); 421 return Optional.empty(); 422 } 423 // If missing relation members were downloaded, perform the analysis again to find the relation 424 // member order for all relations. 425 analysis = analyseSplit(way, wayToKeep, newWays, indexOfWayToKeep); 426 return Optional.of(splitBasedOnAnalyses(way, newWays, newSelection, analysis, indexOfWayToKeep)); 427 case GO_AHEAD_WITHOUT_DOWNLOADS: 428 // Proceed with the split with the information we have. 429 // This can mean that there are no missing members we want, or that the user chooses to continue 430 // the split without downloading them. 431 return Optional.of(splitBasedOnAnalyses(way, newWays, newSelection, analysis, indexOfWayToKeep)); 432 case USER_ABORTED: 433 default: 410 if (missingDataStrategy == MissingDataStrategy.USER_ABORTED) 411 return Optional.empty(); 412 if (missingDataStrategy == MissingDataStrategy.GO_AHEAD_WITH_DOWNLOADS) { 413 try { 414 downloadMissingMembers(incompleteMembers); 415 } catch (OsmTransferException e) { 416 ExceptionDialogUtil.explainException(e); 434 417 return Optional.empty(); 418 } 419 // If missing relation members were downloaded, perform the analysis again to find the relation 420 // member order for all relations. 421 analysis = analyseSplit(way, wayToKeep, newWays, indexOfWayToKeep); 435 422 } 423 return Optional.of(splitBasedOnAnalyses(way, newWays, newSelection, analysis, indexOfWayToKeep)); 436 424 } 437 425 438 426 static Analysis analyseSplit(Way way, … … 472 460 int ir = 0; 473 461 List<RelationMember> relationMembers = r.getMembers(); 474 462 for (RelationMember rm : relationMembers) { 475 if (rm. isWay() && rm.getMember() == way) {463 if (rm.getMember() == way) { 476 464 boolean insert = true; 477 465 if (relationSpecialTypes.containsKey(type) && "restriction".equals(relationSpecialTypes.get(type))) { 478 466 RelationInformation rValue = treatAsRestriction(r, rm, c, newWays, way, changedWay); … … 541 529 // won't help in any case. 542 530 direction = Direction.IRRELEVANT; 543 531 } 532 if (direction == Direction.UNKNOWN && (!way.firstNode().isOutsideDownloadArea() 533 || way.lastNode().isOutsideDownloadArea())) { 534 // check if any other complete way in the relation is connected to the way 535 // if so, the order of the relation is broken 536 for (int i = 0; i < r.getMembersCount(); i++) { 537 if (i >= ir - 1 && i <= ir + 1) 538 continue; 539 RelationMember rmTest = r.getMember(i); 540 if (rmTest.isWay() && !rmTest.getMember().isIncomplete() && 541 (way.isFirstLastNode(rmTest.getWay().firstNode()) 542 || way.isFirstLastNode(rm.getWay().lastNode()))) { 543 direction = Direction.IRRELEVANT; 544 break; 545 } 546 } 547 } 544 548 } 545 549 } else { 546 550 int k = 1; … … 627 631 } 628 632 } 629 633 630 static MissingMemberStrategy offerToDownloadMissingMembersIfNeeded(Analysis analysis,631 List<Relation> relationsNeedingMoreMembers) {632 String[] options = {633 tr("Yes, download the missing members"),634 tr("No, abort the split operation"),635 tr("No, perform the split without downloading")636 };637 638 String msgMemberOfRelations = trn(639 "This way is part of a relation.",640 "This way is part of {0} relations.",641 analysis.getNumberOfRelations(),642 analysis.getNumberOfRelations()643 );644 645 String msgReferToRelations;646 if (analysis.getNumberOfRelations() == 1) {647 msgReferToRelations = tr("this relation");648 } else if (analysis.getNumberOfRelations() == relationsNeedingMoreMembers.size()) {649 msgReferToRelations = tr("these relations");650 } else {651 msgReferToRelations = trn(652 "one relation",653 "{0} relations",654 relationsNeedingMoreMembers.size(),655 relationsNeedingMoreMembers.size()656 );657 }658 659 String msgRelationsMissingData = tr(660 "For {0} the correct order of the new way parts could not be determined. " +661 "To fix this, some missing relation members should be downloaded first.",662 msgReferToRelations663 );664 665 JMultilineLabel msg = new JMultilineLabel(msgMemberOfRelations + " " + msgRelationsMissingData);666 msg.setMaxWidth(600);667 668 int ret = JOptionPane.showOptionDialog(669 MainApplication.getMainFrame(),670 msg,671 tr("Download missing relation members?"),672 JOptionPane.OK_CANCEL_OPTION,673 JOptionPane.QUESTION_MESSAGE,674 null,675 options,676 options[0]677 );678 679 switch (ret) {680 case JOptionPane.OK_OPTION:681 // Ask the user if they want to do this automatically from now on. We only ask this for the download682 // action, because automatically cancelling is confusing (the user can't tell why this happened), and683 // automatically performing the split without downloading missing members despite needing them is684 // likely to break a lot of routes. The user also can't tell the difference between a split that needs685 // no downloads at all, and this special case where downloading missing relation members will prevent686 // broken relations.687 ConditionalOptionPaneUtil.showMessageDialog(688 DOWNLOAD_MISSING_PREF_KEY,689 MainApplication.getMainFrame(),690 tr("Missing relation members will be downloaded. Should this be done automatically from now on?"),691 tr("Downloading missing relation members"),692 JOptionPane.INFORMATION_MESSAGE693 );694 return GO_AHEAD_WITH_DOWNLOADS;695 case JOptionPane.CANCEL_OPTION:696 return GO_AHEAD_WITHOUT_DOWNLOADS;697 default:698 return USER_ABORTED;699 }700 }701 702 634 static void downloadMissingMembers(Set<OsmPrimitive> incompleteMembers) throws OsmTransferException { 703 635 // Download the missing members. 704 636 MultiFetchServerObjectReader reader = MultiFetchServerObjectReader.create(); … … 708 640 MainApplication.getLayerManager().getEditLayer().mergeFrom(ds); 709 641 } 710 642 643 private static void downloadParents(Way way) throws OsmTransferException { 644 // Download possible parent relations 645 OsmServerBackreferenceReader reader = new OsmServerBackreferenceReader(way); 646 DataSet ds = reader.parseOsm(NullProgressMonitor.INSTANCE); 647 MainApplication.getLayerManager().getEditLayer().mergeFrom(ds); 648 } 649 711 650 static SplitWayCommand splitBasedOnAnalyses(Way way, 712 651 List<Way> newWays, 713 652 List<OsmPrimitive> newSelection, … … 879 818 return relationSpecialTypes; 880 819 } 881 820 882 /**883 * What to do when the split way is part of relations, and the order of the new parts in the relation cannot be884 * determined without downloading missing relation members.885 */886 public enum WhenRelationOrderUncertain {887 /**888 * Ask the user to consent to downloading the missing members. The user can abort the operation or choose to889 * proceed without downloading anything.890 */891 ASK_USER_FOR_CONSENT_TO_DOWNLOAD,892 /**893 * If there are relation members missing, and these are needed to determine the order of the new parts in894 * that relation, abort the split operation.895 */896 ABORT,897 /**898 * If there are relation members missing, and these are needed to determine the order of the new parts in899 * that relation, continue with the split operation anyway, without downloading anything. Caution: use this900 * option with care.901 */902 SPLIT_ANYWAY,903 /**904 * If there are relation members missing, and these are needed to determine the order of the new parts in905 * that relation, automatically download these without prompting the user.906 */907 DOWNLOAD_MISSING_MEMBERS908 }909 910 821 static class RelationAnalysis { 911 822 private final Relation relation; 912 823 private final RelationMember relationMember; … … 959 870 ROLE 960 871 } 961 872 962 enum MissingMemberStrategy {963 GO_AHEAD_WITH_DOWNLOADS,964 GO_AHEAD_WITHOUT_DOWNLOADS,965 USER_ABORTED966 }967 873 } -
test/data/regress/18596/data.osm
1 1 <?xml version='1.0' encoding='UTF-8'?> 2 2 <osm version='0.6' generator='JOSM' upload='never' download='never'> 3 <bounds minlat='53.1855593' minlon='5.786497' maxlat='53.1912994' maxlon='5.799107' origin='fake bounds' /> 3 4 <node id='1001' version='1' visible='true' lat='53.18916486972' lon='5.79536381868' /> 4 5 <node id='1002' version='1' visible='true' lat='53.19109032103' lon='5.79066925796' /> 5 6 <node id='1003' version='1' visible='true' lat='53.18576597652' lon='5.79492806044' /> -
test/unit/org/openstreetmap/josm/command/SplitWayCommandTest.java
17 17 import org.junit.Test; 18 18 import org.openstreetmap.josm.TestUtils; 19 19 import org.openstreetmap.josm.command.SplitWayCommand.Strategy; 20 import org.openstreetmap.josm.data.Bounds; 21 import org.openstreetmap.josm.data.DataSource; 20 22 import org.openstreetmap.josm.data.UndoRedoHandler; 21 23 import org.openstreetmap.josm.data.coor.LatLon; 22 24 import org.openstreetmap.josm.data.osm.DataSet; … … 150 152 @Test 151 153 public void testOneMemberOrderedRelationShowsWarningTest() { 152 154 final DataSet dataSet = new DataSet(); 155 dataSet.addDataSource(new DataSource(new Bounds(-180, -90, 180, 90), "Test")); 153 156 154 157 // Positive IDs to mark that these ways are incomplete (i.e., no nodes loaded). 155 158 final Way w1 = new Way(1); … … 180 183 SplitWayCommand.buildSplitChunks(w2, Collections.singletonList(n2)), 181 184 new ArrayList<>(), 182 185 Strategy.keepLongestChunk(), 183 SplitWayCommand.WhenRelationOrderUncertain.ABORT186 MissingDataHelper.WhenRelationOrderUncertain.ABORT 184 187 ); 185 188 186 189 assertFalse(result.isPresent()); … … 311 314 new ArrayList<>(), 312 315 Strategy.keepLongestChunk(), 313 316 // This split requires no additional downloads. 314 SplitWayCommand.WhenRelationOrderUncertain.ABORT317 MissingDataHelper.WhenRelationOrderUncertain.ABORT 315 318 ); 316 319 317 320 assertTrue(result.isPresent()); … … 359 362 new ArrayList<>(), 360 363 Strategy.keepLongestChunk(), 361 364 // This split requires no additional downloads. If any are needed, this command will fail. 362 SplitWayCommand.WhenRelationOrderUncertain.ABORT365 MissingDataHelper.WhenRelationOrderUncertain.ABORT 363 366 ); 364 367 365 368 // Should not result in aborting the split.
