001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions.corrector; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.util.Arrays; 008import java.util.Map; 009 010import javax.swing.JOptionPane; 011 012import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 013import org.openstreetmap.josm.data.osm.Tag; 014import org.openstreetmap.josm.data.osm.TagCollection; 015import org.openstreetmap.josm.data.osm.Tagged; 016import org.openstreetmap.josm.data.osm.Way; 017import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 018import org.openstreetmap.josm.gui.MainApplication; 019import org.openstreetmap.josm.tools.UserCancelException; 020import org.openstreetmap.josm.tools.Utils; 021 022/** 023 * A ReverseWayNoTagCorrector warns about ways that should not be reversed 024 * because their semantic meaning cannot be preserved in that case. 025 * E.g. natural=coastline, natural=cliff, barrier=retaining_wall cannot be changed. 026 * @see ReverseWayTagCorrector for handling of tags that can be modified (oneway=yes, etc.) 027 * @since 5724 028 */ 029public final class ReverseWayNoTagCorrector { 030 031 private ReverseWayNoTagCorrector() { 032 // Hide default constructor for utils classes 033 } 034 035 /** 036 * Tags that imply a semantic meaning from the way direction and cannot be changed. 037 */ 038 private static final TagCollection DIRECTIONAL_TAGS = new TagCollection(Arrays.asList( 039 new Tag("natural", "coastline"), 040 new Tag("natural", "cliff"), 041 new Tag("barrier", "guard_rail"), 042 new Tag("barrier", "kerb"), 043 new Tag("barrier", "retaining_wall"), 044 new Tag("barrier", "city_wall"), 045 new Tag("man_made", "embankment") 046 )); 047 048 /** 049 * Replies the tags that imply a semantic meaning from <code>way</code> direction and cannot be changed. 050 * @param way The way to look for 051 * @return tags that imply a semantic meaning from <code>way</code> direction and cannot be changed 052 */ 053 public static TagCollection getDirectionalTags(Tagged way) { 054 final TagCollection collection = new TagCollection(); 055 for (Map.Entry<String, String> entry : way.getKeys().entrySet()) { 056 final Tag tag = new Tag(entry.getKey(), entry.getValue()); 057 final boolean isDirectional = DIRECTIONAL_TAGS.contains(tag) || tag.isDirectionKey(); 058 if (isDirectional) { 059 final boolean cannotBeCorrected = ReverseWayTagCorrector.getTagCorrections(tag).isEmpty(); 060 061 if (cannotBeCorrected && !way.isKeyTrue("two_sided")) { 062 // two_sided=yes is a special (documented) barrier=city_wall attribute, see #19714 063 collection.add(tag); 064 } 065 } 066 } 067 return collection; 068 } 069 070 /** 071 * Tests whether way can be reversed without semantic change. 072 * Looks for tags like natural=cliff, barrier=retaining_wall. 073 * @param way The way to check 074 * @return false if the semantic meaning change if the way is reversed, true otherwise. 075 */ 076 public static boolean isReversible(Tagged way) { 077 return getDirectionalTags(way).isEmpty(); 078 } 079 080 private static boolean confirmReverseWay(Way way, TagCollection tags) { 081 String msg = trn( 082 // Singular, if a single tag is impacted 083 "<html>You are going to reverse the way ''{0}''," 084 + "<br/> whose semantic meaning of its tag ''{1}'' is defined by its direction.<br/>" 085 + "Do you really want to change the way direction, thus its semantic meaning?</html>", 086 // Plural, if several tags are impacted 087 "<html>You are going to reverse the way ''{0}''," 088 + "<br/> whose semantic meaning of these tags are defined by its direction:<br/>{1}" 089 + "Do you really want to change the way direction, thus its semantic meaning?</html>", 090 tags.size(), 091 Utils.escapeReservedCharactersHTML(way.getDisplayName(DefaultNameFormatter.getInstance())), 092 Utils.joinAsHtmlUnorderedList(tags) 093 ); 094 int ret = ConditionalOptionPaneUtil.showOptionDialog( 095 "reverse_directional_way", 096 MainApplication.getMainFrame(), 097 msg, 098 tr("Reverse directional way."), 099 JOptionPane.YES_NO_CANCEL_OPTION, 100 JOptionPane.WARNING_MESSAGE, 101 null, 102 null 103 ); 104 switch(ret) { 105 case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: 106 case JOptionPane.YES_OPTION: 107 return true; 108 default: 109 return false; 110 } 111 } 112 113 /** 114 * Checks the given way can be safely reversed and asks user to confirm the operation if it not the case. 115 * @param way The way to check 116 * @throws UserCancelException If the user cancels the operation 117 */ 118 public static void checkAndConfirmReverseWay(Way way) throws UserCancelException { 119 TagCollection tags = getDirectionalTags(way); 120 if (!tags.isEmpty() && !confirmReverseWay(way, tags)) { 121 throw new UserCancelException(); 122 } 123 } 124}