source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java@ 5297

Last change on this file since 5297 was 5297, checked in by Don-vip, 12 years ago

fix #4493, fix #7750: conflict dialog, nodes and members tab: allow selection via double-click, allow zoom via contexual menu, plus various fix in EDT violations

  • Property svn:eol-style set to native
File size: 39.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation;
3
4import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.BACKWARD;
5import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.FORWARD;
6import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.NONE;
7import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.ROUNDABOUT_LEFT;
8import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.ROUNDABOUT_RIGHT;
9
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashSet;
15import java.util.Iterator;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Set;
19import java.util.concurrent.CopyOnWriteArrayList;
20
21import javax.swing.DefaultListSelectionModel;
22import javax.swing.ListSelectionModel;
23import javax.swing.event.TableModelEvent;
24import javax.swing.event.TableModelListener;
25import javax.swing.table.AbstractTableModel;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.SelectionChangedListener;
29import org.openstreetmap.josm.data.coor.EastNorth;
30import org.openstreetmap.josm.data.osm.DataSet;
31import org.openstreetmap.josm.data.osm.Node;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.Relation;
34import org.openstreetmap.josm.data.osm.RelationMember;
35import org.openstreetmap.josm.data.osm.Way;
36import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
37import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
38import org.openstreetmap.josm.data.osm.event.DataSetListener;
39import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
40import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
41import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
42import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
43import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
44import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
45import org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction;
46import org.openstreetmap.josm.gui.layer.OsmDataLayer;
47import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
48
49public class MemberTableModel extends AbstractTableModel implements TableModelListener, SelectionChangedListener, DataSetListener, OsmPrimitivesTableModel {
50
51 /**
52 * data of the table model: The list of members and the cached WayConnectionType of each member.
53 **/
54 private List<RelationMember> members;
55 private List<WayConnectionType> connectionType = null;
56
57 private DefaultListSelectionModel listSelectionModel;
58 private CopyOnWriteArrayList<IMemberModelListener> listeners;
59 private OsmDataLayer layer;
60
61 private final int UNCONNECTED = Integer.MIN_VALUE;
62
63 /**
64 * constructor
65 */
66 public MemberTableModel(OsmDataLayer layer) {
67 members = new ArrayList<RelationMember>();
68 listeners = new CopyOnWriteArrayList<IMemberModelListener>();
69 this.layer = layer;
70 addTableModelListener(this);
71 }
72
73 public OsmDataLayer getLayer() {
74 return layer;
75 }
76
77 public void register() {
78 DataSet.addSelectionListener(this);
79 getLayer().data.addDataSetListener(this);
80 }
81
82 public void unregister() {
83 DataSet.removeSelectionListener(this);
84 getLayer().data.removeDataSetListener(this);
85 }
86
87 /* --------------------------------------------------------------------------- */
88 /* Interface SelectionChangedListener */
89 /* --------------------------------------------------------------------------- */
90 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
91 if (Main.main.getEditLayer() != this.layer) return;
92 // just trigger a repaint
93 Collection<RelationMember> sel = getSelectedMembers();
94 fireTableDataChanged();
95 setSelectedMembers(sel);
96 }
97
98 /* --------------------------------------------------------------------------- */
99 /* Interface DataSetListener */
100 /* --------------------------------------------------------------------------- */
101 public void dataChanged(DataChangedEvent event) {
102 // just trigger a repaint - the display name of the relation members may
103 // have changed
104 Collection<RelationMember> sel = getSelectedMembers();
105 fireTableDataChanged();
106 setSelectedMembers(sel);
107 }
108
109 public void nodeMoved(NodeMovedEvent event) {/* ignore */}
110 public void primitivesAdded(PrimitivesAddedEvent event) {/* ignore */}
111
112 public void primitivesRemoved(PrimitivesRemovedEvent event) {
113 // ignore - the relation in the editor might become out of sync with the relation
114 // in the dataset. We will deal with it when the relation editor is closed or
115 // when the changes in the editor are applied.
116 }
117
118 public void relationMembersChanged(RelationMembersChangedEvent event) {
119 // ignore - the relation in the editor might become out of sync with the relation
120 // in the dataset. We will deal with it when the relation editor is closed or
121 // when the changes in the editor are applied.
122 }
123
124 public void tagsChanged(TagsChangedEvent event) {
125 // just refresh the respective table cells
126 //
127 Collection<RelationMember> sel = getSelectedMembers();
128 for (int i=0; i < members.size();i++) {
129 if (members.get(i).getMember() == event.getPrimitive()) {
130 fireTableCellUpdated(i, 1 /* the column with the primitive name */);
131 }
132 }
133 setSelectedMembers(sel);
134 }
135
136 public void wayNodesChanged(WayNodesChangedEvent event) {/* ignore */}
137
138 public void otherDatasetChange(AbstractDatasetChangedEvent event) {/* ignore */}
139 /* --------------------------------------------------------------------------- */
140
141 public void addMemberModelListener(IMemberModelListener listener) {
142 if (listener != null) {
143 listeners.addIfAbsent(listener);
144 }
145 }
146
147 public void removeMemberModelListener(IMemberModelListener listener) {
148 listeners.remove(listener);
149 }
150
151 protected void fireMakeMemberVisible(int index) {
152 for (IMemberModelListener listener : listeners) {
153 listener.makeMemberVisible(index);
154 }
155 }
156
157 public void populate(Relation relation) {
158 members.clear();
159 if (relation != null) {
160 // make sure we work with clones of the relation members
161 // in the model.
162 members.addAll(new Relation(relation).getMembers());
163 }
164 fireTableDataChanged();
165 }
166
167 public int getColumnCount() {
168 return 3;
169 }
170
171 public int getRowCount() {
172 return members.size();
173 }
174
175 public Object getValueAt(int rowIndex, int columnIndex) {
176 switch (columnIndex) {
177 case 0:
178 return members.get(rowIndex).getRole();
179 case 1:
180 return members.get(rowIndex).getMember();
181 case 2:
182 return getWayConnection(rowIndex);
183 }
184 // should not happen
185 return null;
186 }
187
188 @Override
189 public boolean isCellEditable(int rowIndex, int columnIndex) {
190 return columnIndex == 0;
191 }
192
193 @Override
194 public void setValueAt(Object value, int rowIndex, int columnIndex) {
195 RelationMember member = members.get(rowIndex);
196 RelationMember newMember = new RelationMember(value.toString(), member.getMember());
197 members.remove(rowIndex);
198 members.add(rowIndex, newMember);
199 }
200
201 @Override
202 public OsmPrimitive getReferredPrimitive(int idx) {
203 return members.get(idx).getMember();
204 }
205
206 public void moveUp(int[] selectedRows) {
207 if (!canMoveUp(selectedRows))
208 return;
209
210 for (int row : selectedRows) {
211 RelationMember member1 = members.get(row);
212 RelationMember member2 = members.get(row - 1);
213 members.set(row, member2);
214 members.set(row - 1, member1);
215 }
216 fireTableDataChanged();
217 getSelectionModel().setValueIsAdjusting(true);
218 getSelectionModel().clearSelection();
219 for (int row : selectedRows) {
220 row--;
221 getSelectionModel().addSelectionInterval(row, row);
222 }
223 getSelectionModel().setValueIsAdjusting(false);
224 fireMakeMemberVisible(selectedRows[0] - 1);
225 }
226
227 public void moveDown(int[] selectedRows) {
228 if (!canMoveDown(selectedRows))
229 return;
230
231 for (int i = selectedRows.length - 1; i >= 0; i--) {
232 int row = selectedRows[i];
233 RelationMember member1 = members.get(row);
234 RelationMember member2 = members.get(row + 1);
235 members.set(row, member2);
236 members.set(row + 1, member1);
237 }
238 fireTableDataChanged();
239 getSelectionModel();
240 getSelectionModel().setValueIsAdjusting(true);
241 getSelectionModel().clearSelection();
242 for (int row : selectedRows) {
243 row++;
244 getSelectionModel().addSelectionInterval(row, row);
245 }
246 getSelectionModel().setValueIsAdjusting(false);
247 fireMakeMemberVisible(selectedRows[0] + 1);
248 }
249
250 public void remove(int[] selectedRows) {
251 if (!canRemove(selectedRows))
252 return;
253 int offset = 0;
254 for (int row : selectedRows) {
255 row -= offset;
256 members.remove(row);
257 offset++;
258 }
259 fireTableDataChanged();
260 }
261
262 public boolean canMoveUp(int[] rows) {
263 if (rows == null || rows.length == 0)
264 return false;
265 Arrays.sort(rows);
266 return rows[0] > 0 && members.size() > 0;
267 }
268
269 public boolean canMoveDown(int[] rows) {
270 if (rows == null || rows.length == 0)
271 return false;
272 Arrays.sort(rows);
273 return members.size() > 0 && rows[rows.length - 1] < members.size() - 1;
274 }
275
276 public boolean canRemove(int[] rows) {
277 if (rows == null || rows.length == 0)
278 return false;
279 return true;
280 }
281
282 public DefaultListSelectionModel getSelectionModel() {
283 if (listSelectionModel == null) {
284 listSelectionModel = new DefaultListSelectionModel();
285 listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
286 }
287 return listSelectionModel;
288 }
289
290 public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) {
291 if (primitives == null)
292 return;
293 Iterator<RelationMember> it = members.iterator();
294 while (it.hasNext()) {
295 RelationMember member = it.next();
296 if (primitives.contains(member.getMember())) {
297 it.remove();
298 }
299 }
300 fireTableDataChanged();
301 }
302
303 public void applyToRelation(Relation relation) {
304 relation.setMembers(members);
305 }
306
307 public boolean hasSameMembersAs(Relation relation) {
308 if (relation == null)
309 return false;
310 if (relation.getMembersCount() != members.size())
311 return false;
312 for (int i = 0; i < relation.getMembersCount(); i++) {
313 if (!relation.getMember(i).equals(members.get(i)))
314 return false;
315 }
316 return true;
317 }
318
319 /**
320 * Replies the set of incomplete primitives
321 *
322 * @return the set of incomplete primitives
323 */
324 public Set<OsmPrimitive> getIncompleteMemberPrimitives() {
325 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
326 for (RelationMember member : members) {
327 if (member.getMember().isIncomplete()) {
328 ret.add(member.getMember());
329 }
330 }
331 return ret;
332 }
333
334 /**
335 * Replies the set of selected incomplete primitives
336 *
337 * @return the set of selected incomplete primitives
338 */
339 public Set<OsmPrimitive> getSelectedIncompleteMemberPrimitives() {
340 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
341 for (RelationMember member : getSelectedMembers()) {
342 if (member.getMember().isIncomplete()) {
343 ret.add(member.getMember());
344 }
345 }
346 return ret;
347 }
348
349 /**
350 * Replies true if at least one the relation members is incomplete
351 *
352 * @return true if at least one the relation members is incomplete
353 */
354 public boolean hasIncompleteMembers() {
355 for (RelationMember member : members) {
356 if (member.getMember().isIncomplete())
357 return true;
358 }
359 return false;
360 }
361
362 /**
363 * Replies true if at least one of the selected members is incomplete
364 *
365 * @return true if at least one of the selected members is incomplete
366 */
367 public boolean hasIncompleteSelectedMembers() {
368 for (RelationMember member : getSelectedMembers()) {
369 if (member.getMember().isIncomplete())
370 return true;
371 }
372 return false;
373 }
374
375 protected List<Integer> getSelectedIndices() {
376 ArrayList<Integer> selectedIndices = new ArrayList<Integer>();
377 for (int i = 0; i < members.size(); i++) {
378 if (getSelectionModel().isSelectedIndex(i)) {
379 selectedIndices.add(i);
380 }
381 }
382 return selectedIndices;
383 }
384
385 private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) {
386 if (primitives == null)
387 return;
388 int idx = index;
389 for (OsmPrimitive primitive : primitives) {
390 RelationMember member = new RelationMember("", primitive);
391 members.add(idx++, member);
392 }
393 fireTableDataChanged();
394 getSelectionModel().clearSelection();
395 getSelectionModel().addSelectionInterval(index, index + primitives.size() - 1);
396 fireMakeMemberVisible(index);
397 }
398
399 public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) {
400 addMembersAtIndex(primitives, 0);
401 }
402
403 public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) {
404 addMembersAtIndex(primitives, members.size());
405 }
406
407 public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) {
408 addMembersAtIndex(primitives, idx);
409 }
410
411 public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) {
412 addMembersAtIndex(primitives, idx + 1);
413 }
414
415 /**
416 * Replies the number of members which refer to a particular primitive
417 *
418 * @param primitive the primitive
419 * @return the number of members which refer to a particular primitive
420 */
421 public int getNumMembersWithPrimitive(OsmPrimitive primitive) {
422 int count = 0;
423 for (RelationMember member : members) {
424 if (member.getMember().equals(primitive)) {
425 count++;
426 }
427 }
428 return count;
429 }
430
431 /**
432 * updates the role of the members given by the indices in <code>idx</code>
433 *
434 * @param idx the array of indices
435 * @param role the new role
436 */
437 public void updateRole(int[] idx, String role) {
438 if (idx == null || idx.length == 0)
439 return;
440 for (int row : idx) {
441 RelationMember oldMember = members.get(row);
442 RelationMember newMember = new RelationMember(role, oldMember.getMember());
443 members.remove(row);
444 members.add(row, newMember);
445 }
446 fireTableDataChanged();
447 for (int row : idx) {
448 getSelectionModel().addSelectionInterval(row, row);
449 }
450 }
451
452 /**
453 * Get the currently selected relation members
454 *
455 * @return a collection with the currently selected relation members
456 */
457 public Collection<RelationMember> getSelectedMembers() {
458 ArrayList<RelationMember> selectedMembers = new ArrayList<RelationMember>();
459 for (int i : getSelectedIndices()) {
460 selectedMembers.add(members.get(i));
461 }
462 return selectedMembers;
463 }
464
465 /**
466 * Replies the set of selected referers. Never null, but may be empty.
467 *
468 * @return the set of selected referers
469 */
470 public Collection<OsmPrimitive> getSelectedChildPrimitives() {
471 Collection<OsmPrimitive> ret = new ArrayList<OsmPrimitive>();
472 for (RelationMember m: getSelectedMembers()) {
473 ret.add(m.getMember());
474 }
475 return ret;
476 }
477
478 /**
479 * Replies the set of selected referers. Never null, but may be empty.
480 *
481 * @return the set of selected referers
482 */
483 public Set<OsmPrimitive> getChildPrimitives(Collection<? extends OsmPrimitive> referenceSet) {
484 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
485 if (referenceSet == null) return null;
486 for (RelationMember m: members) {
487 if (referenceSet.contains(m.getMember())) {
488 ret.add(m.getMember());
489 }
490 }
491 return ret;
492 }
493
494 /**
495 * Selects the members in the collection selectedMembers
496 *
497 * @param selectedMembers the collection of selected members
498 */
499 public void setSelectedMembers(Collection<RelationMember> selectedMembers) {
500 if (selectedMembers == null || selectedMembers.isEmpty()) {
501 getSelectionModel().clearSelection();
502 return;
503 }
504
505 // lookup the indices for the respective members
506 //
507 Set<Integer> selectedIndices = new HashSet<Integer>();
508 for (RelationMember member : selectedMembers) {
509 for (int idx = 0; idx < members.size(); ++idx) {
510 if (member.equals(members.get(idx))) {
511 selectedIndices.add(idx);
512 }
513 }
514 }
515 setSelectedMembersIdx(selectedIndices);
516 }
517
518 /**
519 * Selects the members in the collection selectedIndices
520 *
521 * @param selectedIndices the collection of selected member indices
522 */
523 public void setSelectedMembersIdx(Collection<Integer> selectedIndices) {
524 if (selectedIndices == null || selectedIndices.isEmpty()) {
525 getSelectionModel().clearSelection();
526 return;
527 }
528 // select the members
529 //
530 getSelectionModel().setValueIsAdjusting(true);
531 getSelectionModel().clearSelection();
532 for (int row : selectedIndices) {
533 getSelectionModel().addSelectionInterval(row, row);
534 }
535 getSelectionModel().setValueIsAdjusting(false);
536 // make the first selected member visible
537 //
538 if (selectedIndices.size() > 0) {
539 fireMakeMemberVisible(Collections.min(selectedIndices));
540 }
541 }
542
543 /**
544 * Replies true if the index-th relation members referrs
545 * to an editable relation, i.e. a relation which is not
546 * incomplete.
547 *
548 * @param index the index
549 * @return true, if the index-th relation members referrs
550 * to an editable relation, i.e. a relation which is not
551 * incomplete
552 */
553 public boolean isEditableRelation(int index) {
554 if (index < 0 || index >= members.size())
555 return false;
556 RelationMember member = members.get(index);
557 if (!member.isRelation())
558 return false;
559 Relation r = member.getRelation();
560 return !r.isIncomplete();
561 }
562
563 /**
564 * Replies true if there is at least one relation member given as {@code members}
565 * which refers to at least on the primitives in {@code primitives}.
566 *
567 * @param members the members
568 * @param primitives the collection of primitives
569 * @return true if there is at least one relation member in this model
570 * which refers to at least on the primitives in <code>primitives</code>; false
571 * otherwise
572 */
573 public static boolean hasMembersReferringTo(Collection<RelationMember> members, Collection<OsmPrimitive> primitives) {
574 if (primitives == null || primitives.isEmpty()) {
575 return false;
576 }
577 HashSet<OsmPrimitive> referrers = new HashSet<OsmPrimitive>();
578 for (RelationMember member : members) {
579 referrers.add(member.getMember());
580 }
581 for (OsmPrimitive referred : primitives) {
582 if (referrers.contains(referred)) {
583 return true;
584 }
585 }
586 return false;
587 }
588
589 /**
590 * Replies true if there is at least one relation member in this model
591 * which refers to at least on the primitives in <code>primitives</code>.
592 *
593 * @param primitives the collection of primitives
594 * @return true if there is at least one relation member in this model
595 * which refers to at least on the primitives in <code>primitives</code>; false
596 * otherwise
597 */
598 public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) {
599 return hasMembersReferringTo(members, primitives);
600 }
601
602 /**
603 * Selects all mebers which refer to {@link OsmPrimitive}s in the collections
604 * <code>primitmives</code>. Does nothing is primitives is null.
605 *
606 * @param primitives the collection of primitives
607 */
608 public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) {
609 if (primitives == null) return;
610 getSelectionModel().setValueIsAdjusting(true);
611 getSelectionModel().clearSelection();
612 for (int i=0; i< members.size();i++) {
613 RelationMember m = members.get(i);
614 if (primitives.contains(m.getMember())) {
615 this.getSelectionModel().addSelectionInterval(i,i);
616 }
617 }
618 getSelectionModel().setValueIsAdjusting(false);
619 if (getSelectedIndices().size() > 0) {
620 fireMakeMemberVisible(getSelectedIndices().get(0));
621 }
622 }
623
624 /**
625 * Replies true if <code>primitive</code> is currently selected in the layer this
626 * model is attached to
627 *
628 * @param primitive the primitive
629 * @return true if <code>primitive</code> is currently selected in the layer this
630 * model is attached to, false otherwise
631 */
632 public boolean isInJosmSelection(OsmPrimitive primitive) {
633 return layer.data.isSelected(primitive);
634 }
635
636 /**
637 * Replies true if the layer this model belongs to is equal to the active
638 * layer
639 *
640 * @return true if the layer this model belongs to is equal to the active
641 * layer
642 */
643 protected boolean isActiveLayer() {
644 if (Main.map == null || Main.map.mapView == null) return false;
645 return Main.map.mapView.getActiveLayer() == layer;
646 }
647
648 /**
649 * get a node we can link against when sorting members
650 * @param element the element we want to link against
651 * @param linked_element already linked against element
652 * @return the unlinked node if element is a way, the node itself if element is a node, null otherwise
653 */
654 /*private static Node getUnusedNode(RelationMember element, RelationMember linked_element)
655 {
656 Node result = null;
657
658 if (element.isWay()) {
659 Way w = element.getWay();
660 if (linked_element.isWay()) {
661 Way x = linked_element.getWay();
662 if ((w.firstNode() == x.firstNode()) || (w.firstNode() == x.lastNode())) {
663 result = w.lastNode();
664 } else {
665 result = w.firstNode();
666 }
667 } else if (linked_element.isNode()) {
668 Node m = linked_element.getNode();
669 if (w.firstNode() == m) {
670 result = w.lastNode();
671 } else {
672 result = w.firstNode();
673 }
674 }
675 } else if (element.isNode()) {
676 Node n = element.getNode();
677 result = n;
678 }
679
680 return result;
681 }*/
682
683 /*
684 * Sort a collection of relation members by the way they are linked.
685 *
686 * @param relationMembers collection of relation members
687 * @return sorted collection of relation members
688 */
689 private List<RelationMember> sortMembers(List<RelationMember> relationMembers) {
690 RelationNodeMap map = new RelationNodeMap(relationMembers);
691 // List of groups of linked members
692 //
693 ArrayList<LinkedList<Integer>> allGroups = new ArrayList<LinkedList<Integer>>();
694
695 // current group of members that are linked among each other
696 // Two successive members are always linked i.e. have a common node.
697 //
698 LinkedList<Integer> group;
699
700 Integer first;
701 while ((first = map.pop()) != null) {
702 group = new LinkedList<Integer>();
703 group.add(first);
704
705 allGroups.add(group);
706
707 Integer next = first;
708 while ((next = map.popAdjacent(next)) != null) {
709 group.addLast(next);
710 }
711
712 // The first element need not be in front of the list.
713 // So the search goes in both directions
714 //
715 next = first;
716 while ((next = map.popAdjacent(next)) != null) {
717 group.addFirst(next);
718 }
719 }
720
721 group = new LinkedList<Integer>();
722 group.addAll(map.getNotSortableMembers());
723 allGroups.add(group);
724
725 ArrayList<RelationMember> newMembers = new ArrayList<RelationMember>();
726 for (LinkedList<Integer> tmpGroup : allGroups) {
727 for (Integer p : tmpGroup) {
728 newMembers.add(relationMembers.get(p));
729 }
730 }
731 return newMembers;
732 }
733
734 /**
735 * Sort the selected relation members by the way they are linked.
736 */
737 void sort() {
738 List<RelationMember> selectedMembers = new ArrayList<RelationMember>(getSelectedMembers());
739 List<RelationMember> sortedMembers = null;
740 List<RelationMember> newMembers;
741 if (selectedMembers.size() <= 1) {
742 newMembers = sortMembers(members);
743 sortedMembers = newMembers;
744 } else {
745 sortedMembers = sortMembers(selectedMembers);
746 List<Integer> selectedIndices = getSelectedIndices();
747 newMembers = new ArrayList<RelationMember>();
748 boolean inserted = false;
749 for (int i=0; i < members.size(); i++) {
750 if (selectedIndices.contains(i)) {
751 if (!inserted) {
752 newMembers.addAll(sortedMembers);
753 inserted = true;
754 }
755 } else {
756 newMembers.add(members.get(i));
757 }
758 }
759 }
760
761 if (members.size() != newMembers.size()) throw new AssertionError();
762
763 members.clear();
764 members.addAll(newMembers);
765 fireTableDataChanged();
766 setSelectedMembers(sortedMembers);
767 }
768
769 private Direction determineDirection(int ref_i, Direction ref_direction, int k) {
770 return determineDirection(ref_i, ref_direction, k, false);
771 }
772 /**
773 * Determines the direction of way k with respect to the way ref_i.
774 * The way ref_i is assumed to have the direction ref_direction and
775 * to be the predecessor of k.
776 *
777 * If both ways are not linked in any way, NONE is returned.
778 *
779 * Else the direction is given as follows:
780 * Let the relation be a route of oneway streets, and someone travels them in the given order.
781 * Direction is FORWARD if it is legal and BACKWARD if it is illegal to do so for the given way.
782 *
783 **/
784 private Direction determineDirection(int ref_i, final Direction ref_direction, int k, boolean reversed) {
785 if (ref_i < 0 || k < 0 || ref_i >= members.size() || k >= members.size())
786 return NONE;
787 if (ref_direction == NONE)
788 return NONE;
789
790 final RelationMember m_ref = members.get(ref_i);
791 final RelationMember m = members.get(k);
792 Way way_ref = null;
793 Way way = null;
794
795 if (m_ref.isWay()) {
796 way_ref = m_ref.getWay();
797 }
798 if (m.isWay()) {
799 way = m.getWay();
800 }
801
802 if (way_ref == null || way == null)
803 return NONE;
804
805 /** the list of nodes the way k can dock to */
806 List<Node> refNodes= new ArrayList<Node>();
807
808 switch (ref_direction) {
809 case FORWARD:
810 refNodes.add(way_ref.lastNode());
811 break;
812 case BACKWARD:
813 refNodes.add(way_ref.firstNode());
814 break;
815 case ROUNDABOUT_LEFT:
816 case ROUNDABOUT_RIGHT:
817 refNodes = way_ref.getNodes();
818 break;
819 }
820
821 if (refNodes == null)
822 return NONE;
823
824 for (Node n : refNodes) {
825 if (n == null) {
826 continue;
827 }
828 if (roundaboutType(k) != NONE) {
829 for (Node nn : way.getNodes()) {
830 if (n == nn)
831 return roundaboutType(k);
832 }
833 } else if(isOneway(m)) {
834 if (n == RelationNodeMap.firstOnewayNode(m) && !reversed) {
835 if(isBackward(m))
836 return BACKWARD;
837 else
838 return FORWARD;
839 }
840 if (n == RelationNodeMap.lastOnewayNode(m) && reversed) {
841 if(isBackward(m))
842 return FORWARD;
843 else
844 return BACKWARD;
845 }
846 } else {
847 if (n == way.firstNode())
848 return FORWARD;
849 if (n == way.lastNode())
850 return BACKWARD;
851 }
852 }
853 return NONE;
854 }
855
856 /**
857 * determine, if the way i is a roundabout and if yes, what type of roundabout
858 */
859 private Direction roundaboutType(int i) {
860 RelationMember m = members.get(i);
861 if (m == null || !m.isWay()) return NONE;
862 Way w = m.getWay();
863 return roundaboutType(w);
864 }
865 static Direction roundaboutType(Way w) {
866 if (w != null &&
867 "roundabout".equals(w.get("junction")) &&
868 w.getNodesCount() < 200 &&
869 w.getNodesCount() > 2 &&
870 w.getNode(0) != null &&
871 w.getNode(1) != null &&
872 w.getNode(2) != null &&
873 w.firstNode() == w.lastNode()) {
874 /** do some simple determinant / cross pruduct test on the first 3 nodes
875 to see, if the roundabout goes clock wise or ccw */
876 EastNorth en1 = w.getNode(0).getEastNorth();
877 EastNorth en2 = w.getNode(1).getEastNorth();
878 EastNorth en3 = w.getNode(2).getEastNorth();
879 en1 = en1.sub(en2);
880 en2 = en2.sub(en3);
881 return en1.north() * en2.east() - en2.north() * en1.east() > 0 ? ROUNDABOUT_LEFT : ROUNDABOUT_RIGHT;
882 } else
883 return NONE;
884 }
885
886 WayConnectionType getWayConnection(int i) {
887 if (connectionType == null) {
888 updateLinks();
889 }
890 return connectionType.get(i);
891 }
892
893 public void tableChanged(TableModelEvent e) {
894 connectionType = null;
895 }
896
897 /**
898 * Reverse the relation members.
899 */
900 void reverse() {
901 List<Integer> selectedIndices = getSelectedIndices();
902 List<Integer> selectedIndicesReversed = getSelectedIndices();
903
904 if (selectedIndices.size() <= 1) {
905 Collections.reverse(members);
906 fireTableDataChanged();
907 setSelectedMembers(members);
908 } else {
909 Collections.reverse(selectedIndicesReversed);
910
911 ArrayList<RelationMember> newMembers = new ArrayList<RelationMember>(members);
912
913 for (int i=0; i < selectedIndices.size(); i++) {
914 newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i)));
915 }
916
917 if (members.size() != newMembers.size()) throw new AssertionError();
918 members.clear();
919 members.addAll(newMembers);
920 fireTableDataChanged();
921 setSelectedMembersIdx(selectedIndices);
922 }
923 }
924
925 /**
926 * refresh the cache of member WayConnectionTypes
927 */
928 public void updateLinks() {
929 connectionType = null;
930 final List<WayConnectionType> con = new ArrayList<WayConnectionType>();
931
932 for (int i=0; i<members.size(); ++i) {
933 con.add(null);
934 }
935
936 firstGroupIdx=0;
937
938 lastForwardWay = UNCONNECTED;
939 lastBackwardWay = UNCONNECTED;
940 onewayBeginning = false;
941 WayConnectionType lastWct = null;
942
943 for (int i=0; i<members.size(); ++i) {
944 final RelationMember m = members.get(i);
945 if (!m.isWay() || m.getWay() == null || m.getWay().isIncomplete()) {
946 if(i > 0) {
947 makeLoopIfNeeded(con, i-1);
948 }
949 con.set(i, new WayConnectionType());
950 firstGroupIdx = i;
951 continue;
952 }
953
954 WayConnectionType wct = new WayConnectionType(false);
955 wct.linkPrev = i>0 && con.get(i-1) != null && con.get(i-1).isValid();
956 wct.direction = NONE;
957
958 if(isOneway(m)){
959 if(lastWct != null && lastWct.isOnewayTail) {
960 wct.isOnewayHead = true;
961 }
962 if(lastBackwardWay == UNCONNECTED && lastForwardWay == UNCONNECTED){ //Beginning of new oneway
963 wct.isOnewayHead = true;
964 lastForwardWay = i-1;
965 lastBackwardWay = i-1;
966 onewayBeginning = true;
967 }
968 }
969
970 if (wct.linkPrev) {
971 if(lastBackwardWay != UNCONNECTED && lastForwardWay != UNCONNECTED) {
972 wct = determineOnewayConnectionType(con, m, i, wct);
973 if(!wct.linkPrev) {
974 firstGroupIdx = i;
975 }
976 }
977
978 if(!isOneway(m)) {
979 wct.direction = determineDirection(i-1, lastWct.direction, i);
980 wct.linkPrev = (wct.direction != NONE);
981 }
982 }
983
984 if (!wct.linkPrev) {
985 wct.direction = determineDirectionOfFirst(i, m);
986 if(isOneway(m)){
987 wct.isOnewayLoopForwardPart = true;
988 lastForwardWay = i;
989 }
990 }
991
992 wct.linkNext = false;
993 if(lastWct != null) {
994 lastWct.linkNext = wct.linkPrev;
995 }
996 con.set(i, wct);
997 lastWct = wct;
998
999 if(!wct.linkPrev) {
1000 if(i > 0) {
1001 makeLoopIfNeeded(con, i-1);
1002 }
1003 firstGroupIdx = i;
1004 }
1005 }
1006 makeLoopIfNeeded(con, members.size()-1);
1007 connectionType = con;
1008 }
1009
1010 // private static void unconnectPreviousLink(List<WayConnectionType> con, int beg, boolean backward){
1011 // int i = beg;
1012 // while(true){
1013 // WayConnectionType t = con.get(i--);
1014 // t.isOnewayOppositeConnected = false;
1015 // if(backward && t.isOnewayLoopBackwardPart) break;
1016 // if(!backward && t.isOnewayLoopForwardPart) break;
1017 // }
1018 // }
1019
1020 private static Direction reverse(final Direction dir){
1021 if(dir == FORWARD) return BACKWARD;
1022 if(dir == BACKWARD) return FORWARD;
1023 return dir;
1024 }
1025
1026 private static boolean isBackward(final RelationMember member){
1027 return member.getRole().equals("backward");
1028 }
1029
1030 private static boolean isForward(final RelationMember member){
1031 return member.getRole().equals("forward");
1032 }
1033
1034 public static boolean isOneway(final RelationMember member){
1035 return isForward(member) || isBackward(member);
1036 }
1037
1038 int firstGroupIdx;
1039 private void makeLoopIfNeeded(final List<WayConnectionType> con, final int i) {
1040 boolean loop;
1041 if (i == firstGroupIdx) { //is primitive loop
1042 loop = determineDirection(i, FORWARD, i) == FORWARD;
1043 } else {
1044 loop = determineDirection(i, con.get(i).direction, firstGroupIdx) == con.get(firstGroupIdx).direction;
1045 }
1046 if (loop) {
1047 for (int j=firstGroupIdx; j <= i; ++j) {
1048 con.get(j).isLoop = true;
1049 }
1050 }
1051 }
1052
1053 private Direction determineDirectionOfFirst(final int i, final RelationMember m) {
1054 if (roundaboutType(i) != NONE)
1055 return roundaboutType(i);
1056
1057 if (isOneway(m)){
1058 if(isBackward(m)) return BACKWARD;
1059 else return FORWARD;
1060 } else { /** guess the direction and see if it fits with the next member */
1061 if(determineDirection(i, FORWARD, i+1) != NONE) return FORWARD;
1062 if(determineDirection(i, BACKWARD, i+1) != NONE) return BACKWARD;
1063 }
1064 return NONE;
1065 }
1066
1067 int lastForwardWay, lastBackwardWay;
1068 boolean onewayBeginning;
1069 private WayConnectionType determineOnewayConnectionType(final List<WayConnectionType> con,
1070 RelationMember m, int i, final WayConnectionType wct) {
1071 Direction dirFW = determineDirection(lastForwardWay, con.get(lastForwardWay).direction, i);
1072 Direction dirBW = NONE;
1073 if(onewayBeginning) {
1074 if(lastBackwardWay < 0) {
1075 dirBW = determineDirection(firstGroupIdx, reverse(con.get(firstGroupIdx).direction), i, true);
1076 } else {
1077 dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true);
1078 }
1079
1080 if(dirBW != NONE) {
1081 onewayBeginning = false;
1082 }
1083 } else {
1084 dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true);
1085 }
1086
1087 if(isOneway(m)) {
1088 if(dirBW != NONE){
1089 wct.direction = dirBW;
1090 lastBackwardWay = i;
1091 wct.isOnewayLoopBackwardPart = true;
1092 }
1093 if(dirFW != NONE){
1094 wct.direction = dirFW;
1095 lastForwardWay = i;
1096 wct.isOnewayLoopForwardPart = true;
1097 }
1098 if(dirFW == NONE && dirBW == NONE) { //Not connected to previous
1099 // unconnectPreviousLink(con, i, true);
1100 // unconnectPreviousLink(con, i, false);
1101 wct.linkPrev = false;
1102 if(isOneway(m)){
1103 wct.isOnewayHead = true;
1104 lastForwardWay = i-1;
1105 lastBackwardWay = i-1;
1106 } else {
1107 lastForwardWay = UNCONNECTED;
1108 lastBackwardWay = UNCONNECTED;
1109 }
1110 onewayBeginning = true;
1111 }
1112
1113 if(dirFW != NONE && dirBW != NONE) { //End of oneway loop
1114 if(i+1<members.size() && determineDirection(i, dirFW, i+1) != NONE) {
1115 wct.isOnewayLoopBackwardPart = false;
1116 dirBW = NONE;
1117 wct.direction = dirFW;
1118 } else {
1119 wct.isOnewayLoopForwardPart = false;
1120 dirFW = NONE;
1121 wct.direction = dirBW;
1122 }
1123
1124 wct.isOnewayTail = true;
1125 }
1126
1127 } else {
1128 lastForwardWay = UNCONNECTED;
1129 lastBackwardWay = UNCONNECTED;
1130 if(dirFW == NONE || dirBW == NONE) {
1131 wct.linkPrev = false;
1132 }
1133 }
1134 return wct;
1135 }
1136}
Note: See TracBrowser for help on using the repository browser.