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

Last change on this file since 4337 was 4337, checked in by stoecker, 13 years ago

duplicate members of relations are now properly selected in relation dialog

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