source: josm/src/org/openstreetmap/josm/actions/ReorderAction.java@ 319

Last change on this file since 319 was 319, checked in by imi, 17 years ago
  • removed MinML2 dependency (use javax.xml)
  • fixed reorder action (thanks Robert)
  • added backup files before saving (thanks Dave)
  • added search for last modifying user (thanks Dave)
  • fixed import of plugin list and added author field (thanks Shaun)
File size: 8.5 KB
Line 
1//License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.util.Collection;
10import java.util.Iterator;
11import java.util.LinkedList;
12import java.util.HashMap;
13
14import javax.swing.JOptionPane;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.command.ChangeCommand;
18import org.openstreetmap.josm.command.Command;
19import org.openstreetmap.josm.command.SequenceCommand;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.Segment;
23import org.openstreetmap.josm.data.osm.Way;
24import org.openstreetmap.josm.data.osm.visitor.NameVisitor;
25
26public class ReorderAction extends JosmAction {
27
28 public ReorderAction() {
29 super(tr("Reorder Segments"), "reorder", tr("Try to reorder segments of a way so that they are in a line. May try to flip segments around to match a line."), KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK, true);
30 }
31
32 /**
33 * This method first sorts all the segments in a way, then makes sure that all
34 * the segments are facing the same direction as the first one.
35 */
36 public void actionPerformed(ActionEvent e) {
37 Collection<Way> ways = new LinkedList<Way>();
38 for (OsmPrimitive osm : Main.ds.getSelected())
39 if (osm instanceof Way)
40 ways.add((Way)osm);
41
42 if (ways.size() < 1) {
43 JOptionPane.showMessageDialog(Main.parent, tr("Please select at least one way."));
44 return;
45 }
46
47 if (ways.size() > 1) {
48 int answer = JOptionPane.showConfirmDialog(Main.parent,
49 trn(null, "You selected more than one way. Reorder the segments of {0} ways?", ways.size(), ways.size()),
50 tr("Reorder segments"), JOptionPane.OK_CANCEL_OPTION);
51 if (answer != JOptionPane.OK_OPTION)
52 return;
53 }
54 boolean doneSomething = false;
55 for (Way way : ways) {
56 if (!way.isIncomplete() && way.segments.size() > 1)
57 {
58 doneSomething = true;
59 Command c = reorderWay(way);
60
61 if( c != null )
62 Main.main.undoRedo.add(c);
63 }
64 }
65 if (!doneSomething) {
66 JOptionPane.showMessageDialog(Main.parent,
67 trn("The selected way is incomplete or has only one segment.",
68 "None of the selected ways are complete and have more than one segment.",
69 ways.size()));
70 }
71 Main.map.repaint();
72 }
73
74 /**
75 * This method first sorts all the segments in a way, then makes sure that all
76 * the segments are facing the same direction as the first one.
77 * @param way The way to reorder
78 * @return The command needed to reorder the way
79 */
80 public static Command reorderWay(Way way) {
81 final LinkedList<Segment> sel = new LinkedList<Segment>(sortSegments(new LinkedList<Segment>(way.segments), false));
82
83 Collection<Command> c = new LinkedList<Command>();
84
85 boolean direction = false;
86 // work out the "average" direction of the way, we use this to direct the rest of the segments
87 int dirCounter = 0;
88 for(int i = 0; i < sel.size() - 1; i++)
89 {
90 Segment firstSegment = sel.get(i);
91 Segment secondSegment = sel.get(i+1);
92 if ( firstSegment.to == secondSegment.from || firstSegment.to == secondSegment.to ) // direction = true when 'from' is the first node in the Way
93 dirCounter++;
94 else
95 dirCounter--;
96 }
97 if ( dirCounter <= 0 )
98 direction = false;
99 else
100 direction = true;
101
102 Node lastNode = null;
103
104 // we need to calculate what the first node in the way is, we work from there
105 Segment firstSegment = sel.getFirst();
106 Segment secondSegment = sel.get(1);
107 if (firstSegment.to == secondSegment.from || firstSegment.to == secondSegment.to)
108 lastNode = firstSegment.from;
109 else
110 lastNode = firstSegment.to;
111
112 // go through each segment and flip them if required
113 for (Segment s : sel) {
114 Segment snew = new Segment(s);
115 boolean segDirection = s.from == lastNode;
116 // segDirection = true when the 'from' node occurs before the 'to' node in the Way
117 if (direction != segDirection)
118 {
119 // reverse the segment's direction
120 Node n = snew.from;
121 snew.from = snew.to;
122 snew.to = n;
123 c.add(new ChangeCommand(s, snew));
124 }
125
126 if (direction) // if its facing forwards,
127 lastNode = snew.to; // our next node is the 'to' one
128 else
129 lastNode = snew.from; // otherwise its the 'from' one
130 }
131
132 LinkedList<Segment> segments = new LinkedList<Segment>();
133
134 // Now we recreate the segment list, in the correct order of the direction
135 for (Segment s : sel)
136 if (!direction)
137 segments.addFirst(s);
138 else
139 segments.addLast(s);
140
141 // Check if the new segment list is actually different from the old one
142 // before we go and add a change command for it
143 for(int i = 0; i < segments.size(); i++)
144 if (way.segments.get(i) != segments.get(i))
145 {
146 Way newWay = new Way(way);
147 newWay.segments.clear();
148 newWay.segments.addAll(segments);
149 c.add(new ChangeCommand(way, newWay));
150 break;
151 }
152
153 // Check we've got some change commands before we add a sequence command
154 if (c.size() != 0) {
155 NameVisitor v = new NameVisitor();
156 way.visit(v);
157 return new SequenceCommand(tr("Reorder segments for way {0}",v.name), c);
158 }
159 return null;
160 }
161
162 /**
163 * This sort is based on the sort in the old ReorderAction, but it can work
164 * irresepective of the direction of the segments. This produces a sort
165 * that can be useful even if the segments are facing the wrong direction.
166 *
167 * @param segments list of segments to be sorted
168 * @param strict true if segment direction should be observed, false if not
169 */
170 public static LinkedList<Segment> sortSegments(LinkedList<Segment> segments, boolean strict) {
171
172 LinkedList<Segment> sortedSegments = new LinkedList<Segment>();
173
174 while (!segments.isEmpty()) {
175 LinkedList<Segment> pivotList = new LinkedList<Segment>();
176 pivotList.add(firstSegment(segments));
177 segments.remove(pivotList.getLast());
178 boolean found;
179 do {
180 found = false;
181 //try working forwards first
182 for (Iterator<Segment> it = segments.iterator(); it.hasNext();) {
183 Segment ls = it.next();
184 if (ls.incomplete)
185 continue; // incomplete segments are never added to a new way
186 if (ls.from == pivotList.getLast().to) {
187 pivotList.addLast(ls);
188 it.remove();
189 found = true;
190 }
191 }
192 if(!found){
193 for (Iterator<Segment> it = segments.iterator(); it.hasNext();) {
194 Segment ls = it.next();
195 if (ls.incomplete)
196 continue; // incomplete segments are never added to a new way
197 if (ls.from == pivotList.getLast().to || (!strict && (ls.to == pivotList.getLast().to || ls.from == pivotList.getLast().from || ls.to == pivotList.getLast().from))) {
198 pivotList.addLast(ls);
199 it.remove();
200 found = true;
201 } else if (ls.to == pivotList.getFirst().from || (!strict && (ls.from == pivotList.getFirst().from || ls.to == pivotList.getFirst().to || ls.from == pivotList.getFirst().to))) {
202 pivotList.addFirst(ls);
203 it.remove();
204 found = true;
205 }
206 }
207 }
208 } while (found);
209 sortedSegments.addAll(pivotList);
210 }
211 return sortedSegments;
212 }
213
214 /**
215 * This method searches for a good segment to start a reorder from.
216 * In most cases this will be a segment with a start node that occurs only
217 * once in the way. In cases with loops, this could be any odd number. If no nodes
218 * are referenced an odd number of times, then any segment is a good start point.
219 */
220 public static Segment firstSegment(Collection<Segment> segments) {
221 HashMap<Node, Integer> refCount = new HashMap<Node, Integer>(segments.size()*2);
222 //loop through all segments and count up how many times each node is referenced
223 for (Segment seg : segments) {
224 if (!refCount.containsKey(seg.from))
225 refCount.put(seg.from, 0);
226 refCount.put(seg.from,refCount.get(seg.from)+1);
227
228 if (!refCount.containsKey(seg.to))
229 refCount.put(seg.to, 0);
230 refCount.put(seg.to,refCount.get(seg.to)+1);
231 }
232
233 //now look for start nodes that are referenced only once
234 for (Segment seg : segments)
235 if (refCount.get(seg.from) == 1)
236 return seg;
237 //now look for start nodes that are referenced only (2n+1)
238 for (Segment seg : segments)
239 if (refCount.get(seg.from) % 2 == 1)
240 return seg;
241 //now look for end nodes that are referenced only once
242 for (Segment seg : segments)
243 if (refCount.get(seg.to) == 1)
244 return seg;
245 //now look for end nodes that are referenced only (2n+1)
246 for (Segment seg : segments)
247 if (refCount.get(seg.to) % 2 == 1)
248 return seg;
249
250 return segments.iterator().next();
251 }
252}
Note: See TracBrowser for help on using the repository browser.