Ticket #3771: relation-dialog_loops.patch

File relation-dialog_loops.patch, 19.6 KB (added by bastiK, 14 years ago)
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java

     
    1414
    1515import javax.swing.DefaultListSelectionModel;
    1616import javax.swing.ListSelectionModel;
    17 import javax.swing.table.AbstractTableModel;
    18 
     17import javax.swing.table.AbstractTableModel;
     18import javax.swing.event.TableModelListener;
     19import javax.swing.event.TableModelEvent;
     20
    1921import org.openstreetmap.josm.Main;
    2022import org.openstreetmap.josm.data.osm.Node;
    2123import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    2426import org.openstreetmap.josm.data.osm.Way;
    2527import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2628
    27 public class MemberTableModel extends AbstractTableModel {
    28 
    29     private ArrayList<RelationMember> members;
     29public class MemberTableModel extends AbstractTableModel implements TableModelListener {
     30
     31    /**
     32     * data of the table model: The list of members and the cached WayConnectionType of each member.
     33     **/
     34    private ArrayList<RelationMember> members;
     35    private ArrayList<WayConnectionType> connectionType = null;
     36   
    3037    private DefaultListSelectionModel listSelectionModel;
    3138    private CopyOnWriteArrayList<IMemberModelListener> listeners;
    3239    private OsmDataLayer layer;
     
    3744    public MemberTableModel(OsmDataLayer layer) {
    3845        members = new ArrayList<RelationMember>();
    3946        listeners = new CopyOnWriteArrayList<IMemberModelListener>();
    40         this.layer = layer;
     47        this.layer = layer;
     48        addTableModelListener(this);
    4149    }
    4250
    4351    public void addMemberModelListener(IMemberModelListener listener) {
     
    100108        return columnIndex == 0;
    101109    }
    102110
    103     @Override
    104     public void setValueAt(Object value, int rowIndex, int columnIndex) {
    105         RelationMember member = members.get(rowIndex);
    106         RelationMember newMember = new RelationMember(value.toString(), member.getMember());
    107         members.remove(rowIndex);
    108         members.add(rowIndex, newMember);
    109     }
    110 
    111111    public OsmPrimitive getReferredPrimitive(int idx) {
    112112        return members.get(idx).getMember();
    113113    }
     
    658658            fireTableDataChanged();
    659659        }
    660660    }
     661
     662/**
     663 * Determines the direction of way k with reference to the way ref_i.
     664 * The direction of way ref_i is ref_direction.
     665 *
     666 * ref_i is usually the predecessor of k.
     667 *
     668 * direction:
     669 * Let the relation be a route of oneway streets, and someone travels them in the given order.
     670 * Direction is 1 for if it is legel and -1 if it is illegal to do so for the given way.
     671 *
     672 * If the two ways are not properly linked the return value is 0.
     673 **/
     674    private int determineDirection(int ref_i,int ref_direction, int k) {
     675        if (ref_i < 0 || k < 0 || ref_i >= members.size() || k >= members.size()) {
     676            return 0;
     677        }
     678        if (ref_direction == 0) {
     679            return 0;
     680        }
     681       
     682        RelationMember m_ref = members.get(ref_i);
     683        RelationMember m = members.get(k);
     684        Way way_ref = null;
     685        Way way = null;
    661686
    662     // simple version of code that was removed from GenericReleationEditor
    663     // no recursion and no forward/backward support
    664     // TODO: add back the number of linked elements
    665     // Returns +1 if member i and (i+1) are ways and could be combined without changing
    666     // the direction of one of them. If they are linked "head to head" or "tail to tail"
    667     // -1 is returned.
    668     // In all other cases the result is null.
    669     private Integer linked(int i) {
    670         // this method is aimed at finding out whether the
    671         // relation member is "linked" with the next, i.e. whether
    672         // (if both are ways) these ways are connected.
    673 
    674         Integer link = null;
    675         RelationMember m1 = members.get(i);
    676         RelationMember m2 = members.get((i + 1) % members.size());
    677         Way way1 = null;
    678         Way way2 = null;
    679 
    680         if (m1.isWay()) {
    681             way1 = m1.getWay();
    682         }
    683         if (m2.isWay()) {
    684             way2 = m2.getWay();
    685         }
    686         if ((way1 != null) && (way2 != null)) {
    687             Node way1first = way1.firstNode();
    688             Node way1last = way1.lastNode();
    689             Node way2first = way2.firstNode();
    690             Node way2last = way2.lastNode();
    691             if (way1first != null && way2first != null && (way1first == way2first)) {
    692                 link = -1;
    693             } else if (way1first != null && way2last != null && (way1first == way2last)) {
    694                 link = 1;
    695             } else if (way1last != null && way2first != null && (way1last == way2first)) {
    696                 link = 1;
    697             } else if (way1last != null && way2last != null && (way1last == way2last)) {
    698                 link = -1;
    699             }
     687        if (m_ref.isWay()) {
     688            way_ref = m_ref.getWay();
    700689        }
    701 
    702         return link;
     690        if (m.isWay()) {
     691            way = m.getWay();
     692        }
     693       
     694        if (way_ref == null || way == null) {
     695            return 0;
     696        }
     697       
     698        Node nRef = ref_direction > 0 ? way_ref.lastNode() : way_ref.firstNode();
     699        if (nRef == null) {
     700            return 0;
     701        }
     702       
     703        if (nRef == way.firstNode()) {
     704            return 1;
     705        }
     706        if (nRef == way.lastNode()) {
     707            return -1;
     708        }
     709        return 0;
    703710    }
    704711
    705712    private WayConnectionType wayConnection(int i) {
    706         RelationMember m = members.get(i);
    707         if (! m.isWay())
    708             return new WayConnectionType();
    709         Way w = m.getWay();
    710         if (w == null || w.incomplete)
    711             return new WayConnectionType();
    712 
    713         int ip = (i - 1 + members.size()) % members.size();
    714         Integer link_p = linked(ip);
    715         Integer link_n = linked(i);
    716         Integer dir = 1;
    717         // FIXME: It is somewhat stupid to loop here, but
    718         // there shouldn't be a performance problem in practice.
    719         for (int k = i - 1; k >= 0; --k) {
    720             Integer link = linked(k);
    721             if (link != null) {
    722                 dir *= link;
    723             } else {
    724                 break;
     713        if (connectionType == null) {
     714            updateLinks();
     715        }
     716        return connectionType.get(i);
     717    }
     718
     719    public void tableChanged(TableModelEvent e) {
     720        connectionType = null;
     721    }
     722
     723    public void updateLinks() {
     724        connectionType = null;
     725        ArrayList<WayConnectionType> con = new ArrayList<WayConnectionType>();
     726
     727        for (int i=0; i<members.size(); ++i) con.add(null);
     728
     729        int firstGroupIdx=0;
     730        boolean resetFirstGoupIdx=false;
     731
     732        for (int i=0; i<members.size(); ++i) {
     733            if (resetFirstGoupIdx) {
     734                firstGroupIdx = i;
     735                resetFirstGoupIdx = false;
     736            }
     737
     738            RelationMember m = members.get(i);
     739            if (! m.isWay()) {
     740                con.set(i, new WayConnectionType());
     741                resetFirstGoupIdx = true;
     742                continue;
     743            }
     744
     745            Way w = m.getWay();
     746            if (w == null || w.incomplete) {
     747                con.set(i, new WayConnectionType());
     748                resetFirstGoupIdx = true;
     749                continue;
     750            }
     751
     752            boolean linkPrev = (i != firstGroupIdx);
     753            boolean linkNext;
     754            int dir;
     755            if (linkPrev) {
     756                dir = determineDirection(i-1, con.get(i-1).direction, i);
     757                linkNext = (determineDirection(i, dir, i+1) != 0);
     758            }
     759            else {
     760                dir = determineDirection(i, +1, i+1) != 0 ? +1 : 0;
     761                if (dir == 0) {
     762                    dir = determineDirection(i, -1, i+1) != 0 ? -1 : 0;
     763                }
     764                linkNext = (dir != 0);
     765            }
     766
     767            con.set(i, new WayConnectionType(linkPrev, linkNext, dir));
     768
     769            if (! linkNext) {
     770                boolean loop;
     771                if (i == firstGroupIdx) {
     772                    loop = determineDirection(i, 1, i) == 1;
     773                } else {
     774                    loop = determineDirection(i, dir, firstGroupIdx) == con.get(firstGroupIdx).direction;
     775                }
     776                if (loop) {
     777                    for (int j=firstGroupIdx; j <= i; ++j) {
     778                        con.get(j).isLoop = true;
     779                    }
     780                }
     781                resetFirstGoupIdx = true;
    725782            }
    726783        }
    727         return new WayConnectionType(link_p != null, link_n != null, dir);
    728     }
    729 }
     784        connectionType = con;
     785    }
     786}
  • src/org/openstreetmap/josm/gui/dialogs/relation/RelationNodeMap.java

     
    11package org.openstreetmap.josm.gui.dialogs.relation;
    22
    33import java.util.ArrayList;
     4import java.util.TreeSet;
    45
    56import org.openstreetmap.josm.data.osm.Node;
    67import org.openstreetmap.josm.data.osm.RelationMember;
     
    1213 * @author Christiaan Welvaart <cjw@time4t.net>
    1314 *
    1415 */
    15 public class RelationNodeMap {
    16     private java.util.HashMap<Node, java.util.TreeSet<Integer>> points;
     16public class RelationNodeMap {
     17    /**
     18     * For each way endpoint, list all ways that share this node
     19     */
     20    private java.util.HashMap<Node, TreeSet<Integer>> points;
     21    /**
     22     * Singleton nodes
     23     */
    1724    private java.util.HashMap<Node, Integer> nodes;
    18     private java.util.Vector<Integer> remaining;
    19     private ArrayList<RelationMember> members;
     25    private java.util.Vector<Integer> remaining;
     26    /**
     27     * read only list
     28     */
     29    private final ArrayList<RelationMember> members;
    2030
    2131    RelationNodeMap(ArrayList<RelationMember> members) {
    2232        int i;
    2333
    2434        this.members = members;
    25         points = new java.util.HashMap<Node, java.util.TreeSet<Integer>>();
     35        points = new java.util.HashMap<Node, TreeSet<Integer>>();
    2636        nodes = new java.util.HashMap<Node, Integer>();
    2737        remaining = new java.util.Vector<Integer>();
    2838
     
    6272            Way w = m.getWay();
    6373            if (w.lastNode() == w.firstNode())
    6474            {
    65                 nodes.put(w.firstNode(), Integer.valueOf(n));
     75                nodes.put(w.firstNode(), n);
    6676            }
    6777            else
    6878            {
    6979                if (!points.containsKey(w.firstNode())) {
    70                     points.put(w.firstNode(), new java.util.TreeSet<Integer>());
     80                    points.put(w.firstNode(), new TreeSet<Integer>());
    7181                }
    72                 points.get(w.firstNode()).add(Integer.valueOf(n));
     82                points.get(w.firstNode()).add(n);
    7383
    7484                if (!points.containsKey(w.lastNode())) {
    75                     points.put(w.lastNode(), new java.util.TreeSet<Integer>());
     85                    points.put(w.lastNode(), new TreeSet<Integer>());
    7686                }
    77                 points.get(w.lastNode()).add(Integer.valueOf(n));
     87                points.get(w.lastNode()).add(n);
    7888            }
    7989        } else if (m.isNode()) {
    8090            Node node = m.getNode();
    81             nodes.put(node, Integer.valueOf(n));
     91            nodes.put(node, n);
    8292        } else {
    83             remaining.add(Integer.valueOf(n));
     93            remaining.add(n);
    8494        }
    8595    }
    8696
     
    103113        return result;
    104114    }
    105115
    106     void move(int from, int to) {
    107         if (from != to) {
    108             RelationMember b = members.get(from);
    109             RelationMember a = members.get(to);
    110 
    111             remove(to, b);
    112             add(to, a);
    113         }
    114     }
    115 
    116116    // no node-mapped entries left
    117117    boolean isEmpty() {
    118118        return points.isEmpty() && nodes.isEmpty();
     
    131131            result = nodes.get(node);
    132132            nodes.remove(node);
    133133        } else if (!points.isEmpty()) {
    134             for (java.util.TreeSet<Integer> set : points.values()) {
     134            for (TreeSet<Integer> set : points.values()) {
    135135                if (!set.isEmpty()) {
    136136                    result = set.first();
    137137                    Way w = members.get(result).getWay();
     
    144144
    145145        return result;
    146146    }
    147 }
     147}
  • src/org/openstreetmap/josm/gui/dialogs/relation/WayConnectionType.java

     
    55
    66public class WayConnectionType {
    77
    8     public final boolean connectedToPrevious;
    9     public final boolean connectedToNext;
    10     /** Is 1 if way has the same direction as the first way in the set of connected ways. Else it is (-1) */
     8    /** True, if the corresponding primitive is not a way or the way is incomplete */
     9    private final boolean invalid;
     10
     11    /** True, if linked to the previous / next member.  */
     12    public final boolean linkPrev;
     13    public final boolean linkNext;
     14
     15    /**
     16     * direction is +1 if the first node of this way is connected to the previous way
     17     * and / or the last node of this way is connected to the next way.
     18     * direction is -1 if it is the other way around.
     19     * If this way is neither connected to the previous nor to the next way, then
     20     * direction has the value 0.
     21     */
    1122    public final int direction;
    12     /** The WayConnectionType is invalid, if the corresponding primitive is not a way or the way is incomplete */
    13     public final boolean invalid;
     23
     24    /** True, if the element is part of a closed loop of ways. */
     25    public boolean isLoop;
    1426
    15     public WayConnectionType(boolean connectedToPrevious, boolean connectedToNext, int direction) {
    16         this.connectedToPrevious = connectedToPrevious;
    17         this.connectedToNext = connectedToNext;
     27    public WayConnectionType(boolean linkPrev, boolean linkNext, int direction) {
     28        this.linkPrev = linkPrev;
     29        this.linkNext = linkNext;
     30        this.isLoop = false;
    1831        this.direction = direction;
    1932        invalid = false;
    2033    }
    2134
     35    /** construct invalid instance */
    2236    public WayConnectionType() {
    23         connectedToPrevious = false;
    24         connectedToNext = false;
    25         direction = 1;
     37        this.linkPrev = false;
     38        this.linkNext = false;
     39        this.isLoop = false;
     40        this.direction = 0;
    2641        invalid = true;
     42    }
     43   
     44    public boolean isValid() {
     45        return !invalid;   
    2746    }
    2847
    29 //    @Override
    30 //    public String toString() {
    31 //        return ...
    32 //    }
     48    @Override
     49    public String toString() {
     50        return "[P "+linkPrev+" ;N "+linkNext+" ;D "+direction+" ;L "+isLoop+"]";
     51    }
    3352
    3453    public String getToolTip() {
    35         if (invalid) {
     54        if (!isValid()) {
    3655            return "";
    3756        }
    38         else if (connectedToPrevious && connectedToNext) {
     57        else if (linkPrev && linkNext) {
    3958            return tr("way is connected");
    4059        }
    41         else if (connectedToPrevious) {
     60        else if (linkPrev) {
    4261            return tr("way is connected to previous relation member");
    4362        }
    44         else if (connectedToNext) {
     63        else if (linkNext) {
    4564            return tr("way is connected to next relation member");
    4665        }
    4766        else {
    4867            return tr("way is not connected to previous or next relation member");
    49         }
     68        }//FIXME: isLoop & direction
    5069    }
    5170}
  • src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableLinkedCellRenderer.java

     
    1616
    1717    final static Image arrowUp = ImageProvider.get("dialogs", "arrowup").getImage();
    1818    final static Image arrowDown = ImageProvider.get("dialogs", "arrowdown").getImage();
     19    final static Image corners = ImageProvider.get("dialogs", "roundedcorners").getImage();
    1920    private WayConnectionType value = new WayConnectionType();
    2021
    2122    @Override
     
    3334    @Override
    3435    public void paintComponent(Graphics g) {
    3536        super.paintComponent(g);
    36         if (value == null || value.invalid) {
     37        if (value == null || !value.isValid()) {
    3738            return;
    3839        }
    3940
    40         Image image = null;
     41        Image arrow = null;
    4142        switch (value.direction) {
    4243            case 1:
    43                 image = arrowDown;
     44                arrow = arrowDown;
    4445                break;
    4546            case -1:
    46                 image = arrowUp;
     47                arrow = arrowUp;
    4748                break;
    4849        }
    4950
    5051        int ymax=this.getSize().height - 1;
     52        int xloop = 8;
    5153        int xoff = this.getSize().width / 2;
     54        if (value.isLoop) {
     55            xoff -= xloop / 2 - 1;
     56        }
    5257        int w = 2;
    5358        int p = 2 + w + 1;
    5459        int y1 = 0;
    5560        int y2 = 0;
    5661
    57         if (image != null && (value.connectedToPrevious || value.connectedToNext)) {
    58             g.drawImage(image, xoff-3, ymax / 2 - 2, null);
    59         }
    6062
    61         if (value.connectedToPrevious) {
     63        if (value.linkPrev) {
    6264            g.setColor(Color.black);
    63             g.fillRect(xoff - 2, 0, 5, 2);
     65            g.fillRect(xoff - 1, 0, 3, 1);
    6466            y1 = 0;
    6567        } else {
    66             g.setColor(Color.red);
    67             g.drawRect(xoff-1, p - 1 - w, w, w);
    68             y1 = p;
     68            if (value.isLoop) {
     69                g.setColor(Color.black);
     70                y1 = 5;
     71                g.drawImage(corners,xoff,y1-3,xoff+3,y1, 0,0,3,3, new Color(0,0,0,0), null);
     72                g.drawImage(corners,xoff+xloop-2,y1-3,xoff+xloop+1,y1, 2,0,5,3, new Color(0,0,0,0), null);
     73                g.drawLine(xoff+3,y1-3,xoff+xloop-3,y1-3);
     74            }
     75            else {
     76                g.setColor(Color.red);
     77                g.drawRect(xoff-1, p - 1 - w, w, w);
     78                y1 = p;
     79            }
    6980        }
    7081
    71         if (value.connectedToNext) {
     82        if (value.linkNext) {
    7283            g.setColor(Color.black);
    73             g.fillRect(xoff - 2, ymax - 1, 5, 2);
     84            g.fillRect(xoff - 1, ymax, 3, 1);
    7485            y2 = ymax;
    7586        } else {
    76             g.setColor(Color.red);
    77             g.drawRect(xoff-1, ymax - p + 1, w, w);
    78             y2 = ymax - p;
     87            if (value.isLoop) {
     88                g.setColor(Color.black);
     89                y2 = ymax - 5;
     90                g.fillRect(xoff-1, y2+2, 3, 3);
     91                g.drawLine(xoff, y2, xoff, y2+2);
     92                g.drawImage(corners,xoff+xloop-2,y2+1,xoff+xloop+1,y2+4, 2,2,5,5, new Color(0,0,0,0), null);
     93                g.drawLine(xoff+3-1,y2+3,xoff+xloop-3,y2+3);
     94            }
     95            else {
     96                g.setColor(Color.red);
     97                g.drawRect(xoff-1, ymax - p + 1, w, w);
     98                y2 = ymax - p;
     99            }
     100        }
     101
     102        if ((arrow != null) && (value.linkPrev || value.linkNext)) {
     103            g.drawImage(arrow, xoff-3, (y1 + y2) / 2 - 2, null);
    79104        }
     105
    80106        g.setColor(Color.black);
    81107        g.drawLine(xoff, y1, xoff, y2);
     108        if (value.isLoop) {
     109            g.drawLine(xoff+xloop, y1, xoff+xloop, y2);
     110        }
     111       
    82112    }
    83113}