Changeset 194 in josm


Ignore:
Timestamp:
2007-01-12T18:37:38+01:00 (18 years ago)
Author:
imi
Message:
  • added some new modes to the SplitWayAction (thanks Frederik)
Location:
src/org/openstreetmap/josm
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/org/openstreetmap/josm/actions/SplitWayAction.java

    r186 r194  
    55import java.awt.event.ActionEvent;
    66import java.awt.event.KeyEvent;
    7 import java.util.Arrays;
     7import java.util.ArrayList;
    88import java.util.Collection;
     9import java.util.Collections;
     10import java.util.Comparator;
     11import java.util.HashMap;
    912import java.util.HashSet;
     13import java.util.Iterator;
     14import java.util.LinkedList;
     15import java.util.List;
     16import java.util.Map.Entry;
    1017
    1118import javax.swing.JOptionPane;
     
    2229import org.openstreetmap.josm.data.osm.Way;
    2330import org.openstreetmap.josm.data.osm.visitor.NameVisitor;
     31import org.openstreetmap.josm.data.osm.visitor.Visitor;
    2432
    2533/**
     34 * Splits a way into multiple ways (all identical except the segments
     35 * belonging to the way).
     36 *
     37 * Various split modes are used depending on what is selected.
     38 *
     39 * 1. One or more NODES (and, optionally, also one way) selected:
     40 *
     41 * (All nodes must be part of the same way. If a way is also selected, that way
     42 * must contain all selected nodes.)
     43 *
     44 * Way is split AT the node(s) into contiguous ways. If the original contained
     45 * one or more parts that were not reachable from any of the nodes, they form an
     46 * extra new way. Examples (numbers are unselected nodes, letters are selected
     47 * nodes)
     48 *
     49 * 1---A---2  becomes  1---A and A---2
     50 *
     51 * 1---A---2---B---3  becomes  1---A and A---2---B and B---3
     52 * 
     53 *     2                   
     54 *     |                   
     55 * 1---A---3  becomes  1---A and 2---A and A---3
    2656 *
    27  * @author framm
     57 * 1---A---2  3---4  becomes  1---A and A---2 and 3---4
     58 *
     59 * If the selected node(s) do not clearly define the way that is to be split,
     60 * then the way must be selected for disambiguation (e.g. you have two ways,
     61 * 1---2---3 and 4---2---5, and select node 2, then you must also select the
     62 * way you want to split).
     63 *
     64 * This function will result in at least two ways, unless the selected node is
     65 * at the end of the way AND the way is contiguous, which will lead to an error
     66 * message.
     67 *
     68 * After executing the operation, the selection will be cleared.
     69 *
     70 * 2. One or more SEGMENTS (and, optionally, also one way) selected:
     71 *
     72 * (All segments must be part of the same way)
     73 *
     74 * The way is split in a fashion that makes a new way from the selected segments,
     75 * i.e. the selected segments are removed from the way to form a new one.
     76 *
     77 * This function will result in exactly two ways.
     78 *
     79 * If splitting the segments out of the way makes a non-contiguous part from
     80 * something that was contiguous before, the action is aborted and an error
     81 * message is shown.
     82 *
     83 * 3. Exactly one WAY selected
     84 *
     85 * If the way is contiguous, you will get an error message. If the way is not
     86 * contiguous it is split it into 2...n contiguous ways.
    2887 */
     88
    2989public class SplitWayAction extends JosmAction implements SelectionChangedListener {
    3090
    31         private Way way;
    32         private Node node;
     91        private Way selectedWay;
     92        private List<Node> selectedNodes;
     93        private List<Segment> selectedSegments;
    3394
    3495        /**
     
    41102
    42103        /**
    43          * Called when the action is executed
     104         * Called when the action is executed.
     105         *
     106         * This method performs an expensive check whether the selection clearly defines one
     107         * of the split actions outlined above, and if yes, calls the splitWay method.
    44108         */
    45109        public void actionPerformed(ActionEvent e) {
    46                 if (!checkSelection(Main.ds.getSelected())) {
    47                         JOptionPane.showMessageDialog(Main.parent, tr("Select exactly one way to split and one node to split the way at."));
     110
     111                Collection<OsmPrimitive> selection = Main.ds.getSelected();
     112
     113                if (!checkSelection(selection)) {
     114                        JOptionPane.showMessageDialog(Main.parent, tr("The current selection cannot be used for splitting."));
    48115                        return;
    49116                }
    50117
    51                 if (way == null) {
    52                         for (Way w : Main.ds.ways) {
    53                                 for (Segment s : w.segments) {
    54                                         if (s.from == node || s.to == node) {
    55                                                 if (way != null) {
    56                                                         JOptionPane.showMessageDialog(Main.parent, tr("The selected node belongs to more than one way. Please select both, the node and the way to split."));
    57                                                         return;
     118                selectedWay = null;
     119                selectedNodes = null;
     120                selectedSegments = null;
     121
     122                Visitor splitVisitor = new Visitor(){
     123                        public void visit(Node n) {
     124                                if (selectedNodes == null)
     125                                        selectedNodes = new LinkedList<Node>();
     126                                selectedNodes.add(n);
     127            }
     128                        public void visit(Segment s) {
     129                                if (selectedSegments == null)
     130                                        selectedSegments = new LinkedList<Segment>();
     131                                selectedSegments.add(s);
     132            }
     133                        public void visit(Way w) {
     134                                selectedWay = w;
     135            }
     136                };
     137               
     138                for (OsmPrimitive p : selection)
     139                        p.visit(splitVisitor);
     140
     141                // If only nodes are selected, try to guess which way to split. This works if there
     142                // is exactly one way that all nodes are part of.
     143                if (selectedWay == null && selectedNodes != null) {
     144                        HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();
     145                        for (Node n : selectedNodes) {
     146                                for (Way w : Main.ds.ways) {
     147                                        for (Segment s : w.segments) {
     148                                                if (n.equals(s.from) || n.equals(s.to)) {
     149                                                        Integer old = wayOccurenceCounter.get(w);
     150                                                        wayOccurenceCounter.put(w, (old == null) ? 1 : old+1);
     151                                                        break;
    58152                                                }
    59                                                 way = w;
     153                                        }
     154                                }
     155                        }
     156                        if (wayOccurenceCounter.isEmpty()) {
     157                                JOptionPane.showMessageDialog(Main.parent, tr("The selected node(s) is (are) not part of any way."));
     158                                return;
     159                        }
     160
     161                        for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
     162                                if (entry.getValue().equals(selectedNodes.size())) {
     163                                        if (selectedWay != null) {
     164                                                JOptionPane.showMessageDialog(Main.parent, tr("There is more than one way using the node(s) you selected. Please select the way also."));
     165                                                return;
     166                                        }
     167                                        selectedWay = entry.getKey();
     168                                }
     169                        }
     170
     171                        if (selectedWay == null) {
     172                                JOptionPane.showMessageDialog(Main.parent, tr("The selected nodes do not share the same way."));
     173                                return;
     174                        }
     175
     176                        // If a way and nodes are selected, verify that the nodes are part of the way.
     177                } else if (selectedWay != null && selectedNodes != null) {
     178
     179                        HashSet<Node> nds = new HashSet<Node>(selectedNodes);
     180                        for (Segment s : selectedWay.segments) {
     181                                nds.remove(s.from);
     182                                nds.remove(s.to);
     183                        }
     184                        if (!nds.isEmpty()) {
     185                                JOptionPane.showMessageDialog(Main.parent, tr("The selected way does not contain (all) the selected node(s)."));
     186                                return;
     187                        }
     188
     189                        // If only segments are selected, guess which way to use.
     190                } else if (selectedWay == null && selectedSegments != null) {
     191
     192                        HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();
     193                        for (Segment s : selectedSegments) {
     194                                for (Way w : Main.ds.ways) {
     195                                        if (w.segments.contains(s)) {
     196                                                Integer old = wayOccurenceCounter.get(w);
     197                                                wayOccurenceCounter.put(w, (old == null) ? 1 : old+1);
    60198                                                break;
    61199                                        }
    62200                                }
    63201                        }
    64                 }
    65                 if (way == null) {
    66                         JOptionPane.showMessageDialog(Main.parent, tr("The seleced node is not part of any way."));
    67                         return;
    68                 }
    69                
    70                 boolean node_found = false;
    71                 for (Segment s : way.segments) {
     202                        if (wayOccurenceCounter.isEmpty()) {
     203                                JOptionPane.showMessageDialog(Main.parent, tr("The selected segment(s) is (are) not part of any way."));
     204                                return;
     205                        }
     206
     207                        for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
     208                                if (entry.getValue().equals(selectedSegments.size())) {
     209                                        if (selectedWay != null) {
     210                                                JOptionPane.showMessageDialog(Main.parent, tr("There is more than one way using the segment(s) you selected. Please select the way also."));
     211                                                return;
     212                                        }
     213                                        selectedWay = entry.getKey();
     214                                }
     215                        }
     216
     217                        if (selectedWay == null) {
     218                                JOptionPane.showMessageDialog(Main.parent, tr("The selected segments do not share the same way."));
     219                                return;
     220                        }
     221
     222                        // If a way and segments are selected, verify that the segments are part of the way.
     223                } else if (selectedWay != null && selectedSegments != null) {
     224
     225                        if (!selectedWay.segments.containsAll(selectedSegments)) {
     226                                JOptionPane.showMessageDialog(Main.parent, tr("The selected way does not contain (all) the selected segment(s)."));
     227                                return;
     228                        }
     229                }
     230
     231                // finally check if the selected way is complete.
     232                for (Segment s : selectedWay.segments) {
    72233                        if (s.incomplete) {
    73234                                JOptionPane.showMessageDialog(Main.parent, tr("Warning: This way is incomplete. Try to download it before splitting."));
    74235                                return;
    75236                        }
    76                         if (!node_found && (s.from.equals(node) || s.to.equals(node))) {
    77                                 node_found = true;
    78                         }
    79                 }
    80 
    81                 if (!node_found) {
    82                         JOptionPane.showMessageDialog(Main.parent, tr("The seleced node is not part of the selected way."));
    83                         return;
    84                 }
    85 
     237                }
     238
     239                // and then do the work.
    86240                splitWay();
    87241        }
    88242
    89243        /**
    90          * Checks if the selection consists of eactly one way and one node.
    91          * Does not check whether the node is part of the way.
    92          */
     244         * Checks if the selection consists of something we can work with.
     245         * Checks only if the number and type of items selected looks good;
     246         * does not check whether the selected items are really a valid
     247         * input for splitting (this would be too expensive to be carried
     248         * out from the selectionChanged listener).
     249         */     
    93250        private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
    94                 if (selection.isEmpty() || selection.size() > 2)
    95                         return false;
    96                 way = null;
    97                 node = null;
     251                boolean way = false;
     252                boolean segment = false;
     253                boolean node = false;
    98254                for (OsmPrimitive p : selection) {
    99                         if ((p instanceof Way) && (way == null)) {
    100                                 way = (Way)p;
    101                         } else if ((p instanceof Node) && (node == null)) {
    102                                 node = (Node)p;
    103                         } else {
     255                        if (p instanceof Way && !way)
     256                                way = true;
     257                        else if (p instanceof Node && !segment)
     258                                node = true;
     259                        else if (p instanceof Segment && !node)
     260                                segment = true;
     261                        else
    104262                                return false;
    105                         }
    106                 }
    107                 return node != null;
     263                }
     264                return way || segment || node;
    108265        }
    109266
    110267        /**
    111          * Split a way into two parts.
     268         * Split a way into two or more parts, starting at a selected node.
     269         *
     270         * @param way the way to split
     271         * @param nodes the node(s) to split the way at; must be part of the way.
    112272         */
    113273        private void splitWay() {
    114274
    115                 HashSet<Node> nodesInFirstHalf = new HashSet<Node>();
    116 
    117                 for (Segment s : way.segments) {
    118                         if (s.from.equals(node)) {
    119                                 nodesInFirstHalf.add(s.to);
    120                                 break;
    121                         } else if (s.to.equals(node)) {
    122                                 nodesInFirstHalf.add(s.from);
    123                                 break;
    124                         }
    125                 }
    126 
    127                 boolean loop = true;
    128                 while (loop) {
    129                         loop = false;
    130                         for (Segment s : way.segments) {
    131                                 if (nodesInFirstHalf.contains(s.from) && (!s.to.equals(node))
    132                                         && !nodesInFirstHalf.contains(s.to)) {
    133                                         nodesInFirstHalf.add(s.to);
    134                                         loop = true;
    135                                 } else if (nodesInFirstHalf.contains(s.to)
    136                                         && (!s.from.equals(node))
    137                                         && !nodesInFirstHalf.contains(s.from)) {
    138                                         nodesInFirstHalf.add(s.from);
    139                                         loop = true;
    140                                 }
    141                         }
    142                 }
    143 
    144                 Way wayToAdd = new Way(way);
    145                 wayToAdd.id = 0;
    146                 Way changedWay = new Way(way);
    147 
    148                 for (Segment s : way.segments) {
    149                         if (nodesInFirstHalf.contains(s.from) || nodesInFirstHalf.contains(s.to)) {
    150                                 changedWay.segments.remove(s);
     275                // The basic idea is to first divide all segments forming this way into
     276                // groups, and later form new ways according to the groups. Initally,
     277                // all segments are copied into allSegments, and then gradually removed
     278                // from there as new groups are built.
     279
     280                LinkedList<Segment> allSegments = new LinkedList<Segment>();
     281                allSegments.addAll(selectedWay.segments);
     282                List<List<Segment>> segmentSets = new ArrayList<List<Segment>>();
     283
     284                if (selectedNodes != null) {
     285
     286                        // This is the "split at node" mode.
     287
     288                        boolean split = true;
     289                        Segment splitSeg = null;
     290                        while (split) {
     291                                split = false;
     292
     293                                // Look for a "split segment". A split segment is any segment
     294                                // that touches one of the split nodes and has not yet been
     295                                // assigned to one of the segment groups.
     296                                for (Segment s : allSegments) {
     297                                        for (Node node : selectedNodes) {
     298                                                if (s.from.equals(node) || s.to.equals(node)) {
     299                                                        split = true;
     300                                                        splitSeg = s;
     301                                                        break;
     302                                                }
     303                                        }
     304                                        if (split)
     305                                                break;
     306                                }
     307
     308                                // If a split segment was found, move this segment and all segments
     309                                // connected to it into a new segment group, stopping only if we
     310                                // reach another split node. Segment moving is done recursively by
     311                                // the moveSegments method.
     312                                if (split) {
     313                                        ArrayList<Segment> subSegments = new ArrayList<Segment>();
     314                                        moveSegments(allSegments, subSegments, splitSeg, selectedNodes);
     315                                        segmentSets.add(subSegments);
     316                                }
     317
     318                                // The loop continues until no more split segments were found.
     319                                // Nb. not all segments touching a split node are split segments;
     320                                // e.g.
     321                                //
     322                                //     2       4
     323                                //     |       |
     324                                // 1---A---3---C---5
     325                                //
     326                                // This way will be split into 5 ways (1---A,2---A,A---3---C,4---C,
     327                                // C---5). Depending on which is processed first, either A---3 becomes
     328                                // a split segment and 3---C is moved as a connecting segment, or vice
     329                                // versa. The result is, of course, the same but this explains why we
     330                                // cannot simply start a new way for each segment connecting to a split
     331                                // node.
     332                        }
     333
     334                } else if (selectedSegments != null) {
     335
     336                        // This is the "split segments" mode. It is quite easy as the segments to
     337                        // remove are already explicitly selected, but some restrictions have to
     338                        // be observed to make sure that no non-contiguous parts are created.
     339
     340                        // first create a "scratch" copy of the full segment list and move all
     341                        // segments connected to the first selected segment into a temporary list.
     342                        LinkedList<Segment> copyOfAllSegments = new LinkedList<Segment>(allSegments);
     343                        LinkedList<Segment> partThatContainsSegments = new LinkedList<Segment>();
     344                        moveSegments(copyOfAllSegments, partThatContainsSegments, selectedSegments.get(0), null);
     345
     346                        // this list must now contain ALL selected segments; otherwise, segments
     347                        // from unconnected parts of the way have been selected and this is not allowed
     348                        // as it would create a new non-contiguous way.
     349                        if (!partThatContainsSegments.containsAll(selectedSegments)) {
     350                                JOptionPane.showMessageDialog(Main.parent, tr("The selected segments are not in the same contiguous part of the way."));                               
     351                                return;         
     352                        }
     353
     354                        // if the contiguous part that contains the segments becomes non-contiguous
     355                        // after the removal of the segments, that is also an error.
     356                        partThatContainsSegments.removeAll(selectedSegments);
     357                        if (!partThatContainsSegments.isEmpty()) {
     358                                ArrayList<Segment> contiguousSubpart = new ArrayList<Segment>();
     359                                moveSegments(partThatContainsSegments, contiguousSubpart, partThatContainsSegments.get(0), null);
     360                                // if partThatContainsSegments was contiguous before, it will now be empty as all segments
     361                                // connected to the first segment therein have been moved
     362                                if (!partThatContainsSegments.isEmpty()) {
     363                                        JOptionPane.showMessageDialog(Main.parent, tr("Removing the selected segments would make a part of the way non-contiguous."));                         
     364                                        return;                         
     365                                }
     366                        }
     367
     368                        ArrayList<Segment> subSegments = new ArrayList<Segment>();
     369                        subSegments.addAll(selectedSegments);
     370                        allSegments.removeAll(selectedSegments);
     371                        segmentSets.add(subSegments);
     372
     373                } else {
     374
     375                        // This is the "split way into contiguous parts" mode.
     376                        // We use a similar mechanism to splitting at nodes, but we do not
     377                        // select split segments. Instead, we randomly grab a segment out
     378                        // of the way and move all connecting segments to a new group. If
     379                        // segments remain in the original way, we repeat the procedure.
     380
     381                        while (!allSegments.isEmpty()) {
     382                                List<Segment> subSegments = new LinkedList<Segment>();
     383                                moveSegments(allSegments, subSegments, allSegments.get(0), null);
     384                                segmentSets.add(subSegments);
     385                        }                       
     386                }
     387
     388                // We now have a number of segment groups.
     389
     390                // If segments remain in allSegments, this means that they were not reachable
     391                // from any of the split nodes, and they will be made into an extra way.
     392                if (!allSegments.isEmpty()) {
     393                        segmentSets.add(allSegments);
     394                }
     395
     396                // If we do not have at least two parts, then the way was circular or the node(s)
     397                // were at one end of the way. User error ;-)
     398                if (segmentSets.size() < 2) {
     399                        if (selectedNodes != null) {
     400                                JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split at the selected node. (Hint: To split circular ways, select two nodes.)"));
    151401                        } else {
    152                                 wayToAdd.segments.remove(s);
    153                         }
    154                 }
     402                                JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split because it is contiguous. (Hint: To split at a node, select that node.)"));                             
     403                        }
     404                        return;
     405                }
     406
     407                // sort the list of segment lists according to their number of elements, so that
     408                // the biggest part of the way comes first. That way, we will "change" the largest
     409                // part of the way by removing a few segments, and "add" new, smaller ways; looks
     410                // nicer.
     411                Collections.sort(segmentSets, new Comparator<Collection<Segment>>() {
     412                        public int compare(Collection<Segment> a, Collection<Segment> b) {
     413                                if (a.size() < b.size())
     414                                        return 1;
     415                                if (b.size() < a.size())
     416                                        return -1;
     417                                return 0;
     418                        }
     419                });
     420
     421                // build a list of commands, and also a list of ways
     422                Collection<Command> commandList = new ArrayList<Command>(segmentSets.size());
     423                Collection<Way> newSelection = new ArrayList<Way>(segmentSets.size());
     424                Iterator<List<Segment>> segsIt = segmentSets.iterator();
    155425               
    156                 if (wayToAdd.segments.isEmpty() || changedWay.segments.isEmpty()) {
    157                         JOptionPane.showMessageDialog(Main.parent, tr("The selected node is not in the middle of the way."));
    158                         return;
     426                // the first is always a change to the existing way;
     427                Way changedWay = new Way(selectedWay);
     428                changedWay.segments.clear();
     429                changedWay.segments.addAll(segsIt.next());
     430                commandList.add(new ChangeCommand(selectedWay, changedWay));
     431                newSelection.add(selectedWay);
     432
     433                // and commands 1...n are additions of new ways.
     434                while (segsIt.hasNext()) {
     435                        Way wayToAdd = new Way();
     436                        if (selectedWay.keys != null)
     437                                wayToAdd.keys = new HashMap<String, String>(selectedWay.keys);
     438                        wayToAdd.segments.clear();
     439                        wayToAdd.segments.addAll(segsIt.next());
     440                        commandList.add(new AddCommand(wayToAdd));
     441                        newSelection.add(wayToAdd);
    159442                }
    160443
    161444                NameVisitor v = new NameVisitor();
    162                 v.visit(way);
    163                 Main.main.editLayer().add(new SequenceCommand(tr("Split way {0}",v.name),
    164                         Arrays.asList(new Command[]{new ChangeCommand(way, changedWay), new AddCommand(wayToAdd)})));
    165                 Main.ds.clearSelection();
    166                 way = null;
    167                 node = null;
    168                 return;
    169         }
    170 
     445                v.visit(selectedWay);
     446                Main.main.editLayer().add(new SequenceCommand(tr("Split way {0} into {1} parts",v.name, segmentSets.size()), commandList));
     447                Main.ds.setSelected(newSelection);
     448        }
     449
     450        /**
     451         * Enable the "split way" menu option if the selection looks like we could use it.
     452         */
    171453        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
    172454                setEnabled(checkSelection(newSelection));
    173455        }
     456
     457        /**
     458         * Move contiguous segments from one collection to another. The given segment is moved first, and
     459         * then the procedure is recursively called for all segments that connect to the first segment at
     460         * either end.
     461         *
     462         * @param source the source collection
     463         * @param destination the destination collection
     464         * @param start the first segment to be moved
     465         * @param stopNodes collection of nodes which should be considered end points for moving (may be null).
     466         */
     467        private void moveSegments(Collection<Segment> source, Collection<Segment> destination, Segment start, Collection<Node> stopNodes) {
     468                source.remove(start);
     469                destination.add(start);
     470                Segment moveSeg = start;
     471                while(moveSeg != null) {
     472                        moveSeg = null;
     473
     474                        for (Node node : new Node[] { start.from, start.to }) {
     475                                if (stopNodes != null && stopNodes.contains(node)) continue;
     476                                for (Segment sourceSeg : source) {
     477                                        if (sourceSeg.from.equals(node) || sourceSeg.to.equals(node)) {
     478                                                moveSeg = sourceSeg;
     479                                                break;
     480                                        }
     481                                }
     482                                if (moveSeg != null)
     483                                        break;
     484                        }
     485                        if (moveSeg != null) {
     486                                moveSegments(source, destination, moveSeg, stopNodes);
     487                        }
     488                }
     489        }
    174490}
  • src/org/openstreetmap/josm/data/osm/OsmPrimitive.java

    r159 r194  
    3434         * An id of 0 means an unknown id. The object has not been uploaded yet to
    3535         * know what id it will get.
     36         *
     37         * Do not write to this attribute except you know exactly what you are doing.
     38         * More specific, it is not good to set this to 0 and think the object is now
     39         * new to the server! To create a new object, call the default constructor of
     40         * the respective class.
    3641         */
    3742        public long id = 0;
Note: See TracChangeset for help on using the changeset viewer.