001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.dialogs.relation; 003 004 import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.BACKWARD; 005 import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.FORWARD; 006 import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.NONE; 007 import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.ROUNDABOUT_LEFT; 008 import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.ROUNDABOUT_RIGHT; 009 010 import java.util.ArrayList; 011 import java.util.Arrays; 012 import java.util.Collection; 013 import java.util.Collections; 014 import java.util.HashSet; 015 import java.util.Iterator; 016 import java.util.LinkedList; 017 import java.util.List; 018 import java.util.Set; 019 import java.util.concurrent.CopyOnWriteArrayList; 020 021 import javax.swing.DefaultListSelectionModel; 022 import javax.swing.ListSelectionModel; 023 import javax.swing.event.TableModelEvent; 024 import javax.swing.event.TableModelListener; 025 import javax.swing.table.AbstractTableModel; 026 027 import org.openstreetmap.josm.Main; 028 import org.openstreetmap.josm.data.SelectionChangedListener; 029 import org.openstreetmap.josm.data.coor.EastNorth; 030 import org.openstreetmap.josm.data.osm.DataSet; 031 import org.openstreetmap.josm.data.osm.Node; 032 import org.openstreetmap.josm.data.osm.OsmPrimitive; 033 import org.openstreetmap.josm.data.osm.Relation; 034 import org.openstreetmap.josm.data.osm.RelationMember; 035 import org.openstreetmap.josm.data.osm.Way; 036 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 037 import org.openstreetmap.josm.data.osm.event.DataChangedEvent; 038 import org.openstreetmap.josm.data.osm.event.DataSetListener; 039 import org.openstreetmap.josm.data.osm.event.NodeMovedEvent; 040 import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent; 041 import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent; 042 import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent; 043 import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; 044 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent; 045 import org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction; 046 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 047 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel; 048 049 public class MemberTableModel extends AbstractTableModel implements TableModelListener, SelectionChangedListener, DataSetListener, OsmPrimitivesTableModel { 050 051 /** 052 * data of the table model: The list of members and the cached WayConnectionType of each member. 053 **/ 054 private List<RelationMember> members; 055 private List<WayConnectionType> connectionType = null; 056 057 private DefaultListSelectionModel listSelectionModel; 058 private CopyOnWriteArrayList<IMemberModelListener> listeners; 059 private OsmDataLayer layer; 060 061 private final int UNCONNECTED = Integer.MIN_VALUE; 062 063 /** 064 * constructor 065 */ 066 public MemberTableModel(OsmDataLayer layer) { 067 members = new ArrayList<RelationMember>(); 068 listeners = new CopyOnWriteArrayList<IMemberModelListener>(); 069 this.layer = layer; 070 addTableModelListener(this); 071 } 072 073 public OsmDataLayer getLayer() { 074 return layer; 075 } 076 077 public void register() { 078 DataSet.addSelectionListener(this); 079 getLayer().data.addDataSetListener(this); 080 } 081 082 public void unregister() { 083 DataSet.removeSelectionListener(this); 084 getLayer().data.removeDataSetListener(this); 085 } 086 087 /* --------------------------------------------------------------------------- */ 088 /* Interface SelectionChangedListener */ 089 /* --------------------------------------------------------------------------- */ 090 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 091 if (Main.main.getEditLayer() != this.layer) return; 092 // just trigger a repaint 093 Collection<RelationMember> sel = getSelectedMembers(); 094 fireTableDataChanged(); 095 setSelectedMembers(sel); 096 } 097 098 /* --------------------------------------------------------------------------- */ 099 /* 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 if (members.size() > row) { 257 members.remove(row); 258 offset++; 259 } 260 } 261 fireTableDataChanged(); 262 } 263 264 public boolean canMoveUp(int[] rows) { 265 if (rows == null || rows.length == 0) 266 return false; 267 Arrays.sort(rows); 268 return rows[0] > 0 && members.size() > 0; 269 } 270 271 public boolean canMoveDown(int[] rows) { 272 if (rows == null || rows.length == 0) 273 return false; 274 Arrays.sort(rows); 275 return members.size() > 0 && rows[rows.length - 1] < members.size() - 1; 276 } 277 278 public boolean canRemove(int[] rows) { 279 if (rows == null || rows.length == 0) 280 return false; 281 return true; 282 } 283 284 public DefaultListSelectionModel getSelectionModel() { 285 if (listSelectionModel == null) { 286 listSelectionModel = new DefaultListSelectionModel(); 287 listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 288 } 289 return listSelectionModel; 290 } 291 292 public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) { 293 if (primitives == null) 294 return; 295 Iterator<RelationMember> it = members.iterator(); 296 while (it.hasNext()) { 297 RelationMember member = it.next(); 298 if (primitives.contains(member.getMember())) { 299 it.remove(); 300 } 301 } 302 fireTableDataChanged(); 303 } 304 305 public void applyToRelation(Relation relation) { 306 relation.setMembers(members); 307 } 308 309 public boolean hasSameMembersAs(Relation relation) { 310 if (relation == null) 311 return false; 312 if (relation.getMembersCount() != members.size()) 313 return false; 314 for (int i = 0; i < relation.getMembersCount(); i++) { 315 if (!relation.getMember(i).equals(members.get(i))) 316 return false; 317 } 318 return true; 319 } 320 321 /** 322 * Replies the set of incomplete primitives 323 * 324 * @return the set of incomplete primitives 325 */ 326 public Set<OsmPrimitive> getIncompleteMemberPrimitives() { 327 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 328 for (RelationMember member : members) { 329 if (member.getMember().isIncomplete()) { 330 ret.add(member.getMember()); 331 } 332 } 333 return ret; 334 } 335 336 /** 337 * Replies the set of selected incomplete primitives 338 * 339 * @return the set of selected incomplete primitives 340 */ 341 public Set<OsmPrimitive> getSelectedIncompleteMemberPrimitives() { 342 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 343 for (RelationMember member : getSelectedMembers()) { 344 if (member.getMember().isIncomplete()) { 345 ret.add(member.getMember()); 346 } 347 } 348 return ret; 349 } 350 351 /** 352 * Replies true if at least one the relation members is incomplete 353 * 354 * @return true if at least one the relation members is incomplete 355 */ 356 public boolean hasIncompleteMembers() { 357 for (RelationMember member : members) { 358 if (member.getMember().isIncomplete()) 359 return true; 360 } 361 return false; 362 } 363 364 /** 365 * Replies true if at least one of the selected members is incomplete 366 * 367 * @return true if at least one of the selected members is incomplete 368 */ 369 public boolean hasIncompleteSelectedMembers() { 370 for (RelationMember member : getSelectedMembers()) { 371 if (member.getMember().isIncomplete()) 372 return true; 373 } 374 return false; 375 } 376 377 protected List<Integer> getSelectedIndices() { 378 ArrayList<Integer> selectedIndices = new ArrayList<Integer>(); 379 for (int i = 0; i < members.size(); i++) { 380 if (getSelectionModel().isSelectedIndex(i)) { 381 selectedIndices.add(i); 382 } 383 } 384 return selectedIndices; 385 } 386 387 private void addMembersAtIndex(List<? extends OsmPrimitive> primitives, int index) { 388 if (primitives == null) 389 return; 390 int idx = index; 391 for (OsmPrimitive primitive : primitives) { 392 RelationMember member = new RelationMember("", primitive); 393 members.add(idx++, member); 394 } 395 fireTableDataChanged(); 396 getSelectionModel().clearSelection(); 397 getSelectionModel().addSelectionInterval(index, index + primitives.size() - 1); 398 fireMakeMemberVisible(index); 399 } 400 401 public void addMembersAtBeginning(List<? extends OsmPrimitive> primitives) { 402 addMembersAtIndex(primitives, 0); 403 } 404 405 public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) { 406 addMembersAtIndex(primitives, members.size()); 407 } 408 409 public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) { 410 addMembersAtIndex(primitives, idx); 411 } 412 413 public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) { 414 addMembersAtIndex(primitives, idx + 1); 415 } 416 417 /** 418 * Replies the number of members which refer to a particular primitive 419 * 420 * @param primitive the primitive 421 * @return the number of members which refer to a particular primitive 422 */ 423 public int getNumMembersWithPrimitive(OsmPrimitive primitive) { 424 int count = 0; 425 for (RelationMember member : members) { 426 if (member.getMember().equals(primitive)) { 427 count++; 428 } 429 } 430 return count; 431 } 432 433 /** 434 * updates the role of the members given by the indices in <code>idx</code> 435 * 436 * @param idx the array of indices 437 * @param role the new role 438 */ 439 public void updateRole(int[] idx, String role) { 440 if (idx == null || idx.length == 0) 441 return; 442 for (int row : idx) { 443 RelationMember oldMember = members.get(row); 444 RelationMember newMember = new RelationMember(role, oldMember.getMember()); 445 members.remove(row); 446 members.add(row, newMember); 447 } 448 fireTableDataChanged(); 449 for (int row : idx) { 450 getSelectionModel().addSelectionInterval(row, row); 451 } 452 } 453 454 /** 455 * Get the currently selected relation members 456 * 457 * @return a collection with the currently selected relation members 458 */ 459 public Collection<RelationMember> getSelectedMembers() { 460 ArrayList<RelationMember> selectedMembers = new ArrayList<RelationMember>(); 461 for (int i : getSelectedIndices()) { 462 selectedMembers.add(members.get(i)); 463 } 464 return selectedMembers; 465 } 466 467 /** 468 * Replies the set of selected referers. Never null, but may be empty. 469 * 470 * @return the set of selected referers 471 */ 472 public Collection<OsmPrimitive> getSelectedChildPrimitives() { 473 Collection<OsmPrimitive> ret = new ArrayList<OsmPrimitive>(); 474 for (RelationMember m: getSelectedMembers()) { 475 ret.add(m.getMember()); 476 } 477 return ret; 478 } 479 480 /** 481 * Replies the set of selected referers. Never null, but may be empty. 482 * 483 * @return the set of selected referers 484 */ 485 public Set<OsmPrimitive> getChildPrimitives(Collection<? extends OsmPrimitive> referenceSet) { 486 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 487 if (referenceSet == null) return null; 488 for (RelationMember m: members) { 489 if (referenceSet.contains(m.getMember())) { 490 ret.add(m.getMember()); 491 } 492 } 493 return ret; 494 } 495 496 /** 497 * Selects the members in the collection selectedMembers 498 * 499 * @param selectedMembers the collection of selected members 500 */ 501 public void setSelectedMembers(Collection<RelationMember> selectedMembers) { 502 if (selectedMembers == null || selectedMembers.isEmpty()) { 503 getSelectionModel().clearSelection(); 504 return; 505 } 506 507 // lookup the indices for the respective members 508 // 509 Set<Integer> selectedIndices = new HashSet<Integer>(); 510 for (RelationMember member : selectedMembers) { 511 for (int idx = 0; idx < members.size(); ++idx) { 512 if (member.equals(members.get(idx))) { 513 selectedIndices.add(idx); 514 } 515 } 516 } 517 setSelectedMembersIdx(selectedIndices); 518 } 519 520 /** 521 * Selects the members in the collection selectedIndices 522 * 523 * @param selectedIndices the collection of selected member indices 524 */ 525 public void setSelectedMembersIdx(Collection<Integer> selectedIndices) { 526 if (selectedIndices == null || selectedIndices.isEmpty()) { 527 getSelectionModel().clearSelection(); 528 return; 529 } 530 // select the members 531 // 532 getSelectionModel().setValueIsAdjusting(true); 533 getSelectionModel().clearSelection(); 534 for (int row : selectedIndices) { 535 getSelectionModel().addSelectionInterval(row, row); 536 } 537 getSelectionModel().setValueIsAdjusting(false); 538 // make the first selected member visible 539 // 540 if (selectedIndices.size() > 0) { 541 fireMakeMemberVisible(Collections.min(selectedIndices)); 542 } 543 } 544 545 /** 546 * Replies true if the index-th relation members referrs 547 * to an editable relation, i.e. a relation which is not 548 * incomplete. 549 * 550 * @param index the index 551 * @return true, if the index-th relation members referrs 552 * to an editable relation, i.e. a relation which is not 553 * incomplete 554 */ 555 public boolean isEditableRelation(int index) { 556 if (index < 0 || index >= members.size()) 557 return false; 558 RelationMember member = members.get(index); 559 if (!member.isRelation()) 560 return false; 561 Relation r = member.getRelation(); 562 return !r.isIncomplete(); 563 } 564 565 /** 566 * Replies true if there is at least one relation member given as {@code members} 567 * which refers to at least on the primitives in {@code primitives}. 568 * 569 * @param members the members 570 * @param primitives the collection of primitives 571 * @return true if there is at least one relation member in this model 572 * which refers to at least on the primitives in <code>primitives</code>; false 573 * otherwise 574 */ 575 public static boolean hasMembersReferringTo(Collection<RelationMember> members, Collection<OsmPrimitive> primitives) { 576 if (primitives == null || primitives.isEmpty()) { 577 return false; 578 } 579 HashSet<OsmPrimitive> referrers = new HashSet<OsmPrimitive>(); 580 for (RelationMember member : members) { 581 referrers.add(member.getMember()); 582 } 583 for (OsmPrimitive referred : primitives) { 584 if (referrers.contains(referred)) { 585 return true; 586 } 587 } 588 return false; 589 } 590 591 /** 592 * Replies true if there is at least one relation member in this model 593 * which refers to at least on the primitives in <code>primitives</code>. 594 * 595 * @param primitives the collection of primitives 596 * @return true if there is at least one relation member in this model 597 * which refers to at least on the primitives in <code>primitives</code>; false 598 * otherwise 599 */ 600 public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) { 601 return hasMembersReferringTo(members, primitives); 602 } 603 604 /** 605 * Selects all mebers which refer to {@link OsmPrimitive}s in the collections 606 * <code>primitmives</code>. Does nothing is primitives is null. 607 * 608 * @param primitives the collection of primitives 609 */ 610 public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) { 611 if (primitives == null) return; 612 getSelectionModel().setValueIsAdjusting(true); 613 getSelectionModel().clearSelection(); 614 for (int i=0; i< members.size();i++) { 615 RelationMember m = members.get(i); 616 if (primitives.contains(m.getMember())) { 617 this.getSelectionModel().addSelectionInterval(i,i); 618 } 619 } 620 getSelectionModel().setValueIsAdjusting(false); 621 if (getSelectedIndices().size() > 0) { 622 fireMakeMemberVisible(getSelectedIndices().get(0)); 623 } 624 } 625 626 /** 627 * Replies true if <code>primitive</code> is currently selected in the layer this 628 * model is attached to 629 * 630 * @param primitive the primitive 631 * @return true if <code>primitive</code> is currently selected in the layer this 632 * model is attached to, false otherwise 633 */ 634 public boolean isInJosmSelection(OsmPrimitive primitive) { 635 return layer.data.isSelected(primitive); 636 } 637 638 /** 639 * Replies true if the layer this model belongs to is equal to the active 640 * layer 641 * 642 * @return true if the layer this model belongs to is equal to the active 643 * layer 644 */ 645 protected boolean isActiveLayer() { 646 if (Main.map == null || Main.map.mapView == null) return false; 647 return Main.map.mapView.getActiveLayer() == layer; 648 } 649 650 /** 651 * get a node we can link against when sorting members 652 * @param element the element we want to link against 653 * @param linked_element already linked against element 654 * @return the unlinked node if element is a way, the node itself if element is a node, null otherwise 655 */ 656 /*private static Node getUnusedNode(RelationMember element, RelationMember linked_element) 657 { 658 Node result = null; 659 660 if (element.isWay()) { 661 Way w = element.getWay(); 662 if (linked_element.isWay()) { 663 Way x = linked_element.getWay(); 664 if ((w.firstNode() == x.firstNode()) || (w.firstNode() == x.lastNode())) { 665 result = w.lastNode(); 666 } else { 667 result = w.firstNode(); 668 } 669 } else if (linked_element.isNode()) { 670 Node m = linked_element.getNode(); 671 if (w.firstNode() == m) { 672 result = w.lastNode(); 673 } else { 674 result = w.firstNode(); 675 } 676 } 677 } else if (element.isNode()) { 678 Node n = element.getNode(); 679 result = n; 680 } 681 682 return result; 683 }*/ 684 685 /* 686 * Sort a collection of relation members by the way they are linked. 687 * 688 * @param relationMembers collection of relation members 689 * @return sorted collection of relation members 690 */ 691 private List<RelationMember> sortMembers(List<RelationMember> relationMembers) { 692 RelationNodeMap map = new RelationNodeMap(relationMembers); 693 // List of groups of linked members 694 // 695 ArrayList<LinkedList<Integer>> allGroups = new ArrayList<LinkedList<Integer>>(); 696 697 // current group of members that are linked among each other 698 // Two successive members are always linked i.e. have a common node. 699 // 700 LinkedList<Integer> group; 701 702 Integer first; 703 while ((first = map.pop()) != null) { 704 group = new LinkedList<Integer>(); 705 group.add(first); 706 707 allGroups.add(group); 708 709 Integer next = first; 710 while ((next = map.popAdjacent(next)) != null) { 711 group.addLast(next); 712 } 713 714 // The first element need not be in front of the list. 715 // So the search goes in both directions 716 // 717 next = first; 718 while ((next = map.popAdjacent(next)) != null) { 719 group.addFirst(next); 720 } 721 } 722 723 group = new LinkedList<Integer>(); 724 group.addAll(map.getNotSortableMembers()); 725 allGroups.add(group); 726 727 ArrayList<RelationMember> newMembers = new ArrayList<RelationMember>(); 728 for (LinkedList<Integer> tmpGroup : allGroups) { 729 for (Integer p : tmpGroup) { 730 newMembers.add(relationMembers.get(p)); 731 } 732 } 733 return newMembers; 734 } 735 736 /** 737 * Sort the selected relation members by the way they are linked. 738 */ 739 void sort() { 740 List<RelationMember> selectedMembers = new ArrayList<RelationMember>(getSelectedMembers()); 741 List<RelationMember> sortedMembers = null; 742 List<RelationMember> newMembers; 743 if (selectedMembers.size() <= 1) { 744 newMembers = sortMembers(members); 745 sortedMembers = newMembers; 746 } else { 747 sortedMembers = sortMembers(selectedMembers); 748 List<Integer> selectedIndices = getSelectedIndices(); 749 newMembers = new ArrayList<RelationMember>(); 750 boolean inserted = false; 751 for (int i=0; i < members.size(); i++) { 752 if (selectedIndices.contains(i)) { 753 if (!inserted) { 754 newMembers.addAll(sortedMembers); 755 inserted = true; 756 } 757 } else { 758 newMembers.add(members.get(i)); 759 } 760 } 761 } 762 763 if (members.size() != newMembers.size()) throw new AssertionError(); 764 765 members.clear(); 766 members.addAll(newMembers); 767 fireTableDataChanged(); 768 setSelectedMembers(sortedMembers); 769 } 770 771 private Direction determineDirection(int ref_i, Direction ref_direction, int k) { 772 return determineDirection(ref_i, ref_direction, k, false); 773 } 774 /** 775 * Determines the direction of way k with respect to the way ref_i. 776 * The way ref_i is assumed to have the direction ref_direction and 777 * to be the predecessor of k. 778 * 779 * If both ways are not linked in any way, NONE is returned. 780 * 781 * Else the direction is given as follows: 782 * Let the relation be a route of oneway streets, and someone travels them in the given order. 783 * Direction is FORWARD if it is legal and BACKWARD if it is illegal to do so for the given way. 784 * 785 **/ 786 private Direction determineDirection(int ref_i, final Direction ref_direction, int k, boolean reversed) { 787 if (ref_i < 0 || k < 0 || ref_i >= members.size() || k >= members.size()) 788 return NONE; 789 if (ref_direction == NONE) 790 return NONE; 791 792 final RelationMember m_ref = members.get(ref_i); 793 final RelationMember m = members.get(k); 794 Way way_ref = null; 795 Way way = null; 796 797 if (m_ref.isWay()) { 798 way_ref = m_ref.getWay(); 799 } 800 if (m.isWay()) { 801 way = m.getWay(); 802 } 803 804 if (way_ref == null || way == null) 805 return NONE; 806 807 /** the list of nodes the way k can dock to */ 808 List<Node> refNodes= new ArrayList<Node>(); 809 810 switch (ref_direction) { 811 case FORWARD: 812 refNodes.add(way_ref.lastNode()); 813 break; 814 case BACKWARD: 815 refNodes.add(way_ref.firstNode()); 816 break; 817 case ROUNDABOUT_LEFT: 818 case ROUNDABOUT_RIGHT: 819 refNodes = way_ref.getNodes(); 820 break; 821 } 822 823 if (refNodes == null) 824 return NONE; 825 826 for (Node n : refNodes) { 827 if (n == null) { 828 continue; 829 } 830 if (roundaboutType(k) != NONE) { 831 for (Node nn : way.getNodes()) { 832 if (n == nn) 833 return roundaboutType(k); 834 } 835 } else if(isOneway(m)) { 836 if (n == RelationNodeMap.firstOnewayNode(m) && !reversed) { 837 if(isBackward(m)) 838 return BACKWARD; 839 else 840 return FORWARD; 841 } 842 if (n == RelationNodeMap.lastOnewayNode(m) && reversed) { 843 if(isBackward(m)) 844 return FORWARD; 845 else 846 return BACKWARD; 847 } 848 } else { 849 if (n == way.firstNode()) 850 return FORWARD; 851 if (n == way.lastNode()) 852 return BACKWARD; 853 } 854 } 855 return NONE; 856 } 857 858 /** 859 * determine, if the way i is a roundabout and if yes, what type of roundabout 860 */ 861 private Direction roundaboutType(int i) { 862 RelationMember m = members.get(i); 863 if (m == null || !m.isWay()) return NONE; 864 Way w = m.getWay(); 865 return roundaboutType(w); 866 } 867 static Direction roundaboutType(Way w) { 868 if (w != null && 869 "roundabout".equals(w.get("junction")) && 870 w.getNodesCount() < 200 && 871 w.getNodesCount() > 2 && 872 w.getNode(0) != null && 873 w.getNode(1) != null && 874 w.getNode(2) != null && 875 w.firstNode() == w.lastNode()) { 876 /** do some simple determinant / cross pruduct test on the first 3 nodes 877 to see, if the roundabout goes clock wise or ccw */ 878 EastNorth en1 = w.getNode(0).getEastNorth(); 879 EastNorth en2 = w.getNode(1).getEastNorth(); 880 EastNorth en3 = w.getNode(2).getEastNorth(); 881 if (en1 != null && en2 != null && en3 != null) { 882 en1 = en1.sub(en2); 883 en2 = en2.sub(en3); 884 return en1.north() * en2.east() - en2.north() * en1.east() > 0 ? ROUNDABOUT_LEFT : ROUNDABOUT_RIGHT; 885 } 886 } 887 return NONE; 888 } 889 890 WayConnectionType getWayConnection(int i) { 891 if (connectionType == null) { 892 updateLinks(); 893 } 894 return connectionType.get(i); 895 } 896 897 public void tableChanged(TableModelEvent e) { 898 connectionType = null; 899 } 900 901 /** 902 * Reverse the relation members. 903 */ 904 void reverse() { 905 List<Integer> selectedIndices = getSelectedIndices(); 906 List<Integer> selectedIndicesReversed = getSelectedIndices(); 907 908 if (selectedIndices.size() <= 1) { 909 Collections.reverse(members); 910 fireTableDataChanged(); 911 setSelectedMembers(members); 912 } else { 913 Collections.reverse(selectedIndicesReversed); 914 915 ArrayList<RelationMember> newMembers = new ArrayList<RelationMember>(members); 916 917 for (int i=0; i < selectedIndices.size(); i++) { 918 newMembers.set(selectedIndices.get(i), members.get(selectedIndicesReversed.get(i))); 919 } 920 921 if (members.size() != newMembers.size()) throw new AssertionError(); 922 members.clear(); 923 members.addAll(newMembers); 924 fireTableDataChanged(); 925 setSelectedMembersIdx(selectedIndices); 926 } 927 } 928 929 /** 930 * refresh the cache of member WayConnectionTypes 931 */ 932 public void updateLinks() { 933 connectionType = null; 934 final List<WayConnectionType> con = new ArrayList<WayConnectionType>(); 935 936 for (int i=0; i<members.size(); ++i) { 937 con.add(null); 938 } 939 940 firstGroupIdx=0; 941 942 lastForwardWay = UNCONNECTED; 943 lastBackwardWay = UNCONNECTED; 944 onewayBeginning = false; 945 WayConnectionType lastWct = null; 946 947 for (int i=0; i<members.size(); ++i) { 948 final RelationMember m = members.get(i); 949 if (!m.isWay() || m.getWay() == null || m.getWay().isIncomplete()) { 950 if(i > 0) { 951 makeLoopIfNeeded(con, i-1); 952 } 953 con.set(i, new WayConnectionType()); 954 firstGroupIdx = i; 955 continue; 956 } 957 958 WayConnectionType wct = new WayConnectionType(false); 959 wct.linkPrev = i>0 && con.get(i-1) != null && con.get(i-1).isValid(); 960 wct.direction = NONE; 961 962 if(isOneway(m)){ 963 if(lastWct != null && lastWct.isOnewayTail) { 964 wct.isOnewayHead = true; 965 } 966 if(lastBackwardWay == UNCONNECTED && lastForwardWay == UNCONNECTED){ //Beginning of new oneway 967 wct.isOnewayHead = true; 968 lastForwardWay = i-1; 969 lastBackwardWay = i-1; 970 onewayBeginning = true; 971 } 972 } 973 974 if (wct.linkPrev) { 975 if(lastBackwardWay != UNCONNECTED && lastForwardWay != UNCONNECTED) { 976 wct = determineOnewayConnectionType(con, m, i, wct); 977 if(!wct.linkPrev) { 978 firstGroupIdx = i; 979 } 980 } 981 982 if(!isOneway(m)) { 983 wct.direction = determineDirection(i-1, lastWct.direction, i); 984 wct.linkPrev = (wct.direction != NONE); 985 } 986 } 987 988 if (!wct.linkPrev) { 989 wct.direction = determineDirectionOfFirst(i, m); 990 if(isOneway(m)){ 991 wct.isOnewayLoopForwardPart = true; 992 lastForwardWay = i; 993 } 994 } 995 996 wct.linkNext = false; 997 if(lastWct != null) { 998 lastWct.linkNext = wct.linkPrev; 999 } 1000 con.set(i, wct); 1001 lastWct = wct; 1002 1003 if(!wct.linkPrev) { 1004 if(i > 0) { 1005 makeLoopIfNeeded(con, i-1); 1006 } 1007 firstGroupIdx = i; 1008 } 1009 } 1010 makeLoopIfNeeded(con, members.size()-1); 1011 connectionType = con; 1012 } 1013 1014 // private static void unconnectPreviousLink(List<WayConnectionType> con, int beg, boolean backward){ 1015 // int i = beg; 1016 // while(true){ 1017 // WayConnectionType t = con.get(i--); 1018 // t.isOnewayOppositeConnected = false; 1019 // if(backward && t.isOnewayLoopBackwardPart) break; 1020 // if(!backward && t.isOnewayLoopForwardPart) break; 1021 // } 1022 // } 1023 1024 private static Direction reverse(final Direction dir){ 1025 if(dir == FORWARD) return BACKWARD; 1026 if(dir == BACKWARD) return FORWARD; 1027 return dir; 1028 } 1029 1030 private static boolean isBackward(final RelationMember member){ 1031 return member.getRole().equals("backward"); 1032 } 1033 1034 private static boolean isForward(final RelationMember member){ 1035 return member.getRole().equals("forward"); 1036 } 1037 1038 public static boolean isOneway(final RelationMember member){ 1039 return isForward(member) || isBackward(member); 1040 } 1041 1042 int firstGroupIdx; 1043 private void makeLoopIfNeeded(final List<WayConnectionType> con, final int i) { 1044 boolean loop; 1045 if (i == firstGroupIdx) { //is primitive loop 1046 loop = determineDirection(i, FORWARD, i) == FORWARD; 1047 } else { 1048 loop = determineDirection(i, con.get(i).direction, firstGroupIdx) == con.get(firstGroupIdx).direction; 1049 } 1050 if (loop) { 1051 for (int j=firstGroupIdx; j <= i; ++j) { 1052 con.get(j).isLoop = true; 1053 } 1054 } 1055 } 1056 1057 private Direction determineDirectionOfFirst(final int i, final RelationMember m) { 1058 if (roundaboutType(i) != NONE) 1059 return roundaboutType(i); 1060 1061 if (isOneway(m)){ 1062 if(isBackward(m)) return BACKWARD; 1063 else return FORWARD; 1064 } else { /** guess the direction and see if it fits with the next member */ 1065 if(determineDirection(i, FORWARD, i+1) != NONE) return FORWARD; 1066 if(determineDirection(i, BACKWARD, i+1) != NONE) return BACKWARD; 1067 } 1068 return NONE; 1069 } 1070 1071 int lastForwardWay, lastBackwardWay; 1072 boolean onewayBeginning; 1073 private WayConnectionType determineOnewayConnectionType(final List<WayConnectionType> con, 1074 RelationMember m, int i, final WayConnectionType wct) { 1075 Direction dirFW = determineDirection(lastForwardWay, con.get(lastForwardWay).direction, i); 1076 Direction dirBW = NONE; 1077 if(onewayBeginning) { 1078 if(lastBackwardWay < 0) { 1079 dirBW = determineDirection(firstGroupIdx, reverse(con.get(firstGroupIdx).direction), i, true); 1080 } else { 1081 dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true); 1082 } 1083 1084 if(dirBW != NONE) { 1085 onewayBeginning = false; 1086 } 1087 } else { 1088 dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true); 1089 } 1090 1091 if(isOneway(m)) { 1092 if(dirBW != NONE){ 1093 wct.direction = dirBW; 1094 lastBackwardWay = i; 1095 wct.isOnewayLoopBackwardPart = true; 1096 } 1097 if(dirFW != NONE){ 1098 wct.direction = dirFW; 1099 lastForwardWay = i; 1100 wct.isOnewayLoopForwardPart = true; 1101 } 1102 if(dirFW == NONE && dirBW == NONE) { //Not connected to previous 1103 // unconnectPreviousLink(con, i, true); 1104 // unconnectPreviousLink(con, i, false); 1105 wct.linkPrev = false; 1106 if(isOneway(m)){ 1107 wct.isOnewayHead = true; 1108 lastForwardWay = i-1; 1109 lastBackwardWay = i-1; 1110 } else { 1111 lastForwardWay = UNCONNECTED; 1112 lastBackwardWay = UNCONNECTED; 1113 } 1114 onewayBeginning = true; 1115 } 1116 1117 if(dirFW != NONE && dirBW != NONE) { //End of oneway loop 1118 if(i+1<members.size() && determineDirection(i, dirFW, i+1) != NONE) { 1119 wct.isOnewayLoopBackwardPart = false; 1120 dirBW = NONE; 1121 wct.direction = dirFW; 1122 } else { 1123 wct.isOnewayLoopForwardPart = false; 1124 dirFW = NONE; 1125 wct.direction = dirBW; 1126 } 1127 1128 wct.isOnewayTail = true; 1129 } 1130 1131 } else { 1132 lastForwardWay = UNCONNECTED; 1133 lastBackwardWay = UNCONNECTED; 1134 if(dirFW == NONE || dirBW == NONE) { 1135 wct.linkPrev = false; 1136 } 1137 } 1138 return wct; 1139 } 1140 }