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

Last change on this file since 3928 was 3928, checked in by jttt, 13 years ago

Fix #6015 Relation-Editor: added objects are not selected when adding "above selected"

  • 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 primtivesAdded(PrimitivesAddedEvent event) {/* ignore */}
110
111 public void primtivesRemoved(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 int idx = members.indexOf(member);
508 if ( idx >= 0) {
509 selectedIndices.add(idx);
510 }
511 }
512 setSelectedMembersIdx(selectedIndices);
513 }
514
515 /**
516 * Selects the members in the collection selectedIndices
517 *
518 * @param selectedIndices the collection of selected member indices
519 */
520 public void setSelectedMembersIdx(Collection<Integer> selectedIndices) {
521 if (selectedIndices == null || selectedIndices.isEmpty()) {
522 getSelectionModel().clearSelection();
523 return;
524 }
525 // select the members
526 //
527 getSelectionModel().setValueIsAdjusting(true);
528 getSelectionModel().clearSelection();
529 for (int row : selectedIndices) {
530 getSelectionModel().addSelectionInterval(row, row);
531 }
532 getSelectionModel().setValueIsAdjusting(false);
533 // make the first selected member visible
534 //
535 if (selectedIndices.size() > 0) {
536 fireMakeMemberVisible(Collections.min(selectedIndices));
537 }
538 }
539
540 /**
541 * Replies true if the index-th relation members referrs
542 * to an editable relation, i.e. a relation which is not
543 * incomplete.
544 *
545 * @param index the index
546 * @return true, if the index-th relation members referrs
547 * to an editable relation, i.e. a relation which is not
548 * incomplete
549 */
550 public boolean isEditableRelation(int index) {
551 if (index < 0 || index >= members.size())
552 return false;
553 RelationMember member = members.get(index);
554 if (!member.isRelation())
555 return false;
556 Relation r = member.getRelation();
557 return !r.isIncomplete();
558 }
559
560 /**
561 * Replies true if there is at least one relation member in this model
562 * which refers to at least on the primitives in <code>primitives</code>.
563 *
564 * @param primitives the collection of primitives
565 * @return true if there is at least one relation member in this model
566 * which refers to at least on the primitives in <code>primitives</code>; false
567 * otherwise
568 */
569 public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) {
570 if (primitives == null || primitives.isEmpty())
571 return false;
572 HashSet<OsmPrimitive> referrers = new HashSet<OsmPrimitive>();
573 for(RelationMember member : members) {
574 referrers.add(member.getMember());
575 }
576 Iterator<OsmPrimitive> it = primitives.iterator();
577 while(it.hasNext()) {
578 OsmPrimitive referred = it.next();
579 if (referrers.contains(referred))
580 return true;
581 }
582 return false;
583 }
584
585 /**
586 * Selects all mebers which refer to {@see OsmPrimitive}s in the collections
587 * <code>primitmives</code>. Does nothing is primitives is null.
588 *
589 * @param primitives the collection of primitives
590 */
591 public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) {
592 if (primitives == null) return;
593 getSelectionModel().setValueIsAdjusting(true);
594 getSelectionModel().clearSelection();
595 for (int i=0; i< members.size();i++) {
596 RelationMember m = members.get(i);
597 if (primitives.contains(m.getMember())) {
598 this.getSelectionModel().addSelectionInterval(i,i);
599 }
600 }
601 getSelectionModel().setValueIsAdjusting(false);
602 if (getSelectedIndices().size() > 0) {
603 fireMakeMemberVisible(getSelectedIndices().get(0));
604 }
605 }
606
607 /**
608 * Replies true if <code>primitive</code> is currently selected in the layer this
609 * model is attached to
610 *
611 * @param primitive the primitive
612 * @return true if <code>primitive</code> is currently selected in the layer this
613 * model is attached to, false otherwise
614 */
615 public boolean isInJosmSelection(OsmPrimitive primitive) {
616 return layer.data.isSelected(primitive);
617 }
618
619 /**
620 * Replies true if the layer this model belongs to is equal to the active
621 * layer
622 *
623 * @return true if the layer this model belongs to is equal to the active
624 * layer
625 */
626 protected boolean isActiveLayer() {
627 if (Main.map == null || Main.map.mapView == null) return false;
628 return Main.map.mapView.getActiveLayer() == layer;
629 }
630
631 /**
632 * get a node we can link against when sorting members
633 * @param element the element we want to link against
634 * @param linked_element already linked against element
635 * @return the unlinked node if element is a way, the node itself if element is a node, null otherwise
636 */
637 private static Node getUnusedNode(RelationMember element, RelationMember linked_element)
638 {
639 Node result = null;
640
641 if (element.isWay()) {
642 Way w = element.getWay();
643 if (linked_element.isWay()) {
644 Way x = linked_element.getWay();
645 if ((w.firstNode() == x.firstNode()) || (w.firstNode() == x.lastNode())) {
646 result = w.lastNode();
647 } else {
648 result = w.firstNode();
649 }
650 } else if (linked_element.isNode()) {
651 Node m = linked_element.getNode();
652 if (w.firstNode() == m) {
653 result = w.lastNode();
654 } else {
655 result = w.firstNode();
656 }
657 }
658 } else if (element.isNode()) {
659 Node n = element.getNode();
660 result = n;
661 }
662
663 return result;
664 }
665
666 /*
667 * Sort a collection of relation members by the way they are linked.
668 *
669 * @param relationMembers collection of relation members
670 * @return sorted collection of relation members
671 */
672 private List<RelationMember> sortMembers(List<RelationMember> relationMembers) {
673 RelationNodeMap map = new RelationNodeMap(relationMembers);
674 // List of groups of linked members
675 //
676 ArrayList<LinkedList<Integer>> allGroups = new ArrayList<LinkedList<Integer>>();
677
678 // current group of members that are linked among each other
679 // Two successive members are always linked i.e. have a common node.
680 //
681 LinkedList<Integer> group;
682
683 Integer first;
684 while ((first = map.pop()) != null) {
685 group = new LinkedList<Integer>();
686 group.add(first);
687
688 allGroups.add(group);
689
690 Integer next = first;
691 while ((next = map.popAdjacent(next)) != null) {
692 group.addLast(next);
693 }
694
695 // The first element need not be in front of the list.
696 // So the search goes in both directions
697 //
698 next = first;
699 while ((next = map.popAdjacent(next)) != null) {
700 group.addFirst(next);
701 }
702 }
703
704 group = new LinkedList<Integer>();
705 group.addAll(map.getNotSortableMembers());
706 allGroups.add(group);
707
708 ArrayList<RelationMember> newMembers = new ArrayList<RelationMember>();
709 for (LinkedList<Integer> tmpGroup : allGroups) {
710 for (Integer p : tmpGroup) {
711 newMembers.add(relationMembers.get(p));
712 }
713 }
714 return newMembers;
715 }
716
717 /**
718 * Sort the selected relation members by the way they are linked.
719 */
720 void sort() {
721 List<RelationMember> selectedMembers = new ArrayList<RelationMember>(getSelectedMembers());
722 List<RelationMember> sortedMembers = null;
723 List<RelationMember> newMembers;
724 if (selectedMembers.size() <= 1) {
725 newMembers = sortMembers(members);
726 sortedMembers = newMembers;
727 } else {
728 sortedMembers = sortMembers(selectedMembers);
729 List<Integer> selectedIndices = getSelectedIndices();
730 newMembers = new ArrayList<RelationMember>();
731 boolean inserted = false;
732 for (int i=0; i < members.size(); i++) {
733 if (selectedIndices.contains(i)) {
734 if (!inserted) {
735 newMembers.addAll(sortedMembers);
736 inserted = true;
737 }
738 } else {
739 newMembers.add(members.get(i));
740 }
741 }
742 }
743
744 if (members.size() != newMembers.size()) throw new AssertionError();
745
746 members.clear();
747 members.addAll(newMembers);
748 fireTableDataChanged();
749 setSelectedMembers(sortedMembers);
750 }
751
752 private Direction determineDirection(int ref_i, Direction ref_direction, int k) {
753 return determineDirection(ref_i, ref_direction, k, false);
754 }
755 /**
756 * Determines the direction of way k with respect to the way ref_i.
757 * The way ref_i is assumed to have the direction ref_direction and
758 * to be the predecessor of k.
759 *
760 * If both ways are not linked in any way, NONE is returned.
761 *
762 * Else the direction is given as follows:
763 * Let the relation be a route of oneway streets, and someone travels them in the given order.
764 * Direction is FORWARD if it is legal and BACKWARD if it is illegal to do so for the given way.
765 *
766 **/
767 private Direction determineDirection(int ref_i, final Direction ref_direction, int k, boolean reversed) {
768 if (ref_i < 0 || k < 0 || ref_i >= members.size() || k >= members.size())
769 return NONE;
770 if (ref_direction == NONE)
771 return NONE;
772
773 final RelationMember m_ref = members.get(ref_i);
774 final RelationMember m = members.get(k);
775 Way way_ref = null;
776 Way way = null;
777
778 if (m_ref.isWay()) {
779 way_ref = m_ref.getWay();
780 }
781 if (m.isWay()) {
782 way = m.getWay();
783 }
784
785 if (way_ref == null || way == null)
786 return NONE;
787
788 /** the list of nodes the way k can dock to */
789 List<Node> refNodes= new ArrayList<Node>();
790
791 switch (ref_direction) {
792 case FORWARD:
793 refNodes.add(way_ref.lastNode());
794 break;
795 case BACKWARD:
796 refNodes.add(way_ref.firstNode());
797 break;
798 case ROUNDABOUT_LEFT:
799 case ROUNDABOUT_RIGHT:
800 refNodes = way_ref.getNodes();
801 break;
802 }
803
804 if (refNodes == null)
805 return NONE;
806
807 for (Node n : refNodes) {
808 if (n == null) {
809 continue;
810 }
811 if (roundaboutType(k) != NONE) {
812 for (Node nn : way.getNodes()) {
813 if (n == nn)
814 return roundaboutType(k);
815 }
816 } else if(isOneway(m)) {
817 if (n == RelationNodeMap.firstOnewayNode(m) && !reversed) {
818 if(isBackward(m))
819 return BACKWARD;
820 else
821 return FORWARD;
822 }
823 if (n == RelationNodeMap.lastOnewayNode(m) && reversed) {
824 if(isBackward(m))
825 return FORWARD;
826 else
827 return BACKWARD;
828 }
829 } else {
830 if (n == way.firstNode())
831 return FORWARD;
832 if (n == way.lastNode())
833 return BACKWARD;
834 }
835 }
836 return NONE;
837 }
838
839 /**
840 * determine, if the way i is a roundabout and if yes, what type of roundabout
841 */
842 private Direction roundaboutType(int i) {
843 RelationMember m = members.get(i);
844 if (m == null || !m.isWay()) return NONE;
845 Way w = m.getWay();
846 return roundaboutType(w);
847 }
848 static Direction roundaboutType(Way w) {
849 if (w != null &&
850 "roundabout".equals(w.get("junction")) &&
851 w.getNodesCount() < 200 &&
852 w.getNodesCount() > 2 &&
853 w.getNode(0) != null &&
854 w.getNode(1) != null &&
855 w.getNode(2) != null &&
856 w.firstNode() == w.lastNode()) {
857 /** do some simple determinant / cross pruduct test on the first 3 nodes
858 to see, if the roundabout goes clock wise or ccw */
859 EastNorth en1 = w.getNode(0).getEastNorth();
860 EastNorth en2 = w.getNode(1).getEastNorth();
861 EastNorth en3 = w.getNode(2).getEastNorth();
862 en1 = en1.sub(en2);
863 en2 = en2.sub(en3);
864 return en1.north() * en2.east() - en2.north() * en1.east() > 0 ? ROUNDABOUT_LEFT : ROUNDABOUT_RIGHT;
865 } else
866 return NONE;
867 }
868
869 private WayConnectionType wayConnection(int i) {
870 if (connectionType == null) {
871 updateLinks();
872 }
873 return connectionType.get(i);
874 }
875
876 public void tableChanged(TableModelEvent e) {
877 connectionType = null;
878 }
879
880 /**
881 * Reverse the relation members.
882 */
883 void reverse() {
884 List<Integer> selectedIndices = getSelectedIndices();
885 List<Integer> selectedIndicesReversed = getSelectedIndices();
886
887 if (selectedIndices.size() <= 1) {
888 Collections.reverse(members);
889 fireTableDataChanged();
890 setSelectedMembers(members);
891 } else {
892 Collections.reverse(selectedIndicesReversed);
893
894 ArrayList<RelationMember> newMembers = new ArrayList<RelationMember>(members);
895
896 for (int i=0; i < selectedIndices.size(); i++) {
897 newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i)));
898 }
899
900 if (members.size() != newMembers.size()) throw new AssertionError();
901 members.clear();
902 members.addAll(newMembers);
903 fireTableDataChanged();
904 setSelectedMembersIdx(selectedIndices);
905 }
906 }
907
908 /**
909 * refresh the cache of member WayConnectionTypes
910 */
911 public void updateLinks() {
912 connectionType = null;
913 final List<WayConnectionType> con = new ArrayList<WayConnectionType>();
914
915 for (int i=0; i<members.size(); ++i) {
916 con.add(null);
917 }
918
919 firstGroupIdx=0;
920
921 lastForwardWay = UNCONNECTED;
922 lastBackwardWay = UNCONNECTED;
923 onewayBeginning = false;
924 WayConnectionType lastWct = null;
925
926 for (int i=0; i<members.size(); ++i) {
927 final RelationMember m = members.get(i);
928 if (!m.isWay() || m.getWay() == null || m.getWay().isIncomplete()) {
929 if(i > 0) {
930 makeLoopIfNeeded(con, i-1);
931 }
932 con.set(i, new WayConnectionType());
933 firstGroupIdx = i;
934 continue;
935 }
936
937 WayConnectionType wct = new WayConnectionType(false);
938 wct.linkPrev = i>0 && con.get(i-1) != null && con.get(i-1).isValid();
939 wct.direction = NONE;
940
941 if(isOneway(m)){
942 if(lastWct != null && lastWct.isOnewayTail) {
943 wct.isOnewayHead = true;
944 }
945 if(lastBackwardWay == UNCONNECTED && lastForwardWay == UNCONNECTED){ //Beginning of new oneway
946 wct.isOnewayHead = true;
947 lastForwardWay = i-1;
948 lastBackwardWay = i-1;
949 onewayBeginning = true;
950 }
951 }
952
953 if (wct.linkPrev) {
954 if(lastBackwardWay != UNCONNECTED && lastForwardWay != UNCONNECTED) {
955 wct = determineOnewayConnectionType(con, m, i, wct);
956 if(!wct.linkPrev) {
957 firstGroupIdx = i;
958 }
959 }
960
961 if(!isOneway(m)) {
962 wct.direction = determineDirection(i-1, lastWct.direction, i);
963 wct.linkPrev = (wct.direction != NONE);
964 }
965 }
966
967 if (!wct.linkPrev) {
968 wct.direction = determineDirectionOfFirst(i, m);
969 if(isOneway(m)){
970 wct.isOnewayLoopForwardPart = true;
971 lastForwardWay = i;
972 }
973 }
974
975 wct.linkNext = false;
976 if(lastWct != null) {
977 lastWct.linkNext = wct.linkPrev;
978 }
979 con.set(i, wct);
980 lastWct = wct;
981
982 if(!wct.linkPrev) {
983 if(i > 0) {
984 makeLoopIfNeeded(con, i-1);
985 }
986 firstGroupIdx = i;
987 }
988 }
989 makeLoopIfNeeded(con, members.size()-1);
990 connectionType = con;
991 }
992
993 // private static void unconnectPreviousLink(List<WayConnectionType> con, int beg, boolean backward){
994 // int i = beg;
995 // while(true){
996 // WayConnectionType t = con.get(i--);
997 // t.isOnewayOppositeConnected = false;
998 // if(backward && t.isOnewayLoopBackwardPart) break;
999 // if(!backward && t.isOnewayLoopForwardPart) break;
1000 // }
1001 // }
1002
1003 private static Direction reverse(final Direction dir){
1004 if(dir == FORWARD) return BACKWARD;
1005 if(dir == BACKWARD) return FORWARD;
1006 return dir;
1007 }
1008
1009 private static boolean isBackward(final RelationMember member){
1010 return member.getRole().equals("backward");
1011 }
1012
1013 private static boolean isForward(final RelationMember member){
1014 return member.getRole().equals("forward");
1015 }
1016
1017 public static boolean isOneway(final RelationMember member){
1018 return isForward(member) || isBackward(member);
1019 }
1020
1021 int firstGroupIdx;
1022 private void makeLoopIfNeeded(final List<WayConnectionType> con, final int i) {
1023 boolean loop;
1024 if (i == firstGroupIdx) { //is primitive loop
1025 loop = determineDirection(i, FORWARD, i) == FORWARD;
1026 } else {
1027 loop = determineDirection(i, con.get(i).direction, firstGroupIdx) == con.get(firstGroupIdx).direction;
1028 }
1029 if (loop) {
1030 for (int j=firstGroupIdx; j <= i; ++j) {
1031 con.get(j).isLoop = true;
1032 }
1033 }
1034 }
1035
1036 private Direction determineDirectionOfFirst(final int i, final RelationMember m) {
1037 if (roundaboutType(i) != NONE)
1038 return roundaboutType(i);
1039
1040 if (isOneway(m)){
1041 if(isBackward(m)) return BACKWARD;
1042 else return FORWARD;
1043 } else { /** guess the direction and see if it fits with the next member */
1044 if(determineDirection(i, FORWARD, i+1) != NONE) return FORWARD;
1045 if(determineDirection(i, BACKWARD, i+1) != NONE) return BACKWARD;
1046 }
1047 return NONE;
1048 }
1049
1050 int lastForwardWay, lastBackwardWay;
1051 boolean onewayBeginning;
1052 private WayConnectionType determineOnewayConnectionType(final List<WayConnectionType> con,
1053 RelationMember m, int i, final WayConnectionType wct) {
1054 Direction dirFW = determineDirection(lastForwardWay, con.get(lastForwardWay).direction, i);
1055 Direction dirBW = NONE;
1056 if(onewayBeginning) {
1057 if(lastBackwardWay < 0) {
1058 dirBW = determineDirection(firstGroupIdx, reverse(con.get(firstGroupIdx).direction), i, true);
1059 } else {
1060 dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true);
1061 }
1062
1063 if(dirBW != NONE) {
1064 onewayBeginning = false;
1065 }
1066 } else {
1067 dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true);
1068 }
1069
1070 if(isOneway(m)) {
1071 if(dirBW != NONE){
1072 wct.direction = dirBW;
1073 lastBackwardWay = i;
1074 wct.isOnewayLoopBackwardPart = true;
1075 }
1076 if(dirFW != NONE){
1077 wct.direction = dirFW;
1078 lastForwardWay = i;
1079 wct.isOnewayLoopForwardPart = true;
1080 }
1081 if(dirFW == NONE && dirBW == NONE) { //Not connected to previous
1082 // unconnectPreviousLink(con, i, true);
1083 // unconnectPreviousLink(con, i, false);
1084 wct.linkPrev = false;
1085 if(isOneway(m)){
1086 wct.isOnewayHead = true;
1087 lastForwardWay = i-1;
1088 lastBackwardWay = i-1;
1089 } else {
1090 lastForwardWay = UNCONNECTED;
1091 lastBackwardWay = UNCONNECTED;
1092 }
1093 onewayBeginning = true;
1094 }
1095
1096 if(dirFW != NONE && dirBW != NONE) { //End of oneway loop
1097 if(i+1<members.size() && determineDirection(i, dirFW, i+1) != NONE) {
1098 wct.isOnewayLoopBackwardPart = false;
1099 dirBW = NONE;
1100 wct.direction = dirFW;
1101 } else {
1102 wct.isOnewayLoopForwardPart = false;
1103 dirFW = NONE;
1104 wct.direction = dirBW;
1105 }
1106
1107 wct.isOnewayTail = true;
1108 }
1109
1110 } else {
1111 lastForwardWay = UNCONNECTED;
1112 lastBackwardWay = UNCONNECTED;
1113 if(dirFW == NONE || dirBW == NONE) {
1114 wct.linkPrev = false;
1115 }
1116 }
1117 return wct;
1118 }
1119}
Note: See TracBrowser for help on using the repository browser.