Changeset 25783 in osm


Ignore:
Timestamp:
2011-04-04T02:12:14+02:00 (14 years ago)
Author:
benshu
Message:

enabled editing of turn relations with one or more ways in the via role
(validation is lagging behind)

Location:
applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes
Files:
18 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/CollectionUtils.java

    r25606 r25783  
    11package org.openstreetmap.josm.plugins.turnlanes;
    22
     3import java.util.ArrayList;
     4import java.util.Collections;
     5import java.util.HashSet;
    36import java.util.Iterator;
    47import java.util.List;
    58import java.util.ListIterator;
     9import java.util.Set;
    610
    711public class CollectionUtils {
     
    3135                };
    3236        }
     37       
     38        public static <E> Set<E> toSet(Iterable<? extends E> iterable) {
     39                final Set<E> set = new HashSet<E>();
     40               
     41                for (E e : iterable) {
     42                        set.add(e);
     43                }
     44               
     45                return Collections.unmodifiableSet(set);
     46        }
     47       
     48        public static <E> List<E> toList(Iterable<? extends E> iterable) {
     49                final List<E> list = new ArrayList<E>();
     50               
     51                for (E e : iterable) {
     52                        list.add(e);
     53                }
     54               
     55                return Collections.unmodifiableList(list);
     56        }
    3357}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiContainer.java

    r25606 r25783  
    11package org.openstreetmap.josm.plugins.turnlanes.gui;
     2
     3import static java.lang.Math.sqrt;
     4import static org.openstreetmap.josm.plugins.turnlanes.gui.GuiUtil.locs;
    25
    36import java.awt.BasicStroke;
     
    58import java.awt.Stroke;
    69import java.awt.geom.Point2D;
     10import java.awt.geom.Rectangle2D;
     11import java.util.ArrayList;
     12import java.util.Collections;
    713import java.util.HashMap;
     14import java.util.List;
    815import java.util.Map;
    916
     17import org.openstreetmap.josm.Main;
     18import org.openstreetmap.josm.data.coor.EastNorth;
     19import org.openstreetmap.josm.data.coor.LatLon;
    1020import org.openstreetmap.josm.plugins.turnlanes.model.Junction;
    1121import org.openstreetmap.josm.plugins.turnlanes.model.Lane;
     22import org.openstreetmap.josm.plugins.turnlanes.model.ModelContainer;
    1223import org.openstreetmap.josm.plugins.turnlanes.model.Road;
    1324
     
    1627        static final Color GREEN = new Color(66, 234, 108);
    1728       
     29        private final ModelContainer mc;
     30       
    1831        private final Point2D translation;
    1932        /**
     
    2134         */
    2235        private final double mpp;
    23         /**
    24          * Meters per source unit.
    25          */
    26         private final double mpsu;
    2736        private final double scale;
    2837        private final double laneWidth;
     
    3342        private final Stroke connectionStroke;
    3443       
    35         public GuiContainer(Point2D origin, double mpsu) {
     44        public GuiContainer(ModelContainer mc) {
     45                final Point2D origin = avgOrigin(locs(mc.getPrimaryJunctions()));
     46               
     47                final LatLon originCoor = Main.proj.eastNorth2latlon(new EastNorth(origin.getX(), origin.getY()));
     48                final LatLon relCoor = Main.proj.eastNorth2latlon(new EastNorth(origin.getX() + 1, origin.getY() + 1));
     49               
     50                // meters per source unit
     51                final double mpsu = relCoor.greatCircleDistance(originCoor) / sqrt(2);
     52               
     53                this.mc = mc;
    3654                this.translation = new Point2D.Double(-origin.getX(), -origin.getY());
    3755                this.mpp = 0.2;
    38                 this.mpsu = mpsu;
    3956                this.scale = mpsu / mpp;
    4057                this.laneWidth = 2 / mpp;
    4158               
    4259                this.connectionStroke = new BasicStroke((float) (laneWidth / 4), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
     60               
     61                for (Junction j : mc.getPrimaryJunctions()) {
     62                        getGui(j);
     63                }
     64        }
     65       
     66        private static Point2D avgOrigin(List<Point2D> locs) {
     67                double x = 0;
     68                double y = 0;
     69               
     70                for (Point2D l : locs) {
     71                        x += l.getX();
     72                        y += l.getY();
     73                }
     74               
     75                return new Point2D.Double(x / locs.size(), y / locs.size());
    4376        }
    4477       
     
    105138        }
    106139       
    107         public GuiContainer empty() {
    108                 return new GuiContainer(new Point2D.Double(-translation.getX(), -translation.getY()), mpsu);
     140        public ModelContainer getModel() {
     141                return mc;
     142        }
     143       
     144        public Rectangle2D getBounds() {
     145                final List<Junction> primaries = new ArrayList<Junction>(mc.getPrimaryJunctions());
     146                final List<Double> top = new ArrayList<Double>();
     147                final List<Double> left = new ArrayList<Double>();
     148                final List<Double> right = new ArrayList<Double>();
     149                final List<Double> bottom = new ArrayList<Double>();
     150               
     151                for (Junction j : primaries) {
     152                        final JunctionGui g = getGui(j);
     153                        final Rectangle2D b = g.getBounds();
     154                       
     155                        top.add(b.getMinY());
     156                        left.add(b.getMinX());
     157                        right.add(b.getMaxX());
     158                        bottom.add(b.getMaxY());
     159                }
     160               
     161                final double t = Collections.min(top);
     162                final double l = Collections.min(left);
     163                final double r = Collections.max(right);
     164                final double b = Collections.max(bottom);
     165               
     166                return new Rectangle2D.Double(l, t, r - l, b - t);
     167        }
     168       
     169        public GuiContainer recalculate() {
     170                return new GuiContainer(mc.recalculate());
     171        }
     172       
     173        public Iterable<RoadGui> getRoads() {
     174                return roads.values();
     175        }
     176       
     177        public Iterable<JunctionGui> getJunctions() {
     178                return junctions.values();
    109179        }
    110180}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/GuiUtil.java

    r25606 r25783  
    1414import org.openstreetmap.josm.data.coor.EastNorth;
    1515import org.openstreetmap.josm.data.osm.Node;
     16import org.openstreetmap.josm.plugins.turnlanes.model.Junction;
    1617
    1718class GuiUtil {
     
    131132        }
    132133       
    133         public static List<Point2D> locs(Iterable<Node> nodes) {
     134        public static List<Point2D> locs(Iterable<Junction> junctions) {
    134135                final List<Point2D> locs = new ArrayList<Point2D>();
    135136               
    136                 for (Node n : nodes) {
    137                         locs.add(loc(n));
     137                for (Junction j : junctions) {
     138                        locs.add(loc(j.getNode()));
    138139                }
    139140               
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/InteractiveElement.java

    r25606 r25783  
    1111                Type LANE_ADDER = new Type() {};
    1212                Type EXTENDER = new Type() {};
     13                Type VIA_CONNECTOR = new Type() {};
    1314        }
    1415       
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionGui.java

    r25606 r25783  
    33import static java.lang.Math.PI;
    44import static java.lang.Math.abs;
     5import static java.lang.Math.hypot;
    56import static java.lang.Math.max;
    67import static java.lang.Math.tan;
     
    1617import java.awt.Color;
    1718import java.awt.Graphics2D;
     19import java.awt.geom.FlatteningPathIterator;
    1820import java.awt.geom.Line2D;
    1921import java.awt.geom.Path2D;
     22import java.awt.geom.PathIterator;
    2023import java.awt.geom.Point2D;
    2124import java.awt.geom.Rectangle2D;
     
    3740                private final Turn turn;
    3841               
    39                 private final Line2D line = new Line2D.Double();
    40                
    4142                private Point2D dragBegin;
    42                
    43                 private Point2D dragOffset = new Point2D.Double();
     43                private double dragOffsetX = 0;
     44                private double dragOffsetY = 0;
    4445               
    4546                public TurnConnection(Turn turn) {
     
    5051                void paint(Graphics2D g2d, State state) {
    5152                        if (isVisible(state)) {
    52                                 final LaneGui laneGui = getContainer().getGui(turn.getFrom());
    53                                 final RoadGui roadGui = getContainer().getGui(turn.getTo().getRoad());
     53                                g2d.setStroke(getContainer().getConnectionStroke());
     54                                g2d.setColor(isRemoveDragOffset() ? GuiContainer.RED : GuiContainer.GREEN);
     55                                g2d.translate(dragOffsetX, dragOffsetY);
     56                                g2d.draw(getPath());
     57                                g2d.translate(-dragOffsetX, -dragOffsetY);
     58                        }
     59                }
     60               
     61                private Path2D getPath() {
     62                        final Path2D path = new Path2D.Double();
     63                       
     64                        final LaneGui laneGui = getContainer().getGui(turn.getFrom());
     65                        final RoadGui roadGui = getContainer().getGui(turn.getTo().getRoad());
     66                       
     67                        path.moveTo(laneGui.outgoing.getCenter().getX(), laneGui.outgoing.getCenter().getY());
     68                       
     69                        Junction j = laneGui.getModel().getOutgoingJunction();
     70                        for (Road v : turn.getVia()) {
     71                                final PathIterator it;
     72                                if (v.getFromEnd().getJunction().equals(j)) {
     73                                        it = getContainer().getGui(v).getLaneMiddle(true).getIterator();
     74                                        j = v.getToEnd().getJunction();
     75                                } else {
     76                                        it = getContainer().getGui(v).getLaneMiddle(false).getIterator();
     77                                        j = v.getFromEnd().getJunction();
     78                                }
    5479                               
    55                                 g2d.setStroke(getContainer().getConnectionStroke());
    56                                
    57                                 g2d.setColor(isRemoveDragOffset() ? GuiContainer.RED : GuiContainer.GREEN);
    58                                
    59                                 line.setLine(laneGui.outgoing.getCenter(), roadGui.getConnector(turn.getTo()).getCenter());
    60                                 line.setLine(line.getX1() + dragOffset.getX(), line.getY1() + dragOffset.getY(),
    61                                     line.getX2() + dragOffset.getX(), line.getY2() + dragOffset.getY());
    62                                 g2d.draw(line);
    63                         }
     80                                path.append(it, true);
     81                        }
     82                       
     83                        path.lineTo(roadGui.getConnector(turn.getTo()).getCenter().getX(), roadGui.getConnector(turn.getTo()).getCenter()
     84                            .getY());
     85                       
     86                        return path;
    6487                }
    6588               
    6689                private boolean isVisible(State state) {
    67                         if (state instanceof State.OutgoingActive) {
     90                        if (state instanceof State.AllTurns) {
     91                                return true;
     92                        } else if (state instanceof State.OutgoingActive) {
    6893                                return turn.getFrom().equals(((State.OutgoingActive) state).getLane().getModel());
    6994                        } else if (state instanceof State.IncomingActive) {
     
    76101                @Override
    77102                boolean contains(Point2D p, State state) {
    78                         final Point2D closest = closest(line, p);
    79                         return p.distance(closest) <= strokeWidth() / 2;
     103                        if (!isVisible(state)) {
     104                                return false;
     105                        }
     106                       
     107                        final PathIterator it = new FlatteningPathIterator(getPath().getPathIterator(null), 0.05 / getContainer()
     108                            .getMpp());
     109                        final double[] coords = new double[6];
     110                        double lastX = 0;
     111                        double lastY = 0;
     112                        while (!it.isDone()) {
     113                                if (it.currentSegment(coords) == PathIterator.SEG_LINETO) {
     114                                        final Point2D closest = closest(new Line2D.Double(lastX, lastY, coords[0], coords[1]), p);
     115                                       
     116                                        if (p.distance(closest) <= strokeWidth() / 2) {
     117                                                return true;
     118                                        }
     119                                }
     120                               
     121                                lastX = coords[0];
     122                                lastY = coords[1];
     123                                it.next();
     124                        }
     125                       
     126                        return false;
    80127                }
    81128               
     
    98145                boolean beginDrag(double x, double y) {
    99146                        dragBegin = new Point2D.Double(x, y);
    100                         dragOffset = new Point2D.Double();
     147                        dragOffsetX = 0;
     148                        dragOffsetY = 0;
    101149                        return true;
    102150                }
     
    104152                @Override
    105153                State drag(double x, double y, InteractiveElement target, State old) {
    106                         dragOffset = new Point2D.Double(x - dragBegin.getX(), y - dragBegin.getY());
     154                        dragOffsetX = x - dragBegin.getX();
     155                        dragOffsetY = y - dragBegin.getY();
    107156                        return old;
    108157                }
     
    113162                       
    114163                        if (isRemoveDragOffset()) {
    115                                 getModel().removeTurn(turn);
    116                         }
    117                        
    118                         dragOffset = new Point2D.Double();
     164                                turn.remove();
     165                        }
     166                       
     167                        dragBegin = null;
     168                        dragOffsetX = 0;
     169                        dragOffsetY = 0;
    119170                        return new State.Dirty(old);
    120171                }
     
    123174                        final double r = getContainer().getGui(turn.getFrom().getRoad()).connectorRadius;
    124175                        final double max = r - strokeWidth() / 2;
    125                         return dragOffset.distance(0, 0) > max;
     176                        return hypot(dragOffsetX, dragOffsetY) > max;
    126177                }
    127178        }
     
    386437                final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
    387438               
    388                 for (Turn t : getModel().getTurns()) {
    389                         result.add(new TurnConnection(t));
     439                if (getModel().isPrimary()) {
     440                        for (Road.End r : new HashSet<Road.End>(getModel().getRoadEnds())) {
     441                                for (Turn t : r.getTurns()) {
     442                                        result.add(new TurnConnection(t));
     443                                }
     444                        }
    390445                }
    391446               
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/JunctionPane.java

    r25606 r25783  
    66import java.awt.Graphics2D;
    77import java.awt.RenderingHints;
     8import java.awt.event.ActionEvent;
     9import java.awt.event.InputEvent;
     10import java.awt.event.KeyEvent;
    811import java.awt.event.MouseAdapter;
    912import java.awt.event.MouseEvent;
     
    1922import java.util.TreeMap;
    2023
     24import javax.swing.AbstractAction;
    2125import javax.swing.JComponent;
    22 
    23 import org.openstreetmap.josm.data.osm.Node;
    24 import org.openstreetmap.josm.plugins.turnlanes.model.ModelContainer;
     26import javax.swing.KeyStroke;
    2527
    2628class JunctionPane extends JComponent {
     
    3133               
    3234                public void mousePressed(MouseEvent e) {
     35                        setFocusable(true);
    3336                        button = e.getButton();
    3437                       
     
    7679                                        if (ie.contains(mouse, state)) {
    7780                                                setState(ie.click(state));
    78                                                 repaint();
    7981                                                break;
    8082                                        }
     
    103105                                        setState(dragging.drag(mouse.getX(), mouse.getY(), dropTarget(mouse), state));
    104106                                }
    105                                
    106                                 repaint();
    107107                        } else if (button == MouseEvent.BUTTON3) {
    108108                                translate(e.getX() - originX, e.getY() - originY);
     
    133133        private final MouseInputProcessor mip = new MouseInputProcessor();
    134134       
    135         private JunctionGui junction;
     135        private GuiContainer container;
    136136       
    137137        private int width = 0;
     
    148148        private InteractiveElement dragging;
    149149       
    150         public JunctionPane(JunctionGui junction) {
    151                 setJunction(junction);
    152         }
    153        
    154         public void setJunction(JunctionGui junction) {
     150        public JunctionPane(GuiContainer container) {
     151                setJunction(container);
     152               
     153                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "refresh");
     154                getActionMap().put("refresh", new AbstractAction() {
     155                        private static final long serialVersionUID = 1L;
     156                       
     157                        @Override
     158                        public void actionPerformed(ActionEvent e) {
     159                                setState(new State.Invalid(state));
     160                        }
     161                });
     162               
     163                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "zoomIn");
     164                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoomIn");
     165                getActionMap().put("zoomIn", new AbstractAction() {
     166                        private static final long serialVersionUID = 1L;
     167                       
     168                        @Override
     169                        public void actionPerformed(ActionEvent e) {
     170                                scale(Math.pow(0.8, -1));
     171                        }
     172                });
     173               
     174                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "zoomOut");
     175                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoomOut");
     176                getActionMap().put("zoomOut", new AbstractAction() {
     177                        private static final long serialVersionUID = 1L;
     178                       
     179                        @Override
     180                        public void actionPerformed(ActionEvent e) {
     181                                scale(Math.pow(0.8, 1));
     182                        }
     183                });
     184               
     185                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "center");
     186                getActionMap().put("center", new AbstractAction() {
     187                        private static final long serialVersionUID = 1L;
     188                       
     189                        @Override
     190                        public void actionPerformed(ActionEvent e) {
     191                                center();
     192                        }
     193                });
     194               
     195                getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), "toggleAllTurns");
     196                getActionMap().put("toggleAllTurns", new AbstractAction() {
     197                        private static final long serialVersionUID = 1L;
     198                       
     199                        @Override
     200                        public void actionPerformed(ActionEvent e) {
     201                                toggleAllTurns();
     202                        }
     203                });
     204        }
     205       
     206        public void setJunction(GuiContainer container) {
    155207                removeMouseListener(mip);
    156208                removeMouseMotionListener(mip);
     
    158210                interactives.clear();
    159211                dragging = null;
    160                 this.junction = junction;
    161                
    162                 dirty = true;
    163                 repaint();
    164                
    165                 if (junction == null) {
     212                this.container = container;
     213               
     214                if (container == null) {
    166215                        this.state = null;
    167216                } else {
    168                         this.state = new State.Default(junction);
    169                        
    170                         final Rectangle2D bounds = junction.getBounds();
    171                         scale = Math.min(getHeight() / 2 / bounds.getHeight(), getWidth() / 2 / bounds.getWidth());
    172                        
    173                         translationX = -bounds.getCenterX();
    174                         translationY = -bounds.getCenterY();
    175                        
    176                         translate(getWidth() / 2d, getHeight() / 2d);
     217                        setState(new State.Dirty(new State.Default()));
     218                       
     219                        center();
    177220                       
    178221                        addMouseListener(mip);
     
    182225        }
    183226       
     227        private void center() {
     228                final Rectangle2D bounds = container.getBounds();
     229                scale = Math.min(getHeight() / 2 / bounds.getHeight(), getWidth() / 2 / bounds.getWidth());
     230               
     231                translationX = -bounds.getCenterX();
     232                translationY = -bounds.getCenterY();
     233               
     234                translate(getWidth() / 2d, getHeight() / 2d);
     235        }
     236       
     237        private void toggleAllTurns() {
     238                if (state instanceof State.AllTurns) {
     239                        setState(((State.AllTurns) state).unwrap());
     240                } else {
     241                        setState(new State.AllTurns(state));
     242                }
     243        }
     244       
    184245        private void setState(State state) {
    185                 if (state instanceof State.Invalid) {
    186                         final Node n = junction.getModel().getNode();
    187                         final ModelContainer m = ModelContainer.create(n);
    188                        
    189                         final GuiContainer c = junction.getContainer().empty();
    190                         junction = c.getGui(m.getJunction(n));
    191                        
     246                if (state instanceof State.AllTurns) {
    192247                        dirty = true;
    193                         this.state = new State.Default(junction);
     248                        this.state = state;
     249                } else if (state instanceof State.Invalid) {
     250                        container = container.recalculate();
     251                        dirty = true;
     252                        this.state = new State.Default();
    194253                } else if (state instanceof State.Dirty) {
    195254                        dirty = true;
     
    198257                        this.state = state;
    199258                }
     259               
     260                repaint();
    200261        }
    201262       
     
    211272                dirty = true;
    212273                repaint();
     274        }
     275       
     276        void scale(double scale) {
     277                scale(getWidth() / 2, getHeight() / 2, scale);
    213278        }
    214279       
     
    233298                }
    234299               
    235                 if (junction == null) {
     300                if (container == null) {
    236301                        super.paintComponent(g);
    237302                        return;
     
    295360               
    296361                g2d.setColor(Color.GRAY);
    297                 for (RoadGui r : junction.getRoads()) {
     362                for (RoadGui r : container.getRoads()) {
    298363                        addAllInteractives(r.paint(g2d));
    299364                }
    300365               
    301                 addAllInteractives(junction.paint(g2d));
    302                
    303                 dot(g2d, new Point2D.Double(junction.x, junction.y), junction.getContainer().getLaneWidth() / 5);
     366                for (JunctionGui j : container.getJunctions()) {
     367                        addAllInteractives(j.paint(g2d));
     368                        dot(g2d, new Point2D.Double(j.x, j.y), container.getLaneWidth() / 5);
     369                }
    304370        }
    305371       
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/LaneGui.java

    r25606 r25783  
    1111import java.awt.Shape;
    1212import java.awt.geom.Ellipse2D;
    13 import java.awt.geom.Line2D;
    1413import java.awt.geom.Path2D;
     14import java.awt.geom.PathIterator;
    1515import java.awt.geom.Point2D;
    1616import java.awt.geom.Rectangle2D;
    1717import java.text.DecimalFormat;
    1818import java.text.NumberFormat;
     19import java.util.ArrayList;
    1920import java.util.List;
    2021
    2122import org.openstreetmap.josm.plugins.turnlanes.gui.RoadGui.IncomingConnector;
    22 import org.openstreetmap.josm.plugins.turnlanes.model.Junction;
    2323import org.openstreetmap.josm.plugins.turnlanes.model.Lane;
     24import org.openstreetmap.josm.plugins.turnlanes.model.Road;
    2425
    2526final class LaneGui {
     
    7980                       
    8081                        final double offset = getRoad().getOffset(x, y);
    81                         final double newLength = getModel().isReverse() ? offset : getRoad().getLength() - offset;
     82                        final double newLength = getModel().getOutgoingRoadEnd().isFromEnd() ? offset : getRoad().getLength() - offset;
    8283                        if (newLength > 0) {
    8384                                getModel().setLength(newLength * getRoad().getContainer().getMpp());
     
    125126                       
    126127                        if (dragLocation != null) {
     128                                final State.Connecting s = (State.Connecting) state;
     129                                final Path2D path = new Path2D.Double();
     130                                path.moveTo(center.getX(), center.getY());
     131                               
     132                                final List<RoadGui.ViaConnector> vias = s.getViaConnectors();
     133                                for (int i = 0; i < vias.size() - 1; i += 2) {
     134                                        final RoadGui.ViaConnector v = vias.get(i);
     135                                        final PathIterator it = v.getRoad().getLaneMiddle(v.getRoadEnd().isFromEnd()).getIterator();
     136                                        path.append(it, true);
     137                                }
     138                                if ((vias.size() & 1) != 0) {
     139                                        final RoadGui.ViaConnector last = vias.get(vias.size() - 1);
     140                                        path.lineTo(last.getCenter().getX(), last.getCenter().getY());
     141                                }
     142                               
     143                                if (dropTarget == null) {
     144                                        g2d.setColor(GuiContainer.RED);
     145                                        path.lineTo(dragLocation.getX(), dragLocation.getY());
     146                                } else {
     147                                        g2d.setColor(GuiContainer.GREEN);
     148                                        path.lineTo(dropTarget.getCenter().getX(), dropTarget.getCenter().getY());
     149                                }
     150                               
    127151                                g2d.setStroke(getContainer().getConnectionStroke());
    128                                 g2d.setColor(dropTarget == null ? GuiContainer.RED : GuiContainer.GREEN);
    129                                 g2d.draw(new Line2D.Double(getCenter(), dropTarget == null ? dragLocation : dropTarget.getCenter()));
     152                                g2d.draw(path);
    130153                        }
    131154                }
     
    150173               
    151174                private boolean isVisible(State state) {
    152                         return getModel().getOutgoingJunction().equals(state.getJunction().getModel());
     175                        if (state instanceof State.Connecting) {
     176                                return ((State.Connecting) state).getLane().equals(getModel());
     177                        }
     178                       
     179                        return !getRoad().getModel().isPrimary() && getModel().getOutgoingJunction().isPrimary();
    153180                }
    154181               
     
    165192                @Override
    166193                public State activate(State old) {
    167                         return new State.OutgoingActive(old.getJunction(), LaneGui.this);
     194                        return new State.OutgoingActive(LaneGui.this);
    168195                }
    169196               
     
    174201               
    175202                @Override
    176                 State drag(double x, double y, InteractiveElement target, State old) {
     203                State.Connecting drag(double x, double y, InteractiveElement target, State old) {
    177204                        dragLocation = new Point2D.Double(x, y);
    178                         dropTarget = target != null && target.getType() == Type.INCOMING_CONNECTOR ? (IncomingConnector) target : null;
    179                         return old;
     205                        dropTarget = null;
     206                       
     207                        if (!(old instanceof State.Connecting)) {
     208                                return new State.Connecting(getModel());
     209                        }
     210                       
     211                        final State.Connecting s = (State.Connecting) old;
     212                        if (target != null && target.getType() == Type.INCOMING_CONNECTOR) {
     213                                dropTarget = (IncomingConnector) target;
     214                               
     215                                return (s.getViaConnectors().size() & 1) == 0 ? s : s.pop();
     216                        } else if (target != null && target.getType() == Type.VIA_CONNECTOR) {
     217                                return s.next((RoadGui.ViaConnector) target);
     218                        }
     219                       
     220                        return s;
    180221                }
    181222               
    182223                @Override
    183224                State drop(double x, double y, InteractiveElement target, State old) {
    184                         drag(x, y, target, old);
     225                        final State.Connecting s = drag(x, y, target, old);
    185226                        dragLocation = null;
    186                        
    187227                        if (dropTarget == null) {
    188                                 return old;
    189                         }
    190                        
    191                         final Junction j = (getModel().isReverse() ? getRoad().getA() : getRoad().getB()).getModel();
    192                        
    193                         j.addTurn(getModel(), dropTarget.getRoadEnd());
    194                        
     228                                return activate(old);
     229                        }
     230                       
     231                        final List<Road> via = new ArrayList<Road>();
     232                        assert (s.getViaConnectors().size() & 1) == 0;
     233                        for (int i = 0; i < s.getViaConnectors().size(); i += 2) {
     234                                final RoadGui.ViaConnector a = s.getViaConnectors().get(i);
     235                                final RoadGui.ViaConnector b = s.getViaConnectors().get(i + 1);
     236                                assert a.getRoadEnd().getOppositeEnd().equals(b);
     237                                via.add(a.getRoadEnd().getRoad());
     238                        }
     239                       
     240                        getModel().addTurn(via, dropTarget.getRoadEnd());
    195241                        dropTarget = null;
    196                         return new State.Dirty(old);
     242                        return new State.Dirty(activate(old));
    197243                }
    198244               
     
    256302                final double WW = 3 / getContainer().getMpp();
    257303               
    258                 final List<LaneGui> lanes = getRoad().getLanes();
    259                 final int i = lanes.indexOf(this);
    260                 final LaneGui left = getModel().isReverse() ? (i < lanes.size() - 1 ? lanes.get(i + 1) : null) : (i > 0 ? lanes
    261                     .get(i - 1) : null);
     304                final LaneGui left = left();
    262305                final Lane leftModel = left == null ? null : left.getModel();
    263                 final double leftLength = leftModel == null || leftModel.isReverse() != getModel().isReverse() ? Double.NEGATIVE_INFINITY
     306                final double leftLength = leftModel == null
     307                    || !leftModel.getOutgoingRoadEnd().equals(getModel().getOutgoingRoadEnd()) ? Double.NEGATIVE_INFINITY
    264308                    : leftModel.getKind() == Lane.Kind.EXTRA_LEFT ? left.getLength() : L;
    265309               
     
    276320                        if (L > leftLength) {
    277321                                innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
    278                                     || getModel().isReverse());
     322                                    || getModel().getOutgoingRoadEnd().isFromEnd());
    279323                                final Point2D op = outer.getPoint(L + WW);
    280324                                innerLine.lineTo(op.getX(), op.getY());
     
    291335                        if (leftLength < L) {
    292336                                innerLine.append(inner.subpath(max(0, leftLength + WW), L).getIterator(), leftLength >= 0
    293                                     || getModel().isReverse());
     337                                    || getModel().getOutgoingRoadEnd().isFromEnd());
    294338                        }
    295339                }
    296340               
    297341                return outer;
     342        }
     343       
     344        private LaneGui left() {
     345                final List<LaneGui> lanes = getRoad().getLanes(getModel().getOutgoingRoadEnd());
     346                final int i = lanes.indexOf(this);
     347                return i > 0 ? lanes.get(i - 1) : null;
    298348        }
    299349       
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/RoadGui.java

    r25606 r25783  
    2929import java.awt.geom.Rectangle2D;
    3030import java.util.ArrayList;
     31import java.util.Arrays;
    3132import java.util.Collections;
    3233import java.util.List;
     
    3435import org.openstreetmap.josm.data.osm.Node;
    3536import org.openstreetmap.josm.data.osm.OsmPrimitive;
    36 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    3737import org.openstreetmap.josm.data.osm.Way;
    38 import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils;
    3938import org.openstreetmap.josm.plugins.turnlanes.model.Lane;
    4039import org.openstreetmap.josm.plugins.turnlanes.model.Road;
     
    4241
    4342class RoadGui {
     43        final class ViaConnector extends InteractiveElement {
     44                private final Road.End end;
     45               
     46                private final Line2D line;
     47                private final float strokeWidth;
     48               
     49                public ViaConnector(Road.End end) {
     50                        this.end = end;
     51                        this.line = new Line2D.Double(getLeftCorner(end), getRightCorner(end));
     52                        this.strokeWidth = (float) (3 * getContainer().getLaneWidth() / 4);
     53                }
     54               
     55                @Override
     56                void paint(Graphics2D g2d, State state) {
     57                        if (isVisible(state)) {
     58                                g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
     59                                g2d.setColor(Color.ORANGE);
     60                                g2d.draw(line);
     61                        }
     62                }
     63               
     64                @Override
     65                boolean contains(Point2D p, State state) {
     66                        if (!isVisible(state)) {
     67                                return false;
     68                        }
     69                       
     70                        final Point2D closest = closest(line, p);
     71                        return p.distance(closest) <= strokeWidth / 2;
     72                }
     73               
     74                private boolean isVisible(State state) {
     75                        if (!(state instanceof State.Connecting)) {
     76                                return false;
     77                        }
     78                       
     79                        final State.Connecting s = (State.Connecting) state;
     80                       
     81                        if (s.getJunction().equals(end.getJunction()) || equals(s.getBacktrackViaConnector())) {
     82                                return true;
     83                        } else if (!s.getViaConnectors().isEmpty()
     84                            && s.getViaConnectors().get(s.getViaConnectors().size() - 1).getRoadModel().equals(getRoadModel())) {
     85                                return true;
     86                        }
     87                       
     88                        return false;
     89                }
     90               
     91                private Road getRoadModel() {
     92                        return getModel();
     93                }
     94               
     95                public RoadGui getRoad() {
     96                        return RoadGui.this;
     97                }
     98               
     99                @Override
     100                Type getType() {
     101                        return Type.VIA_CONNECTOR;
     102                }
     103               
     104                @Override
     105                int getZIndex() {
     106                        return 1;
     107                }
     108               
     109                public Road.End getRoadEnd() {
     110                        return end;
     111                }
     112               
     113                public Point2D getCenter() {
     114                        return relativePoint(line.getP1(), line.getP1().distance(line.getP2()) / 2, angle(line.getP1(), line.getP2()));
     115                }
     116        }
     117       
    44118        private final class Extender extends InteractiveElement {
     119                private final Road.End end;
    45120                private final Way way;
    46121               
    47122                private final Line2D line;
    48123               
    49                 public Extender(Way way, double angle) {
     124                public Extender(Road.End end, Way way, double angle) {
     125                        this.end = end;
    50126                        this.way = way;
    51127                        this.line = new Line2D.Double(a.getPoint(), relativePoint(a.getPoint(), getContainer().getLaneWidth() * 4, angle));
     
    70146                @Override
    71147                State click(State old) {
    72                         getModel().extend(way);
     148                        end.extend(way);
    73149                        return new State.Invalid(old);
    74150                }
     
    83159                        return 0;
    84160                }
    85                
    86161        }
    87162       
    88163        private final class LaneAdder extends InteractiveElement {
    89164                private final Road.End end;
    90                 private final boolean left;
     165                private final Lane.Kind kind;
    91166               
    92167                private final Point2D center;
    93168                private final Ellipse2D background;
    94169               
    95                 public LaneAdder(Road.End end, boolean left) {
     170                public LaneAdder(Road.End end, Lane.Kind kind) {
    96171                        this.end = end;
    97                         this.left = left;
     172                        this.kind = kind;
    98173                       
    99174                        final double a = getAngle(end) + PI;
     
    104179                        final double cx;
    105180                        final double cy;
    106                         if (left) {
     181                        if (kind == Lane.Kind.EXTRA_LEFT) {
    107182                                final JunctionGui j = getContainer().getGui(end.getJunction());
    108183                                final Point2D i = intersection(line(j.getPoint(), a), new Line2D.Double(lc, rc));
     
    139214               
    140215                private boolean isVisible(State state) {
    141                         return end.getJunction().equals(state.getJunction().getModel());
     216                        return end.getJunction().isPrimary();
    142217                }
    143218               
     
    159234                @Override
    160235                public State click(State old) {
    161                         end.addExtraLane(left);
     236                        end.addLane(kind);
    162237                        return new State.Invalid(old);
    163238                }
     
    165240       
    166241        final class IncomingConnector extends InteractiveElement {
    167                 private final boolean forward;
     242                private final Road.End end;
     243                private final List<LaneGui> lanes;
     244               
    168245                private final Point2D center = new Point2D.Double();
    169246                private final Ellipse2D circle = new Ellipse2D.Double();
    170247               
    171                 private final List<LaneGui> lanes = new ArrayList<LaneGui>();
    172                
    173                 private IncomingConnector(boolean forward) {
    174                         this.forward = forward;
     248                private IncomingConnector(Road.End end) {
     249                        this.end = end;
     250                       
     251                        final List<LaneGui> lanes = new ArrayList<LaneGui>(end.getLanes().size());
     252                        for (Lane l : end.getOppositeEnd().getLanes()) {
     253                                lanes.add(new LaneGui(RoadGui.this, l));
     254                        }
     255                        this.lanes = Collections.unmodifiableList(lanes);
    175256                }
    176257               
     
    217298               
    218299                private boolean isVisible(State state) {
    219                         if (!getRoadEnd().getJunction().equals(state.getJunction().getModel())) {
     300                        if (getModel().isPrimary() || !getRoadEnd().getJunction().isPrimary()
     301                            || getRoadEnd().getOppositeEnd().getLanes().isEmpty()) {
    220302                                return false;
    221303                        }
    222304                       
    223                         // must be at least one lane in that direction
    224                         final boolean reverse = getRoadEnd().isToEnd();
    225                         for (Lane l : getModel().getLanes()) {
    226                                 if (l.isReverse() == reverse) {
    227                                         return true;
    228                                 }
    229                         }
    230                        
    231                         return false;
     305                        if (state instanceof State.Connecting) {
     306                                return ((State.Connecting) state).getJunction().equals(getRoadEnd().getJunction());
     307                        }
     308                       
     309                        return true;
    232310                }
    233311               
     
    256334                @Override
    257335                public State activate(State old) {
    258                         return new State.IncomingActive(old.getJunction(), getRoadEnd());
     336                        return new State.IncomingActive(getRoadEnd());
    259337                }
    260338               
     
    270348                }
    271349               
    272                 Road.End getRoadEnd() {
    273                         return forward ? getModel().getFromEnd() : getModel().getToEnd();
     350                public Road.End getRoadEnd() {
     351                        return end;
     352                }
     353               
     354                public List<LaneGui> getLanes() {
     355                        return lanes;
    274356                }
    275357               
     
    400482        private final double length;
    401483       
    402         private final IncomingConnector incomingA = new IncomingConnector(true);
    403         private final IncomingConnector incomingB = new IncomingConnector(false);
     484        private final IncomingConnector incomingA;
     485        private final IncomingConnector incomingB;
    404486       
    405487        private final Road road;
    406         private final List<LaneGui> lanes = new ArrayList<LaneGui>();
    407488        private final List<Segment> segments = new ArrayList<Segment>();
    408489       
    409490        final double connectorRadius;
    410491       
    411         public RoadGui(GuiContainer container, Road r) {
     492        public RoadGui(GuiContainer container, Road road) {
    412493                this.container = container;
    413494               
    414                 this.a = container.getGui(r.getFromEnd().getJunction());
    415                 this.b = container.getGui(r.getToEnd().getJunction());
    416                
    417                 this.road = r;
     495                this.road = road;
     496               
     497                this.a = container.getGui(road.getFromEnd().getJunction());
     498                this.b = container.getGui(road.getToEnd().getJunction());
     499               
     500                this.incomingA = new IncomingConnector(road.getFromEnd());
     501                this.incomingB = new IncomingConnector(road.getToEnd());
    418502               
    419503                final List<Point2D> bends = new ArrayList<Point2D>();
    420                 final List<Node> nodes = r.getRoute().getNodes();
     504                final List<Node> nodes = road.getRoute().getNodes();
    421505                for (int i = nodes.size() - 2; i > 0; --i) {
    422506                        bends.add(container.translateAndScale(loc(nodes.get(i))));
     
    425509                // they add themselves to this.segments
    426510                new Segment(b, bends, a);
    427                
    428511                double l = 0;
    429512                for (Segment s : segments) {
    430513                        l += s.length;
    431514                }
    432                
    433515                this.length = l;
    434516               
    435                 for (Lane lane : r.getLanes()) {
    436                         final LaneGui gui = new LaneGui(this, lane);
    437                         lanes.add(gui);
    438                         (lane.isReverse() ? incomingB : incomingA).add(gui);
    439                 }
    440                
    441                 this.innerMargin = getLaneCount(true) > 0 && getLaneCount(false) > 0 ? 1 * container.getLaneWidth() / 15 : 0;
     517                this.innerMargin = !incomingA.getLanes().isEmpty() && !incomingB.getLanes().isEmpty() ? 1 * container
     518                    .getLaneWidth() / 15 : 0;
    442519                this.outerMargin = container.getLaneWidth() / 6;
    443520                this.connectorRadius = 3 * container.getLaneWidth() / 8;
     
    457534        }
    458535       
    459         private int laneMiddle() {
    460                 int i = 0;
    461                 while (lanes.size() > i && lanes.get(i).getModel().isReverse()) {
    462                         ++i;
    463                 }
    464                 return i;
    465         }
    466        
    467536        public Line2D getLeftCurb(Road.End end) {
    468537                return GuiUtil.line(getCorner(end, true), getAngle(end) + PI);
     
    499568                }
    500569               
    501                 final int lcForward = getLaneCount(false);
    502                 final int lcBackward = getLaneCount(true);
     570                final int lcForward = incomingA.getLanes().size();
     571                final int lcBackward = incomingB.getLanes().size();
    503572               
    504573                final double LW = getContainer().getLaneWidth();
     
    512581        }
    513582       
    514         private int getLaneCount(boolean reverse) {
    515                 int result = 0;
    516                
    517                 for (LaneGui l : lanes) {
    518                         if (l.getModel().isReverse() == reverse) {
    519                                 ++result;
    520                         }
    521                 }
    522                
    523                 return result;
    524         }
    525        
    526583        List<InteractiveElement> paint(Graphics2D g2d) {
    527584                final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
    528585               
    529586                result.addAll(paintLanes(g2d));
    530                 result.addAll(laneAdders());
    531                 result.addAll(extenders());
     587               
     588                if (getModel().isPrimary()) {
     589                        result.add(new ViaConnector(getModel().getFromEnd()));
     590                        result.add(new ViaConnector(getModel().getToEnd()));
     591                } else {
     592                        result.addAll(laneAdders());
     593                        result.addAll(extenders(getModel().getFromEnd()));
     594                        result.addAll(extenders(getModel().getToEnd()));
     595                }
    532596               
    533597                g2d.setColor(Color.RED);
     
    542606                final List<LaneAdder> result = new ArrayList<LaneAdder>(4);
    543607               
    544                 boolean to = false;
    545                 boolean from = false;
    546                 for (Lane l : getModel().getLanes()) {
    547                         to = to || (!l.isReverse() && !l.isExtra());
    548                         from = from || (l.isReverse() && !l.isExtra());
    549                 }
    550                
    551                 if (to) {
    552                         result.add(new LaneAdder(getModel().getToEnd(), true));
    553                         result.add(new LaneAdder(getModel().getToEnd(), false));
    554                 }
    555                
    556                 if (from) {
    557                         result.add(new LaneAdder(getModel().getFromEnd(), true));
    558                         result.add(new LaneAdder(getModel().getFromEnd(), false));
     608                if (!incomingA.getLanes().isEmpty()) {
     609                        result.add(new LaneAdder(getModel().getToEnd(), Lane.Kind.EXTRA_LEFT));
     610                        result.add(new LaneAdder(getModel().getToEnd(), Lane.Kind.EXTRA_RIGHT));
     611                }
     612               
     613                if (!incomingB.getLanes().isEmpty()) {
     614                        result.add(new LaneAdder(getModel().getFromEnd(), Lane.Kind.EXTRA_LEFT));
     615                        result.add(new LaneAdder(getModel().getFromEnd(), Lane.Kind.EXTRA_RIGHT));
    559616                }
    560617               
     
    562619        }
    563620       
    564         private List<Extender> extenders() {
    565                 if (!getModel().isExtendable()) {
     621        private List<Extender> extenders(Road.End end) {
     622                if (!end.isExtendable()) {
    566623                        return Collections.emptyList();
    567624                }
     
    569626                final List<Extender> result = new ArrayList<Extender>();
    570627               
    571                 final Node n = a.getModel().getNode();
    572                 for (OsmPrimitive p : n.getReferrers()) {
    573                         if (p.getType() != OsmPrimitiveType.WAY) {
    574                                 continue;
    575                         }
    576                        
    577                         Way w = (Way) p;
    578                         if (w.isFirstLastNode(n) && w.getNodesCount() > 1 && Utils.isRoad(w)) {
    579                                 if (getModel().getRoute().getFirstSegment().getWay().equals(w)) {
    580                                         continue;
    581                                 }
    582                                
     628                final Node n = end.getJunction().getNode();
     629                for (Way w : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) {
     630                        if (w.getNodesCount() > 1 && !end.getWay().equals(w) && w.isFirstLastNode(n) && Utils.isRoad(w)) {
    583631                                final Node nextNode = w.firstNode().equals(n) ? w.getNode(1) : w.getNode(w.getNodesCount() - 2);
    584632                                final Point2D nextNodeLoc = getContainer().translateAndScale(loc(nextNode));
    585                                 result.add(new Extender(w, angle(a.getPoint(), nextNodeLoc)));
     633                                result.add(new Extender(end, w, angle(a.getPoint(), nextNodeLoc)));
    586634                        }
    587635                }
     
    603651                g2d.setStroke(regularStroke);
    604652               
    605                 final boolean forward = getLaneCount(false) > 0;
    606                 final boolean backward = getLaneCount(true) > 0;
     653                final boolean forward = !incomingA.getLanes().isEmpty();
     654                final boolean backward = !incomingB.getLanes().isEmpty();
    607655               
    608656                final Path2D middleArea;
     
    630678                g2d.draw(middleLines);
    631679               
     680                final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
     681               
    632682                moveIncoming(getModel().getFromEnd());
    633683                moveIncoming(getModel().getToEnd());
    634                
    635                 final int laneMiddle = laneMiddle();
    636                 for (int i = laneMiddle; i < lanes.size(); ++i) {
    637                         moveOutgoing(lanes.get(i), i - laneMiddle);
    638                 }
    639                 for (int i = laneMiddle - 1; i >= 0; --i) {
    640                         moveOutgoing(lanes.get(i), laneMiddle - i - 1);
    641                 }
    642                
    643                 final List<InteractiveElement> result = new ArrayList<InteractiveElement>();
    644                
    645684                result.add(incomingA);
    646685                result.add(incomingB);
    647                 for (LaneGui l : lanes) {
    648                         result.add(l.outgoing);
    649                        
    650                         if (l.getModel().isExtra()) {
    651                                 result.add(l.lengthSlider);
     686               
     687                for (IncomingConnector c : Arrays.asList(incomingA, incomingB)) {
     688                        int offset = 0;
     689                        for (LaneGui l : c.getLanes()) {
     690                                moveOutgoing(l, offset++);
     691                               
     692                                result.add(l.outgoing);
     693                                if (l.getModel().isExtra()) {
     694                                        result.add(l.lengthSlider);
     695                                }
    652696                        }
    653697                }
     
    666710                linePaths.add(innerPath);
    667711               
    668                 for (LaneGui l : lanes(forward)) {
     712                for (LaneGui l : forward ? incomingA.getLanes() : incomingB.getLanes()) {
    669713                        l.setClip(clip);
    670714                        innerPath = l.recalculate(innerPath, middleLines);
     
    719763                final double d = rc.getP1().distance(j.getPoint()) + lc.getP1().distance(j.getPoint());
    720764               
    721                 final Point2D r1 = relativePoint(rc.getP1(), 1, angle(lc.getP1(), rc.getP1()));
    722                 final Point2D r2 = relativePoint(r1, d, angle(rc) + PI);
    723                 final Point2D l1 = relativePoint(lc.getP1(), 1, angle(rc.getP1(), lc.getP1()));
    724                 final Point2D l2 = relativePoint(l1, d, angle(lc) + PI);
     765                final double cm = 0.01 / getContainer().getMpp(); // 1 centimeter
     766                final double rca = angle(rc) + PI;
     767                final double lca = angle(lc) + PI;
     768                final Point2D r1 = relativePoint(relativePoint(rc.getP1(), 1, angle(lc.getP1(), rc.getP1())), cm, rca);
     769                final Point2D r2 = relativePoint(r1, d, rca);
     770                final Point2D l1 = relativePoint(relativePoint(lc.getP1(), 1, angle(rc.getP1(), lc.getP1())), cm, lca);
     771                final Point2D l2 = relativePoint(l1, d, lca);
    725772               
    726773                negativeClip.moveTo(r1.getX(), r1.getY());
     
    733780        }
    734781       
    735         private Iterable<LaneGui> lanes(boolean forward) {
    736                 final int laneMiddle = laneMiddle();
    737                
    738                 return forward ? lanes.subList(laneMiddle, lanes.size()) : CollectionUtils.reverse(lanes.subList(0, laneMiddle));
     782        public Path getLaneMiddle(boolean forward) {
     783                final Path mid = middlePath(!forward);
     784                final double w = getWidth(forward ? getModel().getFromEnd() : getModel().getToEnd(), true);
     785                final double o = (w - outerMargin) / 2;
     786               
     787                return o > 0 ? mid.offset(-o, -1, -1, -o) : mid;
    739788        }
    740789       
     
    778827               
    779828                lane.outgoing.move(loc.getX(), loc.getY());
     829        }
     830       
     831        public JunctionGui getJunction(Road.End end) {
     832                if (!getModel().equals(end.getRoad())) {
     833                        throw new IllegalArgumentException();
     834                }
     835               
     836                return end.isFromEnd() ? getA() : getB();
    780837        }
    781838       
     
    801858        }
    802859       
    803         public List<LaneGui> getLanes() {
    804                 return Collections.unmodifiableList(lanes);
    805         }
    806        
    807860        public double getOffset(double x, double y) {
    808861                return segments.get(0).getOffset(x, y);
     
    812865                return container;
    813866        }
     867       
     868        public List<LaneGui> getLanes() {
     869                final List<LaneGui> result = new ArrayList<LaneGui>();
     870               
     871                result.addAll(incomingB.getLanes());
     872                result.addAll(incomingA.getLanes());
     873               
     874                return Collections.unmodifiableList(result);
     875        }
     876       
     877        public List<LaneGui> getLanes(Road.End end) {
     878                return getConnector(end.getOppositeEnd()).getLanes();
     879        }
    814880}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/State.java

    r25606 r25783  
    11package org.openstreetmap.josm.plugins.turnlanes.gui;
    22
     3import java.util.ArrayList;
     4import java.util.Arrays;
     5import java.util.Collections;
     6import java.util.List;
     7
     8import org.openstreetmap.josm.plugins.turnlanes.gui.RoadGui.ViaConnector;
     9import org.openstreetmap.josm.plugins.turnlanes.model.Junction;
     10import org.openstreetmap.josm.plugins.turnlanes.model.Lane;
    311import org.openstreetmap.josm.plugins.turnlanes.model.Road;
    412
    513interface State {
     14        public class AllTurns implements State {
     15                private final State wrapped;
     16               
     17                public AllTurns(State wrapped) {
     18                        this.wrapped = wrapped;
     19                }
     20               
     21                public State unwrap() {
     22                        return wrapped;
     23                }
     24        }
     25       
     26        public class Connecting implements State {
     27                private final Lane lane;
     28                private final List<RoadGui.ViaConnector> vias;
     29               
     30                public Connecting(Lane lane) {
     31                        this(lane, Collections.<RoadGui.ViaConnector> emptyList());
     32                }
     33               
     34                public Connecting(Lane lane, List<ViaConnector> vias) {
     35                        this.lane = lane;
     36                        this.vias = vias;
     37                }
     38               
     39                public Connecting next(RoadGui.ViaConnector via) {
     40                        if (vias.isEmpty()) {
     41                                return new Connecting(lane, Collections.unmodifiableList(Arrays.asList(via)));
     42                        }
     43                       
     44                        final List<RoadGui.ViaConnector> tmp = new ArrayList<RoadGui.ViaConnector>(vias.size() + 1);
     45                        final boolean even = (vias.size() & 1) == 0;
     46                        final RoadGui.ViaConnector last = vias.get(vias.size() - 1);
     47                       
     48                        if (last.equals(via) || !even && last.getRoadEnd().getJunction().equals(via.getRoadEnd().getJunction())) {
     49                                return pop().next(via);
     50                        }
     51                       
     52                        if (vias.size() >= 2) {
     53                                if (lane.getOutgoingJunction().equals(via.getRoadEnd().getJunction())) {
     54                                        return new Connecting(lane);
     55                                } else if (via.equals(getBacktrackViaConnector())) {
     56                                        return new Connecting(lane, vias.subList(0, vias.size() - 1));
     57                                }
     58                        }
     59                       
     60                        for (RoadGui.ViaConnector v : vias) {
     61                                tmp.add(v);
     62                               
     63                                if (!(even && v.equals(last)) && v.getRoadEnd().getJunction().equals(via.getRoadEnd().getJunction())) {
     64                                        return new Connecting(lane, Collections.unmodifiableList(tmp));
     65                                }
     66                        }
     67                       
     68                        tmp.add(via);
     69                        return new Connecting(lane, Collections.unmodifiableList(tmp));
     70                }
     71               
     72                public Junction getJunction() {
     73                        return vias.isEmpty() ? lane.getOutgoingJunction() : vias.get(vias.size() - 1).getRoadEnd().getJunction();
     74                }
     75               
     76                public RoadGui.ViaConnector getBacktrackViaConnector() {
     77                        return vias.size() < 2 ? null : vias.get(vias.size() - 2);
     78                }
     79               
     80                public List<RoadGui.ViaConnector> getViaConnectors() {
     81                        return vias;
     82                }
     83               
     84                public Lane getLane() {
     85                        return lane;
     86                }
     87               
     88                public Connecting pop() {
     89                        return new Connecting(lane, vias.subList(0, vias.size() - 1));
     90                }
     91        }
     92       
    693        public class Invalid implements State {
    794                private final State wrapped;
     
    996                public Invalid(State wrapped) {
    1097                        this.wrapped = wrapped;
    11                 }
    12                
    13                 public JunctionGui getJunction() {
    14                         return wrapped.getJunction();
    1598                }
    1699               
     
    27110                }
    28111               
    29                 public JunctionGui getJunction() {
    30                         return wrapped.getJunction();
    31                 }
    32                
    33112                public State unwrap() {
    34113                        return wrapped;
     
    37116       
    38117        class Default implements State {
    39                 private final JunctionGui junction;
    40                
    41                 public Default(JunctionGui junction) {
    42                         this.junction = junction;
    43                 }
    44                
    45                 public JunctionGui getJunction() {
    46                         return junction;
    47                 }
     118                public Default() {}
    48119        }
    49120       
    50121        class IncomingActive implements State {
    51                 private final JunctionGui junction;
    52122                private final Road.End roadEnd;
    53123               
    54                 public IncomingActive(JunctionGui junction, Road.End roadEnd) {
    55                         this.junction = junction;
     124                public IncomingActive(Road.End roadEnd) {
    56125                        this.roadEnd = roadEnd;
    57126                }
     
    60129                        return roadEnd;
    61130                }
    62                
    63                 @Override
    64                 public JunctionGui getJunction() {
    65                         return junction;
    66                 }
    67131        }
    68132       
    69133        class OutgoingActive implements State {
    70                 private final JunctionGui junction;
    71134                private final LaneGui lane;
    72135               
    73                 public OutgoingActive(JunctionGui junction, LaneGui lane) {
    74                         this.junction = junction;
     136                public OutgoingActive(LaneGui lane) {
    75137                        this.lane = lane;
    76138                }
     
    79141                        return lane;
    80142                }
    81                
    82                 @Override
    83                 public JunctionGui getJunction() {
    84                         return junction;
    85                 }
    86143        }
    87        
    88         JunctionGui getJunction();
    89144}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/gui/TurnLanesDialog.java

    r25606 r25783  
    11package org.openstreetmap.josm.plugins.turnlanes.gui;
    22
    3 import static org.openstreetmap.josm.plugins.turnlanes.gui.GuiUtil.loc;
    43import static org.openstreetmap.josm.tools.I18n.tr;
    54
     
    98import java.awt.event.ActionEvent;
    109import java.util.Collection;
     10import java.util.Collections;
     11import java.util.List;
    1112
    1213import javax.swing.Action;
     14import javax.swing.ButtonGroup;
    1315import javax.swing.JLabel;
    1416import javax.swing.JPanel;
     17import javax.swing.JToggleButton;
    1518
    16 import org.openstreetmap.josm.Main;
    1719import org.openstreetmap.josm.actions.JosmAction;
    1820import org.openstreetmap.josm.data.SelectionChangedListener;
    19 import org.openstreetmap.josm.data.coor.EastNorth;
    2021import org.openstreetmap.josm.data.osm.DataSet;
    2122import org.openstreetmap.josm.data.osm.Node;
    2223import org.openstreetmap.josm.data.osm.OsmPrimitive;
    23 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    24 import org.openstreetmap.josm.gui.SideButton;
     24import org.openstreetmap.josm.data.osm.Way;
    2525import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
    26 import org.openstreetmap.josm.plugins.turnlanes.model.Junction;
    2726import org.openstreetmap.josm.plugins.turnlanes.model.ModelContainer;
    2827
     
    7170                        @Override
    7271                        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
    73                                 if (newSelection.size() != 1) {
     72                                final Collection<OsmPrimitive> s = Collections.unmodifiableCollection(newSelection);
     73                                final List<Node> nodes = OsmPrimitive.getFilteredList(s, Node.class);
     74                                final List<Way> ways = OsmPrimitive.getFilteredList(s, Way.class);
     75                               
     76                                if (nodes.isEmpty()) {
     77                                        setJunction(null);
    7478                                        return;
    7579                                }
    7680                               
    77                                 final OsmPrimitive p = newSelection.iterator().next();
    78                                
    79                                 if (p.getType() == OsmPrimitiveType.NODE) {
    80                                         final Node n = (Node) p;
    81                                        
    82                                         final ModelContainer mc;
    83                                         final Junction j;
    84                                        
    85                                         try {
    86                                                 mc = ModelContainer.create(n);
    87                                                 j = mc.getJunction(n);
    88                                         } catch (RuntimeException e) {
    89                                                 displayError(e);
    90                                                
    91                                                 return;
    92                                         }
    93                                        
    94                                         final EastNorth en = Main.proj.latlon2eastNorth(n.getCoor());
    95                                         final EastNorth rel = new EastNorth(en.getX() + 1, en.getY());
    96                                        
    97                                         // meters per source unit
    98                                         final double mpsu = Main.proj.eastNorth2latlon(rel).greatCircleDistance(n.getCoor());
    99                                         final GuiContainer model = new GuiContainer(loc(n), mpsu);
    100                                        
    101                                         setJunction(model.getGui(j));
     81                                try {
     82                                        setJunction(ModelContainer.create(nodes, ways));
     83                                } catch (RuntimeException e) {
     84                                        displayError(e);
     85                                        return;
    10286                                }
    10387                        }
    104                        
    10588                });
    10689               
    10790                final JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 4, 4));
    108                 buttonPanel.add(new SideButton(editAction));
    109                 buttonPanel.add(new SideButton(validateAction));
     91                final ButtonGroup group = new ButtonGroup();
     92                final JToggleButton editButton = new JToggleButton(editAction);
     93                final JToggleButton validateButton = new JToggleButton(validateAction);
     94                group.add(editButton);
     95                group.add(validateButton);
     96                buttonPanel.add(editButton);
     97                buttonPanel.add(validateButton);
    11098               
    11199                body.setLayout(new CardLayout(4, 4));
     
    117105                body.add(new ValidationPanel(), CARD_VALIDATE);
    118106                body.add(error, CARD_ERROR);
    119                 editAction.actionPerformed(null);
     107               
     108                editButton.doClick();
    120109        }
    121110       
    122111        void displayError(RuntimeException e) {
    123112                if (editing) {
    124                         // e.printStackTrace();
     113                        e.printStackTrace();
    125114                       
    126115                        error.setText("<html>An error occured while constructing the model."
     
    133122        }
    134123       
    135         void setJunction(JunctionGui j) {
    136                 if (j != null && editing) {
    137                         junctionPane.setJunction(j);
     124        void setJunction(ModelContainer mc) {
     125                if (mc != null && editing) {
     126                        junctionPane.setJunction(new GuiContainer(mc));
    138127                        final CardLayout cl = (CardLayout) body.getLayout();
    139128                        cl.show(body, CARD_EDIT);
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Constants.java

    r25606 r25783  
    55public interface Constants {
    66        String SEPARATOR = ";";
    7         String SPLIT_REGEX = "[,:;]";
     7        String SPLIT_REGEX = "\\p{Zs}*[,:;]\\p{Zs}*";
    88        Pattern SPLIT_PATTERN = Pattern.compile(SPLIT_REGEX);
    99       
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Junction.java

    r25606 r25783  
    22
    33import java.util.ArrayList;
     4import java.util.HashSet;
    45import java.util.List;
    56import java.util.Set;
    67
    78import org.openstreetmap.josm.data.osm.Node;
    8 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    9 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    10 import org.openstreetmap.josm.data.osm.Relation;
    11 import org.openstreetmap.josm.data.osm.RelationMember;
    129import org.openstreetmap.josm.data.osm.Way;
    1310
    1411public class Junction {
    15         private static final List<Way> filterHighways(List<OsmPrimitive> of) {
    16                 final List<Way> result = new ArrayList<Way>();
    17                
    18                 for (OsmPrimitive p : of) {
    19                         if (p.getType() == OsmPrimitiveType.WAY && Utils.isRoad((Way) p)) {
    20                                 result.add((Way) p);
    21                         }
    22                 }
    23                
    24                 return result;
    25         }
    26        
    27         private static List<Way> filterBeginsOrEndsAt(List<Way> ways, Node node) {
    28                 final List<Way> result = new ArrayList<Way>();
    29                
    30                 for (Way w : ways) {
    31                         if (w.isFirstLastNode(node)) {
    32                                 result.add(w);
    33                         }
    34                 }
    35                
    36                 return result;
    37         }
    38        
    39         private static List<Road> loadRoads(ModelContainer container, Junction j) {
    40                 final List<Way> ways = filterBeginsOrEndsAt(filterHighways(j.getNode().getReferrers()), j.getNode());
    41                
    42                 return Road.map(container, ways, j);
    43         }
    44        
    4512        private final ModelContainer container;
    4613       
    4714        private final Node node;
    48         private final List<Road> roads = new ArrayList<Road>();
     15        private final Set<Way> roads = new HashSet<Way>();
    4916       
    5017        Junction(ModelContainer container, Node n) {
     
    5522               
    5623                if (isPrimary()) {
    57                         loadRoads(container, this);
    5824                        // if turn data is invalid, this will force an exception now, not later during painting
    59                         getTurns();
     25                        // getTurns(); TODO force this again
    6026                }
    6127        }
    6228       
    63         boolean isPrimary() {
    64                 return container.getPrimary().equals(this);
     29        public boolean isPrimary() {
     30                return getContainer().isPrimary(this);
    6531        }
    6632       
     
    7036       
    7137        public List<Road> getRoads() {
    72                 return roads;
     38                final List<Road> result = new ArrayList<Road>(roads.size());
     39               
     40                for (Way w : roads) {
     41                        result.add(container.getRoad(w));
     42                }
     43               
     44                return result;
    7345        }
    7446       
    75         void addRoad(Road r) {
    76                 roads.add(r);
     47        public List<Road.End> getRoadEnds() {
     48                final List<Road.End> result = new ArrayList<Road.End>(roads.size());
     49               
     50                for (Way w : roads) {
     51                        result.add(getRoadEnd(w));
     52                }
     53               
     54                return result;
    7755        }
    7856       
    79         public void addTurn(Lane from, Road.End to) {
    80                 assert equals(from.getOutgoingJunction());
    81                 assert equals(to.getJunction());
     57        void addRoad(Way w) {
     58                roads.add(w);
     59        }
     60       
     61        Road.End getRoadEnd(Way w) {
     62                final Road r = getContainer().getRoad(w);
    8263               
    83                 final Way fromWay = from.isReverse() ? from.getRoad().getRoute().getFirstSegment().getWay() : from.getRoad()
    84                     .getRoute().getLastSegment().getWay();
    85                 final Way toWay = to.isFromEnd() ? to.getRoad().getRoute().getFirstSegment().getWay() : to.getRoad().getRoute()
    86                     .getLastSegment().getWay();
    87                
    88                 Relation existing = null;
    89                 for (Turn t : getTurns()) {
    90                         if ((from.isReverse() ? from.getRoad().getRoute().getFirstSegment() : from.getRoad().getRoute().getLastSegment())
    91                             .getWay().equals(t.getFromWay()) && t.getTo().equals(to)) {
    92                                 if (t.getFrom().isExtra() == from.isExtra() && t.getFrom().getIndex() == from.getIndex()) {
    93                                         // was already added
    94                                         return;
    95                                 }
    96                                
    97                                 existing = t.getRelation();
     64                if (r.getRoute().getSegments().size() == 1) {
     65                        final boolean starts = r.getRoute().getStart().equals(node);
     66                        final boolean ends = r.getRoute().getEnd().equals(node);
     67                       
     68                        if (starts && ends) {
     69                                throw new IllegalArgumentException("Ambiguous: The way starts and ends at the junction node.");
     70                        } else if (starts) {
     71                                return r.getFromEnd();
     72                        } else if (ends) {
     73                                return r.getToEnd();
    9874                        }
     75                } else if (r.getRoute().getFirstSegment().getWay().equals(w)) {
     76                        return r.getFromEnd();
     77                } else if (r.getRoute().getLastSegment().getWay().equals(w)) {
     78                        return r.getToEnd();
    9979                }
    10080               
    101                 final Relation r = existing == null ? new Relation() : existing;
    102                
    103                 final String key = from.isExtra() ? Constants.TURN_KEY_EXTRA_LANES : Constants.TURN_KEY_LANES;
    104                 final List<Integer> lanes = Turn.split(r.get(key));
    105                 lanes.add(from.getIndex());
    106                 r.put(key, Turn.join(lanes));
    107                
    108                 if (existing == null) {
    109                         r.put("type", Constants.TYPE_TURNS);
    110                        
    111                         r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, node));
    112                         r.addMember(new RelationMember(Constants.TURN_ROLE_FROM, fromWay));
    113                         r.addMember(new RelationMember(Constants.TURN_ROLE_TO, toWay));
    114                        
    115                         node.getDataSet().addPrimitive(r);
    116                 }
     81                throw new IllegalArgumentException("While there exists a road for the given way, the way neither "
     82                    + "starts nor ends at the junction node.");
    11783        }
    11884       
    119         public Set<Turn> getTurns() {
    120                 return Turn.load(this);
    121         }
    122        
    123         Road.End getRoadEnd(Way way) {
    124                 final List<Road.End> candidates = new ArrayList<Road.End>();
    125                
    126                 for (Road r : getRoads()) {
    127                         if (r.getFromEnd().getJunction().equals(this)) {
    128                                 if (r.getRoute().getSegments().get(0).getWay().equals(way)) {
    129                                         candidates.add(r.getFromEnd());
    130                                 }
    131                         }
    132                        
    133                         if (r.getToEnd().getJunction().equals(this)) {
    134                                 if (r.getRoute().getSegments().get(r.getRoute().getSegments().size() - 1).getWay().equals(way)) {
    135                                         candidates.add(r.getToEnd());
    136                                 }
    137                         }
    138                 }
    139                
    140                 if (candidates.isEmpty()) {
    141                         throw new IllegalArgumentException("No such road end.");
    142                 } else if (candidates.size() > 1) {
    143                         throw new IllegalArgumentException("There are " + candidates.size()
    144                             + " road ends at this junction for the given way.");
    145                 }
    146                
    147                 return candidates.get(0);
    148         }
    149        
    150         public void removeTurn(Turn turn) {
    151                 turn.remove();
     85        public ModelContainer getContainer() {
     86                return container;
    15287        }
    15388}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Lane.java

    r25606 r25783  
    22
    33import java.util.ArrayList;
    4 import java.util.Collections;
    54import java.util.List;
     5import java.util.Set;
    66
    77import org.openstreetmap.josm.data.osm.Node;
    88import org.openstreetmap.josm.data.osm.Relation;
     9import org.openstreetmap.josm.data.osm.RelationMember;
    910import org.openstreetmap.josm.data.osm.Way;
    1011import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils;
     
    1516                EXTRA_RIGHT,
    1617                REGULAR;
    17         }
    18        
    19         /**
    20          *
    21          * @param road
    22          * @param r
    23          *          lengths relation
    24          * @param w
    25          * @return
    26          */
    27         public static List<Lane> load(Road road, Relation left, Relation right, Way w) {
    28                 double extraLengthLeft = left == null ? 0 : Route.load(left).getLengthFrom(w);
    29                 double extraLengthRight = right == null ? 0 : Route.load(right).getLengthFrom(w);
    30                
    31                 final List<Double> leftLengths = loadLengths(left, Constants.LENGTHS_KEY_LENGTHS_LEFT, extraLengthLeft);
    32                 final List<Double> rightLengths = loadLengths(right, Constants.LENGTHS_KEY_LENGTHS_RIGHT, extraLengthRight);
    33                
    34                 final List<Lane> result = new ArrayList<Lane>(load(road));
    35                 final int mid = getReverseCount(result);
    36                 result.addAll(mid, map(leftLengths, road, true, extraLengthLeft));
    37                 result.addAll(map(rightLengths, road, false, extraLengthRight));
    38                
    39                 return result;
    40         }
    41        
    42         private static List<Lane> map(List<Double> lengths, Road road, boolean left, double extraLength) {
    43                 final List<Lane> result = new ArrayList<Lane>(lengths.size());
    44                
    45                 int index = left ? -lengths.size() : 1;
    46                 for (Double l : (left ? CollectionUtils.reverse(lengths) : lengths)) {
    47                         // TODO road may connect twice with junction => reverse might not always be false
    48                         result.add(new Lane(road, index++, false, left, extraLength, l - extraLength));
    49                 }
    50                
    51                 return result;
    52         }
    53        
    54         private static int getReverseCount(List<Lane> ls) {
    55                 int result = 0;
    56                
    57                 for (Lane l : ls) {
    58                         if (l.isReverse()) {
    59                                 ++result;
    60                         }
     18               
     19                public boolean isExtra() {
     20                        return this == EXTRA_LEFT || this == EXTRA_RIGHT;
     21                }
     22        }
     23       
     24        static List<Lane> load(Road.End roadEnd) {
     25                final List<Lane> result = new ArrayList<Lane>();
     26                int i;
     27               
     28                i = 0;
     29                for (double l : CollectionUtils.reverse(roadEnd.getLengths(Kind.EXTRA_LEFT))) {
     30                        result.add(new Lane(roadEnd, --i, Kind.EXTRA_LEFT, l));
     31                }
     32               
     33                final int regulars = getRegularCount(roadEnd.getWay(), roadEnd.getJunction().getNode());
     34                for (i = 1; i <= regulars; ++i) {
     35                        result.add(new Lane(roadEnd, i));
     36                }
     37               
     38                i = 0;
     39                for (double l : roadEnd.getLengths(Kind.EXTRA_RIGHT)) {
     40                        result.add(new Lane(roadEnd, ++i, Kind.EXTRA_RIGHT, l));
    6141                }
    6242               
     
    11292        }
    11393       
    114         public static List<Lane> load(Road r) {
    115                 final Route.Segment first = r.getRoute().getFirstSegment();
    116                 final Route.Segment last = r.getRoute().getLastSegment();
    117                
    118                 final int back = getRegularCount(first.getWay(), first.getStart());
    119                 final int forth = getRegularCount(last.getWay(), last.getEnd());
    120                
    121                 final List<Lane> result = new ArrayList<Lane>(back + forth);
    122                
    123                 for (int i = back; i >= 1; --i) {
    124                         result.add(new Lane(r, i, true));
    125                 }
    126                 for (int i = 1; i <= forth; ++i) {
    127                         result.add(new Lane(r, i, false));
    128                 }
    129                
    130                 return Collections.unmodifiableList(result);
    131         }
    132        
    133         private final Road road;
     94        private final Road.End roadEnd;
    13495        private final int index;
    13596        private final Kind kind;
    136         private final boolean reverse;
    137        
    138         /**
    139          * If this extra lane extends past the given road, this value equals the length of the way(s)
    140          * which come after the end of the road.
    141          */
    142         private final double extraLength;
    14397       
    14498        private double length = -1;
    14599       
    146         public Lane(Road road, int index, boolean reverse) {
    147                 this.road = road;
     100        public Lane(Road.End roadEnd, int index) {
     101                this.roadEnd = roadEnd;
    148102                this.index = index;
    149103                this.kind = Kind.REGULAR;
    150                 this.reverse = reverse;
    151                 this.extraLength = -1;
    152         }
    153        
    154         public Lane(Road road, int index, boolean reverse, boolean left, double extraLength, double length) {
    155                 this.road = road;
     104        }
     105       
     106        public Lane(Road.End roadEnd, int index, Kind kind, double length) {
     107                assert kind == Kind.EXTRA_LEFT || kind == Kind.EXTRA_RIGHT;
     108               
     109                this.roadEnd = roadEnd;
    156110                this.index = index;
    157                 this.kind = left ? Kind.EXTRA_LEFT : Kind.EXTRA_RIGHT;
    158                 this.reverse = reverse;
    159                 this.extraLength = extraLength;
     111                this.kind = kind;
    160112                this.length = length;
    161113               
     
    163115                        throw new IllegalArgumentException("Length must be positive");
    164116                }
    165                
    166                 if (extraLength < 0) {
    167                         throw new IllegalArgumentException("Extra length must not be negative");
    168                 }
    169117        }
    170118       
    171119        public Road getRoad() {
    172                 return road;
    173         }
    174        
    175         public double getExtraLength() {
    176                 return extraLength;
     120                return roadEnd.getRoad();
    177121        }
    178122       
     
    182126       
    183127        public double getLength() {
    184                 return isExtra() ? length : road.getLength();
    185         }
    186        
    187         double getTotalLength() {
    188                 if (!isExtra()) {
    189                         throw new UnsupportedOperationException();
    190                 }
    191                
    192                 return length + extraLength;
     128                return isExtra() ? length : getRoad().getLength();
    193129        }
    194130       
     
    201137               
    202138                // TODO if needed, increase length of other lanes
    203                 road.updateLengths();
     139                getOutgoingRoadEnd().updateLengths();
    204140               
    205141                this.length = length;
     
    214150        }
    215151       
    216         public boolean isReverse() {
    217                 return reverse;
    218         }
    219        
    220152        public Junction getOutgoingJunction() {
    221                 return isReverse() ? getRoad().getFromEnd().getJunction() : getRoad().getToEnd().getJunction();
     153                return getOutgoingRoadEnd().getJunction();
    222154        }
    223155       
    224156        public Junction getIncomingJunction() {
    225                 return isReverse() ? getRoad().getToEnd().getJunction() : getRoad().getFromEnd().getJunction();
     157                return getIncomingRoadEnd().getJunction();
    226158        }
    227159       
    228160        public Road.End getOutgoingRoadEnd() {
    229                 return isReverse() ? getRoad().getFromEnd() : getRoad().getToEnd();
     161                return roadEnd;
    230162        }
    231163       
    232164        public Road.End getIncomingRoadEnd() {
    233                 return isReverse() ? getRoad().getToEnd() : getRoad().getFromEnd();
     165                return roadEnd.getOppositeEnd();
     166        }
     167       
     168        public ModelContainer getContainer() {
     169                return getRoad().getContainer();
     170        }
     171       
     172        public void addTurn(List<Road> via, Road.End to) {
     173                assert equals(to.getJunction());
     174               
     175                Relation existing = null;
     176                for (Turn t : to.getTurns()) {
     177                        if (t.getFrom().getOutgoingRoadEnd().equals(getOutgoingRoadEnd()) && t.getVia().equals(via)) {
     178                                if (t.getFrom().equals(this)) {
     179                                        // was already added
     180                                        return;
     181                                }
     182                               
     183                                existing = t.getRelation();
     184                        }
     185                }
     186               
     187                final Relation r;
     188                if (existing == null) {
     189                        r = new Relation();
     190                        r.put("type", Constants.TYPE_TURNS);
     191                       
     192                        r.addMember(new RelationMember(Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay()));
     193                        if (via.isEmpty()) {
     194                                r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, getOutgoingJunction().getNode()));
     195                        } else {
     196                                for (Way w : Utils.flattenVia(getOutgoingJunction().getNode(), via, to.getJunction().getNode())) {
     197                                        r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, w));
     198                                }
     199                        }
     200                        r.addMember(new RelationMember(Constants.TURN_ROLE_TO, to.getWay()));
     201                       
     202                        getOutgoingJunction().getNode().getDataSet().addPrimitive(r);
     203                } else {
     204                        r = existing;
     205                }
     206               
     207                final String key = isExtra() ? Constants.TURN_KEY_EXTRA_LANES : Constants.TURN_KEY_LANES;
     208                final List<Integer> lanes = Turn.indices(r, key);
     209                lanes.add(getIndex());
     210                r.put(key, Turn.join(lanes));
     211        }
     212       
     213        public Set<Turn> getTurns() {
     214                return Turn.load(getContainer(), Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay());
    234215        }
    235216}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/ModelContainer.java

    r25606 r25783  
    11package org.openstreetmap.josm.plugins.turnlanes.model;
    22
     3import java.util.ArrayList;
     4import java.util.Collections;
    35import java.util.HashMap;
     6import java.util.HashSet;
     7import java.util.List;
    48import java.util.Map;
     9import java.util.Set;
    510
    611import org.openstreetmap.josm.data.osm.Node;
     12import org.openstreetmap.josm.data.osm.OsmPrimitive;
     13import org.openstreetmap.josm.data.osm.Relation;
     14import org.openstreetmap.josm.data.osm.RelationMember;
    715import org.openstreetmap.josm.data.osm.Way;
     16import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils;
     17import org.openstreetmap.josm.tools.Pair;
    818
    919public class ModelContainer {
    10         public static ModelContainer create(Node n) {
    11                 final ModelContainer container = new ModelContainer(n);
    12                 container.getOrCreateJunction(n);
    13                 return container;
     20        public static ModelContainer create(Iterable<Node> primaryNodes, Iterable<Way> primaryWays) {
     21                final Set<Node> closedNodes = new HashSet<Node>(CollectionUtils.toList(primaryNodes));
     22                final Set<Way> closedWays = new HashSet<Way>(CollectionUtils.toList(primaryWays));
     23               
     24                close(closedNodes, closedWays);
     25               
     26                return new ModelContainer(closedNodes, closedWays);
     27        }
     28       
     29        private static void close(Set<Node> closedNodes, Set<Way> closedWays) {
     30                boolean closed = false;
     31               
     32                while (!closed) {
     33                        closed = true;
     34                       
     35                        for (Node n : closedNodes) {
     36                                for (Way w : Utils.filterRoads(n.getReferrers())) {
     37                                        if (w.isFirstLastNode(n)) {
     38                                                closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_FROM);
     39                                                closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_TO);
     40                                        }
     41                                }
     42                               
     43                                for (Way w : closedWays) {
     44                                        closed &= close(closedNodes, closedWays, w, Constants.TURN_ROLE_VIA);
     45                                }
     46                        }
     47                }
     48        }
     49       
     50        private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Way w, String role) {
     51                boolean closed = true;
     52               
     53                for (Relation r : OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class)) {
     54                        if (!r.get("type").equals(Constants.TYPE_TURNS)) {
     55                                continue;
     56                        }
     57                       
     58                        for (RelationMember m : r.getMembers()) {
     59                                if (m.getRole().equals(role) && m.getMember().equals(w)) {
     60                                        closed &= close(closedNodes, closedWays, r);
     61                                }
     62                        }
     63                }
     64               
     65                return closed;
     66        }
     67       
     68        private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Relation r) {
     69                boolean closed = true;
     70               
     71                final List<Way> via = new ArrayList<Way>();
     72                for (RelationMember m : Utils.getMembers(r, Constants.TURN_ROLE_VIA)) {
     73                        if (m.isWay()) {
     74                                closed &= !closedWays.add(m.getWay());
     75                                via.add(m.getWay());
     76                        } else if (m.isNode()) {
     77                                closed &= !closedNodes.add(m.getNode());
     78                        }
     79                }
     80               
     81                if (!via.isEmpty()) {
     82                        final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
     83                        final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
     84                       
     85                        closed &= !closedNodes.add(Utils.lineUp(from, via.get(0)));
     86                        closed &= !closedNodes.add(Utils.lineUp(via.get(via.size() - 1), to));
     87                }
     88               
     89                return closed;
    1490        }
    1591       
     
    1793        private final Map<Way, Road> roads = new HashMap<Way, Road>();
    1894       
    19         private final Node primary;
    20        
    21         private ModelContainer(Node primary) {
    22                 this.primary = primary;
     95        private final Set<Node> primaryNodes;
     96        private final Set<Way> primaryWays;
     97       
     98        private ModelContainer(Set<Node> primaryNodes, Set<Way> primaryWays) {
     99                this.primaryNodes = Collections.unmodifiableSet(new HashSet<Node>(primaryNodes));
     100                this.primaryWays = Collections.unmodifiableSet(new HashSet<Way>(primaryWays));
     101               
     102                final Set<Pair<Way, Junction>> ws = new HashSet<Pair<Way, Junction>>();
     103                for (Node n : primaryNodes) {
     104                        final Junction j = getOrCreateJunction(n);
     105                       
     106                        for (Way w : Utils.filterRoads(n.getReferrers())) {
     107                                if (w.isFirstLastNode(n)) {
     108                                        ws.add(new Pair<Way, Junction>(w, j));
     109                                }
     110                        }
     111                }
     112               
     113                final List<Route> rs = Utils.orderWays(primaryWays, primaryNodes);
     114                for (Route r : rs) {
     115                        addRoad(new Road(this, r));
     116                }
     117               
     118                for (Pair<Way, Junction> w : ws) {
     119                        if (!primaryWays.contains(w.a)) {
     120                                addRoad(new Road(this, w.a, w.b));
     121                        }
     122                }
    23123        }
    24124       
     
    43143        }
    44144       
    45         Road getRoad(Way w, Junction j) {
    46                 final Road existing = roads.get(w);
    47                
    48                 if (existing != null && j.equals(existing.getToEnd().getJunction())) {
    49                         return existing;
    50                 }
    51                
    52                 final Road newRoad = new Road(this, w, j);
     145        Road getRoad(Way w) {
     146                final Road r = roads.get(w);
     147               
     148                if (r == null) {
     149                        throw new IllegalArgumentException("There is no road containing the given way.");
     150                }
     151               
     152                return r;
     153        }
     154       
     155        private void addRoad(Road newRoad, Road mergedA, Road mergedB) {
     156                assert (mergedA == null) == (mergedB == null);
    53157               
    54158                for (Route.Segment s : newRoad.getRoute().getSegments()) {
     
    56160                       
    57161                        if (oldRoad != null) {
    58                                 return mergeRoads(oldRoad, newRoad);
    59                         }
    60                 }
    61                
    62                 return newRoad;
     162                                if (mergedA == null) {
     163                                        final Road mergedRoad = mergeRoads(oldRoad, newRoad);
     164                                        addRoad(mergedRoad, oldRoad, newRoad);
     165                                } else if (!oldRoad.equals(mergedA) && !oldRoad.equals(mergedB)) {
     166                                        throw new RuntimeException("A road can't be connected to more than two junctions.");
     167                                }
     168                        }
     169                }
     170        }
     171       
     172        private void addRoad(Road newRoad) {
     173                addRoad(newRoad, null, null);
    63174        }
    64175       
    65176        private Road mergeRoads(Road a, Road b) {
    66                 throw null; // TODO implement
     177                final String ERR_ILLEGAL_ARGS = "The given roads can not be merged into one.";
     178               
     179                final List<Way> ws = new ArrayList<Way>(CollectionUtils.toList(CollectionUtils.reverse(a.getRoute().getWays())));
     180                final List<Way> bws = b.getRoute().getWays();
     181               
     182                int i = -1;
     183                for (Way w : ws) {
     184                        if (w.equals(bws.get(i + 1))) {
     185                                ++i;
     186                        } else if (i >= 0) {
     187                                throw new IllegalArgumentException(ERR_ILLEGAL_ARGS);
     188                        }
     189                }
     190               
     191                if (i < 0) {
     192                        throw new IllegalArgumentException(ERR_ILLEGAL_ARGS);
     193                }
     194                ws.addAll(bws.subList(i + 1, bws.size()));
     195               
     196                final Route mergedRoute = Route.create(ws, a.getRoute().getLastSegment().getEnd());
     197                return new Road(this, mergedRoute);
    67198        }
    68199       
     
    73204        }
    74205       
    75         Junction getPrimary() {
    76                 return junctions.get(primary);
     206        public Set<Junction> getPrimaryJunctions() {
     207                final Set<Junction> pjs = new HashSet<Junction>();
     208               
     209                for (Node n : primaryNodes) {
     210                        pjs.add(getOrCreateJunction(n));
     211                }
     212               
     213                return pjs;
     214        }
     215       
     216        public Set<Road> getPrimaryRoads() {
     217                final Set<Road> prs = new HashSet<Road>();
     218               
     219                for (Way w : primaryWays) {
     220                        prs.add(roads.get(w));
     221                }
     222               
     223                return prs;
     224        }
     225       
     226        public ModelContainer recalculate() {
     227                return new ModelContainer(primaryNodes, primaryWays);
     228        }
     229       
     230        public boolean isPrimary(Junction j) {
     231                return primaryNodes.contains(j.getNode());
     232        }
     233       
     234        public boolean isPrimary(Road r) {
     235                return primaryWays.contains(r.getRoute().getFirstSegment().getWay());
    77236        }
    78237}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Road.java

    r25606 r25783  
    77import java.util.Arrays;
    88import java.util.List;
     9import java.util.Set;
    910
    1011import org.openstreetmap.josm.data.osm.Node;
     
    1415import org.openstreetmap.josm.data.osm.RelationMember;
    1516import org.openstreetmap.josm.data.osm.Way;
     17import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils;
    1618import org.openstreetmap.josm.plugins.turnlanes.model.Route.Segment;
    1719import org.openstreetmap.josm.tools.Pair;
     
    1921public class Road {
    2022        public class End {
     23                private final boolean from;
    2124                private final Junction junction;
    2225               
    23                 private End(Junction junction) {
     26                private final Relation lengthsLeft;
     27                private final Relation lengthsRight;
     28               
     29                private final double extraLengthLeft;
     30                private final double extraLengthRight;
     31               
     32                private final List<Lane> lanes;
     33               
     34                private End(boolean from, Junction junction, Relation lengthsLeft, Relation lengthsRight) {
     35                        this.from = from;
    2436                        this.junction = junction;
     37                        this.lengthsLeft = lengthsLeft;
     38                        this.lengthsRight = lengthsRight;
     39                        this.extraLengthLeft = lengthsLeft == null ? 0 : Route.load(lengthsLeft).getLengthFrom(getWay());
     40                        this.extraLengthRight = lengthsRight == null ? 0 : Route.load(lengthsRight).getLengthFrom(getWay());
     41                        this.lanes = Lane.load(this);
     42                       
     43                        junction.addRoad(getWay());
     44                }
     45               
     46                private End(boolean from, Junction junction) {
     47                        this.from = from;
     48                        this.junction = junction;
     49                        this.lengthsLeft = null;
     50                        this.lengthsRight = null;
     51                        this.extraLengthLeft = 0;
     52                        this.extraLengthRight = 0;
     53                        this.lanes = Lane.load(this);
     54                       
     55                        junction.addRoad(getWay());
    2556                }
    2657               
     
    2960                }
    3061               
     62                public Way getWay() {
     63                        return isFromEnd() ? getRoute().getFirstSegment().getWay() : getRoute().getLastSegment().getWay();
     64                }
     65               
    3166                public Junction getJunction() {
    3267                        return junction;
    3368                }
    3469               
    35                 private boolean isFrom() {
    36                         if (this == fromEnd) {
    37                                 return true;
    38                         } else if (this == toEnd) {
    39                                 return false;
    40                         }
    41                        
    42                         throw new IllegalStateException();
    43                 }
    44                
    4570                public boolean isFromEnd() {
    46                         return isFrom();
     71                        return from;
    4772                }
    4873               
    4974                public boolean isToEnd() {
    50                         return !isFrom();
    51                 }
    52                
    53                 public void addExtraLane(boolean left) {
    54                         if (!isToEnd()) {
    55                                 throw new UnsupportedOperationException();
     75                        return !isFromEnd();
     76                }
     77               
     78                public End getOppositeEnd() {
     79                        return isFromEnd() ? toEnd : fromEnd;
     80                }
     81               
     82                /**
     83                 * @return the turns <em>onto</em> this road at this end
     84                 */
     85                public Set<Turn> getTurns() {
     86                        return Turn.load(getContainer(), Constants.TURN_ROLE_TO, getWay());
     87                }
     88               
     89                public void addLane(Lane.Kind kind) {
     90                        if (kind == Lane.Kind.REGULAR) {
     91                                throw new IllegalArgumentException("Only extra lanes can be added.");
    5692                        }
    5793                       
    5894                        double length = Double.POSITIVE_INFINITY;
    59                         for (Lane l : getLanes()) {
    60                                 if (l.getKind() == (left ? Lane.Kind.EXTRA_LEFT : Lane.Kind.EXTRA_RIGHT)) {
    61                                         {
    62                                                 length = Math.min(length, l.getLength());
    63                                         }
     95                        for (Lane l : lanes) {
     96                                if (l.getKind() == kind) {
     97                                        length = Math.max(0, Math.min(length, l.getLength() - 1));
    6498                                }
    6599                        }
     
    69103                        }
    70104                       
    71                         addLength(left, length);
    72                 }
    73         }
    74        
    75         public static List<Road> map(ModelContainer container, List<Way> ws, Junction j) {
    76                 final List<Road> result = new ArrayList<Road>();
    77                
    78                 for (Way w : ws) {
    79                         result.add(container.getRoad(w, j));
    80                 }
    81                
    82                 return result;
     105                        addLane(kind, length);
     106                }
     107               
     108                private void addLane(Lane.Kind kind, double length) {
     109                        assert kind == Lane.Kind.EXTRA_LEFT || kind == Lane.Kind.EXTRA_RIGHT;
     110                       
     111                        final boolean left = kind == Lane.Kind.EXTRA_LEFT;
     112                        final Relation rel = left ? lengthsLeft : lengthsRight;
     113                        final Relation other = left ? lengthsRight : lengthsLeft;
     114                        final Node n = getJunction().getNode();
     115                       
     116                        final String lengthStr = toLengthString(length);
     117                        final Relation target;
     118                        if (rel == null) {
     119                                if (other == null || !Utils.getMemberNode(other, "end").equals(n)) {
     120                                        target = createLengthsRelation();
     121                                } else {
     122                                        target = other;
     123                                }
     124                        } else {
     125                                target = rel;
     126                        }
     127                       
     128                        final String key = left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT;
     129                        final String old = target.get(key);
     130                        if (old == null) {
     131                                target.put(key, lengthStr);
     132                        } else {
     133                                target.put(key, old + Constants.SEPARATOR + lengthStr);
     134                        }
     135                }
     136               
     137                private Relation createLengthsRelation() {
     138                        final Node n = getJunction().getNode();
     139                       
     140                        final Relation r = new Relation();
     141                        r.put("type", Constants.TYPE_LENGTHS);
     142                       
     143                        r.addMember(new RelationMember(Constants.LENGTHS_ROLE_END, n));
     144                        for (Route.Segment s : isFromEnd() ? route.getSegments() : CollectionUtils.reverse(route.getSegments())) {
     145                                r.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, s.getWay()));
     146                        }
     147                       
     148                        n.getDataSet().addPrimitive(r);
     149                       
     150                        return r;
     151                }
     152               
     153                void updateLengths() {
     154                        for (final boolean left : Arrays.asList(true, false)) {
     155                                final Lane.Kind kind = left ? Lane.Kind.EXTRA_LEFT : Lane.Kind.EXTRA_RIGHT;
     156                                final Relation r = left ? lengthsLeft : lengthsRight;
     157                                final double extra = left ? extraLengthLeft : extraLengthRight;
     158                               
     159                                if (r == null) {
     160                                        continue;
     161                                }
     162                               
     163                                final StringBuilder lengths = new StringBuilder(32);
     164                                for (Lane l : left ? CollectionUtils.reverse(lanes) : lanes) {
     165                                        if (l.getKind() == kind) {
     166                                                lengths.append(toLengthString(extra + l.getLength())).append(Constants.SEPARATOR);
     167                                        }
     168                                }
     169                               
     170                                lengths.setLength(lengths.length() - Constants.SEPARATOR.length());
     171                                r.put(left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT, lengths.toString());
     172                        }
     173                }
     174               
     175                public List<Lane> getLanes() {
     176                        return lanes;
     177                }
     178               
     179                public Lane getLane(Lane.Kind kind, int index) {
     180                        for (Lane l : lanes) {
     181                                if (l.getKind() == kind && l.getIndex() == index) {
     182                                        return l;
     183                                }
     184                        }
     185                       
     186                        throw new IllegalArgumentException("No such lane.");
     187                }
     188               
     189                public Lane getExtraLane(int index) {
     190                        return index < 0 ? getLane(Lane.Kind.EXTRA_LEFT, index) : getLane(Lane.Kind.EXTRA_RIGHT, index);
     191                }
     192               
     193                public boolean isExtendable() {
     194                        final End o = getOppositeEnd();
     195                        return (lengthsLeft == null && lengthsRight == null) && (o.lengthsLeft != null || o.lengthsRight != null);
     196                }
     197               
     198                public void extend(Way way) {
     199                        if (!isExtendable()) {
     200                                throw new IllegalStateException();
     201                        }
     202                       
     203                        final End o = getOppositeEnd();
     204                        if (o.lengthsLeft != null) {
     205                                o.lengthsLeft.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, way));
     206                        }
     207                        if (o.lengthsRight != null) {
     208                                o.lengthsRight.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, way));
     209                        }
     210                }
     211               
     212                public List<Double> getLengths(Lane.Kind kind) {
     213                        switch (kind) {
     214                                case EXTRA_LEFT:
     215                                        return Lane.loadLengths(lengthsLeft, Constants.LENGTHS_KEY_LENGTHS_LEFT, extraLengthLeft);
     216                                case EXTRA_RIGHT:
     217                                        return Lane.loadLengths(lengthsRight, Constants.LENGTHS_KEY_LENGTHS_RIGHT, extraLengthRight);
     218                                default:
     219                                        throw new IllegalArgumentException(String.valueOf(kind));
     220                        }
     221                }
    83222        }
    84223       
     
    142281       
    143282        private final ModelContainer container;
    144        
    145         private final Relation lengthsLeft;
    146         private final Relation lengthsRight;
    147283        private final Route route;
    148         private final List<Lane> lanes;
    149        
    150284        private final End fromEnd;
    151285        private final End toEnd;
    152286       
    153287        Road(ModelContainer container, Way w, Junction j) {
    154                 this.container = container;
    155                 this.toEnd = new End(j);
    156                
    157288                final Node n = j.getNode();
    158                
    159289                if (!w.isFirstLastNode(n)) {
    160290                        throw new IllegalArgumentException("Way must start or end in given node.");
    161291                }
    162                
    163                 final Pair<Relation, Relation> lr = getLengthRelations(w, n);
    164                
    165                 this.lengthsLeft = lr.a;
    166                 this.lengthsRight = lr.b;
    167                 this.route = lengthsLeft == null && lengthsRight == null ? Route.create(Arrays.asList(w), n) : Route.load(
    168                     lengthsLeft, lengthsRight, w);
    169                 this.lanes = lengthsLeft == null && lengthsRight == null ? Lane.load(this) : Lane.load(this, lengthsLeft,
    170                     lengthsRight, w);
    171                
    172                 final List<Node> nodes = route.getNodes();
    173                 this.fromEnd = new End(container.getOrCreateJunction(nodes.get(0)));
    174                
    175                 fromEnd.getJunction().addRoad(this);
    176                 toEnd.getJunction().addRoad(this);
     292                final Pair<Relation, Relation> lengthsRelations = getLengthRelations(w, n);
     293               
     294                this.container = container;
     295                this.route = lengthsRelations.a == null && lengthsRelations.b == null ? Route.create(Arrays.asList(w), n) : Route
     296                    .load(lengthsRelations.a, lengthsRelations.b, w);
     297                this.fromEnd = new End(true, container.getOrCreateJunction(route.getFirstSegment().getStart()));
     298                this.toEnd = new End(false, j, lengthsRelations.a, lengthsRelations.b);
     299        }
     300       
     301        Road(ModelContainer container, Route route) {
     302                this.container = container;
     303                this.route = route;
     304                this.fromEnd = new End(true, container.getJunction(route.getStart()));
     305                this.toEnd = new End(false, container.getJunction(route.getEnd()));
    177306        }
    178307       
     
    189318        }
    190319       
    191         public List<Lane> getLanes() {
    192                 return lanes;
    193         }
    194        
    195320        public double getLength() {
    196321                return route.getLength();
    197         }
    198        
    199         void updateLengths() {
    200                 if (lengthsLeft != null) { // TODO only forward lanes at this point
    201                         String lengths = "";
    202                         for (int i = lanes.size() - 1; i >= 0; --i) {
    203                                 final Lane l = lanes.get(i);
    204                                 if (l.getKind() == Lane.Kind.EXTRA_LEFT) {
    205                                         lengths += toLengthString(l.getTotalLength()) + Constants.SEPARATOR;
    206                                 }
    207                         }
    208                         lengthsLeft.put(Constants.LENGTHS_KEY_LENGTHS_LEFT,
    209                             lengths.substring(0, lengths.length() - Constants.SEPARATOR.length()));
    210                 }
    211                 if (lengthsRight != null) {
    212                         String lengths = "";
    213                         for (Lane l : lanes) {
    214                                 if (l.getKind() == Lane.Kind.EXTRA_RIGHT) {
    215                                         lengths += toLengthString(l.getTotalLength()) + Constants.SEPARATOR;
    216                                 }
    217                         }
    218                         lengthsRight.put(Constants.LENGTHS_KEY_LENGTHS_RIGHT,
    219                             lengths.substring(0, lengths.length() - Constants.SEPARATOR.length()));
    220                 }
    221322        }
    222323       
     
    229330        }
    230331       
    231         // TODO create a special Lanes class which deals with this, misplaced here
    232         public Lane getLane(boolean reverse, int index) {
    233                 for (Lane l : lanes) {
    234                         if (!l.isExtra() && l.isReverse() == reverse && l.getIndex() == index) {
    235                                 return l;
    236                         }
    237                 }
    238                
    239                 throw new IllegalArgumentException("No such lane.");
    240         }
    241        
    242         public Lane getExtraLane(boolean reverse, int index) {
    243                 for (Lane l : lanes) {
    244                         if (l.isExtra() && l.isReverse() == reverse && l.getIndex() == index) {
    245                                 return l;
    246                         }
    247                 }
    248                
    249                 throw new IllegalArgumentException("No such lane.");
    250         }
    251        
    252332        public ModelContainer getContainer() {
    253333                return container;
    254334        }
    255335       
    256         private void addLength(boolean left, double length) {
    257                 final Relation l = lengthsLeft;
    258                 final Relation r = lengthsRight;
    259                 final Node n = toEnd.getJunction().getNode();
    260                
    261                 final String lengthStr = toLengthString(length);
    262                 final Relation target;
    263                 if (left) {
    264                         if (l == null) {
    265                                 if (r == null || !Utils.getMemberNode(r, "end").equals(n)) {
    266                                         target = createLengthsRelation();
    267                                 } else {
    268                                         target = r;
    269                                 }
    270                         } else {
    271                                 target = l;
    272                         }
    273                 } else {
    274                         if (r == null) {
    275                                 if (l == null || !Utils.getMemberNode(l, "end").equals(n)) {
    276                                         target = createLengthsRelation();
    277                                 } else {
    278                                         target = l;
    279                                 }
    280                         } else {
    281                                 target = r;
    282                         }
    283                 }
    284                
    285                 final String key = left ? Constants.LENGTHS_KEY_LENGTHS_LEFT : Constants.LENGTHS_KEY_LENGTHS_RIGHT;
    286                 final String old = target.get(key);
    287                 if (old == null) {
    288                         target.put(key, lengthStr);
    289                 } else {
    290                         target.put(key, old + Constants.SEPARATOR + lengthStr);
    291                 }
    292         }
    293        
    294         private Relation createLengthsRelation() {
    295                 final Node n = toEnd.getJunction().getNode();
    296                
    297                 final Relation r = new Relation();
    298                 r.put("type", Constants.TYPE_LENGTHS);
    299                
    300                 r.addMember(new RelationMember(Constants.LENGTHS_ROLE_END, n));
    301                 for (Route.Segment s : route.getSegments()) {
    302                         r.addMember(1, new RelationMember(Constants.LENGTHS_ROLE_WAYS, s.getWay()));
    303                 }
    304                
    305                 n.getDataSet().addPrimitive(r);
    306                
    307                 return r;
    308         }
    309        
    310         public boolean isExtendable() {
    311                 return lengthsLeft != null || lengthsRight != null;
    312         }
    313        
    314         public void extend(Way way) {
    315                 if (lengthsLeft != null) {
    316                         lengthsLeft.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, way));
    317                 }
    318                 if (lengthsRight != null) {
    319                         lengthsRight.addMember(new RelationMember(Constants.LENGTHS_ROLE_WAYS, way));
    320                 }
     336        public boolean isPrimary() {
     337                return getContainer().isPrimary(this);
    321338        }
    322339}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Route.java

    r25606 r25783  
    199199        }
    200200       
     201        public Node getStart() {
     202                return getFirstSegment().getStart();
     203        }
     204       
     205        public Node getEnd() {
     206                return getLastSegment().getEnd();
     207        }
     208       
    201209        public Segment getFirstSegment() {
    202210                return getSegments().get(0);
     
    210218                return new Route(segments.subList(fromIndex, toIndex));
    211219        }
     220       
     221        public List<Way> getWays() {
     222                final List<Way> ws = new ArrayList<Way>();
     223               
     224                for (Segment s : segments) {
     225                        ws.add(s.getWay());
     226                }
     227               
     228                return Collections.unmodifiableList(ws);
     229        }
    212230}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Turn.java

    r25606 r25783  
    22
    33import java.util.ArrayList;
     4import java.util.Collections;
    45import java.util.HashSet;
     6import java.util.Iterator;
     7import java.util.LinkedList;
    58import java.util.List;
    69import java.util.Set;
     
    811import org.openstreetmap.josm.data.osm.Node;
    912import org.openstreetmap.josm.data.osm.OsmPrimitive;
    10 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    1113import org.openstreetmap.josm.data.osm.Relation;
    1214import org.openstreetmap.josm.data.osm.RelationMember;
    1315import org.openstreetmap.josm.data.osm.Way;
     16import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils;
    1417
    15 public class Turn {
    16         public static Set<Turn> load(Junction junction) {
     18public final class Turn {
     19        static Set<Turn> load(ModelContainer c, String role, OsmPrimitive primitive) {
    1720                final Set<Turn> result = new HashSet<Turn>();
    18 
    19                 for (OsmPrimitive p : junction.getNode().getReferrers()) {
    20                         if (p.getType() != OsmPrimitiveType.RELATION) {
    21                                 continue;
    22                         }
    23 
    24                         Relation r = (Relation) p;
    25 
     21               
     22                for (Relation r : OsmPrimitive.getFilteredList(primitive.getReferrers(), Relation.class)) {
    2623                        if (!r.get("type").equals(Constants.TYPE_TURNS)) {
    2724                                continue;
    2825                        }
    29 
    30                         result.addAll(Turn.load(junction, r));
     26                       
     27                        for (RelationMember m : r.getMembers()) {
     28                                if (m.getRole().equals(role) && m.getMember().equals(primitive)) {
     29                                        result.addAll(load(c, r));
     30                                }
     31                        }
    3132                }
    32 
     33               
    3334                return result;
    3435        }
    35 
    36         private static Set<Turn> load(Junction junction, Relation turns) {
    37                 if (!Constants.TYPE_TURNS.equals(turns.get("type"))) {
    38                         throw new IllegalArgumentException("Relation must be a of type \""
    39                                         + Constants.TYPE_TURNS + "\".");
    40                 }
    41 
    42                 Way fromWay = null;
    43                 Way toWay = null;
    44                 Node via = null;
    45                 final List<Integer> lanes = split(turns.get(Constants.TURN_KEY_LANES));
    46                 final List<Integer> extraLanes = split(turns
    47                                 .get(Constants.TURN_KEY_EXTRA_LANES));
    48 
    49                 for (RelationMember m : turns.getMembers()) {
    50                         final String r = m.getRole();
    51                         if (Constants.TURN_ROLE_VIA.equals(r)) {
    52                                 if (via != null) {
    53                                         throw new IllegalArgumentException("More than one \""
    54                                                         + Constants.TURN_ROLE_VIA + "\" members.");
     36       
     37        static Set<Turn> load(ModelContainer c, Relation r) {
     38                for (RelationMember m : r.getMembers()) {
     39                        if (m.getRole().equals(Constants.TURN_ROLE_VIA)) {
     40                                if (m.isNode()) {
     41                                        return loadWithViaNode(c, r);
     42                                } else if (m.isWay()) {
     43                                        return loadWithViaWays(c, r);
    5544                                }
    56 
    57                                 via = m.getNode();
    58                         } else if (Constants.TURN_ROLE_FROM.equals(r)) {
    59                                 if (fromWay != null) {
    60                                         throw new IllegalArgumentException("More than one \""
    61                                                         + Constants.TURN_ROLE_FROM + "\" members.");
    62                                 }
    63 
    64                                 fromWay = m.getWay();
    65                         } else if (Constants.TURN_ROLE_TO.equals(r)) {
    66                                 if (toWay != null) {
    67                                         throw new IllegalArgumentException("More than one \""
    68                                                         + Constants.TURN_ROLE_TO + "\" members.");
    69                                 }
    70 
    71                                 toWay = m.getWay();
    7245                        }
    7346                }
    74 
    75                 if (!via.equals(junction.getNode())) {
    76                         throw new IllegalArgumentException(
    77                                         "Turn is for a different junction.");
    78                 }
    79 
    80                 final Road.End to = junction.getRoadEnd(toWay);
    81 
    82                 final Set<Turn> result = new HashSet<Turn>();
    83                 for (Road r : junction.getRoads()) {
    84                         final boolean reverse;
    85                         if (r.getRoute().getFirstSegment().getWay().equals(fromWay)
    86                                         && r.getRoute().getFirstSegment().getStart().equals(via)) {
    87                                 reverse = true;
    88                         } else if (r.getRoute().getLastSegment().getWay().equals(fromWay)
    89                                         && r.getRoute().getLastSegment().getEnd().equals(via)) {
    90                                 reverse = false;
    91                         } else {
    92                                 continue;
     47               
     48                throw new IllegalArgumentException("No via node or way(s).");
     49        }
     50       
     51        private static Set<Turn> loadWithViaWays(ModelContainer c, Relation r) {
     52                final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
     53                final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
     54               
     55                final List<Way> tmp = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA);
     56                final LinkedList<Road> via = new LinkedList<Road>();
     57               
     58                final Road.End fromRoadEnd = c.getJunction(Utils.lineUp(from, tmp.get(0))).getRoadEnd(from);
     59               
     60                Node n = fromRoadEnd.getJunction().getNode();
     61                final Iterator<Way> it = tmp.iterator();
     62                while (it.hasNext()) {
     63                        final Way w = it.next();
     64                        final Road v = c.getRoad(w);
     65                        via.add(v);
     66                        n = Utils.getOppositeEnd(w, n);
     67                       
     68                        if (!v.isPrimary()) {
     69                                throw new IllegalStateException("The road is not part of the junction.");
    9370                        }
    94 
    95                         for (int l : lanes) {
    96                                 result.add(new Turn(turns, r.getLane(reverse, l), junction, to,
    97                                                 fromWay, toWay));
    98                         }
    99                         for (int l : extraLanes) {
    100                                 result.add(new Turn(turns, r.getExtraLane(reverse, l),
    101                                                 junction, to, fromWay, toWay));
     71                       
     72                        final Iterator<Route.Segment> it2 = (v.getRoute().getFirstSegment().getWay().equals(w) ? v.getRoute()
     73                            .getSegments() : CollectionUtils.reverse(v.getRoute().getSegments())).iterator();
     74                        it2.next(); // first is done
     75                       
     76                        while (it2.hasNext()) {
     77                                final Way w2 = it2.next().getWay();
     78                                n = Utils.getOppositeEnd(w2, n);
     79                               
     80                                if (!it.hasNext() || !w2.equals(it.next())) {
     81                                        throw new IllegalStateException("The via ways of the relation do not form a road.");
     82                                }
    10283                        }
    10384                }
    104 
     85                final Road.End toRoadEnd = c.getJunction(n).getRoadEnd(to);
     86                n = Utils.getOppositeEnd(to, n);
     87               
     88                final Set<Turn> result = new HashSet<Turn>();
     89                for (int i : indices(r, Constants.TURN_KEY_LANES)) {
     90                        result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), via, toRoadEnd));
     91                }
     92                for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) {
     93                        result.add(new Turn(r, fromRoadEnd.getExtraLane(i), via, toRoadEnd));
     94                }
    10595                return result;
    10696        }
    107 
    108         static List<Integer> split(String lanes) {
    109                 if (lanes == null) {
     97       
     98        static List<Integer> indices(Relation r, String key) {
     99                final String joined = r.get(key);
     100               
     101                if (joined == null) {
    110102                        return new ArrayList<Integer>(1);
    111103                }
    112 
     104               
    113105                final List<Integer> result = new ArrayList<Integer>();
    114                 for (String lane : Constants.SPLIT_PATTERN.split(lanes)) {
     106                for (String lane : Constants.SPLIT_PATTERN.split(joined)) {
    115107                        result.add(Integer.parseInt(lane));
    116108                }
    117 
     109               
    118110                return result;
    119111        }
    120 
     112       
     113        private static Set<Turn> loadWithViaNode(ModelContainer c, Relation r) {
     114                final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
     115                final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA);
     116                final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
     117               
     118                final Junction j = c.getJunction(via);
     119               
     120                final Road.End fromRoadEnd = j.getRoadEnd(from);
     121                final Road.End toRoadEnd = j.getRoadEnd(to);
     122               
     123                final Set<Turn> result = new HashSet<Turn>();
     124                for (int i : indices(r, Constants.TURN_KEY_LANES)) {
     125                        result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), Collections.<Road> emptyList(), toRoadEnd));
     126                }
     127                for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) {
     128                        result.add(new Turn(r, fromRoadEnd.getExtraLane(i), Collections.<Road> emptyList(), toRoadEnd));
     129                }
     130                return result;
     131        }
     132       
    121133        static String join(List<Integer> list) {
    122134                if (list.isEmpty()) {
    123135                        return null;
    124136                }
    125 
    126                 final StringBuilder builder = new StringBuilder(list.size()
    127                                 * (2 + Constants.SEPARATOR.length()));
    128 
     137               
     138                final StringBuilder builder = new StringBuilder(list.size() * (2 + Constants.SEPARATOR.length()));
     139               
    129140                for (int e : list) {
    130141                        builder.append(e).append(Constants.SEPARATOR);
    131142                }
    132 
     143               
    133144                builder.setLength(builder.length() - Constants.SEPARATOR.length());
    134145                return builder.toString();
    135146        }
    136 
     147       
    137148        private final Relation relation;
    138 
     149       
    139150        private final Lane from;
    140         private final Junction via;
     151        private final List<Road> via;
    141152        private final Road.End to;
    142 
    143         private final Way fromWay;
    144         private final Way toWay;
    145 
    146         public Turn(Relation relation, Lane from, Junction via, Road.End to,
    147                         Way fromWay, Way toWay) {
     153       
     154        public Turn(Relation relation, Lane from, List<Road> via, Road.End to) {
    148155                this.relation = relation;
    149156                this.from = from;
    150157                this.via = via;
    151158                this.to = to;
    152                 this.fromWay = fromWay;
    153                 this.toWay = toWay;
    154159        }
    155 
     160       
    156161        public Lane getFrom() {
    157162                return from;
    158163        }
    159 
    160         public Junction getVia() {
     164       
     165        public List<Road> getVia() {
    161166                return via;
    162167        }
    163 
     168       
    164169        public Road.End getTo() {
    165170                return to;
    166171        }
    167 
     172       
    168173        Relation getRelation() {
    169174                return relation;
    170175        }
    171 
    172         Way getFromWay() {
    173                 return fromWay;
    174         }
    175 
    176         Way getToWay() {
    177                 return toWay;
    178         }
    179 
    180         void remove() {
    181                 final List<Integer> lanes = split(relation
    182                                 .get(Constants.TURN_KEY_LANES));
    183                 final List<Integer> extraLanes = split(relation
    184                                 .get(Constants.TURN_KEY_EXTRA_LANES));
    185 
    186                 if (lanes.size() + extraLanes.size() == 1
    187                                 && (from.isExtra() ^ !lanes.isEmpty())) {
     176       
     177        public void remove() {
     178                final List<Integer> lanes = indices(relation, Constants.TURN_KEY_LANES);
     179                final List<Integer> extraLanes = indices(relation, Constants.TURN_KEY_EXTRA_LANES);
     180               
     181                if (lanes.size() + extraLanes.size() == 1 && (from.isExtra() ^ !lanes.isEmpty())) {
    188182                        relation.getDataSet().removePrimitive(relation.getPrimitiveId());
    189183                } else if (from.isExtra()) {
     
    192186                        lanes.remove(Integer.valueOf(from.getIndex()));
    193187                }
    194 
    195                 relation.put(Constants.TURN_KEY_LANES, join(lanes));
    196                 relation.put(Constants.TURN_KEY_EXTRA_LANES, join(extraLanes));
     188               
     189                relation.put(Constants.TURN_KEY_LANES, lanes.isEmpty() ? null : join(lanes));
     190                relation.put(Constants.TURN_KEY_EXTRA_LANES, extraLanes.isEmpty() ? null : join(extraLanes));
    197191        }
    198192}
  • applications/editors/josm/plugins/turnlanes/src/org/openstreetmap/josm/plugins/turnlanes/model/Utils.java

    r25606 r25783  
    33import java.util.ArrayList;
    44import java.util.Arrays;
     5import java.util.Collection;
    56import java.util.Collections;
    67import java.util.HashSet;
     8import java.util.Iterator;
     9import java.util.LinkedList;
    710import java.util.List;
    811import java.util.Set;
    912
    1013import org.openstreetmap.josm.data.osm.Node;
     14import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1115import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    1216import org.openstreetmap.josm.data.osm.Relation;
    1317import org.openstreetmap.josm.data.osm.RelationMember;
    1418import org.openstreetmap.josm.data.osm.Way;
     19import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils;
    1520
    1621public class Utils {
     
    2429        }
    2530       
     31        public static final List<Way> filterRoads(List<OsmPrimitive> of) {
     32                final List<Way> result = new ArrayList<Way>();
     33               
     34                for (OsmPrimitive p : of) {
     35                        if (p.getType() == OsmPrimitiveType.WAY && Utils.isRoad((Way) p)) {
     36                                result.add((Way) p);
     37                        }
     38                }
     39               
     40                return result;
     41        }
     42       
    2643        public static Node getMemberNode(Relation r, String role) {
    2744                return getMember(r, role, OsmPrimitiveType.NODE).getNode();
    2845        }
    2946       
     47        public static Way getMemberWay(Relation r, String role) {
     48                return getMember(r, role, OsmPrimitiveType.WAY).getWay();
     49        }
     50       
    3051        public static RelationMember getMember(Relation r, String role, OsmPrimitiveType type) {
    3152                final List<RelationMember> candidates = getMembers(r, role, type);
    32                
    33                 if (candidates.size() == 0) {
     53                if (candidates.isEmpty()) {
    3454                        throw new IllegalStateException("No member with given role and type.");
    3555                } else if (candidates.size() > 1) {
    3656                        throw new IllegalStateException(candidates.size() + " members with given role and type.");
    3757                }
    38                
    3958                return candidates.get(0);
    4059        }
    4160       
    4261        public static List<RelationMember> getMembers(Relation r, String role, OsmPrimitiveType type) {
     62                final List<RelationMember> result = getMembers(r, role);
     63                for (RelationMember m : getMembers(r, role)) {
     64                        if (m.getType() != type) {
     65                                throw new IllegalArgumentException("Member has the specified role, but wrong type.");
     66                        }
     67                }
     68                return result;
     69        }
     70       
     71        public static List<RelationMember> getMembers(Relation r, String role) {
    4372                final List<RelationMember> result = new ArrayList<RelationMember>();
    44                
    4573                for (RelationMember m : r.getMembers()) {
    46                         if (m.getRole().equals(role) && m.getType() == type) {
     74                        if (m.getRole().equals(role)) {
    4775                                result.add(m);
    4876                        }
    4977                }
    50                
    51                 return result;
     78                return result;
     79        }
     80       
     81        public static List<Node> getMemberNodes(Relation r, String role) {
     82                return mapMembers(getMembers(r, role, OsmPrimitiveType.NODE), Node.class);
    5283        }
    5384       
    5485        public static List<Way> getMemberWays(Relation r, String role) {
    55                 final List<Way> result = new ArrayList<Way>();
    56                
    57                 for (RelationMember m : getMembers(r, role, OsmPrimitiveType.WAY)) {
    58                         result.add(m.getWay());
    59                 }
    60                
    61                 return result;
    62         }
    63        
    64         public static List<Node> getMemberNodes(Relation r, String role) {
    65                 final List<Node> result = new ArrayList<Node>();
    66                
    67                 for (RelationMember m : getMembers(r, role, OsmPrimitiveType.NODE)) {
    68                         result.add(m.getNode());
    69                 }
    70                
    71                 return result;
     86                return mapMembers(getMembers(r, role, OsmPrimitiveType.WAY), Way.class);
     87        }
     88       
     89        private static <T> List<T> mapMembers(List<RelationMember> ms, Class<T> t) {
     90                final List<T> result = new ArrayList<T>(ms.size());
     91                for (RelationMember m : ms) {
     92                        result.add(t.cast(m.getMember()));
     93                }
     94                return result;
     95        }
     96       
     97        /**
     98         *
     99         * @param a
     100         * @param b
     101         * @return the node at which {@code a} and {@code b} are connected
     102         */
     103        public static Node lineUp(Way a, Way b) {
     104                final Set<Node> s = new HashSet<Node>(Arrays.asList(a.firstNode(), a.lastNode(), b.firstNode(), b.lastNode()));
     105                if (a.firstNode() == a.lastNode() || b.firstNode().equals(b.lastNode()) || s.size() == 2) {
     106                        throw new IllegalArgumentException("Cycles are not allowed.");
     107                } else if (s.size() == 4) {
     108                        throw new IllegalArgumentException("Ways are not connected (at their first and last nodes).");
     109                }
     110               
     111                if (a.firstNode() == b.firstNode() || a.lastNode() == b.firstNode()) {
     112                        return b.firstNode();
     113                } else if (a.firstNode() == b.lastNode() || a.lastNode() == b.lastNode()) {
     114                        return b.lastNode();
     115                } else {
     116                        throw new AssertionError();
     117                }
    72118        }
    73119       
     
    86132                }
    87133        }
     134       
     135        /**
     136         * Orders the {@code ways} such that the combined ways out of each returned list form a path (in
     137         * order) from one node out of {@code nodes} to another out of {@code nodes}.
     138         *
     139         * <ul>
     140         * <li>Each way is used exactly once.</li>
     141         * <li>Paths contain no {@code nodes} excepting the first and last nodes.</li>
     142         * <li>Paths contain no loops w.r.t. the ways' first and last nodes</li>
     143         * </ul>
     144         *
     145         * @param ways
     146         *          ways to be ordered
     147         * @param nodes
     148         *          start/end nodes
     149         * @return
     150         * @throws IllegalArgumentException
     151         *           if the ways can't be ordered
     152         */
     153        public static List<Route> orderWays(Iterable<Way> ways, Iterable<Node> nodes) {
     154                final List<Way> ws = new LinkedList<Way>(CollectionUtils.toList(ways));
     155                final Set<Node> ns = new HashSet<Node>(CollectionUtils.toList(nodes));
     156               
     157                final List<Route> result = new ArrayList<Route>();
     158               
     159                while (!ws.isEmpty()) {
     160                        result.add(findPath(ws, ns));
     161                }
     162               
     163                return result;
     164        }
     165       
     166        private static Route findPath(List<Way> ws, Set<Node> ns) {
     167                final Way w = findPathSegment(ws, ns);
     168                final boolean first = ns.contains(w.firstNode());
     169                final boolean last = ns.contains(w.lastNode());
     170               
     171                if (first && last) {
     172                        return Route.create(Arrays.asList(w), w.firstNode());
     173                } else if (!first && !last) {
     174                        throw new AssertionError();
     175                }
     176               
     177                final List<Way> result = new ArrayList<Way>();
     178                result.add(w);
     179                Node n = first ? w.lastNode() : w.firstNode();
     180                while (true) {
     181                        final Way next = findPathSegment(ws, Arrays.asList(n));
     182                        result.add(next);
     183                        n = getOppositeEnd(next, n);
     184                       
     185                        if (ns.contains(n)) {
     186                                return Route.create(result, first ? w.firstNode() : w.lastNode());
     187                        }
     188                }
     189        }
     190       
     191        private static Way findPathSegment(List<Way> ws, Collection<Node> ns) {
     192                final Iterator<Way> it = ws.iterator();
     193               
     194                while (it.hasNext()) {
     195                        final Way w = it.next();
     196                       
     197                        if (ns.contains(w.firstNode()) || ns.contains(w.lastNode())) {
     198                                it.remove();
     199                                return w;
     200                        }
     201                }
     202               
     203                throw new IllegalArgumentException("Ways can't be ordered.");
     204        }
     205       
     206        public static Iterable<Way> flattenVia(Node start, List<Road> via, Node end) {
     207                final List<Way> result = new ArrayList<Way>();
     208               
     209                Node n = start;
     210                for (Road r : via) {
     211                        final Iterable<Route.Segment> segments = r.getRoute().getFirstSegment().getWay().isFirstLastNode(n) ? r
     212                            .getRoute().getSegments() : CollectionUtils.reverse(r.getRoute().getSegments());
     213                       
     214                        for (Route.Segment s : segments) {
     215                                result.add(s.getWay());
     216                                n = Utils.getOppositeEnd(s.getWay(), n);
     217                        }
     218                }
     219                if (!end.equals(n)) {
     220                        throw new IllegalArgumentException("The given via ways don't end at the given node.");
     221                }
     222               
     223                return result;
     224        }
    88225}
Note: See TracChangeset for help on using the changeset viewer.